☰
RCNN到Fast-RCNN:目标检测演进与工程实现要点
2026/10/13 17:43:06 网站建设 项目流程

1. 对象识别不是“分类加个框”:四件事必须同时完成

如果你跟我一样,是从图像分类入门深度学习的,那第一次听到“对象识别”这个任务时,多半会有个朴素的想法:分类我会了,就是让卷积网络输出“这是猫还是狗”;再给它加一个边界框回归,让网络顺便输出“猫在图片的哪个位置”,这不就完事了?

这个想法方向没错,但真正做起来会发现一个很现实的问题:一张图片里到底有多少个对象、每个对象是什么、它们分别在哪儿,这三个问题在推理之前是完全未知的。分类任务输入是一张干净的、尺寸固定的图片,输出是一组类别概率;而对象识别输入还是一张图,输出却是一组长度不定的检测结果,每个结果包含类别、置信度、边界框四个坐标。输入输出结构都变了,光靠把分类网络改一改是解决不了的。

我当年第一次写对象识别程序时,最直观的感受就是:网络结构还是那个网络结构,但整个数据流和训练流程的复杂度高了一个量级。你需要同时处理好四个子问题:

  • 定位:对象在哪里?用边界框表示,通常是一个四元组 (x, y, w, h) 或 (x1, y1, x2, y2)。
  • 分类:框里的东西是什么?这是分类网络的老本行。
  • 多目标:图里可能同时有 3 个人、2 辆车、1 只狗,你的算法必须能输出多个框,而不是只给一个答案。
  • 重叠与冗余:同一个对象可能会被许多候选框覆盖,你得有一套去重机制,把最终输出收敛到每个对象只留一个框。

这套东西听起来复杂,但 2014 年 RCNN 的提出,把问题拆成了几个“用旧工具拼装起来的新方案”。这种拆法非常符合程序员的思维习惯:把一个大问题分解成若干可独立处理、可独立验证的小模块,每个模块用当时已经很成熟的技术搞定。想要真正理解后续的 Fast-RCNN、Faster-RCNN 是怎么演进的,必须先理解 RCNN 是怎么把一个“看似无解”的问题拆成三块的。

这篇就顺着 RCNN 的拆解思路讲起,再讲 Fast-RCNN 是怎么解决 RCNN 的性能瓶颈,最后聊一些训练和部署时容易踩的坑。适合有一定 CNN 基础、想从“会分类”跨到“会检测”的开发者。

2. RCNN 的破局思路:把检测拆成候选区域、CNN 特征、SVM 三件旧事

RCNN(Region-based CNN)的核心思想,用一句话说就是:先用传统的计算机视觉方法生成一堆可能包含对象的区域,然后让 CNN 逐个区域做特征提取,最后用分类器判断每个区域是什么。

这看起来不像一个“深度学习方案”,倒更像一个“传统视觉 + CNN 特征”的混合管线。但 2014 年它确实把对象识别的精度提升了一大截。我拆开讲。

2.1 第一步:选择性搜索生成候选区域

在 RCNN 之前,主流的窗口滑动法是在整张图片上以不同尺寸、不同长宽比密集滑动窗口,每一个窗口都进分类器。这样做的计算量极其惊人,即使步长设得很大,一张 500x500 的图也需要跑几万个窗口。

RCNN 换了个思路:先用一个叫“选择性搜索”(selective search)的传统算法,从图里挑出“最可能包含对象”的约 2000 个区域,而不是穷举所有位置。选择性搜索的想法是:先按像素颜色、纹理把图切成很多小块,然后按相似度(颜色相似、纹理相似、大小相近、空间上相邻)不断合并小块,每合并一次就生成一个候选区域。整个过程有点像“从碎片拼出物体”,因为真实物体内部颜色和纹理往往是一致的,所以这些合并出来的区域大概率就是对象所在的地方。

这一步虽然属于传统算法,但它的意义很大:把检测的空间从“全图所有位置”缩小到了“大约 2000 个值得看的位置”。我在理解 RCNN 时,是先把这一步当作一个可替换的黑盒模块来看的。它不参与 CNN 的训练,也不参与梯度传播,只是负责输出一组坐标。这种模块化解耦让调试变得很方便:候选区域质量不对,我可以单独可视化检查它有没有把对象覆盖住。

2.2 第二步:CNN 只负责当特征提取器

选出候选区域后,每个区域都被缩放成统一尺寸(比如 227x227),然后送入一个卷积网络提取特征。

这里的 CNN 并不是为检测专门训练的,它最初做的是图像分类任务,在大型通用视觉数据集上预训练完成。RCNN 的做法是:把 CNN 当作一个“万能特征提取器”,直接取它的全连接层输出(比如 4096 维向量)作为这个区域的语义特征。

这个设计在当时是有争议的。因为预训练模型的分类知识来自 ImageNet 这种纯分类数据集,直接拿来做检测特征,直觉上有点“专业不对口”。但后来实验证明,这些特征已经足够通用,用来描述“区域里有什么”完全可行。作为程序员,你可以把它理解成“迁移学习”:在一个大任务上学到的表示,拿到相关的小任务上依然好使。

不过 RCNN 做了两个针对性微调:

  • 把 CNN 最后一个 1000 类分类层换成 21 类(20 个对象类 + 1 个背景类);
  • 用候选区域数据对 CNN 做一次微调,微调时的正样本是“与真实框 IoU 大于 0.5 的候选区域”,负样本是“IoU 小于 0.3 的区域”。

这一步叫“pre-training + fine-tuning”的组合,目的是让 CNN 输出的特征更贴检测任务。但它还不是最终分类器,只是让特征变得“更适合被后续的分类器使用”。

2.3 第三步:SVM 分类 + 边界框回归

每个候选区域经过 CNN 得到 4096 维特征后,RCNN 并不是直接接 Softmax,而是为每个类别单独训练了一个 SVM 二分类器。比如“猫”类别,所有猫类的正样本区域标为正,所有非猫区域标为负,训练一个判别“这个区域是不是猫”的 SVM。

为什么不用 Softmax?RCNN 作者当时的实验发现,用 SVM 做最终分类比直接 Softmax 精度高。原因比较复杂,其中一个重要因素是 SVM 对负样本的使用方式不同:SVM 训练时会用一种“难负样本挖掘”策略,从所有错误分类的负样本中挑出最难的、最有区分度的那些继续训练,相当于把分类器“针对性磨利”。而 Softmax 是用微调 CNN 时的随机负样本一次性训练的。

这是 RCNN 在工程结构上非常重要的一个细节:CNN 微调和 SVM 训练用的正负样本定义不一样,是两套独立流程。我最初被这一点绕了很久,后面会对这个做对比说明。

最后,还要训练一个边界框回归器。因为选择性搜索给出的框往往不够准,可能框住了对象但留有大量背景,或者切掉了对象一部分。边界框回归做的事情是:输入 CNN 提取的特征,输出四个修正量 (dx, dy, dw, dh),分别表示中心点平移量和宽高校正量,把候选框修正到更贴近真实框。

我写一段伪代码来概括 RCNN 的完整评估流程,方便你理解它的执行顺序:

def rcnn_predict(image, svm_models, bbox_regressors, cnn): proposals = selective_search(image, max_regions=2000) boxes = [] for region in proposals: cropped = crop_resize(image, region, size=(227, 227)) feature = cnn(cropped) # 输出 4096 维向量 for cls_id in range(1, 21): score = svm_models[cls_id].predict(feature) if score > threshold: box = bbox_regressors[cls_id].predict(feature) boxes.append((cls_id, score, adjust_region(region, box))) return nms(boxes) # 非极大值抑制去重

2.4 训练 pipeline 里那三组容易混淆的概念

RCNN 的代码不难,真正难的是它组织训练数据的方式。我第一次上手时,花了不少时间才理清楚三个完全不同的“正负样本”定义:

环节正样本定义负样本定义作用
CNN 微调IoU > 0.5 的候选区域IoU < 0.3 的候选区域让特征适配备检测任务
SVM 训练真实框(ground truth),或者与真实框 IoU 最高的框IoU < 0.3 的区域 + 难负样本获得高精度最终分类边界
BBox 回归训练与真实框 IoU > 0.6 的区域不使用负样本,只对正样本拟合修正候选框位置

同一个“区域”,在三个环节里的“身份”可能完全不同。一个 IoU 为 0.4 的候选区域,在 CNN 微调里是正样本,到 SVM 里可能就变成负样本了(因为没到 0.5 的阈值),到回归器里完全不用。这就是 RCNN 让人“一看就懂、一写就懵”的地方。我建议所有初学 RCNN 的人,先在表格里把三套样本标定义清楚再动代码,不然训练出来精度莫名其妙地差。

3. 为什么 RCNN 单张图片要跑 47 秒:慢是慢在重复计算

RCNN 虽然效果好,但性能差到几乎无法用于实际产品。在当时的公开评测集上,它检测单张图片需要大约 47 秒。我算过一笔账,这笔账对理解后面的 Fast-RCNN 至关重要。

RCNN 的时间主要花在两部分:一是每张图生成约 2000 个候选区域,二是每个候选区域都要单独过一次 CNN。假设你的 CNN 单张推理耗时 20ms,那么 2000 个区域就是 2000 x 20ms = 40 秒。这还不算候选区域生成和 SVM 分类的时间。

问题的本质在于:这 2000 个候选区域互相之间严重重叠,但我们的做法是把它们当作一张张全新的图片来处理。你裁剪出来的区域 A 和区域 B 可能只差十几个像素,它们经过 CNN 时,前几层卷积计算的特征图几乎完全相同,但这些重复计算没有被利用起来。用程序员的说法就是:“没有做子图级别的共享计算优化”。

RCNN 论文里其实也提到,CNN 的卷积部分占据绝大多数计算量,而后面的全连接层只占很小一部分。如果一个候选区域在图上只偏移了 10 个像素,它产生的特征图里,大部分位置的值和偏移前是一样的。如果能设计一套机制,让整张图只算一次卷积,然后直接从卷积特征图里“抠出”各个候选区域对应的部分,就能省掉几乎所有重复计算。这就是 Fast-RCNN 的核心动机。

除了推理慢,RCNN 在训练上也很折磨人。因为每个区域的 CNN 特征都要单独提取,训练数据可能是几万个区域,提取出的特征文件动辄几十 GB。我当时做的第一版教程实验,特征是分批次提取然后存到磁盘上的,跑一次完整训练流程要一天多。而且 RCNN 的精度上限还受制于候选区域数量——你想多生成几个候选区域提升召回率,推理时间就线性往上飙,完全没办法在精度和速度之间做灵活取舍。

RCNN 的另一个工程槽点是训练流程太长。CNN 微调、SVM 训练、边界框回归器训练是三个独立阶段,每个阶段都要单独准备数据、单独调参。中间任何一步的样本定义搞错,后面整条线都废了。这类模型在当时被称为“多阶段检测器”,后来的 Fast-RCNN 之所以让大家兴奋,很大一部分原因就是它把这个多阶段流程变成了一次端到端的训练。

4. Fast-RCNN 的关键一跃:ROI Pooling 把重复卷积变成一次共享

Fast-RCNN 这篇论文的标题基本就说清楚了它快在哪:不再让每个候选区域单独过 CNN,而是整张图只过一遍卷积网络,拿到整图特征图后,再通过一个叫 ROI Pooling 的操作,从特征图里取出对应各个候选区域的特征。

我最初理解 ROI Pooling 时,觉得它像是编程里的“大数组切片”。整张图经过卷积之后,得到的是一个 C x H x W 的特征图,这个特征图把整张图的空间信息和语义信息都压缩在里面。此时,候选区域是从原图坐标系里给出的一个矩形框,比如 (x1, y1, x2, y2),我们需要做的,就是把这个坐标映射到特征图坐标系上,然后把那部分特征“抠”出来。

但“抠”出来之后还有一个问题:CNN 后面的全连接层要求输入尺寸固定,而各个候选区域的大小参差不齐。ROI Pooling 解决这个问题的办法非常简单粗暴:不管候选区域多大,一律在特征图上分成固定的块数(比如 7x7),对每一块做 max pooling。这样,无论原始候选区域是 50x50 还是 200x300,最后输出的都是 7x7xC 的定长特征,可以直接送进全连接层。

这个分块操作里有个值得注意的细节:候选区域的边界在映射到特征图时经常不是整数。比如原图坐标除以卷积步长后得到 10.7,怎么办?Fast-RCNN 当时的做法是直接量化取整(比如向下取整到 10),再把候选区域的高宽也按比例量化到整数。这意味着每个块的边界也不是完全精确的,可能会有最多一个像素的偏差。

作为入门者,你可以先不纠结这个量化误差。因为到了下一代的 Faster-RCNN 和后来的 Mask-RCNN,作者引入了 ROI Align 来解决这个问题,用双线性插值替代取整,精度进一步提高。但在理解 Fast-RCNN 时,只要记住它的量化取整实现非常简单、速度很快,但会有轻微定位精度损失,就够了。

Fast-RCNN 的推理流程变成这样:

def fast_rcnn_predict(image, net, rpn_proposals): feature_map = net.conv_layers(image) # 整图一次前向 results = [] for region in rpn_proposals: roi_feature = roi_pool(feature_map, region, output_size=(7, 7)) cls_scores, bbox_deltas = net.head(roi_feature) results.append((cls_scores, bbox_deltas, region)) return nms(results)

注意这里的候选区域生成和 RCNN 一样,仍然是用选择性搜索等外部算法。这个“外部候选区域”的依赖要到 Faster-RCNN 才解除。

在计算量上,Fast-RCNN 的四阶段变化非常直观:

阶段工作计算量占比(约)
整图卷积每张图 1 次大部分
候选区域生成选择性搜索,CPU 算法较少
ROI Pooling约 2000 次小规模池化极少
全连接 + 分类每区域过一次 head 网络较少

原来的瓶颈“每个区域单独过完整 CNN”被彻底移除了,整图只需要一次卷积。虽然 ROI Pooling 之后每个候选区域仍然要过一次全连接层,但全连接层计算量远小于卷积层,整体速度提升了约 20 倍。在当年的公开评测集上,Fast-RCNN 把单张图片检测时间从 RCNN 的 47 秒降到了约 2 秒,训练时间也从几十小时降到了不到一天。

5. 端到端训练:Softmax 替换 SVM 之后损失函数怎么设计

Fast-RCNN 不只是推理变快了,它还把训练流程从“多阶段独立”变成了“一步到位”,这是它更值得学习的地方。

RCNN 的思路是“CNN 只提取特征,最终分类交给外部 SVM”。但 Fast-RCNN 直接把最终分类器换成了 Softmax,并让整个网络一起训练。模型结构是:卷积层 -> ROI Pooling -> 两个并行的输出头(分类头 + 回归头)。损失函数由两部分的加权和组成:

  • 分类损失:对每个候选区域预测它属于哪一类(包括背景类),用 Softmax 交叉熵。
  • 回归损失:只对非背景的候选区域计算,度量预测的边界框修正量与真实修正量之间的差异,用的是 smooth L1 损失。

smooth L1 的特别之处在于:误差小的时候按平方函数增长,误差大的时候按线性函数增长。相比传统的 L2 损失,它对离群点没那么敏感。检测任务里候选区域的情况千差万别,偶尔会出现一个特别难修正的框,如果用 L2,这个框产生的巨大梯度会带偏整个训练;用 smooth L1 则可以限制它的影响。这是从工程经验里总结出来的损失设计,后来被几乎所有的检测模型沿用。

除了损失函数,Fast-RCNN 在训练时对 mini-batch 的组织方式和普通分类任务完全不同,这一点对写代码的人来讲是最大的坎。

在普通分类任务里,你随机从所有训练样本里抽 32 张图片,每张图片一个标签。但在 Fast-RCNN 里,样本的单位是“候选区域”,而不是“图片”。如果直接从所有图片的所有候选区域里随机抽 128 个区域来训练梯度,那么这 128 个区域很可能来自 128 张不同的图片。每张图片的卷积层都要做一次完整的前向传播,计算量极大。

Fast-RCNN 的做法是:随机抽 2 张图片,然后从每张图片的候选区域里各抽 64 个区域,组成一个 batch=128 的训练单元。这样一次迭代只需要对 2 张图做卷积,但梯度更新却基于 128 个候选区域的信息。在 PyTorch 里,你只需要保证这两个图片的 ROI 特征可以拼成一个 batch 送进全连接层,而卷积层是逐图前向的,最后把梯度回传时按图累计即可。

这里有一点很容易被新手忽略:在接受 ROI Pooling 后的全连接层时,来自不同图片、不同尺寸区域的 ROI 特征,其 batch 维度可以拼在一起,因为每个 ROI 的输出尺寸都是固定的 7x7xC。所以你可以把所有候选区域(不管来自哪张图)作为一个大 batch 输入全连接头,训练代码实现起来非常简洁。

Fast-RCNN 的训练还有个重要细节:正负样本的比例要控制。每张图的 64 个候选区域中,论文里建议最多 25% 是正样本(与真实框 IoU 大于 0.5),其余都是负样本。为什么要限制正样本比例?因为一张给定的图片里,真正包含对象的区域很少,而背景区域极多。如果按自然比例采样,负样本会淹没正样本,模型会直接学会“所有区域都是背景”,而且收敛极慢。把正样本比例强行拉高,是为了让模型在训练时每步都能看到足够的实事例。我在写第一个检测模型的 dataloader 时,最大的困惑就在这个采样逻辑——分类任务的 dataloader 是“按图片采”,检测任务的 dataloader 是“按图片采,再按区域采”,两者的设计思路差了一整个维度。

6. 两个时代的真实差距:RCNN 与 Fast-RCNN 的选择依据

我见过很多人问:既然 Fast-RCNN 更快,为什么还要学 RCNN?直接上 Fast-RCNN 不就行了?

这个问题的答案很简单:Fast-RCNN 是在 RCNN 的思想框架上做的优化,不理解前者,你很难理解后者为什么要做那些看似反常的设计。比如你不理解“CNN 特征提取可以共享”,就理解不了 ROI Pooling 的意义;你不理解“三阶段样本定义不一致”的问题,就理解不了“端到端多任务训练”真正解决了什么。

但作为实践者,你大概率不会真的去训练一个 RCNN,而是直接使用 Fast-RCNN 或 Faster-RCNN。这时候,对比一下两个模型各自的特点,能帮你在自己的项目里做选择:

对比维度RCNNFast-RCNN
候选区域生成选择性搜索约 2000 个选择性搜索约 2000 个
CNN 计算方式每个区域独立过网络整图过网络 + ROI Pooling
最终分类器每个类别一个 SVM单个 Softmax 头
训练方式三阶段独立训练端到端联合训练
边界框回归独立训练回归器与分类共享特征的回归头
推理耗时(当时评测集)约 47 秒/张约 2 秒/张
实现复杂度中等,但流程繁琐中高,需要处理 ROI 采样
精度(当时评测集 mAP)约 58~66约 66~70

对于自己的项目,我一般这样判断:

如果只是一个快速验证想法的小实验,比如检查“用预训练特征 + 线性分类器做检测的效果基线”,RCNN 思路反而是最快出结果的——你甚至不需要修改 CNN 结构,直接提取特征存下来就能调 SVM。很多传统视觉工程师转型初期喜欢先用 RCNN 思路做个基线,再换 Fast-RCNN 提升性能。

如果目标是做一个真正可用的检测服务,那至少从 Fast-RCNN 起步,因为它已经是统一训练的模型,后续想换骨干网络、加数据增强、调候选区域数量,都更加顺滑。更进阶的 Faster-RCNN 只是把“选择性搜索”换成了一个可训练的区域提议网络,主干和 Fast-RCNN 几乎一样。从这个角度看,Fast-RCNN 是整个系列里最核心的“分水岭模型”:它比 RCNN 快得多,又不具备 Faster-RCNN 那种“候选区域 + 检测联合训练”的复杂性,最适合用来理解检测模型的整体骨架。

7. 程序员训练这套模型时容易踩的五个坑

最后这部分,我集中写一些亲身踩过的坑。这些坑在论文里不会写,在教程里容易被篇幅省略,但每一个都足以让你的实验多耗两天。

坑一:候选区域生成方式决定了你的性能上限。

如果你用选择性搜索生成候选区域,一定检查它的召回率:所有真实框里,至少有 90% 以上被候选区域覆盖(IoU > 0.5),后面模型的精度才有保障。候选区域的 IoU 阈值要和训练保持一致。如果候选区域本身就没框住对象,后面 CNN 学得再好也白搭。我做过一个实验,把候选区域数量从 2000 减到 500,mAP 掉了 8 个点——不是模型变笨了,而是漏检了。在代码里把候选区域可视化叠加在原图上检查一次,比看指标曲线更有用。

坑二:调整候选区域数量时,重新训练比改参数更重要。

有些教程会把“候选区域数量”当作一个可以随便调的推理超参。其实不能这么理解。候选区域数量的变化会改变训练时正负样本的分布。比如你从 2000 个候选区域变成 100 个,模型见过的负样本复杂度完全不同,原来训练好的分类边界就不再适用。所以,凡是改了候选区域生成逻辑,无论是数量、算法还是阈值,都要重新训练模型,而不是只改推理代码。

坑三:ROI 坐标对齐不能被忽略。

Fast-RCNN 的 ROI Pooling 里有量化和取整操作,如果原图的坐标映射有问题,边界框会偏移。常见错误是:候选区域坐标是在缩放后的图片上生成的,但你在 ROI Pooling 时用了原图坐标的尺度。一定要记住,所有候选区域坐标都必须在同一坐标系下——要么都在原图坐标系、要么都在网络输入分辨率坐标系,统一后再换算到特征图尺度。坐标单位/尺度不一致导致的检测框偏移,是排查起来最隐蔽的 bug。

坑四:正负样本采样比例要显式控制。

前文说了 Fast-RCNN 一个 batch 里正样本只占 25% 左右。但实际实现时,还要注意一个细节:每张图片的候选区域里,正样本数量不足 25% 的情况很常见。比如图中只有 1 个对象,只有 2 个候选区域与它 IoU 超过 0.5,这时候你就不能严格凑够“16 个正样本”。处理办法是:有多少用多少,剩余的位置直接用负样本补齐。也就是说,构造 batch 时要支持“正样本不足时自动降级”的逻辑,否则程序会一直报错或者训练不收敛。

坑五:pretrain 模型和任务数据差异很大时,要调整微调策略。

Fast-RCNN 系列几乎都依赖预训练 CNN 做初始化。如果你的检测任务和预训练任务差异很大(比如预训练是通用物体分类,你的任务是识别工业零件),直接对整个网络做微调,前期 loss 会下降得极慢。一种稳妥的套路是:先冻结卷积层,只训练 ROI Pooling 之后的全连接头,跑几个 epoch 之后,再解冻卷积层用小学习率整体微调。这么做相当于先让检测头学会“利用已有特征”,再让卷积层“配合检测头”。我跑工业零件检测时,这个策略把训练时间省了将近一半,最终的精度也比从零开始训高不少。

RCNN 和 Fast-RCNN 在今天看来已经是“老古董”了,但它们的很多设计细节在现在的 YOLO、Faster-RCNN 变体里依然能看到影子:共享卷积特征、多任务损失、正负样本采样、非极大值抑制、边界框回归。我个人的体会是,检测模型虽然有各种新版本,但只要你把这一篇里这些核心概念的来龙去脉想清楚了,后面看任何检测论文都会轻松很多。至少我在真正调通第一个 Fast-RCNN 模型之后再去看 Faster-RCNN,明显感觉不再是“看天书”,而是在看同一个故事的不同章节。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询