Lecture 034 · AP 必须说明积分协议 · 实现代码
4 · AP 必须说明积分协议 · 实现代码
并入相关学习路径
固定框匹配、PR/AP、NMS误删相邻对象反例
展开完整实现代码
def all_points_ap(recall, precision):
"""All-points interpolated AP = sum of recall increments * envelope.
This is not 11-point AP, the mean of raw precisions, or COCO 101-point AP.
A sentinel at recall 1 with precision 0 gives unreached recall zero credit.
"""
recall, precision = np.asarray(recall, float), np.asarray(precision, float)
if recall.shape != precision.shape or np.any(np.diff(recall) < -1e-12):
raise ValueError("Aligned arrays and nondecreasing recall required")
if np.any((recall < 0) | (recall > 1) | (precision < 0) | (precision > 1)):
raise ValueError("Recall and precision must be in [0,1]")
mrec = np.r_[0.0, recall, 1.0]
mpre = np.r_[0.0, precision, 0.0]
envelope = np.maximum.accumulate(mpre[::-1])[::-1]
changed = np.where(mrec[1:] > mrec[:-1])[0]
ap = np.sum((mrec[changed + 1] - mrec[changed]) * envelope[changed + 1])
return float(ap), mrec, envelope
def evaluate_single_class(pred_boxes, scores, gt_boxes, iou_threshold=0.50):
"""One image / one class: choose the best-IoU GT, then check if already used.
VOC-style greedy matching, aligned with LAB10: a used best GT makes the
prediction a duplicate FP; do not fall back to a lower-IoU unused GT.
Score ties and IoU ties preserve input order. Threshold comparison is >=.
No crowd, ignore regions, maxDets, area bins, or dataset pooling.
"""
if not 0 <= iou_threshold <= 1:
raise ValueError("iou_threshold must be in [0,1]")
pred_boxes, gt_boxes = boxes_xyxy(pred_boxes), boxes_xyxy(gt_boxes)
scores = np.asarray(scores, dtype=float)
if scores.shape != (len(pred_boxes),) or not np.isfinite(scores).all():
raise ValueError("Require one finite score per prediction")
if len(gt_boxes) == 0:
raise ValueError("AP is undefined for this no-GT single-class example")
order = np.argsort(-scores, kind="stable")
used = np.zeros(len(gt_boxes), dtype=bool)
overlaps = box_iou(pred_boxes, gt_boxes)
tp, rows = [], []
for rank, pred_i in enumerate(order, 1):
gt_i = int(np.argmax(overlaps[pred_i]))
best_iou = float(overlaps[pred_i, gt_i])
meets_threshold = best_iou >= iou_threshold
is_tp = bool(meets_threshold and not used[gt_i])
if is_tp:
used[gt_i] = True
reason = "new GT matched"
elif meets_threshold:
reason = "duplicate: maximum-IoU GT already used; no fallback"
else:
reason = "IoU below evaluation threshold"
tp.append(int(is_tp))
rows.append({"rank": rank, "prediction": int(pred_i + 1),
"score": float(scores[pred_i]), "status": "TP" if is_tp else "FP",
"gt": gt_i + 1 if is_tp else None,
"best_gt": gt_i + 1, "best_iou_any_gt": best_iou, "reason": reason})
tp = np.asarray(tp, dtype=int)
cumulative_tp, cumulative_fp = np.cumsum(tp), np.cumsum(1 - tp)
recall = cumulative_tp / len(gt_boxes)
precision = np.divide(cumulative_tp, np.arange(1, len(tp) + 1), dtype=float)
ap, mrec, envelope = all_points_ap(recall, precision)
for i, row in enumerate(rows):
row.update(cumulative_tp=int(cumulative_tp[i]), cumulative_fp=int(cumulative_fp[i]),
recall=float(recall[i]), precision=float(precision[i]))
return {"iou_threshold": iou_threshold, "rows": rows, "ap": ap,
"recall": recall.tolist(), "precision": precision.tolist(),
"envelope_recall": mrec.tolist(), "precision_envelope": envelope.tolist(),
"final_recall": float(recall[-1]) if len(recall) else 0.0}
for threshold in EVAL_THRESHOLDS:
evaluation = evaluate_single_class(PRED, SCORES, GT, threshold)
print("IoU threshold:", threshold, "all-points AP:", evaluation["ap"])
for row in evaluation["rows"]:
print(row)