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의 계산⚑
-
포인트 개수의 영향:
- 포인트 개수가 적으면: 계산은 빠르지만 정확도가 떨어질 수 있음
- 포인트 개수가 많으면: 더 정확한 결과를 얻을 수 있지만 계산 시간이 늘어남
-
보간 방법:
- PASCAL VOC: 11-point 보간법을 사용함. 재현율 0부터 1까지 0.1 간격으로 11개 포인트에서 정밀도를 계산함
- COCO: 모든 포인트를 사용하여 PR 곡선 전체 영역을 적분함
-
안정성 개선 방법:
- 모든 포인트 사용: 가능한 모든 임계값에 대해 정밀도와 재현율을 계산함
- 정밀도 평활화: 각 재현율 수준에서 해당 재현율 이상의 최대 정밀도를 사용함
- 보간법 사용: 선형 보간이나 더 복잡한 보간 방법을 사용하여 곡선을 부드럽게 만듦
-
구현 시 주의사항:
- 일관성 유지: 평가 시 항상 동일한 방법과 포인트 개수를 사용해야 함
- 표준 준수: 가능한 PASCAL VOC나 COCO와 같은 표준 평가 방식을 따르는 것이 좋음
- 충분한 포인트: 정확한 결과를 위해 충분한 수의 포인트를 사용해야 함
-
라이브러리 활용:
- 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⚑
- https://learnopencv.com/mean-average-precision-map-object-detection-model-evaluation-metric/
- 참고 : Precision / Recall 이 안정적이지 않을 때, 보간법을 사용하여 안정적인 AP를 계산
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}")