supervision 模型基准测试实战指南:mAP、F1 Score 与混淆矩阵全流程解析
【免费下载链接】supervisionWe write your reusable computer vision tools. 💜项目地址: https://gitcode.com/GitHub_Trending/su/supervision
本篇指南面向需要横向对比多个目标检测/实例分割模型效果的开发者,完整讲解基于supervision的模型基准测试(Benchmark)流程:如何加载 YOLO 格式数据集、构建推理评测循环、重映射类别、可视化预测结果,并用MeanAveragePrecision(mAP)、F1Score与sv.ConfusionMatrix输出可量化的性能结论。读完后你可以独立完成一套可复制的模型评测方案,并理解 mAP 计算背后的 COCO 评测器实现细节。
整体流程分为四步:
- 准备带标注的评测数据集;
- 加载待评测的模型;
- 运行模型,逐图收集
predictions与targets; - 用
supervision.metrics中的指标计算 mAP、F1,或用混淆矩阵做可视化诊断。
指南以实例分割模型为例展开,但同样的流程同样适用于目标检测、实例分割与旋转框(OBB)模型。
一、环境准备:下载数据集与加载模型
1.1 安装依赖
基准测试通常涉及三个库:roboflow(管理与下载数据集)、inference(调用本地或云端模型)、supervision(评测指标,注意安装带metrics的可选依赖以启用绘图等能力):
pip install roboflow inference "supervision[metrics]"1.2 下载数据集
评测的前提是一个带标注的数据集。使用roboflow包下载:
from roboflow import Roboflow rf = Roboflow(api_key="<YOUR_API_KEY>") project = rf.workspace("<WORKSPACE_NAME>").project("<PROJECT_NAME>") dataset = project.version("<DATASET_VERSION_NUMBER>").download("<FORMAT>")指南示例使用的是一个小型 Corgi v2 数据集(标注质量高且自带测试集):
rf = Roboflow(api_key="<YOUR_API_KEY>") project = rf.workspace("fbamse1-gm2os").project("corgi-v2") dataset = project.version(4).download("yolov11")下载后会在当前工作目录生成Corgi-v2-4文件夹,其中包含train、test、valid三个目录以及一个data.yaml文件。data.yaml中记录了类别名称与 ID,后续加载数据集和重映射类别都会用到它。
1.3 加载模型
根据所用框架选择对应方式加载模型,关键区别在于模型输出转成sv.Detections的方式:
RF-DETR:预训练检测/分割 checkpoint 由rfdetr包提供,其predict方法直接返回Detections对象,评测循环中无需额外转换:
from rfdetr.detr import RFDETRSegSmall model = RFDETRSegSmall()Inference(本地预训练模型):Roboflow Inference 提供多种预训练模型,且无需 API Key:
from inference import get_model model = get_model(model_id="yolov11s-seg-640")Inference(平台部署模型):在 Roboflow 平台上训练并部署的模型,用项目名/模型版本作为 model_id:
from inference import get_model model_id = "<PROJECT_NAME>/<MODEL_VERSION>" model = get_model(model_id=model_id)Ultralytics:
pip install "ultralytics<=8.3.40"from ultralytics import YOLO model = YOLO("yolo11s-seg.pt")二、评测基准测试的基本问题:用哪个数据集?
选错评测集是基准测试中最常见的错误。四种场景的判断标准如下:
- 无关数据集(Unrelated Dataset):如果有一份从未参与该模型训练的数据集,这是最佳选择。
- 训练集(Training Set):仅当模型不是在该数据上训练时可用。否则绝不要用它做基准测试——结果会虚高得不真实。
- 验证集(Validation Set):模型训练过程中每个 N 个 epoch 都会在其上评估,验证损失往往直接决定是否停止训练。因此即使模型没有直接在这批图上做梯度更新,它也已经间接影响了训练结果,评测结果可能偏乐观。
- 测试集(Test Set):专门保留下来的测试数据,模型在训练期间从未见过——这才是基准测试应该使用的集合。
因此,无关数据集或test集是基准测试的首选。但使用无关数据集时还会遇到三类典型陷阱:
- 额外类别:无关数据集中可能包含模型不认识的类别,需要在计算指标前将其过滤掉(可参考 过滤检测结果指南)。
- 类别不匹配:无关数据集的类别名/ID 与模型输出的类别体系不同,需要重映射,见下文"运行模型"一节。
- 数据污染:如果
test集划分不当,部分图片可能实际出现在training或validation中,结果会过于乐观;训练与测试图拍摄于相同环境、光照、角度等高度相似的情况同样会导致此问题。 - 缺少测试集:部分数据集不带测试集。此时应自行收集并标注数据;退而求其次可用验证集,但要意识到结果偏乐观,并尽快在真实场景中验证。
三、运行模型:用 DetectionDataset 构建评测循环
有了评测数据集和模型后,用sv.DetectionDataset.from_yolo创建数据集迭代器,然后对每张图运行模型。其实现位于 DetectionDataset.from_yolo,关键参数包括:
images_directory_path:图片目录;annotations_directory_path:YOLO 标注目录;data_yaml_path:记录类别信息的data.yaml;force_masks:为True时强制为所有标注加载掩码;is_obb:为True时以 OBB 格式([class_id, x, y, x, y, x, y, x, y])读取标注;show_progress:为True时显示 tqdm 进度条。
返回的DetectionDataset是一个可迭代对象,每次迭代产出(image_path, image, label)三元组,其中label即该图的地真Detections(targets)。
RF-DETR 版本(predict直接返回Detections):
import supervision as sv test_set = sv.DetectionDataset.from_yolo( images_directory_path=f"{dataset.location}/test/images", annotations_directory_path=f"{dataset.location}/test/labels", data_yaml_path=f"{dataset.location}/data.yaml", ) image_paths = [] predictions_list = [] targets_list = [] for image_path, image, label in test_set: predictions = model.predict(image[:, :, ::-1]) image_paths.append(image_path) predictions_list.append(predictions) targets_list.append(label)Inference 版本(用sv.Detections.from_inference转换模型输出):
import numpy as np import supervision as sv test_set = sv.DetectionDataset.from_yolo( images_directory_path=f"{dataset.location}/test/images", annotations_directory_path=f"{dataset.location}/test/labels", data_yaml_path=f"{dataset.location}/data.yaml", ) image_paths = [] predictions_list = [] targets_list = [] for image_path, image, label in test_set: result = model.infer(image)[0] predictions = sv.Detections.from_inference(result) image_paths.append(image_path) predictions_list.append(predictions) targets_list.append(label)Ultralytics 版本(用sv.Detections.from_ultralytics转换):
import supervision as sv test_set = sv.DetectionDataset.from_yolo( images_directory_path=f"{dataset.location}/test/images", annotations_directory_path=f"{dataset.location}/test/labels", data_yaml_path=f"{dataset.location}/data.yaml", ) image_paths = [] predictions_list = [] targets_list = [] for image_path, image, label in test_set: result = model(image)[0] predictions = sv.Detections.from_ultralytics(result) image_paths.append(image_path) predictions_list.append(predictions) targets_list.append(label)注意 RF-DETR 传入的图像做了image[:, :, ::-1]处理(BGR 转 RGB),这是 RF-DETR 接口的约定,而 Inference/Ultralytics 直接消费 BGR 原图。
四、重映射类别:让模型输出与数据集对齐
使用无关数据集时,模型输出的类别 ID 与名称往往和数据集不一致。例如模型按 COCO 80 类训练,输出dog(COCO 中dog的 ID 为 16),而数据集只有一个类Corgi(ID 为 0)。先定义一个通用的重映射函数:
import numpy as np def remap_classes( detections: sv.Detections, class_ids_from_to: dict[int, int], class_names_from_to: dict[str, str], ) -> None: new_class_ids = [ class_ids_from_to.get(class_id, class_id) for class_id in detections.class_id ] detections.class_id = np.array(new_class_ids) new_class_names = [ class_names_from_to.get(name, name) for name in detections["class_name"] ] detections["class_name"] = np.array(new_class_names)然后把重映射和"剔除数据集中不存在的类别"两步嵌入评测循环。数据集的类别名与 ID 可以从data.yaml查看,或打印dataset.classes。
RF-DETR 版本:RF-DETR 自带 COCO 类别配置,对应下方映射。一个值得注意的细节是——指南建议按重映射后的 class_id 过滤,而不是按模型生成的 class_name 过滤,这样才能兼容那些 COCO 稀疏名称查询行为不一致的 RF-DETR 版本:
import numpy as np import supervision as sv test_set = sv.DetectionDataset.from_yolo( images_directory_path=f"{dataset.location}/test/images", annotations_directory_path=f"{dataset.location}/test/labels", data_yaml_path=f"{dataset.location}/data.yaml", ) image_paths = [] predictions_list = [] targets_list = [] for image_path, image, label in test_set: predictions = model.predict(image[:, :, ::-1]) remap_classes( detections=predictions, class_ids_from_to={18: 0}, class_names_from_to={"dog": "Corgi"}, ) predictions = predictions[ np.isin(predictions.class_id, np.arange(len(test_set.classes))) ] image_paths.append(image_path) predictions_list.append(predictions) targets_list.append(label)Inference / Ultralytics 版本:按class_name过滤即可(COCO 预训练模型的dog对应 ID 16):
import supervision as sv test_set = sv.DetectionDataset.from_yolo( images_directory_path=f"{dataset.location}/test/images", annotations_directory_path=f"{dataset.location}/test/labels", data_yaml_path=f"{dataset.location}/data.yaml", ) image_paths = [] predictions_list = [] targets_list = [] for image_path, image, label in test_set: result = model.infer(image)[0] # Ultralytics 版为 model(image)[0] predictions = sv.Detections.from_inference(result) # Ultralytics 版为 sv.Detections.from_ultralytics(result) remap_classes( detections=predictions, class_ids_from_to={16: 0}, class_names_from_to={"dog": "Corgi"}, ) predictions = predictions[ np.isin(predictions["class_name"], test_set.classes) ] image_paths.append(image_path) predictions_list.append(predictions) targets_list.append(label)每个模型训练的类别映射都不同,重映射表需要根据所用模型的具体类别配置来编写,这一点务必核对模型文档。
五、可视化预测:直观检查模型的失败点
数值指标之前,先用图像直观地对比地真(targets)与预测(predictions)。用两种颜色的sv.PolygonAnnotator分别标注后拼成 3x3 网格展示:
import supervision as sv N = 9 GRID_SIZE = (3, 3) target_annotator = sv.PolygonAnnotator(color=sv.Color.from_hex("#8315f9"), thickness=8) prediction_annotator = sv.PolygonAnnotator( color=sv.Color.from_hex("#00cfc6"), thickness=6 ) annotated_images = [] for image_path, predictions, targets in zip( image_paths[:N], predictions_list[:N], targets_list[:N] ): annotated_image = cv2.imread(image_path) annotated_image = target_annotator.annotate( scene=annotated_image, detections=targets ) annotated_image = prediction_annotator.annotate( scene=annotated_image, detections=predictions ) annotated_images.append(annotated_image) sv.plot_images_grid(images=annotated_images, grid_size=GRID_SIZE)这里紫色(#8315f9)是地真标注,青色(#00cfc6)是模型预测。对于目标检测模型用sv.BoxAnnotator,对于 OBB 模型用sv.OrientedBoxAnnotator,更多标注器选项可参考 annotator 文档。
六、可视化基准测试:ConfusionMatrix.benchmark 逐图落盘
如果不想手动拼标注网格,可以直接用sv.ConfusionMatrix.benchmark(...)。它的实现见 ConfusionMatrix.benchmark,签名为:
confusion_matrix = sv.ConfusionMatrix.benchmark( dataset=test_set, callback=callback, conf_threshold=0.3, # 置信度阈值,低于该值的预测被丢弃 iou_threshold=0.5, # 低于该 IoU 的匹配被判为 FP metric_target=MetricTarget.BOXES, save_directory_path="./results", # 可选:逐图可视化落盘目录 )其中callback是一个"输入图像、返回Detections"的函数,例如 RF-DETR 场景下可写成lambda image: model.predict(image[:, :, ::-1])(源码 docstring 中的示例正是这样写的)。
关键参数说明(均来自源码 docstring 与实现):
dataset:DetectionDataset实例,迭代时自动产出图像与地真标注;conf_threshold:预测置信度阈值(默认0.3),低于该值的预测不参与 TP/FP/FN 判定;iou_threshold:预测与地真框(或 OBB)的 IoU 判定阈值(默认0.5),低于该值的匹配被判为假阳性;metric_target:支持BOXES(默认)与ORIENTED_BOUNDING_BOXES,不支持MASKS;save_directory_path:指定后,为每张图写出一张 2x2 结果网格(Ground Truth/True Positives/False Positives/False Negatives四个面板)到该目录,直接复用原始图像文件名、不建子目录;文件已存在时会发出UserWarning并覆盖。目录不存在时会自动mkdir(parents=True, exist_ok=True)。
落盘由内部的_save_detection_validation_visualization完成,最终函数通过cls.from_detections(...)汇总所有图,返回可plot()的ConfusionMatrix对象,一次调用同时得到逐图诊断图与聚合混淆矩阵。
混淆矩阵的判定逻辑值得展开:在 evaluate_detection_batch 中,每张图的预测先按conf_threshold过滤,然后计算预测×地真的 IoU 矩阵,取所有IoU > iou_threshold的候选匹配,按"类别先匹配优先、再按 IoU 降序"贪心地一对一分配;已匹配的对计入矩阵[gt_class, det_class](对角线即 TP,非对角线是类别错误),未匹配的地真计入最后一列(FN),未匹配的预测计入最后一行(FP)。矩阵形状为(num_classes + 1, num_classes + 1)。
得到对象后可用confusion_matrix.plot()渲染热力图(支持save_path、normalize、fig_size等参数),矩阵本体则存在confusion_matrix.matrix属性中。
七、Benchmarking Metrics 之一:mAP
7.1 计算 mAP
mAP(Mean Average Precision)是目标检测最常用的指标,衡量模型在所有类别与 IoU 阈值下的平均精度。supervision的实现位于 MeanAveragePrecision,构造参数包括:
metric_target:使用BOXES、MASKS还是ORIENTED_BOUNDING_BOXES计算 IoU(默认BOXES)。注意选择MASKS时,predictions 和 targets 必须都携带mask,否则compute()会抛出ValueError(见 _detections_content);class_agnostic:是否忽略类别、把所有对象当作单类计算;class_mapping:类别 ID 重映射字典——这可以替代上文手动remap_classes的做法,直接在指标侧完成映射;image_indices:参与计算图像的子集索引。
按指南示例(分割模型评测掩码)计算:
from supervision.metrics import MeanAveragePrecision, MetricTarget map_metric = MeanAveragePrecision(metric_target=MetricTarget.MASKS) map_result = map_metric.update(predictions_list, targets_list).compute()update负责累积各图的Detections(可多次调用、内部按列表extend,并在 compute 前校验预测数与地真数一致);compute返回MeanAveragePrecisionResult。
7.2 理解 mAP 结果与 mAP 50:95 的含义
打印结果一目了然:
print(map_result)MeanAveragePrecisionResult: Metric target: MetricTarget.MASKS Class agnostic: False mAP @ 50:95: 0.2409 mAP @ 50: 0.3591 mAP @ 75: 0.2915 mAP scores: [0.35909 0.3468 0.34556 ...] IoU thresh: [0.5 0.55 0.6 ...] AP per class: 0: [0.35909 0.3468 0.34556 ...] ... Small objects: ... Medium objects: ... Large objects: ...其中最常用的是mAP 50:95:它在 IoU 阈值0.5到0.95(步长0.05,共 10 档)上取平均精度再对类别求平均;而mAP 50、mAP 75只考虑单一阈值(0.5/0.75)。这一点可以从源码严格印证——COCOEvaluatorParameters 中:
# IoU thresholds [0.5, 0.55, 0.6, 0.65, ..., 0.95] self.iou_thrs = np.linspace(0.5, 0.95, int(np.round((0.95 - 0.5) / 0.05)) + 1, endpoint=True) self.rec_thrs = np.linspace(0.0, 1.00, 101, endpoint=True) # 101 档召回阈值 self.max_dets = [1, 10, 100] # 每图最大检测数MeanAveragePrecisionResult直接暴露map50_95、map50、map75属性(见 结果类定义),无检测或无地真时返回-1哨兵值。
结果同样可以绘图:
map_result.plot()plot()基于 matplotlib 绘制包含mAP@50:95、mAP@50、mAP@75以及 small/medium/large 分档的柱状图(实现见 MeanAveragePrecisionResult.plot);此外还有to_pandas()方法可将指标导出为 DataFrame,方便多模型对比时落表。
7.3 面积分档:small / medium / large
mAP 还会按检测对象的面积拆分结果。源码中常量定义明确(mean_average_precision.py):
SMALL_OBJECT_AREA = 32**2 # < 1024 像素 MEDIUM_OBJECT_AREA = 96**2 # 1024 ~ 9216 像素即 small 为面积小于32²像素、medium 介于32²与96²之间、large 大于96²像素。这与 COCO 官方评测口径一致,便于定位模型在远景小目标上的短板。
7.4 底层原理:内建的 COCO 风格评测器
从源码结构看,MeanAveragePrecision并非简单循环求值:compute()会把累积的 predictions/targets 转成 COCO 风格的字典(images/annotations/categories,标注含bbox、area、iscrowd、ignore等字段),然后交给内建的 COCOEvaluator 完成完整评测:
- 对每张图、每个类别,先按分数降序排列预测并截断到
max_dets[-1](100)个; - 按
metric_target分派 IoU 计算:BOXES走box_iou_batch_with_jaccard(含 crowd Jaccard 约定)、MASKS走批量掩码 IoU、OBB 走oriented_box_iou_batch; - 对 10 档 IoU 阈值逐一做贪心匹配(同一地真至多匹配一次,crowd 除外),记录
dtMatches/gtMatches/dtIgnore; - 最后
_accumulate()在(阈值 × 召回 × 类别 × 面积档 × max_dets)五维张量上按置信度降序累积 TP/FP,做单调精度包络(if pr[i] > pr[i - 1]: pr[i - 1] = pr[i])后在 101 档召回阈值处采样,得到各档 Average Precision。
这套流程与 pycocotools 的评测语义对齐(_pycocotools_summarize甚至按 pycocotools 的 12 项统计口径打印摘要),因此用supervision跑出的 mAP 可以与传统 COCO 评测直接互相对照。
八、Benchmarking Metrics 之二:F1 Score
F1 是精确率(多少预测是正确的)与召回率(多少真实实例被检出)的调和平均:F1 = 2 * precision * recall / (precision + recall),尤其适合关注假阳性与假阴性平衡的场景。
supervision的 F1Score 构造参数:
metric_target:同上,支持BOXES(默认)/MASKS/ORIENTED_BOUNDING_BOXES;averaging_method:跨类别聚合方式,默认AveragingMethod.WEIGHTED。AveragingMethod 定义了三种:MACRO(各类别等权平均,不考虑类别不平衡)、MICRO(全局统计 TP/FP/FN,样本多的类权重更大)、WEIGHTED(按各类真值实例数加权,兼顾类别不平衡)。
计算方式与 mAP 一致的两段式 API:
from supervision.metrics import F1Score, MetricTarget f1_metric = F1Score(metric_target=MetricTarget.MASKS) f1_result = f1_metric.update(predictions_list, targets_list).compute()打印结果:
print(f1_result)F1ScoreResult: Metric target: MetricTarget.MASKS Averaging method: AveragingMethod.WEIGHTED F1 @ 50: 0.5341 F1 @ 75: 0.4636 F1 @ thresh: [0.53406 0.5278 0.52153 ...] IoU thresh: [0.5 0.55 0.6 ...] F1 per class: 0: [0.53406 0.5278 0.52153 ...] ... Small objects: ... Medium objects: ... Large objects: ...与 mAP 类似,F1 也支持f1_result.plot()出图、按 small/medium/large 面积分档拆分(compute()内部会对ANY、SMALL、MEDIUM、LARGE四个口径各算一次,见 F1Score.compute)。面积分档口径与 mAP 相同:< 32²、32² ~ 96²、> 96²像素。从源码看,F1 的 IoU 阈值序列取np.linspace(0.5, 0.95, 10),即同样是 10 档 0.05 步长;且当某张图只有预测没有地真时(如背景图),所有预测直接计为假阳性——这一边界行为在实现中有显式处理。
此外,supervision.metrics 还导出了Precision、Recall、MeanAverageRecall三个同类指标(共享同一套Metric.update(...) -> compute()接口与MetricTarget体系),在需要更细粒度对比时可自行组合。
九、常见问题(FAQ)
Q:如何用 supervision 基准测试一个模型?使用supervision.metrics.MeanAveragePrecision:用update(predictions_list, targets_list)累积各图的预测与地真Detections,再调用compute()得到结果。若要做混淆矩阵,则用sv.ConfusionMatrix.from_detections(predictions=predictions, targets=targets, classes=classes)构造后调用plot()。
Q:MeanAveragePrecision 使用哪些 IoU 阈值?在0.50到0.95之间、步长0.05上计算 mAP(即 mAP@50:95),并单独给出 mAP@50 与 mAP@75——源码中iou_thrs = np.linspace(0.5, 0.95, 10)印证了这一点。
Q:能评测分割模型吗?可以。将模型输出转为Detections传入MeanAveragePrecision.update(...)即可;若metric_target为BOXES,mAP 路径直接使用detections.xyxy构造 COCO 风格边框;选MASKS则要求预测与地真都带掩码。
Q:ConfusionMatrix 是什么、怎么用?sv.ConfusionMatrix按类别可视化 TP / FP / FN:sv.ConfusionMatrix.from_detections(predictions=..., targets=..., classes=..., conf_threshold=0.3, iou_threshold=0.5)(注意conf_threshold源码默认值为0.3)构造,confusion_matrix.plot()渲染热力图。若想把逐图验证可视化写盘,给sv.ConfusionMatrix.benchmark(...)传save_directory_path="./results",它会在该目录中按原始图像文件名写出包含Ground Truth、True Positives、False Positives、False Negatives四个面板的 2x2 结果网格。
十、小结
本文围绕supervision的模型基准测试流程展开,要点回顾:
- 数据集选择是结果可信度的第一道关口:优先使用未参与训练的无关数据集或
test集,警惕数据污染与类别不匹配; - 评测循环以 DetectionDataset.from_yolo 为核心,
(image_path, image, label)三元组让你可以无差别地接入 RF-DETR、Inference、Ultralytics 等不同框架,只需在循环内把模型输出转成sv.Detections; - 类别重映射通过
class_ids_from_to/class_names_from_to两张映射表完成,过滤时按class_id(RF-DETR)或class_name(Inference/Ultralytics)保留数据集内的类别; ConfusionMatrix.benchmark一次调用同时给出聚合混淆矩阵与逐图 TP/FP/FN 诊断网格,save_directory_path直接落盘、复用原文件名;- mAP 与 F1共享
update() -> compute()的两段式 API,支持BOXES/MASKS/ORIENTED_BOUNDING_BOXES三种 IoU 口径、10 档 0.05 步长 IoU 阈值、small/medium/large 面积分档,其底层是内建的 COCO 风格评测器,结果可与 pycocotools 口径直接对照; - 更多指标(Precision、Recall、MeanAverageRecall)与指标参数说明,可进一步参考仓库中 docs/metrics 目录下的文档。
【免费下载链接】supervisionWe write your reusable computer vision tools. 💜项目地址: https://gitcode.com/GitHub_Trending/su/supervision
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考