如果让我只挑一届CVPR来读目标检测论文,我会选2019。那一年的录用列表里,检测方向的文章质量密度高到有点离谱:Anchor-Free从概念验证走向系统化,Cascade R-CNN把高质量检测讲成一套完整方法论,GIoU在损失函数层面补上IoU的硬伤,ScratchDet则开始挑战ImageNet预训练的必要性。对做视觉算法的人来说,2019 CVPR目标检测论文基本就是后来几年检测器演化的地基,哪怕放到现在去翻,依然能挖出不少被低估的思路。
这篇整理适合两类人:一类是想系统入门目标检测的新人,另一类是已经在跑检测器、想快速了解当年有哪些关键工作值得补课的工程师。我尽量不贴大段公式,而是把每篇论文解决的核心矛盾、方法以及复现时容易忽略的细节讲清楚。文章中会提到一小部分不是CVPR2019的同期工作,因为它们和这一年的核心话题深度绑定,我会特别标注出来,免得大家查论文时对不上年份。
1. 2019年的目标检测战场:Anchor与Anchor-Free的正面碰撞
1.1 那时候的检测器,卡在哪儿了
在2019年之前,绝大多数检测器都绕不开anchor。anchor可以理解成一组预先摆好的候选框模板——你告诉模型“目标大概长这样”,模型再去微调它。Faster R-CNN在每个位置放9个anchor,SSD在不同层放不同尺度的框,RetinaNet的FPN也是围绕anchor设计正负样本的。这套机制在当年很有效,但问题也很明显:尺度、纵横比、数量全靠经验调,换一个数据集可能就得重新聚类anchor参数。
更让人头疼的是正负样本定义。训练时,anchor和真实框的IoU超过某个阈值才算正样本,低于另一个阈值就是负样本,中间的直接忽略。这意味着检测器的性能天花板很大程度取决于anchor的密度、尺度和匹配策略。你能在COCO上调好一套参数,换到小目标多的遥感图或者人脸密集场景,性能下降得飞快。于是2019年最热闹的争论就来了:能不能不预设anchor,让模型直接从特征图里回归出框?
这个方向上,CornerNet在2018年已经靠关键点配对证明了“没有anchor也能检测”,但它需要做角点分组,逻辑繁琐。而CVPR2019的FCOS直接把检测重定义成逐像素预测到四条边的距离,FSAF则尝试让特征层自己选,不再按尺寸硬性分配。这两个工作把Anchor-Free往前推了一大步,也正式拉开了和Anchor-Based方法的正面对话。
1.2 站在2019看三条演进路线
如果把2019 CVPR目标检测论文按解决的问题来分,我习惯把它们归成三条路线:
| 路线 | 代表论文 | 核心在解决什么 |
|---|---|---|
| 去锚框化 | FCOS、FSAF | 摆脱anchor引入的手工设计与正负样本定义 |
| 高质量检测 | Cascade R-CNN、Libra R-CNN | 解决IoU阈值不一致、训练样本与特征不平衡 |
| 损失函数与训练范式 | GIoU、Cluster Loss、ScratchDet | 让回归更可优化、让特征更有判别性、摆脱预训练依赖 |
另外,NAS-FPN也出现在这一年的CVPR里,它用神经网络搜索的方式设计特征金字塔结构,训练成本高,但对后来自动化结构搜索的启发很大。总体来看,2019是一个从“怎么把框调得更准”转向“为什么框要这样定义”的节点。读这一届论文,最好带着“它在挑战哪个默认假设”的视角去看,收获会大很多。
2. 一阶段新势力:FCOS、FSAF和同期特征表达工作
2.1 FCOS:把anchor丢掉,多尺度还在
FCOS是CVPR2019里我最推荐精读的一篇一阶段检测器。它把目标检测建模成逐像素的密集预测:只要某个位置落在某个真实框内部,它就作为正样本,直接回归该位置到框四条边的距离,也就是论文里的l、t、r、b。没有anchor,没有IoU匹配,也没有聚类先验,干净得有点像语义分割。
但逐像素预测会带来一个直接问题:同一个位置可能同时落在两个重叠的真实框里,该跟谁走?FCOS的三个解法都很务实。第一,利用FPN,不同金字塔层负责不同尺度范围,比如浅层管小目标、深层管大目标,从结构上减少尺度歧义。第二,每个位置只回归距离它最近的框,降低重叠目标的混乱。第三,引入centerness分支,惩罚离目标中心较远的像素。centerness的计算方式是:
sqrt(min(l,r)/max(l,r) * min(t,b)/max(t,b))
这个值在目标中心是1,越往边缘越接近0。推理时把它乘到分类分数上,相当于告诉模型:即使你预测出框,如果这个位置离目标中心太远,最终分数也要降下来。这个设计非常聪明,它不改变网络结构,只是用一个小分支压制了大量低质量的离中心预测。
复现FCOS的时候要注意,它的正样本数量比Anchor-Based方法多很多,因为每个框内的所有像素都参与训练。这意味着计算量和显存占用会上涨,backbone稍微大一点就得调batch size。另外,centerness分支虽然好用,但训练不稳时容易误杀一些高IoU的边缘预测,损失权重不要一开始就给太高。后来ATSS出来之后,很多FCOS的改进都集中在样本选择上,反而说明FCOS底子足够干净。
2.2 FSAF:让目标自己抢特征层
FSAF的全称是Feature Selective Anchor-Free Module,一句话概括就是:让每个实例自己选择最适合的特征层,而不是由FPN按尺度硬性分配。传统FPN做多尺度检测时,大目标固定分给深层,小目标固定分给浅层,这个规则基于经验,但不一定最优。有些中等尺寸、纹理复杂的目标,可能放到更高层语义更强的地方精度反而更高。
FSAF的做法是在原有anchor分支旁边加一个anchor-free分支,每个实例在每一层金字塔上先算一遍分类和回归损失,然后选择损失最小的那一层来训练。这就是所谓的在线特征选择。推理时,anchor分支和anchor-free分支的结果可以做soft fusion,让两条分支互补。这个思想后来被ATSS吸收,ATSS里的样本选择和FSAF一脉相承,都是在回答“哪个特征层、哪些位置应该学这个目标”。
我觉得FSAF最值得学的是它对“尺度分配”问题的思考方式。以前大家把尺度分配当超参来调,FSAF把它变成一个可优化的选择问题。代价是训练时每个实例都要在所有层上算一遍损失,训练开销会变高。实际复现中,它的精度提升不是爆发式的,但在一些尺度分布特别不均匀的数据集上,能明显看到它对中尺度目标的改善。
2.3 为什么绕不开M2Det、RFBNet这些同期非CVPR2019工作
严格来说,M2Det发在AAAI2020,RFBNet发在ECCV2018,但它们和2019 CVPR讨论的特征表达问题深度绑定,经常被放在同一批读物里。M2Det的核心是MLFPN,也就是多级特征金字塔网络。它不像FPN那样只做一次自顶向下的融合,而是把多个不同尺度的特征层反复融合、反复提炼,形成一个更厚的特征金字塔。效果确实好,但网络很重,显存消耗大,训练速度也慢。如果你在刷比赛,这种结构还能用得上;如果是做工业落地,性价比一般。
RFBNet则从感受野角度切入,受人类视觉系统启发,设计了一个射频模块。它用不同大小的卷积核加空洞卷积,组合出不同尺度的感受野,再通过shortcut保留原始信息,和Inception的思路有点像,但加了空洞卷积让感受野更大。这个模块可以直接嵌进SSD类检测器里,在保持实时性的前提下提升精度。读这两篇工作能帮你理解一个点:检测器的特征表达不只是“深不深”的问题,还有“广不广”和“融不融”的问题。FPN解决的是尺度融合,RFBNet解决的是感受野覆盖,M2Det解决的是多级信息复用,它们是可以同时使用的互补手段。
3. 两阶段与高质量检测:Cascade R-CNN、Libra R-CNN的提分逻辑
3.1 Cascade R-CNN:级联重采样才是关键
Cascade R-CNN在2018年首次提出,2019年CVPR版本扩展到了实例分割,但它的核心思想一直没变:一个检测器只能擅长一个IoU阈值。训练时你用0.5的IoU阈值匹配正样本,检测器就会变成0.5阈值下的专家;推理时RPN给出的候选框其实分布更分散,想让它直接输出高质量框,它反而不太会打分。
很多人以为Cascade就是把三个检测头串起来,每个头阈值不断提高,0.5、0.6、0.7,一直让框越来越准。但其实关键的细节是resampling,也就是重采样。前一个stage回归出来的框,到了下一个stage不能直接当输入用,要重新和真实框计算IoU,重新定义正负样本,再训练下一个检测头。只有这样,每一级检测器接收到的候选框分布才能匹配它自己的训练阈值。如果不重新采样,而只是反复用一个检测头回归同一个框,那是iterative bounding box regression,效果差很多,还容易过拟合。
Cascade R-CNN在COCO上的提升非常稳定,尤其是AP75这种高质量检测指标,比单阈值检测器高出好几个点。这背后的直觉其实很好理解:一个人做复杂任务不可能一步到位,先粗调再细调,每一级都只处理上一级留下的误差分布。2019年版本还加了mask分支,在级联框上做实例分割,效果也顺带涨了。复现时我建议直接用mmdetection里现成的cascade_rcnn_r101_fpn等配置跑一遍,重点体会每个stage的RoI是怎么重采样出来的。把这一点看懂了,两阶段检测器基本就懂了一半。
3.2 Libra R-CNN:三处不平衡一起治
Libra R-CNN的出发点是:检测任务不是一个单独的不平衡问题,而是三处不平衡叠在一起。第一是样本不平衡,训练时大量简单负样本淹没少量难样本;第二是特征不平衡,浅层特征分辨率高但语义弱,深层特征语义强但分辨率低;第三是目标边界不平衡,回归损失里outlier样本贡献太大,inlier样本被压得抬不起头。
针对样本不平衡,Libra R-CNN提了IoU-balanced Sampling。普通随机采样让低IoU的样本更容易被抽到,难的正样本反而出现得少。IoU-balanced的做法是把IoU范围分成均匀的区间,每个区间都保证有样本被抽到,这样可以显著提高高质量正样本在训练中的比例。
特征层面,它提出Balanced Feature Pyramid。做法很简单:把金字塔各层resize到同一尺寸,逐像素相加取平均,得到一个包含全局语义的融合特征,再把它广播回每一层。这个过程相当于把全局信息注入到每一层,缓解低层特征语义不足的问题。第三块是Balanced L1 Loss,把回归损失函数设计成分段形式,让小梯度样本的贡献增强,大梯度样本的贡献被限制。这样模型不会只盯着几个LeCun难样本,在COCO这种长尾分布的数据集上尤其有用。
Libra R-CNN是可以直接加在Faster R-CNN或Cascade R-CNN上的训练配方。它的修改点都不是伤筋动骨的大改,但每一处都踩在痛点,累积提升明显。我自己复现时感觉,它比很多花哨的新模块更实用,因为你可以在现有代码上快速验证,而不需要重写整个网络。
3.3 同期补充:CBNet这类复合骨干为什么会出现
虽然CBNet发在AAAI2019,但如果你想了解2019年检测器刷分的完整拼图,它绕不开。CBNet的思路非常直接:把多个backbone串联起来,前一个backbone输出的特征喂给后一个backbone,相当于让检测器有一个“复核”的特征提取过程。配合Cascade R-CNN,它在一段时间里把COCO的指标推得很高,代价是计算量也翻倍甚至更多。
这类工作在我眼里属于“用计算换精度”的典型代表。它不改变检测器的学习逻辑,只是让特征更强。对比赛刷分有意义,但工业落地时通常会被砍掉。从原理层面看,CBNet提醒我们一件事:骨干网络的选择和设计对检测器的影响,往往比检测头里的trick更直接。到了2020年之后,大家更倾向于用更强的预训练模型或者自监督表征来提升骨干,而不是简单叠加两个backbone。
4. 损失函数与训练技巧:GIoU、Cluster Loss、ScratchDet的实操价值
4.1 GIoU:给不相交的框一条梯度出路
在GIoU之前,目标检测的回归损失大多是Smooth L1,也就是先算坐标差的绝对值,再决定用L1还是L2。但L1类损失和最终评估指标IoU并不一致。你优化的是坐标绝对值,评估的是框的重叠程度,于是可能出现损失降低但IoU没涨的情况。更尴尬的是,IoU损失本身也有问题:如果两个框完全不相交,IoU恒为0,梯度无法引导框向对方靠近。
GIoU的改进很优雅。它先算两个框的最小闭包区域C,然后用公式:
GIoU = IoU - (C - union) / C
如果两个框不相交,闭包区域C会比并集大很多,第二项变成负值,GIoU就小于0。框离得越远,惩罚越大,梯度会先把两个框推近。一旦有重叠,第二项趋近0,GIoU又平滑过渡回IoU。你可以把它理解成一场考试:IoU只告诉你及格还是不及格,GIoU则会给“你离及格还差多远”一个连续分数。
实际使用GIoU时有个细节容易被忽略:闭包面积C在极端情况下可能非常小或者接近0,低精度训练时容易出现除零问题。我在FP16混合精度训练里遇到过NaN,后来给分母加了一个极小值常数才稳住。另外,当两个框有包含关系时,GIoU会退化成IoU,收敛速度变慢。这个问题后来被DIoU和CIoU解决,但GIoU作为把几何度量引入回归损失的里程碑,依然值得好好读。
4.2 Cluster Loss:人脸检测里的嵌入约束
Cluster Loss这篇来自CVPR2019,论文名是Detection with Clustering Loss,应用场景集中于人脸检测。它的动机是:检测器训练时只关心框的位置和分类标签,训练出来的特征对于“判断两个人是不是同一个身份”这种任务帮助有限。如果你做的是人脸检测,下游经常还要接人脸识别,那就希望检测器提的特征本身就有区分度。
Cluster Loss的思路是在检测框架里增加一个度量学习分支。同一张人脸的样本特征被拉向同一个聚类中心,不同身份的聚类中心互相推开。这样检测器不只在学“这里有没有人”,还在学“这个人是谁的特征表达”。在WIDER FACE这类数据集上,它比纯检测损失收敛得更稳,同时提升了后续人脸验证的精度。
这篇论文给我的启发是:损失函数不一定只能做分类和回归,它可以承载另一个子任务的目标。后来很多多任务检测网络也是这个思路,比如同时预测关键点、分割掩码或者嵌入向量。不过对通用目标检测来说,Cluster Loss直接套用的收益不如人脸检测明显,因为它依赖训练集中有明显的身份语义。想尝试的话,我建议把它理解成“辅助度量损失”,而不是把希望全压在聚类分支上。
4.3 ScratchDet:从零训练时如何稳住BN和初始化
ScratchDet是那一届里非常特别的一篇——它问了一个大家习以为常但很少质疑的问题:检测器真的需要ImageNet预训练吗?长期以来,做检测默认要用分类任务预训练好的backbone,否则收敛很慢,精度也差。但预训练和检测场景之间存在领域差异,而且很多实际任务的数据分布和自然图像差很远,迁移未必是最优解。
ScratchDet发现,从零训练检测器并非不行,关键在三点:合理的BN使用、合适的优化器、以及足够强的数据增强。论文里用RMSProp替代SGD,让ResNet从零训练时更容易稳住;同时把BN广泛接入backbone各层,解决深层网络梯度动荡的问题。在VOC和COCO上,它的SSD系列从零训练效果能接近甚至达到预训练版本的精度。
这篇论文的实操价值在于,如果你面对一个数据隐私要求很高或者领域差异很大的任务,完全可以试试不加载预训练权重。我在实际项目中试过从零训练一个检测器,loss波动确实比预训练版本大,尤其是前几个epoch,所以需要更长的warmup,学习率也不能给太猛。另外,从零训练对小batch size特别敏感,BN的统计量很容易偏,建议至少保证batch size在16以上,否则优先使用Group Normalization。
5. 复现与避坑随记:哪些论文值得深读,哪些坑我替你踩过了
5.1 精读优先级与复现难度评估
把这几篇CVPR2019的论文按“影响力和复现性价比”排个序,我的建议是这样的:
| 论文 | 精读理由 | 复现难度 | 是否建议跑源码 |
|---|---|---|---|
| FCOS | Anchor-Free一阶段的标杆,后续很多工作都基于它 | 中等 | 建议,代码量适中 |
| Cascade R-CNN | 高质量检测的核心思想,影响深远 | 中等 | 建议,mmdetection里有现成配置 |
| GIoU | 损失函数设计教科书级别的案例 | 低 | 可以自己实现,10分钟就能验证 |
| Libra R-CNN | 训练平衡方法论,通用性强 | 中等 | 建议,能直接加在baseline上 |
| FSAF | 特征选择思想有启发,但本身已逐步被融合 | 中等偏高 | 可选 |
| ScratchDet | 训练范式特殊,不建议从零实战 | 高 | 了解思路即可 |
| Cluster Loss | 度量学习与检测结合,人脸检测专用 | 中等 | 按需复现 |
我的个人体会是,不要平均用力。想快速建立一个检测器知识框架,FCOS加Cascade R-CNN加GIoU这三篇足够搭起主干。Libra R-CNN可以帮你理解训练配方的重要性。剩下的按自己方向挑着看就行。
5.2 复现时最容易翻车的三个细节
第一个坑是Cascade R-CNN的re-sampling。很多人实现级联时只把上一个stage输出的框微调了一下,没重新和真实框计算IoU就送进下一个stage,结果精度不涨反降。一定要记住,级联的每一步接收的候选框分布都要和当前stage的训练阈值匹配,这是级联有效的根本原因。
第二个坑是GIoU的数值稳定性。计算闭包区域时,如果两个坐标值在浮点逼近下产生微小误差,面积可能变成负数或者极小值。混合精度训练中尤其容易出现NaN。我习惯在计算面积时对长宽做一个clip下限,同时在分母上加上一个很小的大于0的常数,比如0.0001。
第三个坑是FCOS的centerness。推理时要把centerness和分类分数相乘作为最终置信度,这在目标尺度差异极大的数据集上很容易误杀高质量预测。如果你发现召回率没有问题但AP不高,大概率是centerness把一些离中心稍远的正样本分数压得太低。这时候可以试着降低centerness分支的损失权重,或者在验证阶段先不乘centerness,对比一下再决定。
5.3 从2019往后看:哪些思想留了下来
如果站在现在回看2019这届CVPR,会发现很多争论的答案已经溶解在今天的检测器里。FCOS的逐像素预测和centerness成了YOLOX等新一代一阶段检测器的默认组件;ATSS把FSAF的在线选择和FCOS的密集预测整合成了一套完整的样本分配策略;GIoU之后出现了DIoU、CIoU,在YOLO系列和各类工程框架里成为标配;Cascade R-CNN的高质量检测思想也被各种SOTA模型以不同形式吸收,比如用两阶段甚至多阶段的方式逐级优化候选框。
NAS-FPN所代表的搜索式结构设计,后来演变成NAS、可微搜索、自动增广等一系列自动化方法论。ScratchDet的从零训练实验虽然没让大家都放弃预训练,但它推动大家重新审视预训练和下游任务之间的领域差距。这些思想的共同点是:它们都在回答检测器里“每个设计决策到底为什么存在”这个问题。理解了这个逻辑,你再看任何一篇新检测论文,都会习惯性地把它拆回这几个维度。
我自己现在读论文,仍然会习惯性先把2019这批工作拉出来对照。遇到一个新检测器,我会问:它有没有anchor?样本怎么定义?回归用什么loss?特征层怎么选?这几个问题的答案,基本都能从FCOS、FSAF、GIoU、Cascade R-CNN这些论文里找到对应。如果让我给一个最实在的建议,那就是别只啃摘要,至少把FCOS的centerness和Cascade的re-sampling亲手实现一遍,比读十篇论文都管用。那一年大家为Anchor-Free吵得不可开交,几年后再看,争论的答案早就长进了每个检测器的默认配置里。