聊目标检测,绕不开这张经典的“升级地图”:R-CNN、SPP Net、Fast R-CNN、Faster R-CNN、FPN。我最早接触这套演进路线时,看的不是论文,而是同事丢给我的一串训练日志和可视化输出。当时我对两阶段检测器的理解还很模糊,只记得一个直觉:先把图上可疑的东西圈出来,再挨个认真审。后来花了大把时间复现、调参、拆代码,才真正明白为什么这条路线能成为后面十几年检测器设计的“地基”。
这篇文章不讲教科书式的概念罗列,而是围绕“每代模型到底解决了什么真问题”来拆。适合正在入门目标检测、想梳理检测器前因后果,或者准备在项目里对比不同检测方案的人。我会把每代算法的设计思路、关键改动原理、训练与推断时的隐含假设,以及我踩过的坑都串起来,尽量讲得能直接用在你的实际项目里。
1. 两阶段检测的核心设计哲学:先粗后精的流水线分工
1.1 从“一步到位”到“打草稿再精修”
两阶段检测的英文本意是Two-stage,翻译过来就是“两段式”。它跟单阶段(One-stage)的差别不在精度公式里,而在解决问题的顺序:单阶段想一次性从图像直接回归出所有物体的类别和边框;两阶段则刻意把任务拆成“找候选”和“精修候选”两步。
我习惯用一个生活类比理解这件事:你在一堆照片里找朋友,单阶段的做法是让眼睛扫一遍,凭感觉同时说出“哪张照片有人、人在哪个位置”;两阶段的做法是先快速翻一遍,用直觉圈出几个好像有人的区域,再靠近了逐个确认“是不是人、是他本人还是背影”。前者快但容易漏,后者慢一点但准。检测任务里,精度往往比速度更要紧,所以两阶段在很长一段时间里都是精度榜上的常客。
这套流水线分工还带来一个实际好处:每一步的模型可以单独迭代。候选生成只管召回,分类和回归只管判别,两者耦合度低。工程调试时,你能通过查看候选框输出快速定位问题到底出在“没圈中”还是“圈中了但没认出来”。
1.2 上游与下游的协作形态
两阶段的上游负责“提出候选区域”(Region Proposal),下游负责“区域分类+位置精修”。从R-CNN到Faster R-CNN,变的不是这个分工本身,而是“上游怎么做、下游怎么接”。
早期上游用传统图像分割和聚类方法做选择搜索,完全不带学习参数;到了Faster R-CNN,上游被一个叫RPN的小网络取代,候选区域从“手工规则”变成“可训练参数”。下游也从最初的支持向量机分类器,逐步整合成单一的多任务神经网络。
这个“先粗后精”的架构至今仍是很多检测任务的首选方案,是因为它天然适合处理两类问题:一是目标小、目标密的情况,候选生成阶段可以先保证召回,二是类别容易混淆的情况,下游可以用更精细的特征做判别。后面的FPN把“多尺度候选”这件事也顺势交给了特征金字塔,等于在流水线分工之上又叠了一个“粒度分工”,这层递进关系会在第四章详细展开。
2. R-CNN的诞生与三个“笨重”之处
2.1 把深度特征塞进传统检测流程
R-CNN是这条路线真正意义上的开山之作。它做的事情概括成一句话:用深度卷积网络给“候选区域”提取特征,再用传统分类器对这些特征做类别判定。
具体流程分四步。第一步,用选择性搜索在每张图上生成约两千个候选区域;第二步,把每个候选区域裁出来并缩放到固定尺寸,比如227×227;第三步,把缩放后的图片送进预训练的卷积网络,得到4096维的特征向量;第四步,特征向量分别送入多个一类分类器做判定,再用一个边框回归器修正候选框位置。
我在第一次接触这套流程时,最震撼的不是它的精度,而是它对“特征”的重新定义。传统方法设计的都是手写特征,要反复调颜色、梯度、纹理各占多少权重;R-CNN直接把“特征工程”换成“特征学习”,训练好的卷积特征在很多类上都能直接用。它在当时的公开检测基准数据集上把平均精度从三十多个点直接拉到五十个点以上,这个跳升放在今天看依然夸张。
2.2 重复计算、强制变形、训练碎片化
但R-CNN正确,却远谈不上优雅。它有三个明显的“笨重”之处。
第一个是重复计算。一张图两千个候选区域,每个都要完整过一遍卷积网络,候选区域之间还有大量重叠,等于同一个物体被反复提取了多次特征。我当时做过一个不严谨的估算:如果一张图上有五十个物体区域,实际参与重复计算的面积可能是它们的数倍,算力几乎都耗在重复特征提取上。
第二个是强制变形。原始候选区域宽高比例各异,却被强行缩放到固定方形输入给网络。拉伸会改变目标比例,小物体被放大后会模糊,这种信息损失对检测精度是有实打实影响的。
第三个是训练流程碎片化。特征提取器、分类器、回归器不是一起训练出来的,而是分阶段独立训练再组合。这意味着每个环节的误差会累积,你很难说清整体效果究竟被哪一环拖了后腿。
如果你现在翻R-CNN的代码实现,会发现它的结构其实很简单,但简单不等于高效。正因为它把这些缺陷暴露得很彻底,后续的SPP Net和Fast R-CNN才有了明确的优化方向。
3. 从SPP Net到Fast R-CNN:消除重复计算与统一训练
3.1 SPP Net的关键洞察:特征图上的区域映射
SPP Net全称是Spatial Pyramid Pooling Network,核心贡献有两个:一是只对整图做一次卷积,候选区域的特征从共享的整图特征图上抠;二是用空间金字塔池化把任意尺寸的区域特征变成固定长度向量。
先说第一个。一张图通过卷积网络后得到的是C×H×W的特征图,原图上一个候选框可以通过坐标映射关系,对应到特征图上的一个矩形区域。这样就不再需要把每个候选区域单独送进网络,而是直接在这张共享特征图上“截取”对应区域的特征。这一步把重复卷积几乎彻底消除了。
再说第二个。因为候选区域映射到特征图上后大小仍不一致,SPP Net在区域内部做多层级网格池化。举例来说,如果做三层金字塔,网格数分别是1×1、2×2、4×4,那么每个区域会被划分成1+4+16=21个格子,每个格子做一次最大池化。假设特征图有256个通道,最终得到的特征长度就是256×21=5376,是一个完全固定的维度,与区域原始尺寸无关。
我当时理解空间金字塔池化时,想明白了一个点:它本质上是在用“多尺度统计”代替“强制缩放”。你可以把它看成对不同粒度的小块各取代表值,粒度粗的留住整体布局,粒度细的留住局部细节。相比直接拉伸图片,这种池化方式保留的空间信息要丰富得多。
3.2 Fast R-CNN的整合手术:RoI Pooling与多任务训练
Fast R-CNN在SPP Net的基础上往前走了一大步,把整个训练流程统一成一个端到端的网络。它保留了“整图卷积+区域映射”的思路,只是把金字塔池化简化成单尺度的RoI Pooling,同时把分类器和边框回归器重构成两个并行的全连接分支,挂在同一个特征输出后面。
RoI Pooling可以理解为金字塔池化只取一层的情况:把任意大小的候选区域特征图划分为固定数量的网格,比如7×7,每个网格内做最大池化。无论是20×30的区域还是100×80的区域,输出都是7×7×通道数,这样后面接全连接层就没有尺寸障碍。
真正让Fast R-CNN“快”的另一个关键,是把原来碎片化的三阶段训练合并成一个多任务损失。分类分支用交叉熵损失,回归分支用平滑的L1损失,两个分支一起反向传播更新共享的卷积层。这样一来,骨干网络可以同时为分类和定位学到更合适的特征,而不是像R-CNN那样先冻结特征再单独训练分类器。
我在理解多任务损失时,最深的体会是:梯度像一条河,多个任务等于多条支流同时汇入主干,共享的骨干网络因此能看到更全面的需求。单独做分类的特征可能对边界不敏感,单独做回归的特征可能对类别区分度不够,两者联合训练正好互补。
3.3 一个常被忽略的细节:RoI Pooling的两次量化误差
Fast R-CNN虽然优雅,但RoI Pooling内部藏着两个小问题。候选区域坐标通过除法映射到特征图时,通常会有小数;RoI Pooling的网格划分也可能落在非整数坐标上。代码实现里为了简单,通常会直接取整,这就产生了两次量化误差。
对小目标而言,特征图上一个像素的偏差,映射回原图可能对应好几个真实像素,所以当时的检测器在小目标上的表现普遍不好。后来出现的RoI Align用双线性插值取代取整操作,才把这个问题真正解决。
这条线索能帮你理解技术演进里的“隐性收益”:有些改进看起来只是工程细节,却直接影响关键指标。如果你在复现Fast R-CNN时发现小目标召回率上不去,优先检查RoI采样这块是否用了平滑的坐标变换,而不是急着换更大的骨干网络。
4. Faster R-CNN与FPN:可学习的区域提议与多尺度特征
4.1 RPN的引入:让候选区域也从数据中学习
Faster R-CNN把“候选区域生成”这一步也变成了一个可训练的网络,即区域提议网络(RPN)。它做的事情不复杂:在共享特征图上铺一堆锚点框,对每个锚点判断“这里有没有物体”,并给出一个粗略的回归偏移。
具体来说,RPN在特征图的每个位置上预设多个不同尺寸和比例的锚点框。比如某实现里每个位置设三种面积和三种长宽比,组合出九个锚点。每个锚点对应一个小区域,网络对这个区域输出“是前景还是背景”的二分类概率,同时输出四个回归值,用于微调锚点框的位置和大小。
训练RPN时,正样本是与某个真实框的IoU超过某阈值(通常0.7)的锚点,负样本则是低于另一阈值(通常0.3)的锚点。中间犹豫地带通常直接忽略。这个设计思想一直沿用到现在,后来的anchor系检测器基本都沿用了这种匹配规则。
RPN的引入,使得“在哪里找目标”这件事也变成了参数的一部分。候选区域不再依赖传统图像算法的手工规则,而是跟着数据学习“哪里更像物体”。这也是“Faster”这个名字的由来——不仅推断更快,训练流程也更统一,整条流水线真正做到了端到端。
4.2 FPN:自顶向下与侧向连接的尺度补全
FPN全称是Feature Pyramid Network,解决的是一个更隐蔽的问题:多尺度。卷积网络天然存在一个矛盾,浅层特征分辨率高、位置信息丰富,但语义信息弱;深层特征分辨率低、语义信息强,但位置信息粗糙。如果把检测只放在最后一层特征上,小目标基本就丢了。
FPN的解法概括成八个字:自顶向下,侧向连接。自顶向下指的是从最深、最粗糙的特征图开始,逐层上采样,把语义信息往浅层传递;侧向连接指的是每一层上采样结果和同尺寸的浅层特征相加融合。经过融合,每一层特征都同时拥有较好的语义信息和位置信息,然后不同尺度的目标被分配到不同层去做检测。
我理解FPN时用过另一个类比:一个团队里老员工懂全局,新员工了解细节,FPN做的事情就是让老员工把宏观判断讲给新员工听,新员工再把具体细节反馈回去,最后每个人手里都有一份“既懂全局又不丢细节”的说明书。
FPN跟两阶段检测天然契合。在Faster R-CNN的框架里,RPN原本是在单层特征图上滑动的,FPN引入后,RPN也在每一层金字塔特征上独立运行;下游的RoI Pooling则根据候选框尺寸自动选择合适的金字塔层级。大框用深层低分辨率特征,小框用浅层高分辨率特征,整个流程的分工更清晰了。
4.3 从Faster R-CNN到FPN的组合演进
如果把Faster R-CNN和FPN看作独立模块,它们组合起来就是后来大量检测器的基础形态。Faster R-CNN负责“怎么找候选、怎么精修”,FPN负责“在不同尺度上都找得到、认得准”。
这种模块化组合的能力,在工程上特别重要。我在实际项目里换过很多次骨干网络,从较轻量的网络换到更深的网络,整体流程基本不用大改,只需要让FPN的侧向连接适配新的骨干输出尺寸。这正是两阶段检测器“结构稳定”的优势:它的模块边界划得清楚,替换和升级可以局部进行。
到了这一步,两阶段检测的经典要素已经齐了:区域提议、特征复用、多任务损失、多尺度金字塔。后续的各种检测器,无论标榜自己是anchor-based还是anchor-free,基本都在和这套框架对话,要么继承要么反叛。
5. 这条技术路线给现代检测留下的“遗产”
5.1 锚点机制的分叉与延续
Faster R-CNN把锚点框机制带火之后,很长一段时间里锚点框成了检测器的标配。后来的单阶段检测器如YOLOv2和SSD都直接或间接沿用了锚点框设计,只是把候选生成和分类合并到一次前向传播里。
再到后来,anchor-free思路开始兴起,主张直接预测关键点或中心点,省掉预设锚点的麻烦。但如果你仔细看 anchor-free 方法里的正负样本分配策略,会发现它们解决的仍是当年RPN解决的那个问题:怎么从海量位置里挑出真正该学习的位置。这套“为每个位置分配学习目标”的思想,源头就在Faster R-CNN。
所以我说,技术路线会分叉,但核心问题不会消失。不管是锚点框还是中心点,本质都是在回答“哪些位置、哪些尺度、哪些比例需要额外关注”,只是回答方式变了。
5.2 多任务损失的范式模板
Faster R-CNN的多任务损失设计,也为后来的检测器提供了一套通用模板。分类用交叉熵,回归用平滑L1,两项按权重相加。这个模式在无数检测实现里反复出现,甚至很多实例分割模型也照搬了这套多任务损失结构,只是额外加了掩码分支。
我在看一些写得很工整的检测仓库时,经常发现它们把损失函数拆成ins字典,里面分类损失、回归损失各占一项,代码结构跟当初的Faster R-CNN实现有明显血缘关系。理解了这个模板,你去看任何现代检测工具的配置项,都不会觉得陌生。
5.3 单阶段检测器对两阶段的“反向吸收”
单阶段检测器在精度上最初不如两阶段,后来靠一整套改进逐步拉近距离,而其中很多改进恰恰来自两阶段路线。最典型的就是特征金字塔:YOLOv3引入FPN思想之后,小目标检测能力明显提升;后续的更轻量检测器也大量采用“多尺度预测”的架构。
另一个被吸收的设计是匹配策略的精细化。单阶段检测器里每个位置的样本分配,从“只看IoU是否超阈值”慢慢演变成更复杂的动态匹配,本质上都是在向RPN当年那套“分层次分配正负样本”的思路靠拢。
总结起来,这条路线留下的不是一套固定的算法组合,而是一组“思维方式”:先粗后精的流水线、多尺度注意的敏感性、按位置分配学习目标。这些思维方式比任何具体模型都活得久。
6. 实操复盘:复现与调试中的常见坑
6.1 候选区域可视化:先确认上游问题再聊下游
我在复现这类模型时,第一条经验就是先把候选区域可视化出来,再谈精度。很多新手一上来就盯训练损失,但损失曲线在前期波动很大,不容易看出问题。候选框可视化则直观得多:如果RPN输出的候选框大部分落在背景上,或者多个候选框堆在同一位置,说明正负样本分配或锚点参数有问题。
可视化时建议把得分最高的前几十个候选框画在一张图上,观察它们是否覆盖到了你要检测的目标。如果覆盖却分类错误,问题在下游;如果压根没覆盖,优先去查锚点比例是否适配你的目标形状。
6.2 小心锚点参数与RoI尺寸的匹配
锚点参数的选择直接影响整个检测器的表现。我遇到过好几次“模型训练完但小目标一个都检不出来”的情况,最终定位到原因是锚点面积范围设得太大,最小面积都比目标真实尺寸大好几倍,小目标的匹配IoU永远达不到正样本阈值。
另一个容易踩的坑是RoI尺寸设置。RoI Pooling默认输出7×7,但如果你目标很小、特征图下采样倍数又大,映射回去的区域可能只剩几个像素,信息量严重不足。这种情况下把骨干网络的下采样倍数调低,或者把RoI输出尺寸调大,通常能缓解问题。
6.3 训练阶段的选择:分阶段训练并不丢人
Faster R-CNN原始的论文里提到过一种分阶段训练方式:先单独训练RPN,再固定RPN训练下游检测器,然后交替迭代。很多人以为端到端联合训练一定更好,但我在实际复现中发现,分阶段训练在数据量不大时反而更稳,因为联合训练时RPN和下游网络的梯度互相干扰,需要很小心地配学习率和损失权重。
如果你用的是现代检测框架,通常默认就是端到端训练,配置里也可能直接给你一个pretrained模型地址,那没问题。但如果你从零开始训一个自定义数据集,我建议先试分阶段训练,让上游先稳定下来,再放开联合训练微调。
6.4 快速上手现代两阶段模型的代码阅读路径
当你准备进入代码层面时,不要被一个庞大的检测仓库吓到。按这条路线去拆,思路会非常清楚:先找主干网络里的特征输出,再找RPN的锚点生成与采样逻辑,然后找RoI Pooling或RoI Align的取特征过程,最后找两个输出头的损失计算。整条链路就是“图进、特征出、框提、区抠、头判”。
我通常建议读代码时写一张小表,把每个模块的输入输出张量尺寸记下来。这张表一旦写清楚,整个模型在你眼里就不是“一堆模糊的层”,而是一个数据形状不断变化的过程。两阶段检测的底层逻辑其实是数据流逻辑,尺寸变了,功能就变了。
最后再分享一点我的个人体会
这条技术路线的价值,不在于它“曾经是最优解”,而在于它是理解检测器设计问题的一把钥匙。我每次回看这套演进,都会提醒自己一个规律:每个模型都只解决一个真问题,R-CNN解决深度特征引入,SPP解决重复计算,Fast R-CNN解决训练碎片化,Faster R-CNN解决候选生成可学习,FPN解决多尺度融合。把“一个版本对应一个痛点”这条线索理清,任何时候回头看源码,你都能快速定位设计者在想什么。
如果你正在学检测,我建议你亲手画一张对比表:五个模型的输入、候选生成方式、特征提取方式、分类回归方案、训练方式,各有哪一列在变。这张表画完,你就已经比多数停留在“会用API”的人理解得更深了。技术迭代还会继续,但这套“先粗后精、多尺度、按位置学习”的思维框架,很长一段时间内都不会过时。