图像分类只需要给整张图片一个类别,目标检测却要回答得更具体:图里有哪些目标,它们分别在哪里,模型对每个判断有多大把握。最终输出不是一个固定长度向量,而是一组数量可变的结果。每条结果通常包括类别、边界框和分数。

这件事看上去只是“分类再加四个坐标”,真正做起来却多了好几层约束:同一张图可能有多个目标;一个目标周围会产生大量重复候选框;训练时要决定哪个预测负责哪个标注;评估时还要在不同置信度和不同 IoU 要求下做一对一匹配。
我建议把整条链路拆成六个问题来看:输出到底表示什么,框怎样编码,预测怎样与标注匹配,重复框怎样过滤,指标怎样计算,以及模型部署后错在什么地方。只要这六件事能对上,换成 Faster R-CNN、YOLO、FCOS 或 DETR,你都不会只剩下背模型名字。
本节会严格区分三类数值:训练损失用于更新参数,置信度用于排序和筛选预测,IoU 与 AP 用于衡量几何和检测质量。它们可能互相影响,但不是同一个量,也不能用某个训练损失直接代替部署评估。
目标检测的输入是一张图像,输出可以写成一个集合:
是边界框, 是类别, 是模型给这条检测结果的分数。 会随图像内容和阈值改变:空场景可能没有结果,拥挤场景可能有几十个结果。
这里容易混淆四个相邻任务:
矩形框只是目标可见区域的近似。细长、旋转或形状不规则的物体会带入很多背景;严重遮挡时,不同标注规范还可能要求标“可见区域”或“推测的完整区域”。因此,开始建模前必须把标注边界说清楚,否则两个标注员可以都很认真,却给出不一致的框。
检测结果还是一个无序集合。若图里有三个人,模型先输出左边的人还是右边的人,本身没有意义。训练和评估真正关心的是预测能否与真实目标建立合理的一对一对应,而不是输出顺序是否一致。
工程里最常见的两种轴对齐框格式是:
xyxy:左上角 与右下角 ;两者可互相转换:
反向转换时,,,纵向同理。这里还隐藏着一个现实问题:不同库对像素端点是否包含在框内,可能采用连续坐标或离散像素约定。不要凭直觉给宽高额外加一,应当跟随数据格式和算子的定义。

坐标既可以用像素表示,也可以除以图像宽高归一化到 。归一化并不会自动解决所有问题。模型输入若发生缩放、裁剪、填充或翻转,框必须执行同一个几何变换,而且最终还要知道如何映射回原图。
例如,把原图等比例缩放后放进正方形画布,四周补边,这种预处理通常叫 letterbox。模型输出的是画布坐标。若直接画回原图而不减去 padding、再除以缩放比例,框会出现整体平移或尺寸偏差。
基于 anchor 的检测器还会把真实框写成相对 anchor 的偏移。设 anchor 为 ,目标框为 ,一种常见编码是:
这样做把绝对位置回归改成“相对一个参考框修正多少”。具体实现还可能对这些量乘权重或采用其他参数化,读代码时要确认编码器与解码器是否成对。
def xyxy_to_cxcywh(box):
x1, y1, x2, y2 = box
if x2 < x1 or y2 < y1:
raise ValueError("边界框端点顺序错误")
return (
(x1 + x2) / 2,
(y1 + y2) / 2,
x2 - x1,
y2 - y1,
)给定预测框 和真实框 ,交并比定义为:
IoU 在 内。完全重合时为 1,没有交集时为 0。它同时对位置、宽高和尺度敏感,所以比单独比较四个坐标更贴近“两个框是否覆盖同一块区域”。

计算时要先把交集宽高截断到非负值:
def box_iou_xyxy(a, b):
ix1 = max(a[0], b[0])
iy1 = max(a[1], b[1])
ix2 = min(a[2], b[2])
iy2 = min(a[3], b[3])
iw = max(0.0, ix2 - ix1)
IoU 用来评估很自然,但直接把 当回归损失时有一个明显困难:两个框完全不相交时,IoU 都是 0,单看数值无法分辨“只差一点就碰到”还是“离得很远”。
GIoU 在 IoU 之外加入最小外包框 。 是同时包住 和 的最小轴对齐矩形:
当两个框不重叠时,第二项仍能反映它们之间的空隙。GIoU 的范围是 ,越大越好;训练时常写成 。它是一种可选的框回归目标,不意味着 COCO 评估会改用 GIoU。COCO 的匹配仍以 IoU 阈值为核心。
检测头往往先产生大量候选。一个候选可能包含框回归量、类别 logits,以及某种“这里是否有目标”的分数。不同架构的分数定义并不完全相同。
原始 YOLO 把框的置信度设计成“存在目标的条件与定位质量”结合的量,并在测试时再与条件类别概率组合。很多现代实现会把 objectness 与类别分支分开,也有一些检测器直接输出每类分数,没有独立 objectness。DETR 一类集合预测模型则让每个 query 预测类别,其中包含“无目标”类别。
因此,不要把所有检测器都机械写成同一个公式。更稳妥的说法是:后处理使用的类别分数由模型输出按照该实现的定义得到,它用来排序和筛选候选;它不必等于概率,也不必等于 IoU。
训练前还要决定“谁负责哪个真实框”,这一步叫样本分配或匹配:
多任务损失可以抽象写成:
并非所有模型都有这四项。 处理类别, 处理几何, 处理目标存在性, 可以包括辅助层监督等。损失权重决定不同任务的梯度尺度,不是评估时 AP 的加权系数。
密集检测还有严重的前景/背景不平衡:绝大多数位置都没有目标。RetinaNet 提出的 Focal Loss 用 降低容易样本的权重,让训练更关注困难样本:
它解决的是训练中的类别不平衡,不会替代 NMS,也不会直接计算 AP。
密集检测器会在相邻位置或多个 anchor 上给同一个目标输出许多相似框。非极大值抑制(NMS)用一个简单的贪心过程去重:

两种阈值的作用完全不同。置信度阈值决定低分候选能否入场,通常越高,结果越少,误检可能减少但漏检可能增加。NMS IoU 阈值决定两个高分框需要重叠到什么程度才被视为重复:阈值越低,抑制越激进;阈值越高,越容易保留相邻框。
常规做法是按类别执行 NMS,因为一个“人”框和一个“自行车”框可以真实重叠。若完全不区分类别,高分人框可能错误压掉自行车框。另一方面,类别极易混淆且同一实例会被输出多个类别时,也可以研究 class-agnostic NMS,但它是需要验证的设计选择。
NMS 不是完美的一对一推理。拥挤人群中的两个真实目标可能高度重叠,激进 NMS 会把其中一个删掉;同一目标的两个框若 IoU 不够高,又可能同时留下。Soft-NMS 会衰减重叠框分数而不是直接删除;DETR 则通过集合匹配训练减少对 NMS 的依赖。选哪一种要看架构和错误分布。
from torchvision.ops import batched_nms
# boxes: [N, 4],采用 xyxy
# scores: [N],用于排序的类别分数
# labels: [N],保证不同类别分别抑制
keep = batched_nms(boxes, scores, labels, iou_threshold=0.5)
final_boxes = boxes[keep]把模型名字放到流程里,比背版本号更有用。

Faster R-CNN 的第一阶段是区域提议网络(RPN),它在共享特征图上产生一批可能含目标的候选区域;第二阶段把候选区域对齐为固定尺寸特征,再完成类别判断和更精细的框回归。
“两阶段”不等于要对原图运行两遍骨干网络。RPN 与后续检测头共享卷积特征。它的好处是先把密集空间收缩成较稀疏的候选,再对候选做细致判断;代价是候选生成、RoI 操作和后处理让流程更复杂。
YOLO、SSD、RetinaNet、FCOS 等方法直接在一个或多个特征层上做密集预测,不显式生成一批候选区域再送入第二个分类头。原始 YOLO 把整张图的边界框和类别预测统一进一次网络计算;RetinaNet 用 Focal Loss 处理密集前景/背景不平衡;FCOS 展示了不预设 anchor、按像素位置回归框的方案。
“一阶段一定快、两阶段一定准”只能当历史直觉,不能当今天的定律。真实速度受骨干、输入分辨率、候选数、后处理、硬件、精度和导出引擎共同影响。对自己的设备做端到端基准,比引用另一块 GPU 上的论文 FPS 更可靠。
DETR 把检测写成直接集合预测:固定数量的 object queries 与图像特征交互,训练时用匈牙利匹配建立一对一对应。未匹配 query 预测“无目标”。这种全局匹配减少了 anchor 设计和传统 NMS 等手工部件,但原始 DETR 也有收敛慢、小目标表现受限等已知问题。
这三类路线不是互斥标签库。后续工作会混合多尺度特征、动态匹配、卷积与注意力,甚至提供带 NMS 和端到端头的不同运行方式。理解一条实现时,最终还是要回到它如何产生候选、如何分配正样本、如何去重。
anchor 是铺在特征图位置上的参考框。每个位置可放多种尺度和长宽比,检测头预测“这个 anchor 属于什么类别,以及怎样把它修正成目标框”。它把任意框预测转成相对参考框的分类与回归。
anchor-based 方法的难点不只在框的数量。还要决定:哪些 anchor 是正样本,哪些是负样本,中间模糊区是否忽略;不同特征层负责什么尺度;anchor 的尺度和长宽比是否匹配数据。如果目标形状与预设 anchor 差异很大,正样本会变少,训练也更依赖匹配规则。
anchor-free 不表示“没有任何参考位置”。FCOS 仍在特征图的空间点上预测,只是不为每个点预先放一组矩形 anchor。对位于目标内部的点,它可回归到框四边的距离:
多尺度特征层、中心采样和尺度范围仍然属于分配设计。若一个点同时落入多个真实框,还要规定由哪个目标负责。也就是说,anchor-free 删除了一类预设框和相关超参数,并没有删除样本分配问题。
DETR 也常被口语化地归入 anchor-free,但它与 FCOS 的“点回归”不是同一种机制。DETR 的参考单元是 learned queries,并用集合匹配分配目标。讨论“是否使用 anchor”时,最好继续说明它究竟按点、按 query 还是按候选区域预测。
检测数据的一条标注至少需要图像标识、类别和边界框。COCO 风格 JSON 还会记录 annotation id、category id、面积、iscrowd 等字段;YOLO 风格文本常按行保存类别与归一化中心宽高。格式转换时最危险的不是文件扩展名,而是类别编号、坐标格式和图像尺寸的语义是否保持一致。
我建议在训练前做三层抽检。
第一层是结构检查:、,面积为正,类别 id 存在,图像文件可读取,框没有明显越界。第二层是:随机画出几百张图,专门看小目标、遮挡、边缘目标和空图。第三层是:每类实例数、每图实例数、框面积与长宽比分布、不同拍摄地点或时间的占比。
数据划分要避免近重复泄漏。连续视频帧、同一产品的连拍、同一患者的多张影像或同一场地的重复画面,不能简单逐图随机分配到训练和验证。更可靠的做法是按视频、主体、设备、地点或时间分组划分,让验证集更接近真实泛化问题。
检测增强必须同步变换图像和框。水平翻转要改横坐标;裁剪后要截断保留框、更新面积,并规定可见比例过低的目标是否删除;拼接或混合增强还要合并多图标注。颜色变化通常不改几何框,但它是否保留任务语义仍要结合领域判断。
小目标尤其敏感。一次强裁剪或下采样可能让一个原本 的目标只剩几个像素。若验证集中的小目标 AP 很低,先检查输入分辨率、标注最小尺寸、增强后的可见性和特征层步幅,再急着更换模型名字。
先看单个类别和固定 IoU 阈值。评估器把该类别的预测按分数从高到低排序,然后逐条处理:在同一张图中寻找尚未匹配、且 IoU 达到阈值的真实框;找到则记为 TP,否则记为 FP。一个真实框通常只能匹配一次,所以同一目标的第二个重复预测会成为 FP。
随着排序向后推进,累计精确率和召回率为:
改变分数截断位置会得到一条 PR 曲线。AP 是对这条曲线做规定方式的插值与汇总,而不是在某个固定置信度阈值下算一次 precision。COCO API 使用 101 个召回率采样点,并对最大检测数、面积范围等条件有明确设置。
“mAP”在不同论文和库里口径不总一致,报告时最好直接写全:
所以,AP50 高而主 AP 明显低,常见解释是“模型大致找到了目标,但框在严格 IoU 下不够贴合”,也可能包含重复框和排序问题。APs 明显低则要继续检查分辨率、标注、小目标数量和特征层。只报一个“mAP=0.7”而不写 IoU 范围、数据划分、类别平均方式和最大检测数,几乎无法复现。
评估还应固定预处理和推理设置。输入尺寸、测试时增强、置信度下限、每图最大检测数和 NMS 参数都会改变结果。为追求部署速度而提前删掉大量低分候选,可能截断 PR 曲线并降低召回;用于正式比较时,应遵守统一评估协议。
一个检测模型离线 AP 不错,不代表部署链路已经可靠。端到端系统还包括解码、缩放与 padding、颜色通道、归一化、模型计算、候选解码、NMS、坐标映射和业务规则。任何一处与验证时不一致,都可能让线上结果变化。
我会先抽取一批代表性失败样本,把问题分成几类:
这比只盯着总损失有效。重复框多,先检查 NMS 和分数定义;严格 IoU 下掉得多,检查框回归与标注边界;某个设备拍摄的图像大量漏检,检查域差异和划分;训练集与验证集都差,才更像数据、容量或优化基础问题。
置信度阈值不是模型的永久属性。安防报警更在意误报成本,辅助筛查可能更在意召回;不同类别也可能需要不同阈值。用验证集画每类 precision-recall 或成本曲线,在明确的业务代价下选择阈值,再锁定测试集做一次最终评估。
分数还可能没有校准。0.8 分不保证在所有类别、光照和设备上都对应 80% 正确率。若业务把分数当概率使用,应单独检查可靠性,并确保校准数据与部署分布接近。
部署基准应记录硬件、精度、输入尺寸、batch size、预热次数和统计分位数,并覆盖预处理、推理和后处理。平均 20 ms 可能掩盖明显的 P99 尾延迟;GPU 异步执行若没有正确同步,也会得到虚假的低耗时。
模型导出后要做数值和任务两层回归:先比较原框架与部署引擎的原始输出,再在同一批样本上比较解码后的框、类别和 AP。量化、算子替换或 NMS 实现差异都可能改变边界样本。
cxcywh:中心 、宽 和高 。