|
|
|
|
|
""" |
|
|
๐ฆ ์์ฐ ๊ฒ์ถ ํตํฉ ์์คํ
|
|
|
3๊ฐ์ ์ฑ์ ํ๋๋ก ํตํฉ: ์๋ ๊ฒ์ถ, ๋ผ๋ฒจ๋ง ๋๊ตฌ, ๋ฐ๋ชจ |
|
|
RT-DETR ๋๋ VIDraft/Shrimp ํด๋ผ์ฐ๋ ๋ชจ๋ธ ์ ํ ๊ฐ๋ฅ |
|
|
""" |
|
|
import sys |
|
|
sys.stdout.reconfigure(encoding='utf-8') |
|
|
|
|
|
import gradio as gr |
|
|
from PIL import Image, ImageDraw, ImageFont |
|
|
import numpy as np |
|
|
import json |
|
|
import os |
|
|
import glob |
|
|
from datetime import datetime |
|
|
import torch |
|
|
from transformers import RTDetrForObjectDetection, RTDetrImageProcessor |
|
|
import requests |
|
|
import base64 |
|
|
from io import BytesIO |
|
|
from inference_sdk import InferenceHTTPClient |
|
|
import tempfile |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import cv2 |
|
|
|
|
|
def load_rtdetr_model(): |
|
|
"""RT-DETR ๋ชจ๋ธ ๋ก๋""" |
|
|
print("๐ RT-DETR ๋ชจ๋ธ ๋ก๋ฉ ์ค...") |
|
|
processor = RTDetrImageProcessor.from_pretrained("PekingU/rtdetr_r50vd_coco_o365") |
|
|
model = RTDetrForObjectDetection.from_pretrained("PekingU/rtdetr_r50vd_coco_o365") |
|
|
model.eval() |
|
|
print("โ
RT-DETR ๋ก๋ฉ ์๋ฃ") |
|
|
return processor, model |
|
|
|
|
|
def detect_with_rtdetr(image, processor, model, confidence=0.3): |
|
|
"""RT-DETR๋ก ๊ฐ์ฒด ๊ฒ์ถ""" |
|
|
inputs = processor(images=image, return_tensors="pt") |
|
|
with torch.no_grad(): |
|
|
outputs = model(**inputs) |
|
|
|
|
|
target_sizes = torch.tensor([image.size[::-1]]) |
|
|
results = processor.post_process_object_detection( |
|
|
outputs, |
|
|
target_sizes=target_sizes, |
|
|
threshold=confidence |
|
|
)[0] |
|
|
|
|
|
detections = [] |
|
|
for score, label, box in zip(results["scores"], results["labels"], results["boxes"]): |
|
|
x1, y1, x2, y2 = box.tolist() |
|
|
detections.append({ |
|
|
'bbox': [x1, y1, x2, y2], |
|
|
'confidence': score.item(), |
|
|
'label': label.item() |
|
|
}) |
|
|
|
|
|
return detections |
|
|
|
|
|
def calculate_morphological_features(bbox, image_size): |
|
|
"""ํํํ์ ํน์ง ๊ณ์ฐ""" |
|
|
x1, y1, x2, y2 = bbox |
|
|
width = x2 - x1 |
|
|
height = y2 - y1 |
|
|
|
|
|
|
|
|
aspect_ratio = max(width, height) / max(min(width, height), 1) |
|
|
|
|
|
|
|
|
img_w, img_h = image_size |
|
|
area_ratio = (width * height) / (img_w * img_h) |
|
|
|
|
|
|
|
|
perimeter = 2 * (width + height) |
|
|
compactness = (4 * np.pi * width * height) / max(perimeter ** 2, 1) |
|
|
|
|
|
return { |
|
|
'aspect_ratio': aspect_ratio, |
|
|
'area_ratio': area_ratio, |
|
|
'compactness': compactness, |
|
|
'width': width, |
|
|
'height': height |
|
|
} |
|
|
|
|
|
def calculate_visual_features(image_pil, bbox): |
|
|
"""์๊ฐ์ ํน์ง ๊ณ์ฐ (์์, ํ
์ค์ฒ)""" |
|
|
|
|
|
image_cv = cv2.cvtColor(np.array(image_pil), cv2.COLOR_RGB2BGR) |
|
|
x1, y1, x2, y2 = [int(v) for v in bbox] |
|
|
|
|
|
|
|
|
roi = image_cv[y1:y2, x1:x2] |
|
|
if roi.size == 0: |
|
|
return {'hue': 100, 'saturation': 255, 'color_std': 255} |
|
|
|
|
|
|
|
|
hsv = cv2.cvtColor(roi, cv2.COLOR_BGR2HSV) |
|
|
|
|
|
|
|
|
hue_mean = np.mean(hsv[:, :, 0]) |
|
|
|
|
|
|
|
|
saturation = np.mean(hsv[:, :, 1]) |
|
|
|
|
|
|
|
|
color_std = np.std(hsv[:, :, 0]) |
|
|
|
|
|
return { |
|
|
'hue': hue_mean, |
|
|
'saturation': saturation, |
|
|
'color_std': color_std |
|
|
} |
|
|
|
|
|
def apply_universal_filter(detections, image, threshold=90): |
|
|
"""๋ฒ์ฉ ์์ฐ ํํฐ ์ ์ฉ""" |
|
|
img_size = image.size |
|
|
filtered = [] |
|
|
|
|
|
for det in detections: |
|
|
bbox = det['bbox'] |
|
|
|
|
|
|
|
|
morph = calculate_morphological_features(bbox, img_size) |
|
|
|
|
|
|
|
|
visual = calculate_visual_features(image, bbox) |
|
|
|
|
|
|
|
|
score = 0 |
|
|
reasons = [] |
|
|
|
|
|
|
|
|
if 4.0 <= morph['aspect_ratio'] <= 9.0: |
|
|
score += 25 |
|
|
reasons.append(f"โ ์ข
ํก๋น {morph['aspect_ratio']:.1f}") |
|
|
elif 3.0 <= morph['aspect_ratio'] < 4.0 or 9.0 < morph['aspect_ratio'] <= 10.0: |
|
|
score += 12 |
|
|
reasons.append(f"โณ ์ข
ํก๋น {morph['aspect_ratio']:.1f}") |
|
|
else: |
|
|
score -= 5 |
|
|
reasons.append(f"โ ์ข
ํก๋น {morph['aspect_ratio']:.1f}") |
|
|
|
|
|
|
|
|
if morph['compactness'] < 0.40: |
|
|
score += 30 |
|
|
reasons.append(f"โ ์ธ์ฅ๋ {morph['compactness']:.2f}") |
|
|
elif 0.40 <= morph['compactness'] < 0.50: |
|
|
score += 15 |
|
|
reasons.append(f"โณ ์ธ์ฅ๋ {morph['compactness']:.2f}") |
|
|
else: |
|
|
reasons.append(f"โ ์ธ์ฅ๋ {morph['compactness']:.2f}") |
|
|
score -= 20 |
|
|
|
|
|
|
|
|
abs_area = morph['width'] * morph['height'] |
|
|
if 50000 <= abs_area <= 500000: |
|
|
score += 35 |
|
|
reasons.append(f"โ ๋ฉด์ {abs_area/1000:.0f}K") |
|
|
elif 500000 < abs_area <= 800000: |
|
|
score -= 10 |
|
|
reasons.append(f"โณ ๋ฉด์ {abs_area/1000:.0f}K") |
|
|
elif abs_area > 800000: |
|
|
score -= 30 |
|
|
reasons.append(f"โ ๋ฉด์ {abs_area/1000:.0f}K (๋๋ฌดํผ)") |
|
|
else: |
|
|
score -= 10 |
|
|
reasons.append(f"โ ๋ฉด์ {abs_area/1000:.0f}K (๋๋ฌด์์)") |
|
|
|
|
|
|
|
|
hue = visual['hue'] |
|
|
if hue < 40 or hue > 130: |
|
|
score += 10 |
|
|
reasons.append(f"โ ์์ {hue:.0f}") |
|
|
elif 90 <= hue <= 130: |
|
|
score -= 5 |
|
|
reasons.append(f"โ ์์ {hue:.0f} (๋ฐฐ๊ฒฝ)") |
|
|
else: |
|
|
reasons.append(f"โณ ์์ {hue:.0f}") |
|
|
|
|
|
|
|
|
if visual['saturation'] < 85: |
|
|
score += 20 |
|
|
reasons.append(f"โ ์ฑ๋ {visual['saturation']:.0f}") |
|
|
elif 85 <= visual['saturation'] < 120: |
|
|
score += 5 |
|
|
reasons.append(f"โณ ์ฑ๋ {visual['saturation']:.0f}") |
|
|
else: |
|
|
score -= 15 |
|
|
reasons.append(f"โ ์ฑ๋ {visual['saturation']:.0f} (๋์)") |
|
|
|
|
|
|
|
|
if visual['color_std'] < 50: |
|
|
score += 15 |
|
|
reasons.append(f"โ ์์์ผ๊ด์ฑ {visual['color_std']:.1f}") |
|
|
elif 50 <= visual['color_std'] < 80: |
|
|
score += 5 |
|
|
reasons.append(f"โณ ์์์ผ๊ด์ฑ {visual['color_std']:.1f}") |
|
|
else: |
|
|
score -= 10 |
|
|
reasons.append(f"โ ์์์ผ๊ด์ฑ {visual['color_std']:.1f} (๋ถ์ผ์น)") |
|
|
|
|
|
|
|
|
if 'confidence' in det: |
|
|
if det['confidence'] >= 0.3: |
|
|
score += 15 |
|
|
reasons.append(f"โ ์ ๋ขฐ๋ {det['confidence']:.0%}") |
|
|
elif det['confidence'] >= 0.1: |
|
|
score += 8 |
|
|
reasons.append(f"โณ ์ ๋ขฐ๋ {det['confidence']:.0%}") |
|
|
else: |
|
|
reasons.append(f"โ ์ ๋ขฐ๋ {det['confidence']:.0%}") |
|
|
|
|
|
det['filter_score'] = score |
|
|
det['filter_reasons'] = reasons |
|
|
filtered.append(det) |
|
|
|
|
|
return filtered |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
YOLO_MODEL_PATH = "runs/train/yolov8m_shrimp2/weights/best.pt" |
|
|
yolo_model = None |
|
|
YOLO_AVAILABLE = False |
|
|
|
|
|
try: |
|
|
from ultralytics import YOLO |
|
|
import os |
|
|
if os.path.exists(YOLO_MODEL_PATH): |
|
|
YOLO_AVAILABLE = True |
|
|
print(f"โ
YOLOv8 ์ฌ์ฉ ๊ฐ๋ฅ: {YOLO_MODEL_PATH}") |
|
|
else: |
|
|
print(f"โ ๏ธ YOLOv8 ๋ชจ๋ธ ํ์ผ ์์: {YOLO_MODEL_PATH}") |
|
|
except ImportError: |
|
|
print("โ ๏ธ ultralytics ํจํค์ง ์์ - YOLOv8 ๋นํ์ฑํ") |
|
|
|
|
|
def load_yolo_model(): |
|
|
"""YOLOv8 ๋ชจ๋ธ ๋ก๋ฉ""" |
|
|
global yolo_model |
|
|
if not YOLO_AVAILABLE: |
|
|
raise Exception("YOLOv8 ๋ชจ๋ธ์ ์ฌ์ฉํ ์ ์์ต๋๋ค") |
|
|
if yolo_model is None: |
|
|
print(f"๐ YOLOv8 ๋ชจ๋ธ ๋ก๋ฉ ์ค: {YOLO_MODEL_PATH}") |
|
|
yolo_model = YOLO(YOLO_MODEL_PATH) |
|
|
print("โ
YOLOv8 ๋ชจ๋ธ ๋ก๋ฉ ์๋ฃ") |
|
|
return yolo_model |
|
|
|
|
|
def detect_with_yolo(image, confidence=0.1): |
|
|
"""YOLOv8 ๋ชจ๋ธ๋ก ๊ฒ์ถ""" |
|
|
try: |
|
|
model = load_yolo_model() |
|
|
|
|
|
|
|
|
results = model.predict( |
|
|
source=image, |
|
|
conf=confidence, |
|
|
verbose=False |
|
|
) |
|
|
|
|
|
detections = [] |
|
|
for result in results: |
|
|
boxes = result.boxes |
|
|
for box in boxes: |
|
|
x1, y1, x2, y2 = box.xyxy[0].tolist() |
|
|
conf = box.conf[0].item() |
|
|
|
|
|
detections.append({ |
|
|
'bbox': [x1, y1, x2, y2], |
|
|
'confidence': conf |
|
|
}) |
|
|
|
|
|
print(f"โ
YOLOv8 ๊ฒ์ถ ์๋ฃ: {len(detections)}๊ฐ") |
|
|
return detections |
|
|
|
|
|
except Exception as e: |
|
|
print(f"โ YOLOv8 ๊ฒ์ถ ์ค๋ฅ: {str(e)}") |
|
|
import traceback |
|
|
traceback.print_exc() |
|
|
return [] |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ROBOFLOW_API_KEY = os.getenv("ROBOFLOW_API_KEY", "") |
|
|
|
|
|
|
|
|
roboflow_client = InferenceHTTPClient( |
|
|
api_url="https://serverless.roboflow.com", |
|
|
api_key=ROBOFLOW_API_KEY |
|
|
) |
|
|
|
|
|
def detect_with_roboflow(image, confidence=0.065): |
|
|
"""Roboflow API๋ฅผ ์ฌ์ฉํ ์ต์ ํ๋ ๊ฒ์ถ (๋ก์ปฌ ํ
์คํธ์ ๋์ผ)""" |
|
|
try: |
|
|
|
|
|
image_original = image |
|
|
original_size = image_original.size |
|
|
|
|
|
|
|
|
image_resized = image_original.copy() |
|
|
image_resized.thumbnail((640, 640), Image.Resampling.LANCZOS) |
|
|
print(f"๐ ์ด๋ฏธ์ง ๋ฆฌ์ฌ์ด์ฆ: {original_size} โ {image_resized.size}") |
|
|
|
|
|
|
|
|
buffered = BytesIO() |
|
|
image_resized.save(buffered, format="JPEG", quality=80) |
|
|
img_base64 = base64.b64encode(buffered.getvalue()).decode() |
|
|
print(f"๐ฆ Base64 ํฌ๊ธฐ: {len(img_base64)} bytes") |
|
|
|
|
|
print(f"๐ Roboflow API ์ถ๋ก ์์...") |
|
|
|
|
|
|
|
|
response = requests.post( |
|
|
'https://serverless.roboflow.com/vidraft/workflows/find-shrimp-6', |
|
|
headers={'Content-Type': 'application/json'}, |
|
|
json={ |
|
|
'api_key': ROBOFLOW_API_KEY, |
|
|
'inputs': { |
|
|
'image': {'type': 'base64', 'value': img_base64} |
|
|
} |
|
|
}, |
|
|
timeout=30 |
|
|
) |
|
|
|
|
|
if response.status_code != 200: |
|
|
print(f"โ Roboflow API ์ค๋ฅ: {response.status_code}") |
|
|
print(f"์๋ต: {response.text}") |
|
|
return [] |
|
|
|
|
|
result = response.json() |
|
|
print(f"๐ Roboflow ์๋ต: {json.dumps(result, indent=2, ensure_ascii=False)[:500]}...") |
|
|
|
|
|
|
|
|
detections = [] |
|
|
predictions = [] |
|
|
|
|
|
|
|
|
if isinstance(result, dict) and 'outputs' in result and len(result['outputs']) > 0: |
|
|
output = result['outputs'][0] |
|
|
if isinstance(output, dict) and 'predictions' in output: |
|
|
pred_data = output['predictions'] |
|
|
|
|
|
if isinstance(pred_data, dict) and 'predictions' in pred_data: |
|
|
predictions = pred_data['predictions'] |
|
|
|
|
|
elif isinstance(pred_data, list): |
|
|
predictions = pred_data |
|
|
else: |
|
|
predictions = [pred_data] |
|
|
|
|
|
|
|
|
elif isinstance(result, dict) and 'predictions' in result: |
|
|
predictions = result['predictions'] |
|
|
|
|
|
|
|
|
elif isinstance(result, list): |
|
|
predictions = result |
|
|
|
|
|
print(f"๐ฆ ์ฐพ์ predictions: {len(predictions)}๊ฐ") |
|
|
|
|
|
|
|
|
scale_x = original_size[0] / image_resized.size[0] |
|
|
scale_y = original_size[1] / image_resized.size[1] |
|
|
print(f"๐ ์ค์ผ์ผ: x={scale_x:.2f}, y={scale_y:.2f}") |
|
|
|
|
|
for pred in predictions: |
|
|
|
|
|
pred_class = pred.get('class', '') |
|
|
if pred_class != 'shrimp': |
|
|
continue |
|
|
|
|
|
|
|
|
pred_confidence = pred.get('confidence', 0) |
|
|
if pred_confidence < confidence: |
|
|
continue |
|
|
|
|
|
|
|
|
x = pred.get('x', 0) |
|
|
y = pred.get('y', 0) |
|
|
width = pred.get('width', 0) |
|
|
height = pred.get('height', 0) |
|
|
|
|
|
|
|
|
x_scaled = x * scale_x |
|
|
y_scaled = y * scale_y |
|
|
width_scaled = width * scale_x |
|
|
height_scaled = height * scale_y |
|
|
|
|
|
|
|
|
x1 = x_scaled - width_scaled / 2 |
|
|
y1 = y_scaled - height_scaled / 2 |
|
|
x2 = x_scaled + width_scaled / 2 |
|
|
y2 = y_scaled + height_scaled / 2 |
|
|
|
|
|
detections.append({ |
|
|
'bbox': [x1, y1, x2, y2], |
|
|
'confidence': pred_confidence |
|
|
}) |
|
|
print(f" โ ๊ฒ์ถ (shrimp): conf={pred_confidence:.2%}, bbox=[{x1:.0f},{y1:.0f},{x2:.0f},{y2:.0f}]") |
|
|
|
|
|
print(f"โ
Roboflow ๊ฒ์ถ ์๋ฃ: {len(detections)}๊ฐ") |
|
|
return detections |
|
|
|
|
|
except Exception as e: |
|
|
print(f"โ Roboflow SDK ์ค๋ฅ: {str(e)}") |
|
|
import traceback |
|
|
traceback.print_exc() |
|
|
return [] |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
processor = None |
|
|
model = None |
|
|
|
|
|
def load_rtdetr_on_demand(): |
|
|
"""RT-DETR ๋ชจ๋ธ์ ํ์์์๋ง ๋ก๋ฉ""" |
|
|
global processor, model |
|
|
if processor is None or model is None: |
|
|
processor, model = load_rtdetr_model() |
|
|
return "โ
RT-DETR ๋ชจ๋ธ ๋ก๋ฉ ์๋ฃ" |
|
|
else: |
|
|
return "โน๏ธ RT-DETR ๋ชจ๋ธ์ด ์ด๋ฏธ ๋ก๋ฉ๋์ด ์์ต๋๋ค" |
|
|
|
|
|
print("โ
VIDraft/Shrimp ํด๋ผ์ฐ๋ ๋ชจ๋ธ ์ฌ์ฉ ๊ฐ๋ฅ\n") |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
current_data = { |
|
|
'folder': None, |
|
|
'images': [], |
|
|
'current_idx': 0, |
|
|
'detections': {}, |
|
|
'selections': {}, |
|
|
'confidence_threshold': 0.2, |
|
|
'image_cache': {}, |
|
|
'model_type': 'RT-DETR' |
|
|
} |
|
|
|
|
|
GROUND_TRUTH_FILE = "ground_truth.json" |
|
|
DATA_BASE = "data/ํฐ๋ค๋ฆฌ์์ฐ ์ค์ธก ๋ฐ์ดํฐ_์ตํฌ์ค์์ด์์ด(์ฃผ)" |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def detect_with_selected_model(image, confidence, model_type): |
|
|
"""์ ํ๋ ๋ชจ๋ธ๋ก ๊ฒ์ถ""" |
|
|
if model_type == "RT-DETR": |
|
|
if processor is None or model is None: |
|
|
raise ValueError("โ ๏ธ RT-DETR ๋ชจ๋ธ์ด ๋ก๋ฉ๋์ง ์์์ต๋๋ค. '๐ RT-DETR ๋ก๋' ๋ฒํผ์ ๋จผ์ ํด๋ฆญํ์ธ์.") |
|
|
return detect_with_rtdetr(image, processor, model, confidence) |
|
|
elif model_type == "VIDraft/Shrimp": |
|
|
return detect_with_roboflow(image, confidence) |
|
|
elif model_type == "YOLOv8": |
|
|
return detect_with_yolo(image, confidence) |
|
|
else: |
|
|
return [] |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def interactive_detect(image, confidence, filter_threshold, show_all, model_type, use_filter): |
|
|
"""๋ํํ ๊ฒ์ถ""" |
|
|
if image is None: |
|
|
return None, "โ ๏ธ ์ด๋ฏธ์ง๋ฅผ ์
๋ก๋ํ์ธ์." |
|
|
|
|
|
try: |
|
|
|
|
|
all_detections = detect_with_selected_model(image, confidence, model_type) |
|
|
|
|
|
|
|
|
if not use_filter: |
|
|
|
|
|
filtered_detections = all_detections |
|
|
for det in filtered_detections: |
|
|
det['filter_score'] = det['confidence'] * 100 |
|
|
det['filter_reasons'] = [f"์ ๋ขฐ๋: {det['confidence']:.0%} (ํํฐ ๋ฏธ์ฌ์ฉ)"] |
|
|
all_detections_scored = filtered_detections |
|
|
else: |
|
|
|
|
|
if model_type in ["VIDraft/Shrimp", "YOLOv8"]: |
|
|
|
|
|
for det in all_detections: |
|
|
det['filter_score'] = det['confidence'] * 100 |
|
|
det['filter_reasons'] = [f"{model_type} ์ ๋ขฐ๋: {det['confidence']:.0%}"] |
|
|
all_detections_scored = all_detections |
|
|
else: |
|
|
|
|
|
all_detections_scored = apply_universal_filter(all_detections, image, threshold=0) |
|
|
|
|
|
|
|
|
filtered_detections = [det for det in all_detections_scored if det['filter_score'] >= filter_threshold] |
|
|
|
|
|
|
|
|
img = image.copy() |
|
|
draw = ImageDraw.Draw(img) |
|
|
|
|
|
try: |
|
|
font = ImageFont.truetype("arial.ttf", 14) |
|
|
font_large = ImageFont.truetype("arial.ttf", 18) |
|
|
font_small = ImageFont.truetype("arial.ttf", 10) |
|
|
except: |
|
|
font = ImageFont.load_default() |
|
|
font_large = ImageFont.load_default() |
|
|
font_small = ImageFont.load_default() |
|
|
|
|
|
|
|
|
rejected_detections = [det for det in all_detections_scored if det['filter_score'] < filter_threshold] |
|
|
for idx, det in enumerate(rejected_detections, 1): |
|
|
x1, y1, x2, y2 = det['bbox'] |
|
|
score = det['filter_score'] |
|
|
|
|
|
|
|
|
draw.rectangle([x1, y1, x2, y2], outline="red", width=8) |
|
|
|
|
|
|
|
|
label = f"โ{idx} {score:.0f}์ " |
|
|
bbox = draw.textbbox((x1, y1 - 20), label, font=font_small) |
|
|
draw.rectangle(bbox, fill="red") |
|
|
draw.text((x1, y1 - 20), label, fill="white", font=font_small) |
|
|
|
|
|
|
|
|
if show_all: |
|
|
for det in all_detections_scored: |
|
|
if det not in filtered_detections and det not in rejected_detections: |
|
|
x1, y1, x2, y2 = det['bbox'] |
|
|
draw.rectangle([x1, y1, x2, y2], outline="gray", width=4) |
|
|
|
|
|
|
|
|
for idx, det in enumerate(filtered_detections, 1): |
|
|
x1, y1, x2, y2 = det['bbox'] |
|
|
score = det['filter_score'] |
|
|
|
|
|
|
|
|
if score >= 75: |
|
|
color = "lime" |
|
|
elif score >= 50: |
|
|
color = "yellow" |
|
|
else: |
|
|
color = "orange" |
|
|
|
|
|
|
|
|
draw.rectangle([x1, y1, x2, y2], outline=color, width=10) |
|
|
|
|
|
|
|
|
label = f"โ#{idx} {score:.0f}์ " |
|
|
bbox = draw.textbbox((x1, y1 - 25), label, font=font) |
|
|
draw.rectangle(bbox, fill=color) |
|
|
draw.text((x1, y1 - 25), label, fill="black", font=font) |
|
|
|
|
|
|
|
|
details = f"{model_type}:{det['confidence']:.0%}" |
|
|
draw.text((x1, y2 + 5), details, fill=color, font=font_small) |
|
|
|
|
|
|
|
|
header = f"[{model_type}] โ {len(filtered_detections)}๊ฐ / โ {len(rejected_detections)}๊ฐ (์ ์ฒด: {len(all_detections_scored)}๊ฐ)" |
|
|
header_bbox = draw.textbbox((10, 10), header, font=font_large) |
|
|
draw.rectangle([5, 5, header_bbox[2]+10, header_bbox[3]+10], |
|
|
fill="black", outline="lime", width=2) |
|
|
draw.text((10, 10), header, fill="lime", font=font_large) |
|
|
|
|
|
|
|
|
info = f""" |
|
|
### ๐ ๊ฒ์ถ ๊ฒฐ๊ณผ (๋ชจ๋ธ: {model_type}) |
|
|
|
|
|
- **์ ์ฒด ๊ฒ์ถ**: {len(all_detections_scored)}๊ฐ |
|
|
- **ํํฐ๋ง ํ**: {len(filtered_detections)}๊ฐ |
|
|
- **์ ๊ฑฐ๋จ**: {len(rejected_detections)}๊ฐ |
|
|
|
|
|
--- |
|
|
|
|
|
### ๐ฏ ๊ฒ์ถ๋ ๊ฐ์ฒด ์์ธ (โ
ํต๊ณผ) |
|
|
|
|
|
""" |
|
|
|
|
|
for idx, det in enumerate(filtered_detections, 1): |
|
|
info += f""" |
|
|
**#{idx} - ์ ์: {det['filter_score']:.0f}์ ** ({model_type} ์ ๋ขฐ๋: {det['confidence']:.0%}) |
|
|
|
|
|
""" |
|
|
|
|
|
for reason in det['filter_reasons'][:5]: |
|
|
info += f"- {reason}\n" |
|
|
|
|
|
if not filtered_detections: |
|
|
info += """ |
|
|
โ ๏ธ **๊ฒ์ถ๋ ๊ฐ์ฒด๊ฐ ์์ต๋๋ค.** |
|
|
|
|
|
""" |
|
|
|
|
|
|
|
|
if rejected_detections: |
|
|
info += f""" |
|
|
|
|
|
--- |
|
|
|
|
|
### โ ์ ๊ฑฐ๋ ๊ฐ์ฒด ({len(rejected_detections)}๊ฐ) |
|
|
|
|
|
""" |
|
|
for idx, det in enumerate(rejected_detections[:3], 1): |
|
|
info += f""" |
|
|
**์ ๊ฑฐ #{idx} - ์ ์: {det['filter_score']:.0f}์ ** (์๊ณ๊ฐ ๋ฏธ๋ฌ) |
|
|
- {model_type} ์ ๋ขฐ๋: {det['confidence']:.0%} |
|
|
|
|
|
""" |
|
|
|
|
|
for reason in det['filter_reasons'][:3]: |
|
|
info += f"- {reason}\n" |
|
|
|
|
|
return img, info |
|
|
|
|
|
except Exception as e: |
|
|
import traceback |
|
|
error_detail = traceback.format_exc() |
|
|
return None, f"โ ์ค๋ฅ ๋ฐ์:\n\n```\n{error_detail}\n```" |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def detect_with_rtdetr_fast(image, confidence=0.3): |
|
|
"""RT-DETR ๋น ๋ฅธ ๊ฒ์ถ""" |
|
|
inputs = processor(images=image, return_tensors="pt") |
|
|
with torch.no_grad(): |
|
|
outputs = model(**inputs) |
|
|
|
|
|
target_sizes = torch.tensor([image.size[::-1]]) |
|
|
results = processor.post_process_object_detection( |
|
|
outputs, |
|
|
target_sizes=target_sizes, |
|
|
threshold=confidence |
|
|
)[0] |
|
|
|
|
|
detections = [] |
|
|
for score, label, box in zip(results["scores"], results["labels"], results["boxes"]): |
|
|
x1, y1, x2, y2 = box.tolist() |
|
|
detections.append({ |
|
|
'bbox': [x1, y1, x2, y2], |
|
|
'confidence': score.item() |
|
|
}) |
|
|
|
|
|
return detections |
|
|
|
|
|
|
|
|
def load_existing_ground_truth(): |
|
|
"""๊ธฐ์กด ground_truth.json ๋ก๋""" |
|
|
if os.path.exists(GROUND_TRUTH_FILE): |
|
|
with open(GROUND_TRUTH_FILE, 'r', encoding='utf-8') as f: |
|
|
return json.load(f) |
|
|
return {} |
|
|
|
|
|
|
|
|
def save_ground_truth(data): |
|
|
"""ground_truth.json ์ ์ฅ""" |
|
|
backup_dir = "backups" |
|
|
if not os.path.exists(backup_dir): |
|
|
os.makedirs(backup_dir) |
|
|
|
|
|
if os.path.exists(GROUND_TRUTH_FILE): |
|
|
backup_name = f"ground_truth_backup_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json" |
|
|
backup_path = os.path.join(backup_dir, backup_name) |
|
|
import shutil |
|
|
shutil.copy2(GROUND_TRUTH_FILE, backup_path) |
|
|
|
|
|
with open(GROUND_TRUTH_FILE, 'w', encoding='utf-8') as f: |
|
|
json.dump(data, f, ensure_ascii=False, indent=2) |
|
|
|
|
|
print(f"โ
Ground Truth ์ ์ฅ ์๋ฃ: {len(data)}๊ฐ ์ด๋ฏธ์ง") |
|
|
|
|
|
|
|
|
def get_folders(): |
|
|
"""์ฌ์ฉ ๊ฐ๋ฅํ ํด๋ ๋ชฉ๋ก""" |
|
|
folders = sorted(glob.glob(os.path.join(DATA_BASE, "2*"))) |
|
|
return [os.path.basename(f) for f in folders if os.path.isdir(f)] |
|
|
|
|
|
|
|
|
def start_labeling(folder, conf_threshold, model_type): |
|
|
"""๋ผ๋ฒจ๋ง ์์""" |
|
|
if not folder: |
|
|
return None, "โ ํด๋๋ฅผ ์ ํํ์ธ์.", "" |
|
|
|
|
|
current_data['folder'] = folder |
|
|
current_data['confidence_threshold'] = conf_threshold |
|
|
current_data['model_type'] = model_type |
|
|
|
|
|
folder_path = os.path.join(DATA_BASE, folder) |
|
|
all_images = sorted(glob.glob(os.path.join(folder_path, "*.jpg"))) |
|
|
|
|
|
|
|
|
import re |
|
|
images = [img for img in all_images if not re.search(r'-\d+\.jpg$', os.path.basename(img))] |
|
|
|
|
|
if not images: |
|
|
return None, "โ ์ด๋ฏธ์ง ์์", "" |
|
|
|
|
|
print(f"๐ ํด๋: {folder}") |
|
|
print(f" ์ ์ฒด ์ด๋ฏธ์ง: {len(all_images)}๊ฐ") |
|
|
print(f" ๋ผ๋ฒจ๋ง ๋์: {len(images)}๊ฐ (-์ซ์ ํ์ผ ์ ์ธ)") |
|
|
|
|
|
current_data['images'] = images |
|
|
current_data['current_idx'] = 0 |
|
|
current_data['detections'] = {} |
|
|
current_data['selections'] = {} |
|
|
|
|
|
|
|
|
gt = load_existing_ground_truth() |
|
|
|
|
|
|
|
|
for i, img_path in enumerate(images): |
|
|
filename = os.path.basename(img_path) |
|
|
if filename in gt: |
|
|
current_data['selections'][filename] = [j for j in range(len(gt[filename]))] |
|
|
print(f"โญ๏ธ ๊ฑด๋๋ฐ๊ธฐ: {filename} (์ด๋ฏธ ๋ผ๋ฒจ๋ง๋จ)") |
|
|
|
|
|
|
|
|
while current_data['current_idx'] < len(images): |
|
|
filename = os.path.basename(images[current_data['current_idx']]) |
|
|
if filename not in current_data['selections']: |
|
|
break |
|
|
current_data['current_idx'] += 1 |
|
|
|
|
|
if current_data['current_idx'] >= len(images): |
|
|
return None, "โ
๋ชจ๋ ์ด๋ฏธ์ง ๋ผ๋ฒจ๋ง ์๋ฃ!", "" |
|
|
|
|
|
return show_current_image() |
|
|
|
|
|
|
|
|
def show_current_image(): |
|
|
"""ํ์ฌ ์ด๋ฏธ์ง ํ์""" |
|
|
if current_data['current_idx'] >= len(current_data['images']): |
|
|
return None, "โ
์๋ฃ!", "" |
|
|
|
|
|
img_path = current_data['images'][current_data['current_idx']] |
|
|
filename = os.path.basename(img_path) |
|
|
|
|
|
|
|
|
if filename in current_data['image_cache']: |
|
|
image = current_data['image_cache'][filename] |
|
|
else: |
|
|
image = Image.open(img_path) |
|
|
current_data['image_cache'][filename] = image |
|
|
|
|
|
|
|
|
if filename not in current_data['detections']: |
|
|
if current_data['model_type'] == 'RT-DETR': |
|
|
detections = detect_with_rtdetr_fast(image, current_data['confidence_threshold']) |
|
|
elif current_data['model_type'] == 'YOLOv8': |
|
|
detections = detect_with_yolo(image, current_data['confidence_threshold']) |
|
|
else: |
|
|
detections = detect_with_roboflow(image, current_data['confidence_threshold']) |
|
|
current_data['detections'][filename] = detections |
|
|
else: |
|
|
detections = current_data['detections'][filename] |
|
|
|
|
|
|
|
|
selected_indices = current_data['selections'].get(filename, []) |
|
|
|
|
|
|
|
|
vis_image = draw_detections(image, detections, selected_indices) |
|
|
|
|
|
info = f""" |
|
|
### ๐ {current_data['folder']} - ์ด๋ฏธ์ง {current_data['current_idx']+1}/{len(current_data['images'])} |
|
|
|
|
|
**ํ์ผ**: {filename} |
|
|
**๋ชจ๋ธ**: {current_data['model_type']} |
|
|
|
|
|
**๊ฒ์ถ**: {len(detections)}๊ฐ |
|
|
**์ ํ**: {len(selected_indices)}๊ฐ |
|
|
|
|
|
--- |
|
|
|
|
|
### ๐ฑ๏ธ ์ฌ์ฉ ๋ฐฉ๋ฒ: |
|
|
1. ์ด๋ฏธ์ง๋ฅผ ํด๋ฆญํ์ฌ ๋ฐ์ค ์ ํ/ํด์ |
|
|
2. "๋ค์" ๋ฒํผ์ผ๋ก ์ ์ฅ ํ ์ด๋ |
|
|
3. "๊ฑด๋๋ฐ๊ธฐ"๋ก ์ ํ ์์ด ์ด๋ |
|
|
""" |
|
|
|
|
|
return vis_image, info, filename |
|
|
|
|
|
|
|
|
def draw_detections(image, detections, selected_indices): |
|
|
"""๊ฒ์ถ ๊ฒฐ๊ณผ ๊ทธ๋ฆฌ๊ธฐ""" |
|
|
img = image.copy() |
|
|
draw = ImageDraw.Draw(img) |
|
|
|
|
|
try: |
|
|
font_tiny = ImageFont.truetype("arial.ttf", 10) |
|
|
font_large = ImageFont.truetype("arial.ttf", 40) |
|
|
except: |
|
|
font_tiny = ImageFont.load_default() |
|
|
font_large = ImageFont.load_default() |
|
|
|
|
|
|
|
|
for idx, det in enumerate(detections): |
|
|
if idx not in selected_indices: |
|
|
x1, y1, x2, y2 = det['bbox'] |
|
|
draw.rectangle([x1, y1, x2, y2], outline="lime", width=20) |
|
|
corner_label = f"#{idx+1}" |
|
|
draw.rectangle([x1-2, y1-24, x1+30, y1-2], fill="lime") |
|
|
draw.text((x1, y1 - 22), corner_label, fill="white", font=font_tiny) |
|
|
|
|
|
|
|
|
for idx, det in enumerate(detections): |
|
|
if idx in selected_indices: |
|
|
x1, y1, x2, y2 = det['bbox'] |
|
|
draw.rectangle([x1, y1, x2, y2], outline="blue", width=28) |
|
|
corner_label = f"โ#{idx+1}" |
|
|
draw.rectangle([x1-2, y1-24, x1+40, y1-2], fill="blue") |
|
|
draw.text((x1, y1 - 22), corner_label, fill="white", font=font_tiny) |
|
|
|
|
|
|
|
|
for idx, det in enumerate(detections): |
|
|
x1, y1, x2, y2 = det['bbox'] |
|
|
center_x = (x1 + x2) / 2 |
|
|
center_y = (y1 + y2) / 2 |
|
|
|
|
|
selected = idx in selected_indices |
|
|
btn_color = "blue" if selected else "lime" |
|
|
btn_text = f"โ{idx+1}" if selected else f"{idx+1}" |
|
|
|
|
|
box_width = x2 - x1 |
|
|
box_height = y2 - y1 |
|
|
radius = min(55, box_width * 0.18, box_height * 0.35) |
|
|
|
|
|
|
|
|
draw.ellipse( |
|
|
[center_x - radius, center_y - radius, |
|
|
center_x + radius, center_y + radius], |
|
|
fill=btn_color, outline="white", width=4 |
|
|
) |
|
|
draw.text((center_x - radius*0.5, center_y - radius*0.6), |
|
|
btn_text, fill="white", font=font_large) |
|
|
|
|
|
return img |
|
|
|
|
|
|
|
|
def labeling_click(image, filename, evt: gr.SelectData): |
|
|
"""์ด๋ฏธ์ง ํด๋ฆญ ์ด๋ฒคํธ""" |
|
|
if not filename or filename not in current_data['detections']: |
|
|
return image, "โ ๏ธ ์ด๋ฏธ์ง๋ฅผ ๋จผ์ ๋ก๋ํ์ธ์." |
|
|
|
|
|
click_x, click_y = evt.index[0], evt.index[1] |
|
|
detections = current_data['detections'][filename] |
|
|
selected_indices = set(current_data['selections'].get(filename, [])) |
|
|
|
|
|
|
|
|
clicked_idx = None |
|
|
button_candidates = [] |
|
|
|
|
|
|
|
|
for idx, det in enumerate(detections): |
|
|
x1, y1, x2, y2 = det['bbox'] |
|
|
center_x = (x1 + x2) / 2 |
|
|
center_y = (y1 + y2) / 2 |
|
|
|
|
|
box_width = x2 - x1 |
|
|
box_height = y2 - y1 |
|
|
radius = min(55, box_width * 0.18, box_height * 0.35) |
|
|
|
|
|
distance = ((click_x - center_x) ** 2 + (click_y - center_y) ** 2) ** 0.5 |
|
|
|
|
|
if distance <= radius: |
|
|
button_candidates.append((idx, distance)) |
|
|
|
|
|
|
|
|
if button_candidates: |
|
|
button_candidates.sort(key=lambda x: x[1]) |
|
|
clicked_idx = button_candidates[0][0] |
|
|
else: |
|
|
|
|
|
for idx, det in enumerate(detections): |
|
|
x1, y1, x2, y2 = det['bbox'] |
|
|
if x1 <= click_x <= x2 and y1 <= click_y <= y2: |
|
|
clicked_idx = idx |
|
|
break |
|
|
|
|
|
|
|
|
if clicked_idx is not None: |
|
|
if clicked_idx in selected_indices: |
|
|
selected_indices.remove(clicked_idx) |
|
|
print(f"โ ์ ํ ํด์ : ๋ฐ์ค #{clicked_idx+1}") |
|
|
else: |
|
|
selected_indices.add(clicked_idx) |
|
|
print(f"โ
์ ํ: ๋ฐ์ค #{clicked_idx+1}") |
|
|
|
|
|
current_data['selections'][filename] = list(selected_indices) |
|
|
|
|
|
|
|
|
img_path = current_data['images'][current_data['current_idx']] |
|
|
image = Image.open(img_path) |
|
|
vis_image = draw_detections(image, detections, list(selected_indices)) |
|
|
|
|
|
info = f"โ
๋ฐ์ค #{clicked_idx+1} {'์ ํ' if clicked_idx in selected_indices else 'ํด์ '}" |
|
|
return vis_image, info |
|
|
|
|
|
return image, "โ ๋ฐ์ค๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค." |
|
|
|
|
|
|
|
|
def save_and_next(): |
|
|
"""์ ์ฅ ํ ๋ค์""" |
|
|
if current_data['current_idx'] >= len(current_data['images']): |
|
|
return None, "โ
์๋ฃ!", "" |
|
|
|
|
|
img_path = current_data['images'][current_data['current_idx']] |
|
|
filename = os.path.basename(img_path) |
|
|
|
|
|
|
|
|
gt = load_existing_ground_truth() |
|
|
selected_indices = current_data['selections'].get(filename, []) |
|
|
|
|
|
if selected_indices: |
|
|
detections = current_data['detections'][filename] |
|
|
gt[filename] = [ |
|
|
{ |
|
|
'bbox': detections[i]['bbox'], |
|
|
'folder': current_data['folder'] |
|
|
} |
|
|
for i in selected_indices |
|
|
] |
|
|
save_ground_truth(gt) |
|
|
print(f"๐พ ์ ์ฅ: {filename} - {len(selected_indices)}๊ฐ ๋ฐ์ค") |
|
|
else: |
|
|
print(f"โญ๏ธ ๊ฑด๋๋ฐ๊ธฐ: {filename} - ์ ํ ์์") |
|
|
|
|
|
|
|
|
current_data['current_idx'] += 1 |
|
|
|
|
|
|
|
|
while current_data['current_idx'] < len(current_data['images']): |
|
|
next_filename = os.path.basename(current_data['images'][current_data['current_idx']]) |
|
|
if next_filename not in current_data['selections']: |
|
|
break |
|
|
current_data['current_idx'] += 1 |
|
|
|
|
|
if current_data['current_idx'] >= len(current_data['images']): |
|
|
return None, "โ
๋ชจ๋ ์ด๋ฏธ์ง ๋ผ๋ฒจ๋ง ์๋ฃ!", "" |
|
|
|
|
|
return show_current_image() |
|
|
|
|
|
|
|
|
def skip_image(): |
|
|
"""๊ฑด๋๋ฐ๊ธฐ""" |
|
|
current_data['current_idx'] += 1 |
|
|
|
|
|
if current_data['current_idx'] >= len(current_data['images']): |
|
|
return None, "โ
์๋ฃ!", "" |
|
|
|
|
|
return show_current_image() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
with gr.Blocks(title="๐ฆ ์์ฐ ๊ฒ์ถ ํตํฉ ์์คํ
", theme=gr.themes.Soft()) as demo: |
|
|
|
|
|
gr.Markdown(""" |
|
|
# ๐ฆ ์์ฐ ๊ฒ์ถ ํตํฉ ์์คํ
|
|
|
|
|
|
**2๊ฐ์ง ํญ์ผ๋ก ์์ฐ๋ฅผ ์ ํํ๊ฒ ๊ฒ์ถํ๊ณ ๊ด๋ฆฌํ์ธ์** |
|
|
|
|
|
--- |
|
|
""") |
|
|
|
|
|
|
|
|
with gr.Row(): |
|
|
with gr.Column(scale=3): |
|
|
|
|
|
model_choices = ["RT-DETR", "VIDraft/Shrimp"] |
|
|
default_model = "RT-DETR" |
|
|
if YOLO_AVAILABLE: |
|
|
model_choices.append("YOLOv8") |
|
|
default_model = "YOLOv8" |
|
|
|
|
|
model_selector = gr.Radio( |
|
|
choices=model_choices, |
|
|
value=default_model, |
|
|
label="๐ค ๊ฒ์ถ ๋ชจ๋ธ ์ ํ", |
|
|
info="๋ชจ๋ ํญ์ ์ ์ฉ๋ฉ๋๋ค" |
|
|
) |
|
|
with gr.Column(scale=1): |
|
|
load_rtdetr_btn = gr.Button("๐ RT-DETR ๋ก๋", size="sm", variant="secondary") |
|
|
rtdetr_status = gr.Textbox(label="๋ชจ๋ธ ์ํ", value="โธ๏ธ RT-DETR ๋ฏธ๋ก๋ (VIDraft/Shrimp ํด๋ผ์ฐ๋ ๋ชจ๋ธ ์ฌ์ฉ ๊ฐ๋ฅ)", interactive=False, lines=1) |
|
|
|
|
|
|
|
|
load_rtdetr_btn.click( |
|
|
load_rtdetr_on_demand, |
|
|
inputs=[], |
|
|
outputs=[rtdetr_status] |
|
|
) |
|
|
|
|
|
gr.Markdown("---") |
|
|
|
|
|
with gr.Tabs(): |
|
|
|
|
|
with gr.TabItem("๐ค ์๋ ๊ฒ์ถ & ๊ฒ์ฆ"): |
|
|
gr.Markdown(""" |
|
|
### ์ค์๊ฐ์ผ๋ก ํ๋ผ๋ฏธํฐ๋ฅผ ์กฐ์ ํ๋ฉฐ ๊ฒ์ถ ๊ฒฐ๊ณผ๋ฅผ ํ์ธ |
|
|
์ต์ ํ๋ ํ๋ผ๋ฏธํฐ๋ก ์์ฐ ๊ฒ์ถ์ ํ
์คํธํ์ธ์. |
|
|
""") |
|
|
|
|
|
with gr.Row(): |
|
|
with gr.Column(): |
|
|
input_image_detect = gr.Image(label="์
๋ ฅ ์ด๋ฏธ์ง", type="pil") |
|
|
|
|
|
confidence_slider_detect = gr.Slider( |
|
|
0.01, 1.0, 0.1, |
|
|
step=0.01, |
|
|
label="์ ๋ขฐ๋ ์๊ณ๊ฐ", |
|
|
info="RT-DETR: 0.065 | VIDraft/Shrimp: 0.3~0.5 | YOLOv8: 0.1~0.3 ๊ถ์ฅ" |
|
|
) |
|
|
|
|
|
use_filter_check = gr.Checkbox( |
|
|
label="๐ ํํฐ ์ ์ ์๊ณ๊ฐ ์ฌ์ฉ", |
|
|
value=False, |
|
|
info="์ฒดํฌํ๋ฉด ํํฐ ์ ์ ๊ธฐ์ค์ผ๋ก ์ถ๊ฐ ํํฐ๋ง" |
|
|
) |
|
|
|
|
|
filter_slider_detect = gr.Slider( |
|
|
0, 100, 90, |
|
|
step=5, |
|
|
label="ํํฐ ์ ์ ์๊ณ๊ฐ", |
|
|
info="RT-DETR: Universal Filter | VIDraft/Shrimp: ์ ๋ขฐ๋ ๊ธฐ๋ฐ", |
|
|
visible=True |
|
|
) |
|
|
|
|
|
show_all_check = gr.Checkbox( |
|
|
label="์ ์ฒด ๊ฒ์ถ ๊ฒฐ๊ณผ ํ์ (ํ์)", |
|
|
value=False |
|
|
) |
|
|
|
|
|
detect_btn = gr.Button("๐ ๊ฒ์ถ ์คํ", variant="primary", size="lg") |
|
|
|
|
|
|
|
|
example_images = [ |
|
|
"examples/250818_03.jpg", |
|
|
"examples/test_shrimp_tank.png", |
|
|
"examples/250818_05.jpg", |
|
|
] |
|
|
|
|
|
|
|
|
example_images = [img for img in example_images if os.path.exists(img)] |
|
|
|
|
|
if example_images: |
|
|
gr.Examples( |
|
|
examples=[[img] for img in example_images], |
|
|
inputs=[input_image_detect], |
|
|
label="๐ท ์์ ์ด๋ฏธ์ง" |
|
|
) |
|
|
|
|
|
with gr.Column(): |
|
|
output_image_detect = gr.Image(label="๊ฒ์ถ ๊ฒฐ๊ณผ") |
|
|
output_info_detect = gr.Markdown() |
|
|
|
|
|
detect_btn.click( |
|
|
interactive_detect, |
|
|
[input_image_detect, confidence_slider_detect, filter_slider_detect, show_all_check, model_selector, use_filter_check], |
|
|
[output_image_detect, output_info_detect] |
|
|
) |
|
|
|
|
|
|
|
|
def update_filter_interactivity(use_filter): |
|
|
return gr.update(interactive=use_filter) |
|
|
|
|
|
use_filter_check.change( |
|
|
update_filter_interactivity, |
|
|
inputs=[use_filter_check], |
|
|
outputs=[filter_slider_detect] |
|
|
) |
|
|
|
|
|
gr.Markdown(""" |
|
|
### ๐ก ์ฌ์ฉ ํ |
|
|
- ๋ชจ๋ธ์ ์ ํํ๊ณ ์ ๋ขฐ๋๋ฅผ ์กฐ์ ํ์ฌ ๊ฒ์ถ ๊ฒฐ๊ณผ๋ฅผ ํ์ธํ์ธ์ |
|
|
- ๊ฒ์ถ์ด ์ ์ ๋๋ ์ ๋ขฐ๋๋ฅผ ๋ฎ์ถ๊ณ , ์ค๊ฒ์ถ์ด ๋ง์ ๋๋ ๋์ด์ธ์ |
|
|
- ํํฐ ๊ธฐ๋ฅ์ ์ฌ์ฉํ์ฌ ๋ ์ ํํ ๊ฒฐ๊ณผ๋ฅผ ์ป์ ์ ์์ต๋๋ค |
|
|
|
|
|
**๋ฐ์ค ์์:** ๐ข ๋
น์(๋์ ํ๋ฅ ) | ๐ก ๋
ธ๋์(์ค๊ฐ ํ๋ฅ ) | ๐ ์ฃผํฉ์(๋ฎ์ ํ๋ฅ ) | ๐ด ๋นจ๊ฐ์(์ ๊ฑฐ๋จ) |
|
|
""") |
|
|
|
|
|
|
|
|
with gr.TabItem("๐ Ground Truth ๋ผ๋ฒจ๋ง"): |
|
|
gr.Markdown(""" |
|
|
### ์ ํ๋ ๋ชจ๋ธ์ ๊ฒ์ถ ๊ฒฐ๊ณผ์์ ์ฌ๋ฐ๋ฅธ ๋ฐ์ค๋ง ์ ํํ์ฌ ๋ผ๋ฒจ๋ง |
|
|
์ด๋ฏธ์ง๋ฅผ ํด๋ฆญํ์ฌ ์์ฐ ๋ฐ์ค๋ฅผ ์ ํ/ํด์ ํ์ธ์. |
|
|
""") |
|
|
|
|
|
with gr.Row(): |
|
|
with gr.Column(scale=1): |
|
|
folder_dropdown = gr.Dropdown( |
|
|
choices=get_folders(), |
|
|
label="๐ ํด๋ ์ ํ", |
|
|
info="๋ผ๋ฒจ๋งํ ํด๋๋ฅผ ์ ํํ์ธ์" |
|
|
) |
|
|
|
|
|
conf_slider_label = gr.Slider( |
|
|
0.01, 0.5, 0.2, |
|
|
step=0.05, |
|
|
label="์ ๋ขฐ๋", |
|
|
info="๊ฒ์ถ ๋ฏผ๊ฐ๋ ์กฐ์ " |
|
|
) |
|
|
|
|
|
start_btn = gr.Button("โถ๏ธ ๋ผ๋ฒจ๋ง ์์", variant="primary", size="lg") |
|
|
|
|
|
gr.Markdown("---") |
|
|
|
|
|
next_btn = gr.Button("โญ๏ธ ์ ์ฅ & ๋ค์", variant="secondary", size="lg") |
|
|
skip_btn = gr.Button("โฉ ๊ฑด๋๋ฐ๊ธฐ", size="lg") |
|
|
|
|
|
labeling_info = gr.Markdown("ํด๋๋ฅผ ์ ํํ๊ณ '๋ผ๋ฒจ๋ง ์์'์ ํด๋ฆญํ์ธ์.") |
|
|
|
|
|
with gr.Column(scale=2): |
|
|
labeling_image = gr.Image( |
|
|
label="๐ฑ๏ธ ํด๋ฆญํ์ฌ ๋ฐ์ค ์ ํ/ํด์ ", |
|
|
type="pil", |
|
|
interactive=True |
|
|
) |
|
|
|
|
|
labeling_filename = gr.Textbox(visible=False) |
|
|
click_info = gr.Markdown() |
|
|
|
|
|
|
|
|
start_btn.click( |
|
|
start_labeling, |
|
|
[folder_dropdown, conf_slider_label, model_selector], |
|
|
[labeling_image, labeling_info, labeling_filename] |
|
|
) |
|
|
|
|
|
labeling_image.select( |
|
|
labeling_click, |
|
|
[labeling_image, labeling_filename], |
|
|
[labeling_image, click_info] |
|
|
) |
|
|
|
|
|
next_btn.click( |
|
|
save_and_next, |
|
|
[], |
|
|
[labeling_image, labeling_info, labeling_filename] |
|
|
) |
|
|
|
|
|
skip_btn.click( |
|
|
skip_image, |
|
|
[], |
|
|
[labeling_image, labeling_info, labeling_filename] |
|
|
) |
|
|
|
|
|
gr.Markdown(""" |
|
|
### ๐ฑ๏ธ ์ฌ์ฉ ๋ฐฉ๋ฒ |
|
|
1. **๋ชจ๋ธ ์ ํ** (์ต์๋จ์์ ์ ํ) |
|
|
2. ํด๋ ์ ํ ํ "๋ผ๋ฒจ๋ง ์์" |
|
|
3. ์ด๋ฏธ์ง์์ **์ํ ๋ฒํผ ํด๋ฆญ** ๋๋ **๋ฐ์ค ์์ญ ํด๋ฆญ**์ผ๋ก ์ ํ/ํด์ |
|
|
4. "์ ์ฅ & ๋ค์"์ผ๋ก ๋ค์ ์ด๋ฏธ์ง๋ก ์ด๋ (์๋ ์ ์ฅ) |
|
|
5. "๊ฑด๋๋ฐ๊ธฐ"๋ก ์ ํ ์์ด ๋ค์ ์ด๋ฏธ์ง๋ก |
|
|
|
|
|
**๐พ ์ ์ฅ ์์น:** `ground_truth.json` (์๋ ๋ฐฑ์
: `backups/`) |
|
|
""") |
|
|
|
|
|
gr.Markdown(""" |
|
|
--- |
|
|
|
|
|
### ๐ค ๋ชจ๋ธ ์ค๋ช
|
|
|
- **RT-DETR**: ๋ก์ปฌ ๋ชจ๋ธ, ๋น ๋ฅธ ์ถ๋ก ์๋, ์คํ๋ผ์ธ ์ฌ์ฉ ๊ฐ๋ฅ |
|
|
- **VIDraft/Shrimp**: ํด๋ผ์ฐ๋ ๋ชจ๋ธ, ์ธํฐ๋ท ์ฐ๊ฒฐ ํ์ |
|
|
- **YOLOv8**: ๋ก์ปฌ ์ปค์คํ
ํ์ต ๋ชจ๋ธ, ๋น ๋ฅธ ์ถ๋ก ์๋ |
|
|
|
|
|
--- |
|
|
|
|
|
ยฉ 2025 VIDraft. All rights reserved. |
|
|
""") |
|
|
|
|
|
if __name__ == "__main__": |
|
|
print("\n" + "="*60) |
|
|
print("๐ฆ ์์ฐ ๊ฒ์ถ ํตํฉ ์์คํ
v2.2 ์์") |
|
|
print("="*60) |
|
|
print("๐ค ์ฌ์ฉ ๊ฐ๋ฅํ ๋ชจ๋ธ:") |
|
|
print(" 1. RT-DETR (๋ก์ปฌ)") |
|
|
print(" 2. VIDraft/Shrimp (ํด๋ผ์ฐ๋)") |
|
|
print(" 3. YOLOv8 (๋ก์ปฌ ํ์ต) โญ ๊ธฐ๋ณธ๊ฐ") |
|
|
print(f"\n๐ฆ YOLOv8 ๋ชจ๋ธ: {YOLO_MODEL_PATH}") |
|
|
print("="*60) |
|
|
|
|
|
demo.launch( |
|
|
server_name="0.0.0.0", |
|
|
server_port=None, |
|
|
share=False |
|
|
) |
|
|
|