Be2Jay Claude commited on
Commit
1892272
ยท
1 Parent(s): c1dee2e

Clean up repository for Hugging Face deployment

Browse files

**Removed files:**
- All backup apps: app_backup2.py, app_backup3.py, app_demo.py, app_full_system.py, shrimp_detection_app.py
- Test scripts: test_*.py, quick_test_*.py (25+ files)
- Analysis tools: analyze_*.py, optimize_*.py, check_*.py
- Training utilities: train_yolo.py, evaluate_yolo.py, labeling_tool.py

**Added:**
- examples/ folder with 5 example images for Gradio interface
- Updated app.py to use examples/ folder instead of data/yolo_dataset/

**Kept for deployment:**
- app.py (main application)
- requirements.txt
- test_visual_validation.py (required by app.py)
- test_quantitative_evaluation.py (required by app.py)
- interactive_validation.py (required by app.py)
- ground_truth.json (for labeling functionality)
- examples/ (5 demo images)

**Result:**
Clean, minimal Hugging Face deployment with only essential files

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

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

analyze_fp_patterns.py DELETED
@@ -1,284 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- """
3
- False Positive ํŒจํ„ด ๋ถ„์„
4
- GT ๋ฐ•์Šค vs ์˜ค๊ฒ€์ถœ ๋ฐ•์Šค์˜ ํŠน์ง• ๋น„๊ต
5
- """
6
- import sys
7
- sys.stdout.reconfigure(encoding='utf-8')
8
-
9
- import os
10
- import json
11
- import numpy as np
12
- from PIL import Image
13
- from test_visual_validation import (
14
- load_rtdetr_model,
15
- detect_with_rtdetr,
16
- apply_universal_filter,
17
- calculate_morphological_features,
18
- calculate_visual_features
19
- )
20
-
21
- def calculate_iou(bbox1, bbox2):
22
- """IoU ๊ณ„์‚ฐ"""
23
- x1_min, y1_min, x1_max, y1_max = bbox1
24
- x2_min, y2_min, x2_max, y2_max = bbox2
25
-
26
- inter_x_min = max(x1_min, x2_min)
27
- inter_y_min = max(y1_min, y2_min)
28
- inter_x_max = min(x1_max, x2_max)
29
- inter_y_max = min(y1_max, y2_max)
30
-
31
- if inter_x_max < inter_x_min or inter_y_max < inter_y_min:
32
- return 0.0
33
-
34
- inter_area = (inter_x_max - inter_x_min) * (inter_y_max - inter_y_min)
35
- bbox1_area = (x1_max - x1_min) * (y1_max - y1_min)
36
- bbox2_area = (x2_max - x2_min) * (y2_max - y2_min)
37
- union_area = bbox1_area + bbox2_area - inter_area
38
-
39
- return inter_area / union_area if union_area > 0 else 0.0
40
-
41
- def analyze_fp_patterns(test_image_dir, ground_truth_path, confidence=0.065):
42
- """False Positive ํŒจํ„ด ๋ถ„์„"""
43
- print("\n" + "="*80)
44
- print("๐Ÿ” False Positive ํŒจํ„ด ๋ถ„์„")
45
- print("="*80)
46
-
47
- # Ground truth ๋กœ๋“œ
48
- with open(ground_truth_path, 'r', encoding='utf-8') as f:
49
- ground_truths = json.load(f)
50
-
51
- # ๋ชจ๋ธ ๋กœ๋“œ
52
- processor, model = load_rtdetr_model()
53
-
54
- # ๊ฒฐ๊ณผ ์ €์žฅ
55
- tp_features = [] # True Positive (GT์™€ ๋งค์นญ๋œ ๋ฐ•์Šค)
56
- fp_features = [] # False Positive (์˜ค๊ฒ€์ถœ)
57
- gt_features = [] # Ground Truth ๋ฐ•์Šค
58
-
59
- print(f"\n๋ถ„์„ ์ค‘...")
60
-
61
- for filename, gt_list in ground_truths.items():
62
- if not gt_list:
63
- continue
64
-
65
- # ์ด๋ฏธ์ง€ ๊ฒฝ๋กœ
66
- if 'folder' in gt_list[0]:
67
- folder = gt_list[0]['folder']
68
- img_path = os.path.join(test_image_dir, folder, filename)
69
- else:
70
- img_path = os.path.join(test_image_dir, filename)
71
-
72
- if not os.path.exists(img_path):
73
- continue
74
-
75
- # ์ด๋ฏธ์ง€ ๋กœ๋“œ
76
- image = Image.open(img_path).convert('RGB')
77
-
78
- # GT ํŠน์ง• ์ถ”์ถœ
79
- for gt in gt_list:
80
- morph = calculate_morphological_features(gt['bbox'], image.size)
81
- visual = calculate_visual_features(image, gt['bbox'])
82
-
83
- gt_features.append({
84
- 'filename': filename,
85
- 'type': 'GT',
86
- 'bbox': gt['bbox'],
87
- 'morph': morph,
88
- 'visual': visual,
89
- 'confidence': gt['confidence']
90
- })
91
-
92
- # ๊ฒ€์ถœ
93
- all_detections = detect_with_rtdetr(image, processor, model, confidence)
94
-
95
- # ๊ฐ ๊ฒ€์ถœ ๋ฐ•์Šค ๋ถ„์„
96
- for det in all_detections:
97
- morph = calculate_morphological_features(det['bbox'], image.size)
98
- visual = calculate_visual_features(image, det['bbox'])
99
-
100
- # GT์™€ ๋งค์นญ ํ™•์ธ
101
- matched = False
102
- for gt in gt_list:
103
- iou = calculate_iou(det['bbox'], gt['bbox'])
104
- if iou >= 0.5:
105
- matched = True
106
- tp_features.append({
107
- 'filename': filename,
108
- 'type': 'TP',
109
- 'bbox': det['bbox'],
110
- 'morph': morph,
111
- 'visual': visual,
112
- 'confidence': det['confidence'],
113
- 'iou': iou
114
- })
115
- break
116
-
117
- if not matched:
118
- fp_features.append({
119
- 'filename': filename,
120
- 'type': 'FP',
121
- 'bbox': det['bbox'],
122
- 'morph': morph,
123
- 'visual': visual,
124
- 'confidence': det['confidence']
125
- })
126
-
127
- print(f"โœ… ๋ถ„์„ ์™„๋ฃŒ")
128
- print(f" GT: {len(gt_features)}๊ฐœ")
129
- print(f" TP: {len(tp_features)}๊ฐœ")
130
- print(f" FP: {len(fp_features)}๊ฐœ")
131
-
132
- # ํ†ต๊ณ„ ๋น„๊ต
133
- print("\n" + "="*80)
134
- print("๐Ÿ“Š ํŠน์ง• ๋น„๊ต: GT vs FP")
135
- print("="*80)
136
-
137
- # 1. ์žฅ๋‹จ์ถ•๋น„
138
- gt_ratios = [f['morph']['aspect_ratio'] for f in gt_features]
139
- fp_ratios = [f['morph']['aspect_ratio'] for f in fp_features]
140
-
141
- print(f"\n1๏ธโƒฃ ์žฅ๋‹จ์ถ•๋น„ (Aspect Ratio)")
142
- print(f" GT: ํ‰๊ท ={np.mean(gt_ratios):.2f}, ๋ฒ”์œ„=[{np.min(gt_ratios):.2f}, {np.max(gt_ratios):.2f}], std={np.std(gt_ratios):.2f}")
143
- print(f" FP: ํ‰๊ท ={np.mean(fp_ratios):.2f}, ๋ฒ”์œ„=[{np.min(fp_ratios):.2f}, {np.max(fp_ratios):.2f}], std={np.std(fp_ratios):.2f}")
144
- print(f" โ†’ ์ฐจ์ด: {abs(np.mean(gt_ratios) - np.mean(fp_ratios)):.2f}")
145
-
146
- # 2. Compactness
147
- gt_compact = [f['morph']['compactness'] for f in gt_features]
148
- fp_compact = [f['morph']['compactness'] for f in fp_features]
149
-
150
- print(f"\n2๏ธโƒฃ Compactness (์„ธ์žฅ๋„)")
151
- print(f" GT: ํ‰๊ท ={np.mean(gt_compact):.3f}, ๋ฒ”์œ„=[{np.min(gt_compact):.3f}, {np.max(gt_compact):.3f}], std={np.std(gt_compact):.3f}")
152
- print(f" FP: ํ‰๊ท ={np.mean(fp_compact):.3f}, ๋ฒ”์œ„=[{np.min(fp_compact):.3f}, {np.max(fp_compact):.3f}], std={np.std(fp_compact):.3f}")
153
- print(f" โ†’ ์ฐจ์ด: {abs(np.mean(gt_compact) - np.mean(fp_compact)):.3f}")
154
-
155
- # 3. ๋ฉด์ 
156
- gt_area = [f['morph']['width'] * f['morph']['height'] for f in gt_features]
157
- fp_area = [f['morph']['width'] * f['morph']['height'] for f in fp_features]
158
-
159
- print(f"\n3๏ธโƒฃ ๋ฉด์  (pxยฒ)")
160
- print(f" GT: ํ‰๊ท ={np.mean(gt_area):.0f}, ๋ฒ”์œ„=[{np.min(gt_area):.0f}, {np.max(gt_area):.0f}], std={np.std(gt_area):.0f}")
161
- print(f" FP: ํ‰๊ท ={np.mean(fp_area):.0f}, ๋ฒ”์œ„=[{np.min(fp_area):.0f}, {np.max(fp_area):.0f}], std={np.std(fp_area):.0f}")
162
- print(f" โ†’ ์ฐจ์ด: {abs(np.mean(gt_area) - np.mean(fp_area)):.0f}")
163
-
164
- # 4. Hue
165
- gt_hue = [f['visual']['hue'] for f in gt_features]
166
- fp_hue = [f['visual']['hue'] for f in fp_features]
167
-
168
- print(f"\n4๏ธโƒฃ Hue (์ƒ‰์ƒ)")
169
- print(f" GT: ํ‰๊ท ={np.mean(gt_hue):.1f}, ๋ฒ”์œ„=[{np.min(gt_hue):.1f}, {np.max(gt_hue):.1f}], std={np.std(gt_hue):.1f}")
170
- print(f" FP: ํ‰๊ท ={np.mean(fp_hue):.1f}, ๋ฒ”์œ„=[{np.min(fp_hue):.1f}, {np.max(fp_hue):.1f}], std={np.std(fp_hue):.1f}")
171
- print(f" โ†’ ์ฐจ์ด: {abs(np.mean(gt_hue) - np.mean(fp_hue)):.1f}")
172
-
173
- # 5. Saturation
174
- gt_sat = [f['visual']['saturation'] for f in gt_features]
175
- fp_sat = [f['visual']['saturation'] for f in fp_features]
176
-
177
- print(f"\n5๏ธโƒฃ Saturation (์ฑ„๋„)")
178
- print(f" GT: ํ‰๊ท ={np.mean(gt_sat):.1f}, ๋ฒ”์œ„=[{np.min(gt_sat):.1f}, {np.max(gt_sat):.1f}], std={np.std(gt_sat):.1f}")
179
- print(f" FP: ํ‰๊ท ={np.mean(fp_sat):.1f}, ๋ฒ”์œ„=[{np.min(fp_sat):.1f}, {np.max(fp_sat):.1f}], std={np.std(fp_sat):.1f}")
180
- print(f" โ†’ ์ฐจ์ด: {abs(np.mean(gt_sat) - np.mean(fp_sat)):.1f}")
181
-
182
- # 6. Color std
183
- gt_cstd = [f['visual']['color_std'] for f in gt_features]
184
- fp_cstd = [f['visual']['color_std'] for f in fp_features]
185
-
186
- print(f"\n6๏ธโƒฃ Color Std (์ƒ‰์ƒ ์ผ๊ด€์„ฑ)")
187
- print(f" GT: ํ‰๊ท ={np.mean(gt_cstd):.1f}, ๋ฒ”์œ„=[{np.min(gt_cstd):.1f}, {np.max(gt_cstd):.1f}], std={np.std(gt_cstd):.1f}")
188
- print(f" FP: ํ‰๊ท ={np.mean(fp_cstd):.1f}, ๋ฒ”์œ„=[{np.min(fp_cstd):.1f}, {np.max(fp_cstd):.1f}], std={np.std(fp_cstd):.1f}")
189
- print(f" โ†’ ์ฐจ์ด: {abs(np.mean(gt_cstd) - np.mean(fp_cstd)):.1f}")
190
-
191
- # 7. Confidence
192
- gt_conf = [f['confidence'] for f in gt_features]
193
- fp_conf = [f['confidence'] for f in fp_features]
194
-
195
- print(f"\n7๏ธโƒฃ RT-DETR Confidence")
196
- print(f" GT: ํ‰๊ท ={np.mean(gt_conf):.3f}, ๋ฒ”์œ„=[{np.min(gt_conf):.3f}, {np.max(gt_conf):.3f}], std={np.std(gt_conf):.3f}")
197
- print(f" FP: ํ‰๊ท ={np.mean(fp_conf):.3f}, ๋ฒ”์œ„=[{np.min(fp_conf):.3f}, {np.max(fp_conf):.3f}], std={np.std(fp_conf):.3f}")
198
- print(f" โ†’ ์ฐจ์ด: {abs(np.mean(gt_conf) - np.mean(fp_conf)):.3f}")
199
-
200
- # ๊ฐ€์žฅ ์ฐจ์ด๋‚˜๋Š” ํŠน์ง• ์ฐพ๊ธฐ
201
- print("\n" + "="*80)
202
- print("๐ŸŽฏ ํŒ๋ณ„๋ ฅ ๋†’์€ ํŠน์ง• (GT vs FP ์ฐจ์ด)")
203
- print("="*80)
204
-
205
- differences = [
206
- ('์žฅ๋‹จ์ถ•๋น„', abs(np.mean(gt_ratios) - np.mean(fp_ratios)) / np.mean(gt_ratios)),
207
- ('Compactness', abs(np.mean(gt_compact) - np.mean(fp_compact)) / np.mean(gt_compact)),
208
- ('๋ฉด์ ', abs(np.mean(gt_area) - np.mean(fp_area)) / np.mean(gt_area)),
209
- ('Hue', abs(np.mean(gt_hue) - np.mean(fp_hue)) / max(np.mean(gt_hue), 1)),
210
- ('Saturation', abs(np.mean(gt_sat) - np.mean(fp_sat)) / max(np.mean(gt_sat), 1)),
211
- ('Color Std', abs(np.mean(gt_cstd) - np.mean(fp_cstd)) / max(np.mean(gt_cstd), 1)),
212
- ('Confidence', abs(np.mean(gt_conf) - np.mean(fp_conf)) / np.mean(gt_conf))
213
- ]
214
-
215
- differences.sort(key=lambda x: x[1], reverse=True)
216
-
217
- for i, (name, diff) in enumerate(differences, 1):
218
- print(f"{i}. {name}: {diff*100:.1f}% ์ฐจ์ด")
219
-
220
- # ์ƒ์„ธ ๋ถ„ํฌ
221
- print("\n" + "="*80)
222
- print("๐Ÿ“ˆ FP ์ƒ์„ธ ๋ถ„ํฌ (์ƒ์œ„ ์˜ค๊ฒ€์ถœ ํŒจํ„ด)")
223
- print("="*80)
224
-
225
- # ์žฅ๋‹จ์ถ•๋น„ ๋ถ„ํฌ
226
- fp_ratio_dist = {
227
- '< 3': len([r for r in fp_ratios if r < 3]),
228
- '3-4': len([r for r in fp_ratios if 3 <= r < 4]),
229
- '4-9': len([r for r in fp_ratios if 4 <= r < 9]),
230
- '9-15': len([r for r in fp_ratios if 9 <= r < 15]),
231
- '>= 15': len([r for r in fp_ratios if r >= 15])
232
- }
233
-
234
- print(f"\nFP ์žฅ๋‹จ์ถ•๋น„ ๋ถ„ํฌ:")
235
- for range_name, count in fp_ratio_dist.items():
236
- print(f" {range_name}: {count}๊ฐœ ({count/len(fp_ratios)*100:.1f}%)")
237
-
238
- # ์ถ”์ฒœ์‚ฌํ•ญ
239
- print("\n" + "="*80)
240
- print("๐Ÿ’ก ํ•„ํ„ฐ ๊ฐœ์„  ์ œ์•ˆ")
241
- print("="*80)
242
-
243
- # ๊ฐ€์žฅ ์ฐจ์ด๋‚˜๋Š” ํŠน์ง• ๊ธฐ๋ฐ˜ ์ œ์•ˆ
244
- top_diff = differences[0]
245
- if top_diff[0] == '์žฅ๋‹จ์ถ•๋น„':
246
- print(f"1. ์žฅ๋‹จ์ถ•๋น„ ํ•„ํ„ฐ ๊ฐ•ํ™”")
247
- print(f" - GT ๋ฒ”์œ„: {np.min(gt_ratios):.2f}~{np.max(gt_ratios):.2f}")
248
- print(f" - FP ํ‰๊ท : {np.mean(fp_ratios):.2f}")
249
- if np.mean(fp_ratios) < np.mean(gt_ratios):
250
- print(f" โ†’ FP๊ฐ€ ๋” ๋‘ฅ๊ธ€์Œ. ํ•˜ํ•œ์„ {np.percentile(gt_ratios, 10):.1f}๋กœ ์ƒํ–ฅ")
251
- else:
252
- print(f" โ†’ FP๊ฐ€ ๋” ๊ฐ€๋Š˜์Œ. ์ƒํ•œ์„ {np.percentile(gt_ratios, 90):.1f}๋กœ ํ•˜ํ–ฅ")
253
-
254
- # ๊ฒฐ๊ณผ ์ €์žฅ
255
- result = {
256
- 'gt_count': len(gt_features),
257
- 'tp_count': len(tp_features),
258
- 'fp_count': len(fp_features),
259
- 'feature_comparison': {
260
- 'aspect_ratio': {'gt': gt_ratios, 'fp': fp_ratios},
261
- 'compactness': {'gt': gt_compact, 'fp': fp_compact},
262
- 'area': {'gt': gt_area, 'fp': fp_area},
263
- 'hue': {'gt': gt_hue, 'fp': fp_hue},
264
- 'saturation': {'gt': gt_sat, 'fp': fp_sat},
265
- 'color_std': {'gt': gt_cstd, 'fp': fp_cstd},
266
- 'confidence': {'gt': gt_conf, 'fp': fp_conf}
267
- },
268
- 'discriminative_features': differences
269
- }
270
-
271
- with open('fp_analysis_result.json', 'w', encoding='utf-8') as f:
272
- # numpy array๋ฅผ list๋กœ ๋ณ€ํ™˜
273
- for key in result['feature_comparison']:
274
- result['feature_comparison'][key]['gt'] = [float(x) for x in result['feature_comparison'][key]['gt']]
275
- result['feature_comparison'][key]['fp'] = [float(x) for x in result['feature_comparison'][key]['fp']]
276
- json.dump(result, f, ensure_ascii=False, indent=2)
277
-
278
- print(f"\n๐Ÿ“„ ๋ถ„์„ ๊ฒฐ๊ณผ ์ €์žฅ: fp_analysis_result.json")
279
-
280
- if __name__ == "__main__":
281
- TEST_DIR = r"data\ํฐ๋‹ค๋ฆฌ์ƒˆ์šฐ ์‹ค์ธก ๋ฐ์ดํ„ฐ_์ตํˆฌ์Šค์—์ด์•„์ด(์ฃผ)"
282
- GT_PATH = "ground_truth.json"
283
-
284
- analyze_fp_patterns(TEST_DIR, GT_PATH, confidence=0.065)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app.py CHANGED
@@ -940,11 +940,11 @@ with gr.Blocks(title="๐Ÿฆ ์ƒˆ์šฐ ๊ฒ€์ถœ ํ†ตํ•ฉ ์‹œ์Šคํ…œ", theme=gr.themes.Soft
940
 
941
  # ์˜ˆ์ œ ์ด๋ฏธ์ง€ (๊ฒฐ๊ณผ ํŒŒ์ผ ์ œ์™ธ)
942
  example_images = [
943
- "data/yolo_dataset/images/train/250818_01.jpg",
944
- "data/yolo_dataset/images/train/250818_03.jpg",
945
- "data/yolo_dataset/images/train/250818_04.jpg",
946
- "data/yolo_dataset/images/train/250818_05.jpg",
947
- "data/yolo_dataset/images/train/250818_10.jpg",
948
  ]
949
 
950
  # ํŒŒ์ผ์ด ์กด์žฌํ•˜๋Š” ๊ฒƒ๋งŒ ํ•„ํ„ฐ๋ง
@@ -1099,11 +1099,11 @@ with gr.Blocks(title="๐Ÿฆ ์ƒˆ์šฐ ๊ฒ€์ถœ ํ†ตํ•ฉ ์‹œ์Šคํ…œ", theme=gr.themes.Soft
1099
 
1100
  # ์˜ˆ์ œ ์ด๋ฏธ์ง€
1101
  example_images_demo = [
1102
- "data/yolo_dataset/images/train/250818_01.jpg",
1103
- "data/yolo_dataset/images/train/250818_03.jpg",
1104
- "data/yolo_dataset/images/train/250818_04.jpg",
1105
- "data/yolo_dataset/images/train/250818_05.jpg",
1106
- "data/yolo_dataset/images/train/250818_10.jpg",
1107
  ]
1108
 
1109
  # ํŒŒ์ผ์ด ์กด์žฌํ•˜๋Š” ๊ฒƒ๋งŒ ํ•„ํ„ฐ๋ง
 
940
 
941
  # ์˜ˆ์ œ ์ด๋ฏธ์ง€ (๊ฒฐ๊ณผ ํŒŒ์ผ ์ œ์™ธ)
942
  example_images = [
943
+ "examples/250818_01.jpg",
944
+ "examples/250818_03.jpg",
945
+ "examples/250818_04.jpg",
946
+ "examples/250818_05.jpg",
947
+ "examples/250818_10.jpg",
948
  ]
949
 
950
  # ํŒŒ์ผ์ด ์กด์žฌํ•˜๋Š” ๊ฒƒ๋งŒ ํ•„ํ„ฐ๋ง
 
1099
 
1100
  # ์˜ˆ์ œ ์ด๋ฏธ์ง€
1101
  example_images_demo = [
1102
+ "examples/250818_01.jpg",
1103
+ "examples/250818_03.jpg",
1104
+ "examples/250818_04.jpg",
1105
+ "examples/250818_05.jpg",
1106
+ "examples/250818_10.jpg",
1107
  ]
1108
 
1109
  # ํŒŒ์ผ์ด ์กด์žฌํ•˜๋Š” ๊ฒƒ๋งŒ ํ•„ํ„ฐ๋ง
app_backup2.py DELETED
@@ -1,299 +0,0 @@
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
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app_backup3.py DELETED
@@ -1,1342 +0,0 @@
1
- """
2
- ๐Ÿฆ ํฐ๋‹ค๋ฆฌ์ƒˆ์šฐ ๋ถ„์„ ์‹œ์Šคํ…œ - RT-DETR CPU ์ตœ์ ํ™” ๋ฒ„์ „
3
- ์‹ค์ œ ๊ฐ์ฒด ๊ฒ€์ถœ + ์ฒด์žฅ/์ฒด์ค‘ ์ž๋™ ์ถ”์ •
4
- """
5
-
6
- import gradio as gr
7
- import numpy as np
8
- import pandas as pd
9
- import plotly.graph_objects as go
10
- from PIL import Image, ImageDraw, ImageFont
11
- from datetime import datetime
12
- import torch
13
- from transformers import (
14
- RTDetrForObjectDetection,
15
- RTDetrImageProcessor,
16
- AutoImageProcessor,
17
- AutoModelForDepthEstimation
18
- )
19
- import os
20
- import warnings
21
- warnings.filterwarnings('ignore')
22
-
23
- # =====================
24
- # ์‹ค์ธก ๋ฐ์ดํ„ฐ (260๊ฐœ ์ƒ˜ํ”Œ)
25
- # =====================
26
- REAL_DATA = [
27
- {"length": 7.5, "weight": 2.0}, {"length": 7.7, "weight": 2.1},
28
- {"length": 8.3, "weight": 2.7}, {"length": 8.4, "weight": 2.9},
29
- {"length": 8.6, "weight": 3.1}, {"length": 8.7, "weight": 3.0},
30
- {"length": 8.9, "weight": 3.2}, {"length": 9.1, "weight": 3.4},
31
- {"length": 9.4, "weight": 4.0}, {"length": 9.7, "weight": 4.7},
32
- {"length": 9.9, "weight": 4.7}, {"length": 10.0, "weight": 4.6},
33
- {"length": 10.2, "weight": 5.5}, {"length": 10.3, "weight": 5.8},
34
- {"length": 10.4, "weight": 5.5}, {"length": 10.7, "weight": 6.1},
35
- {"length": 10.9, "weight": 6.0}, {"length": 11.0, "weight": 6.2},
36
- {"length": 11.3, "weight": 5.8}, {"length": 11.4, "weight": 6.5},
37
- {"length": 11.6, "weight": 7.5}, {"length": 11.7, "weight": 8.1},
38
- {"length": 11.9, "weight": 9.4}, {"length": 12.0, "weight": 8.8},
39
- {"length": 12.3, "weight": 10.2}, {"length": 12.5, "weight": 10.9},
40
- {"length": 12.7, "weight": 10.1}, {"length": 12.9, "weight": 10.7},
41
- {"length": 13.0, "weight": 10.7}, {"length": 13.1, "weight": 11.3},
42
- ]
43
-
44
- # =====================
45
- # ํšŒ๊ท€ ๋ชจ๋ธ
46
- # =====================
47
- class RegressionModel:
48
- def __init__(self):
49
- self.a = 0.003454
50
- self.b = 3.1298
51
- self.r2 = 0.929
52
- self.mape = 6.4
53
-
54
- def estimate_weight(self, length_cm):
55
- """์ฒด์žฅ์œผ๋กœ ์ฒด์ค‘ ์ถ”์ •: W = a ร— L^b"""
56
- return self.a * (length_cm ** self.b)
57
-
58
- def calculate_error(self, true_weight, pred_weight):
59
- """์˜ค์ฐจ์œจ ๊ณ„์‚ฐ"""
60
- if true_weight == 0:
61
- return 0
62
- return abs(true_weight - pred_weight) / true_weight * 100
63
-
64
- # =====================
65
- # ๊นŠ์ด ์ถ”์ •๊ธฐ
66
- # =====================
67
- class DepthEstimator:
68
- def __init__(self, model_name="depth-anything/Depth-Anything-V2-Small-hf"):
69
- """Depth-Anything V2 ๋ชจ๋ธ ์ดˆ๊ธฐํ™”"""
70
- print(f"๐Ÿ”„ Loading Depth Estimation model: {model_name}")
71
-
72
- self.device = "cuda" if torch.cuda.is_available() else "cpu"
73
-
74
- try:
75
- self.processor = AutoImageProcessor.from_pretrained(model_name)
76
- self.model = AutoModelForDepthEstimation.from_pretrained(model_name)
77
- self.model.to(self.device)
78
- self.model.eval()
79
- print("โœ… Depth model loaded successfully!")
80
- self.enabled = True
81
- except Exception as e:
82
- print(f"โš ๏ธ Depth model loading failed: {e}")
83
- print("๐Ÿ“ Running without depth correction")
84
- self.enabled = False
85
-
86
- @torch.no_grad()
87
- def estimate_depth(self, image):
88
- """์ด๋ฏธ์ง€์—์„œ ๊นŠ์ด ๋งต ์ถ”์ •"""
89
- if not self.enabled or image is None:
90
- return None
91
-
92
- # ์ด๋ฏธ์ง€ ์ „์ฒ˜๋ฆฌ
93
- inputs = self.processor(images=image, return_tensors="pt")
94
- inputs = {k: v.to(self.device) for k, v in inputs.items()}
95
-
96
- # ๊นŠ์ด ์ถ”์ •
97
- outputs = self.model(**inputs)
98
- predicted_depth = outputs.predicted_depth
99
-
100
- # ์›๋ณธ ์ด๋ฏธ์ง€ ํฌ๊ธฐ๋กœ ๋ฆฌ์ƒ˜ํ”Œ๋ง
101
- depth_map = torch.nn.functional.interpolate(
102
- predicted_depth.unsqueeze(1),
103
- size=image.size[::-1], # (height, width)
104
- mode="bicubic",
105
- align_corners=False,
106
- ).squeeze().cpu().numpy()
107
-
108
- # ์ •๊ทœํ™” (0~1 ๋ฒ”์œ„)
109
- depth_min = depth_map.min()
110
- depth_max = depth_map.max()
111
- depth_normalized = (depth_map - depth_min) / (depth_max - depth_min + 1e-8)
112
-
113
- return depth_normalized
114
-
115
- def get_depth_at_bbox(self, depth_map, bbox):
116
- """bbox ์ค‘์‹ฌ์ ์˜ ๊นŠ์ด ๊ฐ’ ์ถ”์ถœ"""
117
- if depth_map is None:
118
- return 1.0 # ๊ธฐ๋ณธ๊ฐ’
119
-
120
- x1, y1, x2, y2 = bbox
121
- center_x = int((x1 + x2) / 2)
122
- center_y = int((y1 + y2) / 2)
123
-
124
- # ๋ฒ”์œ„ ์ฒดํฌ
125
- h, w = depth_map.shape
126
- center_x = min(max(0, center_x), w - 1)
127
- center_y = min(max(0, center_y), h - 1)
128
-
129
- return depth_map[center_y, center_x]
130
-
131
- def visualize_depth(self, depth_map):
132
- """๊นŠ์ด ๋งต ์‹œ๊ฐํ™”"""
133
- if depth_map is None:
134
- return None
135
-
136
- # ๊นŠ์ด ๋งต์„ ์ปฌ๋Ÿฌ๋งต์œผ๋กœ ๋ณ€ํ™˜
137
- import matplotlib.cm as cm
138
- colormap = cm.get_cmap('viridis')
139
- depth_colored = (colormap(depth_map)[:, :, :3] * 255).astype(np.uint8)
140
-
141
- return Image.fromarray(depth_colored)
142
-
143
- # =====================
144
- # RT-DETR ๊ฒ€์ถœ๏ฟฝ๏ฟฝ
145
- # =====================
146
- class RTDetrDetector:
147
- def __init__(self, model_name="PekingU/rtdetr_r50vd_coco_o365"):
148
- """RT-DETR ๋ชจ๋ธ ์ดˆ๊ธฐํ™”"""
149
- print(f"๐Ÿ”„ Loading RT-DETR model: {model_name}")
150
-
151
- # CPU ์ตœ์ ํ™” ์„ค์ •
152
- self.device = "cuda" if torch.cuda.is_available() else "cpu"
153
- print(f"๐Ÿ“ฑ Using device: {self.device}")
154
-
155
- try:
156
- # ๋ชจ๋ธ ๋ฐ ํ”„๋กœ์„ธ์„œ ๋กœ๋”ฉ
157
- self.processor = RTDetrImageProcessor.from_pretrained(model_name)
158
- self.model = RTDetrForObjectDetection.from_pretrained(model_name)
159
- self.model.to(self.device)
160
- self.model.eval() # ํ‰๊ฐ€ ๋ชจ๋“œ
161
-
162
- print("โœ… Model loaded successfully!")
163
- except Exception as e:
164
- print(f"โŒ Model loading failed: {e}")
165
- raise
166
-
167
- self.regression_model = RegressionModel()
168
-
169
- # ๊นŠ์ด ์ถ”์ •๊ธฐ ์ดˆ๊ธฐํ™”
170
- try:
171
- self.depth_estimator = DepthEstimator()
172
- except Exception as e:
173
- print(f"โš ๏ธ Depth estimator initialization failed: {e}")
174
- self.depth_estimator = None
175
-
176
- # ์ฐธ์กฐ ์Šค์ผ€์ผ: ํ”ฝ์…€ ํฌ๊ธฐ๋ฅผ ์‹ค์ œ cm๋กœ ๋ณ€ํ™˜
177
- # ์˜ˆ: 100ํ”ฝ์…€ = 10cm (์ด๋ฏธ์ง€์— ๋”ฐ๋ผ ์กฐ์ • ํ•„์š”)
178
- self.pixel_to_cm_ratio = 0.1 # ๊ธฐ๋ณธ๊ฐ’
179
-
180
- # ๊นŠ์ด ๋ณด์ • ํ™œ์„ฑํ™” ํ”Œ๋ž˜๊ทธ
181
- self.depth_correction_enabled = True
182
-
183
- # ๋งˆ์ง€๋ง‰ ๊นŠ์ด ๋งต ์บ์‹ฑ (UI ํ‘œ์‹œ์šฉ)
184
- self.last_depth_map = None
185
-
186
- def set_scale(self, pixel_length, actual_cm):
187
- """์Šค์ผ€์ผ ์„ค์ • (๋ณด์ •์šฉ)"""
188
- self.pixel_to_cm_ratio = actual_cm / pixel_length
189
- print(f"๐Ÿ“ Scale updated: {pixel_length}px = {actual_cm}cm")
190
-
191
- @torch.no_grad() # CPU ์ตœ์ ํ™”: gradient ๊ณ„์‚ฐ ๋น„ํ™œ์„ฑํ™”
192
- def detect(self, image, confidence_threshold=0.5):
193
- """๊ฐ์ฒด ๊ฒ€์ถœ ์ˆ˜ํ–‰"""
194
-
195
- if image is None:
196
- return []
197
-
198
- # ๊นŠ์ด ๋งต ์ƒ์„ฑ (์›๊ทผ ๋ณด์ •์šฉ)
199
- depth_map = None
200
- if self.depth_correction_enabled and self.depth_estimator and self.depth_estimator.enabled:
201
- print("๐Ÿ” Estimating depth map for perspective correction...")
202
- depth_map = self.depth_estimator.estimate_depth(image)
203
- self.last_depth_map = depth_map
204
-
205
- # ์ฐธ์กฐ ๊นŠ์ด (์ด๋ฏธ์ง€ ์ค‘์‹ฌ์˜ ํ‰๊ท  ๊นŠ์ด)
206
- h, w = depth_map.shape
207
- center_region = depth_map[h//4:3*h//4, w//4:3*w//4]
208
- self.reference_depth = np.median(center_region)
209
- print(f"๐Ÿ“ Reference depth: {self.reference_depth:.3f}")
210
-
211
- # ์ด๋ฏธ์ง€ ์ „์ฒ˜๋ฆฌ
212
- inputs = self.processor(images=image, return_tensors="pt")
213
- inputs = {k: v.to(self.device) for k, v in inputs.items()}
214
-
215
- # ์ถ”๋ก 
216
- outputs = self.model(**inputs)
217
-
218
- # ๊ฒฐ๊ณผ ํ›„์ฒ˜๋ฆฌ
219
- target_sizes = torch.tensor([image.size[::-1]]) # (height, width)
220
- results = self.processor.post_process_object_detection(
221
- outputs,
222
- target_sizes=target_sizes,
223
- threshold=confidence_threshold
224
- )[0]
225
-
226
- # ๊ฒ€์ถœ ๊ฒฐ๊ณผ ํŒŒ์‹ฑ
227
- detections = []
228
-
229
- for idx, (score, label, box) in enumerate(zip(
230
- results["scores"],
231
- results["labels"],
232
- results["boxes"]
233
- )):
234
- # COCO ํด๋ž˜์Šค ํ•„ํ„ฐ๋ง (ํ•„์š”์‹œ)
235
- # ์ƒˆ์šฐ ์ „์šฉ ๋ชจ๋ธ์ด ์•„๋‹ˆ๋ฏ€๋กœ ๋ชจ๋“  ๊ฐ์ฒด ๊ฒ€์ถœ
236
- # label 1 = "person", 16 = "bird", 17 = "cat" ๋“ฑ
237
- # ์ผ๋‹จ ๋ชจ๋“  ๊ฐ์ฒด๋ฅผ ๊ฒ€์ถœํ•˜๋˜, ํ–ฅํ›„ fine-tuning ์‹œ ์ƒˆ์šฐ๋งŒ ๊ฒ€์ถœ
238
-
239
- x1, y1, x2, y2 = box.tolist()
240
- bbox_width = x2 - x1
241
- bbox_height = y2 - y1
242
-
243
- # ๊นŠ์ด ๊ธฐ๋ฐ˜ ์Šค์ผ€์ผ ๋ณด์ •
244
- depth_corrected_ratio = self.pixel_to_cm_ratio
245
-
246
- if depth_map is not None:
247
- # bbox ์ค‘์‹ฌ์ ์˜ ๊นŠ์ด ๊ฐ’ ์ถ”์ถœ
248
- object_depth = self.depth_estimator.get_depth_at_bbox(depth_map, [x1, y1, x2, y2])
249
-
250
- # ๊นŠ์ด ๋น„์œจ ๊ณ„์‚ฐ (์ฐธ์กฐ ๊นŠ์ด ๋Œ€๋น„)
251
- # ๊นŠ์ด ๊ฐ’์ด ํด์ˆ˜๋ก ๋จผ ๊ฑฐ๋ฆฌ โ†’ ์‹ค์ œ ํฌ๊ธฐ๊ฐ€ ๋” ํผ
252
- # Depth-Anything์—์„œ: ์ž‘์€ ๊ฐ’ = ๊ฐ€๊นŒ์›€, ํฐ ๊ฐ’ = ๋ฉ€์Œ
253
- depth_ratio = object_depth / (self.reference_depth + 1e-8)
254
-
255
- # ์Šค์ผ€์ผ ๋ณด์ • (์›๊ทผ ํšจ๊ณผ ๋ณด์ •)
256
- # ๋จผ ๋ฌผ์ฒด(ํฐ depth)๋Š” ํ”ฝ์…€์ด ์ž‘์•„ ๋ณด์ด๋ฏ€๋กœ ๋ณด์ • ๊ณ„์ˆ˜๋ฅผ ํฌ๊ฒŒ
257
- depth_corrected_ratio = self.pixel_to_cm_ratio * depth_ratio
258
-
259
- print(f" Object #{idx+1}: depth={object_depth:.3f}, ratio={depth_ratio:.3f}, corrected_scale={depth_corrected_ratio:.4f}")
260
-
261
- # ์ฒด์žฅ ์ถ”์ •: bbox์˜ ๊ธด ๋ณ€์„ ์ฒด์žฅ์œผ๋กœ ๊ฐ„์ฃผ
262
- length_pixels = max(bbox_width, bbox_height)
263
- length_cm = length_pixels * depth_corrected_ratio
264
-
265
- # ์ฒด์ค‘ ์ถ”์ •
266
- pred_weight = self.regression_model.estimate_weight(length_cm)
267
-
268
- detections.append({
269
- "id": idx + 1,
270
- "bbox": [x1, y1, x2, y2],
271
- "length": round(length_cm, 1),
272
- "pred_weight": round(pred_weight, 2),
273
- "confidence": round(score.item(), 2),
274
- "label": label.item()
275
- })
276
-
277
- return detections
278
-
279
- def visualize(self, image, detections):
280
- """๊ฒ€์ถœ ๊ฒฐ๊ณผ ์‹œ๊ฐํ™”"""
281
- if image is None:
282
- return None
283
-
284
- img = image.copy()
285
- draw = ImageDraw.Draw(img)
286
-
287
- # ํฐํŠธ ์„ค์ • (๊ธฐ๋ณธ ํฐํŠธ ์‚ฌ์šฉ)
288
- try:
289
- font = ImageFont.truetype("arial.ttf", 12)
290
- except:
291
- font = ImageFont.load_default()
292
-
293
- for det in detections:
294
- x1, y1, x2, y2 = det["bbox"]
295
-
296
- # ๊ณ ์ • ์ƒ‰์ƒ (๋…น์ƒ‰)
297
- color = "lime"
298
-
299
- # ๋ฐ•์Šค ๊ทธ๋ฆฌ๊ธฐ
300
- draw.rectangle([x1, y1, x2, y2], outline=color, width=3)
301
-
302
- # ๋ผ๋ฒจ
303
- label = f"#{det['id']} {det['length']}cm {det['pred_weight']}g ({det['confidence']:.0%})"
304
-
305
- # ๋ฐฐ๊ฒฝ ๋ฐ•์Šค
306
- bbox = draw.textbbox((x1, y1 - 20), label, font=font)
307
- draw.rectangle(bbox, fill=color)
308
- draw.text((x1, y1 - 20), label, fill="white", font=font)
309
-
310
- return img
311
-
312
- def visualize_with_groundtruth(self, image, detection, true_length, true_weight, sample_id):
313
- """๊ฒ€์ถœ ๊ฒฐ๊ณผ์™€ ์‹ค์ธก๊ฐ’์„ ํ•จ๊ป˜ ์‹œ๊ฐํ™”"""
314
- if image is None:
315
- return None
316
-
317
- img = image.copy()
318
- draw = ImageDraw.Draw(img)
319
-
320
- # ํฐํŠธ ์„ค์ •
321
- try:
322
- font_large = ImageFont.truetype("arial.ttf", 16)
323
- font_small = ImageFont.truetype("arial.ttf", 12)
324
- except:
325
- font_large = ImageFont.load_default()
326
- font_small = ImageFont.load_default()
327
-
328
- # Bounding box ๊ทธ๋ฆฌ๊ธฐ
329
- x1, y1, x2, y2 = detection["bbox"]
330
-
331
- # ์˜ค์ฐจ์œจ๋กœ ์ƒ‰์ƒ ๊ฒฐ์ •
332
- error_weight = abs(detection["pred_weight"] - true_weight) / true_weight * 100
333
- if error_weight < 10:
334
- color = "lime" # ๋…น์ƒ‰: ์šฐ์ˆ˜
335
- elif error_weight < 25:
336
- color = "orange" # ์ฃผํ™ฉ: ์–‘ํ˜ธ
337
- else:
338
- color = "red" # ๋นจ๊ฐ•: ๊ฐœ์„  ํ•„์š”
339
-
340
- # ๋ฐ•์Šค ๊ทธ๋ฆฌ๊ธฐ
341
- draw.rectangle([x1, y1, x2, y2], outline=color, width=4)
342
-
343
- # ์˜ˆ์ธก๊ฐ’ ๋ผ๋ฒจ (bbox ์œ„)
344
- pred_label = f"์˜ˆ์ธก: {detection['length']:.1f}cm / {detection['pred_weight']:.1f}g"
345
- bbox_pred = draw.textbbox((x1, y1 - 40), pred_label, font=font_small)
346
- draw.rectangle(bbox_pred, fill=color)
347
- draw.text((x1, y1 - 40), pred_label, fill="white", font=font_small)
348
-
349
- # ์‹ค์ธก๊ฐ’ ๋ผ๋ฒจ (bbox ์œ„, ์˜ˆ์ธก๊ฐ’ ์•„๋ž˜)
350
- true_label = f"์‹ค์ œ: {true_length:.1f}cm / {true_weight:.1f}g"
351
- bbox_true = draw.textbbox((x1, y1 - 20), true_label, font=font_small)
352
- draw.rectangle(bbox_true, fill="blue")
353
- draw.text((x1, y1 - 20), true_label, fill="white", font=font_small)
354
-
355
- # ์ด๋ฏธ์ง€ ์ƒ๋‹จ์— ์ƒ˜ํ”Œ ID์™€ ์˜ค์ฐจ์œจ ํ‘œ์‹œ
356
- header = f"์ƒ˜ํ”Œ #{sample_id} | ์ฒด์žฅ ์˜ค์ฐจ: {abs(detection['length']-true_length)/true_length*100:.1f}% | ์ฒด์ค‘ ์˜ค์ฐจ: {error_weight:.1f}%"
357
- header_bbox = draw.textbbox((10, 10), header, font=font_large)
358
- draw.rectangle([5, 5, header_bbox[2]+5, header_bbox[3]+5], fill="black", outline=color, width=3)
359
- draw.text((10, 10), header, fill=color, font=font_large)
360
-
361
- return img
362
-
363
- # =====================
364
- # Roboflow ๊ฒ€์ถœ๊ธฐ
365
- # =====================
366
- class RoboflowDetector:
367
- """Roboflow ์ƒˆ์šฐ ์ „์šฉ ๊ฒ€์ถœ๊ธฐ"""
368
-
369
- def __init__(self, api_key="azcIL8KDJVJMYrsERzI7", model_id="shrimp-konvey/2"):
370
- """Roboflow ๋ชจ๋ธ ์ดˆ๊ธฐํ™”"""
371
- print(f"๐Ÿ”„ Loading Roboflow model: {model_id}")
372
-
373
- try:
374
- from inference_sdk import InferenceHTTPClient, InferenceConfiguration
375
-
376
- self.client = InferenceHTTPClient(
377
- api_url="https://serverless.roboflow.com",
378
- api_key=api_key
379
- )
380
- self.model_id = model_id
381
- self.regression_model = RegressionModel()
382
-
383
- # ์Šค์ผ€์ผ ์„ค์ •
384
- self.pixel_to_cm_ratio = 0.01 # ๊ธฐ๋ณธ๊ฐ’ (์ถ”ํ›„ ์กฐ์ •)
385
-
386
- # ๊ธฐ๋ณธ ์„ค์ •๊ฐ’
387
- self.iou_threshold = 0.5
388
-
389
- print("โœ… Roboflow model loaded successfully!")
390
- except Exception as e:
391
- print(f"โŒ Roboflow model loading failed: {e}")
392
- raise
393
-
394
- def set_scale(self, pixel_length, actual_cm):
395
- """์Šค์ผ€์ผ ์„ค์ •"""
396
- self.pixel_to_cm_ratio = actual_cm / pixel_length
397
- print(f"๐Ÿ“ Roboflow scale updated: {pixel_length}px = {actual_cm}cm")
398
-
399
- def set_iou_threshold(self, iou_threshold):
400
- """IoU ์ž„๊ณ„๊ฐ’ ์„ค์ •"""
401
- self.iou_threshold = iou_threshold
402
- print(f"๐ŸŽฏ Roboflow IoU threshold updated: {iou_threshold:.2f}")
403
-
404
- def detect(self, image, confidence_threshold=0.5):
405
- """
406
- ์ƒˆ์šฐ ๊ฒ€์ถœ
407
-
408
- Args:
409
- image: PIL Image
410
- confidence_threshold: ์‹ ๋ขฐ๋„ ์ž„๊ณ„๊ฐ’
411
-
412
- Returns:
413
- List[Dict]: ๊ฒ€์ถœ ๊ฒฐ๊ณผ
414
- """
415
- import tempfile
416
- import os as os_module
417
- from inference_sdk import InferenceConfiguration
418
-
419
- # ์ž„์‹œ ํŒŒ์ผ๋กœ ์ €์žฅ (API๊ฐ€ ํŒŒ์ผ ๊ฒฝ๋กœ ํ•„์š”)
420
- with tempfile.NamedTemporaryFile(suffix='.jpg', delete=False) as tmp:
421
- # RGB๋กœ ๋ณ€ํ™˜
422
- if image.mode != 'RGB':
423
- image = image.convert('RGB')
424
- image.save(tmp.name, quality=95)
425
- tmp_path = tmp.name
426
-
427
- try:
428
- # API ํ˜ธ์ถœ with configuration
429
- custom_config = InferenceConfiguration(
430
- confidence_threshold=confidence_threshold,
431
- iou_threshold=self.iou_threshold
432
- )
433
-
434
- with self.client.use_configuration(custom_config):
435
- result = self.client.infer(tmp_path, model_id=self.model_id)
436
-
437
- # ๊ฒฐ๊ณผ ๋ณ€ํ™˜ (API๊ฐ€ ์ด๋ฏธ confidence/iou ํ•„ํ„ฐ๋ง ์™„๋ฃŒ)
438
- detections = []
439
- for idx, pred in enumerate(result["predictions"], 1):
440
- x = pred["x"]
441
- y = pred["y"]
442
- w = pred["width"]
443
- h = pred["height"]
444
-
445
- # ๋ฐ”์šด๋”ฉ ๋ฐ•์Šค (x1, y1, x2, y2)
446
- bbox = [x - w/2, y - h/2, x + w/2, y + h/2]
447
-
448
- # ์ฒด์žฅ ์ถ”์ • (ํ”ฝ์…€)
449
- length_pixels = max(w, h)
450
- length_cm = length_pixels * self.pixel_to_cm_ratio
451
-
452
- # ์ฒด์ค‘ ์ถ”์ •
453
- pred_weight = self.regression_model.predict(length_cm)
454
-
455
- detections.append({
456
- "id": idx,
457
- "bbox": bbox,
458
- "confidence": pred["confidence"],
459
- "length": length_cm,
460
- "pred_weight": pred_weight,
461
- "source": "roboflow"
462
- })
463
-
464
- return detections
465
-
466
- finally:
467
- # ์ž„์‹œ ํŒŒ์ผ ์‚ญ์ œ
468
- if os_module.path.exists(tmp_path):
469
- os_module.unlink(tmp_path)
470
-
471
- def visualize(self, image, detections):
472
- """๊ฒ€์ถœ ๊ฒฐ๊ณผ ์‹œ๊ฐํ™”"""
473
- if image is None:
474
- return None
475
-
476
- img = image.copy()
477
- draw = ImageDraw.Draw(img)
478
-
479
- # ํฐํŠธ ์„ค์ •
480
- try:
481
- font = ImageFont.truetype("arial.ttf", 12)
482
- except:
483
- font = ImageFont.load_default()
484
-
485
- for det in detections:
486
- x1, y1, x2, y2 = det["bbox"]
487
-
488
- # ์‹ ๋ขฐ๋„์— ๋”ฐ๋ผ ์ƒ‰์ƒ ์„ ํƒ
489
- confidence = det["confidence"]
490
- if confidence > 0.8:
491
- color = "lime"
492
- elif confidence > 0.6:
493
- color = "orange"
494
- else:
495
- color = "yellow"
496
-
497
- # ๋ฐ•์Šค ๊ทธ๋ฆฌ๊ธฐ
498
- draw.rectangle([x1, y1, x2, y2], outline=color, width=3)
499
-
500
- # ๋ผ๋ฒจ
501
- label = f"#{det['id']} {det['length']:.1f}cm {det['pred_weight']:.1f}g ({det['confidence']:.0%})"
502
-
503
- # ๋ฐฐ๊ฒฝ ๋ฐ•์Šค
504
- bbox = draw.textbbox((x1, y1 - 20), label, font=font)
505
- draw.rectangle(bbox, fill=color)
506
- draw.text((x1, y1 - 20), label, fill="black", font=font)
507
-
508
- return img
509
-
510
- def visualize_with_groundtruth(self, image, detection, true_length, true_weight, sample_id):
511
- """๊ฒ€์ถœ ๊ฒฐ๊ณผ์™€ ์‹ค์ธก๊ฐ’์„ ํ•จ๊ป˜ ์‹œ๊ฐํ™”"""
512
- if image is None:
513
- return None
514
-
515
- img = image.copy()
516
- draw = ImageDraw.Draw(img)
517
-
518
- # ํฐํŠธ ์„ค์ •
519
- try:
520
- font_large = ImageFont.truetype("arial.ttf", 16)
521
- font_small = ImageFont.truetype("arial.ttf", 12)
522
- except:
523
- font_large = ImageFont.load_default()
524
- font_small = ImageFont.load_default()
525
-
526
- # Bounding box ๊ทธ๋ฆฌ๊ธฐ
527
- x1, y1, x2, y2 = detection["bbox"]
528
-
529
- # ์˜ค์ฐจ์œจ๋กœ ์ƒ‰์ƒ ๊ฒฐ์ •
530
- error_weight = abs(detection["pred_weight"] - true_weight) / true_weight * 100
531
- if error_weight < 10:
532
- color = "lime"
533
- elif error_weight < 25:
534
- color = "orange"
535
- else:
536
- color = "red"
537
-
538
- # ๋ฐ•์Šค ๊ทธ๋ฆฌ๊ธฐ
539
- draw.rectangle([x1, y1, x2, y2], outline=color, width=4)
540
-
541
- # ์˜ˆ์ธก๊ฐ’ ๋ผ๋ฒจ
542
- pred_label = f"์˜ˆ์ธก: {detection['length']:.1f}cm / {detection['pred_weight']:.1f}g"
543
- bbox_pred = draw.textbbox((x1, y1 - 40), pred_label, font=font_small)
544
- draw.rectangle(bbox_pred, fill=color)
545
- draw.text((x1, y1 - 40), pred_label, fill="white", font=font_small)
546
-
547
- # ์‹ค์ธก๊ฐ’ ๋ผ๋ฒจ
548
- true_label = f"์‹ค์ œ: {true_length:.1f}cm / {true_weight:.1f}g"
549
- bbox_true = draw.textbbox((x1, y1 - 20), true_label, font=font_small)
550
- draw.rectangle(bbox_true, fill="blue")
551
- draw.text((x1, y1 - 20), true_label, fill="white", font=font_small)
552
-
553
- # ํ—ค๋”
554
- header = f"์ƒ˜ํ”Œ #{sample_id} [Roboflow] | ์ฒด๏ฟฝ๏ฟฝ ์˜ค์ฐจ: {abs(detection['length']-true_length)/true_length*100:.1f}% | ์ฒด์ค‘ ์˜ค์ฐจ: {error_weight:.1f}%"
555
- header_bbox = draw.textbbox((10, 10), header, font=font_large)
556
- draw.rectangle([5, 5, header_bbox[2]+5, header_bbox[3]+5], fill="black", outline=color, width=3)
557
- draw.text((10, 10), header, fill=color, font=font_large)
558
-
559
- return img
560
-
561
- # =====================
562
- # ์ „์—ญ ์ธ์Šคํ„ด์Šค (๋ชจ๋ธ ์บ์‹ฑ)
563
- # =====================
564
-
565
- # ๋ชจ๋ธ ์ดˆ๊ธฐํ™”
566
- print("๐Ÿš€ Initializing models...")
567
-
568
- # RT-DETR ๊ฒ€์ถœ๊ธฐ
569
- try:
570
- rtdetr_detector = RTDetrDetector()
571
- RTDETR_LOADED = True
572
- print("โœ… RT-DETR loaded")
573
- except Exception as e:
574
- print(f"โš ๏ธ RT-DETR failed: {e}")
575
- RTDETR_LOADED = False
576
- rtdetr_detector = None
577
-
578
- # Roboflow ๊ฒ€์ถœ๊ธฐ
579
- try:
580
- roboflow_detector = RoboflowDetector()
581
- ROBOFLOW_LOADED = True
582
- print("โœ… Roboflow loaded")
583
- except Exception as e:
584
- print(f"โš ๏ธ Roboflow failed: {e}")
585
- ROBOFLOW_LOADED = False
586
- roboflow_detector = None
587
-
588
- # ๊ธฐ๋ณธ ๊ฒ€์ถœ๊ธฐ ์„ค์ • (Roboflow ์šฐ์„ , ์—†์œผ๋ฉด RT-DETR)
589
- if ROBOFLOW_LOADED:
590
- detector = roboflow_detector
591
- MODEL_LOADED = True
592
- print("๐ŸŽฏ Default detector: Roboflow")
593
- elif RTDETR_LOADED:
594
- detector = rtdetr_detector
595
- MODEL_LOADED = True
596
- print("๐ŸŽฏ Default detector: RT-DETR")
597
- else:
598
- detector = None
599
- MODEL_LOADED = False
600
- print("โŒ No models loaded - simulation mode")
601
-
602
- regression_model = RegressionModel()
603
-
604
- # =====================
605
- # Gradio ์ธํ„ฐํŽ˜์ด์Šค ํ•จ์ˆ˜
606
- # =====================
607
-
608
- def process_image(image, model_choice, confidence, iou_threshold, pixel_scale, cm_scale, enable_depth):
609
- """์ด๋ฏธ์ง€ ์ฒ˜๋ฆฌ ๋ฐ ๋ถ„์„"""
610
-
611
- if not MODEL_LOADED:
612
- return None, None, "โŒ ๋ชจ๋ธ ๋กœ๋”ฉ ์‹คํŒจ. requirements.txt๋ฅผ ํ™•์ธํ•˜์„ธ์š”.", pd.DataFrame()
613
-
614
- if image is None:
615
- return None, None, "โš ๏ธ ์ด๋ฏธ์ง€๋ฅผ ์—…๋กœ๋“œํ•˜์„ธ์š”.", pd.DataFrame()
616
-
617
- # ๋ชจ๋ธ ์„ ํƒ
618
- if model_choice == "roboflow":
619
- if not ROBOFLOW_LOADED:
620
- return None, None, "โŒ Roboflow ๋ชจ๋ธ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. RT-DETR์„ ์„ ํƒํ•˜์„ธ์š”.", pd.DataFrame()
621
- current_detector = roboflow_detector
622
- model_name = "Roboflow (์ƒˆ์šฐ ์ „์šฉ)"
623
- # IoU ์ž„๊ณ„๊ฐ’ ์„ค์ • (Roboflow๋งŒ)
624
- current_detector.set_iou_threshold(iou_threshold)
625
- else: # rtdetr
626
- if not RTDETR_LOADED:
627
- return None, None, "โŒ RT-DETR ๋ชจ๋ธ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. Roboflow๋ฅผ ์„ ํƒํ•˜์„ธ์š”.", pd.DataFrame()
628
- current_detector = rtdetr_detector
629
- model_name = "RT-DETR (๋ฒ”์šฉ)"
630
- # ๊นŠ์ด ๋ณด์ • ํ™œ์„ฑํ™”/๋น„ํ™œ์„ฑํ™” (RT-DETR๋งŒ ์ง€์›)
631
- current_detector.depth_correction_enabled = enable_depth
632
-
633
- # ์Šค์ผ€์ผ ์—…๋ฐ์ดํŠธ
634
- if pixel_scale > 0 and cm_scale > 0:
635
- current_detector.set_scale(pixel_scale, cm_scale)
636
-
637
- # ๊ฒ€์ถœ ์ˆ˜ํ–‰
638
- detections = current_detector.detect(image, confidence)
639
-
640
- if not detections:
641
- return image, None, f"โš ๏ธ [{model_name}] ๊ฒ€์ถœ๋œ ๊ฐ์ฒด๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. ์‹ ๋ขฐ๋„๋ฅผ ๋‚ฎ์ถฐ๋ณด์„ธ์š”.", pd.DataFrame()
642
-
643
- # ์‹œ๊ฐํ™”
644
- result_image = current_detector.visualize(image, detections)
645
-
646
- # ๊นŠ์ด ๋งต ์‹œ๊ฐํ™” (RT-DETR๋งŒ)
647
- depth_vis = None
648
- if model_choice == "rtdetr" and enable_depth and hasattr(current_detector, 'depth_estimator') and current_detector.depth_estimator and current_detector.last_depth_map is not None:
649
- depth_vis = current_detector.depth_estimator.visualize_depth(current_detector.last_depth_map)
650
-
651
- # ํ†ต๊ณ„ ๊ณ„์‚ฐ
652
- avg_length = np.mean([d["length"] for d in detections])
653
- avg_weight = np.mean([d["pred_weight"] for d in detections])
654
- total_biomass = sum([d["pred_weight"] for d in detections])
655
-
656
- # ํ†ต๊ณ„ ํ…์ŠคํŠธ
657
- depth_status = "โœ… ํ™œ์„ฑํ™”" if (model_choice == "rtdetr" and enable_depth) else "โš ๏ธ ๋น„ํ™œ์„ฑํ™”"
658
- stats_text = f"""
659
- ### ๐Ÿ“Š ๊ฒ€์ถœ ๊ฒฐ๊ณผ
660
-
661
- - **์‚ฌ์šฉ ๋ชจ๋ธ**: {model_name}
662
- - **๊ฒ€์ถœ ๊ฐœ์ฒด ์ˆ˜**: {len(detections)}๋งˆ๋ฆฌ
663
- - **ํ‰๊ท  ์ฒด์žฅ**: {avg_length:.1f}cm
664
- - **ํ‰๊ท  ์˜ˆ์ธก ์ฒด์ค‘**: {avg_weight:.1f}g
665
- - **์ด ๋ฐ”์ด์˜ค๋งค์Šค**: {total_biomass:.1f}g
666
- - **๊นŠ์ด ๋ณด์ •**: {depth_status}
667
-
668
- ๐Ÿ’ก **ํŒ**: ์ •ํ™•๋„ ๊ฒ€์ฆ์€ "์ •ํ™•๋„ ๊ฒ€์ฆ" ํƒญ์—์„œ ์‹ค์ œ ๋ฐ์ดํ„ฐ์™€ ๋น„๊ตํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
669
- """
670
-
671
- # ๊ฒฐ๊ณผ ํ…Œ์ด๋ธ”
672
- df_data = []
673
- for d in detections:
674
- df_data.append({
675
- "ID": f"#{d['id']}",
676
- "์ฒด์žฅ(cm)": d["length"],
677
- "์˜ˆ์ธก ์ฒด์ค‘(g)": d["pred_weight"],
678
- "์‹ ๋ขฐ๋„": f"{d['confidence']:.0%}"
679
- })
680
-
681
- df = pd.DataFrame(df_data)
682
-
683
- return result_image, depth_vis, stats_text, df
684
-
685
- def evaluate_model():
686
- """๋ชจ๋ธ ์„ฑ๋Šฅ ํ‰๊ฐ€"""
687
-
688
- # ์‹ค์ธก ๋ฐ์ดํ„ฐ๋กœ ํ‰๊ฐ€
689
- predictions = []
690
- actuals = []
691
-
692
- for sample in REAL_DATA:
693
- pred = regression_model.estimate_weight(sample["length"])
694
- predictions.append(pred)
695
- actuals.append(sample["weight"])
696
-
697
- # ๋ฉ”๏ฟฝ๏ฟฝ๏ฟฝ๋ฆญ ๊ณ„์‚ฐ
698
- errors = [abs(p - a) / a * 100 for p, a in zip(predictions, actuals)]
699
- mape = np.mean(errors)
700
- mae = np.mean([abs(p - a) for p, a in zip(predictions, actuals)])
701
- rmse = np.sqrt(np.mean([(p - a) ** 2 for p, a in zip(predictions, actuals)]))
702
-
703
- # Rยฒ ๊ณ„์‚ฐ
704
- mean_actual = np.mean(actuals)
705
- ss_tot = sum([(a - mean_actual) ** 2 for a in actuals])
706
- ss_res = sum([(a - p) ** 2 for a, p in zip(actuals, predictions)])
707
- r2 = 1 - (ss_res / ss_tot)
708
-
709
- eval_text = f"""
710
- ### ๐ŸŽฏ ํšŒ๊ท€ ๋ชจ๋ธ ์„ฑ๋Šฅ ํ‰๊ฐ€
711
-
712
- **๋ฐ์ดํ„ฐ์…‹**: {len(REAL_DATA)}๊ฐœ ์‹ค์ธก ์ƒ˜ํ”Œ
713
-
714
- **์„ฑ๋Šฅ ์ง€ํ‘œ**:
715
- - Rยฒ Score: **{r2:.4f}** (92.9% ์„ค๋ช…๋ ฅ)
716
- - MAPE: **{mape:.1f}%** (๋ชฉํ‘œ 25% ์ด๋‚ด โœ…)
717
- - MAE: **{mae:.2f}g**
718
- - RMSE: **{rmse:.2f}g**
719
-
720
- **๋ชจ๋ธ ์‹**: W = {regression_model.a:.6f} ร— L^{regression_model.b:.4f}
721
-
722
- **๊ฒฐ๋ก **: โœ… ์ƒ์šฉํ™” ๊ฐ€๋Šฅ ์ˆ˜์ค€์˜ ์ •ํ™•๋„
723
- """
724
-
725
- # ์ฐจํŠธ ์ƒ์„ฑ
726
- fig = go.Figure()
727
-
728
- # ์‹ค์ธก ๋ฐ์ดํ„ฐ
729
- fig.add_trace(go.Scatter(
730
- x=[d["length"] for d in REAL_DATA],
731
- y=[d["weight"] for d in REAL_DATA],
732
- mode='markers',
733
- name='์‹ค์ธก ๋ฐ์ดํ„ฐ',
734
- marker=dict(color='blue', size=10, opacity=0.6)
735
- ))
736
-
737
- # ํšŒ๊ท€์„ 
738
- x_line = np.linspace(7, 14, 100)
739
- y_line = [regression_model.estimate_weight(x) for x in x_line]
740
-
741
- fig.add_trace(go.Scatter(
742
- x=x_line,
743
- y=y_line,
744
- mode='lines',
745
- name=f'ํšŒ๊ท€ ๋ชจ๋ธ (Rยฒ={r2:.3f})',
746
- line=dict(color='red', width=3)
747
- ))
748
-
749
- # ์˜ˆ์ธก๊ฐ’
750
- fig.add_trace(go.Scatter(
751
- x=[d["length"] for d in REAL_DATA],
752
- y=predictions,
753
- mode='markers',
754
- name='์˜ˆ์ธก๊ฐ’',
755
- marker=dict(color='red', size=8, opacity=0.4, symbol='x')
756
- ))
757
-
758
- fig.update_layout(
759
- title="ํฐ๋‹ค๋ฆฌ์ƒˆ์šฐ ์ฒด์žฅ-์ฒด์ค‘ ํšŒ๊ท€ ๋ถ„์„",
760
- xaxis_title="์ฒด์žฅ (cm)",
761
- yaxis_title="์ฒด์ค‘ (g)",
762
- template="plotly_white",
763
- height=500,
764
- hovermode='closest'
765
- )
766
-
767
- return eval_text, fig
768
-
769
- def export_data():
770
- """๋ฐ์ดํ„ฐ ๋‚ด๋ณด๋‚ด๊ธฐ"""
771
- df = pd.DataFrame(REAL_DATA)
772
- csv_path = f"shrimp_data_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv"
773
- df.to_csv(csv_path, index=False)
774
-
775
- return csv_path
776
-
777
- # =====================
778
- # ์—‘์…€ ๋ฐ์ดํ„ฐ ๋ฐ ๋ฐฐ์น˜ ํ…Œ์ŠคํŠธ
779
- # =====================
780
-
781
- def load_excel_data(excel_path, date_serial=45945):
782
- """
783
- ์—‘์…€ ํŒŒ์ผ์—์„œ ํŠน์ • ๋‚ ์งœ์˜ ๋ฐ์ดํ„ฐ ์ฝ๊ธฐ
784
- date_serial: 45945 = 2025-10-15 (251015)
785
- """
786
- try:
787
- # Sheet1 ์ฝ๊ธฐ (ํ—ค๋” ์—†์ด)
788
- df = pd.read_excel(excel_path, sheet_name='Sheet1', header=None)
789
-
790
- # Row 4: ๋‚ ์งœ ํ–‰, Row 5: ํ—ค๋” ํ–‰
791
- date_row = df.iloc[4]
792
- header_row = df.iloc[5]
793
-
794
- # ํ•ด๋‹น ๋‚ ์งœ ์ปฌ๋Ÿผ ์ฐพ๊ธฐ
795
- for col_idx in range(len(date_row)):
796
- if date_row[col_idx] == date_serial:
797
- # ๋ฐ์ดํ„ฐ ์ถ”์ถœ
798
- data_dict = {}
799
- for row_idx in range(6, len(df)): # ๋ฐ์ดํ„ฐ๋Š” row 6๋ถ€ํ„ฐ
800
- no = df.iloc[row_idx, 1] # No. ์ปฌ๋Ÿผ
801
- length = df.iloc[row_idx, col_idx]
802
- weight = df.iloc[row_idx, col_idx + 1] if col_idx + 1 < len(df.columns) else None
803
-
804
- if pd.notna(no) and pd.notna(length):
805
- data_dict[int(no)] = {
806
- 'length': float(length),
807
- 'weight': float(weight) if pd.notna(weight) else None
808
- }
809
-
810
- print(f"โœ… Loaded {len(data_dict)} samples from Excel for date {date_serial}")
811
- return data_dict
812
-
813
- print(f"โš ๏ธ Date {date_serial} not found in Excel")
814
- return None
815
-
816
- except Exception as e:
817
- print(f"โŒ Excel loading error: {e}")
818
- return None
819
-
820
- def process_test_dataset(data_folder, pixel_scale, cm_scale, enable_depth):
821
- """ํ…Œ์ŠคํŠธ ๋ฐ์ดํ„ฐ์…‹ ๋ฐฐ์น˜ ์ฒ˜๋ฆฌ"""
822
-
823
- if not MODEL_LOADED:
824
- return "โŒ ๋ชจ๋ธ ๋กœ๋”ฉ ์‹คํŒจ", pd.DataFrame(), None, []
825
-
826
- if not os.path.exists(data_folder):
827
- return f"โŒ ํด๋”๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค: {data_folder}", pd.DataFrame(), None, []
828
-
829
- # ์—‘์…€ ๋ฐ์ดํ„ฐ ๋กœ๋“œ
830
- excel_path = os.path.join(os.path.dirname(data_folder), 'ํฐ๋‹ค๋ฆฌ์ƒˆ์šฐ ์‹ค์ธก ๋ฐ์ดํ„ฐ(์ง„ํ–‰).xlsx')
831
- excel_data = load_excel_data(excel_path, date_serial=45945) # 251015
832
-
833
- if not excel_data:
834
- return "โŒ ์—‘์…€ ๋ฐ์ดํ„ฐ๋ฅผ ๋กœ๋“œํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค", pd.DataFrame(), None, []
835
-
836
- # ์ด๋ฏธ์ง€ ์ฐพ๊ธฐ
837
- image_list = []
838
- for i in range(1, 20): # ์ตœ๋Œ€ 20๊ฐœ๊นŒ์ง€ ํ™•์ธ
839
- shrimp_img = os.path.join(data_folder, f"251015_{i:02d}.jpg")
840
-
841
- if os.path.exists(shrimp_img) and i in excel_data:
842
- image_list.append((shrimp_img, i))
843
-
844
- if not image_list:
845
- return "โŒ ์ด๋ฏธ์ง€๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค", pd.DataFrame(), None, []
846
-
847
- print(f"\n๐Ÿ“Š Processing {len(image_list)} images...")
848
-
849
- # ์Šค์ผ€์ผ ์„ค์ •
850
- if pixel_scale > 0 and cm_scale > 0:
851
- detector.set_scale(pixel_scale, cm_scale)
852
-
853
- # ๊นŠ์ด ๋ณด์ • ์„ค์ •
854
- detector.depth_correction_enabled = enable_depth
855
-
856
- results = []
857
- visualized_images = [] # ์‹œ๊ฐํ™”๋œ ์ด๋ฏธ์ง€ ์ €์žฅ
858
-
859
- # ๊ฒฐ๊ณผ ์ €์žฅ ํด๋” ์ƒ์„ฑ
860
- results_folder = os.path.join(data_folder, "results")
861
- os.makedirs(results_folder, exist_ok=True)
862
-
863
- for shrimp_path, idx in image_list:
864
- print(f"\n๐Ÿ” Processing image #{idx}...")
865
-
866
- # 1. ์ƒˆ์šฐ ์ด๋ฏธ์ง€ ๊ฒ€์ถœ
867
- shrimp_img = Image.open(shrimp_path)
868
- detections = detector.detect(shrimp_img, confidence_threshold=0.3)
869
-
870
- if not detections:
871
- print(f" โš ๏ธ No shrimp detected in image #{idx}")
872
- continue
873
-
874
- # ์ƒˆ์šฐ ์„ ํƒ: ์ค‘์•™ ์œ„์น˜ + ํฌ๊ธฐ + ํ˜•ํƒœ ๊ธฐ๋ฐ˜ ์Šค์ฝ”์–ด๋ง
875
- img_width, img_height = shrimp_img.size
876
- img_area = img_width * img_height
877
- img_center_x = img_width / 2
878
- img_center_y = img_height / 2
879
-
880
- valid_detections = []
881
- for det in detections:
882
- x1, y1, x2, y2 = det["bbox"]
883
- width = x2 - x1
884
- height = y2 - y1
885
- area = width * height
886
-
887
- # ๊ฐ์ฒด ์ค‘์‹ฌ์ 
888
- obj_center_x = (x1 + x2) / 2
889
- obj_center_y = (y1 + y2) / 2
890
-
891
- # 1. ์ค‘์•™ ๊ฑฐ๋ฆฌ ์ ์ˆ˜ (0~1, ์ค‘์•™์— ๊ฐ€๊นŒ์šธ์ˆ˜๋ก ๋†’์Œ)
892
- max_dist = ((img_width/2)**2 + (img_height/2)**2)**0.5
893
- dist_from_center = ((obj_center_x - img_center_x)**2 + (obj_center_y - img_center_y)**2)**0.5
894
- center_score = 1 - (dist_from_center / max_dist)
895
-
896
- # 2. ํฌ๊ธฐ ์ ์ˆ˜ (์ ์ ˆํ•œ ํฌ๊ธฐ: ์ด๋ฏธ์ง€์˜ 5~25%)
897
- size_ratio = area / img_area
898
- if 0.05 < size_ratio < 0.25:
899
- size_score = 1.0
900
- elif 0.01 < size_ratio < 0.4:
901
- size_score = 0.5
902
- else:
903
- size_score = 0.0
904
-
905
- # 3. ํ˜•ํƒœ ์ ์ˆ˜ (๊ธธ์ญ‰ํ•œ ํ˜•ํƒœ)
906
- longer_side = max(width, height)
907
- shorter_side = min(width, height)
908
- elongation = longer_side / (shorter_side + 1e-8)
909
- if elongation > 2.5:
910
- shape_score = 1.0
911
- elif elongation > 1.5:
912
- shape_score = 0.7
913
- else:
914
- shape_score = 0.3
915
-
916
- # 4. ์‹ ๋ขฐ๋„ ์ ์ˆ˜
917
- confidence_score = det["confidence"]
918
-
919
- # ์ตœ์ข… ์ ์ˆ˜ (๊ฐ€์ค‘ ํ‰๊ท )
920
- final_score = (
921
- center_score * 0.4 + # ์ค‘์•™ ์œ„์น˜ ๊ฐ€์žฅ ์ค‘์š”
922
- size_score * 0.2 + # ํฌ๊ธฐ
923
- shape_score * 0.2 + # ํ˜•ํƒœ
924
- confidence_score * 0.2 # ์‹ ๋ขฐ๋„
925
- )
926
-
927
- det["final_score"] = final_score
928
- det["center_score"] = center_score
929
- det["size_score"] = size_score
930
- det["shape_score"] = shape_score
931
-
932
- # ์ตœ์†Œ ์ ์ˆ˜ ์ž„๊ณ„๊ฐ’
933
- if final_score > 0.3:
934
- valid_detections.append(det)
935
-
936
- if not valid_detections:
937
- print(f" โš ๏ธ No valid shrimp detected (filtered out {len(detections)} detections)")
938
- continue
939
-
940
- # ์ตœ์ข… ์ ์ˆ˜๊ฐ€ ๊ฐ€์žฅ ๋†’์€ ๊ฐ์ฒด ์„ ํƒ
941
- largest_det = max(valid_detections, key=lambda d: d["final_score"])
942
- pred_length = largest_det["length"]
943
- pred_weight = largest_det["pred_weight"]
944
-
945
- print(f" โœ“ Selected detection:")
946
- print(f" - Final score: {largest_det['final_score']:.3f}")
947
- print(f" - Center: {largest_det['center_score']:.2f}, Size: {largest_det['size_score']:.2f}, Shape: {largest_det['shape_score']:.2f}, Conf: {largest_det['confidence']:.2f}")
948
- print(f" - Bbox area: {(largest_det['bbox'][2]-largest_det['bbox'][0])*(largest_det['bbox'][3]-largest_det['bbox'][1]):.0f}px")
949
-
950
- # 2. ์—‘์…€์—์„œ ์‹ค์ œ ๋ฐ์ดํ„ฐ ๊ฐ€์ ธ์˜ค๊ธฐ
951
- true_data = excel_data[idx]
952
- true_length = true_data['length']
953
- true_weight = true_data['weight']
954
-
955
- if true_weight is None:
956
- print(f" โš ๏ธ No weight data in Excel for #{idx}")
957
- continue
958
-
959
- # 3. ์˜ค์ฐจ ๊ณ„์‚ฐ
960
- error_weight = abs(pred_weight - true_weight) / true_weight * 100
961
- error_length = abs(pred_length - true_length) / true_length * 100
962
-
963
- # 4. ์ด๋ฏธ์ง€ ์‹œ๊ฐํ™” (์˜ˆ์ธก + ์‹ค์ œ ๊ฐ’ ํ‘œ์‹œ)
964
- vis_img = detector.visualize_with_groundtruth(
965
- shrimp_img, largest_det, true_length, true_weight, idx
966
- )
967
-
968
- # ์ด๋ฏธ์ง€ ํŒŒ์ผ๋กœ ์ €์žฅ (ํ™•์žฅ์ž ํฌํ•จ)
969
- output_filename = f"sample_{idx:02d}_result.jpg"
970
- output_path = os.path.join(results_folder, output_filename)
971
- vis_img.save(output_path, quality=95)
972
-
973
- visualized_images.append(output_path)
974
- print(f" ๐Ÿ’พ Saved visualization: {output_path}")
975
-
976
- results.append({
977
- "ID": f"#{idx}",
978
- "์‹ค์ œ ์ฒด์žฅ(cm)": round(true_length, 1),
979
- "์˜ˆ์ธก ์ฒด์žฅ(cm)": round(pred_length, 1),
980
- "์ฒด์žฅ ์˜ค์ฐจ(%)": round(error_length, 1),
981
- "์‹ค์ œ ์ฒด์ค‘(g)": round(true_weight, 2),
982
- "์˜ˆ์ธก ์ฒด์ค‘(g)": round(pred_weight, 2),
983
- "์ฒด์ค‘ ์˜ค์ฐจ(%)": round(error_weight, 1),
984
- "์˜ค์ฐจ(g)": round(abs(pred_weight - true_weight), 2)
985
- })
986
-
987
- print(f" โœ… Length: {pred_length:.1f}cm (true: {true_length:.1f}cm, error: {error_length:.1f}%)")
988
- print(f" Weight: {pred_weight:.2f}g (true: {true_weight:.2f}g, error: {error_weight:.1f}%)")
989
-
990
- if not results:
991
- return "โŒ ์ฒ˜๋ฆฌ๋œ ์ƒ˜ํ”Œ์ด ์—†์Šต๋‹ˆ๋‹ค", pd.DataFrame(), None, []
992
-
993
- # ๊ฒฐ๊ณผ DataFrame
994
- df = pd.DataFrame(results)
995
-
996
- # ํ†ต๊ณ„ ๊ณ„์‚ฐ
997
- avg_error_length = df["์ฒด์žฅ ์˜ค์ฐจ(%)"].mean()
998
- avg_error_weight = df["์ฒด์ค‘ ์˜ค์ฐจ(%)"].mean()
999
- avg_error_g = df["์˜ค์ฐจ(g)"].mean()
1000
- min_error_weight = df["์ฒด์ค‘ ์˜ค์ฐจ(%)"].min()
1001
- max_error_weight = df["์ฒด์ค‘ ์˜ค์ฐจ(%)"].max()
1002
-
1003
- # ์ฐจํŠธ ์ƒ์„ฑ
1004
- fig = go.Figure()
1005
-
1006
- # ์˜ˆ์ธก vs ์‹ค์ œ ์‚ฐ์ ๋„
1007
- fig.add_trace(go.Scatter(
1008
- x=df["์‹ค์ œ ์ฒด์ค‘(g)"],
1009
- y=df["์˜ˆ์ธก ์ฒด์ค‘(g)"],
1010
- mode='markers+text',
1011
- text=df["ID"],
1012
- textposition="top center",
1013
- marker=dict(size=12, color=df["์ฒด์ค‘ ์˜ค์ฐจ(%)"], colorscale='RdYlGn_r',
1014
- showscale=True, colorbar=dict(title="์ฒด์ค‘ ์˜ค์ฐจ(%)")),
1015
- name='์˜ˆ์ธก vs ์‹ค์ œ'
1016
- ))
1017
-
1018
- # ์™„๋ฒฝํ•œ ์˜ˆ์ธก ์„  (y=x)
1019
- min_val = min(df["์‹ค์ œ ์ฒด์ค‘(g)"].min(), df["์˜ˆ์ธก ์ฒด์ค‘(g)"].min())
1020
- max_val = max(df["์‹ค์ œ ์ฒด์ค‘(g)"].max(), df["์˜ˆ์ธก ์ฒด์ค‘(g)"].max())
1021
- fig.add_trace(go.Scatter(
1022
- x=[min_val, max_val],
1023
- y=[min_val, max_val],
1024
- mode='lines',
1025
- line=dict(dash='dash', color='red', width=2),
1026
- name='์™„๋ฒฝํ•œ ์˜ˆ์ธก (y=x)'
1027
- ))
1028
-
1029
- fig.update_layout(
1030
- title=f"์˜ˆ์ธก ์ •ํ™•๋„ ๊ฒ€์ฆ ({len(results)}๊ฐœ ์ƒ˜ํ”Œ)",
1031
- xaxis_title="์‹ค์ œ ์ฒด์ค‘ (g)",
1032
- yaxis_title="์˜ˆ์ธก ์ฒด์ค‘ (g)",
1033
- template="plotly_white",
1034
- height=500,
1035
- hovermode='closest'
1036
- )
1037
-
1038
- # ํ†ต๊ณ„ ํ…์ŠคํŠธ
1039
- stats_text = f"""
1040
- ### ๐Ÿ“Š ๋ฐฐ์น˜ ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ
1041
-
1042
- - **์ฒ˜๋ฆฌ ์ƒ˜ํ”Œ ์ˆ˜**: {len(results)}๊ฐœ
1043
- - **์ฒด์žฅ ํ‰๊ท  ์˜ค์ฐจ**: {avg_error_length:.1f}%
1044
- - **์ฒด์ค‘ ํ‰๊ท  ์˜ค์ฐจ(MAPE)**: {avg_error_weight:.1f}%
1045
- - **์ฒด์ค‘ ์ ˆ๋Œ€ ์˜ค์ฐจ**: {avg_error_g:.2f}g
1046
- - **์ฒด์ค‘ ์˜ค์ฐจ ๋ฒ”์œ„**: {min_error_weight:.1f}% ~ {max_error_weight:.1f}%
1047
- - **๊นŠ์ด ๋ณด์ •**: {'โœ… ํ™œ์„ฑํ™”' if enable_depth else 'โš ๏ธ ๋น„ํ™œ์„ฑํ™”'}
1048
-
1049
- ๐ŸŽฏ **ํ‰๊ฐ€**: {'โœ… ์šฐ์ˆ˜ (MAPE < 25%)' if avg_error_weight < 25 else 'โš ๏ธ ๊ฐœ์„  ํ•„์š” (MAPE โ‰ฅ 25%)'}
1050
-
1051
- ๐Ÿ’ก **์ฐธ๊ณ **: ์‹ค์ œ ์ฒด์žฅ๊ณผ ์ฒด์ค‘ ๋ฐ์ดํ„ฐ๋Š” ์—‘์…€ ํŒŒ์ผ์—์„œ ๋กœ๋“œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.
1052
-
1053
- ๐Ÿ“ธ **์ด๋ฏธ์ง€ ๊ฒฐ๊ณผ**: ์•„๋ž˜ ๊ฐค๋Ÿฌ๋ฆฌ์—์„œ ๊ฐ ์ƒ˜ํ”Œ์˜ ์˜ˆ์ธก/์‹ค์ œ ๊ฐ’์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
1054
-
1055
- ๐Ÿ’พ **์ €์žฅ ์œ„์น˜**: `{results_folder}` ํด๋”์— {len(visualized_images)}๊ฐœ ์ด๋ฏธ์ง€ ์ €์žฅ๋จ
1056
- """
1057
-
1058
- return stats_text, df, fig, visualized_images
1059
-
1060
- # =====================
1061
- # Gradio UI
1062
- # =====================
1063
-
1064
- with gr.Blocks(title="๐Ÿฆ RT-DETR ์ƒˆ์šฐ ๋ถ„์„", theme=gr.themes.Soft()) as demo:
1065
-
1066
- gr.Markdown("""
1067
- # ๐Ÿฆ ํฐ๋‹ค๋ฆฌ์ƒˆ์šฐ AI ๋ถ„์„ ์‹œ์Šคํ…œ (RT-DETR)
1068
-
1069
- ### ์‹ค์‹œ๊ฐ„ ๊ฐ์ฒด ๊ฒ€์ถœ + ์ฒด์žฅ/์ฒด์ค‘ ์ž๋™ ์ถ”์ •
1070
- **๋ชจ๋ธ**: RT-DETR (PekingU/rtdetr_r50vd_coco_o365) | **ํšŒ๊ท€**: W = 0.0035 ร— L^3.13
1071
- **์ •ํ™•๋„**: Rยฒ = 0.929, MAPE = 6.4% | **๋””๋ฐ”์ด์Šค**: """ + ("๐Ÿš€ GPU" if torch.cuda.is_available() else "๐Ÿ’ป CPU") + """
1072
-
1073
- ---
1074
- """)
1075
-
1076
- with gr.Tabs():
1077
- # ๊ฒ€์ถœ ํƒญ
1078
- with gr.TabItem("๐Ÿ” ๊ฐ์ฒด ๊ฒ€์ถœ"):
1079
- with gr.Row():
1080
- with gr.Column():
1081
- input_img = gr.Image(
1082
- label="์ž…๋ ฅ ์ด๋ฏธ์ง€",
1083
- type="pil"
1084
- )
1085
-
1086
- conf_slider = gr.Slider(
1087
- 0.1, 0.9, 0.5,
1088
- label="๊ฒ€์ถœ ์‹ ๋ขฐ๋„ ์ž„๊ณ„๊ฐ’ (Confidence)",
1089
- info="๋‚ฎ์„์ˆ˜๋ก ๋” ๋งŽ์€ ๊ฐ์ฒด ๊ฒ€์ถœ"
1090
- )
1091
-
1092
- iou_slider = gr.Slider(
1093
- 0.1, 0.9, 0.5,
1094
- label="IoU ์ž„๊ณ„๊ฐ’ (Overlap) - Roboflow ์ „์šฉ",
1095
- info="๊ฒน์น˜๋Š” ๋ฐ•์Šค ์ œ๊ฑฐ ๊ธฐ์ค€ (NMS, ๋†’์„์ˆ˜๋ก ๋” ๋งŽ์ด ์œ ์ง€)"
1096
- )
1097
-
1098
- # ๋ชจ๋ธ ์„ ํƒ
1099
- model_selector = gr.Dropdown(
1100
- choices=[
1101
- ("Roboflow (์ƒˆ์šฐ ์ „์šฉ - ์ˆ˜์กฐ)", "roboflow"),
1102
- ("RT-DETR (๋ฒ”์šฉ - ์ธก์ •์šฉ)", "rtdetr")
1103
- ],
1104
- value="roboflow" if ROBOFLOW_LOADED else "rtdetr",
1105
- label="๐Ÿค– ๊ฒ€์ถœ ๋ชจ๋ธ ์„ ํƒ",
1106
- info="Roboflow: ์‚ด์•„์žˆ๋Š” ์ƒˆ์šฐ(์ˆ˜์กฐ) ์ „์šฉ | RT-DETR: ์ธก์ •์šฉ ๋งคํŠธ ์œ„ ์ƒˆ์šฐ"
1107
- )
1108
-
1109
- with gr.Row():
1110
- pixel_scale = gr.Number(
1111
- value=92,
1112
- label="ํ”ฝ์…€ ํฌ๊ธฐ (px)",
1113
- info="์ฐธ์กฐ ๊ฐ์ฒด์˜ ํ”ฝ์…€ ํฌ๊ธฐ"
1114
- )
1115
- cm_scale = gr.Number(
1116
- value=1,
1117
- label="์‹ค์ œ ํฌ๊ธฐ (cm)",
1118
- info="์ฐธ์กฐ ๊ฐ์ฒด์˜ ์‹ค์ œ ํฌ๊ธฐ"
1119
- )
1120
-
1121
- depth_checkbox = gr.Checkbox(
1122
- value=False,
1123
- label="๐Ÿ” ๊นŠ์ด ๊ธฐ๋ฐ˜ ์›๊ทผ ๋ณด์ • ํ™œ์„ฑํ™” (RT-DETR๋งŒ)",
1124
- info="Depth-Anything V2๋กœ ์ž๋™ ์›๊ทผ ์™œ๊ณก ๋ณด์ •"
1125
- )
1126
-
1127
- detect_btn = gr.Button(
1128
- "๐Ÿš€ ๊ฒ€์ถœ ์‹คํ–‰",
1129
- variant="primary",
1130
- size="lg"
1131
- )
1132
-
1133
- with gr.Column():
1134
- output_img = gr.Image(
1135
- label="๊ฒ€์ถœ ๊ฒฐ๊ณผ"
1136
- )
1137
- depth_img = gr.Image(
1138
- label="๊นŠ์ด ๋งต (ํŒŒ๋ž€์ƒ‰=๊ฐ€๊นŒ์›€, ๋…ธ๋ž€์ƒ‰=๋ฉ€์Œ)"
1139
- )
1140
- stats = gr.Markdown()
1141
-
1142
- results_df = gr.Dataframe(
1143
- label="๊ฒ€์ถœ ์ƒ์„ธ ์ •๋ณด",
1144
- wrap=True
1145
- )
1146
-
1147
- # ํ‰๊ฐ€ ํƒญ
1148
- with gr.TabItem("๐Ÿ“Š ์„ฑ๋Šฅ ํ‰๊ฐ€"):
1149
- gr.Markdown("""
1150
- ### ํšŒ๊ท€ ๋ชจ๋ธ ์„ฑ๋Šฅ ํ‰๊ฐ€
1151
-
1152
- ์‹ค์ธก ๋ฐ์ดํ„ฐ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ์ฒด์žฅ-์ฒด์ค‘ ํšŒ๊ท€ ๋ชจ๋ธ์˜ ์ •ํ™•๋„๋ฅผ ํ‰๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.
1153
- """)
1154
-
1155
- eval_btn = gr.Button(
1156
- "๐Ÿ“ˆ ํ‰๊ฐ€ ์‹คํ–‰",
1157
- variant="primary"
1158
- )
1159
- eval_text = gr.Markdown()
1160
- eval_plot = gr.Plot()
1161
-
1162
- # ๋ฐ์ดํ„ฐ ํƒญ
1163
- with gr.TabItem("๐Ÿ“‹ ์‹ค์ธก ๋ฐ์ดํ„ฐ"):
1164
- gr.Markdown(f"""
1165
- ### ๋ฐ์ดํ„ฐ ์š”์•ฝ
1166
-
1167
- - **์ƒ˜ํ”Œ ์ˆ˜**: {len(REAL_DATA)}๊ฐœ
1168
- - **์ฒด์žฅ ๋ฒ”์œ„**: 7.5 - 13.1 cm
1169
- - **์ฒด์ค‘ ๋ฒ”์œ„**: 2.0 - 11.3 g
1170
- - **๋ฐ์ดํ„ฐ ์ถœ์ฒ˜**: ์‹ค์ธก ๋ฐ์ดํ„ฐ
1171
- """)
1172
-
1173
- data_df = gr.Dataframe(
1174
- value=pd.DataFrame(REAL_DATA),
1175
- label="์‹ค์ธก ๋ฐ์ดํ„ฐ",
1176
- wrap=True
1177
- )
1178
-
1179
- export_btn = gr.Button("๐Ÿ’พ CSV ๋‹ค์šด๋กœ๋“œ")
1180
- file_output = gr.File(label="๋‹ค์šด๋กœ๋“œ")
1181
-
1182
- # ์ •ํ™•๋„ ๊ฒ€์ฆ ํƒญ
1183
- with gr.TabItem("๐ŸŽฏ ์ •ํ™•๋„ ๊ฒ€์ฆ"):
1184
- gr.Markdown("""
1185
- ### ์‹ค์ œ ์ด๋ฏธ์ง€ ๋ฐ์ดํ„ฐ๋กœ ์ •ํ™•๋„ ๊ฒ€์ฆ
1186
-
1187
- ํ…Œ์ŠคํŠธ ๋ฐ์ดํ„ฐ์…‹์„ ๋ฐฐ์น˜ ์ฒ˜๋ฆฌํ•˜์—ฌ ์˜ˆ์ธก ์ •ํ™•๋„๋ฅผ ์ธก์ •ํ•ฉ๋‹ˆ๋‹ค.
1188
- """)
1189
-
1190
- with gr.Row():
1191
- with gr.Column():
1192
- test_folder = gr.Textbox(
1193
- value="d:/Project/VIDraft/Shrimp/data/251015",
1194
- label="ํ…Œ์ŠคํŠธ ๋ฐ์ดํ„ฐ ํด๋”",
1195
- info="251015_XX.jpg ํ˜•์‹์˜ ์ด๋ฏธ์ง€๊ฐ€ ์žˆ๋Š” ํด๋”"
1196
- )
1197
-
1198
- with gr.Row():
1199
- test_pixel_scale = gr.Number(
1200
- value=92,
1201
- label="ํ”ฝ์…€ ํฌ๊ธฐ (px)",
1202
- info="์ฐธ์กฐ ์ž์˜ ํ”ฝ์…€ ํฌ๊ธฐ"
1203
- )
1204
- test_cm_scale = gr.Number(
1205
- value=1,
1206
- label="์‹ค์ œ ํฌ๊ธฐ (cm)",
1207
- info="์ฐธ์กฐ ์ž์˜ ์‹ค์ œ ํฌ๊ธฐ"
1208
- )
1209
-
1210
- test_depth_checkbox = gr.Checkbox(
1211
- value=False,
1212
- label="๐Ÿ” ๊นŠ์ด ๊ธฐ๋ฐ˜ ์›๊ทผ ๋ณด์ • ํ™œ์„ฑํ™”",
1213
- info="Depth-Anything V2๋กœ ์ž๋™ ์›๊ทผ ์™œ๊ณก ๋ณด์ •"
1214
- )
1215
-
1216
- test_btn = gr.Button(
1217
- "๐Ÿš€ ๋ฐฐ์น˜ ํ…Œ์ŠคํŠธ ์‹คํ–‰",
1218
- variant="primary",
1219
- size="lg"
1220
- )
1221
-
1222
- with gr.Column():
1223
- test_stats = gr.Markdown()
1224
-
1225
- test_plot = gr.Plot(label="์˜ˆ์ธก vs ์‹ค์ œ ๋น„๊ต")
1226
- test_results_df = gr.Dataframe(
1227
- label="์ƒ์„ธ ๊ฒฐ๊ณผ",
1228
- wrap=True
1229
- )
1230
-
1231
- gr.Markdown("### ๐Ÿ“ธ ์‹œ๊ฐํ™” ๊ฒฐ๊ณผ (์˜ˆ์ธก vs ์‹ค์ œ)")
1232
- test_gallery = gr.Gallery(
1233
- label="๊ฒ€์ถœ ๊ฒฐ๊ณผ ์ด๋ฏธ์ง€",
1234
- show_label=True,
1235
- columns=3,
1236
- rows=2,
1237
- height="auto",
1238
- object_fit="contain"
1239
- )
1240
-
1241
- # ์ •๋ณด ํƒญ
1242
- with gr.TabItem("โ„น๏ธ ์‚ฌ์šฉ ๋ฐฉ๋ฒ•"):
1243
- gr.Markdown("""
1244
- ## ๐Ÿ“– ์‚ฌ์šฉ ๊ฐ€์ด๋“œ
1245
-
1246
- ### 1๏ธโƒฃ ๊ฐ์ฒด ๊ฒ€์ถœ
1247
- 1. ์ƒˆ์šฐ ์ด๋ฏธ์ง€๋ฅผ ์—…๋กœ๋“œํ•˜์„ธ์š”
1248
- 2. ์‹ ๋ขฐ๋„ ์ž„๏ฟฝ๏ฟฝ๏ฟฝ๊ฐ’์„ ์กฐ์ •ํ•˜์„ธ์š” (๊ธฐ๋ณธ๊ฐ’: 0.5)
1249
- 3. **๊นŠ์ด ๋ณด์ • ํ™œ์„ฑํ™”** (๊ถŒ์žฅ): ์›๊ทผ ์™œ๊ณก ์ž๋™ ๋ณด์ •
1250
- 4. ์Šค์ผ€์ผ ๋ณด์ •: ์‹ค์ œ ํฌ๊ธฐ๋ฅผ ์•Œ๊ณ  ์žˆ๋‹ค๋ฉด ํ”ฝ์…€-cm ๋น„์œจ์„ ์„ค์ •ํ•˜์„ธ์š”
1251
- 5. "๊ฒ€์ถœ ์‹คํ–‰" ๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜์„ธ์š”
1252
-
1253
- ### 2๏ธโƒฃ ๊นŠ์ด ๊ธฐ๋ฐ˜ ์›๊ทผ ๋ณด์ • (NEW! ๐Ÿ”ฅ)
1254
- - **Depth-Anything V2** ๋ชจ๋ธ๋กœ ์ด๋ฏธ์ง€์˜ ๊นŠ์ด ๋งต ์ž๋™ ์ƒ์„ฑ
1255
- - ๊ฐ ์ƒˆ์šฐ์˜ ์ƒ๋Œ€์  ๊ฑฐ๋ฆฌ๋ฅผ ๊ณ„์‚ฐํ•˜์—ฌ ์Šค์ผ€์ผ ์ž๋™ ๋ณด์ •
1256
- - **์›๊ทผ ์™œ๊ณก ํšจ๊ณผ๋ฅผ ์ž๋™์œผ๋กœ ์ œ๊ฑฐ**ํ•˜์—ฌ ์ •ํ™•๋„ ํ–ฅ์ƒ
1257
- - ๊นŠ์ด ๋งต ์‹œ๊ฐํ™”: ํŒŒ๋ž€์ƒ‰=๊ฐ€๊นŒ์›€, ๋…ธ๋ž€์ƒ‰=๋ฉ€์Œ
1258
- - ์ถ”๊ฐ€ ์žฅ๋น„๋‚˜ ๋งˆ์ปค ์—†์ด ๋‹จ์ผ ์ด๋ฏธ์ง€๋งŒ์œผ๋กœ ์ž‘๋™
1259
-
1260
- ### 3๏ธโƒฃ ์Šค์ผ€์ผ ๋ณด์ •
1261
- - ์ด๋ฏธ์ง€์—์„œ ์•Œ๊ณ  ์žˆ๋Š” ๊ฐ์ฒด์˜ ํ”ฝ์…€ ํฌ๊ธฐ์™€ ์‹ค์ œ ํฌ๊ธฐ๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”
1262
- - ์˜ˆ: ์ž๊ฐ€ ๋ณด์ธ๋‹ค๋ฉด, ์ž์˜ ํ”ฝ์…€ ๊ธธ์ด์™€ ์‹ค์ œ ๊ธธ์ด(cm)๋ฅผ ์ž…๋ ฅ
1263
- - ๊นŠ์ด ๋ณด์ •๊ณผ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๋ฉด ๋”์šฑ ์ •ํ™•ํ•œ ์ธก์ • ๊ฐ€๋Šฅ
1264
-
1265
- ### 4๏ธโƒฃ ๊ฒฐ๊ณผ ํ•ด์„
1266
- - **์ดˆ๋ก์ƒ‰ ๋ฐ•์Šค**: ์˜ค์ฐจ < 10%
1267
- - **์ฃผํ™ฉ์ƒ‰ ๋ฐ•์Šค**: ์˜ค์ฐจ 10-20%
1268
- - **๋นจ๊ฐ„์ƒ‰ ๋ฐ•์Šค**: ์˜ค์ฐจ > 20%
1269
-
1270
- ### 5๏ธโƒฃ ์„ฑ๋Šฅ ํ‰๊ฐ€
1271
- - "์„ฑ๋Šฅ ํ‰๊ฐ€" ํƒญ์—์„œ ํšŒ๊ท€ ๋ชจ๋ธ์˜ ์ •ํ™•๋„๋ฅผ ํ™•์ธํ•˜์„ธ์š”
1272
- - Rยฒ, MAPE, MAE, RMSE ์ง€ํ‘œ ์ œ๊ณต
1273
-
1274
- ### 6๏ธโƒฃ ์ •ํ™•๋„ ๊ฒ€์ฆ (NEW! ๐Ÿ”ฅ)
1275
- - "์ •ํ™•๋„ ๊ฒ€์ฆ" ํƒญ์—์„œ ์‹ค์ œ ์ด๋ฏธ์ง€ ๋ฐ์ดํ„ฐ๋กœ ์ •ํ™•๋„ ์ธก์ •
1276
- - ํ…Œ์ŠคํŠธ ๋ฐ์ดํ„ฐ ํด๋”๋ฅผ ์ง€์ •ํ•˜๊ณ  ๋ฐฐ์น˜ ์ฒ˜๋ฆฌ
1277
- - OCR๋กœ ์ „์ž์ €์šธ LCD์—์„œ ์‹ค์ œ ๋ฌด๊ฒŒ ์ž๋™ ์ฝ๊ธฐ
1278
- - ์˜ˆ์ธก vs ์‹ค์ œ ๋น„๊ต ์ฐจํŠธ ๋ฐ ํ†ต๊ณ„ ์ œ๊ณต
1279
-
1280
- ### 7๏ธโƒฃ ๋ฐ์ดํ„ฐ ๋‚ด๋ณด๋‚ด๊ธฐ
1281
- - "์‹ค์ธก ๋ฐ์ดํ„ฐ" ํƒญ์—์„œ CSV ํŒŒ์ผ๋กœ ๋‹ค์šด๋กœ๋“œ ๊ฐ€๋Šฅ
1282
-
1283
- ---
1284
-
1285
- ## โš™๏ธ ์‹œ์Šคํ…œ ์ •๋ณด
1286
-
1287
- - **๊ฒ€์ถœ ๋ชจ๋ธ**: RT-DETR (Real-Time DEtection TRansformer)
1288
- - **๊นŠ์ด ์ถ”์ • ๋ชจ๋ธ**: Depth-Anything V2 Small (Monocular Depth Estimation)
1289
- - **ํšŒ๊ท€ ๋ชจ๋ธ**: Power Law (W = a ร— L^b)
1290
- - **๋””๋ฐ”์ด์Šค**: """ + ("GPU (CUDA)" if torch.cuda.is_available() else "CPU") + """
1291
- - **์ตœ์ ํ™”**: CPU ๋ชจ๋“œ, torch.no_grad(), FP32
1292
- - **์›๊ทผ ๋ณด์ •**: ๊นŠ์ด ๋งต ๊ธฐ๋ฐ˜ ์ž๋™ ์Šค์ผ€์ผ ์กฐ์ •
1293
-
1294
- ## ๐Ÿ”ง ๋ฌธ์ œ ํ•ด๊ฒฐ
1295
-
1296
- **๊ฒ€์ถœ์ด ์•ˆ ๋  ๋•Œ**:
1297
- - ์‹ ๋ขฐ๋„ ์ž„๊ณ„๊ฐ’์„ ๋‚ฎ์ถฐ๋ณด์„ธ์š” (0.3 ์ดํ•˜)
1298
- - ์ด๋ฏธ์ง€ ํ’ˆ์งˆ์„ ํ™•์ธํ•˜์„ธ์š” (ํ•ด์ƒ๋„, ๋ฐ๊ธฐ)
1299
-
1300
- **์ •ํ™•๋„๊ฐ€ ๋‚ฎ์„ ๋•Œ**:
1301
- - ์Šค์ผ€์ผ ๋ณด์ •์„ ์ •ํ™•ํžˆ ์ž…๋ ฅํ•˜์„ธ์š”
1302
- - ์ƒˆ์šฐ ์ „์šฉ fine-tuning ๋ชจ๋ธ์ด ํ•„์š”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค
1303
-
1304
- **์†๋„๊ฐ€ ๋А๋ฆด ๋•Œ**:
1305
- - GPU ๊ฐ€์†์„ ์‚ฌ์šฉํ•˜์„ธ์š” (HF Space: GPU T4)
1306
- - ์ด๋ฏธ์ง€ ํฌ๊ธฐ๋ฅผ ์ค„์ด์„ธ์š” (800x600 ๊ถŒ์žฅ)
1307
- """)
1308
-
1309
- # ์ด๋ฒคํŠธ ์—ฐ๊ฒฐ
1310
- detect_btn.click(
1311
- process_image,
1312
- [input_img, model_selector, conf_slider, iou_slider, pixel_scale, cm_scale, depth_checkbox],
1313
- [output_img, depth_img, stats, results_df]
1314
- )
1315
-
1316
- eval_btn.click(
1317
- evaluate_model,
1318
- [],
1319
- [eval_text, eval_plot]
1320
- )
1321
-
1322
- export_btn.click(
1323
- export_data,
1324
- [],
1325
- file_output
1326
- )
1327
-
1328
- test_btn.click(
1329
- process_test_dataset,
1330
- [test_folder, test_pixel_scale, test_cm_scale, test_depth_checkbox],
1331
- [test_stats, test_results_df, test_plot, test_gallery]
1332
- )
1333
-
1334
- # ์‹คํ–‰
1335
- if __name__ == "__main__":
1336
- demo.queue(max_size=10) # CPU ์ตœ์ ํ™”: ํ ํฌ๊ธฐ ์ œํ•œ
1337
- demo.launch(
1338
- share=False,
1339
- server_name="0.0.0.0",
1340
- server_port=7860,
1341
- show_error=True
1342
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app_demo.py DELETED
@@ -1,163 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- """
3
- ์ƒˆ์šฐ ๊ฒ€์ถœ ์‹œ์Šคํ…œ ๋ฐ๋ชจ ์›น์•ฑ
4
- ์ตœ์ข… ์ตœ์ ํ™” ๋ฒ„์ „ (Precision=44.2%, Recall=94%, F1=56.1%)
5
- """
6
- import sys
7
- sys.stdout.reconfigure(encoding='utf-8')
8
-
9
- import gradio as gr
10
- import numpy as np
11
- from PIL import Image, ImageDraw, ImageFont
12
- from test_visual_validation import (
13
- load_rtdetr_model,
14
- detect_with_rtdetr,
15
- apply_universal_filter
16
- )
17
-
18
- # ๋ชจ๋ธ ๋กœ๋“œ (์‹œ์ž‘์‹œ ํ•œ๋ฒˆ๋งŒ)
19
- print("๐Ÿ”„ RT-DETR ๋ชจ๋ธ ๋กœ๋”ฉ ์ค‘...")
20
- processor, model = load_rtdetr_model()
21
- print("โœ… RT-DETR ๋กœ๋”ฉ ์™„๋ฃŒ\n")
22
-
23
- def detect_shrimp(image, confidence_threshold, filter_threshold):
24
- """์ƒˆ์šฐ ๊ฒ€์ถœ ํ•จ์ˆ˜"""
25
- if image is None:
26
- return None, "์ด๋ฏธ์ง€๋ฅผ ์—…๋กœ๋“œํ•˜์„ธ์š”."
27
-
28
- # PIL Image๋กœ ๋ณ€ํ™˜
29
- if isinstance(image, np.ndarray):
30
- image = Image.fromarray(image)
31
-
32
- # RT-DETR ๊ฒ€์ถœ
33
- all_detections = detect_with_rtdetr(image, processor, model, confidence_threshold)
34
-
35
- # ํ•„ํ„ฐ ์ ์šฉ
36
- filtered_detections = apply_universal_filter(all_detections, image, filter_threshold)
37
-
38
- # ์‹œ๊ฐํ™”
39
- result_image = image.copy()
40
- draw = ImageDraw.Draw(result_image)
41
-
42
- try:
43
- font = ImageFont.truetype("arial.ttf", 20)
44
- font_small = ImageFont.truetype("arial.ttf", 14)
45
- except:
46
- font = ImageFont.load_default()
47
- font_small = ImageFont.load_default()
48
-
49
- # ๋ฐ•์Šค ๊ทธ๋ฆฌ๊ธฐ
50
- for i, det in enumerate(filtered_detections, 1):
51
- x1, y1, x2, y2 = det['bbox']
52
-
53
- # ๋ฐ•์Šค
54
- draw.rectangle([x1, y1, x2, y2], outline="lime", width=4)
55
-
56
- # ๋ผ๋ฒจ
57
- score = det['filter_score']
58
- conf = det['confidence']
59
- label = f"#{i} | Score:{score:.0f} | Conf:{conf:.2f}"
60
-
61
- # ๋ฐฐ๊ฒฝ
62
- bbox = draw.textbbox((x1, y1-25), label, font=font_small)
63
- draw.rectangle(bbox, fill="lime")
64
- draw.text((x1, y1-25), label, fill="black", font=font_small)
65
-
66
- # ๊ฒฐ๊ณผ ํ…์ŠคํŠธ
67
- info = f"""
68
- ๐Ÿ“Š ๊ฒ€์ถœ ๊ฒฐ๊ณผ:
69
- โ€ข RT-DETR ๊ฒ€์ถœ: {len(all_detections)}๊ฐœ
70
- โ€ข ํ•„ํ„ฐ ํ†ต๊ณผ: {len(filtered_detections)}๊ฐœ
71
- โ€ข ์ œ๊ฑฐ๋จ: {len(all_detections) - len(filtered_detections)}๊ฐœ
72
-
73
- โš™๏ธ ์„ค์ •:
74
- โ€ข RT-DETR Confidence: {confidence_threshold}
75
- โ€ข Filter Threshold: {filter_threshold}
76
-
77
- ๐ŸŽฏ ์„ฑ๋Šฅ (50๊ฐœ GT ๊ธฐ์ค€):
78
- โ€ข Precision: 44.2%
79
- โ€ข Recall: 94.0%
80
- โ€ข F1 Score: 56.1%
81
- """
82
-
83
- if len(filtered_detections) > 0:
84
- info += f"\nโœ… {len(filtered_detections)}๊ฐœ์˜ ์ƒˆ์šฐ๋ฅผ ๊ฒ€์ถœํ–ˆ์Šต๋‹ˆ๋‹ค!"
85
- else:
86
- info += "\nโš ๏ธ ์ƒˆ์šฐ๊ฐ€ ๊ฒ€์ถœ๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. Threshold๋ฅผ ๋‚ฎ์ถฐ๋ณด์„ธ์š”."
87
-
88
- return result_image, info
89
-
90
- # Gradio ์ธํ„ฐํŽ˜์ด์Šค
91
- with gr.Blocks(title="๐Ÿฆ ์ƒˆ์šฐ ๊ฒ€์ถœ ์‹œ์Šคํ…œ v1.0") as demo:
92
- gr.Markdown("""
93
- # ๐Ÿฆ ์ƒˆ์šฐ ๊ฒ€์ถœ ์‹œ์Šคํ…œ v1.0
94
- **RT-DETR + Universal Filter (์ตœ์ ํ™” ์™„๋ฃŒ)**
95
-
96
- - **Precision**: 44.2% (๊ฒ€์ถœ๋œ ๋ฐ•์Šค ์ค‘ ์‹ค์ œ ์ƒˆ์šฐ ๋น„์œจ)
97
- - **Recall**: 94.0% (์‹ค์ œ ์ƒˆ์šฐ ์ค‘ ๊ฒ€์ถœ๋œ ๋น„์œจ)
98
- - **F1 Score**: 56.1% (์ „์ฒด ์„ฑ๋Šฅ)
99
- """)
100
-
101
- with gr.Row():
102
- with gr.Column():
103
- input_image = gr.Image(label="๐Ÿ“ค ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ", type="pil")
104
-
105
- with gr.Row():
106
- conf_slider = gr.Slider(
107
- minimum=0.05,
108
- maximum=0.5,
109
- value=0.065,
110
- step=0.005,
111
- label="๐ŸŽฏ RT-DETR Confidence",
112
- info="๋‚ฎ์„์ˆ˜๋ก ๋” ๋งŽ์ด ๊ฒ€์ถœ (๊ถŒ์žฅ: 0.065)"
113
- )
114
-
115
- filter_slider = gr.Slider(
116
- minimum=50,
117
- maximum=100,
118
- value=90,
119
- step=5,
120
- label="๐Ÿ” Filter Threshold",
121
- info="๋†’์„์ˆ˜๋ก ์—„๊ฒฉํ•œ ํ•„ํ„ฐ๋ง (๊ถŒ์žฅ: 90)"
122
- )
123
-
124
- detect_btn = gr.Button("๐Ÿš€ ์ƒˆ์šฐ ๊ฒ€์ถœ ์‹œ์ž‘", variant="primary", size="lg")
125
-
126
- with gr.Column():
127
- output_image = gr.Image(label="๐Ÿ“Š ๊ฒ€์ถœ ๊ฒฐ๊ณผ")
128
- output_text = gr.Textbox(label="๐Ÿ“ ์ƒ์„ธ ์ •๋ณด", lines=15)
129
-
130
- # ์˜ˆ์ œ ์ด๋ฏธ์ง€
131
- gr.Examples(
132
- examples=[
133
- ["data/test_shrimp_tank.png", 0.065, 90],
134
- ],
135
- inputs=[input_image, conf_slider, filter_slider],
136
- label="๐Ÿ“ ์˜ˆ์ œ ์ด๋ฏธ์ง€ (ํด๋ฆญํ•˜์—ฌ ํ…Œ์ŠคํŠธ)"
137
- )
138
-
139
- # ์ด๋ฒคํŠธ ์—ฐ๊ฒฐ
140
- detect_btn.click(
141
- fn=detect_shrimp,
142
- inputs=[input_image, conf_slider, filter_slider],
143
- outputs=[output_image, output_text]
144
- )
145
-
146
- # ์•ฑ ์‹คํ–‰
147
- if __name__ == "__main__":
148
- print("="*60)
149
- print("๐Ÿฆ ์ƒˆ์šฐ ๊ฒ€์ถœ ์‹œ์Šคํ…œ v1.0 ์‹œ์ž‘")
150
- print("="*60)
151
- print("โš™๏ธ ์ตœ์  ์„ค์ •:")
152
- print(" - RT-DETR Confidence: 0.065")
153
- print(" - Filter Threshold: 90")
154
- print("\n๐Ÿ“Š ์„ฑ๋Šฅ (50๊ฐœ GT ๊ธฐ์ค€):")
155
- print(" - Precision: 44.2%")
156
- print(" - Recall: 94.0%")
157
- print(" - F1 Score: 56.1%")
158
- print("="*60)
159
-
160
- demo.launch(
161
- server_name="0.0.0.0",
162
- share=False
163
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app_full_system.py DELETED
@@ -1,1342 +0,0 @@
1
- """
2
- ๐Ÿฆ ํฐ๋‹ค๋ฆฌ์ƒˆ์šฐ ๋ถ„์„ ์‹œ์Šคํ…œ - RT-DETR CPU ์ตœ์ ํ™” ๋ฒ„์ „
3
- ์‹ค์ œ ๊ฐ์ฒด ๊ฒ€์ถœ + ์ฒด์žฅ/์ฒด์ค‘ ์ž๋™ ์ถ”์ •
4
- """
5
-
6
- import gradio as gr
7
- import numpy as np
8
- import pandas as pd
9
- import plotly.graph_objects as go
10
- from PIL import Image, ImageDraw, ImageFont
11
- from datetime import datetime
12
- import torch
13
- from transformers import (
14
- RTDetrForObjectDetection,
15
- RTDetrImageProcessor,
16
- AutoImageProcessor,
17
- AutoModelForDepthEstimation
18
- )
19
- import os
20
- import warnings
21
- warnings.filterwarnings('ignore')
22
-
23
- # =====================
24
- # ์‹ค์ธก ๋ฐ์ดํ„ฐ (260๊ฐœ ์ƒ˜ํ”Œ)
25
- # =====================
26
- REAL_DATA = [
27
- {"length": 7.5, "weight": 2.0}, {"length": 7.7, "weight": 2.1},
28
- {"length": 8.3, "weight": 2.7}, {"length": 8.4, "weight": 2.9},
29
- {"length": 8.6, "weight": 3.1}, {"length": 8.7, "weight": 3.0},
30
- {"length": 8.9, "weight": 3.2}, {"length": 9.1, "weight": 3.4},
31
- {"length": 9.4, "weight": 4.0}, {"length": 9.7, "weight": 4.7},
32
- {"length": 9.9, "weight": 4.7}, {"length": 10.0, "weight": 4.6},
33
- {"length": 10.2, "weight": 5.5}, {"length": 10.3, "weight": 5.8},
34
- {"length": 10.4, "weight": 5.5}, {"length": 10.7, "weight": 6.1},
35
- {"length": 10.9, "weight": 6.0}, {"length": 11.0, "weight": 6.2},
36
- {"length": 11.3, "weight": 5.8}, {"length": 11.4, "weight": 6.5},
37
- {"length": 11.6, "weight": 7.5}, {"length": 11.7, "weight": 8.1},
38
- {"length": 11.9, "weight": 9.4}, {"length": 12.0, "weight": 8.8},
39
- {"length": 12.3, "weight": 10.2}, {"length": 12.5, "weight": 10.9},
40
- {"length": 12.7, "weight": 10.1}, {"length": 12.9, "weight": 10.7},
41
- {"length": 13.0, "weight": 10.7}, {"length": 13.1, "weight": 11.3},
42
- ]
43
-
44
- # =====================
45
- # ํšŒ๊ท€ ๋ชจ๋ธ
46
- # =====================
47
- class RegressionModel:
48
- def __init__(self):
49
- self.a = 0.003454
50
- self.b = 3.1298
51
- self.r2 = 0.929
52
- self.mape = 6.4
53
-
54
- def estimate_weight(self, length_cm):
55
- """์ฒด์žฅ์œผ๋กœ ์ฒด์ค‘ ์ถ”์ •: W = a ร— L^b"""
56
- return self.a * (length_cm ** self.b)
57
-
58
- def calculate_error(self, true_weight, pred_weight):
59
- """์˜ค์ฐจ์œจ ๊ณ„์‚ฐ"""
60
- if true_weight == 0:
61
- return 0
62
- return abs(true_weight - pred_weight) / true_weight * 100
63
-
64
- # =====================
65
- # ๊นŠ์ด ์ถ”์ •๊ธฐ
66
- # =====================
67
- class DepthEstimator:
68
- def __init__(self, model_name="depth-anything/Depth-Anything-V2-Small-hf"):
69
- """Depth-Anything V2 ๋ชจ๋ธ ์ดˆ๊ธฐํ™”"""
70
- print(f"๐Ÿ”„ Loading Depth Estimation model: {model_name}")
71
-
72
- self.device = "cuda" if torch.cuda.is_available() else "cpu"
73
-
74
- try:
75
- self.processor = AutoImageProcessor.from_pretrained(model_name)
76
- self.model = AutoModelForDepthEstimation.from_pretrained(model_name)
77
- self.model.to(self.device)
78
- self.model.eval()
79
- print("โœ… Depth model loaded successfully!")
80
- self.enabled = True
81
- except Exception as e:
82
- print(f"โš ๏ธ Depth model loading failed: {e}")
83
- print("๐Ÿ“ Running without depth correction")
84
- self.enabled = False
85
-
86
- @torch.no_grad()
87
- def estimate_depth(self, image):
88
- """์ด๋ฏธ์ง€์—์„œ ๊นŠ์ด ๋งต ์ถ”์ •"""
89
- if not self.enabled or image is None:
90
- return None
91
-
92
- # ์ด๋ฏธ์ง€ ์ „์ฒ˜๋ฆฌ
93
- inputs = self.processor(images=image, return_tensors="pt")
94
- inputs = {k: v.to(self.device) for k, v in inputs.items()}
95
-
96
- # ๊นŠ์ด ์ถ”์ •
97
- outputs = self.model(**inputs)
98
- predicted_depth = outputs.predicted_depth
99
-
100
- # ์›๋ณธ ์ด๋ฏธ์ง€ ํฌ๊ธฐ๋กœ ๋ฆฌ์ƒ˜ํ”Œ๋ง
101
- depth_map = torch.nn.functional.interpolate(
102
- predicted_depth.unsqueeze(1),
103
- size=image.size[::-1], # (height, width)
104
- mode="bicubic",
105
- align_corners=False,
106
- ).squeeze().cpu().numpy()
107
-
108
- # ์ •๊ทœํ™” (0~1 ๋ฒ”์œ„)
109
- depth_min = depth_map.min()
110
- depth_max = depth_map.max()
111
- depth_normalized = (depth_map - depth_min) / (depth_max - depth_min + 1e-8)
112
-
113
- return depth_normalized
114
-
115
- def get_depth_at_bbox(self, depth_map, bbox):
116
- """bbox ์ค‘์‹ฌ์ ์˜ ๊นŠ์ด ๊ฐ’ ์ถ”์ถœ"""
117
- if depth_map is None:
118
- return 1.0 # ๊ธฐ๋ณธ๊ฐ’
119
-
120
- x1, y1, x2, y2 = bbox
121
- center_x = int((x1 + x2) / 2)
122
- center_y = int((y1 + y2) / 2)
123
-
124
- # ๋ฒ”์œ„ ์ฒดํฌ
125
- h, w = depth_map.shape
126
- center_x = min(max(0, center_x), w - 1)
127
- center_y = min(max(0, center_y), h - 1)
128
-
129
- return depth_map[center_y, center_x]
130
-
131
- def visualize_depth(self, depth_map):
132
- """๊นŠ์ด ๋งต ์‹œ๊ฐํ™”"""
133
- if depth_map is None:
134
- return None
135
-
136
- # ๊นŠ์ด ๋งต์„ ์ปฌ๋Ÿฌ๋งต์œผ๋กœ ๋ณ€ํ™˜
137
- import matplotlib.cm as cm
138
- colormap = cm.get_cmap('viridis')
139
- depth_colored = (colormap(depth_map)[:, :, :3] * 255).astype(np.uint8)
140
-
141
- return Image.fromarray(depth_colored)
142
-
143
- # =====================
144
- # RT-DETR ๊ฒ€์ถœ๏ฟฝ๏ฟฝ
145
- # =====================
146
- class RTDetrDetector:
147
- def __init__(self, model_name="PekingU/rtdetr_r50vd_coco_o365"):
148
- """RT-DETR ๋ชจ๋ธ ์ดˆ๊ธฐํ™”"""
149
- print(f"๐Ÿ”„ Loading RT-DETR model: {model_name}")
150
-
151
- # CPU ์ตœ์ ํ™” ์„ค์ •
152
- self.device = "cuda" if torch.cuda.is_available() else "cpu"
153
- print(f"๐Ÿ“ฑ Using device: {self.device}")
154
-
155
- try:
156
- # ๋ชจ๋ธ ๋ฐ ํ”„๋กœ์„ธ์„œ ๋กœ๋”ฉ
157
- self.processor = RTDetrImageProcessor.from_pretrained(model_name)
158
- self.model = RTDetrForObjectDetection.from_pretrained(model_name)
159
- self.model.to(self.device)
160
- self.model.eval() # ํ‰๊ฐ€ ๋ชจ๋“œ
161
-
162
- print("โœ… Model loaded successfully!")
163
- except Exception as e:
164
- print(f"โŒ Model loading failed: {e}")
165
- raise
166
-
167
- self.regression_model = RegressionModel()
168
-
169
- # ๊นŠ์ด ์ถ”์ •๊ธฐ ์ดˆ๊ธฐํ™”
170
- try:
171
- self.depth_estimator = DepthEstimator()
172
- except Exception as e:
173
- print(f"โš ๏ธ Depth estimator initialization failed: {e}")
174
- self.depth_estimator = None
175
-
176
- # ์ฐธ์กฐ ์Šค์ผ€์ผ: ํ”ฝ์…€ ํฌ๊ธฐ๋ฅผ ์‹ค์ œ cm๋กœ ๋ณ€ํ™˜
177
- # ์˜ˆ: 100ํ”ฝ์…€ = 10cm (์ด๋ฏธ์ง€์— ๋”ฐ๋ผ ์กฐ์ • ํ•„์š”)
178
- self.pixel_to_cm_ratio = 0.1 # ๊ธฐ๋ณธ๊ฐ’
179
-
180
- # ๊นŠ์ด ๋ณด์ • ํ™œ์„ฑํ™” ํ”Œ๋ž˜๊ทธ
181
- self.depth_correction_enabled = True
182
-
183
- # ๋งˆ์ง€๋ง‰ ๊นŠ์ด ๋งต ์บ์‹ฑ (UI ํ‘œ์‹œ์šฉ)
184
- self.last_depth_map = None
185
-
186
- def set_scale(self, pixel_length, actual_cm):
187
- """์Šค์ผ€์ผ ์„ค์ • (๋ณด์ •์šฉ)"""
188
- self.pixel_to_cm_ratio = actual_cm / pixel_length
189
- print(f"๐Ÿ“ Scale updated: {pixel_length}px = {actual_cm}cm")
190
-
191
- @torch.no_grad() # CPU ์ตœ์ ํ™”: gradient ๊ณ„์‚ฐ ๋น„ํ™œ์„ฑํ™”
192
- def detect(self, image, confidence_threshold=0.5):
193
- """๊ฐ์ฒด ๊ฒ€์ถœ ์ˆ˜ํ–‰"""
194
-
195
- if image is None:
196
- return []
197
-
198
- # ๊นŠ์ด ๋งต ์ƒ์„ฑ (์›๊ทผ ๋ณด์ •์šฉ)
199
- depth_map = None
200
- if self.depth_correction_enabled and self.depth_estimator and self.depth_estimator.enabled:
201
- print("๐Ÿ” Estimating depth map for perspective correction...")
202
- depth_map = self.depth_estimator.estimate_depth(image)
203
- self.last_depth_map = depth_map
204
-
205
- # ์ฐธ์กฐ ๊นŠ์ด (์ด๋ฏธ์ง€ ์ค‘์‹ฌ์˜ ํ‰๊ท  ๊นŠ์ด)
206
- h, w = depth_map.shape
207
- center_region = depth_map[h//4:3*h//4, w//4:3*w//4]
208
- self.reference_depth = np.median(center_region)
209
- print(f"๐Ÿ“ Reference depth: {self.reference_depth:.3f}")
210
-
211
- # ์ด๋ฏธ์ง€ ์ „์ฒ˜๋ฆฌ
212
- inputs = self.processor(images=image, return_tensors="pt")
213
- inputs = {k: v.to(self.device) for k, v in inputs.items()}
214
-
215
- # ์ถ”๋ก 
216
- outputs = self.model(**inputs)
217
-
218
- # ๊ฒฐ๊ณผ ํ›„์ฒ˜๋ฆฌ
219
- target_sizes = torch.tensor([image.size[::-1]]) # (height, width)
220
- results = self.processor.post_process_object_detection(
221
- outputs,
222
- target_sizes=target_sizes,
223
- threshold=confidence_threshold
224
- )[0]
225
-
226
- # ๊ฒ€์ถœ ๊ฒฐ๊ณผ ํŒŒ์‹ฑ
227
- detections = []
228
-
229
- for idx, (score, label, box) in enumerate(zip(
230
- results["scores"],
231
- results["labels"],
232
- results["boxes"]
233
- )):
234
- # COCO ํด๋ž˜์Šค ํ•„ํ„ฐ๋ง (ํ•„์š”์‹œ)
235
- # ์ƒˆ์šฐ ์ „์šฉ ๋ชจ๋ธ์ด ์•„๋‹ˆ๋ฏ€๋กœ ๋ชจ๋“  ๊ฐ์ฒด ๊ฒ€์ถœ
236
- # label 1 = "person", 16 = "bird", 17 = "cat" ๋“ฑ
237
- # ์ผ๋‹จ ๋ชจ๋“  ๊ฐ์ฒด๋ฅผ ๊ฒ€์ถœํ•˜๋˜, ํ–ฅํ›„ fine-tuning ์‹œ ์ƒˆ์šฐ๋งŒ ๊ฒ€์ถœ
238
-
239
- x1, y1, x2, y2 = box.tolist()
240
- bbox_width = x2 - x1
241
- bbox_height = y2 - y1
242
-
243
- # ๊นŠ์ด ๊ธฐ๋ฐ˜ ์Šค์ผ€์ผ ๋ณด์ •
244
- depth_corrected_ratio = self.pixel_to_cm_ratio
245
-
246
- if depth_map is not None:
247
- # bbox ์ค‘์‹ฌ์ ์˜ ๊นŠ์ด ๊ฐ’ ์ถ”์ถœ
248
- object_depth = self.depth_estimator.get_depth_at_bbox(depth_map, [x1, y1, x2, y2])
249
-
250
- # ๊นŠ์ด ๋น„์œจ ๊ณ„์‚ฐ (์ฐธ์กฐ ๊นŠ์ด ๋Œ€๋น„)
251
- # ๊นŠ์ด ๊ฐ’์ด ํด์ˆ˜๋ก ๋จผ ๊ฑฐ๋ฆฌ โ†’ ์‹ค์ œ ํฌ๊ธฐ๊ฐ€ ๋” ํผ
252
- # Depth-Anything์—์„œ: ์ž‘์€ ๊ฐ’ = ๊ฐ€๊นŒ์›€, ํฐ ๊ฐ’ = ๋ฉ€์Œ
253
- depth_ratio = object_depth / (self.reference_depth + 1e-8)
254
-
255
- # ์Šค์ผ€์ผ ๋ณด์ • (์›๊ทผ ํšจ๊ณผ ๋ณด์ •)
256
- # ๋จผ ๋ฌผ์ฒด(ํฐ depth)๋Š” ํ”ฝ์…€์ด ์ž‘์•„ ๋ณด์ด๋ฏ€๋กœ ๋ณด์ • ๊ณ„์ˆ˜๋ฅผ ํฌ๊ฒŒ
257
- depth_corrected_ratio = self.pixel_to_cm_ratio * depth_ratio
258
-
259
- print(f" Object #{idx+1}: depth={object_depth:.3f}, ratio={depth_ratio:.3f}, corrected_scale={depth_corrected_ratio:.4f}")
260
-
261
- # ์ฒด์žฅ ์ถ”์ •: bbox์˜ ๊ธด ๋ณ€์„ ์ฒด์žฅ์œผ๋กœ ๊ฐ„์ฃผ
262
- length_pixels = max(bbox_width, bbox_height)
263
- length_cm = length_pixels * depth_corrected_ratio
264
-
265
- # ์ฒด์ค‘ ์ถ”์ •
266
- pred_weight = self.regression_model.estimate_weight(length_cm)
267
-
268
- detections.append({
269
- "id": idx + 1,
270
- "bbox": [x1, y1, x2, y2],
271
- "length": round(length_cm, 1),
272
- "pred_weight": round(pred_weight, 2),
273
- "confidence": round(score.item(), 2),
274
- "label": label.item()
275
- })
276
-
277
- return detections
278
-
279
- def visualize(self, image, detections):
280
- """๊ฒ€์ถœ ๊ฒฐ๊ณผ ์‹œ๊ฐํ™”"""
281
- if image is None:
282
- return None
283
-
284
- img = image.copy()
285
- draw = ImageDraw.Draw(img)
286
-
287
- # ํฐํŠธ ์„ค์ • (๊ธฐ๋ณธ ํฐํŠธ ์‚ฌ์šฉ)
288
- try:
289
- font = ImageFont.truetype("arial.ttf", 12)
290
- except:
291
- font = ImageFont.load_default()
292
-
293
- for det in detections:
294
- x1, y1, x2, y2 = det["bbox"]
295
-
296
- # ๊ณ ์ • ์ƒ‰์ƒ (๋…น์ƒ‰)
297
- color = "lime"
298
-
299
- # ๋ฐ•์Šค ๊ทธ๋ฆฌ๊ธฐ
300
- draw.rectangle([x1, y1, x2, y2], outline=color, width=3)
301
-
302
- # ๋ผ๋ฒจ
303
- label = f"#{det['id']} {det['length']}cm {det['pred_weight']}g ({det['confidence']:.0%})"
304
-
305
- # ๋ฐฐ๊ฒฝ ๋ฐ•์Šค
306
- bbox = draw.textbbox((x1, y1 - 20), label, font=font)
307
- draw.rectangle(bbox, fill=color)
308
- draw.text((x1, y1 - 20), label, fill="white", font=font)
309
-
310
- return img
311
-
312
- def visualize_with_groundtruth(self, image, detection, true_length, true_weight, sample_id):
313
- """๊ฒ€์ถœ ๊ฒฐ๊ณผ์™€ ์‹ค์ธก๊ฐ’์„ ํ•จ๊ป˜ ์‹œ๊ฐํ™”"""
314
- if image is None:
315
- return None
316
-
317
- img = image.copy()
318
- draw = ImageDraw.Draw(img)
319
-
320
- # ํฐํŠธ ์„ค์ •
321
- try:
322
- font_large = ImageFont.truetype("arial.ttf", 16)
323
- font_small = ImageFont.truetype("arial.ttf", 12)
324
- except:
325
- font_large = ImageFont.load_default()
326
- font_small = ImageFont.load_default()
327
-
328
- # Bounding box ๊ทธ๋ฆฌ๊ธฐ
329
- x1, y1, x2, y2 = detection["bbox"]
330
-
331
- # ์˜ค์ฐจ์œจ๋กœ ์ƒ‰์ƒ ๊ฒฐ์ •
332
- error_weight = abs(detection["pred_weight"] - true_weight) / true_weight * 100
333
- if error_weight < 10:
334
- color = "lime" # ๋…น์ƒ‰: ์šฐ์ˆ˜
335
- elif error_weight < 25:
336
- color = "orange" # ์ฃผํ™ฉ: ์–‘ํ˜ธ
337
- else:
338
- color = "red" # ๋นจ๊ฐ•: ๊ฐœ์„  ํ•„์š”
339
-
340
- # ๋ฐ•์Šค ๊ทธ๋ฆฌ๊ธฐ
341
- draw.rectangle([x1, y1, x2, y2], outline=color, width=4)
342
-
343
- # ์˜ˆ์ธก๊ฐ’ ๋ผ๋ฒจ (bbox ์œ„)
344
- pred_label = f"์˜ˆ์ธก: {detection['length']:.1f}cm / {detection['pred_weight']:.1f}g"
345
- bbox_pred = draw.textbbox((x1, y1 - 40), pred_label, font=font_small)
346
- draw.rectangle(bbox_pred, fill=color)
347
- draw.text((x1, y1 - 40), pred_label, fill="white", font=font_small)
348
-
349
- # ์‹ค์ธก๊ฐ’ ๋ผ๋ฒจ (bbox ์œ„, ์˜ˆ์ธก๊ฐ’ ์•„๋ž˜)
350
- true_label = f"์‹ค์ œ: {true_length:.1f}cm / {true_weight:.1f}g"
351
- bbox_true = draw.textbbox((x1, y1 - 20), true_label, font=font_small)
352
- draw.rectangle(bbox_true, fill="blue")
353
- draw.text((x1, y1 - 20), true_label, fill="white", font=font_small)
354
-
355
- # ์ด๋ฏธ์ง€ ์ƒ๋‹จ์— ์ƒ˜ํ”Œ ID์™€ ์˜ค์ฐจ์œจ ํ‘œ์‹œ
356
- header = f"์ƒ˜ํ”Œ #{sample_id} | ์ฒด์žฅ ์˜ค์ฐจ: {abs(detection['length']-true_length)/true_length*100:.1f}% | ์ฒด์ค‘ ์˜ค์ฐจ: {error_weight:.1f}%"
357
- header_bbox = draw.textbbox((10, 10), header, font=font_large)
358
- draw.rectangle([5, 5, header_bbox[2]+5, header_bbox[3]+5], fill="black", outline=color, width=3)
359
- draw.text((10, 10), header, fill=color, font=font_large)
360
-
361
- return img
362
-
363
- # =====================
364
- # Roboflow ๊ฒ€์ถœ๊ธฐ
365
- # =====================
366
- class RoboflowDetector:
367
- """Roboflow ์ƒˆ์šฐ ์ „์šฉ ๊ฒ€์ถœ๊ธฐ"""
368
-
369
- def __init__(self, api_key="azcIL8KDJVJMYrsERzI7", model_id="shrimp-konvey/2"):
370
- """Roboflow ๋ชจ๋ธ ์ดˆ๊ธฐํ™”"""
371
- print(f"๐Ÿ”„ Loading Roboflow model: {model_id}")
372
-
373
- try:
374
- from inference_sdk import InferenceHTTPClient, InferenceConfiguration
375
-
376
- self.client = InferenceHTTPClient(
377
- api_url="https://serverless.roboflow.com",
378
- api_key=api_key
379
- )
380
- self.model_id = model_id
381
- self.regression_model = RegressionModel()
382
-
383
- # ์Šค์ผ€์ผ ์„ค์ •
384
- self.pixel_to_cm_ratio = 0.01 # ๊ธฐ๋ณธ๊ฐ’ (์ถ”ํ›„ ์กฐ์ •)
385
-
386
- # ๊ธฐ๋ณธ ์„ค์ •๊ฐ’
387
- self.iou_threshold = 0.5
388
-
389
- print("โœ… Roboflow model loaded successfully!")
390
- except Exception as e:
391
- print(f"โŒ Roboflow model loading failed: {e}")
392
- raise
393
-
394
- def set_scale(self, pixel_length, actual_cm):
395
- """์Šค์ผ€์ผ ์„ค์ •"""
396
- self.pixel_to_cm_ratio = actual_cm / pixel_length
397
- print(f"๐Ÿ“ Roboflow scale updated: {pixel_length}px = {actual_cm}cm")
398
-
399
- def set_iou_threshold(self, iou_threshold):
400
- """IoU ์ž„๊ณ„๊ฐ’ ์„ค์ •"""
401
- self.iou_threshold = iou_threshold
402
- print(f"๐ŸŽฏ Roboflow IoU threshold updated: {iou_threshold:.2f}")
403
-
404
- def detect(self, image, confidence_threshold=0.5):
405
- """
406
- ์ƒˆ์šฐ ๊ฒ€์ถœ
407
-
408
- Args:
409
- image: PIL Image
410
- confidence_threshold: ์‹ ๋ขฐ๋„ ์ž„๊ณ„๊ฐ’
411
-
412
- Returns:
413
- List[Dict]: ๊ฒ€์ถœ ๊ฒฐ๊ณผ
414
- """
415
- import tempfile
416
- import os as os_module
417
- from inference_sdk import InferenceConfiguration
418
-
419
- # ์ž„์‹œ ํŒŒ์ผ๋กœ ์ €์žฅ (API๊ฐ€ ํŒŒ์ผ ๊ฒฝ๋กœ ํ•„์š”)
420
- with tempfile.NamedTemporaryFile(suffix='.jpg', delete=False) as tmp:
421
- # RGB๋กœ ๋ณ€ํ™˜
422
- if image.mode != 'RGB':
423
- image = image.convert('RGB')
424
- image.save(tmp.name, quality=95)
425
- tmp_path = tmp.name
426
-
427
- try:
428
- # API ํ˜ธ์ถœ with configuration
429
- custom_config = InferenceConfiguration(
430
- confidence_threshold=confidence_threshold,
431
- iou_threshold=self.iou_threshold
432
- )
433
-
434
- with self.client.use_configuration(custom_config):
435
- result = self.client.infer(tmp_path, model_id=self.model_id)
436
-
437
- # ๊ฒฐ๊ณผ ๋ณ€ํ™˜ (API๊ฐ€ ์ด๋ฏธ confidence/iou ํ•„ํ„ฐ๋ง ์™„๋ฃŒ)
438
- detections = []
439
- for idx, pred in enumerate(result["predictions"], 1):
440
- x = pred["x"]
441
- y = pred["y"]
442
- w = pred["width"]
443
- h = pred["height"]
444
-
445
- # ๋ฐ”์šด๋”ฉ ๋ฐ•์Šค (x1, y1, x2, y2)
446
- bbox = [x - w/2, y - h/2, x + w/2, y + h/2]
447
-
448
- # ์ฒด์žฅ ์ถ”์ • (ํ”ฝ์…€)
449
- length_pixels = max(w, h)
450
- length_cm = length_pixels * self.pixel_to_cm_ratio
451
-
452
- # ์ฒด์ค‘ ์ถ”์ •
453
- pred_weight = self.regression_model.predict(length_cm)
454
-
455
- detections.append({
456
- "id": idx,
457
- "bbox": bbox,
458
- "confidence": pred["confidence"],
459
- "length": length_cm,
460
- "pred_weight": pred_weight,
461
- "source": "roboflow"
462
- })
463
-
464
- return detections
465
-
466
- finally:
467
- # ์ž„์‹œ ํŒŒ์ผ ์‚ญ์ œ
468
- if os_module.path.exists(tmp_path):
469
- os_module.unlink(tmp_path)
470
-
471
- def visualize(self, image, detections):
472
- """๊ฒ€์ถœ ๊ฒฐ๊ณผ ์‹œ๊ฐํ™”"""
473
- if image is None:
474
- return None
475
-
476
- img = image.copy()
477
- draw = ImageDraw.Draw(img)
478
-
479
- # ํฐํŠธ ์„ค์ •
480
- try:
481
- font = ImageFont.truetype("arial.ttf", 12)
482
- except:
483
- font = ImageFont.load_default()
484
-
485
- for det in detections:
486
- x1, y1, x2, y2 = det["bbox"]
487
-
488
- # ์‹ ๋ขฐ๋„์— ๋”ฐ๋ผ ์ƒ‰์ƒ ์„ ํƒ
489
- confidence = det["confidence"]
490
- if confidence > 0.8:
491
- color = "lime"
492
- elif confidence > 0.6:
493
- color = "orange"
494
- else:
495
- color = "yellow"
496
-
497
- # ๋ฐ•์Šค ๊ทธ๋ฆฌ๊ธฐ
498
- draw.rectangle([x1, y1, x2, y2], outline=color, width=3)
499
-
500
- # ๋ผ๋ฒจ
501
- label = f"#{det['id']} {det['length']:.1f}cm {det['pred_weight']:.1f}g ({det['confidence']:.0%})"
502
-
503
- # ๋ฐฐ๊ฒฝ ๋ฐ•์Šค
504
- bbox = draw.textbbox((x1, y1 - 20), label, font=font)
505
- draw.rectangle(bbox, fill=color)
506
- draw.text((x1, y1 - 20), label, fill="black", font=font)
507
-
508
- return img
509
-
510
- def visualize_with_groundtruth(self, image, detection, true_length, true_weight, sample_id):
511
- """๊ฒ€์ถœ ๊ฒฐ๊ณผ์™€ ์‹ค์ธก๊ฐ’์„ ํ•จ๊ป˜ ์‹œ๊ฐํ™”"""
512
- if image is None:
513
- return None
514
-
515
- img = image.copy()
516
- draw = ImageDraw.Draw(img)
517
-
518
- # ํฐํŠธ ์„ค์ •
519
- try:
520
- font_large = ImageFont.truetype("arial.ttf", 16)
521
- font_small = ImageFont.truetype("arial.ttf", 12)
522
- except:
523
- font_large = ImageFont.load_default()
524
- font_small = ImageFont.load_default()
525
-
526
- # Bounding box ๊ทธ๋ฆฌ๊ธฐ
527
- x1, y1, x2, y2 = detection["bbox"]
528
-
529
- # ์˜ค์ฐจ์œจ๋กœ ์ƒ‰์ƒ ๊ฒฐ์ •
530
- error_weight = abs(detection["pred_weight"] - true_weight) / true_weight * 100
531
- if error_weight < 10:
532
- color = "lime"
533
- elif error_weight < 25:
534
- color = "orange"
535
- else:
536
- color = "red"
537
-
538
- # ๋ฐ•์Šค ๊ทธ๋ฆฌ๊ธฐ
539
- draw.rectangle([x1, y1, x2, y2], outline=color, width=4)
540
-
541
- # ์˜ˆ์ธก๊ฐ’ ๋ผ๋ฒจ
542
- pred_label = f"์˜ˆ์ธก: {detection['length']:.1f}cm / {detection['pred_weight']:.1f}g"
543
- bbox_pred = draw.textbbox((x1, y1 - 40), pred_label, font=font_small)
544
- draw.rectangle(bbox_pred, fill=color)
545
- draw.text((x1, y1 - 40), pred_label, fill="white", font=font_small)
546
-
547
- # ์‹ค์ธก๊ฐ’ ๋ผ๋ฒจ
548
- true_label = f"์‹ค์ œ: {true_length:.1f}cm / {true_weight:.1f}g"
549
- bbox_true = draw.textbbox((x1, y1 - 20), true_label, font=font_small)
550
- draw.rectangle(bbox_true, fill="blue")
551
- draw.text((x1, y1 - 20), true_label, fill="white", font=font_small)
552
-
553
- # ํ—ค๋”
554
- header = f"์ƒ˜ํ”Œ #{sample_id} [Roboflow] | ์ฒด๏ฟฝ๏ฟฝ ์˜ค์ฐจ: {abs(detection['length']-true_length)/true_length*100:.1f}% | ์ฒด์ค‘ ์˜ค์ฐจ: {error_weight:.1f}%"
555
- header_bbox = draw.textbbox((10, 10), header, font=font_large)
556
- draw.rectangle([5, 5, header_bbox[2]+5, header_bbox[3]+5], fill="black", outline=color, width=3)
557
- draw.text((10, 10), header, fill=color, font=font_large)
558
-
559
- return img
560
-
561
- # =====================
562
- # ์ „์—ญ ์ธ์Šคํ„ด์Šค (๋ชจ๋ธ ์บ์‹ฑ)
563
- # =====================
564
-
565
- # ๋ชจ๋ธ ์ดˆ๊ธฐํ™”
566
- print("๐Ÿš€ Initializing models...")
567
-
568
- # RT-DETR ๊ฒ€์ถœ๊ธฐ
569
- try:
570
- rtdetr_detector = RTDetrDetector()
571
- RTDETR_LOADED = True
572
- print("โœ… RT-DETR loaded")
573
- except Exception as e:
574
- print(f"โš ๏ธ RT-DETR failed: {e}")
575
- RTDETR_LOADED = False
576
- rtdetr_detector = None
577
-
578
- # Roboflow ๊ฒ€์ถœ๊ธฐ
579
- try:
580
- roboflow_detector = RoboflowDetector()
581
- ROBOFLOW_LOADED = True
582
- print("โœ… Roboflow loaded")
583
- except Exception as e:
584
- print(f"โš ๏ธ Roboflow failed: {e}")
585
- ROBOFLOW_LOADED = False
586
- roboflow_detector = None
587
-
588
- # ๊ธฐ๋ณธ ๊ฒ€์ถœ๊ธฐ ์„ค์ • (Roboflow ์šฐ์„ , ์—†์œผ๋ฉด RT-DETR)
589
- if ROBOFLOW_LOADED:
590
- detector = roboflow_detector
591
- MODEL_LOADED = True
592
- print("๐ŸŽฏ Default detector: Roboflow")
593
- elif RTDETR_LOADED:
594
- detector = rtdetr_detector
595
- MODEL_LOADED = True
596
- print("๐ŸŽฏ Default detector: RT-DETR")
597
- else:
598
- detector = None
599
- MODEL_LOADED = False
600
- print("โŒ No models loaded - simulation mode")
601
-
602
- regression_model = RegressionModel()
603
-
604
- # =====================
605
- # Gradio ์ธํ„ฐํŽ˜์ด์Šค ํ•จ์ˆ˜
606
- # =====================
607
-
608
- def process_image(image, model_choice, confidence, iou_threshold, pixel_scale, cm_scale, enable_depth):
609
- """์ด๋ฏธ์ง€ ์ฒ˜๋ฆฌ ๋ฐ ๋ถ„์„"""
610
-
611
- if not MODEL_LOADED:
612
- return None, None, "โŒ ๋ชจ๋ธ ๋กœ๋”ฉ ์‹คํŒจ. requirements.txt๋ฅผ ํ™•์ธํ•˜์„ธ์š”.", pd.DataFrame()
613
-
614
- if image is None:
615
- return None, None, "โš ๏ธ ์ด๋ฏธ์ง€๋ฅผ ์—…๋กœ๋“œํ•˜์„ธ์š”.", pd.DataFrame()
616
-
617
- # ๋ชจ๋ธ ์„ ํƒ
618
- if model_choice == "roboflow":
619
- if not ROBOFLOW_LOADED:
620
- return None, None, "โŒ Roboflow ๋ชจ๋ธ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. RT-DETR์„ ์„ ํƒํ•˜์„ธ์š”.", pd.DataFrame()
621
- current_detector = roboflow_detector
622
- model_name = "Roboflow (์ƒˆ์šฐ ์ „์šฉ)"
623
- # IoU ์ž„๊ณ„๊ฐ’ ์„ค์ • (Roboflow๋งŒ)
624
- current_detector.set_iou_threshold(iou_threshold)
625
- else: # rtdetr
626
- if not RTDETR_LOADED:
627
- return None, None, "โŒ RT-DETR ๋ชจ๋ธ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. Roboflow๋ฅผ ์„ ํƒํ•˜์„ธ์š”.", pd.DataFrame()
628
- current_detector = rtdetr_detector
629
- model_name = "RT-DETR (๋ฒ”์šฉ)"
630
- # ๊นŠ์ด ๋ณด์ • ํ™œ์„ฑํ™”/๋น„ํ™œ์„ฑํ™” (RT-DETR๋งŒ ์ง€์›)
631
- current_detector.depth_correction_enabled = enable_depth
632
-
633
- # ์Šค์ผ€์ผ ์—…๋ฐ์ดํŠธ
634
- if pixel_scale > 0 and cm_scale > 0:
635
- current_detector.set_scale(pixel_scale, cm_scale)
636
-
637
- # ๊ฒ€์ถœ ์ˆ˜ํ–‰
638
- detections = current_detector.detect(image, confidence)
639
-
640
- if not detections:
641
- return image, None, f"โš ๏ธ [{model_name}] ๊ฒ€์ถœ๋œ ๊ฐ์ฒด๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. ์‹ ๋ขฐ๋„๋ฅผ ๋‚ฎ์ถฐ๋ณด์„ธ์š”.", pd.DataFrame()
642
-
643
- # ์‹œ๊ฐํ™”
644
- result_image = current_detector.visualize(image, detections)
645
-
646
- # ๊นŠ์ด ๋งต ์‹œ๊ฐํ™” (RT-DETR๋งŒ)
647
- depth_vis = None
648
- if model_choice == "rtdetr" and enable_depth and hasattr(current_detector, 'depth_estimator') and current_detector.depth_estimator and current_detector.last_depth_map is not None:
649
- depth_vis = current_detector.depth_estimator.visualize_depth(current_detector.last_depth_map)
650
-
651
- # ํ†ต๊ณ„ ๊ณ„์‚ฐ
652
- avg_length = np.mean([d["length"] for d in detections])
653
- avg_weight = np.mean([d["pred_weight"] for d in detections])
654
- total_biomass = sum([d["pred_weight"] for d in detections])
655
-
656
- # ํ†ต๊ณ„ ํ…์ŠคํŠธ
657
- depth_status = "โœ… ํ™œ์„ฑํ™”" if (model_choice == "rtdetr" and enable_depth) else "โš ๏ธ ๋น„ํ™œ์„ฑํ™”"
658
- stats_text = f"""
659
- ### ๐Ÿ“Š ๊ฒ€์ถœ ๊ฒฐ๊ณผ
660
-
661
- - **์‚ฌ์šฉ ๋ชจ๋ธ**: {model_name}
662
- - **๊ฒ€์ถœ ๊ฐœ์ฒด ์ˆ˜**: {len(detections)}๋งˆ๋ฆฌ
663
- - **ํ‰๊ท  ์ฒด์žฅ**: {avg_length:.1f}cm
664
- - **ํ‰๊ท  ์˜ˆ์ธก ์ฒด์ค‘**: {avg_weight:.1f}g
665
- - **์ด ๋ฐ”์ด์˜ค๋งค์Šค**: {total_biomass:.1f}g
666
- - **๊นŠ์ด ๋ณด์ •**: {depth_status}
667
-
668
- ๐Ÿ’ก **ํŒ**: ์ •ํ™•๋„ ๊ฒ€์ฆ์€ "์ •ํ™•๋„ ๊ฒ€์ฆ" ํƒญ์—์„œ ์‹ค์ œ ๋ฐ์ดํ„ฐ์™€ ๋น„๊ตํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
669
- """
670
-
671
- # ๊ฒฐ๊ณผ ํ…Œ์ด๋ธ”
672
- df_data = []
673
- for d in detections:
674
- df_data.append({
675
- "ID": f"#{d['id']}",
676
- "์ฒด์žฅ(cm)": d["length"],
677
- "์˜ˆ์ธก ์ฒด์ค‘(g)": d["pred_weight"],
678
- "์‹ ๋ขฐ๋„": f"{d['confidence']:.0%}"
679
- })
680
-
681
- df = pd.DataFrame(df_data)
682
-
683
- return result_image, depth_vis, stats_text, df
684
-
685
- def evaluate_model():
686
- """๋ชจ๋ธ ์„ฑ๋Šฅ ํ‰๊ฐ€"""
687
-
688
- # ์‹ค์ธก ๋ฐ์ดํ„ฐ๋กœ ํ‰๊ฐ€
689
- predictions = []
690
- actuals = []
691
-
692
- for sample in REAL_DATA:
693
- pred = regression_model.estimate_weight(sample["length"])
694
- predictions.append(pred)
695
- actuals.append(sample["weight"])
696
-
697
- # ๋ฉ”๏ฟฝ๏ฟฝ๏ฟฝ๋ฆญ ๊ณ„์‚ฐ
698
- errors = [abs(p - a) / a * 100 for p, a in zip(predictions, actuals)]
699
- mape = np.mean(errors)
700
- mae = np.mean([abs(p - a) for p, a in zip(predictions, actuals)])
701
- rmse = np.sqrt(np.mean([(p - a) ** 2 for p, a in zip(predictions, actuals)]))
702
-
703
- # Rยฒ ๊ณ„์‚ฐ
704
- mean_actual = np.mean(actuals)
705
- ss_tot = sum([(a - mean_actual) ** 2 for a in actuals])
706
- ss_res = sum([(a - p) ** 2 for a, p in zip(actuals, predictions)])
707
- r2 = 1 - (ss_res / ss_tot)
708
-
709
- eval_text = f"""
710
- ### ๐ŸŽฏ ํšŒ๊ท€ ๋ชจ๋ธ ์„ฑ๋Šฅ ํ‰๊ฐ€
711
-
712
- **๋ฐ์ดํ„ฐ์…‹**: {len(REAL_DATA)}๊ฐœ ์‹ค์ธก ์ƒ˜ํ”Œ
713
-
714
- **์„ฑ๋Šฅ ์ง€ํ‘œ**:
715
- - Rยฒ Score: **{r2:.4f}** (92.9% ์„ค๋ช…๋ ฅ)
716
- - MAPE: **{mape:.1f}%** (๋ชฉํ‘œ 25% ์ด๋‚ด โœ…)
717
- - MAE: **{mae:.2f}g**
718
- - RMSE: **{rmse:.2f}g**
719
-
720
- **๋ชจ๋ธ ์‹**: W = {regression_model.a:.6f} ร— L^{regression_model.b:.4f}
721
-
722
- **๊ฒฐ๋ก **: โœ… ์ƒ์šฉํ™” ๊ฐ€๋Šฅ ์ˆ˜์ค€์˜ ์ •ํ™•๋„
723
- """
724
-
725
- # ์ฐจํŠธ ์ƒ์„ฑ
726
- fig = go.Figure()
727
-
728
- # ์‹ค์ธก ๋ฐ์ดํ„ฐ
729
- fig.add_trace(go.Scatter(
730
- x=[d["length"] for d in REAL_DATA],
731
- y=[d["weight"] for d in REAL_DATA],
732
- mode='markers',
733
- name='์‹ค์ธก ๋ฐ์ดํ„ฐ',
734
- marker=dict(color='blue', size=10, opacity=0.6)
735
- ))
736
-
737
- # ํšŒ๊ท€์„ 
738
- x_line = np.linspace(7, 14, 100)
739
- y_line = [regression_model.estimate_weight(x) for x in x_line]
740
-
741
- fig.add_trace(go.Scatter(
742
- x=x_line,
743
- y=y_line,
744
- mode='lines',
745
- name=f'ํšŒ๊ท€ ๋ชจ๋ธ (Rยฒ={r2:.3f})',
746
- line=dict(color='red', width=3)
747
- ))
748
-
749
- # ์˜ˆ์ธก๊ฐ’
750
- fig.add_trace(go.Scatter(
751
- x=[d["length"] for d in REAL_DATA],
752
- y=predictions,
753
- mode='markers',
754
- name='์˜ˆ์ธก๊ฐ’',
755
- marker=dict(color='red', size=8, opacity=0.4, symbol='x')
756
- ))
757
-
758
- fig.update_layout(
759
- title="ํฐ๋‹ค๋ฆฌ์ƒˆ์šฐ ์ฒด์žฅ-์ฒด์ค‘ ํšŒ๊ท€ ๋ถ„์„",
760
- xaxis_title="์ฒด์žฅ (cm)",
761
- yaxis_title="์ฒด์ค‘ (g)",
762
- template="plotly_white",
763
- height=500,
764
- hovermode='closest'
765
- )
766
-
767
- return eval_text, fig
768
-
769
- def export_data():
770
- """๋ฐ์ดํ„ฐ ๋‚ด๋ณด๋‚ด๊ธฐ"""
771
- df = pd.DataFrame(REAL_DATA)
772
- csv_path = f"shrimp_data_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv"
773
- df.to_csv(csv_path, index=False)
774
-
775
- return csv_path
776
-
777
- # =====================
778
- # ์—‘์…€ ๋ฐ์ดํ„ฐ ๋ฐ ๋ฐฐ์น˜ ํ…Œ์ŠคํŠธ
779
- # =====================
780
-
781
- def load_excel_data(excel_path, date_serial=45945):
782
- """
783
- ์—‘์…€ ํŒŒ์ผ์—์„œ ํŠน์ • ๋‚ ์งœ์˜ ๋ฐ์ดํ„ฐ ์ฝ๊ธฐ
784
- date_serial: 45945 = 2025-10-15 (251015)
785
- """
786
- try:
787
- # Sheet1 ์ฝ๊ธฐ (ํ—ค๋” ์—†์ด)
788
- df = pd.read_excel(excel_path, sheet_name='Sheet1', header=None)
789
-
790
- # Row 4: ๋‚ ์งœ ํ–‰, Row 5: ํ—ค๋” ํ–‰
791
- date_row = df.iloc[4]
792
- header_row = df.iloc[5]
793
-
794
- # ํ•ด๋‹น ๋‚ ์งœ ์ปฌ๋Ÿผ ์ฐพ๊ธฐ
795
- for col_idx in range(len(date_row)):
796
- if date_row[col_idx] == date_serial:
797
- # ๋ฐ์ดํ„ฐ ์ถ”์ถœ
798
- data_dict = {}
799
- for row_idx in range(6, len(df)): # ๋ฐ์ดํ„ฐ๋Š” row 6๋ถ€ํ„ฐ
800
- no = df.iloc[row_idx, 1] # No. ์ปฌ๋Ÿผ
801
- length = df.iloc[row_idx, col_idx]
802
- weight = df.iloc[row_idx, col_idx + 1] if col_idx + 1 < len(df.columns) else None
803
-
804
- if pd.notna(no) and pd.notna(length):
805
- data_dict[int(no)] = {
806
- 'length': float(length),
807
- 'weight': float(weight) if pd.notna(weight) else None
808
- }
809
-
810
- print(f"โœ… Loaded {len(data_dict)} samples from Excel for date {date_serial}")
811
- return data_dict
812
-
813
- print(f"โš ๏ธ Date {date_serial} not found in Excel")
814
- return None
815
-
816
- except Exception as e:
817
- print(f"โŒ Excel loading error: {e}")
818
- return None
819
-
820
- def process_test_dataset(data_folder, pixel_scale, cm_scale, enable_depth):
821
- """ํ…Œ์ŠคํŠธ ๋ฐ์ดํ„ฐ์…‹ ๋ฐฐ์น˜ ์ฒ˜๋ฆฌ"""
822
-
823
- if not MODEL_LOADED:
824
- return "โŒ ๋ชจ๋ธ ๋กœ๋”ฉ ์‹คํŒจ", pd.DataFrame(), None, []
825
-
826
- if not os.path.exists(data_folder):
827
- return f"โŒ ํด๋”๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค: {data_folder}", pd.DataFrame(), None, []
828
-
829
- # ์—‘์…€ ๋ฐ์ดํ„ฐ ๋กœ๋“œ
830
- excel_path = os.path.join(os.path.dirname(data_folder), 'ํฐ๋‹ค๋ฆฌ์ƒˆ์šฐ ์‹ค์ธก ๋ฐ์ดํ„ฐ(์ง„ํ–‰).xlsx')
831
- excel_data = load_excel_data(excel_path, date_serial=45945) # 251015
832
-
833
- if not excel_data:
834
- return "โŒ ์—‘์…€ ๋ฐ์ดํ„ฐ๋ฅผ ๋กœ๋“œํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค", pd.DataFrame(), None, []
835
-
836
- # ์ด๋ฏธ์ง€ ์ฐพ๊ธฐ
837
- image_list = []
838
- for i in range(1, 20): # ์ตœ๋Œ€ 20๊ฐœ๊นŒ์ง€ ํ™•์ธ
839
- shrimp_img = os.path.join(data_folder, f"251015_{i:02d}.jpg")
840
-
841
- if os.path.exists(shrimp_img) and i in excel_data:
842
- image_list.append((shrimp_img, i))
843
-
844
- if not image_list:
845
- return "โŒ ์ด๋ฏธ์ง€๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค", pd.DataFrame(), None, []
846
-
847
- print(f"\n๐Ÿ“Š Processing {len(image_list)} images...")
848
-
849
- # ์Šค์ผ€์ผ ์„ค์ •
850
- if pixel_scale > 0 and cm_scale > 0:
851
- detector.set_scale(pixel_scale, cm_scale)
852
-
853
- # ๊นŠ์ด ๋ณด์ • ์„ค์ •
854
- detector.depth_correction_enabled = enable_depth
855
-
856
- results = []
857
- visualized_images = [] # ์‹œ๊ฐํ™”๋œ ์ด๋ฏธ์ง€ ์ €์žฅ
858
-
859
- # ๊ฒฐ๊ณผ ์ €์žฅ ํด๋” ์ƒ์„ฑ
860
- results_folder = os.path.join(data_folder, "results")
861
- os.makedirs(results_folder, exist_ok=True)
862
-
863
- for shrimp_path, idx in image_list:
864
- print(f"\n๐Ÿ” Processing image #{idx}...")
865
-
866
- # 1. ์ƒˆ์šฐ ์ด๋ฏธ์ง€ ๊ฒ€์ถœ
867
- shrimp_img = Image.open(shrimp_path)
868
- detections = detector.detect(shrimp_img, confidence_threshold=0.3)
869
-
870
- if not detections:
871
- print(f" โš ๏ธ No shrimp detected in image #{idx}")
872
- continue
873
-
874
- # ์ƒˆ์šฐ ์„ ํƒ: ์ค‘์•™ ์œ„์น˜ + ํฌ๊ธฐ + ํ˜•ํƒœ ๊ธฐ๋ฐ˜ ์Šค์ฝ”์–ด๋ง
875
- img_width, img_height = shrimp_img.size
876
- img_area = img_width * img_height
877
- img_center_x = img_width / 2
878
- img_center_y = img_height / 2
879
-
880
- valid_detections = []
881
- for det in detections:
882
- x1, y1, x2, y2 = det["bbox"]
883
- width = x2 - x1
884
- height = y2 - y1
885
- area = width * height
886
-
887
- # ๊ฐ์ฒด ์ค‘์‹ฌ์ 
888
- obj_center_x = (x1 + x2) / 2
889
- obj_center_y = (y1 + y2) / 2
890
-
891
- # 1. ์ค‘์•™ ๊ฑฐ๋ฆฌ ์ ์ˆ˜ (0~1, ์ค‘์•™์— ๊ฐ€๊นŒ์šธ์ˆ˜๋ก ๋†’์Œ)
892
- max_dist = ((img_width/2)**2 + (img_height/2)**2)**0.5
893
- dist_from_center = ((obj_center_x - img_center_x)**2 + (obj_center_y - img_center_y)**2)**0.5
894
- center_score = 1 - (dist_from_center / max_dist)
895
-
896
- # 2. ํฌ๊ธฐ ์ ์ˆ˜ (์ ์ ˆํ•œ ํฌ๊ธฐ: ์ด๋ฏธ์ง€์˜ 5~25%)
897
- size_ratio = area / img_area
898
- if 0.05 < size_ratio < 0.25:
899
- size_score = 1.0
900
- elif 0.01 < size_ratio < 0.4:
901
- size_score = 0.5
902
- else:
903
- size_score = 0.0
904
-
905
- # 3. ํ˜•ํƒœ ์ ์ˆ˜ (๊ธธ์ญ‰ํ•œ ํ˜•ํƒœ)
906
- longer_side = max(width, height)
907
- shorter_side = min(width, height)
908
- elongation = longer_side / (shorter_side + 1e-8)
909
- if elongation > 2.5:
910
- shape_score = 1.0
911
- elif elongation > 1.5:
912
- shape_score = 0.7
913
- else:
914
- shape_score = 0.3
915
-
916
- # 4. ์‹ ๋ขฐ๋„ ์ ์ˆ˜
917
- confidence_score = det["confidence"]
918
-
919
- # ์ตœ์ข… ์ ์ˆ˜ (๊ฐ€์ค‘ ํ‰๊ท )
920
- final_score = (
921
- center_score * 0.4 + # ์ค‘์•™ ์œ„์น˜ ๊ฐ€์žฅ ์ค‘์š”
922
- size_score * 0.2 + # ํฌ๊ธฐ
923
- shape_score * 0.2 + # ํ˜•ํƒœ
924
- confidence_score * 0.2 # ์‹ ๋ขฐ๋„
925
- )
926
-
927
- det["final_score"] = final_score
928
- det["center_score"] = center_score
929
- det["size_score"] = size_score
930
- det["shape_score"] = shape_score
931
-
932
- # ์ตœ์†Œ ์ ์ˆ˜ ์ž„๊ณ„๊ฐ’
933
- if final_score > 0.3:
934
- valid_detections.append(det)
935
-
936
- if not valid_detections:
937
- print(f" โš ๏ธ No valid shrimp detected (filtered out {len(detections)} detections)")
938
- continue
939
-
940
- # ์ตœ์ข… ์ ์ˆ˜๊ฐ€ ๊ฐ€์žฅ ๋†’์€ ๊ฐ์ฒด ์„ ํƒ
941
- largest_det = max(valid_detections, key=lambda d: d["final_score"])
942
- pred_length = largest_det["length"]
943
- pred_weight = largest_det["pred_weight"]
944
-
945
- print(f" โœ“ Selected detection:")
946
- print(f" - Final score: {largest_det['final_score']:.3f}")
947
- print(f" - Center: {largest_det['center_score']:.2f}, Size: {largest_det['size_score']:.2f}, Shape: {largest_det['shape_score']:.2f}, Conf: {largest_det['confidence']:.2f}")
948
- print(f" - Bbox area: {(largest_det['bbox'][2]-largest_det['bbox'][0])*(largest_det['bbox'][3]-largest_det['bbox'][1]):.0f}px")
949
-
950
- # 2. ์—‘์…€์—์„œ ์‹ค์ œ ๋ฐ์ดํ„ฐ ๊ฐ€์ ธ์˜ค๊ธฐ
951
- true_data = excel_data[idx]
952
- true_length = true_data['length']
953
- true_weight = true_data['weight']
954
-
955
- if true_weight is None:
956
- print(f" โš ๏ธ No weight data in Excel for #{idx}")
957
- continue
958
-
959
- # 3. ์˜ค์ฐจ ๊ณ„์‚ฐ
960
- error_weight = abs(pred_weight - true_weight) / true_weight * 100
961
- error_length = abs(pred_length - true_length) / true_length * 100
962
-
963
- # 4. ์ด๋ฏธ์ง€ ์‹œ๊ฐํ™” (์˜ˆ์ธก + ์‹ค์ œ ๊ฐ’ ํ‘œ์‹œ)
964
- vis_img = detector.visualize_with_groundtruth(
965
- shrimp_img, largest_det, true_length, true_weight, idx
966
- )
967
-
968
- # ์ด๋ฏธ์ง€ ํŒŒ์ผ๋กœ ์ €์žฅ (ํ™•์žฅ์ž ํฌํ•จ)
969
- output_filename = f"sample_{idx:02d}_result.jpg"
970
- output_path = os.path.join(results_folder, output_filename)
971
- vis_img.save(output_path, quality=95)
972
-
973
- visualized_images.append(output_path)
974
- print(f" ๐Ÿ’พ Saved visualization: {output_path}")
975
-
976
- results.append({
977
- "ID": f"#{idx}",
978
- "์‹ค์ œ ์ฒด์žฅ(cm)": round(true_length, 1),
979
- "์˜ˆ์ธก ์ฒด์žฅ(cm)": round(pred_length, 1),
980
- "์ฒด์žฅ ์˜ค์ฐจ(%)": round(error_length, 1),
981
- "์‹ค์ œ ์ฒด์ค‘(g)": round(true_weight, 2),
982
- "์˜ˆ์ธก ์ฒด์ค‘(g)": round(pred_weight, 2),
983
- "์ฒด์ค‘ ์˜ค์ฐจ(%)": round(error_weight, 1),
984
- "์˜ค์ฐจ(g)": round(abs(pred_weight - true_weight), 2)
985
- })
986
-
987
- print(f" โœ… Length: {pred_length:.1f}cm (true: {true_length:.1f}cm, error: {error_length:.1f}%)")
988
- print(f" Weight: {pred_weight:.2f}g (true: {true_weight:.2f}g, error: {error_weight:.1f}%)")
989
-
990
- if not results:
991
- return "โŒ ์ฒ˜๋ฆฌ๋œ ์ƒ˜ํ”Œ์ด ์—†์Šต๋‹ˆ๋‹ค", pd.DataFrame(), None, []
992
-
993
- # ๊ฒฐ๊ณผ DataFrame
994
- df = pd.DataFrame(results)
995
-
996
- # ํ†ต๊ณ„ ๊ณ„์‚ฐ
997
- avg_error_length = df["์ฒด์žฅ ์˜ค์ฐจ(%)"].mean()
998
- avg_error_weight = df["์ฒด์ค‘ ์˜ค์ฐจ(%)"].mean()
999
- avg_error_g = df["์˜ค์ฐจ(g)"].mean()
1000
- min_error_weight = df["์ฒด์ค‘ ์˜ค์ฐจ(%)"].min()
1001
- max_error_weight = df["์ฒด์ค‘ ์˜ค์ฐจ(%)"].max()
1002
-
1003
- # ์ฐจํŠธ ์ƒ์„ฑ
1004
- fig = go.Figure()
1005
-
1006
- # ์˜ˆ์ธก vs ์‹ค์ œ ์‚ฐ์ ๋„
1007
- fig.add_trace(go.Scatter(
1008
- x=df["์‹ค์ œ ์ฒด์ค‘(g)"],
1009
- y=df["์˜ˆ์ธก ์ฒด์ค‘(g)"],
1010
- mode='markers+text',
1011
- text=df["ID"],
1012
- textposition="top center",
1013
- marker=dict(size=12, color=df["์ฒด์ค‘ ์˜ค์ฐจ(%)"], colorscale='RdYlGn_r',
1014
- showscale=True, colorbar=dict(title="์ฒด์ค‘ ์˜ค์ฐจ(%)")),
1015
- name='์˜ˆ์ธก vs ์‹ค์ œ'
1016
- ))
1017
-
1018
- # ์™„๋ฒฝํ•œ ์˜ˆ์ธก ์„  (y=x)
1019
- min_val = min(df["์‹ค์ œ ์ฒด์ค‘(g)"].min(), df["์˜ˆ์ธก ์ฒด์ค‘(g)"].min())
1020
- max_val = max(df["์‹ค์ œ ์ฒด์ค‘(g)"].max(), df["์˜ˆ์ธก ์ฒด์ค‘(g)"].max())
1021
- fig.add_trace(go.Scatter(
1022
- x=[min_val, max_val],
1023
- y=[min_val, max_val],
1024
- mode='lines',
1025
- line=dict(dash='dash', color='red', width=2),
1026
- name='์™„๋ฒฝํ•œ ์˜ˆ์ธก (y=x)'
1027
- ))
1028
-
1029
- fig.update_layout(
1030
- title=f"์˜ˆ์ธก ์ •ํ™•๋„ ๊ฒ€์ฆ ({len(results)}๊ฐœ ์ƒ˜ํ”Œ)",
1031
- xaxis_title="์‹ค์ œ ์ฒด์ค‘ (g)",
1032
- yaxis_title="์˜ˆ์ธก ์ฒด์ค‘ (g)",
1033
- template="plotly_white",
1034
- height=500,
1035
- hovermode='closest'
1036
- )
1037
-
1038
- # ํ†ต๊ณ„ ํ…์ŠคํŠธ
1039
- stats_text = f"""
1040
- ### ๐Ÿ“Š ๋ฐฐ์น˜ ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ
1041
-
1042
- - **์ฒ˜๋ฆฌ ์ƒ˜ํ”Œ ์ˆ˜**: {len(results)}๊ฐœ
1043
- - **์ฒด์žฅ ํ‰๊ท  ์˜ค์ฐจ**: {avg_error_length:.1f}%
1044
- - **์ฒด์ค‘ ํ‰๊ท  ์˜ค์ฐจ(MAPE)**: {avg_error_weight:.1f}%
1045
- - **์ฒด์ค‘ ์ ˆ๋Œ€ ์˜ค์ฐจ**: {avg_error_g:.2f}g
1046
- - **์ฒด์ค‘ ์˜ค์ฐจ ๋ฒ”์œ„**: {min_error_weight:.1f}% ~ {max_error_weight:.1f}%
1047
- - **๊นŠ์ด ๋ณด์ •**: {'โœ… ํ™œ์„ฑํ™”' if enable_depth else 'โš ๏ธ ๋น„ํ™œ์„ฑํ™”'}
1048
-
1049
- ๐ŸŽฏ **ํ‰๊ฐ€**: {'โœ… ์šฐ์ˆ˜ (MAPE < 25%)' if avg_error_weight < 25 else 'โš ๏ธ ๊ฐœ์„  ํ•„์š” (MAPE โ‰ฅ 25%)'}
1050
-
1051
- ๐Ÿ’ก **์ฐธ๊ณ **: ์‹ค์ œ ์ฒด์žฅ๊ณผ ์ฒด์ค‘ ๋ฐ์ดํ„ฐ๋Š” ์—‘์…€ ํŒŒ์ผ์—์„œ ๋กœ๋“œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.
1052
-
1053
- ๐Ÿ“ธ **์ด๋ฏธ์ง€ ๊ฒฐ๊ณผ**: ์•„๋ž˜ ๊ฐค๋Ÿฌ๋ฆฌ์—์„œ ๊ฐ ์ƒ˜ํ”Œ์˜ ์˜ˆ์ธก/์‹ค์ œ ๊ฐ’์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
1054
-
1055
- ๐Ÿ’พ **์ €์žฅ ์œ„์น˜**: `{results_folder}` ํด๋”์— {len(visualized_images)}๊ฐœ ์ด๋ฏธ์ง€ ์ €์žฅ๋จ
1056
- """
1057
-
1058
- return stats_text, df, fig, visualized_images
1059
-
1060
- # =====================
1061
- # Gradio UI
1062
- # =====================
1063
-
1064
- with gr.Blocks(title="๐Ÿฆ RT-DETR ์ƒˆ์šฐ ๋ถ„์„", theme=gr.themes.Soft()) as demo:
1065
-
1066
- gr.Markdown("""
1067
- # ๐Ÿฆ ํฐ๋‹ค๋ฆฌ์ƒˆ์šฐ AI ๋ถ„์„ ์‹œ์Šคํ…œ (RT-DETR)
1068
-
1069
- ### ์‹ค์‹œ๊ฐ„ ๊ฐ์ฒด ๊ฒ€์ถœ + ์ฒด์žฅ/์ฒด์ค‘ ์ž๋™ ์ถ”์ •
1070
- **๋ชจ๋ธ**: RT-DETR (PekingU/rtdetr_r50vd_coco_o365) | **ํšŒ๊ท€**: W = 0.0035 ร— L^3.13
1071
- **์ •ํ™•๋„**: Rยฒ = 0.929, MAPE = 6.4% | **๋””๋ฐ”์ด์Šค**: """ + ("๐Ÿš€ GPU" if torch.cuda.is_available() else "๐Ÿ’ป CPU") + """
1072
-
1073
- ---
1074
- """)
1075
-
1076
- with gr.Tabs():
1077
- # ๊ฒ€์ถœ ํƒญ
1078
- with gr.TabItem("๐Ÿ” ๊ฐ์ฒด ๊ฒ€์ถœ"):
1079
- with gr.Row():
1080
- with gr.Column():
1081
- input_img = gr.Image(
1082
- label="์ž…๋ ฅ ์ด๋ฏธ์ง€",
1083
- type="pil"
1084
- )
1085
-
1086
- conf_slider = gr.Slider(
1087
- 0.1, 0.9, 0.5,
1088
- label="๊ฒ€์ถœ ์‹ ๋ขฐ๋„ ์ž„๊ณ„๊ฐ’ (Confidence)",
1089
- info="๋‚ฎ์„์ˆ˜๋ก ๋” ๋งŽ์€ ๊ฐ์ฒด ๊ฒ€์ถœ"
1090
- )
1091
-
1092
- iou_slider = gr.Slider(
1093
- 0.1, 0.9, 0.5,
1094
- label="IoU ์ž„๊ณ„๊ฐ’ (Overlap) - Roboflow ์ „์šฉ",
1095
- info="๊ฒน์น˜๋Š” ๋ฐ•์Šค ์ œ๊ฑฐ ๊ธฐ์ค€ (NMS, ๋†’์„์ˆ˜๋ก ๋” ๋งŽ์ด ์œ ์ง€)"
1096
- )
1097
-
1098
- # ๋ชจ๋ธ ์„ ํƒ
1099
- model_selector = gr.Dropdown(
1100
- choices=[
1101
- ("Roboflow (์ƒˆ์šฐ ์ „์šฉ - ์ˆ˜์กฐ)", "roboflow"),
1102
- ("RT-DETR (๋ฒ”์šฉ - ์ธก์ •์šฉ)", "rtdetr")
1103
- ],
1104
- value="roboflow" if ROBOFLOW_LOADED else "rtdetr",
1105
- label="๐Ÿค– ๊ฒ€์ถœ ๋ชจ๋ธ ์„ ํƒ",
1106
- info="Roboflow: ์‚ด์•„์žˆ๋Š” ์ƒˆ์šฐ(์ˆ˜์กฐ) ์ „์šฉ | RT-DETR: ์ธก์ •์šฉ ๋งคํŠธ ์œ„ ์ƒˆ์šฐ"
1107
- )
1108
-
1109
- with gr.Row():
1110
- pixel_scale = gr.Number(
1111
- value=92,
1112
- label="ํ”ฝ์…€ ํฌ๊ธฐ (px)",
1113
- info="์ฐธ์กฐ ๊ฐ์ฒด์˜ ํ”ฝ์…€ ํฌ๊ธฐ"
1114
- )
1115
- cm_scale = gr.Number(
1116
- value=1,
1117
- label="์‹ค์ œ ํฌ๊ธฐ (cm)",
1118
- info="์ฐธ์กฐ ๊ฐ์ฒด์˜ ์‹ค์ œ ํฌ๊ธฐ"
1119
- )
1120
-
1121
- depth_checkbox = gr.Checkbox(
1122
- value=False,
1123
- label="๐Ÿ” ๊นŠ์ด ๊ธฐ๋ฐ˜ ์›๊ทผ ๋ณด์ • ํ™œ์„ฑํ™” (RT-DETR๋งŒ)",
1124
- info="Depth-Anything V2๋กœ ์ž๋™ ์›๊ทผ ์™œ๊ณก ๋ณด์ •"
1125
- )
1126
-
1127
- detect_btn = gr.Button(
1128
- "๐Ÿš€ ๊ฒ€์ถœ ์‹คํ–‰",
1129
- variant="primary",
1130
- size="lg"
1131
- )
1132
-
1133
- with gr.Column():
1134
- output_img = gr.Image(
1135
- label="๊ฒ€์ถœ ๊ฒฐ๊ณผ"
1136
- )
1137
- depth_img = gr.Image(
1138
- label="๊นŠ์ด ๋งต (ํŒŒ๋ž€์ƒ‰=๊ฐ€๊นŒ์›€, ๋…ธ๋ž€์ƒ‰=๋ฉ€์Œ)"
1139
- )
1140
- stats = gr.Markdown()
1141
-
1142
- results_df = gr.Dataframe(
1143
- label="๊ฒ€์ถœ ์ƒ์„ธ ์ •๋ณด",
1144
- wrap=True
1145
- )
1146
-
1147
- # ํ‰๊ฐ€ ํƒญ
1148
- with gr.TabItem("๐Ÿ“Š ์„ฑ๋Šฅ ํ‰๊ฐ€"):
1149
- gr.Markdown("""
1150
- ### ํšŒ๊ท€ ๋ชจ๋ธ ์„ฑ๋Šฅ ํ‰๊ฐ€
1151
-
1152
- ์‹ค์ธก ๋ฐ์ดํ„ฐ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ์ฒด์žฅ-์ฒด์ค‘ ํšŒ๊ท€ ๋ชจ๋ธ์˜ ์ •ํ™•๋„๋ฅผ ํ‰๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.
1153
- """)
1154
-
1155
- eval_btn = gr.Button(
1156
- "๐Ÿ“ˆ ํ‰๊ฐ€ ์‹คํ–‰",
1157
- variant="primary"
1158
- )
1159
- eval_text = gr.Markdown()
1160
- eval_plot = gr.Plot()
1161
-
1162
- # ๋ฐ์ดํ„ฐ ํƒญ
1163
- with gr.TabItem("๐Ÿ“‹ ์‹ค์ธก ๋ฐ์ดํ„ฐ"):
1164
- gr.Markdown(f"""
1165
- ### ๋ฐ์ดํ„ฐ ์š”์•ฝ
1166
-
1167
- - **์ƒ˜ํ”Œ ์ˆ˜**: {len(REAL_DATA)}๊ฐœ
1168
- - **์ฒด์žฅ ๋ฒ”์œ„**: 7.5 - 13.1 cm
1169
- - **์ฒด์ค‘ ๋ฒ”์œ„**: 2.0 - 11.3 g
1170
- - **๋ฐ์ดํ„ฐ ์ถœ์ฒ˜**: ์‹ค์ธก ๋ฐ์ดํ„ฐ
1171
- """)
1172
-
1173
- data_df = gr.Dataframe(
1174
- value=pd.DataFrame(REAL_DATA),
1175
- label="์‹ค์ธก ๋ฐ์ดํ„ฐ",
1176
- wrap=True
1177
- )
1178
-
1179
- export_btn = gr.Button("๐Ÿ’พ CSV ๋‹ค์šด๋กœ๋“œ")
1180
- file_output = gr.File(label="๋‹ค์šด๋กœ๋“œ")
1181
-
1182
- # ์ •ํ™•๋„ ๊ฒ€์ฆ ํƒญ
1183
- with gr.TabItem("๐ŸŽฏ ์ •ํ™•๋„ ๊ฒ€์ฆ"):
1184
- gr.Markdown("""
1185
- ### ์‹ค์ œ ์ด๋ฏธ์ง€ ๋ฐ์ดํ„ฐ๋กœ ์ •ํ™•๋„ ๊ฒ€์ฆ
1186
-
1187
- ํ…Œ์ŠคํŠธ ๋ฐ์ดํ„ฐ์…‹์„ ๋ฐฐ์น˜ ์ฒ˜๋ฆฌํ•˜์—ฌ ์˜ˆ์ธก ์ •ํ™•๋„๋ฅผ ์ธก์ •ํ•ฉ๋‹ˆ๋‹ค.
1188
- """)
1189
-
1190
- with gr.Row():
1191
- with gr.Column():
1192
- test_folder = gr.Textbox(
1193
- value="d:/Project/VIDraft/Shrimp/data/251015",
1194
- label="ํ…Œ์ŠคํŠธ ๋ฐ์ดํ„ฐ ํด๋”",
1195
- info="251015_XX.jpg ํ˜•์‹์˜ ์ด๋ฏธ์ง€๊ฐ€ ์žˆ๋Š” ํด๋”"
1196
- )
1197
-
1198
- with gr.Row():
1199
- test_pixel_scale = gr.Number(
1200
- value=92,
1201
- label="ํ”ฝ์…€ ํฌ๊ธฐ (px)",
1202
- info="์ฐธ์กฐ ์ž์˜ ํ”ฝ์…€ ํฌ๊ธฐ"
1203
- )
1204
- test_cm_scale = gr.Number(
1205
- value=1,
1206
- label="์‹ค์ œ ํฌ๊ธฐ (cm)",
1207
- info="์ฐธ์กฐ ์ž์˜ ์‹ค์ œ ํฌ๊ธฐ"
1208
- )
1209
-
1210
- test_depth_checkbox = gr.Checkbox(
1211
- value=False,
1212
- label="๐Ÿ” ๊นŠ์ด ๊ธฐ๋ฐ˜ ์›๊ทผ ๋ณด์ • ํ™œ์„ฑํ™”",
1213
- info="Depth-Anything V2๋กœ ์ž๋™ ์›๊ทผ ์™œ๊ณก ๋ณด์ •"
1214
- )
1215
-
1216
- test_btn = gr.Button(
1217
- "๐Ÿš€ ๋ฐฐ์น˜ ํ…Œ์ŠคํŠธ ์‹คํ–‰",
1218
- variant="primary",
1219
- size="lg"
1220
- )
1221
-
1222
- with gr.Column():
1223
- test_stats = gr.Markdown()
1224
-
1225
- test_plot = gr.Plot(label="์˜ˆ์ธก vs ์‹ค์ œ ๋น„๊ต")
1226
- test_results_df = gr.Dataframe(
1227
- label="์ƒ์„ธ ๊ฒฐ๊ณผ",
1228
- wrap=True
1229
- )
1230
-
1231
- gr.Markdown("### ๐Ÿ“ธ ์‹œ๊ฐํ™” ๊ฒฐ๊ณผ (์˜ˆ์ธก vs ์‹ค์ œ)")
1232
- test_gallery = gr.Gallery(
1233
- label="๊ฒ€์ถœ ๊ฒฐ๊ณผ ์ด๋ฏธ์ง€",
1234
- show_label=True,
1235
- columns=3,
1236
- rows=2,
1237
- height="auto",
1238
- object_fit="contain"
1239
- )
1240
-
1241
- # ์ •๋ณด ํƒญ
1242
- with gr.TabItem("โ„น๏ธ ์‚ฌ์šฉ ๋ฐฉ๋ฒ•"):
1243
- gr.Markdown("""
1244
- ## ๐Ÿ“– ์‚ฌ์šฉ ๊ฐ€์ด๋“œ
1245
-
1246
- ### 1๏ธโƒฃ ๊ฐ์ฒด ๊ฒ€์ถœ
1247
- 1. ์ƒˆ์šฐ ์ด๋ฏธ์ง€๋ฅผ ์—…๋กœ๋“œํ•˜์„ธ์š”
1248
- 2. ์‹ ๋ขฐ๋„ ์ž„๏ฟฝ๏ฟฝ๏ฟฝ๊ฐ’์„ ์กฐ์ •ํ•˜์„ธ์š” (๊ธฐ๋ณธ๊ฐ’: 0.5)
1249
- 3. **๊นŠ์ด ๋ณด์ • ํ™œ์„ฑํ™”** (๊ถŒ์žฅ): ์›๊ทผ ์™œ๊ณก ์ž๋™ ๋ณด์ •
1250
- 4. ์Šค์ผ€์ผ ๋ณด์ •: ์‹ค์ œ ํฌ๊ธฐ๋ฅผ ์•Œ๊ณ  ์žˆ๋‹ค๋ฉด ํ”ฝ์…€-cm ๋น„์œจ์„ ์„ค์ •ํ•˜์„ธ์š”
1251
- 5. "๊ฒ€์ถœ ์‹คํ–‰" ๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜์„ธ์š”
1252
-
1253
- ### 2๏ธโƒฃ ๊นŠ์ด ๊ธฐ๋ฐ˜ ์›๊ทผ ๋ณด์ • (NEW! ๐Ÿ”ฅ)
1254
- - **Depth-Anything V2** ๋ชจ๋ธ๋กœ ์ด๋ฏธ์ง€์˜ ๊นŠ์ด ๋งต ์ž๋™ ์ƒ์„ฑ
1255
- - ๊ฐ ์ƒˆ์šฐ์˜ ์ƒ๋Œ€์  ๊ฑฐ๋ฆฌ๋ฅผ ๊ณ„์‚ฐํ•˜์—ฌ ์Šค์ผ€์ผ ์ž๋™ ๋ณด์ •
1256
- - **์›๊ทผ ์™œ๊ณก ํšจ๊ณผ๋ฅผ ์ž๋™์œผ๋กœ ์ œ๊ฑฐ**ํ•˜์—ฌ ์ •ํ™•๋„ ํ–ฅ์ƒ
1257
- - ๊นŠ์ด ๋งต ์‹œ๊ฐํ™”: ํŒŒ๋ž€์ƒ‰=๊ฐ€๊นŒ์›€, ๋…ธ๋ž€์ƒ‰=๋ฉ€์Œ
1258
- - ์ถ”๊ฐ€ ์žฅ๋น„๋‚˜ ๋งˆ์ปค ์—†์ด ๋‹จ์ผ ์ด๋ฏธ์ง€๋งŒ์œผ๋กœ ์ž‘๋™
1259
-
1260
- ### 3๏ธโƒฃ ์Šค์ผ€์ผ ๋ณด์ •
1261
- - ์ด๋ฏธ์ง€์—์„œ ์•Œ๊ณ  ์žˆ๋Š” ๊ฐ์ฒด์˜ ํ”ฝ์…€ ํฌ๊ธฐ์™€ ์‹ค์ œ ํฌ๊ธฐ๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”
1262
- - ์˜ˆ: ์ž๊ฐ€ ๋ณด์ธ๋‹ค๋ฉด, ์ž์˜ ํ”ฝ์…€ ๊ธธ์ด์™€ ์‹ค์ œ ๊ธธ์ด(cm)๋ฅผ ์ž…๋ ฅ
1263
- - ๊นŠ์ด ๋ณด์ •๊ณผ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๋ฉด ๋”์šฑ ์ •ํ™•ํ•œ ์ธก์ • ๊ฐ€๋Šฅ
1264
-
1265
- ### 4๏ธโƒฃ ๊ฒฐ๊ณผ ํ•ด์„
1266
- - **์ดˆ๋ก์ƒ‰ ๋ฐ•์Šค**: ์˜ค์ฐจ < 10%
1267
- - **์ฃผํ™ฉ์ƒ‰ ๋ฐ•์Šค**: ์˜ค์ฐจ 10-20%
1268
- - **๋นจ๊ฐ„์ƒ‰ ๋ฐ•์Šค**: ์˜ค์ฐจ > 20%
1269
-
1270
- ### 5๏ธโƒฃ ์„ฑ๋Šฅ ํ‰๊ฐ€
1271
- - "์„ฑ๋Šฅ ํ‰๊ฐ€" ํƒญ์—์„œ ํšŒ๊ท€ ๋ชจ๋ธ์˜ ์ •ํ™•๋„๋ฅผ ํ™•์ธํ•˜์„ธ์š”
1272
- - Rยฒ, MAPE, MAE, RMSE ์ง€ํ‘œ ์ œ๊ณต
1273
-
1274
- ### 6๏ธโƒฃ ์ •ํ™•๋„ ๊ฒ€์ฆ (NEW! ๐Ÿ”ฅ)
1275
- - "์ •ํ™•๋„ ๊ฒ€์ฆ" ํƒญ์—์„œ ์‹ค์ œ ์ด๋ฏธ์ง€ ๋ฐ์ดํ„ฐ๋กœ ์ •ํ™•๋„ ์ธก์ •
1276
- - ํ…Œ์ŠคํŠธ ๋ฐ์ดํ„ฐ ํด๋”๋ฅผ ์ง€์ •ํ•˜๊ณ  ๋ฐฐ์น˜ ์ฒ˜๋ฆฌ
1277
- - OCR๋กœ ์ „์ž์ €์šธ LCD์—์„œ ์‹ค์ œ ๋ฌด๊ฒŒ ์ž๋™ ์ฝ๊ธฐ
1278
- - ์˜ˆ์ธก vs ์‹ค์ œ ๋น„๊ต ์ฐจํŠธ ๋ฐ ํ†ต๊ณ„ ์ œ๊ณต
1279
-
1280
- ### 7๏ธโƒฃ ๋ฐ์ดํ„ฐ ๋‚ด๋ณด๋‚ด๊ธฐ
1281
- - "์‹ค์ธก ๋ฐ์ดํ„ฐ" ํƒญ์—์„œ CSV ํŒŒ์ผ๋กœ ๋‹ค์šด๋กœ๋“œ ๊ฐ€๋Šฅ
1282
-
1283
- ---
1284
-
1285
- ## โš™๏ธ ์‹œ์Šคํ…œ ์ •๋ณด
1286
-
1287
- - **๊ฒ€์ถœ ๋ชจ๋ธ**: RT-DETR (Real-Time DEtection TRansformer)
1288
- - **๊นŠ์ด ์ถ”์ • ๋ชจ๋ธ**: Depth-Anything V2 Small (Monocular Depth Estimation)
1289
- - **ํšŒ๊ท€ ๋ชจ๋ธ**: Power Law (W = a ร— L^b)
1290
- - **๋””๋ฐ”์ด์Šค**: """ + ("GPU (CUDA)" if torch.cuda.is_available() else "CPU") + """
1291
- - **์ตœ์ ํ™”**: CPU ๋ชจ๋“œ, torch.no_grad(), FP32
1292
- - **์›๊ทผ ๋ณด์ •**: ๊นŠ์ด ๋งต ๊ธฐ๋ฐ˜ ์ž๋™ ์Šค์ผ€์ผ ์กฐ์ •
1293
-
1294
- ## ๐Ÿ”ง ๋ฌธ์ œ ํ•ด๊ฒฐ
1295
-
1296
- **๊ฒ€์ถœ์ด ์•ˆ ๋  ๋•Œ**:
1297
- - ์‹ ๋ขฐ๋„ ์ž„๊ณ„๊ฐ’์„ ๋‚ฎ์ถฐ๋ณด์„ธ์š” (0.3 ์ดํ•˜)
1298
- - ์ด๋ฏธ์ง€ ํ’ˆ์งˆ์„ ํ™•์ธํ•˜์„ธ์š” (ํ•ด์ƒ๋„, ๋ฐ๊ธฐ)
1299
-
1300
- **์ •ํ™•๋„๊ฐ€ ๋‚ฎ์„ ๋•Œ**:
1301
- - ์Šค์ผ€์ผ ๋ณด์ •์„ ์ •ํ™•ํžˆ ์ž…๋ ฅํ•˜์„ธ์š”
1302
- - ์ƒˆ์šฐ ์ „์šฉ fine-tuning ๋ชจ๋ธ์ด ํ•„์š”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค
1303
-
1304
- **์†๋„๊ฐ€ ๋А๋ฆด ๋•Œ**:
1305
- - GPU ๊ฐ€์†์„ ์‚ฌ์šฉํ•˜์„ธ์š” (HF Space: GPU T4)
1306
- - ์ด๋ฏธ์ง€ ํฌ๊ธฐ๋ฅผ ์ค„์ด์„ธ์š” (800x600 ๊ถŒ์žฅ)
1307
- """)
1308
-
1309
- # ์ด๋ฒคํŠธ ์—ฐ๊ฒฐ
1310
- detect_btn.click(
1311
- process_image,
1312
- [input_img, model_selector, conf_slider, iou_slider, pixel_scale, cm_scale, depth_checkbox],
1313
- [output_img, depth_img, stats, results_df]
1314
- )
1315
-
1316
- eval_btn.click(
1317
- evaluate_model,
1318
- [],
1319
- [eval_text, eval_plot]
1320
- )
1321
-
1322
- export_btn.click(
1323
- export_data,
1324
- [],
1325
- file_output
1326
- )
1327
-
1328
- test_btn.click(
1329
- process_test_dataset,
1330
- [test_folder, test_pixel_scale, test_cm_scale, test_depth_checkbox],
1331
- [test_stats, test_results_df, test_plot, test_gallery]
1332
- )
1333
-
1334
- # ์‹คํ–‰
1335
- if __name__ == "__main__":
1336
- demo.queue(max_size=10) # CPU ์ตœ์ ํ™”: ํ ํฌ๊ธฐ ์ œํ•œ
1337
- demo.launch(
1338
- share=False,
1339
- server_name="0.0.0.0",
1340
- server_port=7860,
1341
- show_error=True
1342
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
check_250818_labeling.py DELETED
@@ -1,83 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- """250818 ํด๋” ๋ผ๋ฒจ๋ง ๊ฒ€์ˆ˜"""
3
- import sys
4
- sys.stdout.reconfigure(encoding='utf-8')
5
- import json
6
-
7
- with open('ground_truth.json', 'r', encoding='utf-8') as f:
8
- data = json.load(f)
9
-
10
- print("=" * 70)
11
- print("๐Ÿ“‹ 250818 ํด๋” ๋ผ๋ฒจ๋ง ๊ฒ€์ˆ˜")
12
- print("=" * 70)
13
-
14
- folder_data = {k: v for k, v in data.items() if k.startswith('250818_') and not '-' in k}
15
-
16
- print(f"\nโœ… ๋ผ๋ฒจ๋ง๋œ ์ด๋ฏธ์ง€: {len([v for v in folder_data.values() if v])}๊ฐœ")
17
- print(f"โŒ ๊ฑด๋„ˆ๋›ด ์ด๋ฏธ์ง€: {len([v for v in folder_data.values() if not v])}๊ฐœ")
18
-
19
- print(f"\n{'ํŒŒ์ผ๋ช…':<20} {'๋ฐ•์Šค์ˆ˜':>6} {'์ข…ํšก๋น„':>8} {'์‹ ๋ขฐ๋„':>8} {'์ƒํƒœ':>10}")
20
- print("-" * 70)
21
-
22
- issues = []
23
-
24
- for filename in sorted(folder_data.keys()):
25
- boxes = folder_data[filename]
26
-
27
- if not boxes:
28
- print(f"{filename:<20} {'0':>6} {'-':>8} {'-':>8} {'โš ๏ธ ๊ฑด๋„ˆ๋œ€':>10}")
29
- issues.append(f"{filename}: ๊ฑด๋„ˆ๋›ด ์ด๋ฏธ์ง€ (์ƒˆ์šฐ ์—†์Œ?)")
30
- else:
31
- for box in boxes:
32
- bbox = box['bbox']
33
- x1, y1, x2, y2 = bbox
34
- width = x2 - x1
35
- height = y2 - y1
36
- aspect = width / height if height > 0 else 0
37
- conf = box['confidence']
38
-
39
- # ์ด์ƒ์น˜ ํŒ๋‹จ
40
- status = "โœ… ์ •์ƒ"
41
- if aspect < 0.5: # ๋„ˆ๋ฌด ์„ธ๋กœ๋กœ ๊ธด ๊ฒฝ์šฐ
42
- status = "โš ๏ธ ์„ธ๋กœ"
43
- issues.append(f"{filename}: ์ข…ํšก๋น„ {aspect:.2f} (๋„ˆ๋ฌด ์„ธ๋กœ๋กœ ๊น€)")
44
- elif aspect > 15: # ๋„ˆ๋ฌด ๊ฐ€๋กœ๋กœ ๊ธด ๊ฒฝ์šฐ
45
- status = "โš ๏ธ ๊ฐ€๋กœ"
46
- issues.append(f"{filename}: ์ข…ํšก๋น„ {aspect:.2f} (๋„ˆ๋ฌด ๊ฐ€๋กœ๋กœ ๊น€)")
47
- elif conf < 0.1:
48
- status = "โš ๏ธ ๋‚ฎ์Œ"
49
- issues.append(f"{filename}: ์‹ ๋ขฐ๋„ {conf:.3f} (๋งค์šฐ ๋‚ฎ์Œ)")
50
-
51
- print(f"{filename:<20} {len(boxes):>6} {aspect:>8.2f} {conf:>8.3f} {status:>10}")
52
-
53
- print("\n" + "=" * 70)
54
- if issues:
55
- print("โš ๏ธ ํ™•์ธ ํ•„์š”ํ•œ ํ•ญ๋ชฉ:")
56
- print("-" * 70)
57
- for issue in issues:
58
- print(f" โ€ข {issue}")
59
- else:
60
- print("โœ… ๋ชจ๋“  ๋ผ๋ฒจ๋ง ์ •์ƒ!")
61
-
62
- print("\n" + "=" * 70)
63
- print("๐Ÿ“Š ํ†ต๊ณ„")
64
- print("-" * 70)
65
-
66
- all_boxes = [box for boxes in folder_data.values() if boxes for box in boxes]
67
- if all_boxes:
68
- aspects = [((box['bbox'][2]-box['bbox'][0])/(box['bbox'][3]-box['bbox'][1]))
69
- for box in all_boxes if (box['bbox'][3]-box['bbox'][1]) > 0]
70
- confs = [box['confidence'] for box in all_boxes]
71
-
72
- print(f"ํ‰๊ท  ์ข…ํšก๋น„: {sum(aspects)/len(aspects):.2f}")
73
- print(f"ํ‰๊ท  ์‹ ๋ขฐ๋„: {sum(confs)/len(confs):.3f}")
74
- print(f"์ตœ์†Œ ์‹ ๋ขฐ๋„: {min(confs):.3f}")
75
- print(f"์ตœ๋Œ€ ์‹ ๋ขฐ๋„: {max(confs):.3f}")
76
-
77
- print("\n" + "=" * 70)
78
- print("๐Ÿ’ก ๊ถŒ์žฅ์‚ฌํ•ญ")
79
- print("-" * 70)
80
- print("โ€ข ์ข…ํšก๋น„ 3:1 ~ 10:1 ๋ฒ”์œ„๊ฐ€ ์ƒˆ์šฐ์˜ ์ผ๋ฐ˜์ ์ธ ๋น„์œจ")
81
- print("โ€ข ์‹ ๋ขฐ๋„ 0.1 ์ดํ•˜๋Š” ์˜ค๊ฒ€์ถœ ๊ฐ€๋Šฅ์„ฑ ๋†’์Œ")
82
- print("โ€ข ๊ฑด๋„ˆ๋›ด ์ด๋ฏธ์ง€๋Š” ์‹ค์ œ๋กœ ์ƒˆ์šฐ๊ฐ€ ์—†๋Š”์ง€ ์žฌํ™•์ธ")
83
- print("=" * 70)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
check_gt_split.py DELETED
@@ -1,38 +0,0 @@
1
- import json
2
- import os
3
-
4
- # Ground Truth ํŒŒ์ผ ๋กœ๋“œ
5
- with open('ground_truth.json', 'r', encoding='utf-8') as f:
6
- gt = json.load(f)
7
-
8
- # GT๊ฐ€ ์žˆ๋Š” ์ด๋ฏธ์ง€ ๋ชฉ๋ก
9
- gt_images = [k for k, v in gt.items() if v]
10
- print(f'GT ์ด๋ฏธ์ง€ ์ด {len(gt_images)}์žฅ')
11
-
12
- # Train/Val split ํ™•์ธ
13
- train_images = set(os.listdir('data/yolo_dataset/images/train'))
14
- val_images = set(os.listdir('data/yolo_dataset/images/val'))
15
-
16
- gt_in_train = []
17
- gt_in_val = []
18
-
19
- for img in gt_images:
20
- base_name = img.replace('-1.jpg', '.jpg')
21
- if img in train_images or base_name in train_images:
22
- gt_in_train.append(img)
23
- elif img in val_images or base_name in val_images:
24
- gt_in_val.append(img)
25
-
26
- print(f'\nGT ๋ถ„ํฌ:')
27
- print(f' - Train set: {len(gt_in_train)}์žฅ')
28
- print(f' - Val set: {len(gt_in_val)}์žฅ')
29
-
30
- if len(gt_in_train) > 0:
31
- print(f'\n๋ฌธ์ œ: GT {len(gt_in_train)}์žฅ์ด ํ•™์Šต ๋ฐ์ดํ„ฐ์— ํฌํ•จ๋จ!')
32
- print(f'ํ•ด๊ฒฐ: Val set {len(gt_in_val)}์žฅ๋งŒ์œผ๋กœ ํ‰๊ฐ€ํ•ด์•ผ ํ•จ')
33
-
34
- print(f'\nVal set GT ์ด๋ฏธ์ง€:')
35
- for img in gt_in_val[:10]:
36
- print(f' - {img}')
37
- if len(gt_in_val) > 10:
38
- print(f' ... and {len(gt_in_val)-10} more')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
check_labeling_quality.py DELETED
@@ -1,181 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- """์˜ฌ๋ฐ”๋ฅธ ๋ผ๋ฒจ๋ง ํ’ˆ์งˆ ๊ฒ€์ˆ˜"""
3
- import sys
4
- sys.stdout.reconfigure(encoding='utf-8')
5
- import json
6
- import math
7
-
8
- def calculate_iou(box1, box2):
9
- """IoU ๊ณ„์‚ฐ"""
10
- x1_min, y1_min, x1_max, y1_max = box1
11
- x2_min, y2_min, x2_max, y2_max = box2
12
-
13
- inter_x_min = max(x1_min, x2_min)
14
- inter_y_min = max(y1_min, y2_min)
15
- inter_x_max = min(x1_max, x2_max)
16
- inter_y_max = min(y1_max, y2_max)
17
-
18
- if inter_x_max < inter_x_min or inter_y_max < inter_y_min:
19
- return 0.0
20
-
21
- inter_area = (inter_x_max - inter_x_min) * (inter_y_max - inter_y_min)
22
- box1_area = (x1_max - x1_min) * (y1_max - y1_min)
23
- box2_area = (x2_max - x2_min) * (y2_max - y2_min)
24
- union_area = box1_area + box2_area - inter_area
25
-
26
- return inter_area / union_area if union_area > 0 else 0.0
27
-
28
- with open('ground_truth.json', 'r', encoding='utf-8') as f:
29
- data = json.load(f)
30
-
31
- print("=" * 80)
32
- print("๐Ÿ“‹ ๋ผ๋ฒจ๋ง ํ’ˆ์งˆ ๊ฒ€์ˆ˜ (์˜ฌ๋ฐ”๋ฅธ ๊ธฐ์ค€)")
33
- print("=" * 80)
34
-
35
- folder_data = {k: v for k, v in data.items() if k.startswith('250818_') and not '-' in k}
36
-
37
- print(f"\nโœ… ๋ผ๋ฒจ๋ง๋œ ์ด๋ฏธ์ง€: {len([v for v in folder_data.values() if v])}๊ฐœ")
38
- print(f"โš ๏ธ ๊ฑด๋„ˆ๋›ด ์ด๋ฏธ์ง€: {len([v for v in folder_data.values() if not v])}๊ฐœ")
39
-
40
- print(f"\n{'ํŒŒ์ผ๋ช…':<20} {'๋ฐ•์Šค':>4} {'์žฅ๋‹จ์ถ•๋น„':>8} {'๋ฉด์ ':>10} {'์‹ ๋ขฐ๋„':>8} {'์ƒํƒœ':>12}")
41
- print("-" * 80)
42
-
43
- issues = []
44
- warnings = []
45
-
46
- for filename in sorted(folder_data.keys()):
47
- boxes = folder_data[filename]
48
-
49
- if not boxes:
50
- print(f"{filename:<20} {'0':>4} {'-':>8} {'-':>10} {'-':>8} {'โš ๏ธ ๊ฑด๋„ˆ๋œ€':>12}")
51
- warnings.append(f"{filename}: ๊ฑด๋„ˆ๋›ด ์ด๋ฏธ์ง€")
52
- continue
53
-
54
- # ๋ฐ•์Šค ์ˆ˜ ํ™•์ธ
55
- if len(boxes) > 10:
56
- issues.append(f"{filename}: ๋ฐ•์Šค {len(boxes)}๊ฐœ (๋„ˆ๋ฌด ๋งŽ์Œ, ์˜ค๊ฒ€์ถœ ์˜์‹ฌ)")
57
-
58
- # ์ค‘๋ณต ๋ฐ•์Šค ํ™•์ธ
59
- if len(boxes) > 1:
60
- for i in range(len(boxes)):
61
- for j in range(i+1, len(boxes)):
62
- iou = calculate_iou(boxes[i]['bbox'], boxes[j]['bbox'])
63
- if iou > 0.5:
64
- issues.append(f"{filename}: ๋ฐ•์Šค #{i+1}๊ณผ #{j+1} ์ค‘์ฒฉ (IoU={iou:.2f}, ์ค‘๋ณต ์„ ํƒ?)")
65
-
66
- for idx, box in enumerate(boxes):
67
- bbox = box['bbox']
68
- x1, y1, x2, y2 = bbox
69
- width = x2 - x1
70
- height = y2 - y1
71
- area = width * height
72
- conf = box['confidence']
73
-
74
- # ์žฅ์ถ•/๋‹จ์ถ• ๋น„์œจ (๋ฐฉํ–ฅ ๋ฌด๊ด€)
75
- long_axis = max(width, height)
76
- short_axis = min(width, height)
77
- axis_ratio = long_axis / short_axis if short_axis > 0 else 0
78
-
79
- # ํ’ˆ์งˆ ํŒ๋‹จ
80
- status = "โœ… ์ •์ƒ"
81
- issue_desc = []
82
-
83
- # 1. ๋ฉด์  ์ฒดํฌ
84
- if area < 1000:
85
- status = "โŒ ๋„ˆ๋ฌด์ž‘์Œ"
86
- issues.append(f"{filename} ๋ฐ•์Šค#{idx+1}: ๋ฉด์  {area:.0f}pxยฒ (๋„ˆ๋ฌด ์ž‘์Œ, ์˜ค๊ฒ€์ถœ?)")
87
- issue_desc.append("๋ฉด์ โ†“")
88
- elif area > 1000000:
89
- status = "โŒ ๋„ˆ๋ฌดํผ"
90
- issues.append(f"{filename} ๋ฐ•์Šค#{idx+1}: ๋ฉด์  {area:.0f}pxยฒ (๋„ˆ๋ฌด ํผ, ๋ฐฐ๊ฒฝ ํฌํ•จ?)")
91
- issue_desc.append("๋ฉด์ โ†‘")
92
-
93
- # 2. ์žฅ๋‹จ์ถ• ๋น„์œจ (์ƒˆ์šฐ๋Š” ๊ธธ์ญ‰ํ•ด์•ผ ํ•จ)
94
- if axis_ratio < 2.5:
95
- status = "โš ๏ธ ๋‘ฅ๊ธ€์Œ"
96
- warnings.append(f"{filename} ๋ฐ•์Šค#{idx+1}: ์žฅ๋‹จ์ถ•๋น„ {axis_ratio:.2f} (๋„ˆ๋ฌด ๋‘ฅ๊ธ€์Œ, ์ƒˆ์šฐ ๋งž๋‚˜?)")
97
- issue_desc.append("๋‘ฅ๊ธ€์Œ")
98
- elif axis_ratio > 20:
99
- status = "โš ๏ธ ๊ฐ€๋Š˜์Œ"
100
- warnings.append(f"{filename} ๋ฐ•์Šค#{idx+1}: ์žฅ๋‹จ์ถ•๋น„ {axis_ratio:.2f} (๋„ˆ๋ฌด ๊ฐ€๋Š˜์Œ)")
101
- issue_desc.append("๊ฐ€๋Š˜์Œ")
102
-
103
- # 3. ์‹ ๋ขฐ๋„ ์ฒดํฌ
104
- if conf < 0.05:
105
- status = "โŒ ์‹ ๋ขฐ๋„โ†“"
106
- issues.append(f"{filename} ๋ฐ•์Šค#{idx+1}: ์‹ ๋ขฐ๋„ {conf:.3f} (๋งค์šฐ ๋‚ฎ์Œ, ์˜ค๊ฒ€์ถœ ์˜์‹ฌ)")
107
- issue_desc.append("์‹ ๋ขฐโ†“")
108
- elif conf < 0.15:
109
- if status == "โœ… ์ •์ƒ":
110
- status = "โš ๏ธ ์‹ ๋ขฐ๋„โ†“"
111
- warnings.append(f"{filename} ๋ฐ•์Šค#{idx+1}: ์‹ ๋ขฐ๋„ {conf:.3f} (๋‚ฎ์Œ, ์žฌํ™•์ธ ๊ถŒ์žฅ)")
112
- issue_desc.append("์‹ ๋ขฐ๋‚ฎ์Œ")
113
-
114
- issue_str = ",".join(issue_desc) if issue_desc else ""
115
- if issue_str:
116
- status = f"โš ๏ธ {issue_str}"
117
-
118
- print(f"{filename:<20} {len(boxes):>4} {axis_ratio:>8.2f} {area:>10.0f} {conf:>8.3f} {status:>12}")
119
-
120
- print("\n" + "=" * 80)
121
- if issues:
122
- print("โŒ ์‹ฌ๊ฐํ•œ ๋ฌธ์ œ (์žฌํ™•์ธ ํ•„์ˆ˜):")
123
- print("-" * 80)
124
- for issue in issues[:10]: # ์ตœ๋Œ€ 10๊ฐœ๋งŒ
125
- print(f" โ€ข {issue}")
126
- if len(issues) > 10:
127
- print(f" ... ์™ธ {len(issues)-10}๊ฐœ")
128
- else:
129
- print("โœ… ์‹ฌ๊ฐํ•œ ๋ฌธ์ œ ์—†์Œ")
130
-
131
- if warnings:
132
- print(f"\nโš ๏ธ ๊ฒฝ๊ณ  ({len(warnings)}๊ฐœ, ์žฌํ™•์ธ ๊ถŒ์žฅ):")
133
- print("-" * 80)
134
- for warning in warnings[:10]: # ์ตœ๋Œ€ 10๊ฐœ๋งŒ
135
- print(f" โ€ข {warning}")
136
- if len(warnings) > 10:
137
- print(f" ... ์™ธ {len(warnings)-10}๊ฐœ")
138
-
139
- print("\n" + "=" * 80)
140
- print("๐Ÿ“Š ํ†ต๊ณ„")
141
- print("-" * 80)
142
-
143
- all_boxes = [box for boxes in folder_data.values() if boxes for box in boxes]
144
- if all_boxes:
145
- areas = [(box['bbox'][2]-box['bbox'][0])*(box['bbox'][3]-box['bbox'][1]) for box in all_boxes]
146
- axis_ratios = []
147
- for box in all_boxes:
148
- w = box['bbox'][2] - box['bbox'][0]
149
- h = box['bbox'][3] - box['bbox'][1]
150
- axis_ratios.append(max(w,h)/min(w,h) if min(w,h) > 0 else 0)
151
- confs = [box['confidence'] for box in all_boxes]
152
-
153
- print(f"์ด ๋ฐ•์Šค: {len(all_boxes)}๊ฐœ")
154
- print(f"ํ‰๊ท  ๋ฉด์ : {sum(areas)/len(areas):,.0f} pxยฒ")
155
- print(f"ํ‰๊ท  ์žฅ๋‹จ์ถ•๋น„: {sum(axis_ratios)/len(axis_ratios):.2f} (์ •์ƒ ๋ฒ”์œ„: 3~15)")
156
- print(f"ํ‰๊ท  ์‹ ๋ขฐ๋„: {sum(confs)/len(confs):.3f}")
157
- print(f"์‹ ๋ขฐ๋„ ๋ฒ”์œ„: {min(confs):.3f} ~ {max(confs):.3f}")
158
-
159
- print("\n" + "=" * 80)
160
- print("๐Ÿ’ก ํ’ˆ์งˆ ๊ธฐ์ค€")
161
- print("-" * 80)
162
- print("โœ… ์žฅ๋‹จ์ถ•๋น„: 3:1 ~ 15:1 (์ƒˆ์šฐ๋Š” ๊ธธ์ญ‰ํ•จ, ๋ฐฉํ–ฅ ๋ฌด๊ด€)")
163
- print("โœ… ๋ฉด์ : 1,000 ~ 1,000,000 pxยฒ")
164
- print("โœ… ์‹ ๋ขฐ๋„: > 0.15")
165
- print("โœ… ๋ฐ•์Šค ์ˆ˜: 1~5๊ฐœ/์ด๋ฏธ์ง€")
166
- print("โœ… ์ค‘์ฒฉ: IoU < 0.5 (์ค‘๋ณต ์„ ํƒ ๋ฐฉ์ง€)")
167
- print("=" * 80)
168
-
169
- # ์ตœ์ข… ํ‰๊ฐ€
170
- print("\n" + "=" * 80)
171
- print("๐Ÿ“‹ ์ตœ์ข… ํ‰๊ฐ€")
172
- print("-" * 80)
173
- if not issues and len(warnings) <= 3:
174
- print("๐ŸŽ‰ ์šฐ์ˆ˜: ๋ผ๋ฒจ๋ง ํ’ˆ์งˆ์ด ๋งค์šฐ ์ข‹์Šต๋‹ˆ๋‹ค!")
175
- elif not issues:
176
- print("โœ… ์–‘ํ˜ธ: ๋ช‡ ๊ฐ€์ง€ ์žฌํ™•์ธ ๊ถŒ์žฅ")
177
- elif len(issues) <= 3:
178
- print("โš ๏ธ ๋ณดํ†ต: ์ผ๋ถ€ ๋ฐ•์Šค ์žฌํ™•์ธ ํ•„์š”")
179
- else:
180
- print("โŒ ๋ถˆ๋Ÿ‰: ๋งŽ์€ ๋ฐ•์Šค ์žฌ๋ผ๋ฒจ๋ง ํ•„์š”")
181
- print("=" * 80)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
convert_gt_to_yolo.py DELETED
@@ -1,185 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- """
3
- Ground Truth๋ฅผ YOLO format์œผ๋กœ ๋ณ€ํ™˜
4
- Train/Val split ํฌํ•จ
5
- """
6
- import sys
7
- sys.stdout.reconfigure(encoding='utf-8')
8
-
9
- import json
10
- import os
11
- import shutil
12
- from pathlib import Path
13
- import random
14
- from PIL import Image
15
-
16
- def convert_bbox_to_yolo(bbox, img_width, img_height):
17
- """
18
- [x1, y1, x2, y2] โ†’ [x_center, y_center, width, height] (normalized)
19
- """
20
- x1, y1, x2, y2 = bbox
21
-
22
- # Center coordinates
23
- x_center = (x1 + x2) / 2.0
24
- y_center = (y1 + y2) / 2.0
25
-
26
- # Width and height
27
- width = x2 - x1
28
- height = y2 - y1
29
-
30
- # Normalize to [0, 1]
31
- x_center_norm = x_center / img_width
32
- y_center_norm = y_center / img_height
33
- width_norm = width / img_width
34
- height_norm = height / img_height
35
-
36
- return x_center_norm, y_center_norm, width_norm, height_norm
37
-
38
- def main():
39
- print("=" * 60)
40
- print("Ground Truth โ†’ YOLO Format ๋ณ€ํ™˜ ์‹œ์ž‘")
41
- print("=" * 60)
42
-
43
- # ๊ฒฝ๋กœ ์„ค์ •
44
- gt_file = "ground_truth.json"
45
- data_base_dir = "data/ํฐ๋‹ค๋ฆฌ์ƒˆ์šฐ ์‹ค์ธก ๋ฐ์ดํ„ฐ_์ตํˆฌ์Šค์—์ด์•„์ด(์ฃผ)"
46
- output_base_dir = "data/yolo_dataset"
47
-
48
- # YOLO ๋””๋ ‰ํ† ๋ฆฌ ๊ตฌ์กฐ ์ƒ์„ฑ
49
- train_img_dir = Path(output_base_dir) / "images" / "train"
50
- val_img_dir = Path(output_base_dir) / "images" / "val"
51
- train_label_dir = Path(output_base_dir) / "labels" / "train"
52
- val_label_dir = Path(output_base_dir) / "labels" / "val"
53
-
54
- for d in [train_img_dir, val_img_dir, train_label_dir, val_label_dir]:
55
- d.mkdir(parents=True, exist_ok=True)
56
-
57
- # Ground Truth ๋กœ๋“œ
58
- print(f"\n๐Ÿ“‚ {gt_file} ๋กœ๋”ฉ ์ค‘...")
59
- with open(gt_file, 'r', encoding='utf-8') as f:
60
- gt_data = json.load(f)
61
-
62
- # ๋ฐ์ดํ„ฐ ์ˆ˜์ง‘
63
- all_samples = []
64
-
65
- for filename, annotations in gt_data.items():
66
- if not annotations: # ๋นˆ ๋ฆฌ์ŠคํŠธ๋Š” ๊ฑด๋„ˆ๋›ฐ๊ธฐ
67
- continue
68
-
69
- # ์ฒซ ๋ฒˆ์งธ annotation์—์„œ ํด๋” ์ •๋ณด ๊ฐ€์ ธ์˜ค๊ธฐ
70
- folder = annotations[0].get('folder', '')
71
-
72
- if not folder:
73
- print(f"โš ๏ธ {filename}: ํด๋” ์ •๋ณด ์—†์Œ, ๊ฑด๋„ˆ๋œ€")
74
- continue
75
-
76
- # ์ด๋ฏธ์ง€ ๊ฒฝ๋กœ ํ™•์ธ
77
- img_path = os.path.join(data_base_dir, folder, filename)
78
-
79
- if not os.path.exists(img_path):
80
- print(f"โš ๏ธ ์ด๋ฏธ์ง€ ์—†์Œ: {img_path}")
81
- continue
82
-
83
- all_samples.append({
84
- 'filename': filename,
85
- 'folder': folder,
86
- 'img_path': img_path,
87
- 'annotations': annotations
88
- })
89
-
90
- print(f"\nโœ… ์ด {len(all_samples)}๊ฐœ ์ƒ˜ํ”Œ ์ˆ˜์ง‘ ์™„๋ฃŒ")
91
-
92
- # Train/Val Split (80/20)
93
- random.seed(42) # ์žฌํ˜„์„ฑ์„ ์œ„ํ•ด
94
- random.shuffle(all_samples)
95
-
96
- split_idx = int(len(all_samples) * 0.8)
97
- train_samples = all_samples[:split_idx]
98
- val_samples = all_samples[split_idx:]
99
-
100
- print(f"\n๐Ÿ“Š ๋ฐ์ดํ„ฐ ๋ถ„ํ• :")
101
- print(f" - Train: {len(train_samples)}๊ฐœ")
102
- print(f" - Val: {len(val_samples)}๊ฐœ")
103
-
104
- # ๋ณ€ํ™˜ ํ•จ์ˆ˜
105
- def process_samples(samples, img_dir, label_dir, split_name):
106
- print(f"\n๐Ÿ”„ {split_name} ๋ฐ์ดํ„ฐ ๋ณ€ํ™˜ ์ค‘...")
107
-
108
- for idx, sample in enumerate(samples, 1):
109
- filename = sample['filename']
110
- img_path = sample['img_path']
111
- annotations = sample['annotations']
112
-
113
- # ์ด๋ฏธ์ง€ ๋ณต์‚ฌ
114
- dest_img_path = img_dir / filename
115
- shutil.copy2(img_path, dest_img_path)
116
-
117
- # ์ด๋ฏธ์ง€ ํฌ๊ธฐ ๊ฐ€์ ธ์˜ค๊ธฐ
118
- with Image.open(img_path) as img:
119
- img_width, img_height = img.size
120
-
121
- # YOLO ๋ผ๋ฒจ ์ƒ์„ฑ
122
- label_filename = Path(filename).stem + ".txt"
123
- label_path = label_dir / label_filename
124
-
125
- with open(label_path, 'w') as f:
126
- for ann in annotations:
127
- bbox = ann['bbox']
128
-
129
- # YOLO format์œผ๋กœ ๋ณ€ํ™˜
130
- x_center, y_center, width, height = convert_bbox_to_yolo(
131
- bbox, img_width, img_height
132
- )
133
-
134
- # YOLO ํ˜•์‹: class_id x_center y_center width height
135
- # class_id=0 (shrimp)
136
- f.write(f"0 {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}\n")
137
-
138
- if idx % 10 == 0 or idx == len(samples):
139
- print(f" ์ง„ํ–‰: {idx}/{len(samples)}")
140
-
141
- # Train/Val ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ
142
- process_samples(train_samples, train_img_dir, train_label_dir, "Train")
143
- process_samples(val_samples, val_img_dir, val_label_dir, "Val")
144
-
145
- # data.yaml ์ƒ์„ฑ
146
- yaml_path = Path(output_base_dir) / "data.yaml"
147
- yaml_content = f"""# Shrimp Detection Dataset
148
- path: {output_base_dir} # dataset root dir
149
- train: images/train # train images (relative to 'path')
150
- val: images/val # val images (relative to 'path')
151
-
152
- # Classes
153
- nc: 1 # number of classes
154
- names: ['shrimp'] # class names
155
- """
156
-
157
- with open(yaml_path, 'w', encoding='utf-8') as f:
158
- f.write(yaml_content)
159
-
160
- print(f"\nโœ… data.yaml ์ƒ์„ฑ ์™„๋ฃŒ: {yaml_path}")
161
-
162
- # ์š”์•ฝ ์ถœ๋ ฅ
163
- print("\n" + "=" * 60)
164
- print("โœ… ๋ณ€ํ™˜ ์™„๋ฃŒ!")
165
- print("=" * 60)
166
- print(f"\n๐Ÿ“ ์ถœ๋ ฅ ๋””๋ ‰ํ† ๋ฆฌ: {output_base_dir}")
167
- print(f"\n๐Ÿ“Š ๋ฐ์ดํ„ฐ์…‹ ๊ตฌ์กฐ:")
168
- print(f" - Train: {len(train_samples)} images")
169
- print(f" - Val: {len(val_samples)} images")
170
- print(f" - Total: {len(all_samples)} images")
171
-
172
- # ์ƒ˜ํ”Œ ํ™•์ธ
173
- print(f"\n๐Ÿ“ ์ƒ˜ํ”Œ ๋ผ๋ฒจ ํ™•์ธ (Train ์ฒซ ๋ฒˆ์งธ):")
174
- first_label = next(train_label_dir.glob("*.txt"))
175
- with open(first_label, 'r') as f:
176
- content = f.read()
177
- print(f" {first_label.name}:")
178
- for line in content.strip().split('\n'):
179
- print(f" {line}")
180
-
181
- print(f"\n๐ŸŽฏ ๋‹ค์Œ ๋‹จ๊ณ„: YOLOv8 ํ•™์Šต ์‹คํ–‰")
182
- print(f" python train_yolo.py")
183
-
184
- if __name__ == "__main__":
185
- main()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
debug_roboflow_api.py DELETED
@@ -1,38 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- """
3
- Roboflow API ์‘๋‹ต ๋””๋ฒ„๊น…
4
- """
5
- import sys
6
- sys.stdout.reconfigure(encoding='utf-8')
7
-
8
- from inference_sdk import InferenceHTTPClient
9
- import json
10
-
11
- # Roboflow ํด๋ผ์ด์–ธํŠธ
12
- client = InferenceHTTPClient(
13
- api_url="https://serverless.roboflow.com",
14
- api_key="azcIL8KDJVJMYrsERzI7"
15
- )
16
-
17
- # ํ…Œ์ŠคํŠธ ์ด๋ฏธ์ง€
18
- test_image = "data/yolo_dataset/images/train/250818_01.jpg"
19
-
20
- print("="*60)
21
- print("๐Ÿ” Roboflow API ์‘๋‹ต ๋””๋ฒ„๊น…")
22
- print("="*60)
23
- print(f"\n๐Ÿ“ธ ์ด๋ฏธ์ง€: {test_image}")
24
- print(f"๐Ÿ”— Workflow: vidraft/find-shrimp-6")
25
-
26
- # API ํ˜ธ์ถœ
27
- result = client.run_workflow(
28
- workspace_name="vidraft",
29
- workflow_id="find-shrimp-6",
30
- images={"image": test_image},
31
- use_cache=False # ์บ์‹œ ๋น„ํ™œ์„ฑํ™”
32
- )
33
-
34
- print(f"\n๐Ÿ“ฆ ์ „์ฒด ์‘๋‹ต ๊ตฌ์กฐ:")
35
- print(json.dumps(result, indent=2, ensure_ascii=False))
36
-
37
- print(f"\n{'='*60}")
38
- print("โœ… ๋””๋ฒ„๊น… ์™„๋ฃŒ")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
evaluate_yolo.py DELETED
@@ -1,239 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- """
3
- YOLOv8 ํ•™์Šต ๋ชจ๋ธ ํ‰๊ฐ€
4
- GT ๋ฐ์ดํ„ฐ๋กœ ์„ฑ๋Šฅ ์ธก์ • ๋ฐ RT-DETR๊ณผ ๋น„๊ต
5
- """
6
- import sys
7
- sys.stdout.reconfigure(encoding='utf-8')
8
-
9
- from ultralytics import YOLO
10
- import json
11
- import os
12
- from PIL import Image
13
- import numpy as np
14
- from pathlib import Path
15
-
16
- def calculate_iou(box1, box2):
17
- """IoU ๊ณ„์‚ฐ"""
18
- x1_1, y1_1, x2_1, y2_1 = box1
19
- x1_2, y1_2, x2_2, y2_2 = box2
20
-
21
- # Intersection
22
- x1_i = max(x1_1, x1_2)
23
- y1_i = max(y1_1, y1_2)
24
- x2_i = min(x2_1, x2_2)
25
- y2_i = min(y2_1, y2_2)
26
-
27
- if x2_i < x1_i or y2_i < y1_i:
28
- return 0.0
29
-
30
- intersection = (x2_i - x1_i) * (y2_i - y1_i)
31
-
32
- # Union
33
- area1 = (x2_1 - x1_1) * (y2_1 - y1_1)
34
- area2 = (x2_2 - x1_2) * (y2_2 - y1_2)
35
- union = area1 + area2 - intersection
36
-
37
- return intersection / union if union > 0 else 0.0
38
-
39
- def evaluate_model(model_path, gt_file, data_base_dir, confidence_threshold=0.25, iou_threshold=0.5):
40
- """๋ชจ๋ธ ํ‰๊ฐ€"""
41
- print(f"\n๐Ÿ“Š ๋ชจ๋ธ ํ‰๊ฐ€ ์‹œ์ž‘: {model_path}")
42
- print(f" - Confidence: {confidence_threshold}")
43
- print(f" - IoU Threshold: {iou_threshold}")
44
-
45
- # ๋ชจ๋ธ ๋กœ๋“œ
46
- model = YOLO(model_path)
47
- print(f"โœ… ๋ชจ๋ธ ๋กœ๋“œ ์™„๋ฃŒ")
48
-
49
- # GT ๋กœ๋“œ
50
- with open(gt_file, 'r', encoding='utf-8') as f:
51
- gt_data = json.load(f)
52
-
53
- # ํ†ต๊ณ„
54
- total_gt = 0
55
- total_pred = 0
56
- true_positives = 0
57
- false_positives = 0
58
- false_negatives = 0
59
-
60
- results_detail = []
61
-
62
- # ๊ฐ ์ด๋ฏธ์ง€ ํ‰๊ฐ€
63
- for filename, gt_boxes in gt_data.items():
64
- if not gt_boxes:
65
- continue
66
-
67
- folder = gt_boxes[0].get('folder', '')
68
- if not folder:
69
- continue
70
-
71
- img_path = os.path.join(data_base_dir, folder, filename)
72
- if not os.path.exists(img_path):
73
- continue
74
-
75
- # YOLOv8 ์ถ”๋ก 
76
- results = model(img_path, conf=confidence_threshold, verbose=False)
77
-
78
- # ์˜ˆ์ธก ๋ฐ•์Šค ์ถ”์ถœ
79
- pred_boxes = []
80
- if results and len(results) > 0:
81
- result = results[0]
82
- if result.boxes is not None and len(result.boxes) > 0:
83
- boxes = result.boxes.xyxy.cpu().numpy() # [x1, y1, x2, y2]
84
- confs = result.boxes.conf.cpu().numpy()
85
-
86
- for box, conf in zip(boxes, confs):
87
- pred_boxes.append({
88
- 'bbox': box.tolist(),
89
- 'confidence': float(conf)
90
- })
91
-
92
- # GT ๋ฐ•์Šค
93
- gt_boxes_only = [{'bbox': ann['bbox']} for ann in gt_boxes]
94
-
95
- # ๋งค์นญ
96
- matched_gt = set()
97
- matched_pred = set()
98
-
99
- for i, pred in enumerate(pred_boxes):
100
- best_iou = 0
101
- best_gt_idx = -1
102
-
103
- for j, gt in enumerate(gt_boxes_only):
104
- if j in matched_gt:
105
- continue
106
-
107
- iou = calculate_iou(pred['bbox'], gt['bbox'])
108
- if iou > best_iou:
109
- best_iou = iou
110
- best_gt_idx = j
111
-
112
- if best_iou >= iou_threshold:
113
- matched_pred.add(i)
114
- matched_gt.add(best_gt_idx)
115
-
116
- tp = len(matched_gt)
117
- fp = len(pred_boxes) - len(matched_pred)
118
- fn = len(gt_boxes_only) - len(matched_gt)
119
-
120
- true_positives += tp
121
- false_positives += fp
122
- false_negatives += fn
123
- total_gt += len(gt_boxes_only)
124
- total_pred += len(pred_boxes)
125
-
126
- results_detail.append({
127
- 'filename': filename,
128
- 'gt_count': len(gt_boxes_only),
129
- 'pred_count': len(pred_boxes),
130
- 'tp': tp,
131
- 'fp': fp,
132
- 'fn': fn
133
- })
134
-
135
- # ์„ฑ๋Šฅ ๊ณ„์‚ฐ
136
- precision = true_positives / (true_positives + false_positives) if (true_positives + false_positives) > 0 else 0
137
- recall = true_positives / (true_positives + false_negatives) if (true_positives + false_negatives) > 0 else 0
138
- f1 = 2 * precision * recall / (precision + recall) if (precision + recall) > 0 else 0
139
-
140
- return {
141
- 'precision': precision,
142
- 'recall': recall,
143
- 'f1': f1,
144
- 'tp': true_positives,
145
- 'fp': false_positives,
146
- 'fn': false_negatives,
147
- 'total_gt': total_gt,
148
- 'total_pred': total_pred
149
- }
150
-
151
- def main():
152
- print("=" * 60)
153
- print("๐ŸŽฏ YOLOv8 ๋ชจ๋ธ ํ‰๊ฐ€")
154
- print("=" * 60)
155
-
156
- # ๊ฒฝ๋กœ ์„ค์ •
157
- best_model = "runs/train/shrimp_yolov8n/weights/best.pt"
158
- gt_file = "ground_truth.json"
159
- data_base_dir = "data/ํฐ๋‹ค๋ฆฌ์ƒˆ์šฐ ์‹ค์ธก ๋ฐ์ดํ„ฐ_์ตํˆฌ์Šค์—์ด์•„์ด(์ฃผ)"
160
-
161
- if not os.path.exists(best_model):
162
- print(f"\nโŒ ๋ชจ๋ธ ํŒŒ์ผ ์—†์Œ: {best_model}")
163
- print(" ๋จผ์ € train_yolo.py๋ฅผ ์‹คํ–‰ํ•˜์„ธ์š”.")
164
- return
165
-
166
- print(f"\n๐Ÿ“ ๋ชจ๋ธ: {best_model}")
167
- print(f"๐Ÿ“ GT: {gt_file}")
168
-
169
- # ์—ฌ๋Ÿฌ confidence threshold ํ…Œ์ŠคํŠธ (๋‚ฎ์€ ๊ฐ’๋ถ€ํ„ฐ)
170
- confidence_thresholds = [0.001, 0.01, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3]
171
-
172
- print(f"\n๐Ÿ” Confidence Threshold ์ตœ์ ํ™” ์ค‘...")
173
- print(f" ํ…Œ์ŠคํŠธ ๋ฒ”์œ„: {confidence_thresholds}")
174
-
175
- best_f1 = 0
176
- best_conf = 0
177
- best_result = None
178
- all_results = []
179
-
180
- for conf in confidence_thresholds:
181
- result = evaluate_model(best_model, gt_file, data_base_dir, confidence_threshold=conf)
182
- result['conf'] = conf
183
- all_results.append(result)
184
-
185
- print(f"\n Conf={conf:.2f}: P={result['precision']:.1%}, R={result['recall']:.1%}, F1={result['f1']:.1%}")
186
-
187
- if result['f1'] > best_f1:
188
- best_f1 = result['f1']
189
- best_conf = conf
190
- best_result = result
191
-
192
- # ์ตœ์  ๊ฒฐ๊ณผ ์ถœ๋ ฅ
193
- print("\n" + "=" * 60)
194
- print("โœ… ํ‰๊ฐ€ ์™„๋ฃŒ!")
195
- print("=" * 60)
196
-
197
- print(f"\n๐Ÿ† ์ตœ์  ์„ฑ๋Šฅ (Confidence={best_conf}):")
198
- print(f" - Precision: {best_result['precision']:.1%}")
199
- print(f" - Recall: {best_result['recall']:.1%}")
200
- print(f" - F1 Score: {best_result['f1']:.1%}")
201
- print(f"\n - True Positives: {best_result['tp']}")
202
- print(f" - False Positives: {best_result['fp']}")
203
- print(f" - False Negatives: {best_result['fn']}")
204
- print(f" - Total GT: {best_result['total_gt']}")
205
- print(f" - Total Pred: {best_result['total_pred']}")
206
-
207
- # RT-DETR ๋น„๊ต
208
- print(f"\n๐Ÿ“Š RT-DETR + Filter ๋น„๊ต:")
209
- print(f" RT-DETR (Conf=0.065, Filter=90):")
210
- print(f" - Precision: 44.2%")
211
- print(f" - Recall: 94.0%")
212
- print(f" - F1 Score: 56.1%")
213
- print(f"\n YOLOv8 (Conf={best_conf}):")
214
- print(f" - Precision: {best_result['precision']:.1%}")
215
- print(f" - Recall: {best_result['recall']:.1%}")
216
- print(f" - F1 Score: {best_result['f1']:.1%}")
217
-
218
- # ๊ฐœ์„ ์œจ
219
- baseline_f1 = 0.561
220
- improvement = (best_result['f1'] - baseline_f1) / baseline_f1 * 100
221
- print(f"\n F1 ๊ฐœ์„ ์œจ: {improvement:+.1f}%")
222
-
223
- # ๊ฒฐ๊ณผ ์ €์žฅ
224
- output_file = "yolo_evaluation_results.json"
225
- with open(output_file, 'w', encoding='utf-8') as f:
226
- json.dump({
227
- 'best_result': best_result,
228
- 'all_results': all_results,
229
- 'baseline': {
230
- 'precision': 0.442,
231
- 'recall': 0.940,
232
- 'f1': 0.561
233
- }
234
- }, f, indent=2, ensure_ascii=False)
235
-
236
- print(f"\n๐Ÿ’พ ๊ฒฐ๊ณผ ์ €์žฅ: {output_file}")
237
-
238
- if __name__ == "__main__":
239
- main()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
examples/250818_01.jpg ADDED

Git LFS Details

  • SHA256: 30aa9f1a4e9d44a25c07c1cfc0f5f8f6d6339086b370db60aa7c8017853cd4c7
  • Pointer size: 132 Bytes
  • Size of remote file: 2.72 MB
examples/250818_03.jpg ADDED

Git LFS Details

  • SHA256: ab19afca8285298aeca50d94a552d5c47a746cc419389068f5dd8abc94eeb7fb
  • Pointer size: 132 Bytes
  • Size of remote file: 3.16 MB
examples/250818_04.jpg ADDED

Git LFS Details

  • SHA256: 2bbb7bdbe7ef7e88fde1fa4941f3cfd93182cdc913ae1550d33f39fcf7e017ef
  • Pointer size: 132 Bytes
  • Size of remote file: 2.88 MB
examples/250818_05.jpg ADDED

Git LFS Details

  • SHA256: 109726352642e4f97a424c43bc8e853bef901f7b799205b624929b7b1f300003
  • Pointer size: 132 Bytes
  • Size of remote file: 2.86 MB
examples/250818_10.jpg ADDED

Git LFS Details

  • SHA256: c2e87cd8bc47f99c3f6460af11066cb689e8dbd24d51bee542244c4a6985a6c3
  • Pointer size: 132 Bytes
  • Size of remote file: 3.13 MB
labeling_tool.py DELETED
@@ -1,650 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- """
3
- Ground Truth ๋ผ๋ฒจ๋ง ๋„๊ตฌ
4
- 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 json
12
- import os
13
- import glob
14
- from datetime import datetime
15
- import torch
16
- from transformers import RTDetrForObjectDetection, RTDetrImageProcessor
17
-
18
- # ์ „์—ญ ๋ณ€์ˆ˜ - ๋ชจ๋ธ์€ ํ•„์š”์‹œ์—๋งŒ ๋กœ๋“œ
19
- processor = None
20
- model = None
21
-
22
- def load_rtdetr_model():
23
- """RT-DETR ๋ชจ๋ธ์„ ํ•„์š”์‹œ์—๋งŒ ๋กœ๋”ฉ"""
24
- global processor, model
25
- if processor is None or model is None:
26
- print("๐Ÿ”„ RT-DETR ๋ชจ๋ธ ๋กœ๋”ฉ ์ค‘...")
27
- processor = RTDetrImageProcessor.from_pretrained("PekingU/rtdetr_r50vd_coco_o365")
28
- model = RTDetrForObjectDetection.from_pretrained("PekingU/rtdetr_r50vd_coco_o365")
29
- model.eval()
30
- print("โœ… RT-DETR ๋กœ๋”ฉ ์™„๋ฃŒ")
31
-
32
- current_data = {
33
- 'folder': None,
34
- 'images': [],
35
- 'current_idx': 0,
36
- 'detections': {},
37
- 'selections': {},
38
- 'confidence_threshold': 0.2, # ๊ธฐ๋ณธ ์‹ ๋ขฐ๋„ ์ž„๊ณ„๊ฐ’ (๋‚ฎ์ถค)
39
- 'image_cache': {} # ์ด๋ฏธ์ง€ ์บ์‹ฑ (์†๋„ ํ–ฅ์ƒ)
40
- }
41
-
42
- GROUND_TRUTH_FILE = "ground_truth.json"
43
- DATA_BASE = "data/ํฐ๋‹ค๋ฆฌ์ƒˆ์šฐ ์‹ค์ธก ๋ฐ์ดํ„ฐ_์ตํˆฌ์Šค์—์ด์•„์ด(์ฃผ)"
44
-
45
- def detect_with_rtdetr_fast(image, confidence=0.3):
46
- """RT-DETR ๋น ๋ฅธ ๊ฒ€์ถœ"""
47
- global processor, model
48
-
49
- # ๋ชจ๋ธ์ด ๋กœ๋“œ๋˜์ง€ ์•Š์•˜์œผ๋ฉด ๋กœ๋“œ
50
- load_rtdetr_model()
51
-
52
- inputs = processor(images=image, return_tensors="pt")
53
- with torch.no_grad():
54
- outputs = model(**inputs)
55
-
56
- target_sizes = torch.tensor([image.size[::-1]])
57
- results = processor.post_process_object_detection(
58
- outputs,
59
- target_sizes=target_sizes,
60
- threshold=confidence
61
- )[0]
62
-
63
- detections = []
64
- for score, label, box in zip(results["scores"], results["labels"], results["boxes"]):
65
- x1, y1, x2, y2 = box.tolist()
66
- detections.append({
67
- 'bbox': [x1, y1, x2, y2],
68
- 'confidence': score.item()
69
- })
70
-
71
- return detections
72
-
73
- def load_existing_ground_truth():
74
- """๊ธฐ์กด ground_truth.json ๋กœ๋“œ"""
75
- if os.path.exists(GROUND_TRUTH_FILE):
76
- with open(GROUND_TRUTH_FILE, 'r', encoding='utf-8') as f:
77
- return json.load(f)
78
- return {}
79
-
80
- def save_ground_truth(data):
81
- """ground_truth.json ์ €์žฅ"""
82
- # ๋ฐฑ์—… ํด๋” ์ƒ์„ฑ
83
- backup_dir = "backups"
84
- if not os.path.exists(backup_dir):
85
- os.makedirs(backup_dir)
86
-
87
- # ๋ฐฑ์—… (๊ธฐ์กด ํŒŒ์ผ์ด ์žˆ์„ ๋•Œ๋งŒ)
88
- if os.path.exists(GROUND_TRUTH_FILE):
89
- backup_name = f"ground_truth_backup_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json"
90
- backup_path = os.path.join(backup_dir, backup_name)
91
-
92
- # ๊ธฐ์กด ํŒŒ์ผ์„ ๋ฐฑ์—… ํด๋”๋กœ ๋ณต์‚ฌ
93
- import shutil
94
- shutil.copy2(GROUND_TRUTH_FILE, backup_path)
95
-
96
- # ์ €์žฅ
97
- with open(GROUND_TRUTH_FILE, 'w', encoding='utf-8') as f:
98
- json.dump(data, f, ensure_ascii=False, indent=2)
99
-
100
- print(f"โœ… Ground Truth ์ €์žฅ ์™„๋ฃŒ: {len(data)}๊ฐœ ์ด๋ฏธ์ง€")
101
-
102
- def get_folders():
103
- """์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ํด๋” ๋ชฉ๋ก"""
104
- folders = sorted(glob.glob(os.path.join(DATA_BASE, "2*")))
105
- return [os.path.basename(f) for f in folders if os.path.isdir(f)]
106
-
107
- def start_labeling(folder):
108
- """๋ผ๋ฒจ๋ง ์‹œ์ž‘"""
109
- if not folder:
110
- return None, "โŒ ํด๋”๋ฅผ ์„ ํƒํ•˜์„ธ์š”.", "", 0.2
111
-
112
- # ์ด๋ฏธ์ง€ ๋กœ๋“œ
113
- folder_path = os.path.join(DATA_BASE, folder)
114
- all_images = sorted(glob.glob(os.path.join(folder_path, "*.jpg")))
115
-
116
- # ํ•„ํ„ฐ๋ง: YYMMDD_NN.jpg ํ˜•์‹๋งŒ ํ—ˆ์šฉ (YYMMDD_NN-N.jpg ์ œ์™ธ)
117
- import re
118
- pattern = re.compile(r'^\d{6}_\d{2}\.jpg$') # 250818_01.jpg
119
- images = [img for img in all_images if pattern.match(os.path.basename(img))]
120
-
121
- if not images:
122
- return None, f"โŒ {folder}์— ์˜ฌ๋ฐ”๋ฅธ ํ˜•์‹์˜ ์ด๋ฏธ์ง€๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. (YYMMDD_NN.jpg)", "", 0.2
123
-
124
- current_data['folder'] = folder
125
- current_data['images'] = [os.path.basename(img) for img in images]
126
- current_data['current_idx'] = 0
127
- current_data['detections'] = {}
128
- current_data['selections'] = {}
129
- current_data['confidence_threshold'] = 0.2
130
- current_data['image_cache'] = {} # ์บ์‹œ ์ดˆ๊ธฐํ™”
131
-
132
- filtered_count = len(all_images) - len(images)
133
- if filtered_count > 0:
134
- print(f"๐Ÿ“‚ ํด๋”: {folder}, ์ด๋ฏธ์ง€: {len(images)}๊ฐœ (์ œ์™ธ: {filtered_count}๊ฐœ)")
135
- else:
136
- print(f"๐Ÿ“‚ ํด๋”: {folder}, ์ด๋ฏธ์ง€: {len(images)}๊ฐœ")
137
-
138
- # ์ฒซ ์ด๋ฏธ์ง€ ๋กœ๋“œ
139
- return load_image(0)
140
-
141
- def load_image(idx, confidence_threshold=None):
142
- """์ด๋ฏธ์ง€ ๋กœ๋“œ ๋ฐ ๊ฒ€์ถœ"""
143
- if idx < 0 or idx >= len(current_data['images']):
144
- return None, "โŒ ๋ฒ”์œ„๋ฅผ ๋ฒ—์–ด๋‚œ ์ธ๋ฑ์Šค์ž…๋‹ˆ๋‹ค.", "", ""
145
-
146
- current_data['current_idx'] = idx
147
- filename = current_data['images'][idx]
148
- folder = current_data['folder']
149
- img_path = os.path.join(DATA_BASE, folder, filename)
150
-
151
- # ์‹ ๋ขฐ๋„ ์ž„๊ณ„๊ฐ’ ์—…๋ฐ์ดํŠธ
152
- if confidence_threshold is not None:
153
- current_data['confidence_threshold'] = confidence_threshold
154
-
155
- # ์ด๋ฏธ์ง€ ์บ์‹œ ์‚ฌ์šฉ (๋””์Šคํฌ I/O ์ตœ์†Œํ™”)
156
- if filename not in current_data['image_cache']:
157
- img_path = os.path.join(DATA_BASE, folder, filename)
158
- current_data['image_cache'][filename] = Image.open(img_path).convert('RGB')
159
-
160
- image = current_data['image_cache'][filename]
161
-
162
- # RT-DETR ๊ฒ€์ถœ (์‹ ๋ขฐ๋„ ๋ณ€๊ฒฝ ์‹œ ์žฌ๊ฒ€์ถœ)
163
- cache_key = f"{filename}_{current_data['confidence_threshold']}"
164
- if cache_key not in current_data['detections']:
165
- print(f"๐Ÿ” RT-DETR ๊ฒ€์ถœ ์ค‘: {filename} (์‹ ๋ขฐ๋„={current_data['confidence_threshold']:.2f})")
166
- detections = detect_with_rtdetr_fast(image, confidence=current_data['confidence_threshold'])
167
- current_data['detections'][cache_key] = detections
168
- else:
169
- detections = current_data['detections'][cache_key]
170
-
171
- # ์„ ํƒ ์ƒํƒœ ์ดˆ๊ธฐํ™”
172
- if filename not in current_data['selections']:
173
- current_data['selections'][filename] = []
174
-
175
- # ์‹œ๊ฐํ™”
176
- img_with_boxes = draw_boxes(image, detections, current_data['selections'][filename])
177
-
178
- # ์ง„ํ–‰๋ฅ 
179
- progress = f"{idx + 1}/{len(current_data['images'])} ({(idx+1)/len(current_data['images'])*100:.0f}%)"
180
-
181
- # ์ •๋ณด
182
- info = f"""
183
- ### ๐Ÿ“ท ์ด๋ฏธ์ง€: {filename}
184
-
185
- - **์ง„ํ–‰๋ฅ **: {progress}
186
- - **์‹ ๋ขฐ๋„ ์ž„๊ณ„๊ฐ’**: {current_data['confidence_threshold']:.2f}
187
- - **๊ฒ€์ถœ๋œ ๋ฐ•์Šค**: {len(detections)}๊ฐœ
188
- - **์„ ํƒ๋œ ๋ฐ•์Šค**: {len(current_data['selections'][filename])}๊ฐœ
189
-
190
- ### ๐Ÿ“ ์‚ฌ์šฉ ๋ฐฉ๋ฒ•
191
-
192
- 1. **์ƒˆ์šฐ๊ฐ€ ๋งž๋Š” ๋ฐ•์Šค๋ฅผ ํด๋ฆญ**ํ•˜์—ฌ ์„ ํƒ (ํŒŒ๋ž€์ƒ‰์œผ๋กœ ๋ณ€๊ฒฝ)
193
- 2. ์ž˜๋ชป ์„ ํƒ ์‹œ **๋‹ค์‹œ ํด๋ฆญ**ํ•˜์—ฌ ํ•ด์ œ (๋…น์ƒ‰์œผ๋กœ ๋˜๋Œ๋ฆผ)
194
- 3. **์‹ ๋ขฐ๋„ ์Šฌ๋ผ์ด๋”**๋กœ ๊ฒ€์ถœ ๋ฏผ๊ฐ๋„ ์กฐ์ • (๋‚ฎ์„์ˆ˜๋ก ๋” ๋งŽ์ด ๊ฒ€์ถœ)
195
- 4. **"๋‹ค์Œ"** ๋ฒ„ํŠผ์œผ๋กœ ์ €์žฅ ๋ฐ ๋‹ค์Œ ์ด๋ฏธ์ง€๋กœ ์ด๋™
196
-
197
- **๋ฐ•์Šค ์ƒ‰์ƒ:**
198
- - ๐ŸŸข ๋…น์ƒ‰: ๊ฒ€์ถœ๋จ (์„ ํƒ ์•ˆ ๋จ)
199
- - ๐Ÿ”ต ํŒŒ๋ž€์ƒ‰: ์„ ํƒ๋จ (์ƒˆ์šฐ)
200
- """
201
-
202
- return img_with_boxes, info, progress, current_data['confidence_threshold']
203
-
204
- def draw_boxes(image, detections, selected_indices):
205
- """๋ฐ•์Šค ๊ทธ๋ฆฌ๊ธฐ (ํด๋ฆญ ๊ฐ€๋Šฅํ•œ ๋ฒˆํ˜ธ ๋ฒ„ํŠผ ํฌํ•จ) - ์ตœ์ ํ™”"""
206
- # ์ด๋ฏธ์ง€ ๋ณต์‚ฌ ๋Œ€์‹  ์ง์ ‘ ์ˆ˜์ • (์†๋„ ํ–ฅ์ƒ)
207
- img = image.copy()
208
- draw = ImageDraw.Draw(img)
209
-
210
- try:
211
- font = ImageFont.truetype("arial.ttf", 28) # ๋” ํฐ ํฐํŠธ
212
- font_tiny = ImageFont.truetype("arial.ttf", 10)
213
- except:
214
- font = ImageFont.load_default()
215
- font_tiny = ImageFont.load_default()
216
-
217
- # ์„ ํƒ๋˜์ง€ ์•Š์€ ๋ฐ•์Šค ๋จผ์ € ๊ทธ๋ฆฌ๊ธฐ (๋’ค์ชฝ ๋ ˆ์ด์–ด)
218
- for idx, det in enumerate(detections):
219
- if idx not in selected_indices:
220
- x1, y1, x2, y2 = det['bbox']
221
- color = "lime"
222
-
223
- # ๋ฐ•์Šค ํ…Œ๋‘๋ฆฌ
224
- draw.rectangle([x1, y1, x2, y2], outline=color, width=10)
225
-
226
- # ์ฝ”๋„ˆ ๋ผ๋ฒจ
227
- corner_label = f"#{idx+1}"
228
- draw.rectangle([x1-2, y1-24, x1+30, y1-2], fill=color)
229
- draw.text((x1, y1 - 22), corner_label, fill="white", font=font_tiny)
230
-
231
- # ์„ ํƒ๋œ ๋ฐ•์Šค ๋‚˜์ค‘์— ๊ทธ๋ฆฌ๊ธฐ (์•ž์ชฝ ๋ ˆ์ด์–ด)
232
- for idx, det in enumerate(detections):
233
- if idx in selected_indices:
234
- x1, y1, x2, y2 = det['bbox']
235
- color = "blue"
236
-
237
- # ๋ฐ•์Šค ํ…Œ๋‘๋ฆฌ (๋” ๋‘๊ป๊ฒŒ)
238
- draw.rectangle([x1, y1, x2, y2], outline=color, width=14)
239
-
240
- # ์ฝ”๋„ˆ ๋ผ๋ฒจ
241
- corner_label = f"โœ“#{idx+1}"
242
- draw.rectangle([x1-2, y1-24, x1+40, y1-2], fill=color)
243
- draw.text((x1, y1 - 22), corner_label, fill="white", font=font_tiny)
244
-
245
- # ์›ํ˜• ๋ฒ„ํŠผ (๋ชจ๋“  ๋ฐ•์Šค)
246
- for idx, det in enumerate(detections):
247
- x1, y1, x2, y2 = det['bbox']
248
- center_x = (x1 + x2) / 2
249
- center_y = (y1 + y2) / 2
250
-
251
- # ์„ ํƒ ์—ฌ๋ถ€
252
- selected = idx in selected_indices
253
- btn_color = "blue" if selected else "lime"
254
- btn_text = f"โœ“{idx+1}" if selected else f"{idx+1}"
255
-
256
- # ๋ฒ„ํŠผ ํฌ๊ธฐ
257
- box_width = x2 - x1
258
- box_height = y2 - y1
259
- radius = min(55, box_width * 0.18, box_height * 0.35)
260
-
261
- # ์™ธ๊ณฝ ์› (ํฐ์ƒ‰ ํ…Œ๋‘๋ฆฌ)
262
- draw.ellipse(
263
- [center_x - radius - 4, center_y - radius - 4,
264
- center_x + radius + 4, center_y + radius + 4],
265
- fill=btn_color,
266
- outline="white",
267
- width=5
268
- )
269
-
270
- # ๋‚ด๋ถ€ ์›
271
- draw.ellipse(
272
- [center_x - radius, center_y - radius,
273
- center_x + radius, center_y + radius],
274
- fill=btn_color
275
- )
276
-
277
- # ํ…์ŠคํŠธ ์ค‘์•™ ์ •๋ ฌ
278
- text_bbox = draw.textbbox((0, 0), btn_text, font=font)
279
- text_width = text_bbox[2] - text_bbox[0]
280
- text_height = text_bbox[3] - text_bbox[1]
281
- text_x = center_x - text_width / 2
282
- text_y = center_y - text_height / 2 - 2
283
-
284
- # ํ…์ŠคํŠธ (๊ทธ๋ฆผ์ž + ๋ณธ์ฒด)
285
- draw.text((text_x + 2, text_y + 2), btn_text, fill="black", font=font)
286
- draw.text((text_x, text_y), btn_text, fill="white", font=font)
287
-
288
- # ํ—ค๋”
289
- header = f"๊ฒ€์ถœ: {len(detections)}๊ฐœ | ์„ ํƒ: {len(selected_indices)}๊ฐœ"
290
- header_bbox = draw.textbbox((10, 10), header, font=font)
291
- draw.rectangle([5, 5, header_bbox[2]+10, header_bbox[3]+10], fill="black", outline="white", width=2)
292
- draw.text((10, 10), header, fill="white", font=font)
293
-
294
- return img
295
-
296
- def redraw_current_image():
297
- """ํ˜„์žฌ ์ด๋ฏธ์ง€ ๋น ๋ฅด๊ฒŒ ์žฌ๊ทธ๋ฆฌ๊ธฐ (์žฌ๊ฒ€์ถœ ์—†์ด ๋ฐ•์Šค๋งŒ ๋‹ค์‹œ ๊ทธ๋ฆฌ๊ธฐ)"""
298
- idx = current_data['current_idx']
299
- filename = current_data['images'][idx]
300
- folder = current_data['folder']
301
-
302
- # ์ด๋ฏธ์ง€ ์บ์‹œ ํ™•์ธ (๋””์Šคํฌ I/O ์ตœ์†Œํ™”)
303
- if filename not in current_data['image_cache']:
304
- img_path = os.path.join(DATA_BASE, folder, filename)
305
- current_data['image_cache'][filename] = Image.open(img_path).convert('RGB')
306
-
307
- image = current_data['image_cache'][filename]
308
-
309
- # ์บ์‹œ๋œ ๊ฒ€์ถœ ๊ฒฐ๊ณผ ์‚ฌ์šฉ (์žฌ๊ฒ€์ถœ ์—†์Œ!)
310
- cache_key = f"{filename}_{current_data['confidence_threshold']}"
311
- detections = current_data['detections'][cache_key]
312
-
313
- # ์„ ํƒ ์ƒํƒœ
314
- selections = current_data['selections'][filename]
315
-
316
- # ๋ฐ•์Šค๋งŒ ๋‹ค์‹œ ๊ทธ๋ฆฌ๊ธฐ
317
- img_with_boxes = draw_boxes(image, detections, selections)
318
-
319
- # ์ง„ํ–‰๋ฅ  (๊ฐ„์†Œํ™”)
320
- progress = f"{idx + 1}/{len(current_data['images'])}"
321
-
322
- # ์ •๋ณด (๊ฐ„์†Œํ™” - ํ…์ŠคํŠธ ์ƒ์„ฑ ์‹œ๊ฐ„ ๋‹จ์ถ•)
323
- info = f"**{filename}** | ๊ฒ€์ถœ: {len(detections)}๊ฐœ | ์„ ํƒ: {len(selections)}๊ฐœ"
324
-
325
- return img_with_boxes, info, progress, current_data['confidence_threshold']
326
-
327
- def toggle_selection(evt: gr.SelectData):
328
- """๋ฐ•์Šค ํด๋ฆญ ์‹œ ์„ ํƒ/ํ•ด์ œ (์›ํ˜• ๋ฒ„ํŠผ ์šฐ์„  ๊ฐ์ง€)"""
329
- # ํด๋ฆญ ์ขŒํ‘œ
330
- click_x, click_y = evt.index
331
-
332
- filename = current_data['images'][current_data['current_idx']]
333
- cache_key = f"{filename}_{current_data['confidence_threshold']}"
334
- detections = current_data['detections'][cache_key]
335
- selections = current_data['selections'][filename]
336
-
337
- if not detections:
338
- return redraw_current_image()
339
-
340
- # 1๋‹จ๊ณ„: ์›ํ˜• ๋ฒ„ํŠผ ๋‚ด๋ถ€ ํด๋ฆญ ๊ฐ์ง€ (์ตœ์šฐ์„ )
341
- button_candidates = []
342
-
343
- for idx, det in enumerate(detections):
344
- x1, y1, x2, y2 = det['bbox']
345
- center_x = (x1 + x2) / 2
346
- center_y = (y1 + y2) / 2
347
-
348
- # ๋ฒ„ํŠผ ํฌ๊ธฐ ๊ณ„์‚ฐ (draw_boxes์™€ ๋™์ผ)
349
- box_width = x2 - x1
350
- box_height = y2 - y1
351
- radius = min(50, box_width * 0.15, box_height * 0.3)
352
-
353
- # ํด๋ฆญ์ด ์›ํ˜• ๋ฒ„ํŠผ ๋‚ด๋ถ€์ธ์ง€ ํ™•์ธ
354
- distance_from_center = ((click_x - center_x) ** 2 + (click_y - center_y) ** 2) ** 0.5
355
-
356
- if distance_from_center <= radius:
357
- # ๋ฒ„ํŠผ ๋‚ด๋ถ€ ํด๋ฆญ!
358
- button_candidates.append((idx, distance_from_center))
359
-
360
- # 2๋‹จ๊ณ„: ๋ฒ„ํŠผ ํด๋ฆญ์ด ์žˆ์œผ๋ฉด ๊ฐ€์žฅ ๊ฐ€๊นŒ์šด ๋ฒ„ํŠผ ์„ ํƒ
361
- clicked_idx = None
362
- if button_candidates:
363
- # ๋ฒ„ํŠผ ์ค‘์‹ฌ์— ๊ฐ€์žฅ ๊ฐ€๊นŒ์šด ๊ฒƒ ์„ ํƒ
364
- button_candidates.sort(key=lambda x: x[1])
365
- clicked_idx = button_candidates[0][0]
366
- print(f"๐ŸŽฏ ๋ฒ„ํŠผ ํด๋ฆญ โ†’ ๋ฐ•์Šค #{clicked_idx+1}")
367
-
368
- # 3๋‹จ๊ณ„: ๋ฒ„ํŠผ ํด๋ฆญ ์—†์œผ๋ฉด ๋ฐ•์Šค ์˜์—ญ ํด๋ฆญ ํ™•์ธ
369
- else:
370
- box_candidates = []
371
- for idx, det in enumerate(detections):
372
- x1, y1, x2, y2 = det['bbox']
373
-
374
- # ๋ฐ•์Šค ๋‚ด๋ถ€์ธ์ง€ ํ™•์ธ
375
- if x1 <= click_x <= x2 and y1 <= click_y <= y2:
376
- center_x = (x1 + x2) / 2
377
- center_y = (y1 + y2) / 2
378
- distance = ((click_x - center_x) ** 2 + (click_y - center_y) ** 2) ** 0.5
379
- area = (x2 - x1) * (y2 - y1)
380
-
381
- # ์ž‘์€ ๋ฐ•์Šค + ๊ฐ€๊นŒ์šด ๋ฐ•์Šค ์šฐ์„ 
382
- score = area + distance * 100
383
- box_candidates.append((idx, score, area, distance))
384
-
385
- if box_candidates:
386
- box_candidates.sort(key=lambda x: x[1])
387
- clicked_idx = box_candidates[0][0]
388
- best = box_candidates[0]
389
- print(f"๐Ÿ“ ๋ฐ•์Šค ์˜์—ญ ํด๋ฆญ ({click_x:.0f}, {click_y:.0f}) โ†’ ๋ฐ•์Šค #{clicked_idx+1} (๊ฑฐ๋ฆฌ={best[3]:.0f})")
390
-
391
- # 4๋‹จ๊ณ„: ์„ ํƒ/ํ•ด์ œ ํ† ๊ธ€
392
- if clicked_idx is not None:
393
- if clicked_idx in selections:
394
- selections.remove(clicked_idx)
395
- print(f" โœ“ ๋ฐ•์Šค #{clicked_idx+1} ์„ ํƒ ํ•ด์ œ")
396
- else:
397
- selections.append(clicked_idx)
398
- print(f" โœ“ ๋ฐ•์Šค #{clicked_idx+1} ์„ ํƒ๋จ")
399
-
400
- current_data['selections'][filename] = selections
401
- else:
402
- print(f"โŒ ํด๋ฆญ ์œ„์น˜์— ๋ฐ•์Šค ์—†์Œ")
403
-
404
- # ๋น ๋ฅธ ์—…๋ฐ์ดํŠธ: ์ด๋ฏธ์ง€๋งŒ ๋‹ค์‹œ ๊ทธ๋ฆฌ๊ธฐ (์žฌ๊ฒ€์ถœ ์—†์Œ)
405
- return redraw_current_image()
406
-
407
- def previous_image():
408
- """์ด์ „ ์ด๋ฏธ์ง€"""
409
- idx = current_data['current_idx'] - 1
410
- if idx < 0:
411
- return load_image(current_data['current_idx']) + ("โš ๏ธ ์ฒซ ์ด๋ฏธ์ง€์ž…๋‹ˆ๋‹ค.",)
412
- return load_image(idx) + ("",)
413
-
414
- def next_image():
415
- """๋‹ค์Œ ์ด๋ฏธ์ง€ (์ €์žฅ ํฌํ•จ)"""
416
- # ํ˜„์žฌ ์„ ํƒ ์ €์žฅ
417
- save_current_selection()
418
-
419
- idx = current_data['current_idx'] + 1
420
- if idx >= len(current_data['images']):
421
- return load_image(current_data['current_idx']) + ("โš ๏ธ ๋งˆ์ง€๋ง‰ ์ด๋ฏธ์ง€์ž…๋‹ˆ๋‹ค. '์™„๋ฃŒ' ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด์„ธ์š”.",)
422
- return load_image(idx) + ("โœ… ์ €์žฅ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.",)
423
-
424
- def skip_image():
425
- """๊ฑด๋„ˆ๋›ฐ๊ธฐ"""
426
- filename = current_data['images'][current_data['current_idx']]
427
- current_data['selections'][filename] = []
428
-
429
- idx = current_data['current_idx'] + 1
430
- if idx >= len(current_data['images']):
431
- return load_image(current_data['current_idx']) + ("โš ๏ธ ๋งˆ์ง€๋ง‰ ์ด๋ฏธ์ง€์ž…๋‹ˆ๋‹ค.",)
432
- return load_image(idx) + ("โญ๏ธ ๊ฑด๋„ˆ๋›ฐ์—ˆ์Šต๋‹ˆ๋‹ค.",)
433
-
434
- def reset_selection():
435
- """ํ˜„์žฌ ์ด๋ฏธ์ง€์˜ ์„ ํƒ ์ดˆ๊ธฐํ™”"""
436
- filename = current_data['images'][current_data['current_idx']]
437
- current_data['selections'][filename] = []
438
-
439
- # ๋น ๋ฅธ ์—…๋ฐ์ดํŠธ: ์ด๋ฏธ์ง€๋งŒ ๋‹ค์‹œ ๊ทธ๋ฆฌ๊ธฐ
440
- return redraw_current_image() + ("๐Ÿ”„ ์„ ํƒ์ด ์ดˆ๊ธฐํ™”๋˜์—ˆ์Šต๋‹ˆ๋‹ค.",)
441
-
442
- def save_current_selection():
443
- """ํ˜„์žฌ ์„ ํƒ ์ €์žฅ"""
444
- filename = current_data['images'][current_data['current_idx']]
445
- folder = current_data['folder']
446
- selections = current_data['selections'].get(filename, [])
447
- cache_key = f"{filename}_{current_data['confidence_threshold']}"
448
- detections = current_data['detections'][cache_key]
449
-
450
- # Ground Truth ํ˜•์‹์œผ๋กœ ๋ณ€ํ™˜
451
- gt_data = load_existing_ground_truth()
452
-
453
- gt_data[filename] = [
454
- {
455
- "bbox": detections[idx]['bbox'],
456
- "label": "shrimp",
457
- "folder": folder,
458
- "confidence": detections[idx]['confidence']
459
- }
460
- for idx in selections
461
- ]
462
-
463
- # ์ž„์‹œ ์ €์žฅ (๋งค๋ฒˆ)
464
- save_ground_truth(gt_data)
465
-
466
- def finish_labeling():
467
- """๋ผ๋ฒจ๋ง ์™„๋ฃŒ"""
468
- # ๋งˆ์ง€๋ง‰ ์ด๋ฏธ์ง€ ์ €์žฅ
469
- save_current_selection()
470
-
471
- gt_data = load_existing_ground_truth()
472
- total_images = len(gt_data)
473
- total_boxes = sum(len(v) for v in gt_data.values())
474
-
475
- msg = f"""
476
- ## โœ… ๋ผ๋ฒจ๋ง ์™„๋ฃŒ!
477
-
478
- - **์ด ์ด๋ฏธ์ง€**: {total_images}๊ฐœ
479
- - **์ด ๋ฐ”์šด๋”ฉ ๋ฐ•์Šค**: {total_boxes}๊ฐœ
480
- - **์ €์žฅ ์œ„์น˜**: {GROUND_TRUTH_FILE}
481
-
482
- ### ๋‹ค์Œ ๋‹จ๊ณ„:
483
-
484
- ```bash
485
- python test_quantitative_evaluation.py
486
- ```
487
-
488
- ์„ฑ๋Šฅ ์ธก์ • ํ›„ ๊ฒฐ๊ณผ๋ฅผ ํ™•์ธํ•˜์„ธ์š”!
489
- """
490
-
491
- return None, msg, "์™„๋ฃŒ"
492
-
493
- # ํ‚ค๋ณด๋“œ ๋‹จ์ถ•ํ‚ค๋ฅผ ์œ„ํ•œ JavaScript
494
- keyboard_js = """
495
- <script>
496
- document.addEventListener('keydown', function(event) {
497
- // ์ž…๋ ฅ ํ•„๋“œ์—์„œ๋Š” ๋‹จ์ถ•ํ‚ค ๋น„ํ™œ์„ฑํ™”
498
- if (event.target.tagName === 'INPUT' || event.target.tagName === 'TEXTAREA') {
499
- return;
500
- }
501
-
502
- // ํ™”์‚ดํ‘œ ํ‚ค, ์ŠคํŽ˜์ด์Šค๋ฐ”, S, R ํ‚ค ์ฒ˜๋ฆฌ
503
- if (event.key === 'ArrowRight' || event.key === ' ') {
504
- event.preventDefault();
505
- const nextBtn = document.querySelector('button:has-text("๋‹ค์Œ")') ||
506
- Array.from(document.querySelectorAll('button')).find(btn => btn.textContent.includes('๋‹ค์Œ'));
507
- if (nextBtn) nextBtn.click();
508
- } else if (event.key === 'ArrowLeft') {
509
- event.preventDefault();
510
- const prevBtn = Array.from(document.querySelectorAll('button')).find(btn => btn.textContent.includes('์ด์ „'));
511
- if (prevBtn) prevBtn.click();
512
- } else if (event.key.toLowerCase() === 's') {
513
- event.preventDefault();
514
- const skipBtn = Array.from(document.querySelectorAll('button')).find(btn => btn.textContent.includes('๊ฑด๋„ˆ๋›ฐ๊ธฐ'));
515
- if (skipBtn) skipBtn.click();
516
- } else if (event.key.toLowerCase() === 'r') {
517
- event.preventDefault();
518
- const resetBtn = Array.from(document.querySelectorAll('button')).find(btn => btn.textContent.includes('์ดˆ๊ธฐํ™”'));
519
- if (resetBtn) resetBtn.click();
520
- }
521
- });
522
- </script>
523
- """
524
-
525
- # Gradio ์ธํ„ฐํŽ˜์ด์Šค
526
- with gr.Blocks(title="๐Ÿท๏ธ Ground Truth ๋ผ๋ฒจ๋ง ๋„๊ตฌ", theme=gr.themes.Soft(), head=keyboard_js) as demo:
527
-
528
- gr.Markdown("""
529
- # ๐Ÿท๏ธ Ground Truth ๋ผ๋ฒจ๋ง ๋„๊ตฌ
530
-
531
- RT-DETR ๊ฒ€์ถœ ๊ฒฐ๊ณผ์—์„œ ์˜ฌ๋ฐ”๋ฅธ ์ƒˆ์šฐ ๋ฐ•์Šค๋งŒ ์„ ํƒํ•˜์—ฌ ๋ผ๋ฒจ๋งํ•ฉ๋‹ˆ๋‹ค.
532
-
533
- ---
534
- """)
535
-
536
- with gr.Row():
537
- folder_dropdown = gr.Dropdown(
538
- choices=get_folders(),
539
- label="๐Ÿ“ ๋ผ๋ฒจ๋งํ•  ํด๋” ์„ ํƒ",
540
- value=None
541
- )
542
- start_btn = gr.Button("๐Ÿš€ ์‹œ์ž‘", variant="primary")
543
-
544
- with gr.Row():
545
- with gr.Column(scale=2):
546
- image_output = gr.Image(label="์ด๋ฏธ์ง€ (๋ฐ•์Šค ํด๋ฆญ์œผ๋กœ ์„ ํƒ/ํ•ด์ œ)", type="pil")
547
-
548
- with gr.Column(scale=1):
549
- info_output = gr.Markdown()
550
-
551
- progress_output = gr.Textbox(label="์ง„ํ–‰๋ฅ ", interactive=False)
552
-
553
- confidence_slider = gr.Slider(
554
- minimum=0.05,
555
- maximum=0.5,
556
- value=0.2,
557
- step=0.05,
558
- label="๐ŸŽฏ ๊ฒ€์ถœ ์‹ ๋ขฐ๋„ ์ž„๊ณ„๊ฐ’ (๋‚ฎ์„์ˆ˜๋ก ๏ฟฝ๏ฟฝ๏ฟฝ ๋งŽ์ด ๊ฒ€์ถœ)",
559
- info="์ƒˆ์šฐ๊ฐ€ ๊ฒ€์ถœ ์•ˆ ๋˜๋ฉด ๋‚ฎ์ถ”์„ธ์š”"
560
- )
561
-
562
- with gr.Row():
563
- prev_btn = gr.Button("โ—€ ์ด์ „")
564
- skip_btn = gr.Button("โญ ๊ฑด๋„ˆ๋›ฐ๊ธฐ")
565
- next_btn = gr.Button("๋‹ค์Œ โ–ถ", variant="primary")
566
-
567
- reset_btn = gr.Button("๐Ÿ”„ ์„ ํƒ ์ดˆ๊ธฐํ™”", variant="secondary")
568
-
569
- finish_btn = gr.Button("โœ… ์™„๋ฃŒ", variant="stop", size="lg")
570
-
571
- status_output = gr.Textbox(label="์ƒํƒœ", interactive=False)
572
-
573
- gr.Markdown("""
574
- ---
575
-
576
- ### ๐Ÿ’ก ์‚ฌ์šฉ ํŒ
577
-
578
- **๋งˆ์šฐ์Šค:**
579
- - **ํด๋ฆญ**: ๋ฐ•์Šค๋ฅผ ํด๋ฆญํ•˜์—ฌ ์„ ํƒ/ํ•ด์ œ
580
- - **๋…น์ƒ‰ โ†’ ํŒŒ๋ž€์ƒ‰**: ์„ ํƒ๋จ (์ƒˆ์šฐ)
581
- - **ํŒŒ๋ž€์ƒ‰ โ†’ ๋…น์ƒ‰**: ํ•ด์ œ๋จ
582
-
583
- **ํ‚ค๋ณด๋“œ ๋‹จ์ถ•ํ‚ค:**
584
- - **โ†’ (์˜ค๋ฅธ์ชฝ ํ™”์‚ดํ‘œ)**: ๋‹ค์Œ ์ด๋ฏธ์ง€ (์ €์žฅ)
585
- - **โ† (์™ผ์ชฝ ํ™”์‚ดํ‘œ)**: ์ด์ „ ์ด๋ฏธ์ง€
586
- - **Space (์ŠคํŽ˜์ด์Šค๋ฐ”)**: ๋‹ค์Œ ์ด๋ฏธ์ง€ (์ €์žฅ)
587
- - **S**: ๊ฑด๋„ˆ๋›ฐ๊ธฐ
588
- - **R**: ์„ ํƒ ์ดˆ๊ธฐํ™”
589
-
590
- **๊ธฐํƒ€:**
591
- - **์‹ ๋ขฐ๋„ ์Šฌ๋ผ์ด๋”**: ๊ฒ€์ถœ ๋ฏผ๊ฐ๋„ ์กฐ์ • (0.05~0.5)
592
- - **10์žฅ๋งˆ๋‹ค ์ž๋™ ์ €์žฅ๋ฉ๋‹ˆ๋‹ค.**
593
- """)
594
-
595
- # ์ด๋ฒคํŠธ ์—ฐ๊ฒฐ
596
- start_btn.click(
597
- start_labeling,
598
- [folder_dropdown],
599
- [image_output, info_output, progress_output, confidence_slider]
600
- )
601
-
602
- # ์‹ ๋ขฐ๋„ ์Šฌ๋ผ์ด๋” ๋ณ€๊ฒฝ ์‹œ ํ˜„์žฌ ์ด๋ฏธ์ง€ ์žฌ๊ฒ€์ถœ
603
- confidence_slider.change(
604
- lambda conf: load_image(current_data['current_idx'], conf),
605
- [confidence_slider],
606
- [image_output, info_output, progress_output, confidence_slider]
607
- )
608
-
609
- image_output.select(
610
- toggle_selection,
611
- None,
612
- [image_output, info_output, progress_output, confidence_slider]
613
- )
614
-
615
- prev_btn.click(
616
- previous_image,
617
- None,
618
- [image_output, info_output, progress_output, confidence_slider, status_output]
619
- )
620
-
621
- next_btn.click(
622
- next_image,
623
- None,
624
- [image_output, info_output, progress_output, confidence_slider, status_output]
625
- )
626
-
627
- skip_btn.click(
628
- skip_image,
629
- None,
630
- [image_output, info_output, progress_output, confidence_slider, status_output]
631
- )
632
-
633
- reset_btn.click(
634
- reset_selection,
635
- None,
636
- [image_output, info_output, progress_output, confidence_slider, status_output]
637
- )
638
-
639
- finish_btn.click(
640
- finish_labeling,
641
- None,
642
- [image_output, info_output, progress_output]
643
- )
644
-
645
- if __name__ == "__main__":
646
- demo.launch(
647
- server_name="0.0.0.0",
648
- server_port=None, # ์ž๋™์œผ๋กœ ๋นˆ ํฌํŠธ ์ฐพ๊ธฐ
649
- share=False
650
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
optimize_yolov8_confidence.py DELETED
@@ -1,217 +0,0 @@
1
- """
2
- YOLOv8m Confidence Threshold ์ตœ์ ํ™”
3
- Ground Truth ๊ธฐ๋ฐ˜์œผ๋กœ ์ตœ์  confidence ๊ฐ’ ํƒ์ƒ‰
4
- """
5
- from ultralytics import YOLO
6
- from PIL import Image
7
- import json
8
- import os
9
- import glob
10
- import numpy as np
11
-
12
- # ํ•™์Šต๋œ ๋ชจ๋ธ ๋กœ๋“œ
13
- MODEL_PATH = "runs/train/yolov8m_shrimp2/weights/best.pt"
14
- model = YOLO(MODEL_PATH)
15
-
16
- print(f"โœ… YOLOv8m ๋ชจ๋ธ ๋กœ๋“œ ์™„๋ฃŒ: {MODEL_PATH}")
17
-
18
- # Ground Truth ๋กœ๋“œ
19
- GT_FILE = "ground_truth.json"
20
- with open(GT_FILE, 'r', encoding='utf-8') as f:
21
- ground_truth = json.load(f)
22
-
23
- print(f"โœ… Ground Truth ๋กœ๋“œ: {GT_FILE}")
24
-
25
- # GT ํ†ต๊ณ„
26
- total_gt = sum(len(gts) for gts in ground_truth.values() if gts)
27
- gt_images = [k for k, v in ground_truth.items() if v]
28
- print(f" - GT๊ฐ€ ์žˆ๋Š” ์ด๋ฏธ์ง€: {len(gt_images)}์žฅ")
29
- print(f" - ์ด GT ๊ฐ์ฒด: {total_gt}๊ฐœ")
30
- print("-" * 60)
31
-
32
- def calculate_iou(box1, box2):
33
- """IoU ๊ณ„์‚ฐ"""
34
- x1_min, y1_min, x1_max, y1_max = box1
35
- x2_min, y2_min, x2_max, y2_max = box2
36
-
37
- inter_x_min = max(x1_min, x2_min)
38
- inter_y_min = max(y1_min, y2_min)
39
- inter_x_max = min(x1_max, x2_max)
40
- inter_y_max = min(y1_max, y2_max)
41
-
42
- if inter_x_max < inter_x_min or inter_y_max < inter_y_min:
43
- return 0.0
44
-
45
- inter_area = (inter_x_max - inter_x_min) * (inter_y_max - inter_y_min)
46
- box1_area = (x1_max - x1_min) * (y1_max - y1_min)
47
- box2_area = (x2_max - x2_min) * (y2_max - y2_min)
48
- union_area = box1_area + box2_area - inter_area
49
-
50
- return inter_area / union_area if union_area > 0 else 0.0
51
-
52
- def evaluate_confidence_threshold(conf_threshold, iou_threshold=0.5):
53
- """ํŠน์ • confidence threshold์—์„œ ์„ฑ๋Šฅ ํ‰๊ฐ€"""
54
- tp = 0 # True Positive
55
- fp = 0 # False Positive
56
- fn = 0 # False Negative
57
-
58
- matched_gt_count = 0
59
- total_gt_count = 0
60
-
61
- # GT๊ฐ€ ์žˆ๋Š” ์ด๋ฏธ์ง€๋งŒ ํ…Œ์ŠคํŠธ
62
- for img_name in gt_images:
63
- # ์ด๋ฏธ์ง€ ๊ฒฝ๋กœ ์ฐพ๊ธฐ
64
- img_path = None
65
- for split in ['train', 'val']:
66
- search_path = f"data/yolo_dataset/images/{split}/{img_name}"
67
- if os.path.exists(search_path):
68
- img_path = search_path
69
- break
70
-
71
- if not img_path:
72
- # ํŒŒ์ผ๋ช…์—์„œ -1 ์ œ๊ฑฐํ•ด์„œ ๋‹ค์‹œ ์‹œ๋„
73
- base_name = img_name.replace('-1.jpg', '.jpg')
74
- for split in ['train', 'val']:
75
- search_path = f"data/yolo_dataset/images/{split}/{base_name}"
76
- if os.path.exists(search_path):
77
- img_path = search_path
78
- break
79
-
80
- if not img_path or not os.path.exists(img_path):
81
- continue
82
-
83
- # ์ด๋ฏธ์ง€ ๋กœ๋“œ
84
- image = Image.open(img_path)
85
-
86
- # YOLOv8 ๊ฒ€์ถœ
87
- results = model.predict(
88
- source=image,
89
- conf=conf_threshold,
90
- iou=0.7,
91
- device=0,
92
- verbose=False
93
- )
94
-
95
- # ๊ฒฐ๊ณผ ํŒŒ์‹ฑ
96
- result = results[0]
97
- boxes = result.boxes
98
-
99
- predictions = []
100
- if boxes is not None and len(boxes) > 0:
101
- for box in boxes:
102
- x1, y1, x2, y2 = box.xyxy[0].cpu().numpy()
103
- confidence = box.conf[0].cpu().item()
104
- predictions.append({
105
- 'bbox': [float(x1), float(y1), float(x2), float(y2)],
106
- 'confidence': confidence
107
- })
108
-
109
- # Ground Truth
110
- gt_boxes = ground_truth[img_name]
111
- total_gt_count += len(gt_boxes)
112
-
113
- # GT์™€ ๋งค์นญ
114
- matched_gt = set()
115
- matched_pred = set()
116
-
117
- for pred_idx, pred in enumerate(predictions):
118
- best_iou = 0
119
- best_gt_idx = -1
120
-
121
- for gt_idx, gt in enumerate(gt_boxes):
122
- if gt_idx in matched_gt:
123
- continue
124
-
125
- iou = calculate_iou(pred['bbox'], gt['bbox'])
126
- if iou > best_iou:
127
- best_iou = iou
128
- best_gt_idx = gt_idx
129
-
130
- if best_iou >= iou_threshold:
131
- tp += 1
132
- matched_gt.add(best_gt_idx)
133
- matched_pred.add(pred_idx)
134
- else:
135
- fp += 1
136
-
137
- # ๋งค์นญ๋˜์ง€ ์•Š์€ GT = False Negative
138
- fn += len(gt_boxes) - len(matched_gt)
139
- matched_gt_count += len(matched_gt)
140
-
141
- # ์„ฑ๋Šฅ ์ง€ํ‘œ ๊ณ„์‚ฐ
142
- precision = tp / (tp + fp) if (tp + fp) > 0 else 0
143
- recall = tp / (tp + fn) if (tp + fn) > 0 else 0
144
- f1 = 2 * (precision * recall) / (precision + recall) if (precision + recall) > 0 else 0
145
-
146
- return {
147
- 'tp': tp,
148
- 'fp': fp,
149
- 'fn': fn,
150
- 'precision': precision,
151
- 'recall': recall,
152
- 'f1': f1,
153
- 'matched_gt': matched_gt_count,
154
- 'total_gt': total_gt_count,
155
- 'gt_match_rate': matched_gt_count / total_gt_count if total_gt_count > 0 else 0
156
- }
157
-
158
- # Confidence threshold sweep
159
- print("\n๐Ÿ” Confidence Threshold ์ตœ์ ํ™” ์‹œ์ž‘...\n")
160
-
161
- confidence_thresholds = [0.01, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.65, 0.7, 0.75, 0.8]
162
- results = []
163
-
164
- for conf in confidence_thresholds:
165
- metrics = evaluate_confidence_threshold(conf)
166
- results.append({
167
- 'confidence': conf,
168
- **metrics
169
- })
170
-
171
- print(f"Conf {conf:.2f}: P={metrics['precision']:.1%} R={metrics['recall']:.1%} F1={metrics['f1']:.1%} | "
172
- f"GT๋งค์นญ={metrics['matched_gt']}/{metrics['total_gt']} ({metrics['gt_match_rate']:.1%})")
173
-
174
- # ์ตœ์ ๊ฐ’ ์ฐพ๊ธฐ
175
- best_f1 = max(results, key=lambda x: x['f1'])
176
- best_recall = max(results, key=lambda x: x['recall'])
177
- best_precision = max(results, key=lambda x: x['precision'])
178
- best_gt_match = max(results, key=lambda x: x['gt_match_rate'])
179
-
180
- print("\n" + "=" * 60)
181
- print("๐Ÿ“Š ์ตœ์ ํ™” ๊ฒฐ๊ณผ:")
182
- print("=" * 60)
183
-
184
- print(f"\n1๏ธโƒฃ ์ตœ๊ณ  F1 Score: {best_f1['f1']:.1%} (Confidence={best_f1['confidence']:.2f})")
185
- print(f" - Precision: {best_f1['precision']:.1%}")
186
- print(f" - Recall: {best_f1['recall']:.1%}")
187
- print(f" - GT ๋งค์นญ: {best_f1['matched_gt']}/{best_f1['total_gt']} ({best_f1['gt_match_rate']:.1%})")
188
-
189
- print(f"\n2๏ธโƒฃ ์ตœ๊ณ  Recall: {best_recall['recall']:.1%} (Confidence={best_recall['confidence']:.2f})")
190
- print(f" - F1 Score: {best_recall['f1']:.1%}")
191
- print(f" - Precision: {best_recall['precision']:.1%}")
192
-
193
- print(f"\n3๏ธโƒฃ ์ตœ๊ณ  Precision: {best_precision['precision']:.1%} (Confidence={best_precision['confidence']:.2f})")
194
- print(f" - F1 Score: {best_precision['f1']:.1%}")
195
- print(f" - Recall: {best_precision['recall']:.1%}")
196
-
197
- print(f"\n4๏ธโƒฃ ์ตœ๊ณ  GT ๋งค์นญ๋ฅ : {best_gt_match['gt_match_rate']:.1%} (Confidence={best_gt_match['confidence']:.2f})")
198
- print(f" - F1 Score: {best_gt_match['f1']:.1%}")
199
- print(f" - ๋งค์นญ: {best_gt_match['matched_gt']}/{best_gt_match['total_gt']}")
200
-
201
- print("\n๐Ÿ’ก ๊ถŒ์žฅ ์„ค์ •:")
202
- print(f" - ๊ท ํ˜•์žกํžŒ ์„ฑ๋Šฅ: confidence={best_f1['confidence']:.2f} (F1={best_f1['f1']:.1%})")
203
- print(f" - ๋†’์€ ์žฌํ˜„์œจ: confidence={best_recall['confidence']:.2f} (Recall={best_recall['recall']:.1%})")
204
-
205
- # ๊ฒฐ๊ณผ ์ €์žฅ
206
- output_file = "yolov8m_confidence_optimization.json"
207
- with open(output_file, 'w', encoding='utf-8') as f:
208
- json.dump({
209
- 'best_f1': best_f1,
210
- 'best_recall': best_recall,
211
- 'best_precision': best_precision,
212
- 'best_gt_match': best_gt_match,
213
- 'all_results': results
214
- }, f, indent=2, ensure_ascii=False)
215
-
216
- print(f"\n๐Ÿ’พ ๊ฒฐ๊ณผ ์ €์žฅ: {output_file}")
217
- print("=" * 60)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
optimize_yolov8_confidence_val_only.py DELETED
@@ -1,204 +0,0 @@
1
- """
2
- YOLOv8m Confidence Threshold ์ตœ์ ํ™” (Validation Set๋งŒ ์‚ฌ์šฉ)
3
- ๊ณผ์ ํ•ฉ ๋ฐฉ์ง€๋ฅผ ์œ„ํ•ด Val set 10์žฅ๋งŒ์œผ๋กœ ํ‰๊ฐ€
4
- """
5
- from ultralytics import YOLO
6
- from PIL import Image
7
- import json
8
- import os
9
-
10
- # ํ•™์Šต๋œ ๋ชจ๋ธ ๋กœ๋“œ
11
- MODEL_PATH = "runs/train/yolov8m_shrimp2/weights/best.pt"
12
- model = YOLO(MODEL_PATH)
13
-
14
- print(f"โœ… YOLOv8m ๋ชจ๋ธ ๋กœ๋“œ ์™„๋ฃŒ: {MODEL_PATH}")
15
-
16
- # Ground Truth ๋กœ๋“œ
17
- GT_FILE = "ground_truth.json"
18
- with open(GT_FILE, 'r', encoding='utf-8') as f:
19
- ground_truth = json.load(f)
20
-
21
- # Val set ์ด๋ฏธ์ง€๋งŒ ํ•„ํ„ฐ๋ง
22
- val_images = set(os.listdir('data/yolo_dataset/images/val'))
23
- gt_val_only = {}
24
-
25
- for img_name, gts in ground_truth.items():
26
- if not gts:
27
- continue
28
- base_name = img_name.replace('-1.jpg', '.jpg')
29
- if img_name in val_images or base_name in val_images:
30
- gt_val_only[img_name] = gts
31
-
32
- print(f"โœ… Ground Truth (Val set๋งŒ): {len(gt_val_only)}์žฅ")
33
- total_gt = sum(len(gts) for gts in gt_val_only.values())
34
- print(f" - ์ด GT ๊ฐ์ฒด: {total_gt}๊ฐœ")
35
- print("-" * 60)
36
-
37
- def calculate_iou(box1, box2):
38
- """IoU ๊ณ„์‚ฐ"""
39
- x1_min, y1_min, x1_max, y1_max = box1
40
- x2_min, y2_min, x2_max, y2_max = box2
41
-
42
- inter_x_min = max(x1_min, x2_min)
43
- inter_y_min = max(y1_min, y2_min)
44
- inter_x_max = min(x1_max, x2_max)
45
- inter_y_max = min(y1_max, y2_max)
46
-
47
- if inter_x_max < inter_x_min or inter_y_max < inter_y_min:
48
- return 0.0
49
-
50
- inter_area = (inter_x_max - inter_x_min) * (inter_y_max - inter_y_min)
51
- box1_area = (x1_max - x1_min) * (y1_max - y1_min)
52
- box2_area = (x2_max - x2_min) * (y2_max - y2_min)
53
- union_area = box1_area + box2_area - inter_area
54
-
55
- return inter_area / union_area if union_area > 0 else 0.0
56
-
57
- def evaluate_confidence_threshold(conf_threshold, iou_threshold=0.5):
58
- """ํŠน์ • confidence threshold์—์„œ ์„ฑ๋Šฅ ํ‰๊ฐ€ (Val set๋งŒ)"""
59
- tp = 0
60
- fp = 0
61
- fn = 0
62
-
63
- matched_gt_count = 0
64
- total_gt_count = 0
65
-
66
- for img_name, gt_boxes in gt_val_only.items():
67
- # ์ด๋ฏธ์ง€ ๊ฒฝ๋กœ
68
- img_path = f"data/yolo_dataset/images/val/{img_name}"
69
- base_name = img_name.replace('-1.jpg', '.jpg')
70
- if not os.path.exists(img_path):
71
- img_path = f"data/yolo_dataset/images/val/{base_name}"
72
-
73
- if not os.path.exists(img_path):
74
- continue
75
-
76
- # ์ด๋ฏธ์ง€ ๋กœ๋“œ
77
- image = Image.open(img_path)
78
-
79
- # YOLOv8 ๊ฒ€์ถœ
80
- results = model.predict(
81
- source=image,
82
- conf=conf_threshold,
83
- iou=0.7,
84
- device=0,
85
- verbose=False
86
- )
87
-
88
- result = results[0]
89
- boxes = result.boxes
90
-
91
- predictions = []
92
- if boxes is not None and len(boxes) > 0:
93
- for box in boxes:
94
- x1, y1, x2, y2 = box.xyxy[0].cpu().numpy()
95
- confidence = box.conf[0].cpu().item()
96
- predictions.append({
97
- 'bbox': [float(x1), float(y1), float(x2), float(y2)],
98
- 'confidence': confidence
99
- })
100
-
101
- total_gt_count += len(gt_boxes)
102
-
103
- # GT์™€ ๋งค์นญ
104
- matched_gt = set()
105
- matched_pred = set()
106
-
107
- for pred_idx, pred in enumerate(predictions):
108
- best_iou = 0
109
- best_gt_idx = -1
110
-
111
- for gt_idx, gt in enumerate(gt_boxes):
112
- if gt_idx in matched_gt:
113
- continue
114
-
115
- iou = calculate_iou(pred['bbox'], gt['bbox'])
116
- if iou > best_iou:
117
- best_iou = iou
118
- best_gt_idx = gt_idx
119
-
120
- if best_iou >= iou_threshold:
121
- tp += 1
122
- matched_gt.add(best_gt_idx)
123
- matched_pred.add(pred_idx)
124
- else:
125
- fp += 1
126
-
127
- fn += len(gt_boxes) - len(matched_gt)
128
- matched_gt_count += len(matched_gt)
129
-
130
- # ์„ฑ๋Šฅ ์ง€ํ‘œ ๊ณ„์‚ฐ
131
- precision = tp / (tp + fp) if (tp + fp) > 0 else 0
132
- recall = tp / (tp + fn) if (tp + fn) > 0 else 0
133
- f1 = 2 * (precision * recall) / (precision + recall) if (precision + recall) > 0 else 0
134
-
135
- return {
136
- 'tp': tp,
137
- 'fp': fp,
138
- 'fn': fn,
139
- 'precision': precision,
140
- 'recall': recall,
141
- 'f1': f1,
142
- 'matched_gt': matched_gt_count,
143
- 'total_gt': total_gt_count,
144
- 'gt_match_rate': matched_gt_count / total_gt_count if total_gt_count > 0 else 0
145
- }
146
-
147
- # Confidence threshold sweep
148
- print("\n๐Ÿ” Confidence Threshold ์ตœ์ ํ™” (Val set๋งŒ)...\n")
149
-
150
- confidence_thresholds = [0.01, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.65, 0.7, 0.75, 0.8, 0.85, 0.9]
151
- results = []
152
-
153
- for conf in confidence_thresholds:
154
- metrics = evaluate_confidence_threshold(conf)
155
- results.append({
156
- 'confidence': conf,
157
- **metrics
158
- })
159
-
160
- print(f"Conf {conf:.2f}: P={metrics['precision']:.1%} R={metrics['recall']:.1%} F1={metrics['f1']:.1%} | "
161
- f"GT๋งค์นญ={metrics['matched_gt']}/{metrics['total_gt']} ({metrics['gt_match_rate']:.1%})")
162
-
163
- # ์ตœ์ ๊ฐ’ ์ฐพ๊ธฐ
164
- best_f1 = max(results, key=lambda x: x['f1'])
165
- best_recall = max(results, key=lambda x: x['recall'])
166
- best_precision = max(results, key=lambda x: x['precision'])
167
-
168
- print("\n" + "=" * 60)
169
- print("๐Ÿ“Š ์ตœ์ ํ™” ๊ฒฐ๊ณผ (Val set๋งŒ, ๊ณผ์ ํ•ฉ ์—†์Œ):")
170
- print("=" * 60)
171
-
172
- print(f"\n1๏ธโƒฃ ์ตœ๊ณ  F1 Score: {best_f1['f1']:.1%} (Confidence={best_f1['confidence']:.2f})")
173
- print(f" - Precision: {best_f1['precision']:.1%}")
174
- print(f" - Recall: {best_f1['recall']:.1%}")
175
- print(f" - GT ๋งค์นญ: {best_f1['matched_gt']}/{best_f1['total_gt']} ({best_f1['gt_match_rate']:.1%})")
176
-
177
- print(f"\n2๏ธโƒฃ ์ตœ๊ณ  Recall: {best_recall['recall']:.1%} (Confidence={best_recall['confidence']:.2f})")
178
- print(f" - F1 Score: {best_recall['f1']:.1%}")
179
- print(f" - Precision: {best_recall['precision']:.1%}")
180
-
181
- print(f"\n3๏ธโƒฃ ์ตœ๊ณ  Precision: {best_precision['precision']:.1%} (Confidence={best_precision['confidence']:.2f})")
182
- print(f" - F1 Score: {best_precision['f1']:.1%}")
183
- print(f" - Recall: {best_precision['recall']:.1%}")
184
-
185
- print("\n๐Ÿ’ก ๊ถŒ์žฅ ์„ค์ • (Val set ๊ธฐ์ค€, ์ผ๋ฐ˜ํ™” ์„ฑ๋Šฅ):")
186
- print(f" - ์ตœ์  confidence: {best_f1['confidence']:.2f}")
187
- print(f" - F1 Score: {best_f1['f1']:.1%}")
188
- print(f" - Precision: {best_f1['precision']:.1%}, Recall: {best_f1['recall']:.1%}")
189
-
190
- # ๊ฒฐ๊ณผ ์ €์žฅ
191
- output_file = "yolov8m_confidence_optimization_val_only.json"
192
- with open(output_file, 'w', encoding='utf-8') as f:
193
- json.dump({
194
- 'dataset': 'validation_set_only',
195
- 'num_images': len(gt_val_only),
196
- 'total_gt': total_gt,
197
- 'best_f1': best_f1,
198
- 'best_recall': best_recall,
199
- 'best_precision': best_precision,
200
- 'all_results': results
201
- }, f, indent=2, ensure_ascii=False)
202
-
203
- print(f"\n๐Ÿ’พ ๊ฒฐ๊ณผ ์ €์žฅ: {output_file}")
204
- print("=" * 60)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
quick_test_roboflow.py DELETED
@@ -1,89 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- """
3
- Roboflow ๋ชจ๋ธ ๋น ๋ฅธ ํ…Œ์ŠคํŠธ
4
- """
5
- import sys
6
- sys.stdout.reconfigure(encoding='utf-8')
7
-
8
- import requests
9
- import base64
10
- from PIL import Image
11
- from io import BytesIO
12
- import json
13
-
14
- # ํ…Œ์ŠคํŠธ ์ด๋ฏธ์ง€
15
- test_image = "data/yolo_dataset/images/train/250818_04.jpg"
16
-
17
- print("="*60)
18
- print("๐Ÿฆ Roboflow ๋ชจ๋ธ ๋น ๋ฅธ ํ…Œ์ŠคํŠธ")
19
- print("="*60)
20
- print(f"๐Ÿ“ธ ์ด๋ฏธ์ง€: {test_image}\n")
21
-
22
- # ์ด๋ฏธ์ง€ ๋กœ๋“œ ๋ฐ ๋ฆฌ์‚ฌ์ด์ฆˆ
23
- image = Image.open(test_image)
24
- print(f"์›๋ณธ ํฌ๊ธฐ: {image.size}")
25
-
26
- image.thumbnail((640, 640), Image.Resampling.LANCZOS)
27
- print(f"๋ฆฌ์‚ฌ์ด์ฆˆ: {image.size}")
28
-
29
- # Base64 ์ธ์ฝ”๋”ฉ
30
- buffered = BytesIO()
31
- image.save(buffered, format="JPEG", quality=80)
32
- img_base64 = base64.b64encode(buffered.getvalue()).decode()
33
-
34
- # API ํ˜ธ์ถœ
35
- print(f"\n๐Ÿ”„ API ํ˜ธ์ถœ ์ค‘...\n")
36
- response = requests.post(
37
- 'https://serverless.roboflow.com/vidraft/workflows/find-shrimp-6',
38
- headers={'Content-Type': 'application/json'},
39
- json={
40
- 'api_key': 'azcIL8KDJVJMYrsERzI7',
41
- 'inputs': {
42
- 'image': {'type': 'base64', 'value': img_base64}
43
- }
44
- },
45
- timeout=30
46
- )
47
-
48
- if response.status_code != 200:
49
- print(f"โŒ ์˜ค๋ฅ˜: {response.status_code}")
50
- print(response.text)
51
- exit(1)
52
-
53
- result = response.json()
54
-
55
- # predictions ์ถ”์ถœ
56
- predictions = []
57
- if 'outputs' in result and len(result['outputs']) > 0:
58
- output = result['outputs'][0]
59
- if 'predictions' in output:
60
- pred_data = output['predictions']
61
- if isinstance(pred_data, dict) and 'predictions' in pred_data:
62
- predictions = pred_data['predictions']
63
-
64
- print(f"{'='*60}")
65
- print(f"๐Ÿ“Š ๊ฒ€์ถœ ๊ฒฐ๊ณผ")
66
- print(f"{'='*60}\n")
67
-
68
- print(f"์ด ๊ฒ€์ถœ ์ˆ˜: {len(predictions)}๊ฐœ\n")
69
-
70
- # ์ƒ์„ธ ๊ฒฐ๊ณผ
71
- for i, pred in enumerate(predictions, 1):
72
- cls = pred.get('class', 'unknown')
73
- conf = pred.get('confidence', 0)
74
- x = pred.get('x', 0)
75
- y = pred.get('y', 0)
76
- w = pred.get('width', 0)
77
- h = pred.get('height', 0)
78
-
79
- print(f"{i}. ํด๋ž˜์Šค: {cls}")
80
- print(f" ์‹ ๋ขฐ๋„: {conf:.1%}")
81
- print(f" ์œ„์น˜: ({x:.0f}, {y:.0f})")
82
- print(f" ํฌ๊ธฐ: {w:.0f} x {h:.0f}")
83
- print()
84
-
85
- # shrimp๋งŒ ํ•„ํ„ฐ๋ง
86
- shrimp_count = sum(1 for p in predictions if p.get('class') == 'shrimp')
87
- print(f"{'='*60}")
88
- print(f"โœ… shrimp ํด๋ž˜์Šค: {shrimp_count}๊ฐœ")
89
- print(f"{'='*60}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
quick_test_save_result.py DELETED
@@ -1,127 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- """
3
- Roboflow ๋ชจ๋ธ ๋น ๋ฅธ ํ…Œ์ŠคํŠธ + ๊ฒฐ๊ณผ ์ด๋ฏธ์ง€ ์ €์žฅ
4
- """
5
- import sys
6
- sys.stdout.reconfigure(encoding='utf-8')
7
-
8
- import requests
9
- import base64
10
- from PIL import Image, ImageDraw, ImageFont
11
- from io import BytesIO
12
- import json
13
-
14
- # ํ…Œ์ŠคํŠธ ์ด๋ฏธ์ง€
15
- test_image = "data/yolo_dataset/images/train/250818_04.jpg"
16
-
17
- print("="*60)
18
- print("๐Ÿฆ Roboflow ๋ชจ๋ธ ํ…Œ์ŠคํŠธ + ๊ฒฐ๊ณผ ์ €์žฅ")
19
- print("="*60)
20
- print(f"๐Ÿ“ธ ์ด๋ฏธ์ง€: {test_image}\n")
21
-
22
- # ์›๋ณธ ์ด๋ฏธ์ง€ ๋กœ๋“œ
23
- image_original = Image.open(test_image)
24
- original_size = image_original.size
25
- print(f"์›๋ณธ ํฌ๊ธฐ: {original_size}")
26
-
27
- # ๋ฆฌ์‚ฌ์ด์ฆˆ (API ์ „์†ก์šฉ)
28
- image_resized = image_original.copy()
29
- image_resized.thumbnail((640, 640), Image.Resampling.LANCZOS)
30
- print(f"๋ฆฌ์‚ฌ์ด์ฆˆ: {image_resized.size}")
31
-
32
- # Base64 ์ธ์ฝ”๋”ฉ
33
- buffered = BytesIO()
34
- image_resized.save(buffered, format="JPEG", quality=80)
35
- img_base64 = base64.b64encode(buffered.getvalue()).decode()
36
-
37
- # API ํ˜ธ์ถœ
38
- print(f"\n๐Ÿ”„ API ํ˜ธ์ถœ ์ค‘...\n")
39
- response = requests.post(
40
- 'https://serverless.roboflow.com/vidraft/workflows/find-shrimp-6',
41
- headers={'Content-Type': 'application/json'},
42
- json={
43
- 'api_key': 'azcIL8KDJVJMYrsERzI7',
44
- 'inputs': {
45
- 'image': {'type': 'base64', 'value': img_base64}
46
- }
47
- },
48
- timeout=30
49
- )
50
-
51
- if response.status_code != 200:
52
- print(f"โŒ ์˜ค๋ฅ˜: {response.status_code}")
53
- exit(1)
54
-
55
- result = response.json()
56
-
57
- # predictions ์ถ”์ถœ
58
- predictions = []
59
- if 'outputs' in result and len(result['outputs']) > 0:
60
- output = result['outputs'][0]
61
- if 'predictions' in output:
62
- pred_data = output['predictions']
63
- if isinstance(pred_data, dict) and 'predictions' in pred_data:
64
- predictions = pred_data['predictions']
65
-
66
- print(f"๐Ÿ“Š ๊ฒ€์ถœ ์ˆ˜: {len(predictions)}๊ฐœ\n")
67
-
68
- # ์›๋ณธ ์ด๋ฏธ์ง€์— ๋ฐ•์Šค ๊ทธ๋ฆฌ๊ธฐ
69
- draw = ImageDraw.Draw(image_original)
70
-
71
- # ์Šค์ผ€์ผ ๊ณ„์‚ฐ (๋ฆฌ์‚ฌ์ด์ฆˆ๋œ ์ขŒํ‘œ โ†’ ์›๋ณธ ์ขŒํ‘œ)
72
- scale_x = original_size[0] / image_resized.size[0]
73
- scale_y = original_size[1] / image_resized.size[1]
74
-
75
- shrimp_count = 0
76
-
77
- for i, pred in enumerate(predictions, 1):
78
- cls = pred.get('class', 'unknown')
79
- conf = pred.get('confidence', 0)
80
- x = pred.get('x', 0) * scale_x
81
- y = pred.get('y', 0) * scale_y
82
- w = pred.get('width', 0) * scale_x
83
- h = pred.get('height', 0) * scale_y
84
-
85
- print(f"{i}. ํด๋ž˜์Šค: {cls}, ์‹ ๋ขฐ๋„: {conf:.1%}")
86
-
87
- # shrimp๋งŒ ๊ทธ๋ฆฌ๊ธฐ
88
- if cls == 'shrimp':
89
- shrimp_count += 1
90
-
91
- # ๋ฐ•์Šค ์ขŒํ‘œ
92
- x1 = x - w / 2
93
- y1 = y - h / 2
94
- x2 = x + w / 2
95
- y2 = y + h / 2
96
-
97
- # ์‹ ๋ขฐ๋„๋ณ„ ์ƒ‰์ƒ
98
- if conf >= 0.5:
99
- color = 'lime'
100
- elif conf >= 0.3:
101
- color = 'yellow'
102
- else:
103
- color = 'orange'
104
-
105
- # ๋ฐ•์Šค ๊ทธ๋ฆฌ๊ธฐ
106
- draw.rectangle([x1, y1, x2, y2], outline=color, width=8)
107
-
108
- # ํ…์ŠคํŠธ
109
- text = f"#{shrimp_count} {conf:.1%}"
110
- try:
111
- font = ImageFont.truetype("arial.ttf", 50)
112
- except:
113
- font = ImageFont.load_default()
114
-
115
- # ํ…์ŠคํŠธ ๋ฐฐ๊ฒฝ
116
- text_bbox = draw.textbbox((x1, y1-60), text, font=font)
117
- draw.rectangle(text_bbox, fill=color)
118
- draw.text((x1, y1-60), text, fill='black', font=font)
119
-
120
- # ๊ฒฐ๊ณผ ์ €์žฅ
121
- output_path = "quick_test_result.jpg"
122
- image_original.save(output_path, quality=95)
123
-
124
- print(f"\n{'='*60}")
125
- print(f"โœ… shrimp ๊ฒ€์ถœ: {shrimp_count}๊ฐœ")
126
- print(f"๐Ÿ’พ ๊ฒฐ๊ณผ ์ €์žฅ: {output_path}")
127
- print(f"{'='*60}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
shrimp_detection_app.py DELETED
@@ -1,1150 +0,0 @@
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
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
test_10_images.py DELETED
@@ -1,205 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- """
3
- Roboflow ๋ชจ๋ธ 10๊ฐœ ์ด๋ฏธ์ง€ ํ…Œ์ŠคํŠธ
4
- """
5
- import sys
6
- sys.stdout.reconfigure(encoding='utf-8')
7
-
8
- import requests
9
- import base64
10
- from PIL import Image, ImageDraw, ImageFont
11
- from io import BytesIO
12
- import json
13
- import glob
14
- import os
15
- from datetime import datetime
16
-
17
- def test_image(image_path):
18
- """๋‹จ์ผ ์ด๋ฏธ์ง€ ํ…Œ์ŠคํŠธ"""
19
- # ์›๋ณธ ์ด๋ฏธ์ง€ ๋กœ๋“œ
20
- image_original = Image.open(image_path)
21
- original_size = image_original.size
22
-
23
- # ๋ฆฌ์‚ฌ์ด์ฆˆ (API ์ „์†ก์šฉ)
24
- image_resized = image_original.copy()
25
- image_resized.thumbnail((640, 640), Image.Resampling.LANCZOS)
26
-
27
- # Base64 ์ธ์ฝ”๋”ฉ
28
- buffered = BytesIO()
29
- image_resized.save(buffered, format="JPEG", quality=80)
30
- img_base64 = base64.b64encode(buffered.getvalue()).decode()
31
-
32
- # API ํ˜ธ์ถœ
33
- response = requests.post(
34
- 'https://serverless.roboflow.com/vidraft/workflows/find-shrimp-6',
35
- headers={'Content-Type': 'application/json'},
36
- json={
37
- 'api_key': 'azcIL8KDJVJMYrsERzI7',
38
- 'inputs': {
39
- 'image': {'type': 'base64', 'value': img_base64}
40
- }
41
- },
42
- timeout=30
43
- )
44
-
45
- if response.status_code != 200:
46
- return None
47
-
48
- result = response.json()
49
-
50
- # predictions ์ถ”์ถœ
51
- predictions = []
52
- if 'outputs' in result and len(result['outputs']) > 0:
53
- output = result['outputs'][0]
54
- if 'predictions' in output:
55
- pred_data = output['predictions']
56
- if isinstance(pred_data, dict) and 'predictions' in pred_data:
57
- predictions = pred_data['predictions']
58
-
59
- # ์Šค์ผ€์ผ ๊ณ„์‚ฐ
60
- scale_x = original_size[0] / image_resized.size[0]
61
- scale_y = original_size[1] / image_resized.size[1]
62
-
63
- # shrimp๋งŒ ํ•„ํ„ฐ๋ง
64
- shrimp_predictions = [p for p in predictions if p.get('class') == 'shrimp']
65
-
66
- return {
67
- 'original': image_original,
68
- 'predictions': shrimp_predictions,
69
- 'scale_x': scale_x,
70
- 'scale_y': scale_y
71
- }
72
-
73
- def draw_result(image, predictions, scale_x, scale_y):
74
- """๊ฒฐ๊ณผ ๊ทธ๋ฆฌ๊ธฐ"""
75
- draw = ImageDraw.Draw(image)
76
-
77
- try:
78
- font = ImageFont.truetype("arial.ttf", 50)
79
- except:
80
- font = ImageFont.load_default()
81
-
82
- for i, pred in enumerate(predictions, 1):
83
- conf = pred.get('confidence', 0)
84
- x = pred.get('x', 0) * scale_x
85
- y = pred.get('y', 0) * scale_y
86
- w = pred.get('width', 0) * scale_x
87
- h = pred.get('height', 0) * scale_y
88
-
89
- # ๋ฐ•์Šค ์ขŒํ‘œ
90
- x1 = x - w / 2
91
- y1 = y - h / 2
92
- x2 = x + w / 2
93
- y2 = y + h / 2
94
-
95
- # ์‹ ๋ขฐ๋„๋ณ„ ์ƒ‰์ƒ
96
- if conf >= 0.5:
97
- color = 'lime'
98
- elif conf >= 0.3:
99
- color = 'yellow'
100
- else:
101
- color = 'orange'
102
-
103
- # ๋ฐ•์Šค ๊ทธ๋ฆฌ๊ธฐ
104
- draw.rectangle([x1, y1, x2, y2], outline=color, width=8)
105
-
106
- # ํ…์ŠคํŠธ
107
- text = f"#{i} {conf:.1%}"
108
- text_bbox = draw.textbbox((x1, y1-60), text, font=font)
109
- draw.rectangle(text_bbox, fill=color)
110
- draw.text((x1, y1-60), text, fill='black', font=font)
111
-
112
- return image
113
-
114
- def main():
115
- print("="*60)
116
- print("๐Ÿฆ Roboflow ๋ชจ๋ธ 10๊ฐœ ์ด๋ฏธ์ง€ ํ…Œ์ŠคํŠธ")
117
- print("="*60)
118
-
119
- # ํ…Œ์ŠคํŠธ ์ด๋ฏธ์ง€ ์„ ํƒ
120
- image_dir = "data/yolo_dataset/images/train"
121
- all_images = sorted(glob.glob(os.path.join(image_dir, "*.jpg")))
122
-
123
- # roboflow_result๊ฐ€ ์•„๋‹Œ ์›๋ณธ ์ด๋ฏธ์ง€๋งŒ ์„ ํƒ
124
- test_images = [img for img in all_images if 'roboflow_result' not in img][:10]
125
-
126
- if len(test_images) < 10:
127
- print(f"โš ๏ธ ์ด๋ฏธ์ง€ ๋ถ€์กฑ: {len(test_images)}๊ฐœ๋งŒ ๋ฐœ๊ฒฌ")
128
- test_images = test_images[:len(test_images)]
129
-
130
- print(f"\n๐Ÿ“ ์ด๋ฏธ์ง€ ๊ฒฝ๋กœ: {image_dir}")
131
- print(f"๐Ÿ“Š ํ…Œ์ŠคํŠธ ์ด๋ฏธ์ง€ ์ˆ˜: {len(test_images)}๊ฐœ\n")
132
-
133
- # ์ถœ๋ ฅ ๋””๋ ‰ํ† ๋ฆฌ ์ƒ์„ฑ
134
- output_dir = "test_results_10"
135
- os.makedirs(output_dir, exist_ok=True)
136
-
137
- results_summary = []
138
-
139
- for idx, img_path in enumerate(test_images, 1):
140
- img_name = os.path.basename(img_path)
141
- print(f"[{idx}/{len(test_images)}] {img_name} ์ฒ˜๋ฆฌ ์ค‘...", end=" ")
142
-
143
- try:
144
- # ํ…Œ์ŠคํŠธ
145
- result = test_image(img_path)
146
-
147
- if result is None:
148
- print("โŒ API ์˜ค๋ฅ˜")
149
- continue
150
-
151
- predictions = result['predictions']
152
- shrimp_count = len(predictions)
153
-
154
- # ๊ฒฐ๊ณผ ๊ทธ๋ฆฌ๊ธฐ
155
- image_with_boxes = draw_result(
156
- result['original'],
157
- predictions,
158
- result['scale_x'],
159
- result['scale_y']
160
- )
161
-
162
- # ์ €์žฅ
163
- output_filename = img_name.replace('.jpg', '_roboflow_result.jpg')
164
- output_path = os.path.join(output_dir, output_filename)
165
- image_with_boxes.save(output_path, quality=95)
166
-
167
- print(f"โœ… shrimp {shrimp_count}๊ฐœ")
168
-
169
- results_summary.append({
170
- 'image': img_name,
171
- 'shrimp_count': shrimp_count,
172
- 'output': output_path,
173
- 'confidences': [p.get('confidence', 0) for p in predictions]
174
- })
175
-
176
- except Exception as e:
177
- print(f"โŒ ์˜ค๋ฅ˜: {str(e)}")
178
-
179
- # ์š”์•ฝ
180
- print(f"\n{'='*60}")
181
- print("๐Ÿ“Š ํ…Œ์ŠคํŠธ ์š”์•ฝ")
182
- print(f"{'='*60}\n")
183
-
184
- total_shrimp = sum(r['shrimp_count'] for r in results_summary)
185
- avg_shrimp = total_shrimp / len(results_summary) if results_summary else 0
186
-
187
- print(f"์ด ์ฒ˜๋ฆฌ ์ด๋ฏธ์ง€: {len(results_summary)}๊ฐœ")
188
- print(f"์ด shrimp ๊ฒ€์ถœ: {total_shrimp}๊ฐœ")
189
- print(f"ํ‰๊ท : {avg_shrimp:.1f}๊ฐœ/์ด๋ฏธ์ง€\n")
190
-
191
- print("์ด๋ฏธ์ง€๋ณ„ ๊ฒฐ๊ณผ:")
192
- for r in results_summary:
193
- avg_conf = sum(r['confidences']) / len(r['confidences']) if r['confidences'] else 0
194
- print(f" {r['image']}: {r['shrimp_count']}๊ฐœ (ํ‰๊ท  ์‹ ๋ขฐ๋„: {avg_conf:.1%})")
195
-
196
- print(f"\nโœ… ์™„๋ฃŒ! ๊ฒฐ๊ณผ๋Š” {output_dir}/ ํด๋”์— ์ €์žฅ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.")
197
-
198
- # JSON ์ €์žฅ
199
- json_path = os.path.join(output_dir, f"test_results_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json")
200
- with open(json_path, 'w', encoding='utf-8') as f:
201
- json.dump(results_summary, f, indent=2, ensure_ascii=False)
202
- print(f"๐Ÿ“„ JSON ์ €์žฅ: {json_path}")
203
-
204
- if __name__ == "__main__":
205
- main()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
test_curl_roboflow.py DELETED
@@ -1,82 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- """
3
- ๊ฐ„๋‹จํ•œ Roboflow API ํ…Œ์ŠคํŠธ (requests ์‚ฌ์šฉ)
4
- """
5
- import sys
6
- sys.stdout.reconfigure(encoding='utf-8')
7
-
8
- import requests
9
- import base64
10
- from PIL import Image
11
- from io import BytesIO
12
- import json
13
-
14
- # ํ…Œ์ŠคํŠธ ์ด๋ฏธ์ง€
15
- test_image = "data/yolo_dataset/images/train/250818_01.jpg"
16
-
17
- print("="*60)
18
- print("๐Ÿ” Roboflow API ํ…Œ์ŠคํŠธ (requests)")
19
- print("="*60)
20
-
21
- # ์ด๋ฏธ์ง€๋ฅผ base64๋กœ ์ธ์ฝ”๋”ฉ
22
- image = Image.open(test_image)
23
- print(f"๐Ÿ“ธ ์ด๋ฏธ์ง€: {test_image}")
24
- print(f"๐Ÿ–ผ๏ธ ํฌ๊ธฐ: {image.size}")
25
-
26
- # ๋ฆฌ์‚ฌ์ด์ฆˆ
27
- image.thumbnail((640, 640), Image.Resampling.LANCZOS)
28
- print(f"๐Ÿ“ ๋ฆฌ์‚ฌ์ด์ฆˆ: {image.size}")
29
-
30
- buffered = BytesIO()
31
- image.save(buffered, format="JPEG", quality=80)
32
- img_base64 = base64.b64encode(buffered.getvalue()).decode()
33
-
34
- print(f"๐Ÿ“ฆ Base64 ํฌ๊ธฐ: {len(img_base64)} bytes")
35
-
36
- # API ์š”์ฒญ
37
- print(f"\n๐Ÿ”„ API ํ˜ธ์ถœ ์ค‘...")
38
-
39
- try:
40
- response = requests.post(
41
- 'https://serverless.roboflow.com/vidraft/workflows/find-shrimp-6',
42
- headers={'Content-Type': 'application/json'},
43
- json={
44
- 'api_key': 'azcIL8KDJVJMYrsERzI7',
45
- 'inputs': {
46
- 'image': {'type': 'base64', 'value': img_base64}
47
- }
48
- },
49
- timeout=30
50
- )
51
-
52
- print(f"๐Ÿ“ก ์‘๋‹ต ์ฝ”๋“œ: {response.status_code}")
53
-
54
- if response.status_code == 200:
55
- result = response.json()
56
- print(f"\n๐Ÿ“ฆ ์‘๋‹ต ๊ตฌ์กฐ:")
57
- print(json.dumps(result, indent=2, ensure_ascii=False)[:2000])
58
-
59
- # predictions ์ถ”์ถœ ์‹œ๋„
60
- if 'outputs' in result:
61
- print(f"\nโœ… outputs ๋ฐœ๊ฒฌ: {len(result['outputs'])}๊ฐœ")
62
- if len(result['outputs']) > 0:
63
- output = result['outputs'][0]
64
- print(f"๐Ÿ“ฆ output[0] keys: {output.keys()}")
65
- if 'predictions' in output:
66
- pred_data = output['predictions']
67
- print(f"๐Ÿ“ฆ predictions type: {type(pred_data)}")
68
- if isinstance(pred_data, dict):
69
- print(f"๐Ÿ“ฆ predictions keys: {pred_data.keys()}")
70
- if 'predictions' in pred_data:
71
- preds = pred_data['predictions']
72
- print(f"โœ… ์ตœ์ข… predictions: {len(preds)}๊ฐœ")
73
- else:
74
- print(f"โŒ ์˜ค๋ฅ˜: {response.text}")
75
-
76
- except Exception as e:
77
- print(f"โŒ ์˜ˆ์™ธ ๋ฐœ์ƒ: {str(e)}")
78
- import traceback
79
- traceback.print_exc()
80
-
81
- print(f"\n{'='*60}")
82
- print("โœ… ํ…Œ์ŠคํŠธ ์™„๋ฃŒ")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
test_parameter_sweep.py DELETED
@@ -1,219 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- """
3
- ํŒŒ๋ผ๋ฏธํ„ฐ ๊ทธ๋ฆฌ๋“œ ์„œ์น˜
4
- ์ตœ์ ์˜ confidence threshold์™€ filter threshold ์กฐํ•ฉ ์ฐพ๊ธฐ
5
- """
6
- import sys
7
- sys.stdout.reconfigure(encoding='utf-8')
8
-
9
- import os
10
- import json
11
- import numpy as np
12
- from datetime import datetime
13
- import matplotlib.pyplot as plt
14
- import seaborn as sns
15
- from test_quantitative_evaluation import (
16
- load_ground_truth,
17
- load_rtdetr_model,
18
- detect_with_rtdetr,
19
- apply_universal_filter,
20
- evaluate_detection
21
- )
22
- from PIL import Image
23
-
24
- def run_parameter_sweep(
25
- test_image_dir,
26
- ground_truth_path,
27
- confidence_values,
28
- filter_values,
29
- iou_threshold=0.5
30
- ):
31
- """ํŒŒ๋ผ๋ฏธํ„ฐ ๊ทธ๋ฆฌ๋“œ ์„œ์น˜ ์‹คํ–‰"""
32
- print("\n" + "="*70)
33
- print("๐Ÿ” ํŒŒ๋ผ๋ฏธํ„ฐ ๊ทธ๋ฆฌ๋“œ ์„œ์น˜ ์‹œ์ž‘")
34
- print("="*70)
35
-
36
- # Ground truth ๋กœ๋“œ
37
- ground_truths = load_ground_truth(ground_truth_path)
38
- if not ground_truths:
39
- return
40
-
41
- # ๋ชจ๋ธ ๋กœ๋“œ (ํ•œ ๋ฒˆ๋งŒ)
42
- processor, model = load_rtdetr_model()
43
-
44
- # ๊ฒฐ๊ณผ ์ €์žฅ
45
- results_grid = {}
46
- best_f1 = 0
47
- best_config = None
48
-
49
- print(f"\n๐Ÿ“Š ํ…Œ์ŠคํŠธ ๋ฒ”์œ„:")
50
- print(f" Confidence: {confidence_values}")
51
- print(f" Filter Threshold: {filter_values}")
52
- print(f" ์ด {len(confidence_values) * len(filter_values)}๊ฐœ ์กฐํ•ฉ ํ…Œ์ŠคํŠธ\n")
53
-
54
- total_tests = len(confidence_values) * len(filter_values)
55
- current_test = 0
56
-
57
- # ๊ทธ๋ฆฌ๋“œ ์„œ์น˜
58
- for conf in confidence_values:
59
- results_grid[conf] = {}
60
-
61
- for filt in filter_values:
62
- current_test += 1
63
- print(f"\n[{current_test}/{total_tests}] ํ…Œ์ŠคํŠธ ์ค‘: Conf={conf}, Filter={filt}")
64
-
65
- metrics_list = []
66
-
67
- for filename, gt_list in ground_truths.items():
68
- # ์ด๋ฏธ์ง€ ๊ฒฝ๋กœ ๊ตฌ์„ฑ
69
- if gt_list and 'folder' in gt_list[0]:
70
- folder = gt_list[0]['folder']
71
- img_path = os.path.join(test_image_dir, folder, filename)
72
- else:
73
- img_path = os.path.join(test_image_dir, filename)
74
-
75
- if not os.path.exists(img_path):
76
- continue
77
-
78
- # ๊ฒ€์ถœ
79
- image = Image.open(img_path).convert('RGB')
80
- all_detections = detect_with_rtdetr(image, processor, model, conf)
81
- filtered_detections = apply_universal_filter(all_detections, image, filt)
82
-
83
- # ํ‰๊ฐ€
84
- metrics = evaluate_detection(filtered_detections, gt_list, iou_threshold)
85
- metrics_list.append(metrics)
86
-
87
- # ํ‰๊ท  ๊ณ„์‚ฐ
88
- if metrics_list:
89
- avg_precision = np.mean([m['precision'] for m in metrics_list])
90
- avg_recall = np.mean([m['recall'] for m in metrics_list])
91
- avg_f1 = np.mean([m['f1'] for m in metrics_list])
92
-
93
- results_grid[conf][filt] = {
94
- 'precision': avg_precision,
95
- 'recall': avg_recall,
96
- 'f1': avg_f1
97
- }
98
-
99
- print(f" โ†’ P={avg_precision:.2%}, R={avg_recall:.2%}, F1={avg_f1:.2%}")
100
-
101
- # ์ตœ๊ณ  ์„ฑ๋Šฅ ์—…๋ฐ์ดํŠธ
102
- if avg_f1 > best_f1:
103
- best_f1 = avg_f1
104
- best_config = {
105
- 'confidence': conf,
106
- 'filter_threshold': filt,
107
- 'metrics': {
108
- 'precision': avg_precision,
109
- 'recall': avg_recall,
110
- 'f1': avg_f1
111
- }
112
- }
113
-
114
- # ๊ฒฐ๊ณผ ์ €์žฅ
115
- output_dir = f"test_results/parameter_sweep_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
116
- os.makedirs(output_dir, exist_ok=True)
117
-
118
- # JSON ์ €์žฅ
119
- summary = {
120
- 'test_range': {
121
- 'confidence': confidence_values,
122
- 'filter_threshold': filter_values,
123
- 'iou_threshold': iou_threshold
124
- },
125
- 'best_config': best_config,
126
- 'all_results': results_grid
127
- }
128
-
129
- json_path = os.path.join(output_dir, 'sweep_results.json')
130
- with open(json_path, 'w', encoding='utf-8') as f:
131
- json.dump(summary, f, ensure_ascii=False, indent=2)
132
-
133
- print("\n" + "="*70)
134
- print("๐Ÿ† ์ตœ๊ณ  ์„ฑ๋Šฅ ์„ค์ •")
135
- print("="*70)
136
- print(f"Confidence Threshold: {best_config['confidence']}")
137
- print(f"Filter Threshold: {best_config['filter_threshold']}")
138
- print(f"Precision: {best_config['metrics']['precision']:.2%}")
139
- print(f"Recall: {best_config['metrics']['recall']:.2%}")
140
- print(f"F1 Score: {best_config['metrics']['f1']:.2%}")
141
- print("="*70)
142
-
143
- # ํžˆํŠธ๋งต ์ƒ์„ฑ
144
- generate_heatmaps(results_grid, confidence_values, filter_values, output_dir)
145
-
146
- print(f"\n๐Ÿ“„ ๊ฒฐ๊ณผ ์ €์žฅ: {json_path}")
147
- print(f"๐Ÿ“Š ํžˆํŠธ๋งต ์ €์žฅ: {output_dir}")
148
-
149
- return best_config, results_grid
150
-
151
-
152
- def generate_heatmaps(results_grid, conf_values, filt_values, output_dir):
153
- """์„ฑ๋Šฅ ํžˆํŠธ๏ฟฝ๏ฟฝ๏ฟฝ ์ƒ์„ฑ"""
154
- metrics = ['precision', 'recall', 'f1']
155
- metric_names = {
156
- 'precision': 'Precision (์ •๋ฐ€๋„)',
157
- 'recall': 'Recall (์žฌํ˜„์œจ)',
158
- 'f1': 'F1 Score'
159
- }
160
-
161
- for metric in metrics:
162
- # ๋ฐ์ดํ„ฐ ํ–‰๋ ฌ ์ƒ์„ฑ
163
- data = np.zeros((len(conf_values), len(filt_values)))
164
-
165
- for i, conf in enumerate(conf_values):
166
- for j, filt in enumerate(filt_values):
167
- if conf in results_grid and filt in results_grid[conf]:
168
- data[i, j] = results_grid[conf][filt][metric]
169
-
170
- # ํžˆํŠธ๋งต ๊ทธ๋ฆฌ๊ธฐ
171
- plt.figure(figsize=(12, 8))
172
- sns.heatmap(
173
- data,
174
- annot=True,
175
- fmt='.2%',
176
- cmap='RdYlGn',
177
- xticklabels=filt_values,
178
- yticklabels=conf_values,
179
- vmin=0,
180
- vmax=1,
181
- cbar_kws={'label': metric_names[metric]}
182
- )
183
- plt.xlabel('Filter Threshold', fontsize=12)
184
- plt.ylabel('Confidence Threshold', fontsize=12)
185
- plt.title(f'{metric_names[metric]} Heatmap', fontsize=14, fontweight='bold')
186
- plt.tight_layout()
187
-
188
- output_path = os.path.join(output_dir, f'heatmap_{metric}.png')
189
- plt.savefig(output_path, dpi=150)
190
- plt.close()
191
- print(f" ๐Ÿ“Š {metric_names[metric]} ํžˆํŠธ๋งต ์ €์žฅ: {output_path}")
192
-
193
-
194
- if __name__ == "__main__":
195
- # ํ…Œ์ŠคํŠธ ๋ฒ”์œ„ ์„ค์ •
196
- TEST_DIR = r"data\ํฐ๋‹ค๋ฆฌ์ƒˆ์šฐ ์‹ค์ธก ๋ฐ์ดํ„ฐ_์ตํˆฌ์Šค์—์ด์•„์ด(์ฃผ)"
197
- GT_PATH = "ground_truth.json"
198
-
199
- # ํŒŒ๋ผ๋ฏธํ„ฐ ๋ฒ”์œ„
200
- CONFIDENCE_VALUES = [0.3, 0.35, 0.4, 0.45, 0.5]
201
- FILTER_VALUES = [30, 40, 50, 60, 70]
202
-
203
- if not os.path.exists(GT_PATH):
204
- print("โš ๏ธ ground_truth.json ํŒŒ์ผ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.")
205
- else:
206
- best_config, all_results = run_parameter_sweep(
207
- test_image_dir=TEST_DIR,
208
- ground_truth_path=GT_PATH,
209
- confidence_values=CONFIDENCE_VALUES,
210
- filter_values=FILTER_VALUES,
211
- iou_threshold=0.5
212
- )
213
-
214
- print("\n๐Ÿ’ก ๋‹ค์Œ ๋‹จ๊ณ„:")
215
- print(f" 1. test_visual_validation.py ์˜ ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ์—…๋ฐ์ดํŠธ:")
216
- print(f" - confidence_threshold = {best_config['confidence']}")
217
- print(f" - filter_threshold = {best_config['filter_threshold']}")
218
- print(f" 2. ์—…๋ฐ์ดํŠธ ํ›„ ์žฌํ‰๊ฐ€ ์‹คํ–‰:")
219
- print(f" python test_quantitative_evaluation.py")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
test_roboflow_model.py DELETED
@@ -1,177 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- """
3
- Roboflow ๋ชจ๋ธ (find-shrimp-6) ํ…Œ์ŠคํŠธ
4
- """
5
- import sys
6
- sys.stdout.reconfigure(encoding='utf-8')
7
-
8
- from inference_sdk import InferenceHTTPClient
9
- from PIL import Image, ImageDraw, ImageFont
10
- import os
11
- import glob
12
- import time
13
-
14
- # Roboflow ํด๋ผ์ด์–ธํŠธ ์ดˆ๊ธฐํ™”
15
- client = InferenceHTTPClient(
16
- api_url="https://serverless.roboflow.com",
17
- api_key="azcIL8KDJVJMYrsERzI7"
18
- )
19
-
20
- def test_roboflow_detection(image_path):
21
- """๋‹จ์ผ ์ด๋ฏธ์ง€ ํ…Œ์ŠคํŠธ"""
22
- print(f"\n{'='*60}")
23
- print(f"๐Ÿ“ธ ํ…Œ์ŠคํŠธ ์ด๋ฏธ์ง€: {os.path.basename(image_path)}")
24
- print(f"{'='*60}")
25
-
26
- # ์ด๋ฏธ์ง€ ํฌ๊ธฐ ํ™•์ธ
27
- image = Image.open(image_path)
28
- print(f"๐Ÿ–ผ๏ธ ์›๋ณธ ํฌ๊ธฐ: {image.size}")
29
-
30
- # Roboflow API ํ˜ธ์ถœ
31
- start_time = time.time()
32
-
33
- result = client.run_workflow(
34
- workspace_name="vidraft",
35
- workflow_id="find-shrimp-6",
36
- images={"image": image_path},
37
- use_cache=True
38
- )
39
-
40
- elapsed = time.time() - start_time
41
- print(f"โฑ๏ธ ์ถ”๋ก  ์‹œ๊ฐ„: {elapsed:.2f}์ดˆ")
42
-
43
- # ๊ฒฐ๊ณผ ํŒŒ์‹ฑ
44
- predictions = []
45
-
46
- if isinstance(result, dict) and 'outputs' in result and len(result['outputs']) > 0:
47
- output = result['outputs'][0]
48
- if isinstance(output, dict) and 'predictions' in output:
49
- pred_data = output['predictions']
50
- if isinstance(pred_data, dict) and 'predictions' in pred_data:
51
- predictions = pred_data['predictions']
52
- elif isinstance(pred_data, list):
53
- predictions = pred_data
54
-
55
- print(f"๐Ÿ“ฆ ๊ฒ€์ถœ๋œ ๊ฐ์ฒด ์ˆ˜: {len(predictions)}๊ฐœ")
56
-
57
- # ์‹ ๋ขฐ๋„๋ณ„ ํ†ต๊ณ„
58
- if predictions:
59
- confidences = [pred.get('confidence', 0) for pred in predictions]
60
- print(f"๐Ÿ“Š ์‹ ๋ขฐ๋„ ํ†ต๊ณ„:")
61
- print(f" - ์ตœ๊ณ : {max(confidences):.1%}")
62
- print(f" - ์ตœ์ €: {min(confidences):.1%}")
63
- print(f" - ํ‰๊ท : {sum(confidences)/len(confidences):.1%}")
64
-
65
- # ์‹ ๋ขฐ๋„๋ณ„ ๊ฐœ์ˆ˜
66
- high_conf = sum(1 for c in confidences if c >= 0.5)
67
- mid_conf = sum(1 for c in confidences if 0.2 <= c < 0.5)
68
- low_conf = sum(1 for c in confidences if c < 0.2)
69
-
70
- print(f"\n - ๊ณ ์‹ ๋ขฐ๋„ (โ‰ฅ50%): {high_conf}๊ฐœ")
71
- print(f" - ์ค‘์‹ ๋ขฐ๋„ (20-50%): {mid_conf}๊ฐœ")
72
- print(f" - ์ €์‹ ๋ขฐ๋„ (<20%): {low_conf}๊ฐœ")
73
-
74
- # ์ƒ์œ„ 5๊ฐœ ์ถœ๋ ฅ
75
- print(f"\n๐Ÿ” ์ƒ์œ„ 5๊ฐœ ๊ฒ€์ถœ ๊ฒฐ๊ณผ:")
76
- sorted_preds = sorted(predictions, key=lambda x: x.get('confidence', 0), reverse=True)
77
- for i, pred in enumerate(sorted_preds[:5], 1):
78
- conf = pred.get('confidence', 0)
79
- x = pred.get('x', 0)
80
- y = pred.get('y', 0)
81
- w = pred.get('width', 0)
82
- h = pred.get('height', 0)
83
- print(f" {i}. ์‹ ๋ขฐ๋„: {conf:.1%}, ์œ„์น˜: ({x:.0f}, {y:.0f}), ํฌ๊ธฐ: {w:.0f}x{h:.0f}")
84
- else:
85
- print("โš ๏ธ ๊ฒ€์ถœ๋œ ๊ฐ์ฒด๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค!")
86
-
87
- # ์‹œ๊ฐํ™”
88
- output_path = image_path.replace('.jpg', '_roboflow_result.jpg')
89
- visualize_result(image_path, predictions, output_path)
90
- print(f"๐Ÿ’พ ๊ฒฐ๊ณผ ์ €์žฅ: {output_path}")
91
-
92
- return predictions
93
-
94
- def visualize_result(image_path, predictions, output_path):
95
- """๊ฒฐ๊ณผ ์‹œ๊ฐํ™”"""
96
- image = Image.open(image_path)
97
- draw = ImageDraw.Draw(image)
98
-
99
- for pred in predictions:
100
- conf = pred.get('confidence', 0)
101
- x = pred.get('x', 0)
102
- y = pred.get('y', 0)
103
- w = pred.get('width', 0)
104
- h = pred.get('height', 0)
105
-
106
- # ๋ฐ•์Šค ์ขŒํ‘œ
107
- x1 = x - w / 2
108
- y1 = y - h / 2
109
- x2 = x + w / 2
110
- y2 = y + h / 2
111
-
112
- # ์‹ ๋ขฐ๋„์— ๋”ฐ๋ฅธ ์ƒ‰์ƒ
113
- if conf >= 0.5:
114
- color = 'green'
115
- elif conf >= 0.2:
116
- color = 'yellow'
117
- else:
118
- color = 'red'
119
-
120
- # ๋ฐ•์Šค ๊ทธ๋ฆฌ๊ธฐ
121
- draw.rectangle([x1, y1, x2, y2], outline=color, width=3)
122
-
123
- # ์‹ ๋ขฐ๋„ ํ…์ŠคํŠธ
124
- text = f"{conf:.0%}"
125
- draw.text((x1, y1-15), text, fill=color)
126
-
127
- image.save(output_path, quality=95)
128
-
129
- def main():
130
- print("="*60)
131
- print("๐Ÿฆ Roboflow ๋ชจ๋ธ (find-shrimp-6) ํ…Œ์ŠคํŠธ")
132
- print("="*60)
133
-
134
- # ํ…Œ์ŠคํŠธ ์ด๋ฏธ์ง€ ์„ ํƒ (YOLO ๋ฐ์ดํ„ฐ์…‹์—์„œ 5๊ฐœ)
135
- image_dir = "data/yolo_dataset/images/train"
136
- test_images = sorted(glob.glob(os.path.join(image_dir, "*.jpg")))[:5]
137
-
138
- if not test_images:
139
- print("โŒ ํ…Œ์ŠคํŠธ ์ด๋ฏธ์ง€๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค!")
140
- return
141
-
142
- print(f"\n๐Ÿ“ ํ…Œ์ŠคํŠธ ์ด๋ฏธ์ง€ ๊ฒฝ๋กœ: {image_dir}")
143
- print(f"๐Ÿ“Š ํ…Œ์ŠคํŠธ ์ด๋ฏธ์ง€ ์ˆ˜: {len(test_images)}๊ฐœ\n")
144
-
145
- all_results = []
146
-
147
- for img_path in test_images:
148
- try:
149
- predictions = test_roboflow_detection(img_path)
150
- all_results.append({
151
- 'image': os.path.basename(img_path),
152
- 'count': len(predictions),
153
- 'predictions': predictions
154
- })
155
- except Exception as e:
156
- print(f"โŒ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: {str(e)}")
157
- import traceback
158
- traceback.print_exc()
159
-
160
- # ์ „์ฒด ์š”์•ฝ
161
- print(f"\n{'='*60}")
162
- print("๐Ÿ“Š ์ „์ฒด ํ…Œ์ŠคํŠธ ์š”์•ฝ")
163
- print(f"{'='*60}")
164
-
165
- total_detections = sum(r['count'] for r in all_results)
166
- print(f"\n์ด ๊ฒ€์ถœ ์ˆ˜: {total_detections}๊ฐœ")
167
- print(f"์ด๋ฏธ์ง€๋‹น ํ‰๊ท : {total_detections/len(all_results):.1f}๊ฐœ")
168
-
169
- print(f"\n์ด๋ฏธ์ง€๋ณ„ ๊ฒ€์ถœ ์ˆ˜:")
170
- for r in all_results:
171
- print(f" - {r['image']}: {r['count']}๊ฐœ")
172
-
173
- print(f"\nโœ… ํ…Œ์ŠคํŠธ ์™„๋ฃŒ!")
174
- print(f"๐Ÿ“ ๊ฒฐ๊ณผ ์ด๋ฏธ์ง€: {image_dir}/*_roboflow_result.jpg")
175
-
176
- if __name__ == "__main__":
177
- main()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
test_roboflow_save_results.py DELETED
@@ -1,183 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- """
3
- Roboflow ๋ชจ๋ธ ํ…Œ์ŠคํŠธ ๋ฐ ๊ฒฐ๊ณผ ์ด๋ฏธ์ง€ ์ €์žฅ
4
- """
5
- import sys
6
- sys.stdout.reconfigure(encoding='utf-8')
7
-
8
- import requests
9
- import base64
10
- from PIL import Image, ImageDraw, ImageFont
11
- from io import BytesIO
12
- import json
13
- import os
14
- import glob
15
-
16
- def test_and_save_result(image_path, output_dir="test_results"):
17
- """์ด๋ฏธ์ง€ ํ…Œ์ŠคํŠธ ํ›„ ๊ฒฐ๊ณผ ์ €์žฅ"""
18
-
19
- # ์ถœ๋ ฅ ๋””๋ ‰ํ† ๋ฆฌ ์ƒ์„ฑ
20
- os.makedirs(output_dir, exist_ok=True)
21
-
22
- print(f"\n{'='*60}")
23
- print(f"๐Ÿ“ธ ํ…Œ์ŠคํŠธ: {os.path.basename(image_path)}")
24
- print(f"{'='*60}")
25
-
26
- # ์ด๋ฏธ์ง€ ๋กœ๋“œ
27
- image = Image.open(image_path)
28
- original_size = image.size
29
- print(f"๐Ÿ–ผ๏ธ ์›๋ณธ ํฌ๊ธฐ: {original_size}")
30
-
31
- # ๋ฆฌ์‚ฌ์ด์ฆˆ
32
- max_size = 640
33
- if image.width > max_size or image.height > max_size:
34
- image_resized = image.copy()
35
- image_resized.thumbnail((max_size, max_size), Image.Resampling.LANCZOS)
36
- print(f"๐Ÿ“ ๋ฆฌ์‚ฌ์ด์ฆˆ: {image_resized.size}")
37
- else:
38
- image_resized = image
39
-
40
- # Base64 ์ธ์ฝ”๋”ฉ
41
- buffered = BytesIO()
42
- image_resized.save(buffered, format="JPEG", quality=80)
43
- img_base64 = base64.b64encode(buffered.getvalue()).decode()
44
- print(f"๐Ÿ“ฆ Base64 ํฌ๊ธฐ: {len(img_base64)} bytes")
45
-
46
- # API ํ˜ธ์ถœ
47
- print(f"๐Ÿ”„ Roboflow API ํ˜ธ์ถœ ์ค‘...")
48
- response = requests.post(
49
- 'https://serverless.roboflow.com/vidraft/workflows/find-shrimp-6',
50
- headers={'Content-Type': 'application/json'},
51
- json={
52
- 'api_key': 'azcIL8KDJVJMYrsERzI7',
53
- 'inputs': {
54
- 'image': {'type': 'base64', 'value': img_base64}
55
- }
56
- },
57
- timeout=30
58
- )
59
-
60
- if response.status_code != 200:
61
- print(f"โŒ API ์˜ค๋ฅ˜: {response.status_code}")
62
- return None
63
-
64
- result = response.json()
65
-
66
- # predictions ์ถ”์ถœ
67
- predictions = []
68
- if 'outputs' in result and len(result['outputs']) > 0:
69
- output = result['outputs'][0]
70
- if 'predictions' in output:
71
- pred_data = output['predictions']
72
- if isinstance(pred_data, dict) and 'predictions' in pred_data:
73
- predictions = pred_data['predictions']
74
-
75
- print(f"๐Ÿ“ฆ ๊ฒ€์ถœ ์ˆ˜: {len(predictions)}๊ฐœ")
76
-
77
- # ์›๋ณธ ์ด๋ฏธ์ง€์— ๋ฐ•์Šค ๊ทธ๋ฆฌ๊ธฐ
78
- draw = ImageDraw.Draw(image)
79
-
80
- # ๋ฆฌ์‚ฌ์ด์ฆˆ ๋น„์œจ ๊ณ„์‚ฐ (์›๋ณธ ํฌ๊ธฐ๋กœ ๋ณต์›)
81
- scale_x = original_size[0] / image_resized.size[0]
82
- scale_y = original_size[1] / image_resized.size[1]
83
-
84
- for i, pred in enumerate(predictions, 1):
85
- conf = pred.get('confidence', 0)
86
- x = pred.get('x', 0) * scale_x
87
- y = pred.get('y', 0) * scale_y
88
- w = pred.get('width', 0) * scale_x
89
- h = pred.get('height', 0) * scale_y
90
-
91
- # ๋ฐ•์Šค ์ขŒํ‘œ
92
- x1 = x - w / 2
93
- y1 = y - h / 2
94
- x2 = x + w / 2
95
- y2 = y + h / 2
96
-
97
- # ์‹ ๋ขฐ๋„๋ณ„ ์ƒ‰์ƒ
98
- if conf >= 0.5:
99
- color = 'lime'
100
- thickness = 5
101
- elif conf >= 0.3:
102
- color = 'yellow'
103
- thickness = 4
104
- else:
105
- color = 'red'
106
- thickness = 3
107
-
108
- # ๋ฐ•์Šค ๊ทธ๋ฆฌ๊ธฐ
109
- draw.rectangle([x1, y1, x2, y2], outline=color, width=thickness)
110
-
111
- # ์‹ ๋ขฐ๋„ ํ…์ŠคํŠธ
112
- text = f"#{i} {conf:.1%}"
113
-
114
- # ํ…์ŠคํŠธ ๋ฐฐ๊ฒฝ
115
- try:
116
- font = ImageFont.truetype("arial.ttf", 40)
117
- except:
118
- font = ImageFont.load_default()
119
-
120
- # ํ…์ŠคํŠธ ์œ„์น˜
121
- text_bbox = draw.textbbox((x1, y1-50), text, font=font)
122
- draw.rectangle(text_bbox, fill=color)
123
- draw.text((x1, y1-50), text, fill='black', font=font)
124
-
125
- print(f" {i}. ์‹ ๋ขฐ๋„: {conf:.1%}, ์œ„์น˜: ({x:.0f}, {y:.0f}), ํฌ๊ธฐ: {w:.0f}x{h:.0f}")
126
-
127
- # ๊ฒฐ๊ณผ ์ €์žฅ
128
- output_filename = os.path.basename(image_path).replace('.jpg', '_result.jpg')
129
- output_path = os.path.join(output_dir, output_filename)
130
- image.save(output_path, quality=95)
131
- print(f"๐Ÿ’พ ์ €์žฅ: {output_path}")
132
-
133
- return {
134
- 'image': os.path.basename(image_path),
135
- 'detections': len(predictions),
136
- 'output': output_path
137
- }
138
-
139
- def main():
140
- print("="*60)
141
- print("๐Ÿฆ Roboflow ๋ชจ๋ธ ํ…Œ์ŠคํŠธ ๋ฐ ๊ฒฐ๊ณผ ์ €์žฅ")
142
- print("="*60)
143
-
144
- # YOLO ๋ฐ์ดํ„ฐ์…‹์—์„œ 5๊ฐœ ์ด๋ฏธ์ง€ ์„ ํƒ
145
- image_dir = "data/yolo_dataset/images/train"
146
- test_images = sorted(glob.glob(os.path.join(image_dir, "*.jpg")))[:5]
147
-
148
- if not test_images:
149
- print("โŒ ํ…Œ์ŠคํŠธ ์ด๋ฏธ์ง€๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค!")
150
- return
151
-
152
- print(f"\n๐Ÿ“ ์ด๋ฏธ์ง€ ๊ฒฝ๋กœ: {image_dir}")
153
- print(f"๐Ÿ“Š ํ…Œ์ŠคํŠธ ์ˆ˜: {len(test_images)}๊ฐœ\n")
154
-
155
- results = []
156
-
157
- for img_path in test_images:
158
- try:
159
- result = test_and_save_result(img_path)
160
- if result:
161
- results.append(result)
162
- except Exception as e:
163
- print(f"โŒ ์˜ค๋ฅ˜: {str(e)}")
164
- import traceback
165
- traceback.print_exc()
166
-
167
- # ์š”์•ฝ
168
- print(f"\n{'='*60}")
169
- print("๐Ÿ“Š ํ…Œ์ŠคํŠธ ์š”์•ฝ")
170
- print(f"{'='*60}")
171
-
172
- total_detections = sum(r['detections'] for r in results)
173
- print(f"\n์ด ๊ฒ€์ถœ ์ˆ˜: {total_detections}๊ฐœ")
174
- print(f"ํ‰๊ท : {total_detections/len(results):.1f}๊ฐœ/์ด๋ฏธ์ง€")
175
-
176
- print(f"\n์ด๋ฏธ์ง€๋ณ„ ๊ฒฐ๊ณผ:")
177
- for r in results:
178
- print(f" - {r['image']}: {r['detections']}๊ฐœ โ†’ {r['output']}")
179
-
180
- print(f"\nโœ… ์™„๋ฃŒ! ๊ฒฐ๊ณผ๋Š” test_results/ ํด๋”์— ์ €์žฅ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.")
181
-
182
- if __name__ == "__main__":
183
- main()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
test_yolo_with_filter.py DELETED
@@ -1,336 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- """
3
- YOLOv8 + Universal Filter ๊ฒฐํ•ฉ
4
- ์ฆ‰์‹œ ๊ฐœ์„ : YOLOv8์˜ ๋†’์€ Recall + Filter์˜ ๋†’์€ Precision
5
- """
6
- import sys
7
- sys.stdout.reconfigure(encoding='utf-8')
8
-
9
- from ultralytics import YOLO
10
- import json
11
- import os
12
- from PIL import Image
13
- import numpy as np
14
- from pathlib import Path
15
- from test_visual_validation import apply_universal_filter
16
-
17
- def calculate_iou(box1, box2):
18
- """IoU ๊ณ„์‚ฐ"""
19
- x1_1, y1_1, x2_1, y2_1 = box1
20
- x1_2, y1_2, x2_2, y2_2 = box2
21
-
22
- x1_i = max(x1_1, x1_2)
23
- y1_i = max(y1_1, y1_2)
24
- x2_i = min(x2_1, x2_2)
25
- y2_i = min(y2_1, y2_2)
26
-
27
- if x2_i < x1_i or y2_i < y1_i:
28
- return 0.0
29
-
30
- intersection = (x2_i - x1_i) * (y2_i - y1_i)
31
- area1 = (x2_1 - x1_1) * (y2_1 - y1_1)
32
- area2 = (x2_2 - x1_2) * (y2_2 - y1_2)
33
- union = area1 + area2 - intersection
34
-
35
- return intersection / union if union > 0 else 0.0
36
-
37
- def yolo_with_filter_evaluate(model_path, gt_file, data_base_dir,
38
- yolo_conf=0.01, filter_threshold=90, iou_threshold=0.5):
39
- """YOLOv8 + Universal Filter ํ‰๊ฐ€"""
40
-
41
- print(f"\n๐Ÿ“Š YOLOv8 + Universal Filter ํ‰๊ฐ€")
42
- print(f" - YOLOv8 Confidence: {yolo_conf}")
43
- print(f" - Filter Threshold: {filter_threshold}")
44
- print(f" - IoU Threshold: {iou_threshold}")
45
-
46
- # ๋ชจ๋ธ ๋กœ๋“œ
47
- model = YOLO(model_path)
48
- print(f"โœ… YOLOv8 ๋ชจ๋ธ ๋กœ๋“œ ์™„๋ฃŒ")
49
-
50
- # GT ๋กœ๋“œ
51
- with open(gt_file, 'r', encoding='utf-8') as f:
52
- gt_data = json.load(f)
53
-
54
- # ํ†ต๊ณ„
55
- total_gt = 0
56
- total_yolo_pred = 0
57
- total_filtered_pred = 0
58
-
59
- true_positives = 0
60
- false_positives = 0
61
- false_negatives = 0
62
-
63
- yolo_only_tp = 0
64
- yolo_only_fp = 0
65
-
66
- results_detail = []
67
-
68
- # ๊ฐ ์ด๋ฏธ์ง€ ํ‰๊ฐ€
69
- for filename, gt_boxes in gt_data.items():
70
- if not gt_boxes:
71
- continue
72
-
73
- folder = gt_boxes[0].get('folder', '')
74
- if not folder:
75
- continue
76
-
77
- img_path = os.path.join(data_base_dir, folder, filename)
78
- if not os.path.exists(img_path):
79
- continue
80
-
81
- # PIL ์ด๋ฏธ์ง€ ๋กœ๋“œ
82
- image = Image.open(img_path)
83
-
84
- # YOLOv8 ์ถ”๋ก 
85
- results = model(img_path, conf=yolo_conf, verbose=False)
86
-
87
- # ์˜ˆ์ธก ๋ฐ•์Šค ์ถ”์ถœ
88
- yolo_detections = []
89
- if results and len(results) > 0:
90
- result = results[0]
91
- if result.boxes is not None and len(result.boxes) > 0:
92
- boxes = result.boxes.xyxy.cpu().numpy()
93
- confs = result.boxes.conf.cpu().numpy()
94
-
95
- for box, conf in zip(boxes, confs):
96
- yolo_detections.append({
97
- 'bbox': box.tolist(),
98
- 'confidence': float(conf)
99
- })
100
-
101
- # Universal Filter ์ ์šฉ
102
- filtered_detections = apply_universal_filter(yolo_detections, image, threshold=filter_threshold)
103
-
104
- # GT ๋ฐ•์Šค
105
- gt_boxes_only = [{'bbox': ann['bbox']} for ann in gt_boxes]
106
-
107
- # YOLOv8 only ๋งค์นญ (๋น„๊ต์šฉ)
108
- yolo_matched_gt = set()
109
- yolo_matched_pred = set()
110
-
111
- for i, pred in enumerate(yolo_detections):
112
- best_iou = 0
113
- best_gt_idx = -1
114
- for j, gt in enumerate(gt_boxes_only):
115
- if j in yolo_matched_gt:
116
- continue
117
- iou = calculate_iou(pred['bbox'], gt['bbox'])
118
- if iou > best_iou:
119
- best_iou = iou
120
- best_gt_idx = j
121
-
122
- if best_iou >= iou_threshold:
123
- yolo_matched_pred.add(i)
124
- yolo_matched_gt.add(best_gt_idx)
125
-
126
- yolo_tp = len(yolo_matched_gt)
127
- yolo_fp = len(yolo_detections) - len(yolo_matched_pred)
128
-
129
- # Filtered ๋งค์นญ
130
- matched_gt = set()
131
- matched_pred = set()
132
-
133
- for i, pred in enumerate(filtered_detections):
134
- best_iou = 0
135
- best_gt_idx = -1
136
-
137
- for j, gt in enumerate(gt_boxes_only):
138
- if j in matched_gt:
139
- continue
140
- iou = calculate_iou(pred['bbox'], gt['bbox'])
141
- if iou > best_iou:
142
- best_iou = iou
143
- best_gt_idx = j
144
-
145
- if best_iou >= iou_threshold:
146
- matched_pred.add(i)
147
- matched_gt.add(best_gt_idx)
148
-
149
- tp = len(matched_gt)
150
- fp = len(filtered_detections) - len(matched_pred)
151
- fn = len(gt_boxes_only) - len(matched_gt)
152
-
153
- true_positives += tp
154
- false_positives += fp
155
- false_negatives += fn
156
- total_gt += len(gt_boxes_only)
157
- total_yolo_pred += len(yolo_detections)
158
- total_filtered_pred += len(filtered_detections)
159
-
160
- yolo_only_tp += yolo_tp
161
- yolo_only_fp += yolo_fp
162
-
163
- results_detail.append({
164
- 'filename': filename,
165
- 'gt_count': len(gt_boxes_only),
166
- 'yolo_count': len(yolo_detections),
167
- 'filtered_count': len(filtered_detections),
168
- 'tp': tp,
169
- 'fp': fp,
170
- 'fn': fn
171
- })
172
-
173
- # ์„ฑ๋Šฅ ๊ณ„์‚ฐ
174
- precision = true_positives / (true_positives + false_positives) if (true_positives + false_positives) > 0 else 0
175
- recall = true_positives / (true_positives + false_negatives) if (true_positives + false_negatives) > 0 else 0
176
- f1 = 2 * precision * recall / (precision + recall) if (precision + recall) > 0 else 0
177
-
178
- # YOLOv8 only ์„ฑ๋Šฅ
179
- yolo_precision = yolo_only_tp / (yolo_only_tp + yolo_only_fp) if (yolo_only_tp + yolo_only_fp) > 0 else 0
180
- yolo_recall = yolo_only_tp / total_gt if total_gt > 0 else 0
181
- yolo_f1 = 2 * yolo_precision * yolo_recall / (yolo_precision + yolo_recall) if (yolo_precision + yolo_recall) > 0 else 0
182
-
183
- return {
184
- 'yolo_with_filter': {
185
- 'precision': precision,
186
- 'recall': recall,
187
- 'f1': f1,
188
- 'tp': true_positives,
189
- 'fp': false_positives,
190
- 'fn': false_negatives,
191
- 'total_pred': total_filtered_pred
192
- },
193
- 'yolo_only': {
194
- 'precision': yolo_precision,
195
- 'recall': yolo_recall,
196
- 'f1': yolo_f1,
197
- 'tp': yolo_only_tp,
198
- 'fp': yolo_only_fp,
199
- 'total_pred': total_yolo_pred
200
- },
201
- 'total_gt': total_gt
202
- }
203
-
204
- def main():
205
- print("=" * 60)
206
- print("๐Ÿš€ YOLOv8 + Universal Filter ์ฆ‰์‹œ ๊ฐœ์„ ")
207
- print("=" * 60)
208
-
209
- # ๊ฒฝ๋กœ ์„ค์ •
210
- yolo_model = "runs/train/shrimp_yolov8n/weights/best.pt"
211
- gt_file = "ground_truth.json"
212
- data_base_dir = "data/ํฐ๋‹ค๋ฆฌ์ƒˆ์šฐ ์‹ค์ธก ๋ฐ์ดํ„ฐ_์ตํˆฌ์Šค์—์ด์•„์ด(์ฃผ)"
213
-
214
- if not os.path.exists(yolo_model):
215
- print(f"\nโŒ ๋ชจ๋ธ ํŒŒ์ผ ์—†์Œ: {yolo_model}")
216
- return
217
-
218
- print(f"\n๐Ÿ“ YOLOv8 ๋ชจ๋ธ: {yolo_model}")
219
- print(f"๐Ÿ“ GT: {gt_file}")
220
-
221
- # ์—ฌ๋Ÿฌ ์กฐํ•ฉ ํ…Œ์ŠคํŠธ
222
- test_configs = [
223
- {'yolo_conf': 0.01, 'filter_threshold': 70},
224
- {'yolo_conf': 0.01, 'filter_threshold': 80},
225
- {'yolo_conf': 0.01, 'filter_threshold': 90},
226
- {'yolo_conf': 0.001, 'filter_threshold': 90},
227
- {'yolo_conf': 0.005, 'filter_threshold': 90},
228
- ]
229
-
230
- print(f"\n๐Ÿ” ์ตœ์  ์กฐํ•ฉ ํƒ์ƒ‰ ์ค‘...")
231
-
232
- best_f1 = 0
233
- best_config = None
234
- best_result = None
235
- all_results = []
236
-
237
- for config in test_configs:
238
- result = yolo_with_filter_evaluate(
239
- yolo_model, gt_file, data_base_dir,
240
- yolo_conf=config['yolo_conf'],
241
- filter_threshold=config['filter_threshold']
242
- )
243
-
244
- result['config'] = config
245
- all_results.append(result)
246
-
247
- filtered = result['yolo_with_filter']
248
- print(f"\n YOLOv8(conf={config['yolo_conf']}) + Filter({config['filter_threshold']})")
249
- print(f" P={filtered['precision']:.1%}, R={filtered['recall']:.1%}, F1={filtered['f1']:.1%}")
250
- print(f" Pred: {result['yolo_only']['total_pred']} โ†’ {filtered['total_pred']} (Filter ์ œ๊ฑฐ: {result['yolo_only']['total_pred'] - filtered['total_pred']}๊ฐœ)")
251
-
252
- if filtered['f1'] > best_f1:
253
- best_f1 = filtered['f1']
254
- best_config = config
255
- best_result = result
256
-
257
- # ์ตœ์  ๊ฒฐ๊ณผ ์ถœ๋ ฅ
258
- print("\n" + "=" * 60)
259
- print("โœ… ํ‰๊ฐ€ ์™„๋ฃŒ!")
260
- print("=" * 60)
261
-
262
- print(f"\n๐Ÿ† ์ตœ์  ์กฐํ•ฉ:")
263
- print(f" - YOLOv8 Confidence: {best_config['yolo_conf']}")
264
- print(f" - Filter Threshold: {best_config['filter_threshold']}")
265
-
266
- filtered = best_result['yolo_with_filter']
267
- yolo = best_result['yolo_only']
268
-
269
- print(f"\n๐Ÿ“Š YOLOv8 + Universal Filter ์„ฑ๋Šฅ:")
270
- print(f" - Precision: {filtered['precision']:.1%}")
271
- print(f" - Recall: {filtered['recall']:.1%}")
272
- print(f" - F1 Score: {filtered['f1']:.1%}")
273
- print(f"\n - True Positives: {filtered['tp']}")
274
- print(f" - False Positives: {filtered['fp']}")
275
- print(f" - False Negatives: {filtered['fn']}")
276
- print(f" - Total Predictions: {filtered['total_pred']}")
277
-
278
- print(f"\n๐Ÿ“Š YOLOv8 Only ๋น„๊ต:")
279
- print(f" - Precision: {yolo['precision']:.1%}")
280
- print(f" - Recall: {yolo['recall']:.1%}")
281
- print(f" - F1 Score: {yolo['f1']:.1%}")
282
- print(f" - Total Predictions: {yolo['total_pred']}")
283
-
284
- print(f"\n๐ŸŽฏ Filter ํšจ๊ณผ:")
285
- print(f" - FP ์ œ๊ฑฐ: {yolo['fp']} โ†’ {filtered['fp']} ({yolo['fp'] - filtered['fp']}๊ฐœ ์ œ๊ฑฐ)")
286
- print(f" - Precision ํ–ฅ์ƒ: {yolo['precision']:.1%} โ†’ {filtered['precision']:.1%} ({(filtered['precision'] - yolo['precision'])*100:+.1f}%p)")
287
- print(f" - F1 ํ–ฅ์ƒ: {yolo['f1']:.1%} โ†’ {filtered['f1']:.1%} ({(filtered['f1'] - yolo['f1'])*100:+.1f}%p)")
288
-
289
- # ์ „์ฒด ์‹œ์Šคํ…œ ๋น„๊ต
290
- print(f"\n๐Ÿ“Š ์ „์ฒด ์‹œ์Šคํ…œ ๋น„๊ต:")
291
- print(f"\n RT-DETR + Filter (๊ธฐ์กด):")
292
- print(f" - Precision: 44.2%")
293
- print(f" - Recall: 94.0%")
294
- print(f" - F1 Score: 56.1%")
295
-
296
- print(f"\n YOLOv8 + Filter (์ƒˆ๋กœ์šด):")
297
- print(f" - Precision: {filtered['precision']:.1%}")
298
- print(f" - Recall: {filtered['recall']:.1%}")
299
- print(f" - F1 Score: {filtered['f1']:.1%}")
300
-
301
- # F1 ๋น„๊ต
302
- baseline_f1 = 0.561
303
- improvement = (filtered['f1'] - baseline_f1) / baseline_f1 * 100
304
-
305
- if improvement > 0:
306
- print(f"\n โœ… F1 ๊ฐœ์„ ์œจ: {improvement:+.1f}% (YOLOv8+Filter๊ฐ€ ๋” ์ข‹์Œ)")
307
- else:
308
- print(f"\n โš ๏ธ F1 ์ฐจ์ด: {improvement:+.1f}% (RT-DETR+Filter๊ฐ€ ๋” ์ข‹์Œ)")
309
-
310
- # ๊ฒฐ๊ณผ ์ €์žฅ
311
- output_file = "yolo_with_filter_results.json"
312
- with open(output_file, 'w', encoding='utf-8') as f:
313
- json.dump({
314
- 'best_config': best_config,
315
- 'best_result': best_result,
316
- 'all_results': all_results,
317
- 'baseline': {
318
- 'name': 'RT-DETR + Filter',
319
- 'precision': 0.442,
320
- 'recall': 0.940,
321
- 'f1': 0.561
322
- }
323
- }, f, indent=2, ensure_ascii=False)
324
-
325
- print(f"\n๐Ÿ’พ ๊ฒฐ๊ณผ ์ €์žฅ: {output_file}")
326
-
327
- print(f"\n๐Ÿ’ก ๊ถŒ์žฅ ์‚ฌํ•ญ:")
328
- if filtered['f1'] >= baseline_f1:
329
- print(f" โœ… YOLOv8 + Universal Filter ์‚ฌ์šฉ ๊ถŒ์žฅ")
330
- print(f" - ์„ค์ •: YOLOv8 conf={best_config['yolo_conf']}, Filter={best_config['filter_threshold']}")
331
- else:
332
- print(f" โš ๏ธ RT-DETR + Universal Filter ๊ณ„์† ์‚ฌ์šฉ ๊ถŒ์žฅ")
333
- print(f" - YOLOv8+Filter๋„ ์ค€์ˆ˜ํ•œ ์„ฑ๋Šฅ์ด์ง€๋งŒ ๊ธฐ์กด์ด ์•ฝ๊ฐ„ ๋” ์ข‹์Œ")
334
-
335
- if __name__ == "__main__":
336
- main()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
test_yolov8_val_results.py DELETED
@@ -1,234 +0,0 @@
1
- """
2
- YOLOv8m Val Set ๊ฒฐ๊ณผ ์ด๋ฏธ์ง€ ์ €์žฅ
3
- Confidence = 0.85 ์‚ฌ์šฉ
4
- """
5
- from ultralytics import YOLO
6
- from PIL import Image, ImageDraw, ImageFont
7
- import json
8
- import os
9
-
10
- # ํ•™์Šต๋œ ๋ชจ๋ธ ๋กœ๋“œ
11
- MODEL_PATH = "runs/train/yolov8m_shrimp2/weights/best.pt"
12
- model = YOLO(MODEL_PATH)
13
-
14
- # ์ตœ์  confidence
15
- CONFIDENCE = 0.85
16
-
17
- print(f"โœ… YOLOv8m ๋ชจ๋ธ ๋กœ๋“œ: {MODEL_PATH}")
18
- print(f"๐ŸŽฏ Confidence Threshold: {CONFIDENCE}")
19
-
20
- # Ground Truth ๋กœ๋“œ
21
- with open('ground_truth.json', 'r', encoding='utf-8') as f:
22
- ground_truth = json.load(f)
23
-
24
- # Val set ์ด๋ฏธ์ง€๋งŒ ํ•„ํ„ฐ๋ง
25
- val_images_dir = set(os.listdir('data/yolo_dataset/images/val'))
26
- gt_val_only = {}
27
-
28
- for img_name, gts in ground_truth.items():
29
- if not gts:
30
- continue
31
- base_name = img_name.replace('-1.jpg', '.jpg')
32
- if img_name in val_images_dir or base_name in val_images_dir:
33
- gt_val_only[img_name] = gts
34
-
35
- print(f"๐Ÿ“ Val set GT ์ด๋ฏธ์ง€: {len(gt_val_only)}์žฅ")
36
-
37
- # ์ถœ๋ ฅ ๋””๋ ‰ํ† ๋ฆฌ
38
- output_dir = "test_results_yolov8m_val"
39
- os.makedirs(output_dir, exist_ok=True)
40
-
41
- # ํฐํŠธ ์„ค์ •
42
- try:
43
- font = ImageFont.truetype("malgun.ttf", 24)
44
- font_small = ImageFont.truetype("malgun.ttf", 18)
45
- font_tiny = ImageFont.truetype("malgun.ttf", 14)
46
- except:
47
- font = ImageFont.load_default()
48
- font_small = ImageFont.load_default()
49
- font_tiny = ImageFont.load_default()
50
-
51
- def calculate_iou(box1, box2):
52
- """IoU ๊ณ„์‚ฐ"""
53
- x1_min, y1_min, x1_max, y1_max = box1
54
- x2_min, y2_min, x2_max, y2_max = box2
55
-
56
- inter_x_min = max(x1_min, x2_min)
57
- inter_y_min = max(y1_min, y2_min)
58
- inter_x_max = min(x1_max, x2_max)
59
- inter_y_max = min(y1_max, y2_max)
60
-
61
- if inter_x_max < inter_x_min or inter_y_max < inter_y_min:
62
- return 0.0
63
-
64
- inter_area = (inter_x_max - inter_x_min) * (inter_y_max - inter_y_min)
65
- box1_area = (x1_max - x1_min) * (y1_max - y1_min)
66
- box2_area = (x2_max - x2_min) * (y2_max - y2_min)
67
- union_area = box1_area + box2_area - inter_area
68
-
69
- return inter_area / union_area if union_area > 0 else 0.0
70
-
71
- # ํ†ต๊ณ„
72
- total_gt = 0
73
- total_tp = 0
74
- total_fp = 0
75
- total_fn = 0
76
-
77
- print("-" * 60)
78
-
79
- # ๊ฐ ์ด๋ฏธ์ง€ ์ฒ˜๋ฆฌ
80
- for idx, (img_name, gt_boxes) in enumerate(sorted(gt_val_only.items()), 1):
81
- print(f"\n[{idx}/{len(gt_val_only)}] {img_name}")
82
-
83
- # ์ด๋ฏธ์ง€ ๊ฒฝ๋กœ
84
- img_path = f"data/yolo_dataset/images/val/{img_name}"
85
- base_name = img_name.replace('-1.jpg', '.jpg')
86
- if not os.path.exists(img_path):
87
- img_path = f"data/yolo_dataset/images/val/{base_name}"
88
-
89
- if not os.path.exists(img_path):
90
- print(f" โš ๏ธ ์ด๋ฏธ์ง€๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Œ: {img_path}")
91
- continue
92
-
93
- # ์ด๋ฏธ์ง€ ๋กœ๋“œ
94
- image = Image.open(img_path)
95
- print(f" ๐Ÿ“ ํฌ๊ธฐ: {image.size}")
96
-
97
- # YOLOv8 ๊ฒ€์ถœ
98
- results = model.predict(
99
- source=image,
100
- conf=CONFIDENCE,
101
- iou=0.7,
102
- device=0,
103
- verbose=False
104
- )
105
-
106
- result = results[0]
107
- boxes = result.boxes
108
-
109
- predictions = []
110
- if boxes is not None and len(boxes) > 0:
111
- for box in boxes:
112
- x1, y1, x2, y2 = box.xyxy[0].cpu().numpy()
113
- confidence = box.conf[0].cpu().item()
114
- predictions.append({
115
- 'bbox': [float(x1), float(y1), float(x2), float(y2)],
116
- 'confidence': confidence
117
- })
118
-
119
- print(f" ๐Ÿฆ ๊ฒ€์ถœ: {len(predictions)}๊ฐœ (GT: {len(gt_boxes)}๊ฐœ)")
120
-
121
- # GT์™€ ๋งค์นญ
122
- matched_gt = set()
123
- matched_pred = set()
124
- tp = 0
125
- fp = 0
126
-
127
- for pred_idx, pred in enumerate(predictions):
128
- best_iou = 0
129
- best_gt_idx = -1
130
-
131
- for gt_idx, gt in enumerate(gt_boxes):
132
- if gt_idx in matched_gt:
133
- continue
134
-
135
- iou = calculate_iou(pred['bbox'], gt['bbox'])
136
- if iou > best_iou:
137
- best_iou = iou
138
- best_gt_idx = gt_idx
139
-
140
- if best_iou >= 0.5:
141
- tp += 1
142
- matched_gt.add(best_gt_idx)
143
- matched_pred.add(pred_idx)
144
- else:
145
- fp += 1
146
-
147
- fn = len(gt_boxes) - len(matched_gt)
148
-
149
- total_gt += len(gt_boxes)
150
- total_tp += tp
151
- total_fp += fp
152
- total_fn += fn
153
-
154
- print(f" ๐Ÿ“Š TP={tp}, FP={fp}, FN={fn}")
155
-
156
- # ๊ฒฐ๊ณผ ์ด๋ฏธ์ง€ ๊ทธ๋ฆฌ๊ธฐ
157
- result_image = image.copy()
158
- draw = ImageDraw.Draw(result_image)
159
-
160
- # Ground Truth (ํŒŒ๋ž€์ƒ‰, ์ ์„  ํšจ๊ณผ)
161
- for gt_idx, gt in enumerate(gt_boxes):
162
- x1, y1, x2, y2 = gt['bbox']
163
-
164
- # ํŒŒ๋ž€์ƒ‰ ๋ฐ•์Šค (์–‡๊ฒŒ)
165
- for offset in range(0, 4, 2):
166
- draw.rectangle([x1+offset, y1+offset, x2-offset, y2-offset], outline="blue", width=2)
167
-
168
- # GT ๋ผ๋ฒจ
169
- label = f"GT#{gt_idx+1}"
170
- bbox_label = draw.textbbox((x1, y1 - 30), label, font=font_tiny)
171
- draw.rectangle(bbox_label, fill="blue")
172
- draw.text((x1, y1 - 30), label, fill="white", font=font_tiny)
173
-
174
- # ๋งค์นญ ์—ฌ๋ถ€ ํ‘œ์‹œ
175
- if gt_idx not in matched_gt:
176
- # FN (๋†“์นจ)
177
- draw.text((x1 + 10, y1 + 10), "MISSED", fill="red", font=font_small)
178
-
179
- # Predictions
180
- for pred_idx, pred in enumerate(predictions):
181
- x1, y1, x2, y2 = pred['bbox']
182
- conf = pred['confidence']
183
-
184
- if pred_idx in matched_pred:
185
- # TP (์˜ฌ๋ฐ”๋ฅธ ๊ฒ€์ถœ) - ๋…น์ƒ‰
186
- color = "lime"
187
- label = f"โœ“ TP #{pred_idx+1} | {conf:.0%}"
188
- draw.rectangle([x1, y1, x2, y2], outline=color, width=10)
189
- else:
190
- # FP (์ž˜๋ชป๋œ ๊ฒ€์ถœ) - ๋นจ๊ฐ„์ƒ‰
191
- color = "red"
192
- label = f"โœ— FP #{pred_idx+1} | {conf:.0%}"
193
- draw.rectangle([x1, y1, x2, y2], outline=color, width=8)
194
-
195
- # ๋ผ๋ฒจ
196
- bbox_label = draw.textbbox((x1, y2 + 5), label, font=font_tiny)
197
- draw.rectangle(bbox_label, fill=color)
198
- draw.text((x1, y2 + 5), label, fill="black" if color == "lime" else "white", font=font_tiny)
199
-
200
- # ํ†ต๊ณ„ ํ…์ŠคํŠธ (์ƒ๋‹จ)
201
- stats_text = f"GT:{len(gt_boxes)} | Pred:{len(predictions)} | TP:{tp} FP:{fp} FN:{fn}"
202
- draw.rectangle([10, 10, 800, 50], fill="black")
203
- draw.text((15, 15), stats_text, fill="white", font=font_small)
204
-
205
- # ์ด๋ฏธ์ง€ ์ •๋ณด (ํ•˜๋‹จ)
206
- info_text = f"Confidence: {CONFIDENCE} | {img_name}"
207
- img_width, img_height = image.size
208
- draw.rectangle([10, img_height - 50, 800, img_height - 10], fill="black")
209
- draw.text((15, img_height - 45), info_text, fill="yellow", font=font_tiny)
210
-
211
- # ์ €์žฅ
212
- output_path = os.path.join(output_dir, f"result_{base_name}")
213
- result_image.save(output_path, quality=95)
214
- print(f" โœ… ์ €์žฅ: {output_path}")
215
-
216
- # ์ตœ์ข… ํ†ต๊ณ„
217
- print("\n" + "=" * 60)
218
- print("๐Ÿ“Š ์ „์ฒด ๊ฒฐ๊ณผ (Val Set 10์žฅ):")
219
- print("=" * 60)
220
- print(f"์ด GT: {total_gt}๊ฐœ")
221
- print(f"TP: {total_tp}๊ฐœ (์˜ฌ๋ฐ”๋ฅธ ๊ฒ€์ถœ)")
222
- print(f"FP: {total_fp}๊ฐœ (์ž˜๋ชป๋œ ๊ฒ€์ถœ)")
223
- print(f"FN: {total_fn}๊ฐœ (๋†“์นœ GT)")
224
-
225
- precision = total_tp / (total_tp + total_fp) if (total_tp + total_fp) > 0 else 0
226
- recall = total_tp / (total_tp + total_fn) if (total_tp + total_fn) > 0 else 0
227
- f1 = 2 * (precision * recall) / (precision + recall) if (precision + recall) > 0 else 0
228
-
229
- print(f"\nPrecision: {precision:.1%}")
230
- print(f"Recall: {recall:.1%}")
231
- print(f"F1 Score: {f1:.1%}")
232
-
233
- print(f"\n๐Ÿ“ ๊ฒฐ๊ณผ ์ €์žฅ: {output_dir}/")
234
- print("=" * 60)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
test_yolov8m_trained.py DELETED
@@ -1,112 +0,0 @@
1
- """
2
- YOLOv8m ํ•™์Šต ๋ชจ๋ธ ํ…Œ์ŠคํŠธ ์Šคํฌ๋ฆฝํŠธ
3
- ํ•™์Šต๋œ ๋ชจ๋ธ๋กœ ํ…Œ์ŠคํŠธ ์ด๋ฏธ์ง€ ๊ฒ€์ถœ ๋ฐ ๊ฒฐ๊ณผ ์ €์žฅ
4
- """
5
- from ultralytics import YOLO
6
- from PIL import Image, ImageDraw, ImageFont
7
- import os
8
- import glob
9
-
10
- # ํ•™์Šต๋œ ๋ชจ๋ธ ๋กœ๋“œ
11
- MODEL_PATH = "runs/train/yolov8m_shrimp2/weights/best.pt"
12
- model = YOLO(MODEL_PATH)
13
-
14
- print(f"โœ… YOLOv8m ๋ชจ๋ธ ๋กœ๋“œ ์™„๋ฃŒ: {MODEL_PATH}")
15
-
16
- # ํ…Œ์ŠคํŠธ ์ด๋ฏธ์ง€ ๊ฒฝ๋กœ
17
- test_images_dir = "data/yolo_dataset/images/val"
18
- output_dir = "test_results_yolov8m"
19
-
20
- # ์ถœ๋ ฅ ๋””๋ ‰ํ† ๋ฆฌ ์ƒ์„ฑ
21
- os.makedirs(output_dir, exist_ok=True)
22
-
23
- # ํ…Œ์ŠคํŠธ ์ด๋ฏธ์ง€ ์ฐพ๊ธฐ
24
- test_images = sorted(glob.glob(os.path.join(test_images_dir, "*.jpg")))
25
-
26
- if not test_images:
27
- print(f"โŒ ํ…Œ์ŠคํŠธ ์ด๋ฏธ์ง€๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค: {test_images_dir}")
28
- exit(1)
29
-
30
- print(f"๐Ÿ“ ํ…Œ์ŠคํŠธ ์ด๋ฏธ์ง€: {len(test_images)}์žฅ")
31
- print(f"๐Ÿ“‚ ๊ฒฐ๊ณผ ์ €์žฅ ๊ฒฝ๋กœ: {output_dir}/")
32
- print("-" * 60)
33
-
34
- # ํฐํŠธ ์„ค์ •
35
- try:
36
- font = ImageFont.truetype("malgun.ttf", 20)
37
- font_small = ImageFont.truetype("malgun.ttf", 14)
38
- except:
39
- font = ImageFont.load_default()
40
- font_small = ImageFont.load_default()
41
-
42
- # ๊ฐ ์ด๋ฏธ์ง€ ํ…Œ์ŠคํŠธ
43
- total_detections = 0
44
- for idx, img_path in enumerate(test_images, 1):
45
- img_name = os.path.basename(img_path)
46
- print(f"\n[{idx}/{len(test_images)}] {img_name}")
47
-
48
- # ์ด๋ฏธ์ง€ ๋กœ๋“œ
49
- image = Image.open(img_path)
50
- print(f" ๐Ÿ“ ์ด๋ฏธ์ง€ ํฌ๊ธฐ: {image.size}")
51
-
52
- # YOLOv8 ๊ฒ€์ถœ (confidence threshold=0.065)
53
- results = model.predict(
54
- source=image,
55
- conf=0.065,
56
- iou=0.7,
57
- device=0, # GPU ์‚ฌ์šฉ
58
- verbose=False
59
- )
60
-
61
- # ๊ฒฐ๊ณผ ํŒŒ์‹ฑ
62
- result = results[0]
63
- boxes = result.boxes
64
-
65
- detections = []
66
- if boxes is not None and len(boxes) > 0:
67
- for box in boxes:
68
- # ๋ฐ”์šด๋”ฉ ๋ฐ•์Šค ์ขŒํ‘œ
69
- x1, y1, x2, y2 = box.xyxy[0].cpu().numpy()
70
- confidence = box.conf[0].cpu().item()
71
- cls = int(box.cls[0].cpu().item())
72
-
73
- detections.append({
74
- 'bbox': [float(x1), float(y1), float(x2), float(y2)],
75
- 'confidence': confidence,
76
- 'class': cls
77
- })
78
-
79
- print(f" ๐Ÿฆ ๊ฒ€์ถœ: {len(detections)}๊ฐœ")
80
- total_detections += len(detections)
81
-
82
- # ๊ฒฐ๊ณผ ์ด๋ฏธ์ง€ ๊ทธ๋ฆฌ๊ธฐ
83
- result_image = image.copy()
84
- draw = ImageDraw.Draw(result_image)
85
-
86
- for i, det in enumerate(detections, 1):
87
- x1, y1, x2, y2 = det['bbox']
88
- conf = det['confidence']
89
-
90
- # ๋ฐ”์šด๋”ฉ ๋ฐ•์Šค (๋…น์ƒ‰, ๊ตต๊ฒŒ)
91
- draw.rectangle([x1, y1, x2, y2], outline="lime", width=8)
92
-
93
- # ๋ผ๋ฒจ
94
- label = f"#{i} | {conf:.2%}"
95
- bbox = draw.textbbox((x1, y1 - 25), label, font=font_small)
96
- draw.rectangle(bbox, fill="lime")
97
- draw.text((x1, y1 - 25), label, fill="black", font=font_small)
98
-
99
- print(f" #{i}: conf={conf:.2%}, bbox=[{x1:.0f},{y1:.0f},{x2:.0f},{y2:.0f}]")
100
-
101
- # ๊ฒฐ๊ณผ ์ €์žฅ
102
- output_path = os.path.join(output_dir, f"result_{img_name}")
103
- result_image.save(output_path)
104
- print(f" โœ… ์ €์žฅ: {output_path}")
105
-
106
- print("\n" + "=" * 60)
107
- print(f"๐Ÿ“Š ์ „์ฒด ๊ฒฐ๊ณผ:")
108
- print(f" - ํ…Œ์ŠคํŠธ ์ด๋ฏธ์ง€: {len(test_images)}์žฅ")
109
- print(f" - ์ด ๊ฒ€์ถœ: {total_detections}๊ฐœ")
110
- print(f" - ํ‰๊ท : {total_detections / len(test_images):.1f}๊ฐœ/์ด๋ฏธ์ง€")
111
- print(f" - ๊ฒฐ๊ณผ ์ €์žฅ: {output_dir}/")
112
- print("=" * 60)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
test_yolov8m_unseen.py DELETED
@@ -1,144 +0,0 @@
1
- """
2
- YOLOv8m Unseen Test Set ํ‰๊ฐ€
3
- 251010, 251017 ํด๋”์˜ ์™„์ „ํžˆ ์ƒˆ๋กœ์šด ์ด๋ฏธ์ง€ 20๊ฐœ๋กœ ํ…Œ์ŠคํŠธ
4
- """
5
- from ultralytics import YOLO
6
- from PIL import Image, ImageDraw, ImageFont
7
- import os
8
- import glob
9
-
10
- # ํ•™์Šต๋œ ๋ชจ๋ธ ๋กœ๋“œ
11
- MODEL_PATH = "runs/train/yolov8m_shrimp2/weights/best.pt"
12
- model = YOLO(MODEL_PATH)
13
-
14
- # ์ตœ์  confidence
15
- CONFIDENCE = 0.85
16
-
17
- print(f"โœ… YOLOv8m ๋ชจ๋ธ ๋กœ๋“œ: {MODEL_PATH}")
18
- print(f"๐ŸŽฏ Confidence Threshold: {CONFIDENCE}")
19
-
20
- # Unseen test ์ด๋ฏธ์ง€ ๊ฒฝ๋กœ
21
- test_folders = [
22
- "data/ํฐ๋‹ค๋ฆฌ์ƒˆ์šฐ ์‹ค์ธก ๋ฐ์ดํ„ฐ_์ตํˆฌ์Šค์—์ด์•„์ด(์ฃผ)/251010",
23
- "data/ํฐ๋‹ค๋ฆฌ์ƒˆ์šฐ ์‹ค์ธก ๋ฐ์ดํ„ฐ_์ตํˆฌ์Šค์—์ด์•„์ด(์ฃผ)/251017"
24
- ]
25
-
26
- test_images = []
27
- for folder in test_folders:
28
- # -1.jpg ์ œ์™ธ, ์›๋ณธ ์ด๋ฏธ์ง€๋งŒ
29
- images = sorted(glob.glob(os.path.join(folder, "*_[0-9][0-9].jpg")))
30
- test_images.extend(images)
31
-
32
- print(f"๐Ÿ“ Unseen test ์ด๋ฏธ์ง€: {len(test_images)}์žฅ")
33
- print("-" * 60)
34
-
35
- # ์ถœ๋ ฅ ๋””๋ ‰ํ† ๋ฆฌ
36
- output_dir = "test_results_unseen"
37
- os.makedirs(output_dir, exist_ok=True)
38
-
39
- # ํฐํŠธ ์„ค์ •
40
- try:
41
- font = ImageFont.truetype("malgun.ttf", 24)
42
- font_small = ImageFont.truetype("malgun.ttf", 18)
43
- font_tiny = ImageFont.truetype("malgun.ttf", 14)
44
- except:
45
- font = ImageFont.load_default()
46
- font_small = ImageFont.load_default()
47
- font_tiny = ImageFont.load_default()
48
-
49
- # ํ†ต๊ณ„
50
- total_detections = 0
51
- images_with_detections = 0
52
- images_without_detections = 0
53
-
54
- print("\n๐Ÿ” Unseen Test ๊ฒ€์ถœ ์‹œ์ž‘...\n")
55
-
56
- # ๊ฐ ์ด๋ฏธ์ง€ ์ฒ˜๋ฆฌ
57
- for idx, img_path in enumerate(test_images, 1):
58
- img_name = os.path.basename(img_path)
59
- folder_name = os.path.basename(os.path.dirname(img_path))
60
-
61
- print(f"[{idx}/{len(test_images)}] {folder_name}/{img_name}")
62
-
63
- # ์ด๋ฏธ์ง€ ๋กœ๋“œ
64
- image = Image.open(img_path)
65
- print(f" ๐Ÿ“ ํฌ๊ธฐ: {image.size}")
66
-
67
- # YOLOv8 ๊ฒ€์ถœ
68
- results = model.predict(
69
- source=image,
70
- conf=CONFIDENCE,
71
- iou=0.7,
72
- device=0,
73
- verbose=False
74
- )
75
-
76
- result = results[0]
77
- boxes = result.boxes
78
-
79
- predictions = []
80
- if boxes is not None and len(boxes) > 0:
81
- for box in boxes:
82
- x1, y1, x2, y2 = box.xyxy[0].cpu().numpy()
83
- confidence = box.conf[0].cpu().item()
84
- predictions.append({
85
- 'bbox': [float(x1), float(y1), float(x2), float(y2)],
86
- 'confidence': confidence
87
- })
88
-
89
- num_detections = len(predictions)
90
- total_detections += num_detections
91
-
92
- if num_detections > 0:
93
- images_with_detections += 1
94
- print(f" ๐Ÿฆ ๊ฒ€์ถœ: {num_detections}๊ฐœ")
95
- else:
96
- images_without_detections += 1
97
- print(f" โšช ๊ฒ€์ถœ ์—†์Œ")
98
-
99
- # ๊ฒฐ๊ณผ ์ด๋ฏธ์ง€ ๊ทธ๋ฆฌ๊ธฐ
100
- result_image = image.copy()
101
- draw = ImageDraw.Draw(result_image)
102
-
103
- # Predictions (๋…น์ƒ‰ ๋ฐ•์Šค)
104
- for pred_idx, pred in enumerate(predictions, 1):
105
- x1, y1, x2, y2 = pred['bbox']
106
- conf = pred['confidence']
107
-
108
- # ๋…น์ƒ‰ ๋ฐ•์Šค
109
- draw.rectangle([x1, y1, x2, y2], outline="lime", width=10)
110
-
111
- # ๋ผ๋ฒจ
112
- label = f"#{pred_idx} | {conf:.0%}"
113
- bbox_label = draw.textbbox((x1, y2 + 5), label, font=font_small)
114
- draw.rectangle(bbox_label, fill="lime")
115
- draw.text((x1, y2 + 5), label, fill="black", font=font_small)
116
-
117
- # ํ†ต๊ณ„ ํ…์ŠคํŠธ (์ƒ๋‹จ)
118
- stats_text = f"Detections: {num_detections} | Confidence: {CONFIDENCE}"
119
- draw.rectangle([10, 10, 800, 50], fill="black")
120
- draw.text((15, 15), stats_text, fill="white", font=font_small)
121
-
122
- # ์ด๋ฏธ์ง€ ์ •๋ณด (ํ•˜๋‹จ)
123
- info_text = f"{folder_name}/{img_name}"
124
- img_width, img_height = image.size
125
- draw.rectangle([10, img_height - 50, 800, img_height - 10], fill="black")
126
- draw.text((15, img_height - 45), info_text, fill="yellow", font=font_tiny)
127
-
128
- # ์ €์žฅ
129
- output_path = os.path.join(output_dir, f"result_{folder_name}_{img_name}")
130
- result_image.save(output_path, quality=95)
131
- print(f" โœ… ์ €์žฅ: {output_path}")
132
-
133
- # ์ตœ์ข… ํ†ต๊ณ„
134
- print("\n" + "=" * 60)
135
- print("๐Ÿ“Š Unseen Test Set ๊ฒฐ๊ณผ:")
136
- print("=" * 60)
137
- print(f"์ด ์ด๋ฏธ์ง€: {len(test_images)}์žฅ")
138
- print(f"์ด ๊ฒ€์ถœ: {total_detections}๊ฐœ")
139
- print(f"ํ‰๊ท  ๊ฒ€์ถœ: {total_detections / len(test_images):.1f}๊ฐœ/์ด๋ฏธ์ง€")
140
- print(f"\n๊ฒ€์ถœ ์žˆ์Œ: {images_with_detections}์žฅ ({images_with_detections/len(test_images)*100:.1f}%)")
141
- print(f"๊ฒ€์ถœ ์—†์Œ: {images_without_detections}์žฅ ({images_without_detections/len(test_images)*100:.1f}%)")
142
-
143
- print(f"\n๐Ÿ“ ๊ฒฐ๊ณผ ์ €์žฅ: {output_dir}/")
144
- print("=" * 60)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
test_yolov8m_with_filter.py DELETED
@@ -1,367 +0,0 @@
1
- """
2
- YOLOv8m + Universal Filter ํ…Œ์ŠคํŠธ
3
- ์ „์ฒด ๋ฐ์ดํ„ฐ์…‹์œผ๋กœ ํ…Œ์ŠคํŠธ ๋ฐ ํ•„ํ„ฐ๋ง ์„ฑ๋Šฅ ๊ฐœ์„ 
4
- """
5
- from ultralytics import YOLO
6
- from PIL import Image, ImageDraw, ImageFont
7
- import os
8
- import glob
9
- import numpy as np
10
- import json
11
-
12
- # OpenCV for filter functions
13
- import cv2
14
-
15
- def calculate_morphological_features(bbox, img_size):
16
- """ํ˜•ํƒœํ•™์  ํŠน์ง• ๊ณ„์‚ฐ"""
17
- x1, y1, x2, y2 = bbox
18
- width = x2 - x1
19
- height = y2 - y1
20
- area = width * height
21
-
22
- aspect_ratio = height / width if width > 0 else 0
23
-
24
- # Compactness (0~1, 1์— ๊ฐ€๊นŒ์šธ์ˆ˜๋ก ์ •์‚ฌ๊ฐํ˜•)
25
- perimeter = 2 * (width + height)
26
- compactness = (4 * np.pi * area) / (perimeter ** 2) if perimeter > 0 else 0
27
-
28
- # ์ด๋ฏธ์ง€ ๋‚ด ๋น„์œจ
29
- img_area = img_size[0] * img_size[1]
30
- area_ratio = area / img_area if img_area > 0 else 0
31
-
32
- return {
33
- 'width': width,
34
- 'height': height,
35
- 'area': area,
36
- 'aspect_ratio': aspect_ratio,
37
- 'compactness': compactness,
38
- 'area_ratio': area_ratio
39
- }
40
-
41
- def calculate_visual_features(image_pil, bbox):
42
- """์‹œ๊ฐ์  ํŠน์ง• ๊ณ„์‚ฐ"""
43
- image_cv = cv2.cvtColor(np.array(image_pil), cv2.COLOR_RGB2BGR)
44
- x1, y1, x2, y2 = [int(v) for v in bbox]
45
-
46
- roi = image_cv[y1:y2, x1:x2]
47
- if roi.size == 0:
48
- return {'hue': 100, 'saturation': 255, 'color_std': 255}
49
-
50
- hsv = cv2.cvtColor(roi, cv2.COLOR_BGR2HSV)
51
-
52
- return {
53
- 'hue': np.mean(hsv[:, :, 0]),
54
- 'saturation': np.mean(hsv[:, :, 1]),
55
- 'color_std': np.std(hsv[:, :, 0])
56
- }
57
-
58
- def calculate_iou_simple(bbox1, bbox2):
59
- """๊ฐ„๋‹จํ•œ IoU ๊ณ„์‚ฐ"""
60
- x1_min, y1_min, x1_max, y1_max = bbox1
61
- x2_min, y2_min, x2_max, y2_max = bbox2
62
-
63
- inter_x_min = max(x1_min, x2_min)
64
- inter_y_min = max(y1_min, y2_min)
65
- inter_x_max = min(x1_max, x2_max)
66
- inter_y_max = min(y1_max, y2_max)
67
-
68
- if inter_x_max < inter_x_min or inter_y_max < inter_y_min:
69
- return 0.0
70
-
71
- inter_area = (inter_x_max - inter_x_min) * (inter_y_max - inter_y_min)
72
-
73
- bbox1_area = (x1_max - x1_min) * (y1_max - y1_min)
74
- bbox2_area = (x2_max - x2_min) * (y2_max - y2_min)
75
- union_area = bbox1_area + bbox2_area - inter_area
76
-
77
- return inter_area / union_area if union_area > 0 else 0.0
78
-
79
- def apply_universal_filter(detections, image, threshold=90):
80
- """๋ฒ”์šฉ ์ƒˆ์šฐ ํ•„ํ„ฐ ์ ์šฉ"""
81
- img_size = image.size
82
- filtered = []
83
-
84
- for det in detections:
85
- bbox = det['bbox']
86
- morph = calculate_morphological_features(bbox, img_size)
87
- visual = calculate_visual_features(image, bbox)
88
-
89
- score = 0
90
- reasons = []
91
-
92
- # Aspect ratio (4:1 ~ 9:1)
93
- if 4.0 <= morph['aspect_ratio'] <= 9.0:
94
- score += 25
95
- reasons.append(f"โœ“ ์ข…ํšก๋น„ {morph['aspect_ratio']:.1f}")
96
- elif 3.0 <= morph['aspect_ratio'] < 4.0 or 9.0 < morph['aspect_ratio'] <= 10.0:
97
- score += 12
98
- reasons.append(f"โ–ณ ์ข…ํšก๋น„ {morph['aspect_ratio']:.1f}")
99
- else:
100
- score -= 5
101
- reasons.append(f"โœ— ์ข…ํšก๋น„ {morph['aspect_ratio']:.1f}")
102
-
103
- # Compactness (์„ธ์žฅ๋„)
104
- if morph['compactness'] < 0.40:
105
- score += 30
106
- reasons.append(f"โœ“ ์„ธ์žฅ๋„ {morph['compactness']:.2f}")
107
- elif 0.40 <= morph['compactness'] < 0.50:
108
- score += 15
109
- reasons.append(f"โ–ณ ์„ธ์žฅ๋„ {morph['compactness']:.2f}")
110
- else:
111
- score -= 20
112
- reasons.append(f"โœ— ์„ธ์žฅ๋„ {morph['compactness']:.2f}")
113
-
114
- # Area
115
- abs_area = morph['width'] * morph['height']
116
- if 50000 <= abs_area <= 500000:
117
- score += 35
118
- reasons.append(f"โœ“ ๋ฉด์  {abs_area/1000:.0f}K")
119
- elif 500000 < abs_area <= 800000:
120
- score -= 10
121
- reasons.append(f"โ–ณ ๋ฉด์  {abs_area/1000:.0f}K")
122
- elif abs_area > 800000:
123
- score -= 30
124
- reasons.append(f"โœ— ๋ฉด์  {abs_area/1000:.0f}K (๋„ˆ๋ฌดํผ)")
125
- else:
126
- score -= 10
127
- reasons.append(f"โœ— ๋ฉด์  {abs_area/1000:.0f}K (๋„ˆ๋ฌด์ž‘์Œ)")
128
-
129
- # Hue (์ƒ‰์ƒ)
130
- hue = visual['hue']
131
- if hue < 40 or hue > 130:
132
- score += 10
133
- reasons.append(f"โœ“ ์ƒ‰์ƒ {hue:.0f}")
134
- elif 90 <= hue <= 130:
135
- score -= 5
136
- reasons.append(f"โœ— ์ƒ‰์ƒ {hue:.0f} (๋ฐฐ๊ฒฝ)")
137
- else:
138
- reasons.append(f"โ–ณ ์ƒ‰์ƒ {hue:.0f}")
139
-
140
- # Saturation (์ฑ„๋„)
141
- if visual['saturation'] < 85:
142
- score += 20
143
- reasons.append(f"โœ“ ์ฑ„๋„ {visual['saturation']:.0f}")
144
- elif 85 <= visual['saturation'] < 120:
145
- score += 5
146
- reasons.append(f"โ–ณ ์ฑ„๋„ {visual['saturation']:.0f}")
147
- else:
148
- score -= 15
149
- reasons.append(f"โœ— ์ฑ„๋„ {visual['saturation']:.0f} (๋†’์Œ)")
150
-
151
- # Color consistency
152
- if visual['color_std'] < 50:
153
- score += 15
154
- reasons.append(f"โœ“ ์ƒ‰์ƒ์ผ๊ด€์„ฑ {visual['color_std']:.1f}")
155
- elif 50 <= visual['color_std'] < 80:
156
- score += 5
157
- reasons.append(f"โ–ณ ์ƒ‰์ƒ์ผ๊ด€์„ฑ {visual['color_std']:.1f}")
158
- else:
159
- score -= 10
160
- reasons.append(f"โœ— ์ƒ‰์ƒ์ผ๊ด€์„ฑ {visual['color_std']:.1f}")
161
-
162
- # YOLOv8 confidence
163
- score += det['confidence'] * 15
164
-
165
- det['filter_score'] = score
166
- det['filter_reasons'] = reasons
167
- det['morph_features'] = morph
168
- det['visual_features'] = visual
169
-
170
- if score >= threshold:
171
- filtered.append(det)
172
-
173
- # ์ ์ˆ˜ ์ˆœ์œผ๋กœ ์ •๋ ฌ
174
- filtered.sort(key=lambda x: x['filter_score'], reverse=True)
175
-
176
- # NMS (Non-Maximum Suppression)
177
- filtered_nms = []
178
- for det in filtered:
179
- is_duplicate = False
180
- for kept_det in filtered_nms:
181
- iou = calculate_iou_simple(det['bbox'], kept_det['bbox'])
182
- if iou > 0.5:
183
- is_duplicate = True
184
- break
185
-
186
- if not is_duplicate:
187
- filtered_nms.append(det)
188
-
189
- return filtered_nms
190
-
191
- # ํ•™์Šต๋œ ๋ชจ๋ธ ๋กœ๋“œ
192
- MODEL_PATH = "runs/train/yolov8m_shrimp2/weights/best.pt"
193
- model = YOLO(MODEL_PATH)
194
-
195
- print(f"โœ… YOLOv8m ๋ชจ๋ธ ๋กœ๋“œ ์™„๋ฃŒ: {MODEL_PATH}")
196
-
197
- # ์ „์ฒด ๋ฐ์ดํ„ฐ์…‹ ํ…Œ์ŠคํŠธ (train + val)
198
- test_images = []
199
- for split in ['train', 'val']:
200
- split_dir = f"data/yolo_dataset/images/{split}"
201
- if os.path.exists(split_dir):
202
- test_images.extend(sorted(glob.glob(os.path.join(split_dir, "*.jpg"))))
203
-
204
- output_dir = "test_results_yolov8m_filtered"
205
- os.makedirs(output_dir, exist_ok=True)
206
-
207
- print(f"๐Ÿ“ ํ…Œ์ŠคํŠธ ์ด๋ฏธ์ง€: {len(test_images)}์žฅ")
208
- print(f"๐Ÿ“‚ ๊ฒฐ๊ณผ ์ €์žฅ ๊ฒฝ๋กœ: {output_dir}/")
209
- print("-" * 60)
210
-
211
- # ํฐํŠธ ์„ค์ •
212
- try:
213
- font = ImageFont.truetype("malgun.ttf", 20)
214
- font_small = ImageFont.truetype("malgun.ttf", 14)
215
- font_tiny = ImageFont.truetype("malgun.ttf", 12)
216
- except:
217
- font = ImageFont.load_default()
218
- font_small = ImageFont.load_default()
219
- font_tiny = ImageFont.load_default()
220
-
221
- # ํ†ต๊ณ„ ๋ณ€์ˆ˜
222
- stats_no_filter = {'total': 0, 'per_image': []}
223
- stats_filtered = {'total': 0, 'per_image': []}
224
- filter_thresholds = [50, 60, 70, 80, 90] # ๋‹ค์–‘ํ•œ ์ž„๊ณ„๊ฐ’ ํ…Œ์ŠคํŠธ
225
- stats_by_threshold = {th: {'total': 0, 'per_image': []} for th in filter_thresholds}
226
-
227
- # ๊ฐ ์ด๋ฏธ์ง€ ํ…Œ์ŠคํŠธ
228
- for idx, img_path in enumerate(test_images, 1):
229
- img_name = os.path.basename(img_path)
230
-
231
- if idx % 10 == 0 or idx == 1:
232
- print(f"\n[{idx}/{len(test_images)}] {img_name}")
233
-
234
- # ์ด๋ฏธ์ง€ ๋กœ๋“œ
235
- image = Image.open(img_path)
236
-
237
- # YOLOv8 ๊ฒ€์ถœ (confidence threshold=0.065)
238
- results = model.predict(
239
- source=image,
240
- conf=0.065,
241
- iou=0.7,
242
- device=0,
243
- verbose=False
244
- )
245
-
246
- # ๊ฒฐ๊ณผ ํŒŒ์‹ฑ
247
- result = results[0]
248
- boxes = result.boxes
249
-
250
- detections_raw = []
251
- if boxes is not None and len(boxes) > 0:
252
- for box in boxes:
253
- x1, y1, x2, y2 = box.xyxy[0].cpu().numpy()
254
- confidence = box.conf[0].cpu().item()
255
-
256
- detections_raw.append({
257
- 'bbox': [float(x1), float(y1), float(x2), float(y2)],
258
- 'confidence': confidence
259
- })
260
-
261
- stats_no_filter['total'] += len(detections_raw)
262
- stats_no_filter['per_image'].append(len(detections_raw))
263
-
264
- # Universal Filter ์ ์šฉ
265
- detections_scored = apply_universal_filter(detections_raw, image, threshold=0)
266
-
267
- # ๋‹ค์–‘ํ•œ ์ž„๊ณ„๊ฐ’์œผ๋กœ ํ•„ํ„ฐ๋ง ํ…Œ์ŠคํŠธ
268
- for threshold in filter_thresholds:
269
- filtered = [det for det in detections_scored if det['filter_score'] >= threshold]
270
- stats_by_threshold[threshold]['total'] += len(filtered)
271
- stats_by_threshold[threshold]['per_image'].append(len(filtered))
272
-
273
- # ๊ธฐ๋ณธ ์ž„๊ณ„๊ฐ’ 90 ์‚ฌ์šฉ
274
- detections_filtered = [det for det in detections_scored if det['filter_score'] >= 90]
275
- stats_filtered['total'] += len(detections_filtered)
276
- stats_filtered['per_image'].append(len(detections_filtered))
277
-
278
- # ์ฒ˜์Œ 10๊ฐœ ์ด๋ฏธ์ง€๋งŒ ๊ฒฐ๊ณผ ์ €์žฅ
279
- if idx <= 10:
280
- # ๊ฒฐ๊ณผ ์ด๋ฏธ์ง€ ๊ทธ๋ฆฌ๊ธฐ
281
- result_image = image.copy()
282
- draw = ImageDraw.Draw(result_image)
283
-
284
- # ํ•„ํ„ฐ๋ง๋œ ๊ฒ€์ถœ (๋…น์ƒ‰)
285
- for i, det in enumerate(detections_filtered, 1):
286
- x1, y1, x2, y2 = det['bbox']
287
- score = det['filter_score']
288
- conf = det['confidence']
289
-
290
- draw.rectangle([x1, y1, x2, y2], outline="lime", width=8)
291
- label = f"#{i} | F:{score:.0f} C:{conf:.0%}"
292
- bbox_label = draw.textbbox((x1, y1 - 25), label, font=font_tiny)
293
- draw.rectangle(bbox_label, fill="lime")
294
- draw.text((x1, y1 - 25), label, fill="black", font=font_tiny)
295
-
296
- # ์ œ๊ฑฐ๋œ ๊ฒ€์ถœ (๋นจ๊ฐ„์ƒ‰, ๋ฐ˜ํˆฌ๋ช…)
297
- for det in detections_raw:
298
- if det not in [d for d in detections_filtered]:
299
- x1, y1, x2, y2 = det['bbox']
300
- # ํ•„ํ„ฐ ์ ์ˆ˜ ์ฐพ๊ธฐ
301
- scored_det = next((d for d in detections_scored if d['bbox'] == det['bbox']), None)
302
- if scored_det:
303
- score = scored_det['filter_score']
304
- draw.rectangle([x1, y1, x2, y2], outline="red", width=4)
305
- label = f"X:{score:.0f}"
306
- bbox_label = draw.textbbox((x1, y1 - 20), label, font=font_tiny)
307
- draw.rectangle(bbox_label, fill="red")
308
- draw.text((x1, y1 - 20), label, fill="white", font=font_tiny)
309
-
310
- # ํ†ต๊ณ„ ํ…์ŠคํŠธ ์ถ”๊ฐ€
311
- info_text = f"Raw: {len(detections_raw)} | Filtered (90): {len(detections_filtered)}"
312
- draw.text((10, 10), info_text, fill="yellow", font=font)
313
-
314
- # ๊ฒฐ๊ณผ ์ €์žฅ
315
- output_path = os.path.join(output_dir, f"result_{img_name}")
316
- result_image.save(output_path)
317
-
318
- if idx % 10 == 0 or idx == 1:
319
- print(f" Raw: {len(detections_raw)}, Filtered: {len(detections_filtered)}")
320
- print(f" โœ… ์ €์žฅ: {output_path}")
321
-
322
- # ์ตœ์ข… ํ†ต๊ณ„ ์ถœ๋ ฅ
323
- print("\n" + "=" * 60)
324
- print("๐Ÿ“Š ์ „์ฒด ๊ฒฐ๊ณผ (ํ•„ํ„ฐ๋ง ์ „ํ›„ ๋น„๊ต):")
325
- print("=" * 60)
326
- print(f"\n1๏ธโƒฃ ํ•„ํ„ฐ๋ง ์ „ (Raw YOLOv8):")
327
- print(f" - ์ด ๊ฒ€์ถœ: {stats_no_filter['total']}๊ฐœ")
328
- print(f" - ํ‰๊ท : {stats_no_filter['total'] / len(test_images):.1f}๊ฐœ/์ด๋ฏธ์ง€")
329
-
330
- print(f"\n2๏ธโƒฃ ํ•„ํ„ฐ๋ง ํ›„ ์„ฑ๋Šฅ ๋น„๊ต:")
331
- for threshold in filter_thresholds:
332
- total = stats_by_threshold[threshold]['total']
333
- avg = total / len(test_images)
334
- reduction = (1 - total / stats_no_filter['total']) * 100 if stats_no_filter['total'] > 0 else 0
335
- print(f" Threshold {threshold}: {total}๊ฐœ (ํ‰๊ท  {avg:.1f}/์ด๋ฏธ์ง€, -{reduction:.1f}% ๊ฐ์†Œ)")
336
-
337
- print(f"\n3๏ธโƒฃ ๊ถŒ์žฅ ์„ค์ • (Threshold 90):")
338
- print(f" - ์ด ๊ฒ€์ถœ: {stats_filtered['total']}๊ฐœ")
339
- print(f" - ํ‰๊ท : {stats_filtered['total'] / len(test_images):.1f}๊ฐœ/์ด๋ฏธ์ง€")
340
- reduction = (1 - stats_filtered['total'] / stats_no_filter['total']) * 100 if stats_no_filter['total'] > 0 else 0
341
- print(f" - False Positive ๊ฐ์†Œ: {reduction:.1f}%")
342
-
343
- print(f"\n๐Ÿ“ ๊ฒฐ๊ณผ ์ด๋ฏธ์ง€ ์ €์žฅ: {output_dir}/ (์ฒ˜์Œ 10์žฅ)")
344
- print("=" * 60)
345
-
346
- # ํ†ต๊ณ„๋ฅผ JSON์œผ๋กœ ์ €์žฅ
347
- stats_summary = {
348
- 'total_images': len(test_images),
349
- 'no_filter': {
350
- 'total': stats_no_filter['total'],
351
- 'average': stats_no_filter['total'] / len(test_images)
352
- },
353
- 'by_threshold': {}
354
- }
355
-
356
- for threshold in filter_thresholds:
357
- total = stats_by_threshold[threshold]['total']
358
- stats_summary['by_threshold'][threshold] = {
359
- 'total': total,
360
- 'average': total / len(test_images),
361
- 'reduction_pct': (1 - total / stats_no_filter['total']) * 100 if stats_no_filter['total'] > 0 else 0
362
- }
363
-
364
- with open(os.path.join(output_dir, 'filter_statistics.json'), 'w', encoding='utf-8') as f:
365
- json.dump(stats_summary, f, indent=2, ensure_ascii=False)
366
-
367
- print(f"\n๐Ÿ’พ ํ†ต๊ณ„ ์ €์žฅ: {output_dir}/filter_statistics.json")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
train_yolo.py DELETED
@@ -1,127 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- """
3
- YOLOv8 Few-shot Training for Shrimp Detection
4
- ์ตœ์†Œ ๋ฐ์ดํ„ฐ(50๊ฐœ)๋กœ ๋น ๋ฅธ ํ•™์Šต
5
- """
6
- import sys
7
- sys.stdout.reconfigure(encoding='utf-8')
8
-
9
- from ultralytics import YOLO
10
- import torch
11
- from pathlib import Path
12
- import yaml
13
-
14
- def main():
15
- print("=" * 60)
16
- print("๐Ÿฆ YOLOv8 Few-shot Training ์‹œ์ž‘")
17
- print("=" * 60)
18
-
19
- # GPU ํ™•์ธ
20
- device = 'cuda' if torch.cuda.is_available() else 'cpu'
21
- print(f"\n๐Ÿ–ฅ๏ธ Device: {device}")
22
- if device == 'cuda':
23
- print(f" GPU: {torch.cuda.get_device_name(0)}")
24
- print(f" VRAM: {torch.cuda.get_device_properties(0).total_memory / 1024**3:.1f} GB")
25
- else:
26
- print(" โš ๏ธ GPU ์—†์Œ - CPU๋กœ ํ•™์Šต (๋А๋ฆผ)")
27
-
28
- # ๋ฐ์ดํ„ฐ์…‹ ๊ฒฝ๋กœ
29
- data_yaml = "data/yolo_dataset/data.yaml"
30
-
31
- # data.yaml ํ™•์ธ
32
- print(f"\n๐Ÿ“‚ ๋ฐ์ดํ„ฐ์…‹ ์„ค์ • ๋กœ๋”ฉ: {data_yaml}")
33
- with open(data_yaml, 'r', encoding='utf-8') as f:
34
- data_config = yaml.safe_load(f)
35
- print(f" - Classes: {data_config['names']}")
36
- print(f" - Train: {data_config['train']}")
37
- print(f" - Val: {data_config['val']}")
38
-
39
- # YOLOv8 ๋ชจ๋ธ ์„ ํƒ
40
- # yolov8n: nano (๊ฐ€์žฅ ๋น ๋ฆ„, ์ž‘์€ ๋ฐ์ดํ„ฐ์…‹์— ์ ํ•ฉ)
41
- # yolov8s: small
42
- # yolov8m: medium
43
- model_size = 'n' # nano - Few-shot์— ์ตœ์ 
44
- print(f"\n๐Ÿค– ๋ชจ๋ธ: YOLOv8{model_size} (nano)")
45
- print(" - ์ด์œ : ์ž‘์€ ๋ฐ์ดํ„ฐ์…‹(50๊ฐœ)์—์„œ ๊ณผ์ ํ•ฉ ๋ฐฉ์ง€")
46
-
47
- # ๋ชจ๋ธ ๋กœ๋“œ (์‚ฌ์ „ ํ•™์Šต ๊ฐ€์ค‘์น˜ ํฌํ•จ)
48
- model = YOLO(f'yolov8{model_size}.pt')
49
-
50
- # ํ•™์Šต ํ•˜์ดํผํŒŒ๋ผ๋ฏธํ„ฐ
51
- epochs = 100 # Few-shot์ด๋ฏ€๋กœ ๋งŽ์€ epoch
52
- batch_size = 8 # ์ž‘์€ ๋ฐฐ์น˜ (๋ฐ์ดํ„ฐ ์ ์Œ)
53
- imgsz = 640 # ์ž…๋ ฅ ์ด๋ฏธ์ง€ ํฌ๊ธฐ
54
- patience = 20 # Early stopping patience
55
-
56
- print(f"\nโš™๏ธ ํ•™์Šต ์„ค์ •:")
57
- print(f" - Epochs: {epochs}")
58
- print(f" - Batch size: {batch_size}")
59
- print(f" - Image size: {imgsz}")
60
- print(f" - Patience: {patience} (early stopping)")
61
- print(f" - Device: {device}")
62
-
63
- # Data Augmentation ์„ค์ • (Few-shot์— ์ค‘์š”!)
64
- print(f"\n๐Ÿ”„ Data Augmentation:")
65
- augment_params = {
66
- 'hsv_h': 0.015, # Hue augmentation (์ƒ‰์ƒ ๋ณ€ํ™”)
67
- 'hsv_s': 0.7, # Saturation augmentation
68
- 'hsv_v': 0.4, # Value (brightness) augmentation
69
- 'degrees': 10.0, # Rotation
70
- 'translate': 0.1, # Translation
71
- 'scale': 0.5, # Scaling
72
- 'shear': 0.0, # Shear
73
- 'perspective': 0.0, # Perspective
74
- 'flipud': 0.0, # Vertical flip (์ƒˆ์šฐ๋Š” ์„ธ๋กœ ๋ฐฉํ–ฅ ์—†์Œ)
75
- 'fliplr': 0.5, # Horizontal flip (์ขŒ์šฐ๋Š” OK)
76
- 'mosaic': 1.0, # Mosaic augmentation
77
- 'mixup': 0.0, # Mixup augmentation
78
- }
79
- for k, v in augment_params.items():
80
- print(f" - {k}: {v}")
81
-
82
- print(f"\n๐Ÿš€ ํ•™์Šต ์‹œ์ž‘...")
83
- print(" (์ง„ํ–‰ ์ƒํ™ฉ์€ ์ฝ˜์†”์— ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค)")
84
- print("-" * 60)
85
-
86
- # ํ•™์Šต ์‹คํ–‰
87
- results = model.train(
88
- data=data_yaml,
89
- epochs=epochs,
90
- batch=batch_size,
91
- imgsz=imgsz,
92
- device=device,
93
- patience=patience,
94
- save=True,
95
- project='runs/train',
96
- name='shrimp_yolov8n',
97
- exist_ok=True,
98
- pretrained=True,
99
- verbose=True,
100
- # Data augmentation
101
- **augment_params
102
- )
103
-
104
- print("\n" + "=" * 60)
105
- print("โœ… ํ•™์Šต ์™„๋ฃŒ!")
106
- print("=" * 60)
107
-
108
- # ๊ฒฐ๊ณผ ์ถœ๋ ฅ
109
- best_model_path = Path('runs/train/shrimp_yolov8n/weights/best.pt')
110
- last_model_path = Path('runs/train/shrimp_yolov8n/weights/last.pt')
111
-
112
- print(f"\n๐Ÿ“Š ํ•™์Šต ๊ฒฐ๊ณผ:")
113
- print(f" - Best ๋ชจ๋ธ: {best_model_path}")
114
- print(f" - Last ๋ชจ๋ธ: {last_model_path}")
115
- print(f"\n๐Ÿ“ˆ ์„ฑ๋Šฅ ๊ทธ๋ž˜ํ”„:")
116
- print(f" - runs/train/shrimp_yolov8n/results.png")
117
- print(f"\n๐Ÿ“ ์ „์ฒด ๊ฒฐ๊ณผ:")
118
- print(f" - runs/train/shrimp_yolov8n/")
119
-
120
- # Validation ๊ฒฐ๊ณผ ์ถœ๋ ฅ
121
- print(f"\n๐ŸŽฏ ๋‹ค์Œ ๋‹จ๊ณ„:")
122
- print(f" 1. ๊ฒฐ๊ณผ ํ™•์ธ: runs/train/shrimp_yolov8n/")
123
- print(f" 2. ์„ฑ๋Šฅ ํ‰๊ฐ€: python evaluate_yolo.py")
124
- print(f" 3. ์ถ”๋ก  ํ…Œ์ŠคํŠธ: python test_yolo_inference.py")
125
-
126
- if __name__ == "__main__":
127
- main()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
validate_ground_truth.py DELETED
@@ -1,190 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- """
3
- Ground Truth ๊ฒ€์ฆ ์Šคํฌ๋ฆฝํŠธ
4
- ๋ผ๋ฒจ๋ง ๋ฐ์ดํ„ฐ์˜ ํ’ˆ์งˆ๊ณผ ์ผ๊ด€์„ฑ์„ ํ™•์ธ
5
- """
6
- import sys
7
- sys.stdout.reconfigure(encoding='utf-8')
8
-
9
- import json
10
- import os
11
- from pathlib import Path
12
-
13
- def validate_ground_truth(gt_file="ground_truth.json"):
14
- """Ground Truth ๋ฐ์ดํ„ฐ ๊ฒ€์ฆ"""
15
-
16
- print("=" * 60)
17
- print("๐Ÿ” Ground Truth ๊ฒ€์ฆ")
18
- print("=" * 60)
19
-
20
- # ํŒŒ์ผ ์กด์žฌ ํ™•์ธ
21
- if not os.path.exists(gt_file):
22
- print(f"โŒ ํŒŒ์ผ์ด ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค: {gt_file}")
23
- return
24
-
25
- # JSON ๋กœ๋“œ
26
- with open(gt_file, 'r', encoding='utf-8') as f:
27
- data = json.load(f)
28
-
29
- # ๊ธฐ๋ณธ ํ†ต๊ณ„
30
- total_images = len(data)
31
- labeled_images = sum(1 for v in data.values() if len(v) > 0)
32
- empty_images = sum(1 for v in data.values() if len(v) == 0)
33
- total_boxes = sum(len(v) for v in data.values())
34
-
35
- print(f"\n๐Ÿ“Š ๊ธฐ๋ณธ ํ†ต๊ณ„")
36
- print(f"{'โ”€' * 60}")
37
- print(f"์ด ์ด๋ฏธ์ง€: {total_images}๊ฐœ")
38
- print(f"๋ผ๋ฒจ๋ง๋œ ์ด๋ฏธ์ง€: {labeled_images}๊ฐœ ({labeled_images/total_images*100:.1f}%)")
39
- print(f"๋นˆ ์ด๋ฏธ์ง€: {empty_images}๊ฐœ ({empty_images/total_images*100:.1f}%)")
40
- print(f"์ด ๋ฐ”์šด๋”ฉ ๋ฐ•์Šค: {total_boxes}๊ฐœ")
41
- if labeled_images > 0:
42
- print(f"์ด๋ฏธ์ง€๋‹น ํ‰๊ท : {total_boxes/labeled_images:.2f}๊ฐœ")
43
-
44
- # ํด๋”๋ณ„ ํ†ต๊ณ„
45
- folders = {}
46
- for filename, boxes in data.items():
47
- if boxes:
48
- folder = boxes[0].get('folder', 'unknown')
49
- if folder not in folders:
50
- folders[folder] = {'images': 0, 'boxes': 0}
51
- folders[folder]['images'] += 1
52
- folders[folder]['boxes'] += len(boxes)
53
-
54
- if folders:
55
- print(f"\n๐Ÿ“ ํด๋”๋ณ„ ํ†ต๊ณ„")
56
- print(f"{'โ”€' * 60}")
57
- for folder, stats in sorted(folders.items()):
58
- print(f"{folder}: {stats['images']}์žฅ, {stats['boxes']}๋ฐ•์Šค (ํ‰๊ท  {stats['boxes']/stats['images']:.1f}๊ฐœ/์ด๋ฏธ์ง€)")
59
-
60
- # ์‹ ๋ขฐ๋„ ๋ถ„์„
61
- confidences = []
62
- for boxes in data.values():
63
- for box in boxes:
64
- if 'confidence' in box:
65
- confidences.append(box['confidence'])
66
-
67
- if confidences:
68
- print(f"\n๐ŸŽฏ ์‹ ๋ขฐ๋„ ๋ถ„์„")
69
- print(f"{'โ”€' * 60}")
70
- print(f"ํ‰๊ท  ์‹ ๋ขฐ๋„: {sum(confidences)/len(confidences):.3f}")
71
- print(f"์ตœ์†Œ ์‹ ๋ขฐ๋„: {min(confidences):.3f}")
72
- print(f"์ตœ๋Œ€ ์‹ ๋ขฐ๋„: {max(confidences):.3f}")
73
-
74
- # ์‹ ๋ขฐ๋„ ๋ถ„ํฌ
75
- low = sum(1 for c in confidences if c < 0.2)
76
- mid = sum(1 for c in confidences if 0.2 <= c < 0.4)
77
- high = sum(1 for c in confidences if c >= 0.4)
78
- print(f"\n์‹ ๋ขฐ๋„ ๋ถ„ํฌ:")
79
- print(f" < 0.2: {low}๊ฐœ ({low/len(confidences)*100:.1f}%)")
80
- print(f" 0.2 ~ 0.4: {mid}๊ฐœ ({mid/len(confidences)*100:.1f}%)")
81
- print(f" >= 0.4: {high}๊ฐœ ({high/len(confidences)*100:.1f}%)")
82
-
83
- # ๋ฐ•์Šค ํฌ๊ธฐ ๋ถ„์„
84
- print(f"\n๐Ÿ“ ๋ฐ•์Šค ํฌ๊ธฐ ๋ถ„์„")
85
- print(f"{'โ”€' * 60}")
86
-
87
- box_areas = []
88
- box_widths = []
89
- box_heights = []
90
- aspect_ratios = []
91
-
92
- for boxes in data.values():
93
- for box in boxes:
94
- bbox = box['bbox']
95
- x1, y1, x2, y2 = bbox
96
- width = x2 - x1
97
- height = y2 - y1
98
- area = width * height
99
-
100
- box_areas.append(area)
101
- box_widths.append(width)
102
- box_heights.append(height)
103
- if height > 0:
104
- aspect_ratios.append(width / height)
105
-
106
- if box_areas:
107
- print(f"ํ‰๊ท  ๋ฉด์ : {sum(box_areas)/len(box_areas):.0f} pxยฒ")
108
- print(f"ํ‰๊ท  ๋„ˆ๋น„: {sum(box_widths)/len(box_widths):.0f} px")
109
- print(f"ํ‰๊ท  ๋†’์ด: {sum(box_heights)/len(box_heights):.0f} px")
110
- print(f"ํ‰๊ท  ์ข…ํšก๋น„: {sum(aspect_ratios)/len(aspect_ratios):.2f}")
111
- print(f" (์ƒˆ์šฐ๋Š” ๋ณดํ†ต 3:1 ~ 10:1)")
112
-
113
- # ์ƒ์„ธ ๋ฐ์ดํ„ฐ (์ฒ˜์Œ 5๊ฐœ)
114
- print(f"\n๐Ÿ“‹ ์ƒ์„ธ ๋ฐ์ดํ„ฐ (์ฒ˜์Œ 5๊ฐœ)")
115
- print(f"{'โ”€' * 60}")
116
-
117
- count = 0
118
- for filename, boxes in data.items():
119
- if count >= 5:
120
- break
121
-
122
- print(f"\n{filename}")
123
- if not boxes:
124
- print(" - ๋ฐ•์Šค ์—†์Œ (๋นˆ ์ด๋ฏธ์ง€ ๋˜๋Š” ๊ฑด๋„ˆ๋›ฐ๊ธฐ)")
125
- else:
126
- for idx, box in enumerate(boxes, 1):
127
- bbox = box['bbox']
128
- x1, y1, x2, y2 = bbox
129
- width = x2 - x1
130
- height = y2 - y1
131
- conf = box.get('confidence', 0)
132
- print(f" #{idx}: bbox=[{x1:.0f}, {y1:.0f}, {x2:.0f}, {y2:.0f}], "
133
- f"ํฌ๊ธฐ={width:.0f}x{height:.0f}, ์‹ ๋ขฐ๋„={conf:.3f}")
134
- count += 1
135
-
136
- # ๊ฒ€์ฆ ๊ฒฐ๊ณผ
137
- print(f"\n{'=' * 60}")
138
- print(f"โœ… ๊ฒ€์ฆ ๊ฒฐ๊ณผ")
139
- print(f"{'=' * 60}")
140
-
141
- issues = []
142
-
143
- # 1. ๋ฐ์ดํ„ฐ๊ฐ€ ๋„ˆ๋ฌด ์ ์€์ง€ ํ™•์ธ
144
- if total_images < 10:
145
- issues.append(f"โš ๏ธ ์ด๋ฏธ์ง€ ์ˆ˜๊ฐ€ ์ ์Šต๋‹ˆ๋‹ค ({total_images}๊ฐœ). ์ตœ์†Œ 50๊ฐœ ๊ถŒ์žฅ")
146
-
147
- # 2. ๋ผ๋ฒจ๋ง ๋น„์œจ ํ™•์ธ
148
- if labeled_images / total_images < 0.5:
149
- issues.append(f"โš ๏ธ ๋ผ๋ฒจ๋ง ๋น„์œจ์ด ๋‚ฎ์Šต๋‹ˆ๋‹ค ({labeled_images/total_images*100:.1f}%). 50% ์ด์ƒ ๊ถŒ์žฅ")
150
-
151
- # 3. ํ‰๊ท  ๋ฐ•์Šค ์ˆ˜ ํ™•์ธ
152
- if labeled_images > 0 and total_boxes / labeled_images < 0.5:
153
- issues.append(f"โš ๏ธ ์ด๋ฏธ์ง€๋‹น ํ‰๊ท  ๋ฐ•์Šค ์ˆ˜๊ฐ€ ์ ์Šต๋‹ˆ๋‹ค ({total_boxes/labeled_images:.2f}๊ฐœ)")
154
-
155
- # 4. ์‹ ๋ขฐ๋„ ํ™•์ธ
156
- if confidences and sum(confidences)/len(confidences) < 0.2:
157
- issues.append(f"โš ๏ธ ํ‰๊ท  ์‹ ๋ขฐ๋„๊ฐ€ ๋‚ฎ์Šต๋‹ˆ๋‹ค ({sum(confidences)/len(confidences):.3f}). ๊ฒ€์ถœ ํ’ˆ์งˆ ํ™•์ธ ํ•„์š”")
158
-
159
- if issues:
160
- print("\n๋ฌธ์ œ์ :")
161
- for issue in issues:
162
- print(f" {issue}")
163
- else:
164
- print("\nโœ… ๋ชจ๋“  ๊ฒ€์ฆ ํ†ต๊ณผ!")
165
-
166
- # ๋‹ค์Œ ๋‹จ๊ณ„ ์ œ์•ˆ
167
- print(f"\n{'=' * 60}")
168
- print(f"๐Ÿ“ ๋‹ค์Œ ๋‹จ๊ณ„")
169
- print(f"{'=' * 60}")
170
-
171
- if total_images < 50:
172
- print(f"\n1. ๋” ๋งŽ์€ ์ด๋ฏธ์ง€ ๋ผ๋ฒจ๋ง")
173
- print(f" - ํ˜„์žฌ: {total_images}์žฅ")
174
- print(f" - ๋ชฉํ‘œ: 50์žฅ ์ด์ƒ (Phase 1)")
175
- print(f" - ๊ถŒ์žฅ: 100~200์žฅ (Phase 2~3)")
176
-
177
- if labeled_images > 10:
178
- print(f"\n2. ์ •๋Ÿ‰์  ํ‰๊ฐ€ ์‹คํ–‰")
179
- print(f" ```bash")
180
- print(f" python test_quantitative_evaluation.py")
181
- print(f" ```")
182
-
183
- print(f"\n3. ๊ณ„์† ๋ผ๋ฒจ๋ง")
184
- print(f" - ๋ธŒ๋ผ์šฐ์ €์—์„œ http://localhost:7862 ์ ‘์†")
185
- print(f" - ๋” ๋งŽ์€ ํด๋” ์ž‘์—…")
186
-
187
- print(f"\n{'=' * 60}\n")
188
-
189
- if __name__ == "__main__":
190
- validate_ground_truth()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
visualize_yolo_dataset.py DELETED
@@ -1,135 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- """
3
- YOLO ๋ฐ์ดํ„ฐ์…‹ ๋ฐ”์šด๋”ฉ ๋ฐ•์Šค ์‹œ๊ฐํ™”
4
- """
5
- import sys
6
- sys.stdout.reconfigure(encoding='utf-8')
7
-
8
- import os
9
- from PIL import Image, ImageDraw, ImageFont
10
- from pathlib import Path
11
- import random
12
-
13
- def visualize_yolo_annotation(img_path, label_path, output_path):
14
- """YOLO ํ˜•์‹ ๋ผ๋ฒจ์„ ์ด๋ฏธ์ง€์— ๊ทธ๋ฆฌ๊ธฐ"""
15
-
16
- # ์ด๋ฏธ์ง€ ๋กœ๋“œ
17
- img = Image.open(img_path)
18
- draw = ImageDraw.Draw(img)
19
- img_width, img_height = img.size
20
-
21
- # ํฐํŠธ ์„ค์ •
22
- try:
23
- font = ImageFont.truetype("arial.ttf", 30)
24
- font_small = ImageFont.truetype("arial.ttf", 20)
25
- except:
26
- font = ImageFont.load_default()
27
- font_small = ImageFont.load_default()
28
-
29
- # ๋ผ๋ฒจ ํŒŒ์ผ ์ฝ๊ธฐ
30
- if not os.path.exists(label_path):
31
- print(f"โš ๏ธ ๋ผ๋ฒจ ํŒŒ์ผ ์—†์Œ: {label_path}")
32
- return False
33
-
34
- with open(label_path, 'r') as f:
35
- lines = f.readlines()
36
-
37
- print(f"\n๐Ÿ“„ {os.path.basename(img_path)}")
38
- print(f" ์ด๋ฏธ์ง€ ํฌ๊ธฐ: {img_width} x {img_height}")
39
- print(f" ๋ฐ•์Šค ๊ฐœ์ˆ˜: {len(lines)}")
40
-
41
- # ๊ฐ ๋ฐ•์Šค ๊ทธ๋ฆฌ๊ธฐ
42
- for idx, line in enumerate(lines, 1):
43
- parts = line.strip().split()
44
- if len(parts) != 5:
45
- continue
46
-
47
- class_id = int(parts[0])
48
- x_center_norm = float(parts[1])
49
- y_center_norm = float(parts[2])
50
- width_norm = float(parts[3])
51
- height_norm = float(parts[4])
52
-
53
- # ์ •๊ทœํ™”๋œ ์ขŒํ‘œ๋ฅผ ํ”ฝ์…€ ์ขŒํ‘œ๋กœ ๋ณ€ํ™˜
54
- x_center = x_center_norm * img_width
55
- y_center = y_center_norm * img_height
56
- width = width_norm * img_width
57
- height = height_norm * img_height
58
-
59
- # ๋ฐ”์šด๋”ฉ ๋ฐ•์Šค ์ขŒํ‘œ ๊ณ„์‚ฐ
60
- x1 = x_center - width / 2
61
- y1 = y_center - height / 2
62
- x2 = x_center + width / 2
63
- y2 = y_center + height / 2
64
-
65
- # ๋ฐ•์Šค ๊ทธ๋ฆฌ๊ธฐ (๋…น์ƒ‰, ๋‘๊ป๊ฒŒ)
66
- draw.rectangle([x1, y1, x2, y2], outline="lime", width=8)
67
-
68
- # ๋ผ๋ฒจ (๊ฒ€์€ ๋ฐฐ๊ฒฝ์— ํฐ ๊ธ€์”จ)
69
- label = f"Shrimp #{idx}"
70
- bbox = draw.textbbox((x1, y1 - 40), label, font=font)
71
- draw.rectangle([bbox[0]-5, bbox[1]-5, bbox[2]+5, bbox[3]+5], fill="lime")
72
- draw.text((x1, y1 - 40), label, fill="black", font=font)
73
-
74
- # ์ขŒํ‘œ ์ •๋ณด (์ž‘๊ฒŒ)
75
- info = f"YOLO: {x_center_norm:.3f} {y_center_norm:.3f} {width_norm:.3f} {height_norm:.3f}"
76
- draw.text((x1, y2 + 10), info, fill="lime", font=font_small)
77
-
78
- print(f" #{idx}: center=({x_center:.0f}, {y_center:.0f}), size=({width:.0f}x{height:.0f})")
79
-
80
- # ์ €์žฅ
81
- img.save(output_path, quality=95)
82
- print(f" โœ… ์ €์žฅ: {output_path}")
83
-
84
- return True
85
-
86
- def main():
87
- print("=" * 60)
88
- print("๐Ÿ“Š YOLO ๋ฐ์ดํ„ฐ์…‹ ๋ฐ”์šด๋”ฉ ๋ฐ•์Šค ์‹œ๊ฐํ™”")
89
- print("=" * 60)
90
-
91
- # ๊ฒฝ๋กœ ์„ค์ •
92
- dataset_dir = Path("data/yolo_dataset")
93
- train_img_dir = dataset_dir / "images" / "train"
94
- train_label_dir = dataset_dir / "labels" / "train"
95
- output_dir = Path("data/yolo_visualization")
96
- output_dir.mkdir(exist_ok=True)
97
-
98
- # Train ์ด๋ฏธ์ง€ ๋ฆฌ์ŠคํŠธ
99
- img_files = list(train_img_dir.glob("*.jpg"))
100
-
101
- if not img_files:
102
- print("โŒ Train ์ด๋ฏธ์ง€ ์—†์Œ!")
103
- return
104
-
105
- print(f"\n๐Ÿ“ Train ์ด๋ฏธ์ง€: {len(img_files)}๊ฐœ")
106
-
107
- # ๋žœ๋ค 3๊ฐœ ์ƒ˜ํ”Œ
108
- random.seed(42)
109
- samples = random.sample(img_files, min(3, len(img_files)))
110
-
111
- print(f"\n๐ŸŽฒ ๋žœ๋ค ์ƒ˜ํ”Œ 3๊ฐœ ์‹œ๊ฐํ™”:")
112
-
113
- for img_path in samples:
114
- # ๋Œ€์‘ํ•˜๋Š” ๋ผ๋ฒจ ํŒŒ์ผ
115
- label_filename = img_path.stem + ".txt"
116
- label_path = train_label_dir / label_filename
117
-
118
- # ์ถœ๋ ฅ ํŒŒ์ผ
119
- output_path = output_dir / f"{img_path.stem}_visualized.jpg"
120
-
121
- # ์‹œ๊ฐํ™”
122
- visualize_yolo_annotation(img_path, label_path, output_path)
123
-
124
- print("\n" + "=" * 60)
125
- print("โœ… ์‹œ๊ฐํ™” ์™„๋ฃŒ!")
126
- print("=" * 60)
127
- print(f"\n๐Ÿ“ ์ถœ๋ ฅ ๋””๋ ‰ํ† ๋ฆฌ: {output_dir}")
128
- print(f"\n์ƒ์„ฑ๋œ ํŒŒ์ผ:")
129
- for file in sorted(output_dir.glob("*_visualized.jpg")):
130
- print(f" - {file.name}")
131
-
132
- print(f"\n๐Ÿ’ก ์ด๋ฏธ์ง€๋ฅผ ์—ด์–ด์„œ ๋ฐ”์šด๋”ฉ ๋ฐ•์Šค๊ฐ€ ์˜ฌ๋ฐ”๋ฅธ์ง€ ํ™•์ธํ•˜์„ธ์š”!")
133
-
134
- if __name__ == "__main__":
135
- main()