1 Faster R-CNN 简介

经过RCNN和Fast RCNN的积淀,Ross B. Girshick在2016年提出了新的Faster RCNN,在结构上,Faster RCN已经将特征抽取(feature extraction),proposal提取,bounding box regression(rect refine),classification都整合在了一个网络中,使得综合性能有较大提高,在检测速度方面尤为明显。

示意图1:

示意图2:

Faster RCNN 可以分为4个主要内容:

  • Conv layers。作为一种CNN网络目标检测方法,Faster RCNN首先使用一组基础的conv+relu+pooling层提取image的feature maps。该feature maps被共享用于后续RPN层和全连接层。
  • Region Proposal Networks。RPN网络用于生成region proposals。该层通过softmax判断anchors属于foreground或者background,再利用bounding box regression修正anchors获得精确的proposals。
  • Roi Pooling。该层收集输入的feature maps和proposals,综合这些信息后提取proposal feature maps,送入后续全连接层判定目标类别。
  • Classification。利用proposal feature maps计算proposal的类别,同时再次bounding box regression获得检测框最终的精确位置。

下图展示了Python版本中的VGG16模型中的faster_rcnn_test.pt的网络结构。

可以清晰的看到该网络对于一副任意大小 \(P\times Q\) 的图像,首先缩放至固定大小 \(M\times N\),然后将 \(M\times N\) 图像送入网络;而Conv layers中包含了13个conv层+13个relu层+4个pooling层;
RPN网络首先经过 \(3\times 3\) 卷积,再分别生成foreground anchors与bounding box regression偏移量,然后计算出proposals;
而Roi Pooling层则利用proposals从feature maps中提取proposal feature送入后续全连接和softmax网络作classification(即分类proposal到底是什么object)。

2 Conv layers

Conv layers包含了conv,pooling,relu三种层。以python版本中的VGG16模型中的faster_rcnn_test.pt的网络结构为例,如图2,Conv layers部分共有13个conv层,13个relu层,4个pooling层。这里有一个非常容易被忽略但是又无比重要的信息,在Conv layers中:

  1. 所有的conv层都是:kernel_size=3,pad=1
  2. 所有的pooling层都是:kernel_size=2,stride=2

为何重要?在Faster RCNN Conv layers中对所有的卷积都做了扩边处理(pad=1,即填充一圈0),导致原图变为 \((M+2)\times (N+2)\) 大小,再做 \(3\times 3\) 卷积后输出 \(M\times N\)。正是这种设置,导致Conv layers中的conv层不改变输入和输出矩阵大小。如下图:

类似的是,Conv layers中的pooling层kernel_size=2,stride=2。这样每个经过pooling层的MxN矩阵,都会变为 \((M/2)\times (N/2)\) 大小。综上所述,在整个Conv layers中,conv和relu层不改变输入输出大小,只有pooling层使输出长宽都变为输入的 \(\frac 1 2\)。

那么,一个 \(M\times N\) 大小的矩阵经过Conv layers固定变为 \((M/16) \times (N/16)\)!这样Conv layers生成的featuure map中都可以和原图对应起来。

3 Region Proposal Networks (RPN)

经典的检测方法生成检测框都非常耗时,如OpenCV adaboost使用滑动窗口+图像金字塔生成检测框;或如RCNN使用SS(Selective Search)方法生成检测框。而Faster RCNN则抛弃了传统的滑动窗口和SS方法,直接使用RPN生成检测框,将Region Proposal也交给CNN来做,它能和整个检测网络共享全图的卷积特征,使得区域建议几乎不花时间,这也是Faster RCNN的巨大优势,能极大提升检测框的生成速度。

RCNN解决的是,“为什么不用CNN做classification呢?”
Fast R-CNN解决的是,“为什么不一起输出bounding box和label呢?”
Faster R-CNN解决的是,“为什么还要用selective search呢?”

RPN的核心思想是使用CNN卷积神经网络直接产生Region Proposal,使用的方法本质上就是滑动窗口(只需在最后的卷积层上滑动一遍)。

RPN的输入是任意大小的图像,输出是一组打过分的候选框(object proposals)。在卷积的最后一层feature map上使用固定大小的窗口(注意sliding window只是选择位置,除此之外没有其它作用,和后面的3*3的卷积区分)滑动,每个窗口会输出固定大小维度的特征(图中256),每一个窗口对候选的9个box进行回归坐标和分类(这里的分类表示box中是不是一个object,而不是具体的类别)。


下图展示了RPN网络的具体结构。可以看到RPN网络实际分为2条线,

  1. 上面一条通过softmax分类anchors获得foreground和background(检测目标是foreground)
  2. 下面一条用于计算对于anchors的bounding box regression偏移量,以获得精确的proposal。

而最后的Proposal层则负责综合foreground anchors和bounding box regression偏移量获取proposals,同时剔除太小和超出边界的proposals。其实整个网络到了Proposal Layer这里,就完成了相当于目标定位的功能。

3.1 anchors

Anchors是一组大小固定的参考窗口:
\(三种尺度 (128^2,256^2,512^2)\times 三种长宽比(1:1,1:2,2:1)\) ,如下图所示,表示RPN网络中对特征图滑窗时每个滑窗位置所对应的原图区域中9种可能的大小,相当于模板,对任意图像任意滑窗位置都是这9中模板。继而根据图像大小计算滑窗中心点对应原图区域的中心点,通过中心点和size就可以得到滑窗位置和原图位置的映射关系,由此原图位置并根据与Ground Truth重复率贴上正负标签,让RPN学习该Anchors是否有物体即可

所谓anchors,实际上就是一组由rpn/generate_anchors.py生成的矩形。直接运行作者demo中的generate_anchors.py可以得到以下输出:

1
2
3
4
5
6
7
8
9
[[ -84.  -40.   99.   55.]  
[-176. -88. 191. 103.]
[-360. -184. 375. 199.]
[ -56. -56. 71. 71.]
[-120. -120. 135. 135.]
[-248. -248. 263. 263.]
[ -36. -80. 51. 95.]
[ -80. -168. 95. 183.]
[-168. -344. 183. 359.]]

其中每行的4个值 \([x1,y1,x2,y2]\) 代表矩形左上和右下角点坐标。9个矩形共有3种形状,长宽比为大约为:\(width:height = [1:1, 1:2, 2:1]\) 三种,实际上通过anchors就引入了检测中常用到的多尺度方法

注:关于上面的anchors size,其实是根据检测图像设置的。在python demo中,会把任意大小的输入图像reshape成800x600(即第二张图中的M=800,N=600)。再回头来看anchors的大小,anchors中长宽1:2中最大为352x704,长宽2:1中最大736x384,基本是cover了800x600的各个尺度和形状。

那么这9个anchors是做什么的呢?如下图,遍历Conv layers计算获得的feature maps,为每一个点都配备这9种anchors作为初始的检测框。这样做获得检测框很不准确,不用担心,后面还有2次bounding box regression可以修正检测框位置。

解释一下上面这张图的数字:

  1. 在原文中使用的是ZF model中,其Conv Layers中最后的conv5层num_output=256,对应生成256张特征图,所以相当于feature map每个点都是256-d
  2. 在conv5之后,做了RPN_conv/3x3卷积且num_output=256,相当于每个点又融合了周围3x3的空间信息,同时256-d不变(如第四张图中的红框)
  3. 假设在conv5 feature map中每个点上有 \(k\) 个anchor(默认k=9),而每个anhcor要分foreground和background,所以每个点由256d feature转化为 \(cls=2k\) scores;而每个anchor都有 \([x, y, w, h]\) 对应4个偏移量,所以 \(reg=4k\) coordinates
  4. 补充一点,全部anchors拿去训练太多了,训练程序会选取256个合适的anchors进行训练(什么是合适的anchors下文有解释)

看完单个点的,再来看完整的:

关于正负样本的划分:考察训练集中的每张图像(含有人工标定的ground true box) 的所有anchor(\(N\times M \times k\))

  1. 对每个标定的ground true box区域,与其重叠比例最大的anchor记为 正样本 (保证每个ground true 至少对应一个正样本anchor)
  2. 对1剩余的anchor,如果其与某个标定区域重叠比例大于0.7,记为正样本(每个ground true box可能会对应多个正样本anchor。但每个正样本anchor 只可能对应一个grand true box);如果其与任意一个标定的重叠比例都小于0.3,记为负样本。
  3. 对1,2剩余的anchor,弃去不用。
  4. 跨越图像边界的anchor弃去不用

所谓IoU,就是预测box和真实box的覆盖率,其值等于两个box的交集除以两个box的并集。其它的anchor不参与训练。

3.2 bounding box regression 原理

介绍bounding box regression数学模型及原理。如图9所示绿色框为飞机的Ground Truth(GT),红色为提取的foreground anchors,那么即便红色的框被分类器识别为飞机,但是由于红色的框定位不准,这张图相当于没有正确的检测出飞机。所以我们希望采用一种方法对红色的框进行微调,使得foreground anchors和GT更加接近。

对于窗口一般使用四维向量 \((x, y, w, h)\) 表示,分别表示窗口的中心点坐标和宽高。对于下图,红色的框A代表原始的Foreground Anchors,绿色的框G代表目标的GT,我们的目标是寻找一种关系,使得输入原始的anchor A经过映射得到一个跟真实窗口G更接近的回归窗口G’,即:给定 \(A=(A_x, A_y, A_w, A_h)\),寻找一种映射 \(f\),使得

$$f(A_x, A_y, A_w, A_h)=(G’x, G’y, G’w, G’h)≈(Gx, Gy, Gw, Gh)$$

那么经过何种变换才能从A变为G’呢? 比较简单的思路就是:

  1. 先做平移 \((\Delta x, \Delta y)\),\(\Delta x = A_w d_x(A), \Delta y = A_h d_y(A)\)

  1. 再做缩放 \((S_w, S_h)\),\(S_w = exp(d_w(A)), S_h = exp(d_h(A))\)

观察上面4个公式发现,需要学习的是 \(d_{x}(A),d_{y}(A),d_{w}(A),d_{h}(A)\) 这四个变换。当输入的anchor与GT相差较小时,可以认为这种变换是一种线性变换, 那么就可以用线性回归来建模对窗口进行微调(注意,只有当anchors和GT比较接近时,才能使用线性回归模型,否则就是复杂的非线性问题了)。对应于Faster RCNN原文,平移量 \((t_{x}, t_{y})\) 与尺度因子 \((t_{w}, t_{h})\) 如下:

接下来的问题就是如何通过线性回归获得 \(d_{x}(A),d_{y}(A),d_{w}(A),d_{h}(A)\) 了。线性回归就是给定输入的特征向量 \(X\), 学习一组参数 \(W\), 使得经过线性回归后的值跟真实值Y(即GT)非常接近,即 \(Y=WX\)。对于该问题,输入 \(X\) 是一张经过num_output=1的 \(1\times 1\) 卷积获得的feature map,定义为 \(Φ\);同时还有训练传入的GT,即 \((tx, ty, tw, th)\)。输出是 \(dx(A),dy(A),dw(A),dh(A)\) 四个变换。那么目标函数可以表示为:

其中 \(Φ(A)\) 是对应anchor的feature map组成的特征向量,\(w\) 是需要学习的参数,\(d(A)\) 是得到的预测值(\(*\)表示 \(x,y,w,h\),也就是每一个变换对应一个上述目标函数)。为了让预测值 \((tx, ty, tw, th)\) 与真实值最小,得到损失函数:

函数优化目标为:

需要说明,只有在GT与需要回归框位置比较接近时,才可近似认为上述线性变换成立。

说完原理,对应于Faster RCNN原文,foreground anchor与ground truth之间的平移量 \((t_x, t_y)\) 与尺度因子 \((t_w, t_h)\) 如下:

其中:

$$x,y,w,h\ 为\ box\ 的中心坐标,宽,高$$

$$\begin{cases}
x: predicted\ box\ (就是Proposal)\\
x_{a}: anchor\ box\ (N\times M \times k 个)\\
x^{*}: Ground\ Truth\ (正确标定的GT)\
\end{cases}
$$

对于训练bouding box regression网络回归分支,输入是cnn feature \(Φ\),监督信号是Anchor与GT的差距 \((t_x, t_y, t_w, t_h)\),即训练目标是:输入 Φ的情况下使网络输出与监督信号尽可能接近。

那么当bouding box regression工作时,再输入Φ时,回归网络分支的输出就是每个Anchor的平移量和变换尺度 \((t_x, t_y, t_w, t_h)\),显然即可用来修正Anchor位置了。

3.3 Proposal Layer

Proposal Layer负责综合所有 \([d_{x}(A),d_{y}(A),d_{w}(A),d_{h}(A)]\) 变换量和foreground anchors,计算出精准的proposal,送入后续RoI Pooling Layer。

Proposal Layer有3个输入:fg/bg anchors分类器结果rpn_cls_prob_reshape,对应的bbox reg的[dx(A),dy(A),dw(A),dh(A)]变换量rpn_bbox_pred,以及im_info;另外还有参数feat_stride=16,这和图4是对应的。

首先解释im_info。对于一副任意大小PxQ图像,传入Faster RCNN前首先reshape到固定 \(M\times N\),\(im\_info=[M, N, scale\_factor]\)则保存了此次缩放的所有信息。然后经过Conv Layers,经过4次pooling变为 \(W\times H=(M/16)\times (N/16)\) 大小,其中feature_stride=16则保存了该信息。所有这些数值都是为了将proposal映射回原图而设置的,如下图,毕竟检测就是为了在原图上画一个框而已。

Proposal Layer forward 按照以下顺序依次处理:

  1. 再次生成anchors,并对所有的anchors做bbox reg位置回归(注意这里的anchors生成顺序和之前是即完全一致的)
  2. 按照输入的foreground softmax scores由大到小排序anchors,提取前pre_nms_topN(e.g. 6000)个anchors。即提取修正位置后的foreground anchors
  3. 利用feat_stride和im_info将anchors映射回原图,判断fg anchors是否大范围超过边界,剔除严重超出边界fg anchors。
  4. 进行nms(nonmaximum suppression,非极大值抑制
  5. 再次按照nms后的foreground softmax scores由大到小排序fg anchors,提取前post_nms_topN(e.g. 300)结果作为proposal输出。

之后输出proposal=[x1, y1, x2, y2],注意,由于在第三步中将anchors映射回原图判断是否超出边界,所以这里输出的proposal是对应MxN输入图像尺度的,这点在后续网络中有用。另外我认为,严格意义上的检测应该到此就结束了,后续部分应该属于识别了~

RPN网络结构就介绍到这里,总结起来就是:

  1. 生成 anchors
  2. softmax 分类器提取 fg anchors
  3. bbox reg 回归 fg anchors
  4. Proposal Layer 生成 proposals

4 RoI pooling

对于传统的CNN(如AlexNet,VGG),当网络训练好后输入的图像尺寸必须是固定值,同时网络输出也是固定大小的vector or matrix。如果输入图像大小不定,这个问题就变得比较麻烦。有2种解决办法:

  1. 从图像中crop一部分传入网络
  2. 将图像warp成需要的大小后传入网络

传统办法的示意图如上图,可以看到无论采取那种办法都不好,要么crop后破坏了图像的完整结构,要么warp破坏了图像原始形状信息。回忆RPN网络生成的proposals的方法:对foreground anchors进行bound box regression,那么这样获得的proposals也是大小形状各不相同,即也存在上述问题。所以Faster RCNN中提出了RoI Pooling解决这个问题(RoI Pooling是从SPP发展而来)

ROI Pooling的过程就是将一个个大小不同的box矩形框,都映射成大小为\(w\times h\)的矩形框;

RoI Pooling层则负责收集proposal,并计算出proposal feature maps,送入后续网络。Rol pooling层有2个输入:

  1. 原始的feature maps(指的是进入RPN层之前的那个Conv层的Feature Map,通常我们称之为 share_conv)
  2. RPN输出的proposal boxes(大小各不相同),其中值得注意的是:坐标的参考系不是针对feature map这张图的,而是针对原图的(神经网络最开始的输入)

在之前有明确提到:\(proposal=[x1, y1, x2, y2]\) 是对应 \(M\times N\) 尺度的,所以首先使用spatial_scale参数将其映射回 \(M/16 \times N/16\) 大小的feature maps尺度(这里来回多次映射,是有点绕);

之后将每个proposal水平和竖直都分为7份,对每一份都进行max pooling处理。这样处理后,即使大小不同的proposal,输出结果都是7x7大小,实现了fixed-length output。

5 Classification

Classification部分利用已经获得的proposal feature maps,通过full connect层与softmax计算每个proposal具体属于那个类别(如人,车,电视等),输出cls_prob概率向量;同时再次利用bounding box regression获得每个proposal的位置偏移量bbox_pred,用于回归更加精确的目标检测框。

6 Faster RCNN 训练

Faster CNN的训练,是在已经训练好的model(如VGG_CNN_M_1024,VGG,ZF)的基础上继续进行训练。实际中训练过程分为6个步骤:

  1. 在已经训练好的model上,训练RPN网络
  2. 利用步骤1中训练好的RPN网络,收集proposals
  3. 第一次训练Fast RCNN网络
  4. 第二训练RPN网络
  5. 再次利用步骤4中训练好的RPN网络,收集proposals
  6. 第二次训练Fast RCNN网络

可以看到训练过程类似于一种“迭代”的过程,不过只循环了2次。至于只循环了2次的原因是应为作者提到:”A similar alternating training can be run for more iterations, but we have observed negligible improvements”,即循环更多次没有提升了。接下来本章以上述6个步骤讲解训练过程。

RPN通过反向传播(BP,back-propagation)和随机梯度下降(SGD,stochastic gradient descent)进行端到端(end-to-end)训练。

每一个mini-batch包含从一张图像中随机提取的256个anchor(注意,不是所有的anchor都用来训练),训练的过程对正负样本进行抽样,前景样本和背景样本均取128个,保证正负比例为1:1。如果一个图像中的正样本数小于128,则多用一些负样本以满足有256个Proposal可以用于训练。

整体损失函数具体为:

$$L({p_{i}},{t_{i}})={\frac {1} {N_{cls}}}\sum_{i} L_{cls}(p_{i},p_{i}^{*}) + \lambda {\frac {1}{N_{reg}}} \sum_{i}p_{i}^{*} L_{reg}(t_i,t_i^{*})$$

其中i是mini-batch中anchor的索引

\(p_i\) 为 anchor 预测为目标的概率(foreground softmax predict概率);

\(p_i^{*}\)代表对应的GT predict概率:

  • 即当第i个anchor与GT间 \(IoU>0.7\),认为是该anchor是foreground,\(p_i^{*}=1\);
  • 反之 \(IoU<0.3\) 时,认为是该anchor是background,\(p_i^{*}=0\);
  • 至于那些 \(0.3<IoU<0.7\) 的anchor则不参与训练。

$$p_i^*=\begin{cases}
0\ \ negative\ label\\
1\ \ positive\ label\
\end{cases}
$$

\(L_{cls}(p_i,p_i^*)\) 是两个类别(目标 vs. 非目标)的对数损失:

$$ L_{cls}(p_i,p_i^{*})=-\log [p_{i}^{*}p_i+(1-p_i^{*})(1-p_i)]$$


\(t_i=\{t_x,t_y,t_w,t_h\}\) 是一个向量,表示预测的 bounding box 包围盒的4个参数化坐标;

\(t_i^*\) 是与 positive anchor 对应的 ground truth (GT box)包围盒的坐标向量;

\(L_{reg}(t_i,t_i^*)\) 是回归损失,用 \(L_{reg}(t_i,t_i^*)=R(t_i-t_i^*)\) 来计算,\(R\) 是 \(smooth L1\) 函数:

$$L_{reg}(t_i,t_i^*)={\sum_{i \in {x,y,w,h}} smooth_{L1}(t_i-t_i^*)}$$

$$
smooth_{L_{1}}(x) =
\begin{cases}
0.5x^2, & \text{if |x| < 1}\\
|x|-0.5, & \text{otherwise}
\end{cases}
$$

smooth L1 函数图像为:

注意:在该loss中乘了 \(pi^*\),相当于只关心foreground anchors的回归,因为负样本时 \(p_i^*=0\) 该项被消去(其实在回归中也完全没必要去关心background)。


可以看到,整个Loss分为2部分:

  1. cls loss,即rpn_cls_loss层计算的softmax loss,用于分类anchors为forground与background的网络训练
  2. reg loss,即rpn_loss_bbox层计算的soomth L1 loss,用于bounding box regression网络训练。

由于在实际过程中,\(N_{cls}\) 和 \(N_{reg}\) 差距过大,用参数 \(\lambda\) 平衡二者(如 \(N_{cls}=256\),\(N_{reg}=2400\) 时设置 \(λ=10\)),使总的网络Loss计算过程中能够均匀考虑2种Loss。

参考及部分转载

  1. 一文读懂Faster R-CNN
  2. Faster R-CNN
  3. Faster RCNN解析