Be2Jay Claude commited on
Commit
e39c21f
ยท
1 Parent(s): fd446ad

Set shrimp_detection_app as main app (app.py)

Browse files

- Backup original app.py to app_backup.py
- Copy shrimp_detection_app.py to app.py for deployment
- Main app now includes YOLOv8 model, VIDraft/Shrimp branding, and full features

๐Ÿค– Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

Files changed (2) hide show
  1. app.py +1055 -204
  2. app_backup.py +299 -0
app.py CHANGED
@@ -1,299 +1,1150 @@
1
  # -*- coding: utf-8 -*-
2
  """
3
- ๋ฐ”์šด๋”ฉ ๋ฐ•์Šค ๊ฒ€์ถœ ํ…Œ์ŠคํŠธ ํŽ˜์ด์ง€
4
- VIDraft/Shrimp ์ „์šฉ ๋ชจ๋ธ๊ณผ RT-DETR ๋ฒ”์šฉ ๋ชจ๋ธ์˜ ๊ฒ€์ถœ ๊ฒฐ๊ณผ ๋น„๊ต
 
5
  """
6
  import sys
7
  sys.stdout.reconfigure(encoding='utf-8')
8
 
9
  import gradio as gr
10
  from PIL import Image, ImageDraw, ImageFont
 
 
11
  import os
 
 
 
 
 
 
 
 
 
12
 
13
- # VIDraft/Shrimp ์ „์šฉ ๊ฒ€์ถœ๊ธฐ
14
- try:
15
- from inference_sdk import InferenceHTTPClient, InferenceConfiguration
 
 
 
 
 
16
 
17
- vidraft_client = InferenceHTTPClient(
18
- api_url="https://serverless.roboflow.com",
19
- api_key="azcIL8KDJVJMYrsERzI7"
20
- )
21
- VIDRAFT_AVAILABLE = True
22
- print("โœ… VIDraft/Shrimp ๋ชจ๋ธ ์‚ฌ์šฉ ๊ฐ€๋Šฅ")
23
- except Exception as e:
24
- VIDRAFT_AVAILABLE = False
25
- print(f"โŒ VIDraft/Shrimp ๋ชจ๋ธ ์‚ฌ์šฉ ๋ถˆ๊ฐ€: {e}")
26
 
27
- def detect_with_vidraft(image, confidence, iou_threshold):
28
- """VIDraft/Shrimp ์ „์šฉ ๋ชจ๋ธ๋กœ ๊ฒ€์ถœ"""
29
- if not VIDRAFT_AVAILABLE:
30
- return None, "โŒ VIDraft/Shrimp ๋ชจ๋ธ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค."
 
31
 
32
- if image is None:
33
- return None, "โš ๏ธ ์ด๋ฏธ์ง€๋ฅผ ์—…๋กœ๋“œํ•˜์„ธ์š”."
 
 
 
 
 
 
34
 
 
 
35
  try:
36
- # ์ž„์‹œ ํŒŒ์ผ๋กœ ์ €์žฅ
37
- import tempfile
38
- with tempfile.NamedTemporaryFile(suffix='.jpg', delete=False) as tmp:
39
- if image.mode != 'RGB':
40
- image = image.convert('RGB')
41
- image.save(tmp.name, quality=95)
42
- tmp_path = tmp.name
43
-
44
- # API ํ˜ธ์ถœ with configuration
45
- custom_config = InferenceConfiguration(
46
- confidence_threshold=confidence,
47
- iou_threshold=iou_threshold
48
  )
49
 
50
- with vidraft_client.use_configuration(custom_config):
51
- result = vidraft_client.infer(tmp_path, model_id="shrimp-konvey/2")
 
 
 
 
52
 
53
- # ์ž„์‹œ ํŒŒ์ผ ์‚ญ์ œ
54
- os.unlink(tmp_path)
 
 
55
 
56
- # ๊ฒฐ๊ณผ ๊ทธ๋ฆฌ๊ธฐ
57
- img = image.copy()
58
- draw = ImageDraw.Draw(img)
59
 
60
- try:
61
- font = ImageFont.truetype("arial.ttf", 14)
62
- except:
63
- font = ImageFont.load_default()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
64
 
65
- predictions = result["predictions"]
66
- detected_count = 0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
67
 
68
  for pred in predictions:
69
- if pred["confidence"] < confidence:
 
 
70
  continue
71
 
72
- detected_count += 1
 
 
 
73
 
74
- x = pred["x"]
75
- y = pred["y"]
76
- w = pred["width"]
77
- h = pred["height"]
78
- conf = pred["confidence"]
79
 
80
- # ๋ฐ”์šด๋”ฉ ๋ฐ•์Šค ์ขŒํ‘œ
81
- x1 = x - w/2
82
- y1 = y - h/2
83
- x2 = x + w/2
84
- y2 = y + h/2
85
 
86
- # ์‹ ๋ขฐ๋„์— ๋”ฐ๋ผ ์ƒ‰์ƒ
87
- if conf > 0.8:
88
- color = "lime"
89
- elif conf > 0.6:
90
- color = "orange"
91
- else:
92
- color = "yellow"
93
 
94
- # ๋ฐ•์Šค ๊ทธ๋ฆฌ๊ธฐ
95
- draw.rectangle([x1, y1, x2, y2], outline=color, width=3)
 
 
 
96
 
97
- # ๋ผ๋ฒจ
98
- label = f"#{detected_count} {conf:.0%}"
99
- bbox = draw.textbbox((x1, y1 - 25), label, font=font)
100
- draw.rectangle(bbox, fill=color)
101
- draw.text((x1, y1 - 25), label, fill="black", font=font)
102
 
103
- # ํ—ค๋”
104
- header = f"VIDraft/Shrimp: {detected_count}๋งˆ๋ฆฌ ๊ฒ€์ถœ"
105
- header_bbox = draw.textbbox((10, 10), header, font=font)
106
- draw.rectangle([5, 5, header_bbox[2]+10, header_bbox[3]+10], fill="black", outline="lime", width=2)
107
- draw.text((10, 10), header, fill="lime", font=font)
108
 
109
- info = f"""
110
- ### ๐Ÿ“Š VIDraft/Shrimp ๋ชจ๋ธ ๊ฒ€์ถœ ๊ฒฐ๊ณผ
 
 
 
111
 
112
- - **๊ฒ€์ถœ ์ˆ˜**: {detected_count}๋งˆ๋ฆฌ
113
- - **์ „์ฒด ์˜ˆ์ธก**: {len(predictions)}๊ฐœ
114
- - **์‹ ๋ขฐ๋„ ์ž„๊ณ„๊ฐ’**: {confidence:.0%}
115
- - **IoU ์ž„๊ณ„๊ฐ’**: {iou_threshold:.0%}
116
- - **์ฒ˜๋ฆฌ ์‹œ๊ฐ„**: {result['time']:.2f}์ดˆ
117
- """
 
 
 
 
 
118
 
119
- return img, info
120
 
121
- except Exception as e:
122
- return None, f"โŒ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: {str(e)}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
123
 
124
- def detect_with_rtdetr(image, confidence):
125
- """RT-DETR๋กœ ๊ฒ€์ถœ (๊ฐ„๋‹จ ๋ฒ„์ „)"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
126
  if image is None:
127
  return None, "โš ๏ธ ์ด๋ฏธ์ง€๋ฅผ ์—…๋กœ๋“œํ•˜์„ธ์š”."
128
 
129
  try:
130
- from transformers import RTDetrForObjectDetection, RTDetrImageProcessor
131
- import torch
132
-
133
- # ๋ชจ๋ธ ๋กœ๋“œ (์บ์‹œ ์‚ฌ์šฉ)
134
- if not hasattr(detect_with_rtdetr, 'model'):
135
- print("๐Ÿ”„ RT-DETR ๋ชจ๋ธ ๋กœ๋”ฉ ์ค‘...")
136
- processor = RTDetrImageProcessor.from_pretrained("PekingU/rtdetr_r50vd_coco_o365")
137
- model = RTDetrForObjectDetection.from_pretrained("PekingU/rtdetr_r50vd_coco_o365")
138
- model.eval()
139
- detect_with_rtdetr.processor = processor
140
- detect_with_rtdetr.model = model
141
- print("โœ… RT-DETR ๋กœ๋”ฉ ์™„๋ฃŒ")
142
-
143
- processor = detect_with_rtdetr.processor
144
- model = detect_with_rtdetr.model
145
-
146
- # ์ถ”๋ก 
147
- inputs = processor(images=image, return_tensors="pt")
148
- with torch.no_grad():
149
- outputs = model(**inputs)
150
-
151
- target_sizes = torch.tensor([image.size[::-1]])
152
- results = processor.post_process_object_detection(
153
- outputs,
154
- target_sizes=target_sizes,
155
- threshold=confidence
156
- )[0]
157
-
158
- # ๊ฒฐ๊ณผ ๊ทธ๋ฆฌ๊ธฐ
159
  img = image.copy()
160
  draw = ImageDraw.Draw(img)
161
 
162
  try:
163
  font = ImageFont.truetype("arial.ttf", 14)
 
 
164
  except:
165
  font = ImageFont.load_default()
 
 
166
 
167
- detected_count = len(results["scores"])
 
 
 
 
168
 
169
- for idx, (score, label, box) in enumerate(zip(results["scores"], results["labels"], results["boxes"]), 1):
170
- x1, y1, x2, y2 = box.tolist()
171
- conf = score.item()
172
 
173
- # ์ƒ‰์ƒ
174
- if conf > 0.8:
175
- color = "cyan"
176
- elif conf > 0.6:
177
- color = "magenta"
178
- else:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
179
  color = "yellow"
 
 
180
 
181
- # ๋ฐ•์Šค
182
- draw.rectangle([x1, y1, x2, y2], outline=color, width=3)
183
 
184
  # ๋ผ๋ฒจ
185
- label_text = f"#{idx} {conf:.0%}"
186
- bbox = draw.textbbox((x1, y1 - 25), label_text, font=font)
187
  draw.rectangle(bbox, fill=color)
188
- draw.text((x1, y1 - 25), label_text, fill="black", font=font)
 
 
 
 
189
 
190
  # ํ—ค๋”
191
- header = f"RT-DETR: {detected_count}๊ฐœ ๊ฒ€์ถœ"
192
- header_bbox = draw.textbbox((10, 10), header, font=font)
193
- draw.rectangle([5, 5, header_bbox[2]+10, header_bbox[3]+10], fill="black", outline="cyan", width=2)
194
- draw.text((10, 10), header, fill="cyan", font=font)
 
195
 
 
196
  info = f"""
197
- ### ๐Ÿ“Š RT-DETR ๋ฒ”์šฉ ๋ชจ๋ธ ๊ฒ€์ถœ ๊ฒฐ๊ณผ
 
 
 
 
 
 
198
 
199
- - **๊ฒ€์ถœ ์ˆ˜**: {detected_count}๊ฐœ
200
- - **์‹ ๋ขฐ๋„ ์ž„๊ณ„๊ฐ’**: {confidence:.0%}
201
 
202
- โš ๏ธ **์ฐธ๊ณ **: RT-DETR์€ ๋ฒ”์šฉ ๊ฐ์ฒด ๊ฒ€์ถœ ๋ชจ๋ธ์ž…๋‹ˆ๋‹ค. ์ƒˆ์šฐ ๊ฒ€์ถœ์€ VIDraft/Shrimp ๋ชจ๋ธ์„ ์‚ฌ์šฉํ•˜์„ธ์š”.
203
- """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
204
 
205
  return img, info
206
 
207
  except Exception as e:
208
- return None, f"โŒ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: {str(e)}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
209
 
210
- # Gradio ์ธํ„ฐํŽ˜์ด์Šค
211
- with gr.Blocks(title="๐Ÿฆ ๋ฐ”์šด๋”ฉ ๋ฐ•์Šค ๊ฒ€์ถœ ํ…Œ์ŠคํŠธ", theme=gr.themes.Soft()) as demo:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
212
 
213
  gr.Markdown("""
214
- # ๐Ÿฆ ๋ฐ”์šด๋”ฉ ๋ฐ•์Šค ๊ฒ€์ถœ ๋น„๊ต ํ…Œ์ŠคํŠธ
215
 
216
- VIDraft/Shrimp ์ „์šฉ ๋ชจ๋ธ๊ณผ RT-DETR ๋ฒ”์šฉ ๋ชจ๋ธ์˜ ๊ฒ€์ถœ ์„ฑ๋Šฅ์„ ๋น„๊ตํ•ฉ๋‹ˆ๋‹ค.
217
 
218
  ---
219
  """)
220
 
 
221
  with gr.Row():
222
- with gr.Column():
223
- input_image = gr.Image(label="์ž…๋ ฅ ์ด๋ฏธ์ง€", type="pil")
224
- confidence_slider = gr.Slider(
225
- 0.1, 0.9, 0.5,
226
- label="์‹ ๋ขฐ๋„ ์ž„๊ณ„๊ฐ’ (Confidence)",
227
- info="๋‚ฎ์„์ˆ˜๋ก ๋” ๋งŽ์ด ๊ฒ€์ถœ"
228
  )
229
- iou_slider = gr.Slider(
230
- 0.1, 0.9, 0.5,
231
- label="IoU ์ž„๊ณ„๊ฐ’ (Overlap)",
232
- info="๊ฒน์น˜๋Š” ๋ฐ•์Šค ์ œ๊ฑฐ ๊ธฐ์ค€ (๋†’์„์ˆ˜๋ก ๋” ๋งŽ์ด ์œ ์ง€)"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
233
  )
234
 
235
- # ์˜ˆ์ œ ์ด๋ฏธ์ง€
236
- gr.Examples(
237
- examples=[
238
- ["imgs/test_shrimp_tank.png", 0.1, 0.1],
239
- ],
240
- inputs=[input_image, confidence_slider, iou_slider],
241
- label="๐Ÿ“ท ์˜ˆ์ œ ์ด๋ฏธ์ง€ (ํด๋ฆญํ•˜์—ฌ ๋ฐ”๋กœ ํ…Œ์ŠคํŠธ)"
 
242
  )
243
 
244
- with gr.Column():
245
- gr.Markdown("### ๐Ÿ“ ์‚ฌ์šฉ ๋ฐฉ๋ฒ•")
246
  gr.Markdown("""
247
- 1. **์•„๋ž˜ ์˜ˆ์ œ ์ด๋ฏธ์ง€๋ฅผ ํด๋ฆญ**ํ•˜๊ฑฐ๋‚˜ ์ง์ ‘ ์—…๋กœ๋“œ
248
- 2. ํŒŒ๋ผ๋ฏธํ„ฐ ์กฐ์ •:
249
- - **Confidence**: ๊ฒ€์ถœ ์‹ ๋ขฐ๋„ (๋‚ฎ์„์ˆ˜๋ก ๋” ๋งŽ์ด ๊ฒ€์ถœ)
250
- - **IoU**: ์ค‘๋ณต ๋ฐ•์Šค ์ œ๊ฑฐ ๊ธฐ์ค€ (NMS)
251
- 3. ๋ฒ„ํŠผ ํด๋ฆญํ•˜์—ฌ ๊ฒ€์ถœ
252
-
253
- **์ƒ‰์ƒ ์˜๋ฏธ:**
254
- - **๋…น์ƒ‰/์ฒญ๋ก**: ๋†’์€ ์‹ ๋ขฐ๋„ (>80%)
255
- - **์ฃผํ™ฉ/์žํ™**: ์ค‘๊ฐ„ ์‹ ๋ขฐ๋„ (60-80%)
256
- - **๋…ธ๋ž€์ƒ‰**: ๋‚ฎ์€ ์‹ ๋ขฐ๋„ (<60%)
257
  """)
258
 
259
- with gr.Tabs():
260
- with gr.TabItem("๐Ÿค– VIDraft/Shrimp (์ƒˆ์šฐ ์ „์šฉ)"):
261
- vidraft_btn = gr.Button("๐Ÿš€ VIDraft/Shrimp ๋ชจ๋ธ๋กœ ๊ฒ€์ถœ", variant="primary", size="lg")
262
- vidraft_result = gr.Image(label="๊ฒ€์ถœ ๊ฒฐ๊ณผ")
263
- vidraft_info = gr.Markdown()
264
-
265
- with gr.TabItem("๐Ÿ” RT-DETR (๋ฒ”์šฉ)"):
266
- rtdetr_btn = gr.Button("๐Ÿš€ RT-DETR๋กœ ๊ฒ€์ถœ", variant="secondary", size="lg")
267
- rtdetr_result = gr.Image(label="๊ฒ€์ถœ ๊ฒฐ๊ณผ")
268
- rtdetr_info = gr.Markdown()
269
-
270
- # ์ด๋ฒคํŠธ ์—ฐ๊ฒฐ
271
- vidraft_btn.click(
272
- detect_with_vidraft,
273
- [input_image, confidence_slider, iou_slider],
274
- [vidraft_result, vidraft_info]
275
- )
276
 
277
- rtdetr_btn.click(
278
- detect_with_rtdetr,
279
- [input_image, confidence_slider],
280
- [rtdetr_result, rtdetr_info]
281
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
282
 
283
  gr.Markdown("""
284
  ---
285
 
286
- ### ๐Ÿ’ก ํŒ
 
 
 
287
 
288
- - **์ˆ˜์กฐ ์ด๋ฏธ์ง€**: VIDraft/Shrimp ๋ชจ๋ธ์ด ํ›จ์”ฌ ์ •ํ™•ํ•ฉ๋‹ˆ๋‹ค (์ƒˆ์šฐ ์ „์šฉ ํ•™์Šต)
289
- - **์ธก์ •์šฉ ์ด๋ฏธ์ง€**: RT-DETR ๋ฒ”์šฉ ๋ชจ๋ธ์„ ์‚ฌ์šฉํ•˜์„ธ์š”
290
- - **๊ฒ€์ถœ ์•ˆ ๋จ**: ์‹ ๋ขฐ๋„๋ฅผ ๋‚ฎ์ถฐ๋ณด์„ธ์š” (0.3~0.4)
291
- - **์ค‘๋ณต ๋ฐ•์Šค**: IoU ์ž„๊ณ„๊ฐ’์„ ์กฐ์ •ํ•˜์„ธ์š” (VIDraft/Shrimp ๋ชจ๋ธ๋งŒ)
292
  """)
293
 
294
  if __name__ == "__main__":
 
 
 
 
 
 
 
 
 
 
295
  demo.launch(
296
  server_name="0.0.0.0",
297
- server_port=7860, # Hugging Face default port
298
  share=False
299
  )
 
1
  # -*- coding: utf-8 -*-
2
  """
3
+ ๐Ÿฆ ์ƒˆ์šฐ ๊ฒ€์ถœ ํ†ตํ•ฉ ์‹œ์Šคํ…œ
4
+ 3๊ฐœ์˜ ์•ฑ์„ ํ•˜๋‚˜๋กœ ํ†ตํ•ฉ: ์ž๋™ ๊ฒ€์ถœ, ๋ผ๋ฒจ๋ง ๋„๊ตฌ, ๋ฐ๋ชจ
5
+ RT-DETR ๋˜๋Š” VIDraft/Shrimp ํด๋ผ์šฐ๋“œ ๋ชจ๋ธ ์„ ํƒ ๊ฐ€๋Šฅ
6
  """
7
  import sys
8
  sys.stdout.reconfigure(encoding='utf-8')
9
 
10
  import gradio as gr
11
  from PIL import Image, ImageDraw, ImageFont
12
+ import numpy as np
13
+ import json
14
  import os
15
+ import glob
16
+ from datetime import datetime
17
+ import torch
18
+ from transformers import RTDetrForObjectDetection, RTDetrImageProcessor
19
+ import requests
20
+ import base64
21
+ from io import BytesIO
22
+ from inference_sdk import InferenceHTTPClient
23
+ import tempfile
24
 
25
+ # test_visual_validation์—์„œ ๊ฐ€์ ธ์˜ค๊ธฐ (์ง€์—ฐ import๋กœ ๋ณ€๊ฒฝ)
26
+ # from test_visual_validation import (
27
+ # load_rtdetr_model,
28
+ # detect_with_rtdetr,
29
+ # apply_universal_filter,
30
+ # calculate_morphological_features,
31
+ # calculate_visual_features
32
+ # )
33
 
34
+ # YOLOv8 import
35
+ from ultralytics import YOLO
 
 
 
 
 
 
 
36
 
37
+ # ============================================================
38
+ # YOLOv8 ๋ชจ๋ธ ์„ค์ •
39
+ # ============================================================
40
+ YOLO_MODEL_PATH = "runs/train/yolov8m_shrimp2/weights/best.pt"
41
+ yolo_model = None
42
 
43
+ def load_yolo_model():
44
+ """YOLOv8 ๋ชจ๋ธ ๋กœ๋”ฉ"""
45
+ global yolo_model
46
+ if yolo_model is None:
47
+ print(f"๐Ÿ”„ YOLOv8 ๋ชจ๋ธ ๋กœ๋”ฉ ์ค‘: {YOLO_MODEL_PATH}")
48
+ yolo_model = YOLO(YOLO_MODEL_PATH)
49
+ print("โœ… YOLOv8 ๋ชจ๋ธ ๋กœ๋”ฉ ์™„๋ฃŒ")
50
+ return yolo_model
51
 
52
+ def detect_with_yolo(image, confidence=0.1):
53
+ """YOLOv8 ๋ชจ๋ธ๋กœ ๊ฒ€์ถœ"""
54
  try:
55
+ model = load_yolo_model()
56
+
57
+ # ์ถ”๋ก  ์‹คํ–‰
58
+ results = model.predict(
59
+ source=image,
60
+ conf=confidence,
61
+ verbose=False
 
 
 
 
 
62
  )
63
 
64
+ detections = []
65
+ for result in results:
66
+ boxes = result.boxes
67
+ for box in boxes:
68
+ x1, y1, x2, y2 = box.xyxy[0].tolist()
69
+ conf = box.conf[0].item()
70
 
71
+ detections.append({
72
+ 'bbox': [x1, y1, x2, y2],
73
+ 'confidence': conf
74
+ })
75
 
76
+ print(f"โœ… YOLOv8 ๊ฒ€์ถœ ์™„๋ฃŒ: {len(detections)}๊ฐœ")
77
+ return detections
 
78
 
79
+ except Exception as e:
80
+ print(f"โŒ YOLOv8 ๊ฒ€์ถœ ์˜ค๋ฅ˜: {str(e)}")
81
+ import traceback
82
+ traceback.print_exc()
83
+ return []
84
+
85
+ # ============================================================
86
+ # Roboflow SDK ์„ค์ • (์ตœ์ ํ™”๋œ ๋ฐฉ์‹)
87
+ # ============================================================
88
+ ROBOFLOW_API_KEY = "azcIL8KDJVJMYrsERzI7"
89
+
90
+ # Roboflow Inference SDK ํด๋ผ์ด์–ธํŠธ (connection pooling ์ง€์›)
91
+ roboflow_client = InferenceHTTPClient(
92
+ api_url="https://serverless.roboflow.com",
93
+ api_key=ROBOFLOW_API_KEY
94
+ )
95
+
96
+ def detect_with_roboflow(image, confidence=0.065):
97
+ """Roboflow API๋ฅผ ์‚ฌ์šฉํ•œ ์ตœ์ ํ™”๋œ ๊ฒ€์ถœ (๋กœ์ปฌ ํ…Œ์ŠคํŠธ์™€ ๋™์ผ)"""
98
+ try:
99
+ # ์›๋ณธ ์ด๋ฏธ์ง€ ๋ณด์กด
100
+ image_original = image
101
+ original_size = image_original.size
102
+
103
+ # ๋ฆฌ์‚ฌ์ด์ฆˆ (API ์ „์†ก์šฉ)
104
+ image_resized = image_original.copy()
105
+ image_resized.thumbnail((640, 640), Image.Resampling.LANCZOS)
106
+ print(f"๐Ÿ“ ์ด๋ฏธ์ง€ ๋ฆฌ์‚ฌ์ด์ฆˆ: {original_size} โ†’ {image_resized.size}")
107
 
108
+ # Base64 ์ธ์ฝ”๋”ฉ
109
+ buffered = BytesIO()
110
+ image_resized.save(buffered, format="JPEG", quality=80)
111
+ img_base64 = base64.b64encode(buffered.getvalue()).decode()
112
+ print(f"๐Ÿ“ฆ Base64 ํฌ๊ธฐ: {len(img_base64)} bytes")
113
+
114
+ print(f"๐Ÿ”„ Roboflow API ์ถ”๋ก  ์‹œ์ž‘...")
115
+
116
+ # ๐Ÿš€ ์ตœ์ ํ™” 3: requests๋กœ API ํ˜ธ์ถœ (SDK ๋Œ€์‹  ์‚ฌ์šฉ - ๋” ์•ˆ์ •์ )
117
+ response = requests.post(
118
+ 'https://serverless.roboflow.com/vidraft/workflows/find-shrimp-6',
119
+ headers={'Content-Type': 'application/json'},
120
+ json={
121
+ 'api_key': ROBOFLOW_API_KEY,
122
+ 'inputs': {
123
+ 'image': {'type': 'base64', 'value': img_base64}
124
+ }
125
+ },
126
+ timeout=30
127
+ )
128
+
129
+ if response.status_code != 200:
130
+ print(f"โŒ Roboflow API ์˜ค๋ฅ˜: {response.status_code}")
131
+ print(f"์‘๋‹ต: {response.text}")
132
+ return []
133
+
134
+ result = response.json()
135
+ print(f"๐Ÿ” Roboflow ์‘๋‹ต: {json.dumps(result, indent=2, ensure_ascii=False)[:500]}...")
136
+
137
+ # Workflow ์‘๋‹ต ๊ตฌ์กฐ ํŒŒ์‹ฑ
138
+ detections = []
139
+ predictions = []
140
+
141
+ # ๋ฐฉ๋ฒ• 1: outputs[0].predictions.predictions (workflow ํ˜•ํƒœ)
142
+ if isinstance(result, dict) and 'outputs' in result and len(result['outputs']) > 0:
143
+ output = result['outputs'][0]
144
+ if isinstance(output, dict) and 'predictions' in output:
145
+ pred_data = output['predictions']
146
+ # predictions๊ฐ€ dict์ด๊ณ  ๊ทธ ์•ˆ์— predictions ๋ฐฐ์—ด์ด ์žˆ๋Š” ๊ฒฝ์šฐ
147
+ if isinstance(pred_data, dict) and 'predictions' in pred_data:
148
+ predictions = pred_data['predictions']
149
+ # predictions๊ฐ€ ๋ฐ”๋กœ ๋ฐฐ์—ด์ธ ๊ฒฝ์šฐ
150
+ elif isinstance(pred_data, list):
151
+ predictions = pred_data
152
+ else:
153
+ predictions = [pred_data]
154
+
155
+ # ๋ฐฉ๋ฒ• 2: ์ง์ ‘ predictions
156
+ elif isinstance(result, dict) and 'predictions' in result:
157
+ predictions = result['predictions']
158
+
159
+ # ๋ฐฉ๋ฒ• 3: ๋‹ค๋ฅธ ๊ตฌ์กฐ
160
+ elif isinstance(result, list):
161
+ predictions = result
162
+
163
+ print(f"๐Ÿ“ฆ ์ฐพ์€ predictions: {len(predictions)}๊ฐœ")
164
+
165
+ # ์Šค์ผ€์ผ ๊ณ„์‚ฐ (๋ฆฌ์‚ฌ์ด์ฆˆ๋œ ์ขŒํ‘œ โ†’ ์›๋ณธ ์ขŒํ‘œ)
166
+ scale_x = original_size[0] / image_resized.size[0]
167
+ scale_y = original_size[1] / image_resized.size[1]
168
+ print(f"๐Ÿ“ ์Šค์ผ€์ผ: x={scale_x:.2f}, y={scale_y:.2f}")
169
 
170
  for pred in predictions:
171
+ # ํด๋ž˜์Šค ํ•„ํ„ฐ๋ง (shrimp๋งŒ ๊ฒ€์ถœ)
172
+ pred_class = pred.get('class', '')
173
+ if pred_class != 'shrimp':
174
  continue
175
 
176
+ # ์‹ ๋ขฐ๋„ ํ•„ํ„ฐ๋ง
177
+ pred_confidence = pred.get('confidence', 0)
178
+ if pred_confidence < confidence:
179
+ continue
180
 
181
+ # ๋ฐ”์šด๋”ฉ ๋ฐ•์Šค ์ถ”์ถœ (๋ฆฌ์‚ฌ์ด์ฆˆ๋œ ์ขŒํ‘œ)
182
+ x = pred.get('x', 0)
183
+ y = pred.get('y', 0)
184
+ width = pred.get('width', 0)
185
+ height = pred.get('height', 0)
186
 
187
+ # ์›๋ณธ ํฌ๊ธฐ๋กœ ์Šค์ผ€์ผ ๋ณ€ํ™˜
188
+ x_scaled = x * scale_x
189
+ y_scaled = y * scale_y
190
+ width_scaled = width * scale_x
191
+ height_scaled = height * scale_y
192
 
193
+ # ์ค‘์‹ฌ์  ์ขŒํ‘œ๋ฅผ ์ขŒ์ƒ๋‹จ/์šฐํ•˜๋‹จ ์ขŒํ‘œ๋กœ ๋ณ€ํ™˜
194
+ x1 = x_scaled - width_scaled / 2
195
+ y1 = y_scaled - height_scaled / 2
196
+ x2 = x_scaled + width_scaled / 2
197
+ y2 = y_scaled + height_scaled / 2
 
 
198
 
199
+ detections.append({
200
+ 'bbox': [x1, y1, x2, y2],
201
+ 'confidence': pred_confidence
202
+ })
203
+ print(f" โœ“ ๊ฒ€์ถœ (shrimp): conf={pred_confidence:.2%}, bbox=[{x1:.0f},{y1:.0f},{x2:.0f},{y2:.0f}]")
204
 
205
+ print(f"โœ… Roboflow ๊ฒ€์ถœ ์™„๋ฃŒ: {len(detections)}๊ฐœ")
206
+ return detections
 
 
 
207
 
208
+ except Exception as e:
209
+ print(f"โŒ Roboflow SDK ์˜ค๋ฅ˜: {str(e)}")
210
+ import traceback
211
+ traceback.print_exc()
212
+ return []
213
 
214
+ # ============================================================
215
+ # ์ „์—ญ ๋ชจ๋ธ ๋ณ€์ˆ˜ (์ง€์—ฐ ๋กœ๋”ฉ)
216
+ # ============================================================
217
+ processor = None
218
+ model = None
219
 
220
+ def load_rtdetr_on_demand():
221
+ """RT-DETR ๋ชจ๋ธ์„ ํ•„์š”์‹œ์—๋งŒ ๋กœ๋”ฉ"""
222
+ global processor, model
223
+ if processor is None or model is None:
224
+ print("๐Ÿ”„ RT-DETR ๋ชจ๋ธ ๋กœ๋”ฉ ์ค‘...")
225
+ from test_visual_validation import load_rtdetr_model
226
+ processor, model = load_rtdetr_model()
227
+ print("โœ… RT-DETR ๋กœ๋”ฉ ์™„๋ฃŒ")
228
+ return "โœ… RT-DETR ๋ชจ๋ธ ๋กœ๋”ฉ ์™„๋ฃŒ"
229
+ else:
230
+ return "โ„น๏ธ RT-DETR ๋ชจ๋ธ์ด ์ด๋ฏธ ๋กœ๋”ฉ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค"
231
 
232
+ print("โœ… VIDraft/Shrimp ํด๋ผ์šฐ๋“œ ๋ชจ๋ธ ์‚ฌ์šฉ ๊ฐ€๋Šฅ\n")
233
 
234
+ # ============================================================
235
+ # ๋ผ๋ฒจ๋ง ๋„๊ตฌ ์ „์—ญ ๋ณ€์ˆ˜
236
+ # ============================================================
237
+ current_data = {
238
+ 'folder': None,
239
+ 'images': [],
240
+ 'current_idx': 0,
241
+ 'detections': {},
242
+ 'selections': {},
243
+ 'confidence_threshold': 0.2,
244
+ 'image_cache': {},
245
+ 'model_type': 'RT-DETR' # ํ˜„์žฌ ์„ ํƒ๋œ ๋ชจ๋ธ
246
+ }
247
+
248
+ GROUND_TRUTH_FILE = "ground_truth.json"
249
+ DATA_BASE = "data/ํฐ๋‹ค๋ฆฌ์ƒˆ์šฐ ์‹ค์ธก ๋ฐ์ดํ„ฐ_์ตํˆฌ์Šค์—์ด์•„์ด(์ฃผ)"
250
+
251
+ # ============================================================
252
+ # ๋ชจ๋ธ๋ณ„ ๊ฒ€์ถœ ํ•จ์ˆ˜
253
+ # ============================================================
254
 
255
+ def detect_with_selected_model(image, confidence, model_type):
256
+ """์„ ํƒ๋œ ๋ชจ๋ธ๋กœ ๊ฒ€์ถœ"""
257
+ if model_type == "RT-DETR":
258
+ if processor is None or model is None:
259
+ raise ValueError("โš ๏ธ RT-DETR ๋ชจ๋ธ์ด ๋กœ๋”ฉ๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. '๐Ÿ”„ RT-DETR ๋กœ๋“œ' ๋ฒ„ํŠผ์„ ๋จผ์ € ํด๋ฆญํ•˜์„ธ์š”.")
260
+ from test_visual_validation import detect_with_rtdetr
261
+ return detect_with_rtdetr(image, processor, model, confidence)
262
+ elif model_type == "VIDraft/Shrimp":
263
+ return detect_with_roboflow(image, confidence)
264
+ elif model_type == "YOLOv8":
265
+ return detect_with_yolo(image, confidence)
266
+ else:
267
+ return []
268
+
269
+ # ============================================================
270
+ # ํƒญ 1: ์ž๋™ ๊ฒ€์ถœ (Interactive Validation)
271
+ # ============================================================
272
+
273
+ def interactive_detect(image, confidence, filter_threshold, show_all, model_type, use_filter):
274
+ """๋Œ€ํ™”ํ˜• ๊ฒ€์ถœ"""
275
  if image is None:
276
  return None, "โš ๏ธ ์ด๋ฏธ์ง€๋ฅผ ์—…๋กœ๋“œํ•˜์„ธ์š”."
277
 
278
  try:
279
+ # ์„ ํƒ๋œ ๋ชจ๋ธ๋กœ ๊ฒ€์ถœ
280
+ all_detections = detect_with_selected_model(image, confidence, model_type)
281
+
282
+ # ํ•„ํ„ฐ ์ ์šฉ ์—ฌ๋ถ€์— ๋”ฐ๋ผ ์ฒ˜๋ฆฌ
283
+ if not use_filter:
284
+ # ํ•„ํ„ฐ ๋ฏธ์‚ฌ์šฉ: ์‹ ๋ขฐ๋„๋งŒ ์ ์šฉ
285
+ filtered_detections = all_detections
286
+ for det in filtered_detections:
287
+ det['filter_score'] = det['confidence'] * 100
288
+ det['filter_reasons'] = [f"์‹ ๋ขฐ๋„: {det['confidence']:.0%} (ํ•„ํ„ฐ ๋ฏธ์‚ฌ์šฉ)"]
289
+ all_detections_scored = filtered_detections
290
+ else:
291
+ # ํ•„ํ„ฐ ์‚ฌ์šฉ
292
+ if model_type in ["VIDraft/Shrimp", "YOLOv8"]:
293
+ # Roboflow & YOLOv8: ์‹ ๋ขฐ๋„๋ฅผ ํ•„ํ„ฐ ์ ์ˆ˜๋กœ ์‚ฌ์šฉ
294
+ for det in all_detections:
295
+ det['filter_score'] = det['confidence'] * 100
296
+ det['filter_reasons'] = [f"{model_type} ์‹ ๋ขฐ๋„: {det['confidence']:.0%}"]
297
+ all_detections_scored = all_detections
298
+ else:
299
+ # RT-DETR: Universal Filter ์‚ฌ์šฉ
300
+ from test_visual_validation import apply_universal_filter
301
+ all_detections_scored = apply_universal_filter(all_detections, image, threshold=0)
302
+
303
+ # ํ•„ํ„ฐ ์ž„๊ณ„๊ฐ’ ์ ์šฉ
304
+ filtered_detections = [det for det in all_detections_scored if det['filter_score'] >= filter_threshold]
305
+
306
+ # ์‹œ๊ฐํ™”
 
307
  img = image.copy()
308
  draw = ImageDraw.Draw(img)
309
 
310
  try:
311
  font = ImageFont.truetype("arial.ttf", 14)
312
+ font_large = ImageFont.truetype("arial.ttf", 18)
313
+ font_small = ImageFont.truetype("arial.ttf", 10)
314
  except:
315
  font = ImageFont.load_default()
316
+ font_large = ImageFont.load_default()
317
+ font_small = ImageFont.load_default()
318
 
319
+ # ์ œ๊ฑฐ๋œ ๊ฐ์ฒด ๋จผ์ € ํ‘œ์‹œ (๋นจ๊ฐ„์ƒ‰)
320
+ rejected_detections = [det for det in all_detections_scored if det['filter_score'] < filter_threshold]
321
+ for idx, det in enumerate(rejected_detections, 1):
322
+ x1, y1, x2, y2 = det['bbox']
323
+ score = det['filter_score']
324
 
325
+ # ๋นจ๊ฐ„์ƒ‰ ๋ฐ•์Šค (์ œ๊ฑฐ๋จ)
326
+ draw.rectangle([x1, y1, x2, y2], outline="red", width=8)
 
327
 
328
+ # ๋ผ๋ฒจ (์ž‘๊ฒŒ)
329
+ label = f"โœ—{idx} {score:.0f}์ "
330
+ bbox = draw.textbbox((x1, y1 - 20), label, font=font_small)
331
+ draw.rectangle(bbox, fill="red")
332
+ draw.text((x1, y1 - 20), label, fill="white", font=font_small)
333
+
334
+ # ์ „์ฒด ๊ฒ€์ถœ ํ‘œ์‹œ (์˜ต์…˜) - ํšŒ์ƒ‰
335
+ if show_all:
336
+ for det in all_detections_scored:
337
+ if det not in filtered_detections and det not in rejected_detections:
338
+ x1, y1, x2, y2 = det['bbox']
339
+ draw.rectangle([x1, y1, x2, y2], outline="gray", width=4)
340
+
341
+ # ํ•„ํ„ฐ๋ง๋œ ๊ฒฐ๊ณผ (ํ†ต๊ณผ) - ๋…น์ƒ‰/๋…ธ๋ž€์ƒ‰/์ฃผํ™ฉ์ƒ‰
342
+ for idx, det in enumerate(filtered_detections, 1):
343
+ x1, y1, x2, y2 = det['bbox']
344
+ score = det['filter_score']
345
+
346
+ # ์ ์ˆ˜์— ๋”ฐ๋ผ ์ƒ‰์ƒ
347
+ if score >= 75:
348
+ color = "lime"
349
+ elif score >= 50:
350
  color = "yellow"
351
+ else:
352
+ color = "orange"
353
 
354
+ # ๋ฐ•์Šค (๋‘๊ป๊ฒŒ)
355
+ draw.rectangle([x1, y1, x2, y2], outline=color, width=10)
356
 
357
  # ๋ผ๋ฒจ
358
+ label = f"โœ“#{idx} {score:.0f}์ "
359
+ bbox = draw.textbbox((x1, y1 - 25), label, font=font)
360
  draw.rectangle(bbox, fill=color)
361
+ draw.text((x1, y1 - 25), label, fill="black", font=font)
362
+
363
+ # ์„ธ๋ถ€ ์ •๋ณด (์ž‘๊ฒŒ)
364
+ details = f"{model_type}:{det['confidence']:.0%}"
365
+ draw.text((x1, y2 + 5), details, fill=color, font=font_small)
366
 
367
  # ํ—ค๋”
368
+ header = f"[{model_type}] โœ“ {len(filtered_detections)}๊ฐœ / โœ— {len(rejected_detections)}๊ฐœ (์ „์ฒด: {len(all_detections_scored)}๊ฐœ)"
369
+ header_bbox = draw.textbbox((10, 10), header, font=font_large)
370
+ draw.rectangle([5, 5, header_bbox[2]+10, header_bbox[3]+10],
371
+ fill="black", outline="lime", width=2)
372
+ draw.text((10, 10), header, fill="lime", font=font_large)
373
 
374
+ # ์ •๋ณด ์ƒ์„ฑ
375
  info = f"""
376
+ ### ๐Ÿ“Š ๊ฒ€์ถœ ๊ฒฐ๊ณผ (๋ชจ๋ธ: {model_type})
377
+
378
+ - **์ „์ฒด ๊ฒ€์ถœ**: {len(all_detections_scored)}๊ฐœ
379
+ - **ํ•„ํ„ฐ๋ง ํ›„**: {len(filtered_detections)}๊ฐœ
380
+ - **์ œ๊ฑฐ๋จ**: {len(rejected_detections)}๊ฐœ
381
+
382
+ ---
383
 
384
+ ### ๐ŸŽฏ ๊ฒ€์ถœ๋œ ๊ฐ์ฒด ์ƒ์„ธ (โœ… ํ†ต๊ณผ)
 
385
 
386
+ """
387
+
388
+ for idx, det in enumerate(filtered_detections, 1):
389
+ info += f"""
390
+ **#{idx} - ์ ์ˆ˜: {det['filter_score']:.0f}์ ** ({model_type} ์‹ ๋ขฐ๋„: {det['confidence']:.0%})
391
+
392
+ """
393
+ # ์ฃผ์š” ํŠน์ง•๋งŒ 5๊ฐœ
394
+ for reason in det['filter_reasons'][:5]:
395
+ info += f"- {reason}\n"
396
+
397
+ if not filtered_detections:
398
+ info += """
399
+ โš ๏ธ **๊ฒ€์ถœ๋œ ๊ฐ์ฒด๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.**
400
+
401
+ """
402
+
403
+ # ์ œ๊ฑฐ๋œ ๊ฐ์ฒด ์ •๋ณด ์ถ”๊ฐ€
404
+ if rejected_detections:
405
+ info += f"""
406
+
407
+ ---
408
+
409
+ ### โŒ ์ œ๊ฑฐ๋œ ๊ฐ์ฒด ({len(rejected_detections)}๊ฐœ)
410
+
411
+ """
412
+ for idx, det in enumerate(rejected_detections[:3], 1): # ์ตœ๋Œ€ 3๊ฐœ๋งŒ ํ‘œ์‹œ
413
+ info += f"""
414
+ **์ œ๊ฑฐ #{idx} - ์ ์ˆ˜: {det['filter_score']:.0f}์ ** (์ž„๊ณ„๊ฐ’ ๋ฏธ๋‹ฌ)
415
+ - {model_type} ์‹ ๋ขฐ๋„: {det['confidence']:.0%}
416
+
417
+ """
418
+ # ์‹คํŒจ ์ด์œ  ํ‘œ์‹œ
419
+ for reason in det['filter_reasons'][:3]:
420
+ info += f"- {reason}\n"
421
 
422
  return img, info
423
 
424
  except Exception as e:
425
+ import traceback
426
+ error_detail = traceback.format_exc()
427
+ return None, f"โŒ ์˜ค๋ฅ˜ ๋ฐœ์ƒ:\n\n```\n{error_detail}\n```"
428
+
429
+
430
+ # ============================================================
431
+ # ํƒญ 2: ๋ผ๋ฒจ๋ง ๋„๊ตฌ (Labeling Tool)
432
+ # ============================================================
433
+
434
+ def detect_with_rtdetr_fast(image, confidence=0.3):
435
+ """RT-DETR ๋น ๋ฅธ ๊ฒ€์ถœ"""
436
+ inputs = processor(images=image, return_tensors="pt")
437
+ with torch.no_grad():
438
+ outputs = model(**inputs)
439
+
440
+ target_sizes = torch.tensor([image.size[::-1]])
441
+ results = processor.post_process_object_detection(
442
+ outputs,
443
+ target_sizes=target_sizes,
444
+ threshold=confidence
445
+ )[0]
446
+
447
+ detections = []
448
+ for score, label, box in zip(results["scores"], results["labels"], results["boxes"]):
449
+ x1, y1, x2, y2 = box.tolist()
450
+ detections.append({
451
+ 'bbox': [x1, y1, x2, y2],
452
+ 'confidence': score.item()
453
+ })
454
+
455
+ return detections
456
+
457
+
458
+ def load_existing_ground_truth():
459
+ """๊ธฐ์กด ground_truth.json ๋กœ๋“œ"""
460
+ if os.path.exists(GROUND_TRUTH_FILE):
461
+ with open(GROUND_TRUTH_FILE, 'r', encoding='utf-8') as f:
462
+ return json.load(f)
463
+ return {}
464
+
465
+
466
+ def save_ground_truth(data):
467
+ """ground_truth.json ์ €์žฅ"""
468
+ backup_dir = "backups"
469
+ if not os.path.exists(backup_dir):
470
+ os.makedirs(backup_dir)
471
+
472
+ if os.path.exists(GROUND_TRUTH_FILE):
473
+ backup_name = f"ground_truth_backup_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json"
474
+ backup_path = os.path.join(backup_dir, backup_name)
475
+ import shutil
476
+ shutil.copy2(GROUND_TRUTH_FILE, backup_path)
477
+
478
+ with open(GROUND_TRUTH_FILE, 'w', encoding='utf-8') as f:
479
+ json.dump(data, f, ensure_ascii=False, indent=2)
480
+
481
+ print(f"โœ… Ground Truth ์ €์žฅ ์™„๋ฃŒ: {len(data)}๊ฐœ ์ด๋ฏธ์ง€")
482
+
483
+
484
+ def get_folders():
485
+ """์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ํด๋” ๋ชฉ๋ก"""
486
+ folders = sorted(glob.glob(os.path.join(DATA_BASE, "2*")))
487
+ return [os.path.basename(f) for f in folders if os.path.isdir(f)]
488
+
489
+
490
+ def start_labeling(folder, conf_threshold, model_type):
491
+ """๋ผ๋ฒจ๋ง ์‹œ์ž‘"""
492
+ if not folder:
493
+ return None, "โŒ ํด๋”๋ฅผ ์„ ํƒํ•˜์„ธ์š”.", ""
494
+
495
+ current_data['folder'] = folder
496
+ current_data['confidence_threshold'] = conf_threshold
497
+ current_data['model_type'] = model_type
498
+
499
+ folder_path = os.path.join(DATA_BASE, folder)
500
+ all_images = sorted(glob.glob(os.path.join(folder_path, "*.jpg")))
501
+
502
+ # -1, -2 ๋“ฑ์ด ๋ถ™์€ ํŒŒ์ผ ์ œ์™ธ (์˜ˆ: 251017_01-1.jpg ์ œ์™ธ, 251017_01.jpg๋งŒ ํฌํ•จ)
503
+ import re
504
+ images = [img for img in all_images if not re.search(r'-\d+\.jpg$', os.path.basename(img))]
505
+
506
+ if not images:
507
+ return None, "โŒ ์ด๋ฏธ์ง€ ์—†์Œ", ""
508
+
509
+ print(f"๐Ÿ“ ํด๋”: {folder}")
510
+ print(f" ์ „์ฒด ์ด๋ฏธ์ง€: {len(all_images)}๊ฐœ")
511
+ print(f" ๋ผ๋ฒจ๋ง ๋Œ€์ƒ: {len(images)}๊ฐœ (-์ˆซ์ž ํŒŒ์ผ ์ œ์™ธ)")
512
+
513
+ current_data['images'] = images
514
+ current_data['current_idx'] = 0
515
+ current_data['detections'] = {}
516
+ current_data['selections'] = {}
517
+
518
+ # ๊ธฐ์กด GT ๋กœ๋“œ
519
+ gt = load_existing_ground_truth()
520
+
521
+ # ์ด๋ฏธ ๋ผ๋ฒจ๋ง๋œ ์ด๋ฏธ์ง€ ๊ฑด๋„ˆ๋›ฐ๊ธฐ
522
+ for i, img_path in enumerate(images):
523
+ filename = os.path.basename(img_path)
524
+ if filename in gt:
525
+ current_data['selections'][filename] = [j for j in range(len(gt[filename]))]
526
+ print(f"โญ๏ธ ๊ฑด๋„ˆ๋›ฐ๊ธฐ: {filename} (์ด๋ฏธ ๋ผ๋ฒจ๋ง๋จ)")
527
+
528
+ # ์ฒซ ๋ฏธ๋ผ๋ฒจ๋ง ์ด๋ฏธ์ง€ ์ฐพ๊ธฐ
529
+ while current_data['current_idx'] < len(images):
530
+ filename = os.path.basename(images[current_data['current_idx']])
531
+ if filename not in current_data['selections']:
532
+ break
533
+ current_data['current_idx'] += 1
534
+
535
+ if current_data['current_idx'] >= len(images):
536
+ return None, "โœ… ๋ชจ๋“  ์ด๋ฏธ์ง€ ๋ผ๋ฒจ๋ง ์™„๋ฃŒ!", ""
537
+
538
+ return show_current_image()
539
+
540
+
541
+ def show_current_image():
542
+ """ํ˜„์žฌ ์ด๋ฏธ์ง€ ํ‘œ์‹œ"""
543
+ if current_data['current_idx'] >= len(current_data['images']):
544
+ return None, "โœ… ์™„๋ฃŒ!", ""
545
+
546
+ img_path = current_data['images'][current_data['current_idx']]
547
+ filename = os.path.basename(img_path)
548
+
549
+ # ์บ์‹œ ํ™•์ธ
550
+ if filename in current_data['image_cache']:
551
+ image = current_data['image_cache'][filename]
552
+ else:
553
+ image = Image.open(img_path)
554
+ current_data['image_cache'][filename] = image
555
+
556
+ # ์„ ํƒ๋œ ๋ชจ๋ธ๋กœ ๊ฒ€์ถœ
557
+ if filename not in current_data['detections']:
558
+ if current_data['model_type'] == 'RT-DETR':
559
+ detections = detect_with_rtdetr_fast(image, current_data['confidence_threshold'])
560
+ elif current_data['model_type'] == 'YOLOv8':
561
+ detections = detect_with_yolo(image, current_data['confidence_threshold'])
562
+ else: # VIDraft/Shrimp
563
+ detections = detect_with_roboflow(image, current_data['confidence_threshold'])
564
+ current_data['detections'][filename] = detections
565
+ else:
566
+ detections = current_data['detections'][filename]
567
+
568
+ # ์„ ํƒ๋œ ๋ฐ•์Šค
569
+ selected_indices = current_data['selections'].get(filename, [])
570
+
571
+ # ์‹œ๊ฐํ™”
572
+ vis_image = draw_detections(image, detections, selected_indices)
573
+
574
+ info = f"""
575
+ ### ๐Ÿ“ {current_data['folder']} - ์ด๋ฏธ์ง€ {current_data['current_idx']+1}/{len(current_data['images'])}
576
+
577
+ **ํŒŒ์ผ**: {filename}
578
+ **๋ชจ๋ธ**: {current_data['model_type']}
579
+
580
+ **๊ฒ€์ถœ**: {len(detections)}๊ฐœ
581
+ **์„ ํƒ**: {len(selected_indices)}๊ฐœ
582
+
583
+ ---
584
+
585
+ ### ๐Ÿ–ฑ๏ธ ์‚ฌ์šฉ ๋ฐฉ๋ฒ•:
586
+ 1. ์ด๋ฏธ์ง€๋ฅผ ํด๋ฆญํ•˜์—ฌ ๋ฐ•์Šค ์„ ํƒ/ํ•ด์ œ
587
+ 2. "๋‹ค์Œ" ๋ฒ„ํŠผ์œผ๋กœ ์ €์žฅ ํ›„ ์ด๋™
588
+ 3. "๊ฑด๋„ˆ๋›ฐ๊ธฐ"๋กœ ์„ ํƒ ์—†์ด ์ด๋™
589
+ """
590
+
591
+ return vis_image, info, filename
592
+
593
+
594
+ def draw_detections(image, detections, selected_indices):
595
+ """๊ฒ€์ถœ ๊ฒฐ๊ณผ ๊ทธ๋ฆฌ๊ธฐ"""
596
+ img = image.copy()
597
+ draw = ImageDraw.Draw(img)
598
+
599
+ try:
600
+ font_tiny = ImageFont.truetype("arial.ttf", 10)
601
+ font_large = ImageFont.truetype("arial.ttf", 40)
602
+ except:
603
+ font_tiny = ImageFont.load_default()
604
+ font_large = ImageFont.load_default()
605
+
606
+ # ์„ ํƒ๋˜์ง€ ์•Š์€ ๋ฐ•์Šค ๋จผ์ € (๋’ค์ชฝ ๋ ˆ์ด์–ด)
607
+ for idx, det in enumerate(detections):
608
+ if idx not in selected_indices:
609
+ x1, y1, x2, y2 = det['bbox']
610
+ draw.rectangle([x1, y1, x2, y2], outline="lime", width=20)
611
+ corner_label = f"#{idx+1}"
612
+ draw.rectangle([x1-2, y1-24, x1+30, y1-2], fill="lime")
613
+ draw.text((x1, y1 - 22), corner_label, fill="white", font=font_tiny)
614
 
615
+ # ์„ ํƒ๋œ ๋ฐ•์Šค ๋‚˜์ค‘์— (์•ž์ชฝ ๋ ˆ์ด์–ด)
616
+ for idx, det in enumerate(detections):
617
+ if idx in selected_indices:
618
+ x1, y1, x2, y2 = det['bbox']
619
+ draw.rectangle([x1, y1, x2, y2], outline="blue", width=28)
620
+ corner_label = f"โœ“#{idx+1}"
621
+ draw.rectangle([x1-2, y1-24, x1+40, y1-2], fill="blue")
622
+ draw.text((x1, y1 - 22), corner_label, fill="white", font=font_tiny)
623
+
624
+ # ์›ํ˜• ๋ฒ„ํŠผ
625
+ for idx, det in enumerate(detections):
626
+ x1, y1, x2, y2 = det['bbox']
627
+ center_x = (x1 + x2) / 2
628
+ center_y = (y1 + y2) / 2
629
+
630
+ selected = idx in selected_indices
631
+ btn_color = "blue" if selected else "lime"
632
+ btn_text = f"โœ“{idx+1}" if selected else f"{idx+1}"
633
+
634
+ box_width = x2 - x1
635
+ box_height = y2 - y1
636
+ radius = min(55, box_width * 0.18, box_height * 0.35)
637
+
638
+ # ์›ํ˜• ๋ฒ„ํŠผ
639
+ draw.ellipse(
640
+ [center_x - radius, center_y - radius,
641
+ center_x + radius, center_y + radius],
642
+ fill=btn_color, outline="white", width=4
643
+ )
644
+ draw.text((center_x - radius*0.5, center_y - radius*0.6),
645
+ btn_text, fill="white", font=font_large)
646
+
647
+ return img
648
+
649
+
650
+ def labeling_click(image, filename, evt: gr.SelectData):
651
+ """์ด๋ฏธ์ง€ ํด๋ฆญ ์ด๋ฒคํŠธ"""
652
+ if not filename or filename not in current_data['detections']:
653
+ return image, "โš ๏ธ ์ด๋ฏธ์ง€๋ฅผ ๋จผ์ € ๋กœ๋“œํ•˜์„ธ์š”."
654
+
655
+ click_x, click_y = evt.index[0], evt.index[1]
656
+ detections = current_data['detections'][filename]
657
+ selected_indices = set(current_data['selections'].get(filename, []))
658
+
659
+ # ํด๋ฆญํ•œ ๋ฐ•์Šค ์ฐพ๊ธฐ
660
+ clicked_idx = None
661
+ button_candidates = []
662
+
663
+ # ๋ฒ„ํŠผ ์˜์—ญ ํ™•์ธ
664
+ for idx, det in enumerate(detections):
665
+ x1, y1, x2, y2 = det['bbox']
666
+ center_x = (x1 + x2) / 2
667
+ center_y = (y1 + y2) / 2
668
+
669
+ box_width = x2 - x1
670
+ box_height = y2 - y1
671
+ radius = min(55, box_width * 0.18, box_height * 0.35)
672
+
673
+ distance = ((click_x - center_x) ** 2 + (click_y - center_y) ** 2) ** 0.5
674
+
675
+ if distance <= radius:
676
+ button_candidates.append((idx, distance))
677
+
678
+ # ๋ฒ„ํŠผ ํด๋ฆญ์ด ์žˆ์œผ๋ฉด ์„ ํƒ
679
+ if button_candidates:
680
+ button_candidates.sort(key=lambda x: x[1])
681
+ clicked_idx = button_candidates[0][0]
682
+ else:
683
+ # ๋ฐ•์Šค ์˜์—ญ ํด๋ฆญ ํ™•์ธ
684
+ for idx, det in enumerate(detections):
685
+ x1, y1, x2, y2 = det['bbox']
686
+ if x1 <= click_x <= x2 and y1 <= click_y <= y2:
687
+ clicked_idx = idx
688
+ break
689
+
690
+ # ์„ ํƒ ํ† ๊ธ€
691
+ if clicked_idx is not None:
692
+ if clicked_idx in selected_indices:
693
+ selected_indices.remove(clicked_idx)
694
+ print(f"โŒ ์„ ํƒ ํ•ด์ œ: ๋ฐ•์Šค #{clicked_idx+1}")
695
+ else:
696
+ selected_indices.add(clicked_idx)
697
+ print(f"โœ… ์„ ํƒ: ๋ฐ•์Šค #{clicked_idx+1}")
698
+
699
+ current_data['selections'][filename] = list(selected_indices)
700
+
701
+ # ์ด๋ฏธ์ง€ ๋‹ค์‹œ ๊ทธ๋ฆฌ๊ธฐ
702
+ img_path = current_data['images'][current_data['current_idx']]
703
+ image = Image.open(img_path)
704
+ vis_image = draw_detections(image, detections, list(selected_indices))
705
+
706
+ info = f"โœ… ๋ฐ•์Šค #{clicked_idx+1} {'์„ ํƒ' if clicked_idx in selected_indices else 'ํ•ด์ œ'}"
707
+ return vis_image, info
708
+
709
+ return image, "โŒ ๋ฐ•์Šค๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค."
710
+
711
+
712
+ def save_and_next():
713
+ """์ €์žฅ ํ›„ ๋‹ค์Œ"""
714
+ if current_data['current_idx'] >= len(current_data['images']):
715
+ return None, "โœ… ์™„๋ฃŒ!", ""
716
+
717
+ img_path = current_data['images'][current_data['current_idx']]
718
+ filename = os.path.basename(img_path)
719
+
720
+ # GT ์ €์žฅ
721
+ gt = load_existing_ground_truth()
722
+ selected_indices = current_data['selections'].get(filename, [])
723
+
724
+ if selected_indices:
725
+ detections = current_data['detections'][filename]
726
+ gt[filename] = [
727
+ {
728
+ 'bbox': detections[i]['bbox'],
729
+ 'folder': current_data['folder']
730
+ }
731
+ for i in selected_indices
732
+ ]
733
+ save_ground_truth(gt)
734
+ print(f"๐Ÿ’พ ์ €์žฅ: {filename} - {len(selected_indices)}๊ฐœ ๋ฐ•์Šค")
735
+ else:
736
+ print(f"โญ๏ธ ๊ฑด๋„ˆ๋›ฐ๊ธฐ: {filename} - ์„ ํƒ ์—†์Œ")
737
+
738
+ # ๋‹ค์Œ ์ด๋ฏธ์ง€
739
+ current_data['current_idx'] += 1
740
+
741
+ # ๋‹ค์Œ ๋ฏธ๋ผ๋ฒจ๋ง ์ด๋ฏธ์ง€ ์ฐพ๊ธฐ
742
+ while current_data['current_idx'] < len(current_data['images']):
743
+ next_filename = os.path.basename(current_data['images'][current_data['current_idx']])
744
+ if next_filename not in current_data['selections']:
745
+ break
746
+ current_data['current_idx'] += 1
747
+
748
+ if current_data['current_idx'] >= len(current_data['images']):
749
+ return None, "โœ… ๋ชจ๋“  ์ด๋ฏธ์ง€ ๋ผ๋ฒจ๋ง ์™„๋ฃŒ!", ""
750
+
751
+ return show_current_image()
752
+
753
+
754
+ def skip_image():
755
+ """๊ฑด๋„ˆ๋›ฐ๊ธฐ"""
756
+ current_data['current_idx'] += 1
757
+
758
+ if current_data['current_idx'] >= len(current_data['images']):
759
+ return None, "โœ… ์™„๋ฃŒ!", ""
760
+
761
+ return show_current_image()
762
+
763
+
764
+ # ============================================================
765
+ # ํƒญ 3: ๊ฐ„๋‹จ ๋ฐ๋ชจ (App Demo)
766
+ # ============================================================
767
+
768
+ def demo_detect(image, confidence_threshold, filter_threshold, model_type, use_filter):
769
+ """๊ฐ„๋‹จํ•œ ๋ฐ๋ชจ ๊ฒ€์ถœ"""
770
+ if image is None:
771
+ return None, "์ด๋ฏธ์ง€๋ฅผ ์—…๋กœ๋“œํ•˜์„ธ์š”."
772
+
773
+ if isinstance(image, np.ndarray):
774
+ image = Image.fromarray(image)
775
+
776
+ # ์„ ํƒ๋œ ๋ชจ๋ธ๋กœ ๊ฒ€์ถœ
777
+ all_detections = detect_with_selected_model(image, confidence_threshold, model_type)
778
+
779
+ # ํ•„ํ„ฐ ์ ์šฉ ์—ฌ๋ถ€
780
+ if not use_filter:
781
+ # ํ•„ํ„ฐ ๋ฏธ์‚ฌ์šฉ
782
+ filtered_detections = all_detections
783
+ for det in filtered_detections:
784
+ det['filter_score'] = det['confidence'] * 100
785
+ else:
786
+ # ํ•„ํ„ฐ ์‚ฌ์šฉ
787
+ if model_type in ["Roboflow", "YOLOv8"]:
788
+ # Roboflow & YOLOv8: ์‹ ๋ขฐ๋„ ๊ธฐ๋ฐ˜ ํ•„ํ„ฐ
789
+ for det in all_detections:
790
+ det['filter_score'] = det['confidence'] * 100
791
+ filtered_detections = [det for det in all_detections if det['filter_score'] >= filter_threshold]
792
+ else:
793
+ # RT-DETR: Universal Filter
794
+ from test_visual_validation import apply_universal_filter
795
+ filtered_detections = apply_universal_filter(all_detections, image, filter_threshold)
796
+
797
+ # ์‹œ๊ฐํ™”
798
+ result_image = image.copy()
799
+ draw = ImageDraw.Draw(result_image)
800
+
801
+ try:
802
+ font = ImageFont.truetype("arial.ttf", 20)
803
+ font_small = ImageFont.truetype("arial.ttf", 14)
804
+ except:
805
+ font = ImageFont.load_default()
806
+ font_small = ImageFont.load_default()
807
+
808
+ # ๋ฐ•์Šค ๊ทธ๋ฆฌ๊ธฐ
809
+ for i, det in enumerate(filtered_detections, 1):
810
+ x1, y1, x2, y2 = det['bbox']
811
+ draw.rectangle([x1, y1, x2, y2], outline="lime", width=8)
812
+
813
+ score = det['filter_score']
814
+ conf = det['confidence']
815
+ label = f"#{i} | Score:{score:.0f} | Conf:{conf:.2f}"
816
+
817
+ bbox = draw.textbbox((x1, y1-25), label, font=font_small)
818
+ draw.rectangle(bbox, fill="lime")
819
+ draw.text((x1, y1-25), label, fill="black", font=font_small)
820
+
821
+ # ๊ฒฐ๊ณผ ํ…์ŠคํŠธ
822
+ info = f"""
823
+ ๐Ÿ“Š **๊ฒ€์ถœ ๊ฒฐ๊ณผ (๋ชจ๋ธ: {model_type}):**
824
+ โ€ข ์ „์ฒด ๊ฒ€์ถœ: {len(all_detections)}๊ฐœ
825
+ โ€ข ํ•„ํ„ฐ ํ†ต๊ณผ: {len(filtered_detections)}๊ฐœ
826
+ โ€ข ์ œ๊ฑฐ๋จ: {len(all_detections) - len(filtered_detections)}๊ฐœ
827
+
828
+ โš™๏ธ **์„ค์ •:**
829
+ โ€ข {model_type} Confidence: {confidence_threshold}
830
+ โ€ข Filter Threshold: {filter_threshold}
831
+
832
+ ๐ŸŽฏ **์„ฑ๋Šฅ (50๊ฐœ GT ๊ธฐ์ค€, RT-DETR):**
833
+ โ€ข Precision: 44.2%
834
+ โ€ข Recall: 94.0%
835
+ โ€ข F1 Score: 56.1%
836
+ """
837
+
838
+ if len(filtered_detections) > 0:
839
+ info += f"\nโœ… {len(filtered_detections)}๊ฐœ์˜ ์ƒˆ์šฐ๋ฅผ ๊ฒ€์ถœํ–ˆ์Šต๋‹ˆ๋‹ค!"
840
+ else:
841
+ info += "\nโš ๏ธ ์ƒˆ์šฐ๊ฐ€ ๊ฒ€์ถœ๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. Threshold๋ฅผ ๋‚ฎ์ถฐ๋ณด์„ธ์š”."
842
+
843
+ return result_image, info
844
+
845
+
846
+ # ============================================================
847
+ # Gradio ์ธํ„ฐํŽ˜์ด์Šค - 3๊ฐœ ํƒญ์œผ๋กœ ํ†ตํ•ฉ
848
+ # ============================================================
849
+
850
+ with gr.Blocks(title="๐Ÿฆ ์ƒˆ์šฐ ๊ฒ€์ถœ ํ†ตํ•ฉ ์‹œ์Šคํ…œ", theme=gr.themes.Soft()) as demo:
851
 
852
  gr.Markdown("""
853
+ # ๐Ÿฆ ์ƒˆ์šฐ ๊ฒ€์ถœ ํ†ตํ•ฉ ์‹œ์Šคํ…œ
854
 
855
+ **3๊ฐ€์ง€ ๋ชจ๋ธ๋กœ ์ƒˆ์šฐ๋ฅผ ์ •ํ™•ํ•˜๊ฒŒ ๊ฒ€์ถœํ•˜์„ธ์š”**
856
 
857
  ---
858
  """)
859
 
860
+ # ==================== ์ตœ์ƒ๋‹จ: ๋ชจ๋ธ ์„ ํƒ ====================
861
  with gr.Row():
862
+ with gr.Column(scale=3):
863
+ model_selector = gr.Radio(
864
+ choices=["RT-DETR", "VIDraft/Shrimp", "YOLOv8"],
865
+ value="YOLOv8",
866
+ label="๐Ÿค– ๊ฒ€์ถœ ๋ชจ๋ธ ์„ ํƒ",
867
+ info="๋ชจ๋“  ํƒญ์— ์ ์šฉ๋ฉ๋‹ˆ๋‹ค"
868
  )
869
+ with gr.Column(scale=1):
870
+ load_rtdetr_btn = gr.Button("๐Ÿ”„ RT-DETR ๋กœ๋“œ", size="sm", variant="secondary")
871
+ rtdetr_status = gr.Textbox(label="๋ชจ๋ธ ์ƒํƒœ", value="โธ๏ธ RT-DETR ๋ฏธ๋กœ๋“œ (VIDraft/Shrimp ํด๋ผ์šฐ๋“œ ๋ชจ๋ธ ์‚ฌ์šฉ ๊ฐ€๋Šฅ)", interactive=False, lines=1)
872
+
873
+ # RT-DETR ๋กœ๋”ฉ ๋ฒ„ํŠผ ์ด๋ฒคํŠธ
874
+ load_rtdetr_btn.click(
875
+ load_rtdetr_on_demand,
876
+ inputs=[],
877
+ outputs=[rtdetr_status]
878
+ )
879
+
880
+ gr.Markdown("---")
881
+
882
+ with gr.Tabs():
883
+ # ==================== ํƒญ 1: ์ž๋™ ๊ฒ€์ถœ ====================
884
+ with gr.TabItem("๐Ÿค– ์ž๋™ ๊ฒ€์ถœ & ๊ฒ€์ฆ"):
885
+ gr.Markdown("""
886
+ ### ์‹ค์‹œ๊ฐ„์œผ๋กœ ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ์กฐ์ •ํ•˜๋ฉฐ ๊ฒ€์ถœ ๊ฒฐ๊ณผ๋ฅผ ํ™•์ธ
887
+ ์ตœ์ ํ™”๋œ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ์ƒˆ์šฐ ๊ฒ€์ถœ์„ ํ…Œ์ŠคํŠธํ•˜์„ธ์š”.
888
+ """)
889
+
890
+ with gr.Row():
891
+ with gr.Column():
892
+ input_image_detect = gr.Image(label="์ž…๋ ฅ ์ด๋ฏธ์ง€", type="pil")
893
+
894
+ confidence_slider_detect = gr.Slider(
895
+ 0.01, 1.0, 0.1,
896
+ step=0.01,
897
+ label="์‹ ๋ขฐ๋„ ์ž„๊ณ„๊ฐ’",
898
+ info="RT-DETR: 0.065 | VIDraft/Shrimp: 0.3~0.5 | YOLOv8: 0.1~0.3 ๊ถŒ์žฅ"
899
+ )
900
+
901
+ use_filter_check = gr.Checkbox(
902
+ label="๐Ÿ” ํ•„ํ„ฐ ์ ์ˆ˜ ์ž„๊ณ„๊ฐ’ ์‚ฌ์šฉ",
903
+ value=False,
904
+ info="์ฒดํฌํ•˜๋ฉด ํ•„ํ„ฐ ์ ์ˆ˜ ๊ธฐ์ค€์œผ๋กœ ์ถ”๊ฐ€ ํ•„ํ„ฐ๋ง"
905
+ )
906
+
907
+ filter_slider_detect = gr.Slider(
908
+ 0, 100, 90,
909
+ step=5,
910
+ label="ํ•„ํ„ฐ ์ ์ˆ˜ ์ž„๊ณ„๊ฐ’",
911
+ info="RT-DETR: Universal Filter | VIDraft/Shrimp: ์‹ ๋ขฐ๋„ ๊ธฐ๋ฐ˜",
912
+ visible=True
913
+ )
914
+
915
+ show_all_check = gr.Checkbox(
916
+ label="์ „์ฒด ๊ฒ€์ถœ ๊ฒฐ๊ณผ ํ‘œ์‹œ (ํšŒ์ƒ‰)",
917
+ value=False
918
+ )
919
+
920
+ detect_btn = gr.Button("๐Ÿš€ ๊ฒ€์ถœ ์‹คํ–‰", variant="primary", size="lg")
921
+
922
+ # ์˜ˆ์ œ ์ด๋ฏธ์ง€ (๊ฒฐ๊ณผ ํŒŒ์ผ ์ œ์™ธ)
923
+ example_images = [
924
+ "data/yolo_dataset/images/train/250818_01.jpg",
925
+ "data/yolo_dataset/images/train/250818_03.jpg",
926
+ "data/yolo_dataset/images/train/250818_04.jpg",
927
+ "data/yolo_dataset/images/train/250818_05.jpg",
928
+ "data/yolo_dataset/images/train/250818_10.jpg",
929
+ ]
930
+
931
+ # ํŒŒ์ผ์ด ์กด์žฌํ•˜๋Š” ๊ฒƒ๋งŒ ํ•„ํ„ฐ๋ง
932
+ example_images = [img for img in example_images if os.path.exists(img)]
933
+
934
+ if example_images:
935
+ gr.Examples(
936
+ examples=[[img] for img in example_images],
937
+ inputs=[input_image_detect],
938
+ label="๐Ÿ“ท ์˜ˆ์ œ ์ด๋ฏธ์ง€"
939
+ )
940
+
941
+ with gr.Column():
942
+ output_image_detect = gr.Image(label="๊ฒ€์ถœ ๊ฒฐ๊ณผ")
943
+ output_info_detect = gr.Markdown()
944
+
945
+ detect_btn.click(
946
+ interactive_detect,
947
+ [input_image_detect, confidence_slider_detect, filter_slider_detect, show_all_check, model_selector, use_filter_check],
948
+ [output_image_detect, output_info_detect]
949
  )
950
 
951
+ # ํ•„ํ„ฐ ์‚ฌ์šฉ ์ฒดํฌ๋ฐ•์Šค์— ๋”ฐ๋ผ ํ•„ํ„ฐ ์Šฌ๋ผ์ด๋” ํ™œ์„ฑํ™”/๋น„ํ™œ์„ฑํ™”
952
+ def update_filter_interactivity(use_filter):
953
+ return gr.update(interactive=use_filter)
954
+
955
+ use_filter_check.change(
956
+ update_filter_interactivity,
957
+ inputs=[use_filter_check],
958
+ outputs=[filter_slider_detect]
959
  )
960
 
 
 
961
  gr.Markdown("""
962
+ ### ๐Ÿ’ก ์‚ฌ์šฉ ํŒ
963
+ - ๋ชจ๋ธ์„ ์„ ํƒํ•˜๊ณ  ์‹ ๋ขฐ๋„๋ฅผ ์กฐ์ •ํ•˜์—ฌ ๊ฒ€์ถœ ๊ฒฐ๊ณผ๋ฅผ ํ™•์ธํ•˜์„ธ์š”
964
+ - ๊ฒ€์ถœ์ด ์ ์„ ๋•Œ๋Š” ์‹ ๋ขฐ๋„๋ฅผ ๋‚ฎ์ถ”๊ณ , ์˜ค๊ฒ€์ถœ์ด ๋งŽ์„ ๋•Œ๋Š” ๋†’์ด์„ธ์š”
965
+ - ํ•„ํ„ฐ ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•˜์—ฌ ๋” ์ •ํ™•ํ•œ ๊ฒฐ๊ณผ๋ฅผ ์–ป์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค
966
+
967
+ **๋ฐ•์Šค ์ƒ‰์ƒ:** ๐ŸŸข ๋…น์ƒ‰(๋†’์€ ํ™•๋ฅ ) | ๐ŸŸก ๋…ธ๋ž€์ƒ‰(์ค‘๊ฐ„ ํ™•๋ฅ ) | ๐ŸŸ  ์ฃผํ™ฉ์ƒ‰(๋‚ฎ์€ ํ™•๋ฅ ) | ๐Ÿ”ด ๋นจ๊ฐ„์ƒ‰(์ œ๊ฑฐ๋จ)
 
 
 
 
968
  """)
969
 
970
+ # ==================== ํƒญ 2: ๋ผ๋ฒจ๋ง ๋„๊ตฌ ====================
971
+ with gr.TabItem("๐Ÿ“ Ground Truth ๋ผ๋ฒจ๋ง"):
972
+ gr.Markdown("""
973
+ ### ์„ ํƒ๋œ ๋ชจ๋ธ์˜ ๊ฒ€์ถœ ๊ฒฐ๊ณผ์—์„œ ์˜ฌ๋ฐ”๋ฅธ ๋ฐ•์Šค๋งŒ ์„ ํƒํ•˜์—ฌ ๋ผ๋ฒจ๋ง
974
+ ์ด๋ฏธ์ง€๋ฅผ ํด๋ฆญํ•˜์—ฌ ์ƒˆ์šฐ ๋ฐ•์Šค๋ฅผ ์„ ํƒ/ํ•ด์ œํ•˜์„ธ์š”.
975
+ """)
 
 
 
 
 
 
 
 
 
 
 
976
 
977
+ with gr.Row():
978
+ with gr.Column(scale=1):
979
+ folder_dropdown = gr.Dropdown(
980
+ choices=get_folders(),
981
+ label="๐Ÿ“ ํด๋” ์„ ํƒ",
982
+ info="๋ผ๋ฒจ๋งํ•  ํด๋”๋ฅผ ์„ ํƒํ•˜์„ธ์š”"
983
+ )
984
+
985
+ conf_slider_label = gr.Slider(
986
+ 0.01, 0.5, 0.2,
987
+ step=0.05,
988
+ label="์‹ ๋ขฐ๋„",
989
+ info="๊ฒ€์ถœ ๋ฏผ๊ฐ๋„ ์กฐ์ •"
990
+ )
991
+
992
+ start_btn = gr.Button("โ–ถ๏ธ ๋ผ๋ฒจ๋ง ์‹œ์ž‘", variant="primary", size="lg")
993
+
994
+ gr.Markdown("---")
995
+
996
+ next_btn = gr.Button("โญ๏ธ ์ €์žฅ & ๋‹ค์Œ", variant="secondary", size="lg")
997
+ skip_btn = gr.Button("โฉ ๊ฑด๋„ˆ๋›ฐ๊ธฐ", size="lg")
998
+
999
+ labeling_info = gr.Markdown("ํด๋”๋ฅผ ์„ ํƒํ•˜๊ณ  '๋ผ๋ฒจ๋ง ์‹œ์ž‘'์„ ํด๋ฆญํ•˜์„ธ์š”.")
1000
+
1001
+ with gr.Column(scale=2):
1002
+ labeling_image = gr.Image(
1003
+ label="๐Ÿ–ฑ๏ธ ํด๋ฆญํ•˜์—ฌ ๋ฐ•์Šค ์„ ํƒ/ํ•ด์ œ",
1004
+ type="pil",
1005
+ interactive=True
1006
+ )
1007
+
1008
+ labeling_filename = gr.Textbox(visible=False)
1009
+ click_info = gr.Markdown()
1010
+
1011
+ # ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ
1012
+ start_btn.click(
1013
+ start_labeling,
1014
+ [folder_dropdown, conf_slider_label, model_selector],
1015
+ [labeling_image, labeling_info, labeling_filename]
1016
+ )
1017
+
1018
+ labeling_image.select(
1019
+ labeling_click,
1020
+ [labeling_image, labeling_filename],
1021
+ [labeling_image, click_info]
1022
+ )
1023
+
1024
+ next_btn.click(
1025
+ save_and_next,
1026
+ [],
1027
+ [labeling_image, labeling_info, labeling_filename]
1028
+ )
1029
+
1030
+ skip_btn.click(
1031
+ skip_image,
1032
+ [],
1033
+ [labeling_image, labeling_info, labeling_filename]
1034
+ )
1035
+
1036
+ gr.Markdown("""
1037
+ ### ๐Ÿ–ฑ๏ธ ์‚ฌ์šฉ ๋ฐฉ๋ฒ•
1038
+ 1. **๋ชจ๋ธ ์„ ํƒ** (์ตœ์ƒ๋‹จ์—์„œ ์„ ํƒ)
1039
+ 2. ํด๋” ์„ ํƒ ํ›„ "๋ผ๋ฒจ๋ง ์‹œ์ž‘"
1040
+ 3. ์ด๋ฏธ์ง€์—์„œ **์›ํ˜• ๋ฒ„ํŠผ ํด๋ฆญ** ๋˜๋Š” **๋ฐ•์Šค ์˜์—ญ ํด๋ฆญ**์œผ๋กœ ์„ ํƒ/ํ•ด์ œ
1041
+ 4. "์ €์žฅ & ๋‹ค์Œ"์œผ๋กœ ๋‹ค์Œ ์ด๋ฏธ์ง€๋กœ ์ด๋™ (์ž๋™ ์ €์žฅ)
1042
+ 5. "๊ฑด๋„ˆ๋›ฐ๊ธฐ"๋กœ ์„ ํƒ ์—†์ด ๋‹ค์Œ ์ด๋ฏธ์ง€๋กœ
1043
+
1044
+ **๐Ÿ’พ ์ €์žฅ ์œ„์น˜:** `ground_truth.json` (์ž๋™ ๋ฐฑ์—…: `backups/`)
1045
+ """)
1046
+
1047
+ # ==================== ํƒญ 3: ๊ฐ„๋‹จ ๋ฐ๋ชจ ====================
1048
+ with gr.TabItem("๐ŸŽฏ ๊ฐ„๋‹จ ๋ฐ๋ชจ"):
1049
+ gr.Markdown("""
1050
+ ### ๋น ๋ฅด๊ณ  ๊ฐ„๋‹จํ•œ ์ƒˆ์šฐ ๊ฒ€์ถœ ๋ฐ๋ชจ
1051
+ ์ด๋ฏธ์ง€๋ฅผ ์—…๋กœ๋“œํ•˜๊ณ  ๋ฐ”๋กœ ๊ฒฐ๊ณผ๋ฅผ ํ™•์ธํ•˜์„ธ์š”.
1052
+ """)
1053
+
1054
+ with gr.Row():
1055
+ with gr.Column():
1056
+ input_image_demo = gr.Image(label="์ž…๋ ฅ ์ด๋ฏธ์ง€", type="pil")
1057
+
1058
+ confidence_slider_demo = gr.Slider(
1059
+ 0.01, 1.0, 0.1,
1060
+ step=0.01,
1061
+ label="์‹ ๋ขฐ๋„",
1062
+ info="RT-DETR: 0.065 | VIDraft/Shrimp: 0.3~0.5 | YOLOv8: 0.1~0.3 ๊ถŒ์žฅ"
1063
+ )
1064
+
1065
+ use_filter_demo = gr.Checkbox(
1066
+ label="๐Ÿ” ํ•„ํ„ฐ ์ ์ˆ˜ ์ž„๊ณ„๊ฐ’ ์‚ฌ์šฉ",
1067
+ value=False,
1068
+ info="์ฒดํฌํ•˜๋ฉด ํ•„ํ„ฐ ์ ์ˆ˜ ๊ธฐ์ค€์œผ๋กœ ์ถ”๊ฐ€ ํ•„ํ„ฐ๋ง"
1069
+ )
1070
+
1071
+ filter_slider_demo = gr.Slider(
1072
+ 0, 100, 90,
1073
+ step=5,
1074
+ label="ํ•„ํ„ฐ ์ž„๊ณ„๊ฐ’",
1075
+ info="RT-DETR: Universal Filter | VIDraft/Shrimp: ์‹ ๋ขฐ๋„ ๊ธฐ๋ฐ˜",
1076
+ visible=True
1077
+ )
1078
+
1079
+ demo_detect_btn = gr.Button("๐Ÿš€ ๊ฒ€์ถœ", variant="primary", size="lg")
1080
+
1081
+ # ์˜ˆ์ œ ์ด๋ฏธ์ง€
1082
+ example_images_demo = [
1083
+ "data/yolo_dataset/images/train/250818_01.jpg",
1084
+ "data/yolo_dataset/images/train/250818_03.jpg",
1085
+ "data/yolo_dataset/images/train/250818_04.jpg",
1086
+ "data/yolo_dataset/images/train/250818_05.jpg",
1087
+ "data/yolo_dataset/images/train/250818_10.jpg",
1088
+ ]
1089
+
1090
+ # ํŒŒ์ผ์ด ์กด์žฌํ•˜๋Š” ๊ฒƒ๋งŒ ํ•„ํ„ฐ๋ง
1091
+ example_images_demo = [img for img in example_images_demo if os.path.exists(img)]
1092
+
1093
+ if example_images_demo:
1094
+ gr.Examples(
1095
+ examples=[[img] for img in example_images_demo],
1096
+ inputs=[input_image_demo],
1097
+ label="๐Ÿ“ท ์˜ˆ์ œ ์ด๋ฏธ์ง€"
1098
+ )
1099
+
1100
+ with gr.Column():
1101
+ output_image_demo = gr.Image(label="๊ฒ€์ถœ ๊ฒฐ๊ณผ")
1102
+ output_info_demo = gr.Markdown()
1103
+
1104
+ demo_detect_btn.click(
1105
+ demo_detect,
1106
+ [input_image_demo, confidence_slider_demo, filter_slider_demo, model_selector, use_filter_demo],
1107
+ [output_image_demo, output_info_demo]
1108
+ )
1109
+
1110
+ # ํ•„ํ„ฐ ์‚ฌ์šฉ ์ฒดํฌ๋ฐ•์Šค์— ๋”ฐ๋ผ ํ•„ํ„ฐ ์Šฌ๋ผ์ด๋” ํ™œ์„ฑํ™”/๋น„ํ™œ์„ฑํ™”
1111
+ use_filter_demo.change(
1112
+ lambda x: gr.update(interactive=x),
1113
+ inputs=[use_filter_demo],
1114
+ outputs=[filter_slider_demo]
1115
+ )
1116
+
1117
+ gr.Markdown("""
1118
+ ### ๐Ÿ’ก ๋น ๋ฅด๊ณ  ๊ฐ„๋‹จํ•œ ๊ฒ€์ถœ
1119
+ ์ด๋ฏธ์ง€๋ฅผ ์—…๋กœ๋“œํ•˜๊ฑฐ๋‚˜ ์˜ˆ์ œ ์ด๋ฏธ์ง€๋ฅผ ์„ ํƒํ•˜์—ฌ ๋ฐ”๋กœ ๊ฒ€์ถœ ๊ฒฐ๊ณผ๋ฅผ ํ™•์ธํ•˜์„ธ์š”.
1120
+ """)
1121
 
1122
  gr.Markdown("""
1123
  ---
1124
 
1125
+ ### ๐Ÿค– ๋ชจ๋ธ ์„ค๋ช…
1126
+ - **RT-DETR**: ๋กœ์ปฌ ๋ชจ๋ธ, ๋น ๋ฅธ ์ถ”๋ก  ์†๋„, ์˜คํ”„๋ผ์ธ ์‚ฌ์šฉ ๊ฐ€๋Šฅ
1127
+ - **VIDraft/Shrimp**: ํด๋ผ์šฐ๋“œ ๋ชจ๋ธ, ์ธํ„ฐ๋„ท ์—ฐ๊ฒฐ ํ•„์š”
1128
+ - **YOLOv8**: ๋กœ์ปฌ ์ปค์Šคํ…€ ํ•™์Šต ๋ชจ๋ธ, ๋น ๋ฅธ ์ถ”๋ก  ์†๋„
1129
 
1130
+ ---
1131
+
1132
+ ยฉ 2025 VIDraft. All rights reserved.
 
1133
  """)
1134
 
1135
  if __name__ == "__main__":
1136
+ print("\n" + "="*60)
1137
+ print("๐Ÿฆ ์ƒˆ์šฐ ๊ฒ€์ถœ ํ†ตํ•ฉ ์‹œ์Šคํ…œ v2.1 ์‹œ์ž‘")
1138
+ print("="*60)
1139
+ print("๐Ÿค– ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ๋ชจ๋ธ:")
1140
+ print(" 1. RT-DETR (๋กœ์ปฌ)")
1141
+ print(" 2. VIDraft/Shrimp (ํด๋ผ์šฐ๋“œ)")
1142
+ print(" 3. YOLOv8 (๋กœ์ปฌ ํ•™์Šต) โญ ๊ธฐ๋ณธ๊ฐ’")
1143
+ print(f"\n๐Ÿ“ฆ YOLOv8 ๋ชจ๋ธ: {YOLO_MODEL_PATH}")
1144
+ print("="*60)
1145
+
1146
  demo.launch(
1147
  server_name="0.0.0.0",
1148
+ server_port=None, # ์ž๋™์œผ๋กœ ๋นˆ ํฌํŠธ ์ฐพ๊ธฐ
1149
  share=False
1150
  )
app_backup.py ADDED
@@ -0,0 +1,299 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ ๋ฐ”์šด๋”ฉ ๋ฐ•์Šค ๊ฒ€์ถœ ํ…Œ์ŠคํŠธ ํŽ˜์ด์ง€
4
+ VIDraft/Shrimp ์ „์šฉ ๋ชจ๋ธ๊ณผ RT-DETR ๋ฒ”์šฉ ๋ชจ๋ธ์˜ ๊ฒ€์ถœ ๊ฒฐ๊ณผ ๋น„๊ต
5
+ """
6
+ import sys
7
+ sys.stdout.reconfigure(encoding='utf-8')
8
+
9
+ import gradio as gr
10
+ from PIL import Image, ImageDraw, ImageFont
11
+ import os
12
+
13
+ # VIDraft/Shrimp ์ „์šฉ ๊ฒ€์ถœ๊ธฐ
14
+ try:
15
+ from inference_sdk import InferenceHTTPClient, InferenceConfiguration
16
+
17
+ vidraft_client = InferenceHTTPClient(
18
+ api_url="https://serverless.roboflow.com",
19
+ api_key="azcIL8KDJVJMYrsERzI7"
20
+ )
21
+ VIDRAFT_AVAILABLE = True
22
+ print("โœ… VIDraft/Shrimp ๋ชจ๋ธ ์‚ฌ์šฉ ๊ฐ€๋Šฅ")
23
+ except Exception as e:
24
+ VIDRAFT_AVAILABLE = False
25
+ print(f"โŒ VIDraft/Shrimp ๋ชจ๋ธ ์‚ฌ์šฉ ๋ถˆ๊ฐ€: {e}")
26
+
27
+ def detect_with_vidraft(image, confidence, iou_threshold):
28
+ """VIDraft/Shrimp ์ „์šฉ ๋ชจ๋ธ๋กœ ๊ฒ€์ถœ"""
29
+ if not VIDRAFT_AVAILABLE:
30
+ return None, "โŒ VIDraft/Shrimp ๋ชจ๋ธ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค."
31
+
32
+ if image is None:
33
+ return None, "โš ๏ธ ์ด๋ฏธ์ง€๋ฅผ ์—…๋กœ๋“œํ•˜์„ธ์š”."
34
+
35
+ try:
36
+ # ์ž„์‹œ ํŒŒ์ผ๋กœ ์ €์žฅ
37
+ import tempfile
38
+ with tempfile.NamedTemporaryFile(suffix='.jpg', delete=False) as tmp:
39
+ if image.mode != 'RGB':
40
+ image = image.convert('RGB')
41
+ image.save(tmp.name, quality=95)
42
+ tmp_path = tmp.name
43
+
44
+ # API ํ˜ธ์ถœ with configuration
45
+ custom_config = InferenceConfiguration(
46
+ confidence_threshold=confidence,
47
+ iou_threshold=iou_threshold
48
+ )
49
+
50
+ with vidraft_client.use_configuration(custom_config):
51
+ result = vidraft_client.infer(tmp_path, model_id="shrimp-konvey/2")
52
+
53
+ # ์ž„์‹œ ํŒŒ์ผ ์‚ญ์ œ
54
+ os.unlink(tmp_path)
55
+
56
+ # ๊ฒฐ๊ณผ ๊ทธ๋ฆฌ๊ธฐ
57
+ img = image.copy()
58
+ draw = ImageDraw.Draw(img)
59
+
60
+ try:
61
+ font = ImageFont.truetype("arial.ttf", 14)
62
+ except:
63
+ font = ImageFont.load_default()
64
+
65
+ predictions = result["predictions"]
66
+ detected_count = 0
67
+
68
+ for pred in predictions:
69
+ if pred["confidence"] < confidence:
70
+ continue
71
+
72
+ detected_count += 1
73
+
74
+ x = pred["x"]
75
+ y = pred["y"]
76
+ w = pred["width"]
77
+ h = pred["height"]
78
+ conf = pred["confidence"]
79
+
80
+ # ๋ฐ”์šด๋”ฉ ๋ฐ•์Šค ์ขŒํ‘œ
81
+ x1 = x - w/2
82
+ y1 = y - h/2
83
+ x2 = x + w/2
84
+ y2 = y + h/2
85
+
86
+ # ์‹ ๋ขฐ๋„์— ๋”ฐ๋ผ ์ƒ‰์ƒ
87
+ if conf > 0.8:
88
+ color = "lime"
89
+ elif conf > 0.6:
90
+ color = "orange"
91
+ else:
92
+ color = "yellow"
93
+
94
+ # ๋ฐ•์Šค ๊ทธ๋ฆฌ๊ธฐ
95
+ draw.rectangle([x1, y1, x2, y2], outline=color, width=3)
96
+
97
+ # ๋ผ๋ฒจ
98
+ label = f"#{detected_count} {conf:.0%}"
99
+ bbox = draw.textbbox((x1, y1 - 25), label, font=font)
100
+ draw.rectangle(bbox, fill=color)
101
+ draw.text((x1, y1 - 25), label, fill="black", font=font)
102
+
103
+ # ํ—ค๋”
104
+ header = f"VIDraft/Shrimp: {detected_count}๋งˆ๋ฆฌ ๊ฒ€์ถœ"
105
+ header_bbox = draw.textbbox((10, 10), header, font=font)
106
+ draw.rectangle([5, 5, header_bbox[2]+10, header_bbox[3]+10], fill="black", outline="lime", width=2)
107
+ draw.text((10, 10), header, fill="lime", font=font)
108
+
109
+ info = f"""
110
+ ### ๐Ÿ“Š VIDraft/Shrimp ๋ชจ๋ธ ๊ฒ€์ถœ ๊ฒฐ๊ณผ
111
+
112
+ - **๊ฒ€์ถœ ์ˆ˜**: {detected_count}๋งˆ๋ฆฌ
113
+ - **์ „์ฒด ์˜ˆ์ธก**: {len(predictions)}๊ฐœ
114
+ - **์‹ ๋ขฐ๋„ ์ž„๊ณ„๊ฐ’**: {confidence:.0%}
115
+ - **IoU ์ž„๊ณ„๊ฐ’**: {iou_threshold:.0%}
116
+ - **์ฒ˜๋ฆฌ ์‹œ๊ฐ„**: {result['time']:.2f}์ดˆ
117
+ """
118
+
119
+ return img, info
120
+
121
+ except Exception as e:
122
+ return None, f"โŒ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: {str(e)}"
123
+
124
+ def detect_with_rtdetr(image, confidence):
125
+ """RT-DETR๋กœ ๊ฒ€์ถœ (๊ฐ„๋‹จ ๋ฒ„์ „)"""
126
+ if image is None:
127
+ return None, "โš ๏ธ ์ด๋ฏธ์ง€๋ฅผ ์—…๋กœ๋“œํ•˜์„ธ์š”."
128
+
129
+ try:
130
+ from transformers import RTDetrForObjectDetection, RTDetrImageProcessor
131
+ import torch
132
+
133
+ # ๋ชจ๋ธ ๋กœ๋“œ (์บ์‹œ ์‚ฌ์šฉ)
134
+ if not hasattr(detect_with_rtdetr, 'model'):
135
+ print("๐Ÿ”„ RT-DETR ๋ชจ๋ธ ๋กœ๋”ฉ ์ค‘...")
136
+ processor = RTDetrImageProcessor.from_pretrained("PekingU/rtdetr_r50vd_coco_o365")
137
+ model = RTDetrForObjectDetection.from_pretrained("PekingU/rtdetr_r50vd_coco_o365")
138
+ model.eval()
139
+ detect_with_rtdetr.processor = processor
140
+ detect_with_rtdetr.model = model
141
+ print("โœ… RT-DETR ๋กœ๋”ฉ ์™„๋ฃŒ")
142
+
143
+ processor = detect_with_rtdetr.processor
144
+ model = detect_with_rtdetr.model
145
+
146
+ # ์ถ”๋ก 
147
+ inputs = processor(images=image, return_tensors="pt")
148
+ with torch.no_grad():
149
+ outputs = model(**inputs)
150
+
151
+ target_sizes = torch.tensor([image.size[::-1]])
152
+ results = processor.post_process_object_detection(
153
+ outputs,
154
+ target_sizes=target_sizes,
155
+ threshold=confidence
156
+ )[0]
157
+
158
+ # ๊ฒฐ๊ณผ ๊ทธ๋ฆฌ๊ธฐ
159
+ img = image.copy()
160
+ draw = ImageDraw.Draw(img)
161
+
162
+ try:
163
+ font = ImageFont.truetype("arial.ttf", 14)
164
+ except:
165
+ font = ImageFont.load_default()
166
+
167
+ detected_count = len(results["scores"])
168
+
169
+ for idx, (score, label, box) in enumerate(zip(results["scores"], results["labels"], results["boxes"]), 1):
170
+ x1, y1, x2, y2 = box.tolist()
171
+ conf = score.item()
172
+
173
+ # ์ƒ‰์ƒ
174
+ if conf > 0.8:
175
+ color = "cyan"
176
+ elif conf > 0.6:
177
+ color = "magenta"
178
+ else:
179
+ color = "yellow"
180
+
181
+ # ๋ฐ•์Šค
182
+ draw.rectangle([x1, y1, x2, y2], outline=color, width=3)
183
+
184
+ # ๋ผ๋ฒจ
185
+ label_text = f"#{idx} {conf:.0%}"
186
+ bbox = draw.textbbox((x1, y1 - 25), label_text, font=font)
187
+ draw.rectangle(bbox, fill=color)
188
+ draw.text((x1, y1 - 25), label_text, fill="black", font=font)
189
+
190
+ # ํ—ค๋”
191
+ header = f"RT-DETR: {detected_count}๊ฐœ ๊ฒ€์ถœ"
192
+ header_bbox = draw.textbbox((10, 10), header, font=font)
193
+ draw.rectangle([5, 5, header_bbox[2]+10, header_bbox[3]+10], fill="black", outline="cyan", width=2)
194
+ draw.text((10, 10), header, fill="cyan", font=font)
195
+
196
+ info = f"""
197
+ ### ๐Ÿ“Š RT-DETR ๋ฒ”์šฉ ๋ชจ๋ธ ๊ฒ€์ถœ ๊ฒฐ๊ณผ
198
+
199
+ - **๊ฒ€์ถœ ์ˆ˜**: {detected_count}๊ฐœ
200
+ - **์‹ ๋ขฐ๋„ ์ž„๊ณ„๊ฐ’**: {confidence:.0%}
201
+
202
+ โš ๏ธ **์ฐธ๊ณ **: RT-DETR์€ ๋ฒ”์šฉ ๊ฐ์ฒด ๊ฒ€์ถœ ๋ชจ๋ธ์ž…๋‹ˆ๋‹ค. ์ƒˆ์šฐ ๊ฒ€์ถœ์€ VIDraft/Shrimp ๋ชจ๋ธ์„ ์‚ฌ์šฉํ•˜์„ธ์š”.
203
+ """
204
+
205
+ return img, info
206
+
207
+ except Exception as e:
208
+ return None, f"โŒ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: {str(e)}"
209
+
210
+ # Gradio ์ธํ„ฐํŽ˜์ด์Šค
211
+ with gr.Blocks(title="๐Ÿฆ ๋ฐ”์šด๋”ฉ ๋ฐ•์Šค ๊ฒ€์ถœ ํ…Œ์ŠคํŠธ", theme=gr.themes.Soft()) as demo:
212
+
213
+ gr.Markdown("""
214
+ # ๐Ÿฆ ๋ฐ”์šด๋”ฉ ๋ฐ•์Šค ๊ฒ€์ถœ ๋น„๊ต ํ…Œ์ŠคํŠธ
215
+
216
+ VIDraft/Shrimp ์ „์šฉ ๋ชจ๋ธ๊ณผ RT-DETR ๋ฒ”์šฉ ๋ชจ๋ธ์˜ ๊ฒ€์ถœ ์„ฑ๋Šฅ์„ ๋น„๊ตํ•ฉ๋‹ˆ๋‹ค.
217
+
218
+ ---
219
+ """)
220
+
221
+ with gr.Row():
222
+ with gr.Column():
223
+ input_image = gr.Image(label="์ž…๋ ฅ ์ด๋ฏธ์ง€", type="pil")
224
+ confidence_slider = gr.Slider(
225
+ 0.1, 0.9, 0.5,
226
+ label="์‹ ๋ขฐ๋„ ์ž„๊ณ„๊ฐ’ (Confidence)",
227
+ info="๋‚ฎ์„์ˆ˜๋ก ๋” ๋งŽ์ด ๊ฒ€์ถœ"
228
+ )
229
+ iou_slider = gr.Slider(
230
+ 0.1, 0.9, 0.5,
231
+ label="IoU ์ž„๊ณ„๊ฐ’ (Overlap)",
232
+ info="๊ฒน์น˜๋Š” ๋ฐ•์Šค ์ œ๊ฑฐ ๊ธฐ์ค€ (๋†’์„์ˆ˜๋ก ๋” ๋งŽ์ด ์œ ์ง€)"
233
+ )
234
+
235
+ # ์˜ˆ์ œ ์ด๋ฏธ์ง€
236
+ gr.Examples(
237
+ examples=[
238
+ ["imgs/test_shrimp_tank.png", 0.1, 0.1],
239
+ ],
240
+ inputs=[input_image, confidence_slider, iou_slider],
241
+ label="๐Ÿ“ท ์˜ˆ์ œ ์ด๋ฏธ์ง€ (ํด๋ฆญํ•˜์—ฌ ๋ฐ”๋กœ ํ…Œ์ŠคํŠธ)"
242
+ )
243
+
244
+ with gr.Column():
245
+ gr.Markdown("### ๐Ÿ“ ์‚ฌ์šฉ ๋ฐฉ๋ฒ•")
246
+ gr.Markdown("""
247
+ 1. **์•„๋ž˜ ์˜ˆ์ œ ์ด๋ฏธ์ง€๋ฅผ ํด๋ฆญ**ํ•˜๊ฑฐ๋‚˜ ์ง์ ‘ ์—…๋กœ๋“œ
248
+ 2. ํŒŒ๋ผ๋ฏธํ„ฐ ์กฐ์ •:
249
+ - **Confidence**: ๊ฒ€์ถœ ์‹ ๋ขฐ๋„ (๋‚ฎ์„์ˆ˜๋ก ๋” ๋งŽ์ด ๊ฒ€์ถœ)
250
+ - **IoU**: ์ค‘๋ณต ๋ฐ•์Šค ์ œ๊ฑฐ ๊ธฐ์ค€ (NMS)
251
+ 3. ๋ฒ„ํŠผ ํด๋ฆญํ•˜์—ฌ ๊ฒ€์ถœ
252
+
253
+ **์ƒ‰์ƒ ์˜๋ฏธ:**
254
+ - **๋…น์ƒ‰/์ฒญ๋ก**: ๋†’์€ ์‹ ๋ขฐ๋„ (>80%)
255
+ - **์ฃผํ™ฉ/์žํ™**: ์ค‘๊ฐ„ ์‹ ๋ขฐ๋„ (60-80%)
256
+ - **๋…ธ๋ž€์ƒ‰**: ๋‚ฎ์€ ์‹ ๋ขฐ๋„ (<60%)
257
+ """)
258
+
259
+ with gr.Tabs():
260
+ with gr.TabItem("๐Ÿค– VIDraft/Shrimp (์ƒˆ์šฐ ์ „์šฉ)"):
261
+ vidraft_btn = gr.Button("๐Ÿš€ VIDraft/Shrimp ๋ชจ๋ธ๋กœ ๊ฒ€์ถœ", variant="primary", size="lg")
262
+ vidraft_result = gr.Image(label="๊ฒ€์ถœ ๊ฒฐ๊ณผ")
263
+ vidraft_info = gr.Markdown()
264
+
265
+ with gr.TabItem("๐Ÿ” RT-DETR (๋ฒ”์šฉ)"):
266
+ rtdetr_btn = gr.Button("๐Ÿš€ RT-DETR๋กœ ๊ฒ€์ถœ", variant="secondary", size="lg")
267
+ rtdetr_result = gr.Image(label="๊ฒ€์ถœ ๊ฒฐ๊ณผ")
268
+ rtdetr_info = gr.Markdown()
269
+
270
+ # ์ด๋ฒคํŠธ ์—ฐ๊ฒฐ
271
+ vidraft_btn.click(
272
+ detect_with_vidraft,
273
+ [input_image, confidence_slider, iou_slider],
274
+ [vidraft_result, vidraft_info]
275
+ )
276
+
277
+ rtdetr_btn.click(
278
+ detect_with_rtdetr,
279
+ [input_image, confidence_slider],
280
+ [rtdetr_result, rtdetr_info]
281
+ )
282
+
283
+ gr.Markdown("""
284
+ ---
285
+
286
+ ### ๐Ÿ’ก ํŒ
287
+
288
+ - **์ˆ˜์กฐ ์ด๋ฏธ์ง€**: VIDraft/Shrimp ๋ชจ๋ธ์ด ํ›จ์”ฌ ์ •ํ™•ํ•ฉ๋‹ˆ๋‹ค (์ƒˆ์šฐ ์ „์šฉ ํ•™์Šต)
289
+ - **์ธก์ •์šฉ ์ด๋ฏธ์ง€**: RT-DETR ๋ฒ”์šฉ ๋ชจ๋ธ์„ ์‚ฌ์šฉํ•˜์„ธ์š”
290
+ - **๊ฒ€์ถœ ์•ˆ ๋จ**: ์‹ ๋ขฐ๋„๋ฅผ ๋‚ฎ์ถฐ๋ณด์„ธ์š” (0.3~0.4)
291
+ - **์ค‘๋ณต ๋ฐ•์Šค**: IoU ์ž„๊ณ„๊ฐ’์„ ์กฐ์ •ํ•˜์„ธ์š” (VIDraft/Shrimp ๋ชจ๋ธ๋งŒ)
292
+ """)
293
+
294
+ if __name__ == "__main__":
295
+ demo.launch(
296
+ server_name="0.0.0.0",
297
+ server_port=7860, # Hugging Face default port
298
+ share=False
299
+ )