第一次接触物体检测的时候,我脑子里的第一个疑问是:这东西和图像分类到底有什么不一样?分类就是把一张图打上一个标签,比如"猫"或者"狗",简单直接。但检测要回答的问题复杂得多——图里有哪些物体?它们各自在什么位置?两个物体挨得很近怎么办?物体被遮挡了怎么办?这些问题让检测变成了一个既需要"看见"又需要"定位"的任务。而R-CNN,恰好是深度学习全面接管目标检测之前,最关键的"引信"之一。
如果你是刚入门计算机视觉的新手,或者已经把分类玩得很熟、想往检测方向迈一步,那么从R-CNN读起是一段绕不开的路。它把候选区域、卷积神经网络、分类器和回归器拼装在一起,虽然今天看来笨重又缓慢,但几乎所有现代检测器都能从它身上找到影子。这篇博文会带你从头到尾把R-CNN的来龙去脉拆一遍,结合我当年复现它时踩过的坑,尽量把每个环节讲透。
1. 从图像分类到物体检测:概念与评价指标
1.1 分类、定位、检测、分割到底差在哪
图像分类是最基础的任务:输入一张图片,模型输出一个类别标签。如果图片里同时有猫和狗,分类模型只会告诉你"这张图里有猫"或者"这张图里有狗",甚至可能只给出概率最高的那个。这对很多真实场景是不够的。
物体检测要做两件事:一是找出图里有哪几类目标,二是用矩形框把每个目标的位置框出来。比如一张街景图,检测结果可能是"人1在左上角,汽车2在右侧,红绿灯在中间偏上"。这里的矩形框通常用四个数字表示:中心点坐标加宽高,或者左上角坐标加右下角坐标。
比检测更进一步的是实例分割,它不仅要框出物体,还要把每个物体的像素轮廓都抠出来。语义分割则是按像素对图像做分类,但不区分同一类的不同个体。这里面的层级关系很有意思:分类给的是"是什么",检测额外给了"在哪里",分割则把"在哪里"精细到像素级别。R-CNN解决的是检测任务,它的输出就是一系列带类别标签的矩形框。
1.2 mAP怎么算:检测任务最重要的评价指标
做分类时,我们经常看准确率(Accuracy)和Top-1/Top-5错误率。但检测的输出是一堆框,每个框有类别、有置信度、有位置,到底怎么衡量模型好不好?行业里最通用的指标是mAP,也就是mean Average Precision,平均精度均值。
mAP的计算牵扯到两个概念:交并比(IoU,Intersection over Union)和召回率/精确率。IoU就是预测框和真实框的交集面积除以并集面积,取值从0到1。通常设定一个阈值,比如IoU大于0.5,就认为这个预测框"命中"了某个真实框,算作正样本。
有了"命中"判定之后,把所有预测框按置信度从高到低排序,依次计算精确率(被判定为正的样本里真正命中的比例)和召回率(真实目标里被成功找出的比例)。每到一个置信度阈值,就能算出一组精确率和召回率,连起来画一条P-R曲线,曲线下的面积就是Average Precision(AP)。对每个类别分别计算AP,再取平均值,就是mAP。
我在第一次复现R-CNN时,花在理解mAP上的时间几乎和写代码一样多。这里有几个容易混淆的地方:mAP不是跑一次测试就得到的一个固定数,它的计算方式非常依赖IoU阈值和置信度阈值。不同数据集的mAP口径可能完全不同,比如传统Pascal VOC默认IoU=0.5,而新数据集会计算多个IoU阈值下的平均mAP(比如从0.5到0.95每隔0.05取一次)。所以看论文里报的mAP时,一定要先弄清楚它的计算协议,否则很容易被数字误导。
2. R-CNN的技术底色:为什么2014年它能引爆这个领域
2.1 深度学习之前的目标检测长什么样
在R-CNN出现之前,目标检测的主流方法基本都建立在手工特征之上。经典路线是先通过滑动窗口或者候选区域生成一些可能包含物体的位置,然后在每个位置提取HOG(方向梯度直方图)或者SIFT(尺度不变特征变换)这样的手工特征,最后交给支持向量机分类器判断这块区域是什么类别。
这类方法有几个很致命的毛病。首先是特征表达能力有限,HOG擅长描述边缘和梯度,但遇到遮挡、光照变化、外观多样性强的目标就很难招架。其次是滑动窗口采样效率极低,为了找到物体,需要在图像金字塔上以不同尺度、不同长宽比滑动窗口,大部分窗口都是背景,计算浪费非常严重。那时候Pascal VOC数据集上最好的检测器mAP也只有百分之三四十,和今天动辄八十多甚至九十的成绩差距巨大。
当时深度学习在图像分类上已经展现了极强的特征学习能力,但没人直接把它搬来做检测,主要有两个顾虑:一是深度模型需要大量标注数据,检测数据集的标注成本远高于分类;二是检测涉及大量候选位置,如果直接跑深度网络,计算量难以承受。R-CNN的作者做了一个非常聪明的组合:既然深度学习特征这么强,那我干脆先用传统方法筛选出可能包含物体的候选区域,然后只用深度网络对这些候选区域做分类。这样既避开了滑动窗口的暴力搜索,又让深度特征用在了刀刃上。
2.2 候选区域:选择性搜索如何降低复杂度
说到候选区域,不能不提选择性搜索(Selective Search)。它的想法很直观:一张自然图像里的物体,往往是一些颜色、纹理、形状上相对一致的区域。选择性搜索先从像素级的超像素分割开始,然后根据颜色相似度、纹理相似度、尺寸相似度和填充相似度,把相邻的小区域不断合并,最终生成一堆可能包含物体的候选框。
用一个生活化比喻:选择性搜索像是先用眼睛快速扫一圈,把图里"看起来像是一整块东西"的区域都圈出来。它不关心具体是什么物体,只负责告诉你"这些地方可能有物体"。相比滑动窗口,它的候选框数量从几十万级降到一两千级,而且覆盖率高,不容易漏掉目标。
R-CNN用选择性搜索大概生成2000个候选框。这个数量听起来还是很大,但已经比滑窗少了好几个数量级。而且选择性搜索还有一个特点:它生成的候选框不受固定比例限制,能适应物体的多种形状。虽然今天看来,这个阶段依然慢了,但它是整条流水线中把"猜物体位置"和"认物体类别"解耦的关键一步。
我自己后来实现选择性搜索时发现,它的超参数对结果影响很大。比如合并初期用较小区域有利于保持边缘,但物体如果被分割得太碎会合并失败;如果合并太粗,一个框可能同时包含多个物体。实际使用中往往要根据数据集的尺度分布适调配参数,这部分经验在R-CNN之后也沿用到了很多传统检测方法中。
3. R-CNN三大模块逐一拆解:候选区域、CNN特征提取、支持向量机分类
3.1 模块一:选择性搜索提取约2000个候选区域
R-CNN正式流程的第一步,是对输入图片运行选择性搜索,生成约2000个候选区域。之所以选择2000这个数量,是当时实验得出的平衡点:太少会漏检,太多会拖慢后续特征提取。每个候选区域都要被后续步骤单独处理,所以这一步的候选质量直接决定了整个检测器的上限。
这里有个细节需要注意:选择性搜索是在原始分辨率图像上运行的,而不是先在缩放后的图上运行。因为物体在低分辨率下可能会丢失太多细节,导致候选框定位不准。我的经验是,图像尺寸越大,生成的候选框越多,但随之而来的是计算量增大。R-CNN默认的输入尺寸并不固定,因为它的候选框大小不一,需要后续裁剪缩放到统一尺寸。
3.2 模块二:CNN特征提取与微调策略
拿到候选区域之后,下一步就是把每个候选区域缩放到固定尺寸,送入一个在大规模图像分类数据集上预训练过的卷积神经网络,提取出一个固定长度的特征向量。这里有个工程上的细节:候选区域的长宽比例五花八门,直接拉伸变形会影响特征质量。R-CNN作者做了个实验,最终采用了"各向异性缩放",也就是把候选区域直接强制缩放到227×227,简单粗暴但有效。当时也有人尝试加边框填充(padding)再缩放,但效果提升有限,反而增加工程量。
预训练模型在ImageNet上做分类,学到的特征对物体类别有很强的区分能力。但分类任务和检测任务还是有细节差异,比如分类的目标居中且完整,检测的目标可能被截断或只露出一部分。为了让CNN特征更适合检测,R-CNN引入了微调阶段:用预训练权重初始化,然后在检测数据集上继续训练,但训练样本不是简单的所有候选框,而是把与真实框IoU大于0.5的候选框当作正样本,其他当作背景负样本。
微调时的学习率要比较低,我当时用0.001的量级,因为是在预训练基础上微调,学得太猛会把之前学到的通用特征摧毁。还有一个细节是正负样本配比,如果背景样本太多,网络会严重偏向输出"背景"类别。R-CNN训练时使用的是mini-batch采样,每个批次固定包含一定比例的正样本和负样本,保证类别均衡。
3.3 模块三:类别SVM与回归器修正边框
微调完成后的CNN特征提取器,并不是直接输出候选框的类别。R-CNN做了一件在今天看来很绕的事:它把最后一个分类层换成支持向量机(SVM),为每个目标类别训练一个二分类SVM。为什么不用CNN自己的Softmax分类器?作者在论文里分析的原因是:CNN微调时使用的正样本定义(IoU>0.5的候选框)比较宽松,可能导致那部分内容其实不是准确的目标SVM正样本应该更严格一些。此外,当时认为SVM在特征基础上做分类鲁棒性更好。后来Fast R-CNN证明Softmax也不差,但在R-CNN那个年代,为了追求极致性能,SVM是重要选择。
SVM的输出是一个得分。得分高的候选框被判定为包含某类目标。但此时框的位置是选择性搜索给出来的,并不精准,可能目标只占框的一小块,或者框偏了。于是R-CNN还训练了一个线性回归器,输入是CNN提取的特征,预测的是候选框与真实框之间的偏移量(中心点横纵坐标偏移、宽高缩放)。
边框回归这一步非常重要,它通常能把定位误差降低好几个百分点mAP。我当时复现时,一开始忽略了回归器,只做了分类,结果检测框明显偏大偏松。换上回归器之后,框紧贴目标,效果提升肉眼可见。回归器只在IoU特别大的候选框上训练,通常是IoU>0.6或者直接只取候选框与真实框重叠最大的那部分,确保学的是"微调校正",而不是"从零学习定位"。
4. 完整跑通R-CNN的实操记录与关键参数
4.1 数据准备与候选区域生成细节
如果从零复现R-CNN,第一步是准备数据集。以Pascal VOC为例,每张训练图都有标注文件,里面写着每个目标的类别和真实框坐标。需要用选择性搜索为每张图生成候选框。很多现成实现里已经有selectivesearch函数,但要注意生成模式有fast和quality之分。R-CNN用的是quality模式,因为它更追求召回率。我实际测试过,fast模式大约每张图0.1秒,quality模式要0.3秒左右,差了三倍,但候选框的质量确实更好。
生成的候选框要和标注框计算IoU,然后打标签:IoU超过0.5的记为正样本,低于0.1的记为负样本。那中间0.1到0.5的怎么办?直接丢掉。为什么?把它们当作正样本会带来大量噪声,当作负样本又会干扰SVM,一个模糊两可的样本无论放在哪一边都会把分类边界搅浑。这个丢中间样本的做法,在R-CNN里体现得很明显,后续很多检测方法继承了这套"忽略模糊区域"的思想。
有一个容易踩的坑:正样本数量往往很少。一张图可能只有几个物体,而候选框有2000个,负样本几万个,严重不平衡。训练SVM时会用难负样本挖掘(hard negative mining),即第一轮训练后,把被误分为正样本的难负样本挑出来追加进训练集,再重新训练。这有点像考试后把错题本重新做一遍,模型会更在意那些"看似是目标其实不是"的区域。
4.2 训练流程的四个阶段与超参选择
完整的R-CNN训练分为四个阶段:
第一阶段是预训练CNN分类模型。大多数复现直接用公开的预训练权重,不需要自己从头练。第二阶段是用检测数据集微调CNN,把原来的一千类分类层换成类别数+1的背景类,用IoU>0.5的候选框作为正样本,学习率取预训练最后阶段的十分之一左右。第三阶段是在CNN提取的特征上训练每个类别的SVM,用难负样本挖掘。第四阶段是训练边框回归器。
这四个阶段是串行的,每一轮都要保存中间结果。我当时复现时觉得最繁琐的是第二阶段和第三阶段之间的衔接:微调后的CNN要去提取所有候选框的特征,存在磁盘上,作为SVM的输入。如果数据集很大,特征文件会非常占空间。我记得用了约五万张训练图,每张图两千个候选框,每个候选框的特征4096维浮点数,算下来要几十GB的磁盘。这还没算上各种中间文件。
SVM训练阶段也有很多小坑。比如正则化参数C的选择,太大容易过拟合,太小分类边界太空。我当时用网格搜索调C,从0.01到100范围取对数间隔,最后选在10左右效果较好。边框回归器要标准化特征和回归目标,否则训练不稳定。
4.3 推理流程与NMS去重
推理阶段更要仔细。输入一张新图,先跑选择性搜索得到约2000个候选框,然后对每个候选框裁剪、缩放、过CNN提特征,再用SVM打分。但问题来了:一个物体可能被很多候选框同时覆盖,输出结果会有大量高度重叠的框,这时候就需要非极大值抑制(NMS)来去重。
NMS的思路很简单:把所有框按SVM得分从高到低排序,保留得分最高的框,然后删除所有与它IoU超过某个阈值的其他框,重复这个过程。这个IoU阈值就是NMS窗口。阈值设得太低会导致两个挨得很近的物体被误删一个,设得太高又会留下大量重复框。在Pascal VOC上,NMS阈值通常在0.3到0.5之间。我当时发现0.4比较平衡,不过具体要根据应用场景去调。
NMS之后,还要对保留下来的框应用边框回归,得到更精准的坐标。这里有一个细节容易被忽略:回归器是基于CNN特征预测偏移量的,如果候选框被NMS删掉了,就没有必要再回归,所以顺序是先NMS再回归,还是先回归再NMS?我自己的经验是,先SVM打分,然后做NMS(得分高的先保留),再对保留的框做回归,这样计算量更小。有些实现是每个框先回归再NMS,效果差别不大,但回归后的框重叠度可能更高,NMS反而会误删,所以我倾向先NMS后回归。
5. R-CNN的先天短板:速度、存储与多阶段误差积累
5.1 速度瓶颈:每张图要跑约2000次CNN
R-CNN推理一张图,要跑约2000次完整的CNN前向计算。即使当时主流GPU跑一遍AlexNet需要约几十毫秒,这2000次加起来也要接近一分钟甚至更久。CPU实现就更不用说了,单张图等半分钟是常事。这在实验室里调参还能忍,但放到真实场景里基本没法用。
有意思的是,R-CNN的作者显然也知道这个问题,但他们的首要目标是把mAP拉上去,先证明深度学习做检测的可行性,速度问题可以后续解决。果然后面的优化很快就来了,但R-CNN这种"重复计算2000次"的做法确实暴露了深度学习检测器在工程上的原始病根。
5.2 多阶段训练与中间特征存储
除了速度,R-CNN的训练流程极其割裂。预训练、微调、SVM训练、回归器训练,四个阶段各自为政。每一个阶段都要单独保存中间结果,而且SVM训练依赖CNN特征,CNN特征又要靠候选框提取。整个系统不是端到端的,任何一处修改都可能要对前面阶段的产出重新计算一遍。
我记得调试的时候最痛苦的就是改动了一个超参数,结果所有中间特征都要重新提取。更别提磁盘占用,特征文件动辄几十GB,每次跑完都要小心清理。这也直接启发了Fast R-CNN的设计:能不能让CNN自己直接输出类别和回归结果,不做SVM,不存中间特征?
5.3 从这些短板反向推导出后续优化的方向
R-CNN的长短板非常鲜明,短板的背后其实藏着三个优化方向。一是特征重复计算问题:既然一张图有2000个候选框,它们之间一定有大量重叠区域,能不能让整个图像只过一次CNN,再通过某种方式把候选区域对应的特征图"抠"出来?这后来成了Fast R-CNN的出发点。
二是多阶段训练问题:SVM和回归器都依赖CNN特征,如果把类别预测和边框回归也集成到网络中,让所有模块可以联合训练,整个系统就简洁多了,落点也彻底打通。三是候选区域本身的生成方式:选择性搜索是在CPU上运行的,耗时不短,而且它是独立于检测网络的,能不能把候选区域生成也变成一个网络层?这就是Faster R-CNN的答案。
如果你完整复现过一遍R-CNN,你会发现这套思路的延展性极好。它把检测拆成"在外面的找区域"和"在里面的看内容"两个阶段,后来的两阶段检测器几乎都是在这个框架上演进的。理解了R-CNN的优缺点,再看新论文时会觉得很多设计都是像拼图一样严丝合缝地补上了R-CNN留下的坑。
6. 从R-CNN到Fast R-CNN再到Faster R-CNN:演进逻辑与借鉴
6.1 Fast R-CNN如何解决特征重复计算
R-CNN最明显的浪费是每个候选框都要单独跑一遍CNN。Fast R-CNN的做法是:把整张图像一次性输入CNN,得到整个特征图,然后用一个叫"感兴趣区域池化"(RoI Pooling)的操作,把每个候选框对应的特征区域从特征图上截下来,缩放到固定大小,再送入后面的全连接层。这样整张图只需要跑一次CNN,速度比R-CNN快了二十多倍。
RoI Pooling的操作有点像一个自适应池化层:它把候选框对应的特征区域划分成固定网格(比如7×7),每个网格做最大池化,输出固定尺寸的特征。这个操作的精妙之处在于,它不需要候选框在像素空间上是同一个尺寸,候选框对应特征图的位置和尺寸不同,都可以通过归一化网格提取出相同长度的特征向量。这样整个CNN就成了整图一次性前向,后面的分类和回归都共享这个特征图。
Fast R-CNN还把类别SVM换成了Softmax分类器,把边框回归也合并进了网络一起训练。整个训练过程变成了多任务损失:一个分支预测类别,一个分支预测边框偏移。不再需要单独的SVM和回归器训练阶段,也不需要保存中间特征到磁盘。这个改动极大简化了流程,mAP还比R-CNN提升了一点。
6.2 Faster R-CNN如何把候选区域也交给网络
Fast R-CNN虽然快了,但候选区域仍然用选择性搜索生成,这一步依然耗时且无法和GPU上的网络跨界协同。Faster R-CNN的洞察是:既然CNN已经提取了整图特征,为什么不能直接在特征图上预测出候选框呢?它引入了一个新组件叫区域提议网络(RPN,Region Proposal Network)。
RPN在共享特征图上滑动一个窗口,在每个位置同时预测多个预设尺寸和长宽比的"锚点框"是否存在目标,并给出目标性得分和粗略的坐标偏移。这些锚点框经过筛选和NMS之后,就代替了选择性搜索的候选框。然后整个系统可以端到端训练,RPN和检测头共享CNN特征,候选区域生成不再依赖CPU级的传统算法。
这一步的意义非常深远。它让"找区域"和"认物体"这两件事在同一个框架下联合训练,整个检测器的速度和精度都上了一个台阶。虽然后来又出现了大量单阶段检测器,大多数追求极致的速度,但两阶段方法在许多高精度场景依然是首选。R-CNN提出的候选区域+CNN分类+边框回归的组合,在今天依然是一个清晰的思维模型。
6.3 今天的检测器还保留了R-CNN的哪些遗产
如果你去看现在的检测框架,不管是两阶段的还是单阶段的,几乎都能找到R-CNN的基因。锚点框的概念和RPN的候选区域思想一脉相承,正负样本采样策略里IoU阈值的设定也沿用了R-CNN的经验,NMS去重和边框回归几乎是所有检测器不可或缺的组件。
我经常跟新人说,不要觉得R-CNN过时就直接跳过,那是一切检测思路的原点。很多今天看起来"理所当然"的设计,其实都是在R-CNN的坑里边踩边改出来的。比如为什么正样本IoU阈值取0.5而不是0.7?为什么难负样本挖掘有效?为什么边框回归能纠正定位误差?这些问题只有在R-CNN语境里才最容易讲清楚。
再说回R-CNN之后的传承:Fast R-CNN把分类和回归合并在网络里,Faster R-CNN把候选区域也变成了网络层,而后来的很多方法在特征金字塔、注意力机制上继续延伸。但"先做区域提议,再逐区域识别"这个双阶段范式,至今仍是高性能检测器的主流模式。单阶段检测器虽然跳过了显式的区域提议,但很多设计哲学仍然在向R-CNN致敬。
我个人在实际复现过程中的最大体会是,R-CNN最大的价值不在它的最终精度或速度,而在于它把问题拆解的清清楚楚。每一块组件都能单独理解,单独调试,对于新手来说是绝佳的入门教材。哪怕你没有真的跑一遍,只要在纸上画出它的流程图,把每一步的输入输出搞清楚,再去看后续的检测器,都会觉得通畅很多。
最后分享一个小技巧:如果你要读新出的检测论文,先别急着看模型结构,先问自己"这个方法是两阶段还是单阶段的?候选框怎么来的?正负样本怎么定义?损失函数里有几项?"这四个问题问下来,其实就是在R-CNN的框架里找参照系。很多新方法听起来花哨,本质上都是在R-CNN铺好的这条路上做优化。把R-CNN吃透,你在目标检测这个领域就有了一个稳固的坐标系,以后往里塞新知识都轻松得多。