Skip to content

object-detection-metric

TL; DR

  • 객체 검출 모델의 성능을 평가하는 대표적인 지표는 mAP가 있음
  • AP는 PR 곡선 아래 면적을 의미하고, 구체적으로 계산하는 방법은 PASCAL VOC와 COCO 평가 방식이 있음
  • mAP는 여러 클래스에 대한 AP의 평균을 의미함

Object Detection의 평가 방식

  • PASCAL VOC 평가 방식:
    • 주로 IoU 임계값 0.5에서의 평균 정밀도(AP)를 사용함
    • 포인트 보간법을 사용하여 AP를 계산함
  • COCO 평가 방식:
    • 여러 IoU 임계값(0.5에서 0.95까지 0.05 간격)에서의 AP를 평균내어 사용함
    • PR 곡선 전체 영역을 적분하여 AP를 계산함
  • KITTI 평가 방식:
    • 자율주행 차량을 위한 객체 검출에 특화된 평가 방식임
    • 3D 바운딩 박스와 2D 바운딩 박스 모두에 대해 평가함
    • 쉬움, 보통, 어려움의 세 가지 난이도로 평가함
  • Open Images 평가 방식:
    • 계층적 레이블 구조를 고려한 평가 방식을 사용함
    • 그룹 평균 정밀도(Group-Average Precision)를 사용하여 클래스 간 불균형을 처리함

IoU 에 따 True Positive, False Positive, False Negative

  • IoU 임계값: 예측된 바운딩 박스와 실제 바운딩 박스의 겹치는 정도를 측정하는 임계값

    • 일반적으로 0.5나 0.7과 같은 값을 사용함
    • IoU 임계값이 높을수록 더 엄격한 평가 기준이 됨
  • True Positive (TP):

    • 예측된 바운딩 박스와 실제 바운딩 박스가 IoU 임계값 이상으로 겹치는 경우
    • 올바른 클래스로 예측되어야 함
    • 하나의 실제 객체에 대해 가장 높은 IoU를 가진 예측만 TP로 간주됨
  • False Positive (FP):

    • 예측된 바운딩 박스가 실제 바운딩 박스와 IoU 임계값 이상으로 겹치지 않는 경우
    • 배경을 객체로 잘못 예측한 경우도 포함됨
    • 동일한 객체에 대해 중복 예측된 경우도 FP로 간주됨
  • False Negative (FN):

    • 실제 바운딩 박스가 예측된 바운딩 박스와 IoU 임계값 이상으로 겹치지 않는 경우
    • 모델이 객체를 검출하지 못한 경우를 의미함
  • 추가 고려사항:

    • 클래스별 평가: 각 클래스마다 별도로 TP, FP, FN을 계산함
    • 신뢰도 점수: 객체 검출 모델은 각 예측에 대한 신뢰도 점수를 제공하며, 이를 기반으로 정밀도-재현율 곡선을 그릴 수 있음
    • 다중 IoU 임값: COCO 평가 방식처럼 여러 IoU 임계값에 대해 평가를 수행하여 모델의 성능을 더 종합적으로 평가할 수 있음
  • True Negative (TN)의 부재:

    • 객체 검출에서는 True Negative를 명시적으로 정의하지 않음
    • 이유:
    • 배경의 정의: 객체가 없는 모든 영역을 배경으로 간주하면, TN의 수가 무한대에 가까워질 수 있음
    • 관심 대상: 객체 검출의 주요 목적은 객체를 찾는 것이므로, 배경을 올바르게 식별하는 것은 덜 중요함
    • 평가 지표: 주로 사용되는 mAP나 정밀도-재현율 곡선은 TN을 필요로 하지 않음
    • 대신 False Positive를 통해 모델이 배경을 객체로 잘못 식별하는 경우를 평가함
  • 객체 검출에서의 이진 분류와의 차이:

    • 이진 분류에서는 TP, TN, FP, FN 모두를 사용하지만, 객체 검출은 TP, FP, FN만을 사용함
    • 이로 인해 정확도(Accuracy)와 같은 TN을 포함하는 지표 대신 mAP, F1 점수 등을 주로 사용함

지표

  • 정밀도(Precision):
    • 예측된 양성 샘플 중 실제 양성 샘플의 비율임
    • 수식: Precision = True Positives / (True Positives + False Positives)
  • 재현율(Recall):
    • 실제 양성 샘플 중 예측된 양성 샘플의 비율임
    • 수식: Recall = True Positives / (True Positives + False Negatives)
  • 평균 정밀도(Average Precision, AP):
    • 정밀도와 재현율의 곡선 아래 면적임
    • 모든 가능한 재현율 값에 대한 정밀도의 평균임
  • IoU (Intersection over Union):
    • 예측된 바운딩 박스와 실제 바운딩 박스의 겹치는 정도를 측정함
    • 수식: IoU = (예측 박스 ∩ 실제 박스) / (예측 박스 ∪ 실제 박스)
  • mAP (mean Average Precision):
    • 여러 클래스에 대한 AP의 평균임
    • 객체 탐지 모델의 전체적인 성능을 나타내는 단일 지표임
  • F1 점수:
    • 정밀도와 재현율의 조화 평균임
    • 수식: F1 = 2 (Precision Recall) / (Precision + Recall)
  • FPS (Frames Per Second):
    • 모델의 실시간 처리 능력을 나타내는 지표임
    • 초당 처리할 수 있는 이미지 프레임 수임

mAP의 계산

  1. 포인트 개수의 영향:

    • 포인트 개수가 적으면: 계산은 빠르지만 정확도가 떨어질 수 있음
    • 포인트 개수가 많으면: 더 정확한 결과를 얻을 수 있지만 계산 시간이 늘어남
  2. 보간 방법:

    • PASCAL VOC: 11-point 보간법을 사용함. 재현율 0부터 1까지 0.1 간격으로 11개 포인트에서 정밀도를 계산함
    • COCO: 모든 포인트를 사용하여 PR 곡선 전체 영역을 적분함
  3. 안정성 개선 방법:

    • 모든 포인트 사용: 가능한 모든 임계값에 대해 정밀도와 재현율을 계산함
    • 정밀도 평활화: 각 재현율 수준에서 해당 재현율 이상의 최대 정밀도를 사용함
    • 보간법 사용: 선형 보간이나 더 복잡한 보간 방법을 사용하여 곡선을 부드럽게 만듦
  4. 구현 시 주의사항:

    • 일관성 유지: 평가 시 항상 동일한 방법과 포인트 개수를 사용해야 함
    • 표준 준수: 가능한 PASCAL VOC나 COCO와 같은 표준 평가 방식을 따르는 것이 좋음
    • 충분한 포인트: 정확한 결과를 위해 충분한 수의 포인트를 사용해야 함
  5. 라이브러리 활용:

    • pycocotools나 VOC 평가 스크립트와 같은 검증된 라이브러리를 용하면 일관성 있는 결과를 얻을 수 있음

IoU 에 따른 True Positive, False Positive, False Negative

  • True Negative (TN)의 부재:

    • 객체 검출에서는 True Negative를 명시적으로 정의하지 않음
    • 이유:
      • 배경의 정의: 객체가 없는 모든 영역을 배경으로 간주하면, TN의 수가 무한대에 가까워질 수 있음
      • 관심 대상: 객체 검출의 주요 목적은 객체를 찾는 것이므로, 배경을 올바르게 식별하는 것은 덜 중요함
      • 평가 지표: 주로 사용되는 mAP나 정밀도-재현율 곡선은 TN을 필요로 하지 않음
    • 대신 False Positive를 통해 모델이 배경을 객체로 잘못 식별하는 경우를 평가함
  • 객체 검출에서의 이진 분류와의 차이:

    • 이진 분류에서는 TP, TN, FP, FN 모두를 사용하지만, 객체 검출은 TP, FP, FN만을 사용함
    • 이로 인해 정확도(Accuracy)와 같은 TN을 포함하는 지표 대신 mAP, F1 점수 등을 주로 사용함

Code Implementation

import numpy as np
from collections import defaultdict

def calculate_iou(box1, box2):
    # IoU 계산 함수
    x1, y1, w1, h1 = box1
    x2, y2, w2, h2 = box2

    inter_x1 = max(x1, x2)
    inter_y1 = max(y1, y2)
    inter_x2 = min(x1 + w1, x2 + w2)
    inter_y2 = min(y1 + h1, y2 + h2)

    inter_area = max(0, inter_x2 - inter_x1) * max(0, inter_y2 - inter_y1)
    union_area = w1 * h1 + w2 * h2 - inter_area

    return inter_area / union_area if union_area > 0 else 0


def compute_ap_pascal_voc(recall, precision):
    # PASCAL VOC 스타일: 11-point 보간법
    ap = 0.
    for t in np.arange(0., 1.1, 0.1):
        if np.sum(recall >= t) == 0:
            p = 0
        else:
            p = np.max(precision[recall >= t])
        ap = ap + p / 11.
    return ap

def compute_ap_coco(recall, precision):
    # COCO 스타일: 모든 포인트를 사용한 적분
    mrec = np.concatenate(([0.], recall, [1.]))
    mpre = np.concatenate(([0.], precision, [0.]))

    for i in range(mpre.size - 1, 0, -1):
        mpre[i - 1] = np.maximum(mpre[i - 1], mpre[i])

    i = np.where(mrec[1:] != mrec[:-1])[0]
    ap = np.sum((mrec[i + 1] - mrec[i]) * mpre[i + 1])
    return ap

def calculate_ap(gt_boxes, pred_boxes, iou_threshold, ap_method):
    # AP 계산 (공통 로직)
    num_gt = len(gt_boxes)
    num_pred = len(pred_boxes)

    pred_boxes = sorted(pred_boxes, key=lambda x: x[4], reverse=True)

    tp = np.zeros(num_pred)
    fp = np.zeros(num_pred)

    matched = [False] * num_gt

    for i, pred in enumerate(pred_boxes):
        best_iou = 0
        best_gt_idx = -1

        for j, gt in enumerate(gt_boxes):
            if not matched[j]:
                iou = calculate_iou(pred[:4], gt[:4])
                if iou > best_iou:
                    best_iou = iou
                    best_gt_idx = j

        if best_iou >= iou_threshold:
            if not matched[best_gt_idx]:
                tp[i] = 1
                matched[best_gt_idx] = True
            else:
                fp[i] = 1
        else:
            fp[i] = 1

    tp_cumsum = np.cumsum(tp)
    fp_cumsum = np.cumsum(fp)

    recalls = tp_cumsum / num_gt
    precisions = tp_cumsum / (tp_cumsum + fp_cumsum)

    if ap_method == 'pascal_voc':
        return compute_ap_pascal_voc(recalls, precisions)
    elif ap_method == 'coco':
        return compute_ap_coco(recalls, precisions)
    else:
        raise ValueError("Unknown AP method")

def calculate_map(gt_boxes_all, pred_boxes_all, iou_thresholds, ap_method):
    aps = defaultdict(list)

    for image_id in gt_boxes_all.keys():
        gt_boxes = gt_boxes_all[image_id]
        pred_boxes = pred_boxes_all[image_id]

        for class_id in set([box[4] for box in gt_boxes + pred_boxes]):
            gt_class = [box[:4] for box in gt_boxes if box[4] == class_id]
            pred_class = [box for box in pred_boxes if box[4] == class_id]

            for iou_threshold in iou_thresholds:
                ap = calculate_ap(gt_class, pred_class, iou_threshold, ap_method)
                aps[class_id].append(ap)

    mean_ap = np.mean([np.mean(ap_list) for ap_list in aps.values()])
    return mean_ap

def pascal_voc_map(gt_boxes_all, pred_boxes_all):
    return calculate_map(gt_boxes_all, pred_boxes_all, [0.5], 'pascal_voc')

def coco_map(gt_boxes_all, pred_boxes_all):
    return calculate_map(gt_boxes_all, pred_boxes_all, np.arange(0.5, 1.0, 0.05), 'coco')

# 사용 예시
gt_boxes_all = {
    'image1': [[10, 10, 100, 100, 0], [150, 150, 50, 50, 1]],
    'image2': [[30, 30, 80, 80, 0]]
}

pred_boxes_all = {
    'image1': [[15, 15, 95, 95, 0, 0.9], [155, 155, 45, 45, 1, 0.8]],
    'image2': [[35, 35, 75, 75, 0, 0.95]]
}

pascal_map_score = pascal_voc_map(gt_boxes_all, pred_boxes_all)
print(f"PASCAL VOC mAP: {pascal_map_score}")

coco_map_score = coco_map(gt_boxes_all, pred_boxes_all)
print(f"COCO mAP: {coco_map_score}")