모델이 잘못 분류한 영상/객체에 대해서 분석하고, 해당 문제의

발생의 이유를 설명하라.

import os
from ultralytics import YOLO
import cv2
from tqdm import tqdm

# 1. 학습된 모델 불러오기
model = YOLO("/home/wkdwnsals0413/runs/train/yolo11n_custom2/weights/best.pt")

# 2. 데이터셋 경로
test_images_dir = "/home/wkdwnsals0413/dataset/images/test"
test_labels_dir = "/home/wkdwnsals0413/dataset/labels/test"
save_dir = "misclassified_results"
os.makedirs(save_dir, exist_ok=True)

# 3. 클래스 이름
class_names = model.names

# 4. 라벨(.txt) 읽기
def read_labels(label_path):
    boxes = []
    with open(label_path, "r") as f:
        for line in f.readlines():
            cls, x, y, w, h = map(float, line.strip().split())
            boxes.append([int(cls), x, y, w, h])
    return boxes

# 5. IoU 계산 함수
def compute_iou(box1, box2):
    """
    box = [x, y, w, h] (normalized xywh)
    """
    x1_min = box1[0] - box1[2] / 2
    y1_min = box1[1] - box1[3] / 2
    x1_max = box1[0] + box1[2] / 2
    y1_max = box1[1] + box1[3] / 2

    x2_min = box2[0] - box2[2] / 2
    y2_min = box2[1] - box2[3] / 2
    x2_max = box2[0] + box2[2] / 2
    y2_max = box2[1] + box2[3] / 2

    inter_xmin = max(x1_min, x2_min)
    inter_ymin = max(y1_min, y2_min)
    inter_xmax = min(x1_max, x2_max)
    inter_ymax = min(y1_max, y2_max)

    inter_area = max(0, inter_xmax - inter_xmin) * max(0, inter_ymax - inter_ymin)
    box1_area = (x1_max - x1_min) * (y1_max - y1_min)
    box2_area = (x2_max - x2_min) * (y2_max - y2_min)

    union_area = box1_area + box2_area - inter_area
    return inter_area / union_area if union_area > 0 else 0

# 6. Bounding Box 그리기
def draw_boxes(img, boxes, color, label_text):
    h, w = img.shape[:2]
    for box in boxes:
        cls, x, y, bw, bh = box
        x1 = int((x - bw/2) * w)
        y1 = int((y - bh/2) * h)
        x2 = int((x + bw/2) * w)
        y2 = int((y + bh/2) * h)

        cv2.rectangle(img, (x1, y1), (x2, y2), color, 2)
        cv2.putText(img, f"{label_text}: {class_names[cls]}",
                    (x1, max(20, y1 - 5)), cv2.FONT_HERSHEY_SIMPLEX,
                    0.6, color, 2)
    return img

# 7. 평가 및 저장
misclassified_count = 0

for img_name in tqdm(os.listdir(test_images_dir)):
    if not img_name.endswith((".jpg", ".png", ".jpeg")):
        continue

    img_path = os.path.join(test_images_dir, img_name)
    label_path = os.path.join(test_labels_dir, img_name.rsplit(".", 1)[0] + ".txt")

    if not os.path.exists(label_path):
        continue

    # 실제 라벨
    true_boxes = read_labels(label_path)

    # 예측
    results = model(img_path, conf=0.25, iou=0.45, verbose=False)[0]
    pred_boxes = []
    for box in results.boxes:
        cls = int(box.cls.item())
        xywh = box.xywhn.cpu().numpy()[0]  # normalized [x,y,w,h]
        pred_boxes.append([cls, *xywh])

    # mAP 방식 검증 (IoU threshold = 0.3)
    misclassified = False
    for tb in true_boxes:
        t_cls, tx, ty, tw, th = tb
        best_iou, best_idx = 0, -1

        for idx, pb in enumerate(pred_boxes):
            p_cls, px, py, pw, ph = pb
            iou = compute_iou([tx, ty, tw, th], [px, py, pw, ph])

            if iou > best_iou:
                best_iou, best_idx = iou, idx

        if best_iou >= 0.30:  # IoU 조건 충족
            p_cls = pred_boxes[best_idx][0]
            if p_cls != t_cls:  # 클래스 불일치
                misclassified = True
        else:  # 매칭되는 박스 없음
            misclassified = True

    # 불일치 데이터만 저장
    if misclassified:
        img = cv2.imread(img_path)
        img = draw_boxes(img, true_boxes, (255, 0, 0), "GT")   # 파랑
        img = draw_boxes(img, pred_boxes, (0, 0, 255), "Pred") # 빨강
        cv2.imwrite(os.path.join(save_dir, img_name), img)
        misclassified_count += 1

print(f"[완료] IoU=0.3 기준 잘못 분류된 이미지들이 '{save_dir}'에 저장되었습니다.")
print(f"총 잘못 분류된 파일 개수: {misclassified_count}")

100%|██████████| 8549/8549 [01:07<00:00, 125.80it/s]
[완료] IoU=0.3 기준 잘못 분류된 이미지들이 'misclassified_results'에 저장되었습니다.
총 잘못 분류된 파일 개수: 492