手机相册里搜一张合影,要框出人脸再说出是谁。自动驾驶要在街景里圈出车、行人、红绿灯。这些任务有个共同点,输入一张图,图里有多个物体,模型既要指出物体在哪里,又要说出物体是什么类别。这就是目标检测。在深度学习流行之前,检测靠手工设计的特征加滑动窗口,效果和速度都上不去。R-CNN 是把卷积神经网络成功搬进目标检测的开山之作,2014 年由 Girshick 等人提出。后来的 Fast R-CNN、Faster R-CNN 都是在它的骨架上一步步改出来的,弄懂它,后面几个版本就顺了。
接下来,我们按它原来的四步走一遍,重点讲清候选区域怎么来、特征怎么提、SVM 分类和边框回归各自在做什么,最后用一组具体数字把整张图的处理流程和尺寸算例从头走到尾。读这篇之前,你需要知道卷积神经网络大致是干什么的,还要知道 IOU 这个交并比的概念,不清楚的地方我会在用到时先解释。
1. 检测任务
我们先把任务本身钉死。图像分类是给整张图贴一个标签,比如这是一只猫。可真实场景里,一张图往往同时有猫、沙发、窗户,只给一个标签不够用,你还得知道猫在哪个位置。目标检测要的就是两件东西,一组矩形框,框住每个感兴趣的物体,再加每个框内物体的类别。输入是一张 H 行 W 列三通道的图片,输出是若干个框和对应的类别名。
难就难在,你事先不知道物体出现在画面哪个位置,也不知道它是大是小。如果用滑动窗口把每种位置、每种尺寸都试一遍,窗口数量是天文数字,每个窗口还要单独做一次卷积计算,根本跑不动。R-CNN 的思路是先粗后细,先用一种便宜的传统算法圈出大概可能有物体的两千个候选框,再让昂贵的卷积网络只去处理这两千个框,这就是它被称为两阶段检测的原因。
2. 整体流程
R-CNN 把上面这个先粗后细拆成四步,顺序很固定。第一步,Selective Search 这种传统算法在原图上生成大约两千个候选矩形框,这一步不看类别,只负责框得宽。第二步,把每个候选框原样抠出来,缩放到卷积网络要求的固定尺寸。第三步,每个缩放后的框各自过一遍主干网络,抽出一个 4096 维的特征向量。第四步,拿这个特征向量去喂 SVM 分类器判断类别,再喂边框回归器把框的位置修准。
四步里最贵的是第三步,两千个框要做两千次前向计算。R-CNN 没有共享计算,这也是它后来被 Fast R-CNN 重点改造的地方。我们把四步串起来看,每一步的产物和数量是这样的:
| 步骤 | 做什么 | 产物 | 数量级 |
|---|---|---|---|
| 候选区域 | Selective Search 圈出可能有物体的框 | 一组矩形框 | 约 2000 个 |
| 缩放 | 把不规则框 warp 成固定尺寸 | 227×227×3 的图块 | 2000 块 |
| 特征提取 | AlexNet 卷积加全连接 | 4096 维特征向量 | 每个框一个 |
| 分类与回归 | SVM 判类别,边框回归修位置 | 类别标签加修正后的框 | 每张图几十个有效框 |
3. 候选区域
那这两千个候选框从哪来呢?作者选的是 Selective Search 算法,它不需要标注,是一种类别独立的区域生成方法。直觉很朴素,一张图里同一个物体的像素在颜色、纹理上往往连在一起,那就先把图过分割成无数小碎块,再不断把最像的两块合并,合并过程中形成的大大小小的块,就是候选框。
具体循环是这样。先把原图分割成互不重叠的小区域,全部放进集合 R。然后算出 R 里所有相邻区域两两之间的相似度,综合颜色、纹理、尺寸、形状四个因素,存进集合 S。每轮从 S 里挑相似度最大的两块合并成一个大区域,把这两块以及和它们有关的相似度记录删掉,再算这个新大区域和它邻居的相似度,加回 S,同时把大区域放进 R。S 空了就停。这样一路合并下来,过程中产生的所有区域,从最小的碎块到最大的整图,都收集起来,就是那约两千个候选框。它宁可多框,不漏框,具体是不是物体、是什么类别,交给后面的网络判断。
4. 特征提取
候选框大小千奇百怪,有的细长有的方正,可卷积网络的输入尺寸是固定的。R-CNN 的做法很直接,不管原来什么比例,一律 warp 拉伸到 227×227×3。拉伸会改变物体比例,但后面全连接层只认特征,训练时见过各种被拉变形的样本,反而能容忍这种形变。主干网络可以用 AlexNet 也可以用 VGG,原论文用的是 AlexNet,最后一层全连接输出一个 4096 维的向量,作为这个候选框的特征表示。
直接从零训练数据不够,做法是迁移学习。先拿在 ImageNet 上百万张图上预训练好的权重起步,再在自己的检测数据上微调。微调时要告诉网络哪些框算物体、哪些不算。这里用 IOU 来量,IOU 是候选框和人工标注的真实框之间交集面积除以并集面积,越接近 1 框得越准。我们规定候选框和任意真实框的 IOU 大于等于 0.5,就算这个框里有物体,当正样本,其余一律当背景负样本。正负样本数量悬殊,训练时要按比例采样,不然网络会偷懒全猜背景。
5. 分类与回归
特征有了,我们先分类再修框。R-CNN 用的不是神经网络分类头,而是为每个类别单独训练一个 SVM 支持向量机。特征向量是 4096 维,喂进去,SVM 输出这个框属于哪一类。类别数是 n 加 1,多出的那个是背景类。比如 PASCAL VOC 有 20 个物体类,就要训 21 个 SVM,专门有一个 SVM 负责把什么都不是的框判成背景。这里要注意,SVM 的正负样本划分和上面微调时不一样,SVM 的正样本直接用人工标注的真实框,负样本是那些和真实框 IOU 小于 0.3 的候选框。两套阈值不统一,是 R-CNN 一个有意的设计,不是写错。
分类之后还有两件事要做。一件是去重,一个物体往往被好几个候选框同时命中,SVM 都给了高分,这时用 NMS 非极大值抑制,保留得分最高的那个,把和它重叠超过阈值的其他框删掉。另一件是修位置,Selective Search 出来的框经常贴着物体边缘偏一圈,所以再为每个类别单独训一个边框回归器。它不学类别,只学四个修正量,分别是中心横坐标偏移、中心纵坐标偏移、宽度缩放、高度缩放,记为 dx、dy、dw、dh。
修正规则写成公式是这样,候选框的中心坐标和宽高记为 Px、Py、Pw、Ph,修正后的框是 Gx 等于 Px 加 Pw 乘 dx,Gy 等于 Py 加 Ph 乘 dy,Gw 等于 Pw 乘 e 的 dw 次方,Gh 等于 Ph 乘 e 的 dh 次方。看得出来,dx 和 dy 是先把平移量按框宽高归一后再加回去,dw 和 dh 走指数,保证宽高始终是正的。回归器学的就是这四个数,训练时把真实框 G 相对候选框 P 应该做的调整量算出来当目标,横坐标目标 tx 等于 Gx 减 Px 再除以 Pw,纵坐标 ty 等于 Gy 减 Py 再除以 Ph,宽度 tw 等于 Gw 除以 Pw 取对数,高度 th 等于 Gh 除以 Ph 取对数。回归器输出的四个值尽量逼近这四个目标,损失用均方误差就行。
6. 完整算例
我们拿一张 600 行 800 列的图走一遍,把数量和尺寸坐实。Selective Search 在这张图上产出约 2000 个候选框,大小不一。每个框 warp 成 227×227×3,过 AlexNet 后各得一个 4096 维向量。光这张图就要存下 2000 乘 4096,约 819 万个浮点数的特征,这也解释了为什么 R-CNN 训练慢、还要把特征先落盘。
再把边框回归这一段用数字算透。假设有一个候选框 P,中心在 (100, 100),宽 50 高 80。人工标注的真实框 G,中心在 (110, 120),宽 60 高 100。我们先看这个候选框框得准不准,P 的面积是 50 乘 80 等于 4000,G 的面积是 60 乘 100 等于 6000,两个框重叠的部分面积是 3150,并集面积是 4000 加 6000 减 3150 等于 6850,IOU 等于 3150 除以 6850,约 0.46,刚好卡在 0.5 阈值下面,说明框得偏,需要修。
按上面的公式,我们算一下回归器要拟合的目标。tx 等于 (110 减 100) 除以 50,等于 0.2。ty 等于 (120 减 100) 除以 80,等于 0.25。tw 等于 ln(60 除以 50),也就是 ln(1.2),约 0.1823。th 等于 ln(100 除以 80),也就是 ln(1.25),约 0.2231。如果回归器恰好输出这四个数,代回去修正,Gx 等于 100 加 50 乘 0.2 等于 110,Gy 等于 100 加 80 乘 0.25 等于 120,Gw 等于 50 乘 e 的 0.1823 次方,e 的 0.1823 次方约等于 1.2,所以 Gw 正好是 60,Gh 等于 80 乘 e 的 0.2231 次方,e 的 0.2231 次方约等于 1.25,Gh 正好是 100,分毫不差地回到真实框。这组对照列出来是这样:
| 项目 | 候选框 P | 真实框 G | 修正后 |
|---|---|---|---|
| 中心横坐标 | 100 | 110 | 110 |
| 中心纵坐标 | 100 | 120 | 120 |
| 宽度 | 50 | 60 | 60 |
| 高度 | 80 | 100 | 100 |
这组数字用 numpy 跑一遍就能复现,也顺便验证公式没错:
import numpy as np # 候选框 P 与真实框 G, 用中心坐标加宽高表示 Px, Py, Pw, Ph = 100.0, 100.0, 50.0, 80.0 Gx, Gy, Gw, Gh = 110.0, 120.0, 60.0, 100.0 # 边框回归要拟合的四个目标偏移 tx = (Gx - Px) / Pw ty = (Gy - Py) / Ph tw = np.log(Gw / Pw) th = np.log(Gh / Ph) print(round(tx, 4), round(ty, 4), round(tw, 4), round(th, 4)) # 0.2 0.25 0.1823 0.2231 # 拿这组偏移反过来修正候选框, 看是否回到 G print(Px + Pw * tx, Py + Ph * ty, Pw * np.exp(tw), Ph * np.exp(th)) # 110.0 120.0 60.0 100.0
7. 常见误区
第一个坑,以为两千个候选框是卷积网络自己找的。其实框是 Selective Search 这个传统算法圈的,卷积网络只负责对圈好的框提特征,这两件事别混。
第二个坑,以为微调网络和后面 SVM 用的是同一批正负样本。前面说过,微调时 IOU 大于等于 0.5 算物体,SVM 训练时正样本直接用真实框、IOU 小于 0.3 才算背景,这两套标签是故意不一样的。
第三个坑,以为边框回归能把差得很远的框拉回来。它本质是线性修正,只在候选框已经离物体很近时才管用,框和真实框 IoU 差太多,强行修正只会越修越歪。这也是为什么候选区域这一步宁可多框不能漏。
第四个坑,以为 n 加 1 里多出来的 1 是第 n 加 1 种物体。它不是物体,是背景类,专门用来收容那些什么都没框中的候选框。
我们把整条线收一下。R-CNN 用 Selective Search 先圈约两千个候选框,逐个 warp 到 227×227,过 AlexNet 得 4096 维特征,再用每类一个的 SVM 判类别,用每类一个的边框回归器修位置,最后 NMS 去掉重复框。它第一次证明了卷积特征能把检测做上去,但两千次重复前向太慢,特征还得离线存盘,这些短板后来催出了 Fast R-CNN 的整张图共享卷积和 Faster R-CNN 的神经网络出候选框。原论文见 https://arxiv.org/pdf/1311.2524.pdf




冀公网安备13050302001966号