经过R-CNN和Fast RCNN的积淀,Ross B. Girshick在2016年提出了新的Faster RCNN,在结构上,Faster RCNN已经将特征抽取(feature extraction),proposal提取,bounding box regression(rect refine),classification都整合在了一个网络中,使得综合性能有较大提高,在检测速度方面尤为明显。
图1 Faster RCNN基本结构(来自原论文)
依作者看来,如图1,Faster RCNN其实可以分为4个主要内容:
所以本文以上述4个内容作为切入点介绍Faster R-CNN网络。
图2展示了python版本中的VGG16模型中的faster_rcnn_test.pt的网络结构,可以清晰的看到该网络对于一副任意大小PxQ的图像,首先缩放至固定大小MxN,然后将MxN图像送入网络;而Conv layers中包含了13个conv层+13个relu层+4个pooling层;RPN网络首先经过3x3卷积,再分别生成positive anchors和对应bounding box regression偏移量,然后计算出proposals;而Roi Pooling层则利用proposals从feature maps中提取proposal feature送入后续全连接和softmax网络作classification(即分类proposal到底是什么object)。
图2 faster_rcnn_test.pt网络结构 (pascal_voc/VGG16/faster_rcnn_alt_opt/faster_rcnn_test.pt)
虽然优雅的Fast R-CNN已经提出了一个single stage的目标检测过程,但是它仍然不够优雅,因为region proposal还是需要单独计算,所以整个过程还不够FAST,于是Faster R-CNN就被提出来啦 。
这篇文章最重要的创新在于提出了Region Proposal Network(RPN)和anchor box,使得可以用神经网络来提取proposal,同时使得整个过程具有以下的优点:
总的来说,Faster R-CNN是对Fast R-CNN进行的改进,所以模型上有很多相似之处。简单来说,Faster R-CNN其实是在Fast R-CNN模块的基础上加了一个Region Proposal Network模块。
Conv layers包含了conv,pooling,relu三种层。以python版本中的VGG16模型中的faster_rcnn_test.pt的网络结构为例,如图2,Conv layers部分共有13个conv层,13个relu层,4个pooling层。这里有一个非常容易被忽略但是又无比重要的信息,在Conv layers中:
为何重要?在Faster RCNN Conv layers中对所有的卷积都做了扩边处理( pad=1,即填充一圈0),导致原图变为 (M+2)x(N+2)大小,再做3x3卷积后输出MxN 。正是这种设置,导致Conv layers中的conv层不改变输入和输出矩阵大小。如图3:
图3 卷积示意图
类似的是,Conv layers中的pooling层kernel_size=2,stride=2。这样每个经过pooling层的MxN矩阵,都会变为(M/2)x(N/2)大小。综上所述,在整个Conv layers中,conv和relu层不改变输入输出大小,只有pooling层使输出长宽都变为输入的1/2。
那么,一个MxN大小的矩阵经过Conv layers固定变为(M/16)x(N/16)!这样Conv layers生成的feature map中都可以和原图对应起来。
经典的检测方法生成检测框都非常耗时,如OpenCV adaboost使用滑动窗口+图像金字塔生成检测框;或如R-CNN使用SS(Selective Search)方法生成检测框。而Faster RCNN则抛弃了传统的滑动窗口和SS方法,直接使用RPN生成检测框,这也是Faster R-CNN的巨大优势,能极大提升检测框的生成速度。
如何训练出一个网络来替代selective search相类似的功能呢?论文借鉴SPP和ROI中的思想 在feature map中提取proposal。 先通过对应关系把feature map的点映射回原图,在每一个对应的原图设计不同的固定尺度窗口(bbox),根据该窗口与ground truth的IOU给它正负标签,让它学习里面是否有object,这样就训练一个网络(Region Proposal Network)。
由于我们只需要找出大致的地方,无论是精确定位位置还是尺寸,后面的工作都可以完成,作者对bbox做了三个固定:固定尺度变化(三种尺度{ 12 8 2 , 25 6 2 , 52 1 2 128^2,256^2,521^2 1282,2562,5212}),固定scale ratio变化(三种ratio,{1:1,1:2,2:1}),固定采样方式(只在feature map的每个点在原图中的对应ROI上采样,反正后面的工作能进行调整) 。如此就可以降低任务复杂度。
图4 RPN网络结构
上图4展示了RPN网络的具体结构。可以看到RPN网络实际分为2条线,上面一条通过softmax分类anchors获得positive和negative分类,下面一条用于计算对于anchors的bounding box regression偏移量,以获得精确的proposal。而最后的Proposal层则负责综合positive anchors和对应bounding box regression偏移量获取proposals,同时剔除太小和超出边界的proposals。其实整个网络到了Proposal Layer这里,就完成了相当于目标定位的功能。
从图中可以看出RPN的主要流程包括:生成anchors、softmax分类器、bbox reg回归器 、Proposal Layer生成proposals。
图5 多通道卷积计算方式
如图5,输入有3个通道,同时有2个卷积核。对于每个卷积核,先在输入3个通道分别作卷积,再将3个通道结果加起来得到卷积输出。所以对于某个卷积层,无论输入图像有多少个通道,输出图像通道数总是等于卷积核数量!
对多通道图像做1x1卷积,其实就是将输入图像于每个通道乘以卷积系数后加在一起,即相当于把原图像中本来各个独立的通道“联通”在了一起。
提到RPN网络,就不能不说anchors。所谓anchors,实际上就是一组由rpn/generate_anchors.py生成的矩形。直接运行作者demo中的generate_anchors.py可以得到以下输出:
[[ -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.]]
如何生成anchor:首先使用一个(3, 3)的sliding window在feature map上滑动(就是做卷积,卷积的本质就是卷积核滑动求和),然后取(3, 3)滑窗的中心点,将这个点对应到原图上,然后取三种不同的尺寸(128, 256, 512)和三种比例(1:1, 2:1, 1:2)在原图上产生9个anchor,所以feature map上的每个点都会在原图上对应点的位置生成9个anchor,而我们从上边的分析已经得知feature map的大小为输入图片的1/16倍,所以最终一共有 m 16 × n 16 × 9 \frac{m}{16} \times \frac{n}{16} \times 9 16m×16n×9个anchor。
其中每行的4个值 ( x 1 , y 1 , x 2 , y 2 ) \left(x_{1}, y_{1}, x_{2}, y_{2}\right) (x1,y1,x2,y2)表示矩形左上和右下角点坐标。9个矩形共有3种形状,长宽比为大约为width:height ∈ { 1 : 1 , 1 : 2 , 2 : 1 } \in\{1: 1,1: 2,2: 1\} ∈{1:1,1:2,2:1}三种,如图6。实际上通过anchors就引入了检测中常用到的多尺度方法。
图6 anchors示意图
注:关于上面的anchors size,其实是根据检测图像设置的。在python demo中,会把任意大小的输入图像reshape成800x600(即图2中的M=800,N=600)。再回头来看anchors的大小,anchors中长宽1:2中最大为352x704,长宽2:1中最大736x384,基本是cover了800x600的各个尺度和形状。
那么这9个anchors是做什么的呢?借用Faster RCNN论文中的原图,如图7,遍历Conv layers计算获得的feature maps,为每一个点都配备这9种anchors作为初始的检测框。这样做获得检测框很不准确,不用担心,后面还有2次bounding box regression可以修正检测框位置。
图7
解释一下上面这张图的数字。
注意,在本文讲解中使用的VGG conv5 num_output=512,所以是512d,其他类似。
其实RPN最终就是在原图尺度上,设置了密密麻麻的候选Anchor。然后用cnn去判断哪些Anchor是里面有目标的positive anchor,哪些是没目标的negative anchor。所以,仅仅是个二分类而已!
那么Anchor一共有多少个?原图800x600,VGG下采样16倍,feature map每个点设置9个Anchor,所以: ceil ( 800 / 16 ) × ceil ( 600 / 16 ) × 9 = 50 × 38 × 9 = 17100 \operatorname{ceil}(800 / 16) \times \operatorname{ceil}(600 / 16) \times 9=50 \times 38 \times 9=17100 ceil(800/16)×ceil(600/16)×9=50×38×9=17100
其中ceil()表示向上取整,是因为VGG输出的feature map size= 50* 38。
图8 Gernerate Anchors
其实生成anchors和那个 3 ∗ 3 3*3 3∗3的卷积并没有太大关系,anchors的生成完全是由另一个函数生成的。所以那个 3 ∗ 3 3*3 3∗3的卷积就只是在之前输出的feature map上再做一次卷积。
这个softmax分类器起的作用只是一个初步的分类,并不是最终的,它的作用主要是来判断每个anchor对应的是foreground还是background,所以只是一个简单的二分类器。
一副MxN大小的矩阵送入Faster RCNN网络后,到RPN网络变为(M/16)x(N/16),不妨设 W=M/16,H=N/16。在进入reshape与softmax之前,先做了1x1卷积,如图9:
图9 RPN中判定positive/negative网络结构
该1x1卷积的caffe prototxt定义如下:
layer {
name: “rpn_cls_score”
type: “Convolution”
bottom: “rpn/output”
top: “rpn_cls_score”
convolution_param {
num_output: 18 # 2(positive/negative) * 9(anchors)
kernel_size: 1 pad: 0 stride: 1
}
}
可以看到其num_output=18,也就是经过该卷积的输出图像为WxHx18大小(注意第二章开头提到的卷积计算方式)。这也就刚好对应了feature maps每一个点都有9个anchors,同时每个anchors又有可能是positive和negative,所有这些信息都保存WxHx(9* 2)大小的矩阵。为何这样做?后面接softmax分类获得positive anchors,也就相当于初步提取了检测目标候选区域box(一般认为目标在positive anchors中)。
那么为何要在softmax前后都接一个reshape layer?其实只是为了便于softmax分类,至于具体原因这就要从caffe的实现形式说起了。在caffe基本数据结构blob中以如下形式保存数据:
blob=[batch_size, channel,height,width]
对应至上面的保存positive/negative anchors的矩阵,其在caffe blob中的存储形式为[1, 2x9, H, W]。而在softmax分类时需要进行positive/negative二分类,所以reshape layer会将其变为[1, 2, 9xH, W]大小,即单独“腾空”出来一个维度以便softmax分类,之后再reshape回复原状。贴一段caffe softmax_loss_layer.cpp的reshape函数的解释,非常精辟:
"Number of labels must match number of predictions; "
"e.g., if softmax axis == 1 and prediction shape is (N, C, H, W), "
"label count (number of labels) must be NHW, "
“with integer values in {0, 1, …, C-1}.”;
综上所述,RPN网络中利用anchors和softmax初步提取出positive anchors作为候选区域(另外也有实现用sigmoid代替softmax,原理类似)。
这个bounding box回归器也只是一个初步的回归,主要是来修正anchor的,同样也是将 3 ∗ 3 3*3 3∗3的卷积输出的feature map(在VGG16中,大小为 m 16 × n 16 × 512 \frac{m}{16} \times \frac{n}{16} \times 512 16m×16n×512输入到一个 1 ∗ 1 1*1 1∗1的卷积层中,然后得到输出的大小为 m 16 × n 16 × 36 \frac{m}{16} \times \frac{n}{16} \times 36 16m×16n×36。为什么是36呢,因为feature中每个点都有9个anchor,每个anchor又对应着4个位置的变换量,所以是36。
在了解bounding box regression后,再回头来看RPN网络第二条线路,如图12。
图12 RPN中的bbox reg
先来看一看上图11中1x1卷积的caffe prototxt定义:
layer {
name: “rpn_bbox_pred”
type: “Convolution”
bottom: “rpn/output”
top: “rpn_bbox_pred”
convolution_param {
num_output: 36 # 4 * 9(anchors)
kernel_size: 1 pad: 0 stride: 1
}
}
可以看到其 num_output=36,即经过该卷积输出图像为WxHx36,在caffe blob存储为[1, 4x9, H, W],这里相当于feature maps每个点都有9个anchors,每个anchors又都有4个用于回归的 [ d x ( A ) , d y ( A ) , d w ( A ) , d h ( A ) ] \left[d_{x}(A), d_{y}(A), d_{w}(A), d_{h}(A)\right] [dx(A),dy(A),dw(A),dh(A)]变换量。
回到图8,VGG输出 50 ∗ 38 ∗ 512 50 * 38 * 512 50∗38∗512的特征,对应设置 50 ∗ 38 ∗ k 50 * 38 * k 50∗38∗k个anchors,而RPN输出:
恰好满足RPN完成positive/negative分类+bounding box regression坐标回归.
Proposal Layer负责综合所有 [ d x ( A ) , d y ( A ) , d w ( A ) , d h ( A ) ] \left[d_{x}(A), d_{y}(A), d_{w}(A), d_{h}(A)\right] [dx(A),dy(A),dw(A),dh(A)]变换量和positive anchors,计算出精准的proposal,送入后续RoI Pooling Layer。还是先来看看Proposal Layer的caffe prototxt定义:
layer {
name: ‘proposal’
type: ‘Python’
bottom: ‘rpn_cls_prob_reshape’
bottom: ‘rpn_bbox_pred’
bottom: ‘im_info’
top: ‘rois’
python_param {
module: ‘rpn.proposal_layer’
layer: ‘ProposalLayer’
param_str: “‘feat_stride’: 16”
}
}
Proposal Layer有3个输入:
首先解释im_info。对于一副任意大小PxQ图像,传入Faster RCNN前首先reshape到固定MxN,im_info=[M, N, scale_factor]则保存了此次缩放的所有信息。然后经过Conv Layers,经过4次pooling变为WxH=(M/16)x(N/16)大小,其中feature_stride=16则保存了该信息,用于计算anchor偏移量。
图13
Proposal Layer forward(caffe layer的前传函数)按照以下顺序依次处理:
之后输出proposal=[x1, y1, x2, y2],注意,由于在第三步中将anchors映射回原图判断是否超出边界,所以这里输出的proposal是对应MxN输入图像尺度的,这点在后续网络中有用。另外我认为,严格意义上的检测应该到此就结束了,后续部分应该属于识别了。
RPN网络结构就介绍到这里,总结起来就是:
生成anchors -> softmax分类器提取positvie anchors -> bbox reg回归positive anchors -> Proposal Layer生成proposals
而RoI Pooling层则负责收集proposal,并计算出proposal feature maps,送入后续网络。从图2中可以看到Rol pooling层有2个输入:
先来看一个问题:对于传统的CNN(如AlexNet和VGG),当网络训练好后输入的图像尺寸必须是固定值,同时网络输出也是固定大小的vector or matrix。如果输入图像大小不定,这个问题就变得比较麻烦。有2种解决办法:
图14 crop与warp破坏图像原有结构信息
两种办法的示意图如图14,可以看到无论采取那种办法都不好,要么crop后破坏了图像的完整结构,要么warp破坏了图像原始形状信息。
回忆RPN网络生成的proposals的方法:对positive anchors进行bounding box regression,那么这样获得的proposals也是大小形状各不相同,即也存在上述问题。所以Faster R-CNN中提出了RoI Pooling解决这个问题。不过RoI Pooling确实是从Spatial Pyramid Pooling发展而来,但是限于篇幅这里略去不讲,有兴趣的读者可以自行查阅相关论文。
分析之前先来看看RoI Pooling Layer的caffe prototxt的定义:
layer {
name: “roi_pool5”
type: “ROIPooling”
bottom: “conv5_3”
bottom: “rois”
top: “pool5”
roi_pooling_param {
pooled_w: 7
pooled_h: 7
spatial_scale: 0.0625 # 1/16
}
}
其中有新参数pooled_w和pooled_h,另外一个参数spatial_scale认真阅读的读者肯定已经知道知道用途。RoI Pooling layer forward过程:
这样处理后,即使大小不同的proposal输出结果都是 7*7固定大小,实现了固定长度输出。
图15 proposal示意图
Classification部分利用已经获得的proposal feature maps,通过full connect层与softmax计算每个proposal具体属于那个类别(如人,车,电视等),输出cls_prob概率向量;同时再次利用bounding box regression获得每个proposal的位置偏移量bbox_pred,用于回归更加精确的目标检测框。Classification部分网络结构如图16。
图16 Classification部分网络结构图
从RoI Pooling获取到7x7=49大小的proposal feature maps后,送入后续网络,可以看到做了如下2件事:
这里来看看全连接层InnerProduct layers,简单的示意图如图17,
图17 全连接层示意图
其计算公式如下:
其中W和bias B都是预先训练好的,即大小是固定的,当然输入X和输出Y也就是固定大小。所以,这也就印证了之前Roi Pooling的必要性。
Faster R-CNN,可以大致分为两个部分,一个是RPN网络,另一个是Fast R-CNN网络,前者是一种候选框(proposal)的推荐算法,而后者则是在此基础上对框的位置和框内的物体的类别进行细致计算。(Faster R-CNN对Fast R-CNN的改进主要是候选框的生成,因此,可以认为是RPN与Fast R-CNN的结合)
不管是RPN还是Fast R-CNN网络,其网络结构一部分来自于pre-trained model的卷积层(下文简称model),另一部分则是他们各自特有的结构(有卷积和FC,下文简称unique)。
这里,我主要讲的就是训练过程。
小结一下,Faster R-CNN的训练,是在已经训练好的model(如VGG_CNN_M_1024,VGG,ZF)的基础上继续进行训练。实际中训练过程分为6个步骤:
可以看到训练过程类似于一种“迭代”的过程,不过只循环了2次。至于只循环了2次的原因是应为作者提到:“A similar alternating training can be run for more iterations, but we have observed negligible improvements”,即循环更多次没有提升了。接下来本章以上述6个步骤讲解训练过程。
图18 Faster RCNN训练步骤
在该步骤中,首先读取RBG提供的预训练好的model(本文使用VGG),开始迭代训练。来看看stage1_rpn_train.pt网络结构,如图19。
图19 stage1_rpn_train.pt(考虑图片大小,Conv Layers中所有的层都画在一起了,如红圈所示,后续图都如此处理)
与检测网络类似的是,依然使用Conv Layers提取feature maps。整个网络使用的Loss如下:
了解数学原理后,反过来看图18:
这样,公式与代码就完全对应了。特别需要注意的是,在训练和检测阶段生成和存储anchors的顺序完全一样,这样训练结果才能被用于检测!
在该步骤中,利用之前的RPN网络,获取proposal rois,同时获取positive softmax probability,如图20,然后将获取的信息保存在python pickle文件中。该网络本质上和检测中的RPN网络一样,没有什么区别。
图20 rpn_test.pt
读取之前保存的pickle文件,获取proposals与positive probability。从data层输入网络。然后:
回顾anchor生成步骤:首先生成9个base anchor,然后通过坐标偏移在50* 38大小的1/16 下采样FeatureMap每个点都放上这9个base anchor,就形成了50* 38* k个anhcors。至于这9个base anchor坐标是什么其实并不重要,不同代码实现也许不同。
显然这里面有一部分边缘anchors会超出图像边界,而真实中不会有超出图像的目标,所以会有clip anchor步骤。
VGG输出5038512的特征,对应设置 50 ∗ 38 ∗ k 50*38*k 50∗38∗k个anchors,而RPN输出 50 ∗ 38 ∗ 2 k 50*38*2k 50∗38∗2k的分类特征矩阵和 50 ∗ 38 ∗ 4 k 50*38*4k 50∗38∗4k的坐标回归特征矩阵。
其实在实现过程中,每个点的2k个分类特征与 4k回归特征,与k个anchor逐个对应即可,这实际是一种“人为设置的逻辑映射”。当然,也可以不这样设置,但是无论如何都需要保证在训练和测试过程中映射方式必须一致。
由于引入ROI Pooling,从原理上说Faster R-CNN确实能够检测任意大小的图片。但是由于在训练的时候需要使用大batch训练网络,而不同大小输入拼batch在实现的时候代码较为复杂,而且当时以Caffe为代表的第一代深度学习框架也不如Tensorflow和PyTorch灵活,所以作者选择了把输入图片resize到固定大小的800x600。这应该算是历史遗留问题。
【目标检测】Faster R-CNN
Faster R-CNN
原始图片中的ROI如何映射到到feature map?
一文读懂Faster RCNN