Lecture 03 学习讲义
正文
工具
按问题查找
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)