1. 两种范式的前世今生:为什么这个问题值得搞清楚
做目标检测的朋友,不管你是刚入门还是已经调过几年模型,肯定都绕不开一个选择题:用 Anchor-based 还是 Anchor-free?
先说个真实的场景。前阵子有个朋友接手一个工业质检项目,要在产线上检测几十种不同规格的零件缺陷。他在网上找了一圈开源代码,发现有的用 Faster R-CNN 系列,有的用 FCOS 或 CenterNet,但问他为什么选这个不选那个,他说不出来,只说"大家都这么用"。后来模型在长条形的零件上检测效果很差,他才意识到 Anchor 大小设置和数据集目标尺寸分布压根不匹配,白白折腾了两周。
这个例子不是个例。很多人其实并不清楚 Anchor-based 和 Anchor-free 背后的本质区别,只是跟着感觉选。但这两种范式从原理到工程落地的差别都非常大,选错方向往往意味着后面所有调参、修 bug、提精度的努力都可能是白费功夫。
这篇文章我想用自己的理解和实战经验,把这两条技术路线的来龙去脉、核心机制、训练细节、坑点都掰开揉碎讲清楚。适合三类人看:刚接触目标检测、对 Anchor 概念模棱两可的新手;被正负样本分配、损失函数设计折磨到头秃的调参选手;以及需要在真实项目里做技术选型、评估方案的工程师。
我尽量少讲空泛的理论,多讲"为什么这样做"和"实际跑起来是什么样的"。
2. 从"猜窗口"到"指哪打哪":两种范式的底层逻辑差异
2.1 Anchor-based 的祖师爷思想:预设候选框再微调
Anchor-based 的核心逻辑其实特别朴素:与其让模型凭空预测物体的位置,不如提前在图像上铺一大堆预设好的矩形框,每个框有不同的尺度(scale)和长宽比(aspect ratio),然后让模型判断每个框里有没有物体,如果有,再对这个框的位置和大小做微调回归。
这个思想最早成熟于 Faster R-CNN 中的 RPN(Region Proposal Network)。你可以在特征图的每个位置上铺 K 个 Anchor,K 通常是尺度数量乘以长宽比数量,经典配置是 3 种尺度搭配 3 种长宽比,也就是 9 个。假设输入一个 800×600 的图,特征图下采样 16 倍后是 50×38,那么 Anchor 总数就是 50×38×9,大约 17100 个。这就是 Anchor-based 方法"密集采样"的本质。
要理解为什么需要 Anchor,得先知道检测任务本身有多难。检测模型要做两件事:分类——这个框里是什么;回归——这个框应该挪到哪、调多大。如果没有 Anchor 作为"起始猜测",模型相当于要在无穷多的可能位置和形状里去预测,这在数学上虽然可行,但优化起来非常困难,尤其对尺度差异很大的物体。
Anchor 的引入把"预测绝对量"变成了"预测相对量"。模型不再是直接输出"框的中心在(200,150),宽100,高80",而是输出"相对于预设 Anchor 的偏移量,dx, dy, dw, dh"。这个偏移量一般通过下面这组公式定义:
gx = px + dx * pw gy = py + dy * ph gw = pw * exp(dw) gh = ph * exp(dh)其中 px, py, pw, ph 是 Anchor 的位置和尺寸,dx, dy, dw, dh 是模型预测的偏移量。用对数形式回归宽高比,是为了让尺度缩放关系变成线性可加的,模型学起来更容易收敛。
这一套设计在当年是革命性的。它直接把目标检测从一个"自由回归问题"变成了一个"分类问题 + 小范围回归问题",大大降低了学习难度。经典的一阶段检测器 SSD 和 YOLOv2/v3,都继承了 Anchor 思想,只是不再做两阶段的区域提议,而是在特征图的每个位置上直接预测分类和回归结果。
2.2 Anchor-free 的逆袭:关键点与中心点预测
Anchor-free 不是一个单一方法,而是一族方法的总称。它们最核心的共性是:不再预设密集的 Anchor 框,而是直接用目标本身的一些属性来定义监督信号。
目前主流的 Anchor-free 范式可以分成两个流派:
一个是关键点流派,代表是 CornerNet 和 CenterNet。CornerNet 把物体检测变成预测目标左上角和右下角两个角点;CenterNet 更进一步,只预测目标的中心点,外加回归宽、高。这类方法用高斯热图(heatmap)来表示某个位置出现目标中心的概率,峰值响应点天然对应目标中心。
另一个是逐像素回归流派,代表是 FCOS。FCOS 在特征图的每个位置上直接预测该点到目标框四边的距离(left, top, right, bottom),也就是说每个位置本身成为"候选样本",如果这个位置落在某个目标框内部,它就负责预测这个目标框。
可以做一个直观的类比:Anchor-based 像一个广撒网的渔民,宁可撒一万个网眼密集的网,靠模型去筛掉空网;Anchor-free 则更像精确制导的渔枪,先定位目标特征点位,再估算目标范围。一个是"先猜框再校准",一个是"先找点再扩框"。
这个差异直接影响了两个核心问题:训练的样本分配策略,以及推理时如何把"大量预测"聚合成最终检测框。
2.3 两种范式在特征金字塔上的不同姿态
另外一个认知升级点在于多尺度特征融合(FPN)。Anchor-based 时代,Anchor 的尺度是手工设计并分配到不同特征层级的:小的 Anchor 配浅层高分辨率特征图去检测小物体,大的 Anchor 配深层低分辨率特征图去检测大物体。这个设计逻辑清晰,但坑也很明显——Anchor 尺度配置一旦和目标实际尺度分布不匹配,小目标就会漏检,大目标会重复检。
Anchor-free 同样重度依赖 FPN,但分配方式灵活得多。以 FCOS 为例,它不需要为每层指定 Anchor 尺度,而是规定:如果某个位置的"回归目标距离"超过了本层允许的上下限,就把它视为不在本层负责范围内。这样前向传播时,不同的目标会自适应地分配到匹配的特征层,人为干预少了一大截。
CenterNet 的方式更有意思,它完全不做多尺度 Anchor 分配,而是在单层热图上预测目标中心点,然后额外对每个目标的尺寸做回归。小目标的中心点虽然在深层特征上响应偏弱,但因为只是"一个点"而不是"一个框",漏检率并不会像 Anchor-based 那样因为 Anchor 尺度过大而急剧恶化。
3. 工程质量的关键:样本分配与损失函数怎么设计
3.1 Anchor-based 的正负样本分配:一套精密的"筛选机制"
Anchor-based 训练中,最关键的一步是判断一个 Anchor 是正样本、负样本还是忽略样本。
Faster R-CNN 用的规则是:与某个 Ground Truth 框 IoU 超过 0.7 的 Anchor 视为正样本;IoU 低于 0.3 的视为负样本;中间模糊地带直接忽略,不参与损失计算,避免给模型传递模糊信号。
YOLOv2/v3 的思路则单纯一些:只选与每个 Ground Truth 框 IoU 最大的那个 Anchor 作为正样本,IoU 低于阈值的都是负样本。但这样会导致每个目标只有一个正样本,学习信号太稀疏。
于是后来的增强版本普遍采用"1对N"甚至"1对多"匹配策略。实践中我发现,YOLOv3 在每层特征上为每个 Ground Truth 分配多个 Anchor 的做法效果更稳,相当于给模型更多"接近"目标的样本去学习,能显著加速收敛。
但 Anchor-based 的样本分配有个绕不开的难题:Anchor 参数本身需要根据数据集调。比如人像检测常用 1:1 和 1:1.5 的长宽比,交通标志则可能极端地需要 1:0.5 甚至更扁的形状。如果你不做基于 K-Means 的 Anchor 聚类,或者聚类后的样本分布变化了但 Anchor 没更新,正样本数量就会急剧下跌,漏检立刻显现。
3.2 Anchor-free 的样本分配:靠位置和距离说话
FCOS 的样本分配规则很简单:某个特征图位置点落在 Ground Truth 框内部,它就是正样本。但"在框内"的位置实在太多了,如果全部作为正样本,低质量位置(比如靠近边缘的地方)会影响回归精度。所以我习惯上会加一个 center sampling 策略:只有落在目标框中心附近某个半径范围内的位置才算正样本,进一步控制样本质量。
CenterNet 的思路是彻底换个角度:它对每个目标中心点生成一个高斯核(即一个以中心为峰值的响应区域),然后在特征图上回归一个连续的热力图。训练时用 modified focal loss 进行监督,正样本不再是"某个位置",而是"热力图上的高斯峰"。高斯核半径由目标尺寸决定,半径越大,目标中心附近可被接受的候选点越多,训练越容易,但过大的半径会造成临近目标之间的高斯峰相互干扰。
这里有个容易被忽略的细节:Anchor-free 虽然摆脱了 Anchor 的预设框数量,但它并没有摆脱"样本匹配"。只是匹配成本大不相同——Anchor-based 要计算上万个 Anchor 和所有 Ground Truth 之间的 IoU 矩阵,Anchor-free 通常只需要做一次"点是否在框内"的判定或高斯核覆盖范围检查。
3.3 损失函数的三重门:分类、回归、中心度
无论哪种范式,训练中计算损失的三个大头是躲不开的:分类损失、回归损失和辅助损失。
分类损失:
- Anchor-based 经典做法是交叉熵或 focal loss(用于解决正负样本严重不平衡的问题)。正负比例经常是 1:1000 到 1:10000,focal loss 通过调节难易样本权重来缓解。
- FCOS 同样用 focal loss 处理分类分支。
- CenterNet 用 modified focal loss,专门适配高斯热图的像素级分类,对峰点附近的衰减响应也计入损失。
回归损失:
- 大多数框架用 Smooth L1 Loss(也叫 Huber Loss),对异常值更鲁棒。
- 也有越来越多方法使用 GIoU / DIoU / CIoU Loss。CIoU 额外考虑了重叠面积、中心点距离和长宽比三个维度,实测在 Anchor-based 回归精度上提升非常明显,我个人调模型时基本已经默认 CIoU 替换 Smooth L1。
- FCOS 回归的是四条边的距离值,用 exp 函数限制输出范围为正数。注意 exp 会让回归值动态范围变大,训练初期特别容易梯度爆炸,我一般会在回归分支后面加一个 Scale 层(就是一个可学习的标量)来平衡量纲。
辅助损失:
- FCOS 有个经典操作叫 center-ness:在分类分支上并行预测一个"中心度"值,它的取值范围是 0 到 1,衡量预测位置偏离目标中心的程度。推理时把分类得分乘以 center-ness 得分作为最终置信度,能有效压制那些"位置在框内但离中心很远"的低质量预测框。
- CenterNet 则额外回归一个目标尺寸分支,这个分支和热图分支是完全分离的。实践中有个坑:如果目标尺寸方差特别大,回归分支会把精力集中在大的目标上,对小的物体尺寸预测不准。我试过把尺寸回归改成 log 空间,让小目标误差也能得到合理梯度。
4. 从理论到跑通:我自己的选型经验和踩坑记录
4.1 怎么选:自己项目里的决策清单
我不太喜欢说什么"Anchor-free 全面超越 Anchor-based"之类的话,因为这根本不是事实。选型要看具体场景,我建议按下面这个问题清单过一遍:
- 数据集类别数是否很多、目标尺度跨度是否极大?Anchor-free(特别是 FCOS 类)在长尾尺度分布上要省心很多。
- 推理延迟要求是毫秒级还是秒级?Anchor-free 通常在推理阶段省去了 Anchor 生成和 NMS 中大量候选框的排序负担,很多模型结构可以用全卷积直接端到端输出,对嵌入式平台更友好。
- 训练资源和迭代周期是否充足?Anchor-based 对初值敏感,经常需要反复调 Anchor 设置、匹配策略和正样本数量,训练调试的成本通常比 Anchor-free 高。
- 是否要求部署到 FPGA/DSP 这类受限平台?Anchor-free 的全卷积算子相对规整,更容易被量化或转换为固定算子;Anchor-based 里生成 Anchor 的网格操作虽然简单,但动态 IoU 计算如果在后处理,某些加速芯片支持不够好。
我举个自己的例子。之前做一个河道漂浮物检测项目,目标是矿泉水瓶、树枝、塑料袋和各种形态的杂物,尺度从几十像素到几百像素都有。最开始用 Anchor-based 的方法,不同 Anchor 尺度调了几轮,始终有 30% 左右的"细长条"漏检。后来换上 FCOS 类似的 Anchor-free 架构,靠 FPN 自适应分配,问题基本解决。但反过来,在另一个人脸人体检测项目中,目标形态非常规整(都是竖长的矩形),Anchor-based 加上 K-Means 聚类出的 3~5 个 Anchor 就能覆盖 95% 的情况,推理速度和精度都明显优于 Anchor-free 的实现,因为后处理里的 NMS 候选框数量少、置信度分布更集中。
结论:Anchor-based 适合目标尺度相对可控、有强先验形状的场景;Anchor-free 适合目标形态多样、尺度跨度大、希望减少人工先验干预的场景。两者不是替代关系,更像是工具尺和软件尺的关系,各有用处。
4.2 实战调参:让 Anchor-based 少翻车的几个习惯
如果你是第一次在自定义数据集上跑 Anchor-based 模型,我强烈建议按下面这三步走:
第一步,先统计数据集里所有 Ground Truth 框的宽高分布,画出散点图或直方图。这比直接抄 COCO 数据集的 Anchor 配置靠谱一百倍。很多开源代码默认给 9 个 Anchor,是基于 COCO 这种尺度分布很广的通用数据集做的聚类结果,放到工业零件或医学影像上往往会水土不服。
第二步,使用 K-Means 聚类重新生成 Anchor 数量,注意距离度量用 IoU 相关指标而不是欧氏距离。我之前踩过一个坑:用标准 K-Means 的欧氏距离聚 Anchor,小目标被聚到一个簇里,大的分散到其他簇,效果还不如默认配置。改成 IoU 距离(距离定义成 1 减去 IoU)后,Anchor 的尺度分布才和标签分布对齐。
第三步,验证正样本数量是否足够。训练刚开始的时候,可以写个简单脚本统计每张图的正样本 Anchor 数量,如果每张图正样本均值小于 5,大概率是 Anchor 配置或者匹配 IoU 阈值设置有问题。我之前在某个检测小目标的模型里,发现正样本平均只有 2.3 个,后来把匹配 IoU 阈值从 0.5 降到 0.4,正样本数量翻了一倍,小目标的召回率大幅提升。
4.3 训练 Anchor-free 时容易踩的三个坑
Anchor-free 不是免调参魔法,它有自己的专属坑。
第一个坑是热图峰值偏斜问题。用 CenterNet 时,如果目标尺寸很小(比如 20×20 像素),它的高斯核半径很小,正样本数量极少,模型很容易在没有目标的地方产生假阳性热图。我一般会加大对小目标的权重,或者给 heatmap loss 加一个针对小目标的 scale 加权系数。也试过把高斯核半径下限固定为 1,避免半径为零导致没有正样本。
第二个坑可称为"中心点偏移不够鲁棒"。Anchor-free 在推理时直接以峰值点或回归后的框输出,一旦中心点预测出现 1~2 个像素偏差,对于小目标的 IoU 就立刻掉到很难看的地步。解决思路包括:训练时对中心点做高斯抖动增广;预测阶段结合周边热图的局部峰值做亚像素插值(我在工程里用过一个三次插值求热图坐标偏移的方法,精度能涨两个点左右,效果跟作者实现的 center offset 回归差不多)。
第三个坑是标签重叠下的模糊匹配。两个 Ground Truth 框如果高度重叠,Anchor-free 的正样本区域也会重叠。这种情况下靠近重叠区域的像素点到底归哪个目标,不同的实现策略直接影响最终效果。我倾向于在分配时做优先级排序:先分配面积小的目标,再分配面积大的;如果某个位置同时落在多个框内,选距离该位置最近的 Ground Truth 负责回归。
4.4 推理后处理:我的 NMS 使用习惯和简化方向
Anchor-based 和 Anchor-free 在推理时基本都要做最终的 NMS(非极大值抑制),尽管过程不太一样。我总结了一个通用原则:好的模型应该让 NMS 的负担很低,而不是靠 NMS 来救命。
具体到实操:
- Anchor-based 检测器因为 Anchor 密集,会产生大量的冗余框。我一般用置信度阈值先过滤一轮(保留得分前 2000 个候选,低于 0.05 的直接丢弃),然后做 IoU 阈值为 0.5 的 NMS。如果目标之间本身挨得很近(比如密集场景),阈值要往上调,我习惯从 0.5 到 0.7 之间做网格搜索。
- FCOS 类 Anchor-free 数量比 Anchor-based 少很多,但质量均匀度不够。加了 center-ness 之后,低质量框的置信度会被压低,NMS 阶段压力小了不少。我试过 DIoU-NMS 来代替传统 NMS,在密集行人场景里召回率提升明显,但推理耗时增加了 15% 左右,需要按需取舍。
- CenterNet 不太一样,它在训练时已经通过热图局部峰值抑制了大部分冗余,推理阶段甚至不需要传统 NMS,只需要对热图上的局部峰值点做 3×3 max-pooling 来选峰。这也是它能做到极低延迟的关键原因之一。
5. 现在的新趋势:两者正在融合,边界越来越模糊
如果说再往前看一两年,这两条路线的边界已经不像以前那么清晰了。现在不少主流检测框架都在走融合路线,典型方向可以大致摸出三条。
第一条是"隐式 Anchor"。有些方法不再显式生成 Anchor,但仍然隐性地把"参考点"嵌入到预测头部里,让模型学出更灵活的参考分布,本质上是让 Anchor 变成可学习的参数而不是手工设定。
第二条是"动态样本分配"。像 SimOTA 这类方法已经把目标检测的"正样本分配"问题从一个手工规则变成了一个可学习的匹配过程。算法会给每个目标计算所有候选位置的代价矩阵(分类代价加回归代价),再用类似最优传输的思路分配样本。这种方式让 Anchor-based 和 Anchor-free 在训练层面的差异越来越小——因为它们都在解决同一个问题:怎么选出最合适的学习样本。
第三条是"端到端免 NMS"思路。随着 Transformer 在检测领域的应用,像 DETR 系列这类方法,通过集合损失和二分匹配直接输出最终框集合,完全打破了手工后处理的局限。虽然它们不再依赖 Anchor,但也没法简单归类到原来的 Anchor-free 谱系里,因为它们的正负样本定义完全不同。
从工程角度,我现在的态度是:不要再把 Anchor-based 和 Anchor-free 看作"谁取代谁"的问题,而是把任务分解成三个独立决策:
- 样本分配方式:固定 Anchor 匹配、中心点高斯匹配还是动态集合匹配?
- 监督信号组织:IoU 回归、距离回归还是关键点回归?
- 后处理策略:传统 NMS、Diou NMS 还是不 NMS?
这三者的组合空间很大。实际项目里你会发现,很可能你在主干里用 Anchor-free 的思想(比如预测中心点),但在样本分配里借鉴 Anchor 的 IoU 匹配策略,效果反而比纯粹走任何一个极端都好。我自己最近在两个项目里都是这种混合方案,收敛更快,调参反而更省心。
6. 收尾前再分享一点心得
最后聊聊我个人在实际项目里积累的几个不一定会写在论文里的体会。
第一件事:Anchor 数量的直觉判断。以前我总想着 Anchor 越多越密越好,但后来发现 Anchor 数量和性能不是单调递增关系。Anchor 太多,正负样本比例失衡程度更严重,模型需要更多时间去区分哪些是有效候选;Anchor 数量适中但匹配质量高,比如通过聚类让每个目标至少匹配 5 个合理的 Anchor,训练效果会好很多。
第二件事:训练初期多关注样本分配而不是损失曲线。很多人看到一个检测模型 loss 不下降,第一反应是调节学习率或者换优化器,但实际上问题往往出在样本分配上——可能大量 Anchor 或者热图响应点根本没有有效覆盖到目标。我现在的习惯是:刚开始训练的第一个 epoch,单独冻住特征提取层,先只训头部的分类和回归分支,把损失降到一个相对合理的区间,再解冻全部参数做联合训练。这个过程能有效排查样本匹配是否正确。
第三件事:多尺度测试救不了小目标,训练阶段的匹配策略才能救。以前做小目标检测,喜欢用多尺度测试(把原图放大 1.5 倍),精度确实涨,但部署时会面临大量额外的计算开销。后来我改成在训练阶段专注改进样本匹配策略,比如给 Anchor-free 加中心采样,给 Anchor-based 降低正样本匹配的 IoU 阈值,以及给回归分支换 DIoU Loss,收到的收益是端到端的,部署时不需要任何额外的 trick 就能稳定拿到更高精度。
从更长远的角度看,Anchor-based 和 Anchor-free 的争论本身可能没那么重要了,重要的是你理解每种设计在解决什么问题、付出了什么代价、在什么条件下有效。把这个底层逻辑想透了,不管框架怎么迭代、新论文怎么出,你都能快速判断哪个方案适合你的业务场景,而不是永远跟着开源代码走。