You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用Python手动实现YOLOv8混淆矩阵及PR曲线计算

手动实现YOLOv8评估指标(不依赖val.py)

1. 提取模型预测与真实标注数据

首先加载YOLOv8模型并获取测试集的预测结果与真实标注:

from ultralytics import YOLO
import numpy as np

# 加载预训练模型
model = YOLO('yolov8n.pt')

# 假设你已经有测试集的图片路径和对应的标注(xyxy格式+类别ID)
# 示例:每个样本的真实标注格式为(boxes: np.ndarray, cls: np.ndarray)
test_data = [
    ("test_img1.jpg", (np.array([[10,20,50,60], [30,40,70,80]]), np.array([0, 1]))),
    # 更多测试样本...
]

# 批量获取预测结果,整理为统一格式
preds = []
targets = []
for img_path, (target_boxes, target_cls) in test_data:
    results = model(img_path)
    pred_boxes = results[0].boxes.xyxy.numpy()
    pred_scores = results[0].boxes.conf.numpy()
    pred_cls = results[0].boxes.cls.numpy()
    preds.append((pred_boxes, pred_scores, pred_cls))
    targets.append((target_boxes, target_cls))

2. 实现IoU计算函数

IoU是匹配预测框与真实框的核心指标,直接用numpy实现:

def calculate_iou(box1, box2):
    # box格式:(x1, y1, x2, y2)
    x1 = max(box1[0], box2[0])
    y1 = max(box1[1], box2[1])
    x2 = min(box1[2], box2[2])
    y2 = min(box1[3], box2[3])
    
    inter_area = max(0, x2 - x1) * max(0, y2 - y1)
    box1_area = (box1[2] - box1[0]) * (box1[3] - box1[1])
    box2_area = (box2[2] - box2[0]) * (box2[3] - box2[1])
    union_area = box1_area + box2_area - inter_area
    
    return inter_area / union_area if union_area > 0 else 0.0

3. 按conf阈值计算混淆矩阵

混淆矩阵行代表真实类别,列代表预测类别,最后一行/列用于统计未匹配的FP/FN:

def compute_confusion_matrix(preds, targets, conf_thresh, iou_thresh=0.5, num_classes=80):
    conf_matrix = np.zeros((num_classes + 1, num_classes + 1), dtype=np.int32)
    
    for pred, target in zip(preds, targets):
        pred_boxes, pred_scores, pred_cls = pred
        # 筛选当前conf阈值以上的预测框
        mask = pred_scores >= conf_thresh
        pred_boxes = pred_boxes[mask]
        pred_cls = pred_cls[mask].astype(np.int32)
        
        target_boxes, target_cls = target
        target_cls = target_cls.astype(np.int32)
        target_matched = np.zeros(len(target_boxes), dtype=bool)
        
        # 按预测得分从高到低匹配,避免重复匹配
        sorted_indices = np.argsort(pred_scores[mask])[::-1]
        for idx in sorted_indices:
            box = pred_boxes[idx]
            cls = pred_cls[idx]
            # 找同类别未匹配的真实框
            same_cls_mask = (target_cls == cls) & (~target_matched)
            if not np.any(same_cls_mask):
                conf_matrix[num_classes, cls] += 1  # 未匹配的FP
                continue
            # 计算IoU并匹配
            ious = np.array([calculate_iou(box, tb) for tb in target_boxes[same_cls_mask]])
            if np.max(ious) >= iou_thresh:
                m_idx = np.argmax(ious)
                original_idx = np.where(same_cls_mask)[0][m_idx]
                target_matched[original_idx] = True
                conf_matrix[target_cls[original_idx], cls] += 1  # TP
            else:
                conf_matrix[num_classes, cls] += 1  # 低IoU的FP
        
        # 统计未被匹配的真实框(FN)
        for idx in np.where(~target_matched)[0]:
            cls = target_cls[idx]
            conf_matrix[cls, num_classes] += 1
    
    return conf_matrix

4. 计算Precision、Recall与PR曲线

遍历0到1的conf阈值区间,计算每个阈值对应的precision和recall:

def compute_pr_curve(preds, targets, num_classes=80, iou_thresh=0.5):
    conf_thresholds = np.linspace(0, 1, 101)  # 生成0.00到1.00的101个阈值
    precision = []
    recall = []
    
    for conf_thresh in conf_thresholds:
        tp, fp, fn = 0, 0, 0
        for pred, target in zip(preds, targets):
            pred_boxes, pred_scores, pred_cls = pred
            mask = pred_scores >= conf_thresh
            pred_boxes = pred_boxes[mask]
            pred_cls = pred_cls[mask].astype(np.int32)
            
            target_boxes, target_cls = target
            target_cls = target_cls.astype(np.int32)
            target_matched = np.zeros(len(target_boxes), dtype=bool)
            
            # 匹配预测框
            for box, cls in zip(pred_boxes, pred_cls):
                same_cls_mask = (target_cls == cls) & (~target_matched)
                if not np.any(same_cls_mask):
                    fp += 1
                    continue
                ious = np.array([calculate_iou(box, tb) for tb in target_boxes[same_cls_mask]])
                if np.max(ious) >= iou_thresh:
                    m_idx = np.argmax(ious)
                    original_idx = np.where(same_cls_mask)[0][m_idx]
                    target_matched[original_idx] = True
                    tp += 1
                else:
                    fp += 1
            
            # 统计FN
            fn += np.sum(~target_matched)
        
        # 计算当前阈值的precision和recall
        current_precision = tp / (tp + fp) if (tp + fp) > 0 else 0.0
        current_recall = tp / (tp + fn) if (tp + fn) > 0 else 0.0
        precision.append(current_precision)
        recall.append(current_recall)
    
    return conf_thresholds, np.array(precision), np.array(recall)

绘制PR曲线:

import matplotlib.pyplot as plt

conf_thresholds, precision, recall = compute_pr_curve(preds, targets)
plt.plot(recall, precision, linewidth=2)
plt.xlabel('Recall')
plt.ylabel('Precision')
plt.title('YOLOv8 Precision-Recall Curve')
plt.grid(alpha=0.3)
plt.show()

5. 确定最优conf阈值

最优阈值通常选择F1分数最高的点,F1是precision和recall的调和平均数:

f1_scores = 2 * (precision * recall) / (precision + recall + 1e-8)  # 加极小值避免除零
best_idx = np.argmax(f1_scores)
best_conf_thresh = conf_thresholds[best_idx]
best_f1 = f1_scores[best_idx]

print(f"最优conf阈值: {best_conf_thresh:.2f}")
print(f"对应F1分数: {best_f1:.2f}")

内容的提问来源于stack exchange,提问作者Baro

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.18 22:38:31