阶段 2 · 看懂图像

目标检测:分类只是决定"是什么"
检测还要说清"在哪"

上一章《数据增强与迁移学习》讲"没数据也能练", 这一章接着问"在哪":分类网络的输出是"这是猫",检测网络的输出是一串框: "左上角 (12, 40)、右下角 (89, 156) 的地方有一只猫,置信度 0.94" (置信度 = 模型认为"这里真有目标"的把握,0 到 1)。

1

框的位置是连续的,没法分类

对比项分类检测
输出一个类别(1000 选 1)4 个坐标 + 1 个类别 + 1 个置信度
输出空间离散、有限 连续、无限(坐标可以是任意实数)
核心难题学一道分界线(决策边界) 框的数量、位置、大小都未知
损失函数交叉熵分类损失 + 回归损失(坐标差多少,常用 Smooth L1:误差小时像平方 MSE,大时像绝对值 MAE,和《损失函数》里的 Huber 是一回事;两项要联合优化,常见做法是直接相加)
主要指标准确率mAP(既要框对、又要类别对、还要不多报)

静态图 · 分类从有限个格子里挑一个,检测要吐一串任意实数

分类 · 输出是离散、有限的:从固定几个格子里挑一个 猫 狗 车 鸟 … 一共 1000 个格子,答案一定落在格子里 检测 · 输出是连续、数量不定的:坐标是任意实数,框有几个事先不知道 x y w h 128.4 96.7 152.0 134.2 ← 这四个实数 可以是任意值, 没有格子可挑 这张图有 3 个框,下一张有 30 个 —— 输出的长度本身就在变

核心矛盾:回归坐标,训练时要能逐格对答案;但一张图里的目标数量是变化的—— 有的图有 1 个目标,有的有 30 个。而网络最后一层的输出个数是写死的,它没法吐出变长的列表。 所有检测框架的差别,都是"怎么把变长问题变成定长问题"的不同答案。

2

锚框:先撒网,再修正

最直接的想法:在每个位置预先放几种固定形状的框(锚框 anchor), 让网络对每个框回答两个问题——"这里面有东西吗?"和"需要挪多少才能对准?"

这样输出就变成定长的了:如果撒了 20 万个锚框,网络就输出 20 万个"有无 + 偏移量"。 这些框的尺度和长宽比是人预先定的一串经验值(也可以在训练集真框上聚类——把相似的框自动分成几堆,每堆取一个代表形状; YOLOv2,2017 年 YOLO 的第二版,就这么做)。

互动 · 锚框与 IoU

💡 IoU 是检测里最重要的一个数

IoU(交并比)= 两个框重叠的面积 ÷ 它们合起来的面积。
· 训练时怎么贴标签:不同方法阈值不同——Faster R-CNN 的 RPN 用 0.7 / 0.3,RetinaNet 用 0.5 / 0.4; 本页互动取 0.5 / 0.3(过 0.5 正样本、低于 0.3 负样本、中间忽略)。 一个真框没有锚框过 0.5 时,与它 IoU 最高的那个也转正。
· 评估时:预测框和真实框的 IoU 决定它算不算命中(常用门槛 0.5,严格的用 0.75)。
拖圆点盯着右边那个数:跨过 0.50 的一瞬间,框只挪几个像素,身份就从背景翻成正样本; 往回拖先掉进 0.3~0.5 的灰色地带,再变回背景。
一句话:IoU 是把"框得准不准"变成一个可以计算、可以比较的数字。

3

去重:非极大值抑制 NMS

锚框撒出去之后,同一个目标周围会有几百个框都预测"这里有猫"。 必须去掉重复的。NMS 的做法极其简单粗暴:

① 排序按置信度从高到低排列所有框
② 拿最高分取出当前置信度最高的框,保留它
③ 干掉邻居删掉所有和它 IoU > 阈值的框(比如 0.5)
④ 重复回到第 ② 步,直到没有框剩下

互动 · 看 NMS 一步步清理

互动 · 把那个阈值从 0.9 拖到 0.2,看两只挨着的猫怎么被误杀

⚠️ NMS 的两个著名坑

① 拥挤场景会误杀。如果有两只猫紧挨在一起,它们各自的框 IoU 很高, NMS 会把其中一只删掉——这是"密集小目标"检测难的主要原因。
② 阈值很敏感。设太高(0.8)会留下大量重复框;设太低(0.2)会把不同目标也误删。
改进方案:Soft-NMS(不删,只降低邻居的置信度)、 DETR(用匈牙利匹配一次性配对,从根本上不需要 NMS)。

M

数学 · 「在哪」怎么变成四个数

网络要学的不是「框在哪」,而是「从锚框改到目标要挪多少」。 这一节把它画成一张能拖的图:公式里每个符号,图上都有一块。

互动 · 把四个数凑出来,让锚框盖住目标(每一步都当场算)

公式 · 从锚框到预测框的这四个数,每一个在图上都有一块

🎬 自己验一遍

先把 t_x、t_y 拖到能看到预测框(实线)压住真实框(暖色实线), 再把 t_w 拖回来——你会看到位置不用动,只要缩放变了,IoU 就又掉下去。
然后去第 2 节,把那个锚框拖到目标上:你刚才手拖的,就是这四个数在动。 网络做的不是「猜一个坐标」,而是把四个偏移量一点点逼近它们该有的值—— 所以它最终能对多准,是被「锚框撒得够不够密」卡住的。

4

两条路线:两阶段 vs 单阶段

对比项两阶段(Two-Stage)单阶段(One-Stage)
第一步先粗筛出几千个"可能含目标"的区域(RPN:专门干这件事的小网络) 一次前向直接输出所有框
第二步对每个候选区域做精细分类和回归无
速度慢(Faster R-CNN 约 5 FPS,即每秒 5 张图) 快(YOLO 可到 100+ FPS)
精度小目标、密集目标更好(2017 年前后的对比结论) 在 2017 年同期的对比里,单阶段在大、中目标上追平了两阶段;后来两边各有新版本互有胜负
代表R-CNN → Fast → Faster → Mask R-CNN YOLO 系列、SSD、RetinaNet、FCOS
今天主流特定高精度场景绝大多数实际部署

互动 · 把容易样本的权重压到多小,才是「单阶段逆袭」的关键一步

💡 单阶段逆袭的关键:Focal Loss

单阶段一开始精度差,主要原因是正负样本极度不平衡: 20 万个锚框里可能只有 10 个真框,也就是 99.995% 都是"背景"。 全猜"背景"就有 99.995% 准确率,于是学不到东西。
Focal Loss 的做法:给每个样本的损失乘一个系数:预测越有把握,系数越小。 系数就是 (1 − p)^γ,p 是模型给正确答案的概率,γ 控制压得多狠; p = 0.9、γ = 2 时系数是 0.01,所以 0.105 × 0.01 ≈ 0.0011。 这就把训练注意力从"一万个明显的背景"挪到了"几个真正难的框"上, 单阶段也就是靠它追上了两阶段。这是 RetinaNet 论文的核心贡献。

5

mAP:给框得准不准打分

两条路线谁更强,要靠一个分数来判。检测的分数就是 mAP。

概念含义
IoU 阈值预测框和真实框的 IoU 超过这个值,才算"检测到了"
TP / FP / FN 混淆矩阵里的三个格子:TP = 框对且类别对;FP = 多出来的框或类别错;FN = 漏掉的真框
Precision 精确率TP / (TP + FP) —— 你报出来的框里有多少是对的
Recall 召回率TP / (TP + FN) —— 真实目标里你找出了多少
AP 平均精度对某一个类别,画出 Precision-Recall 曲线,取曲线下面积
mAP所有类别的 AP 求平均。这是检测任务最常用的综合指标

AP 这样量出来:把预测框按置信度从高到低排好,一个一个往下数。 数到第 k 个时,「报出的框里对了几个」占 k 的比例是准确率(Precision), 「找回的真目标」占总数的比例是召回率(Recall)。 把这些点连起来就是 PR 曲线,曲线下面积就是 AP。

怎么算「配上对」:预测框按置信度从高到低,每个去找 IoU 最高、过了阈值、还没被配过的真框。 配上的算命中(TP),配不上的算误报(FP),没人配的真框算漏掉(FN)。 下面这批 12 个预测对 12 个真目标,阈值 0.5 时命中 11、误报 1——量出面积就是 AP = 0.882。

互动 · 同一个模型的同一批预测,换一个交并比阈值,平均精度就变一个数

⚠️ 一个很容易被误导的地方

论文里的 mAP@0.5 和 mAP@0.75 差别可能非常大。 早期论文习惯报 @0.5(框住大致位置就算对),但太宽松——只框住目标一半也能拿到 0.5。
所以现在更看重 mAP@[.5:.95](COCO 数据集的主指标,10 个阈值平均)。 看论文先确认用的是哪个阈值。

6

检测贵在哪、难在哪

难点为什么难什么时候真的会痛 → 换什么
标注成本极高 分类只要一个标签,检测要画框(真框由人工标,存成 COCO JSON 这类格式;标错一个就教模型框错一个)。一张图可能有上百个框,画框比点标签慢得多 要攒几十万张图 → 半自动预标注(先用现成模型框一遍,人只改错的),或先分类后检测(先判断图里有没有目标,再对有的图做检测)
尺度变化 同一张图里,小目标和占满画面的大目标混在一起;特征金字塔(FPN:把不同深度的特征图叠起来用)就是为这个 小目标被漏掉 → 提分辨率 / 加 FPN / 大图切片检测
密集场景 NMS 在拥挤场景会误杀,这是结构性缺陷 人群、货架、细胞这类场景 → 调 NMS 阈值、换 Soft-NMS,或直接用 DETR 这类无 NMS 的模型
长尾类别 常见类别(人、车)数据多,罕见类别(某种工具)数据极少,mAP 被长尾拖累 要报一个平均分 → 分别看每一类 AP,别只看 mAP
评估本身很绕 要同时权衡"框的准确度""类别正确""不多报""不漏报",任何单一数字都会误导 要用一个数下结论 → 同时报不同 IoU 阈值下的数,看清它怎么算的

互动 · 一张图里到底撒了多少个锚框,以及为什么九成九是背景

另一边是 DETR:2020 年的它把检测当成集合预测,用匈牙利匹配(一次性给每个预测框配一个真框的算法) 直接把两边配对,从头到尾不需要锚框和 NMS。刚出来时收敛慢、小目标差,后来 DINO(2022)、RT-DETR 追上了 YOLO。 它是检测里第一个端到端用 Transformer 的方法,比分类那边的 ViT 早五个月(Transformer 后面章节讲)。

7

小结

它对应哪条线 ② 没有免费午餐 → 必须先假设框在哪——检测不可能凭空知道目标在哪, 所以第 2 节先撒下一串锚框(一组写死的尺度和长宽比,0 个参数、不学也不训)。 正是这套先验把「变长的连续问题」压成「固定数量的提问」,密集预测才有了起点。
一句话 检测是把一个变长的连续问题塞进一台定长的离散机器,方案是“先撒满锚框”。 所以每一步都要靠阈值把连续量切开(IoU 0.5、NMS 0.5、mAP@0.5),争议和指标陷阱都长在这些切线上。
它牺牲了什么 牺牲了误差的连续性。连续坐标被量化成“相对锚框的偏移量”; 去重被交给一个不可导的几何规则(NMS,训练时根本传不回梯度); 4 个坐标最后被压成一个 0/1 判定。
换来的是“能训、能打分”;代价是密集场景容易错杀——两只挨着的猫,只要框重叠超过抑制阈值,其中一只就会被压掉。 这不是实现 bug,是这套切法的结构性缺陷(回扣暗线 B)。
🎬 自己验一遍

回到第 2 节那张锚框图,把圆点拖到真实目标上,看右边的 IoU 从 0.05 涨过 0.5—— 跨过那条线的一瞬间,右下角那段话从“通常会被忽略”换成“会被标为正样本”, 网络开始为这个框算损失。

你看到的就是“对错不是事实,是阈值”。框的位置只挪了几个像素, 它的身份却从背景变成了正样本。再挪回去一点,又变回背景。

它在暗线里站在哪

暗线这一章的回答
A 信息流动 输入 (H, W, 3) → 骨架网络(提特征的主干,常是 ResNet)每下采样一次尺寸减半,给出 H/8~H/32 大小的特征图(最后几层分别是原图的 1/8 到 1/32) → 每个位置撒 A 个锚框(A 是每个位置撒几个) → 输出 A×(1+类别数+4) 个数。出口是变长框列表(NMS 后 1~100 个),全站唯一「单次前向张量长度」不固定的任务。
定长化靠两步:撒锚框,把「有几个目标」变成固定数量的提问;再用 NMS 收成变长列表
B 什么被牺牲了 牺牲了可导性和定位精度的上限。坐标从“绝对位置”改成“相对锚框的偏移量”, 精度被锚框形状和数量卡住;NMS 是纯几何规则,训练时不在计算图里,两个目标只能分开处理。
这笔债直到 DETR 用匈牙利匹配把“去重”变成训练的一部分才还上(回看第 6 节)
C 参数账本 检测头(输出框和类别的那几层)很轻,骨架是绝对大头: ResNet-50 骨架 23.5M 参数,检测头只有几 M。锚框本身是 0 个参数——写死在代码里的几何数字。
张量账更贵:20 万个锚框在 COCO 的 80 类下要输出 20万 × (1+80+4) ≈ 1700 万个数,fp32 就是 68 MB; FPN 通常用 3~5 个尺度,账单还要乘 3~5。检测的显存大头是密集预测,不是参数
D 跑在什么上 前向是骨架卷积,算术强度高、GPU 吃得很香;可末尾的 NMS 要全局排序 + 逐个比 IoU,难并行, 实时检测最贵的动作常常是“把几万个框排序再比较”,不是卷积——嵌入式设备上先卡住的常常是后处理。
另一半:锚框让正负样本在显存里极度不平衡(99.995% 是背景), 于是“算这 20 万个样本的损失”本身就成了算力开销—— Focal Loss 重要,一半是数学,一半是它让损失算得动。完整账在 《硬件与算力账本》
E 它假设了什么 假设目标能被一组预定义好的形状覆盖(锚框的尺度与长宽比)。 这条假设在自然图像上大致成立,换成“长条形的缺陷”“极细的血管”就不成立—— 因为长宽比分布根本不在那几个预设里。
第二层假设更隐蔽:NMS 假设“同一个目标上那个框得更好的预测,置信度也更高”。 拥挤场景下这条假设会直接失效,这才是 Soft-NMS 存在的理由
F 违背了哪个直觉 两条。
① 99.995% 准确率的模型毫无用处——把 20 万个锚框全判成“背景”就能拿到这个数 (20 万个里只有约 10 个真框),但它一个目标也没找到。这就是“类别极不平衡”的算术真相。
② “框得准不准”不是一个事实,而是一个阈值。同一个预测框, 在 mAP@0.5 下算命中,在 mAP@0.75 下算漏检。看检测论文时最大的误导就来自这里
🎯 前后钩子

它接住了上一章的什么:《数据增强与迁移学习》开头那句 “ImageNet 花了两年多、几万人、上百万次人工标注”——检测就是那个“贵”的具体形状: 分类只要点一下,检测要画框(本页第 6 节那张表)。

它给后面留了什么:检测给出来的是一个粗糙的矩形。 如果连轮廓都要,就得进到像素级——那是 《语义分割》。 而 NMS 那个不可导的环节,最终被 DETR 的匈牙利匹配绕开了。

一句话带走目标检测

检测比分类多的那件事是"在哪",而"在哪"是连续的、数量可变的——这就是全部难点的来源。
锚框把变长问题变成定长问题;IoU 把"准不准"变成可计算的数字; NMS 去重(但在拥挤场景会误杀);Focal Loss 解决了正负样本极度不平衡, 让单阶段追上两阶段。
看论文时注意 mAP 用的是哪个 IoU 阈值——@0.5 和 @0.75 差别巨大。

8

拓展阅读

上面讲的都是「够用」的版本。想往下挖,这里有三个入口—— 它们不是必修内容,是给想再往前走一步的读者准备的。

📄 这一章的说法从哪来

💻 工业界怎么写

∑ 更严格的形式