做一些比较有条理的梳理,尽管网上已经有很多梳理成文的博客,不过静心沉气的理解一下也是很有必要的。方便日后在脑海里形成比较有条理的知识系统。
先后经历了RCNN(region proposals +CNN)—> SPP net—> Fast RCNN—> Faster RCNN —>Mask RCNN
RCNN的整体过程如上图所示,首先是通过选择性搜索算法SS(selective serch)算法在输入图像中执行图像分割算法,在不同的色块上生成可靠的候选区域(region proposals)。大致生成2000个候选区域,然后将每个候选区域缩放成227×227的大小输入到CNN特征提取网络中,对每个候选区域的特征输入到SVM(二分类)进行分类,每个类别分别对应一个SVM分类器。接着是对图像特征进行边框回归,对区域的边界信息(x,y,h,w)进行调整。最后对每个候选区域都对应的输出检测目标的可信度,再通过非极大抑制NMS选取可能性最大的候选区域。
目标检测问题的衡量标准是重叠面积:许多分类准确的检测结果,往往因为候选框不够准确,导致重叠面积很小。故需要一个边框调整步骤。 判定为本类的候选框需要和真实矩形框重叠面积大于0.7才行。 回归器对每一类目标,使用一个线性回归器进行调整,输入为深度网络pool5层的4096维特征,输出为x, y方向的缩放和平移。
传统的目标检测算法是使用滑动窗口,每滑一个窗口检测一次,相邻窗口信息重叠高,检测速度慢。R-CNN使用传统启发式方法来解决滑动窗口问题。步骤:
主要的改进在于添加了一个金字塔池化层,在CNN卷积操作时,只需要一次卷积即可。很可啊。。因为对于R-CNN来说对2000个region proposals都需要进行卷积操作是很冗余和耗时的
改进的思路是:
金字塔池化操作如下:
- 2000个对应的小特征图分被输入三种不同大小网格划分的池化核(4*4,2*2,1*1)
- 每个网格进行的都是最大池化操作,不管输入特征图的长度是多少,经过池化层后都得到16+4+1=21个值
- 所以经过SPP金字塔池化后的尺寸都为256*21的特征向量,将这个作为FC的输入进行后续操作。
由于R-CNN的FC需要固定输入的维度,所以候选区域需要先被crop或者warp到固定尺寸,在很大程度上丢失和更改了图片的原有信息导致训练的效果不好;SPP的优点在于可以直接将图像输入到CNN,在输入FC前通过SPP,SPP可以将任意尺度映射为固定维度输出,再输入到FC中,所以其一可以减少计算量,其二可以打破固定尺寸这一限制。如下图所示:
这里做一下为什么可以固定输出维度,因为池化的参数ker_size和stride都是可选参数,因此当固定了输入的feture map和输出的维度大小后,可以直接根据对应的计算出pool_size和 stride.
上述的R-CNN和 SPP-net存在的统一问题在于训练pipeline的时候是隔离的,即是:提取region proposal,训练CNN,SVM,Bbox regression。
一个具体的网络
-
从上面两个图可以看出输入的是原图和在原图上提取到的ROI 5 ∗ P 5*P 5∗P,其中这里的5个量分别表示一个类别加上4个BBOX的几何参数,ROI的产生还是使用的是SS。
这里我们提出两个问题:
上图的操作中我们可以看到只有原图经历了CNN的特征提取,我们要如何通过ROIs去定位到对应的原图的feature map上去呢?
找到后,不同维度上的ROIs对应的特征区域的维度不一样,我们如何转化为固定额维度特征向量?
解答上面的两个问题
与SPP的目的相同,都是把不同尺度的ROI映射为固定大小的特征。ROI就是特殊的SPP,它不考虑多尺度空间,只使用单个尺度。如下:
具体实现可以看做是针对ROI区域的普通整个图像feature map的Pooling,只不过不是固定尺寸的输入,因此每次的pooling网格大小需要计算。假设某个ROI区域的坐标为 ( x 1 , y 1 , x 2 , y 2 ) (x_1,y_1,x_2,y_2) (x1,y1,x2,y2),那么呢输入的size就应该是 ( y 2 − y 1 ) × ( x 2 − x 1 ) (y_2-y_1)\times(x_2-x_1) (y2−y1)×(x2−x1),假设需要的pooling的size为 p o o l e d h e i g h t × p o o l e d w i d t h pooledheight\times{pooledwidth} pooledheight×pooledwidth,那么每个网格的size就是 ( y 2 − y 1 ) / p o o l e d h e i g h t × ( x 2 − x 1 ) / p o o l e d w i d t h {(y_2-y_1)/pooledheight}\times{(x_2-x_1)/pooledwidth} (y2−y1)/pooledheight×(x2−x1)/pooledwidth
整个过程是先用SS等proposals提取一组bbox坐标,而后输入网络对每一个bbox所包含的对象进行预测,此时,神经网络也依然仅仅是一个图片分类工具而已,只不过是ROI区域的图像分类;然后作者进一步把bbox放入网络进行优化。因此,在Fast-rcnn中存在两个输出层,第一个是针对每个ROI的分类概率,第二个是针对每个ROI区域坐标的坐标偏移优化 ( t x k , t y k , t w k , t h k ) (t_x^k,t_y^k,t_w^k,t_h^k) (txk,tyk,twk,thk).具体的两个Loss的操作过程可以看下面我盗的图:
假设对于一个类别 k ∗ k^* k∗,在图片中标注了一个ground_truth坐标: t ∗ = ( t x ∗ , t y ∗ , t w ∗ , t h ∗ ) t^*=(t_x^*,t_y^*,t_w^*,t_h^*) t∗=(tx∗,ty∗,tw∗,th∗),而预测值为 t = ( t x , t y , t w , t h ) t=(t_x,t_y,t_w,t_h) t=(tx,ty,tw,th).因此定义Loss:
where the s m o o t h L 1 ( x ) smooth_L{_1}(x) smoothL1(x) denotes t i − t i ∗ t_i-t_i^* ti−ti∗,即是对应坐标的差距。该函数在(-1,1)之间为二次函数,其他地方为线性的,这样可以做啥?可以增强对异常数据的鲁棒性。
参考知乎的大佬回答一文读懂Faster RCNN
Faster RCNN Conv layers中对所有的卷积都做了扩边处理( pad=1,即填充一圈0),导致原图变为 (M+2)x(N+2)大小,再做3x3卷积后输出MxN 。正是这种设置,导致Conv layers中的conv层不改变输入和输出矩阵大小。如下图:
类似的是,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的巨大优势,能极大提升检测框的生成速度。
RPN网络实际分为2条线,上面一条通过softmax分类anchors获得positive和negative分类,下面一条用于计算对于anchors的bounding box regression偏移量,以获得精确的proposal。而最后的Proposal层则负责综合positive anchors和对应bounding box regression偏移量获取proposals,同时剔除太小和超出边界的proposals。其实整个网络到了Proposal Layer这里,就完成了相当于目标定位的功能。
anchors,实际上就是一组由矩形.9个矩形共有3种形状,长宽比为大约为 w i d t h : h e i g h t = { 1 : 1 , 1 : 2 , 2 : 1 } width:height={\{ 1:1,1:2,2:1\}{}} width:height={1:1,1:2,2:1}三种,如下图。实际上通过anchors就引入了检测中常用到的多尺度方法。
那么这9个anchors是做什么的呢?借用Faster RCNN论文中的原图,如下图,遍历Conv layers计算获得的feature maps,为每一个点都配备这9种anchors作为初始的检测框。这样做获得检测框很不准确,不用担心,后面还有2次bounding box regression可以修正检测框位置。
上图解释如下:
一副MxN大小的矩阵送入Faster RCNN网络后,到RPN网络变为(M/16)x(N/16),不妨设 W=M/16,H=N/16。在进入reshape与softmax之前,先做了1x1卷积,如下图:
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 N*H*W, "
"with integer values in {0, 1, ..., C-1}.";
上面的意思就是说在caffe的channel通道中必须要和label的种类数量一致。
在了解bounding box regression后,再回头来看RPN网络第二条线路,如图:
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 ) ] [d_x(A),d_y(A),d_w(A),d_h(A)] [dx(A),dy(A),dw(A),dh(A)]的变换量。
VGG输出 50 ∗ 38 ∗ 512 50*38*512 50∗38∗512 的特征,对应anchors设置 50 ∗ 38 ∗ k 50*38*k 50∗38∗k个anchors,而RPN输出:
Proposal Layer负责综合所有回归尺度变换量和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个输入:positive vs negative anchors分类器结果rpn_cls_prob_reshape,对应的bbox reg的变换量rpn_bbox_pred,以及im_info;另外还有参数feat_stride=16,这和图4是对应的。
首先解释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偏移量。
Proposal Layer forward(caffe layer的前传函数)按照以下顺序依次处理:
h和前文中fast-rcnn一致。
Classification部分利用已经获得的proposal feature maps,通过full connect层与softmax计算每个proposal具体属于那个类别(如人,车,电视等),输出cls_prob概率向量;同时再次利用bounding box regression获得每个proposal的位置偏移量bbox_pred,用于回归更加精确的目标检测框。Classification部分网络结构如下图。
从RoI Pooling获取到7x7=49大小的proposal feature maps后,送入后续网络,可以看到做了如下2件事:
前文已经介绍了fast-RCNN的具体改进,而把fast-RCNN和FPN结合就是对应的Faster-RCNN算法了,这里我们先介绍FPN。
FPN(Feature Pyramid Network)是一种精心设计的多尺度检测算法。主要用于目标检测中的多尺度问题,对于图像中较小和较大的物体在卷积层的不同feature特征进行检测。我们知道网络的感受野和stride通常是很矛盾的,因为一般网络结构的对应stride都是比较大的,而图像中的小物体可能比stride还小,为解决这个问题,传统上采用的方法包括:
结合上文中所所说的两点解决办法,我们看出存在问题为:
针对上述问题,FPN(上图(d))所示的过程,在原来的网络上做修改:每个分辨率的feature map引入后一分辨率缩放两倍的feature map做element-wise相加操作。如此,每层分辨率的feature map都融合了不同分辨率和不同语义轻度的特征,融合后的不同分辨率的feature map 分别做对应的物体检测,这样就保留了每一层都具有合适的分辨率以及强语义特征。同时,计算量也小。
FPN结构中包含自下而上,自上而下和横向连接三个部分,如上图所示,这个结构可以把各个层级进行特征融合。FPN是一种通用的框架,可以结合各种骨架网络使用,例如VGG和resnet,在Mask RCNN文章中使用了ResNNet-FPN网络结构,如下图:
上图的Resent-PFN包括有三部分,自下而上连接,自上而下连接以及横向连接。
和普通的特征提取没有区别,就是这个是采用的Resent网络特征结构,根据feature map 的分辨率可以得到5个stage,stage2,stage3,stage4和stage5各自最后一层输出conv2,conv3,conv4和conv5分别定义为 C 2 , C 3 , C 4 , C 5 C_2,C_3,C_4,C_5 C2,C3,C4,C5,他们相对于原始图片的stride是{4,8,16,32}。需要注意的是,考虑到内存原因,stage1的conv1并没有使用。
自上而下是从最高层开始进行上采样,这里的上采样直接使用的是最近邻上采样,而不是使用反卷积操作,一方面简单,另外一方面可以减少训练参数。(何为反卷积?如何训练参数)
横向连接则是将上采样的结果和自底向上生成的相同大小的feature map进行融合。具体就是对 C 1 , C 2 , C 3 , C 4 C_1,C_2,C_3,C_4 C1,C2,C3,C4中的每一层经过一个conv 1x1操作**(1x1卷积用于降低通道数)**,无激活函数操作,输出通道全部设置为相同的256通道,然后和上采样的feature map进行加和操作,在融合之后还会再采用3*3的卷积核对已经融合的特征进行处理,目的是消除上采样的混叠效应(aliasing effect)。
总结一下,ResNet-FPN作为RPN输入的feature map是 P 2 , P 3 , P 4 , P 5 , P 6 P2,P3,P4,P5,P6 P2,P3,P4,P5,P6,而作为后续Fast RCNN的输入则是 P 2 , P 3 , P 4 , P 5 P2,P3,P4,P5 P2,P3,P4,P5 。
将ResNet-FPN和Fast RCNN进行结合,实际上就是Faster RCNN的了,但与最初的Faster RCNN不同的是,FPN产生了特征金字塔 [ P 2 , P 3 , P 4 , P 5 , P 6 ] [P2,P3,P4,P5,P6] [P2,P3,P4,P5,P6]而并非只是一个feature map。金字塔经过RPN之后会产生很多region proposal,这些region proposal是分别由 [ P 2 , P 3 , P 4 , P 5 , P 6 ] [P2,P3,P4,P5,P6] [P2,P3,P4,P5,P6]经过RPN产生的,但用于输入到Fast RCNN中的是 [ P 2 , P 3 , P 4 , P 5 ] [P2,P3,P4,P5] [P2,P3,P4,P5] ,也就是说要在 [ P 2 , P 3 , P 4 , P 5 ] [P2,P3,P4,P5] [P2,P3,P4,P5]中根据region proposal切出ROI进行后续的分类和回归预测。**问题来了,我们要选择哪个feature map来切出这些ROI区域呢?**实际上,我们会选择最合适的尺度的feature map来切ROI。具体来说,我们通过一个公式来决定宽w和高h的ROI到底要从哪个[ P K P_K PK来切:
这里224表示用于预训练的ImageNet图片的大小, k 0 k_0 k0表示面积为 224 ∗ 224 224*224 224∗224的ROI所应该在的层级。作者将 k 0 k_0 k0设置为4,也就是说 w ∗ h = 224 ∗ 224 w*h=224*224 w∗h=224∗224的ROI应该从 P 4 P_4 P4 中切出来。假设ROI的scale小于224(比如说是112 * 112), k = k 0 − 1 = 3 k=k_0 -1=3 k=k0−1=3,就意味着要从更高分辨率的 P 3 P_3 P3 中产生。另外, k k k值会做取整处理,防止结果不是整数。
这种做法很合理,大尺度的ROI要从低分辨率的feature map上切,有利于检测大目标,小尺度的ROI要从高分辨率的feature map上切,有利于检测小目标。
我们再进一步,将ResNet-FPN+Fast RCNN+mask,则得到了最终的Mask RCNN,如下图:
Mask RCNN的构建很简单,只是在ROI pooling(实际上用到的是ROIAlign,后面会讲到)之后添加卷积层,进行mask预测的任务。
下面总结一下Mask RCNN的网络:
实际上,Mask RCNN中还有一个很重要的改进,就是ROIAlign。Faster R-CNN存在的问题是:特征图与原始图像是不对准的(mis-alignment),所以会影响检测精度。而Mask R-CNN提出了RoIAlign的方法来取代ROI pooling,RoIAlign可以保留大致的空间位置。为了讲清楚ROI Align,这里先插入两个知识,双线性插值和ROI pooling。
双线性插值本质上就是在两个方向上做线性插值。
如图,假设我们想得到P点的插值,我们可以先在x方向上,对 Q 1 1 Q_11 Q11和 Q 2 1 Q_21 Q21之间做线性插值得到 R 1 R_1 R1 , R 2 R_2 R2 同理可得。然后在y方向上对 R 1 R_1 R1和 R 2 R_2 R2进行线性插值就可以得到最终的 P P P。
这就是fast-rcnn的了。不过多解释。
下面回到ROI Align当中。
在Faster RCNN中,有两次整数化的过程:
两整数化的过程如上图所示。事实上,经过上述两次整数化,此时的候选框已经和最开始回归出来的位置有一定的偏差,这个偏差会影响检测或者分割的准确度。在论文里,作者把它总结为“不匹配问题”(misalignment)。为了解决这个问题,ROI Align方法取消整数化操作,保留了小数,使用以上介绍的双线性插值的方法获得坐标为浮点数的像素点上的图像数值。但在实际操作中,ROI Align并不是简单地补充出候选区域边界上的坐标点,然后进行池化,而是重新进行设计。
下面通过一个例子来讲解ROI Align操作。如下图所示,虚线部分表示feature map,实线表示ROI,这里将ROI切分成2x2的单元格。如果采样点数是4,那我们首先将每个单元格子均分成四个小方格(如红色线所示),每个小方格中心就是采样点。这些采样点的坐标通常是浮点数,所以需要对采样点像素进行双线性插值(如四个箭头所示),就可以得到该像素点的值了。然后对每个单元格内的四个采样点进行maxpooling,就可以得到最终的ROIAlign的结果。
Mask RCNN定义多任务损失:
L = L c l s + L b o x + L m a s k L=L_{cls}+L_{box}+L{mask} L=Lcls+Lbox+Lmask