学习Mask RCNN之前,先了解Faster RCNN(讲的很详细的好文章)的思想,其特征提取采用ResNet-FPN的架构,另外多加了一个Mask预测分支。可见Mask RCNN综合了很多此前优秀的研究成果。Faster RCNN就不多说了,这里讲的很详细了。我们先看看ResNet-FPN。
ResNet FPN
多尺度检测在目标检测中变得越来越重要,对小目标的检测尤其如此。现在主流的目标检测方法很多都用到了多尺度的方法,包括最新的yolo v3。Feature Pyramid Network (FPN)则是一种精心设计的多尺度检测方法,FPN结构中包括自下而上,自上而下和横向连接三个部分,如下图所示。这种结构可以将各个层级的特征进行融合,使其同时具有强语义信息和强空间信息。
上图(a)中的方法即为常规的生成一张图片的多维度特征组合的经典方法。即对某一输入图片我们通过压缩或放大从而形成不同维度的图片作为模型输入,使用同一模型对这些不同维度的图片分别处理后,最终再将这些分别得到的特征(feature maps)组合起来就得到了我们想要的可反映多维度信息的特征集。此种方法缺点在于需要对同一图片在更改维度后输入处理多次,因此对计算机的算力及内存大小都有较高要求。
图(b)中的方法则只拿单一维度的图片做为输入,然后经CNN模型处理后,拿最终一层的feature maps作为最终的特征集。显然此种方法只能得到单一维度的信息。优点是计算简单,对计算机算力及内存大小都无过高需求。此方法为大多数R-CNN系列目标检测方法所用像R-CNN/Fast-RCNN/Faster-RCNN等。因此最终这些模型对小维度的目标检测性能不是很好。
图(c)中的方法同样是拿单一维度的图片做为输入,不过最终选取用于接下来分类或检测任务时的特征组合时,此方法不只选用了最后一层的high level feature maps,同样也会选用稍靠下的反映图片low level 信息的feature maps。然后将这些不同层次(反映不同level的图片信息)的特征简单合并起来(一般为concat处理),用于最终的特征组合输出。此方法可见于SSD当中。不过SSD在选取层特征时都选用了较高层次的网络。比如在它以VGG16作为主干网络的检测模型里面所选用的最低的Convolution的层为Conv4,这样一些具有更低级别信息的层特征像Conv2/Conv3就被它给漏掉了,于是它对更小维度的目标检测效果就不大好。
图(d)中的方法同图(c)中的方法有些类似,也是拿单一维度的图片作为输入,然后它会选取所有层的特征来处理然后再联合起来做为最终的特征输出组合。(作者在论文中拿Resnet为实例时并没选用Conv1层,那是为了算力及内存上的考虑,毕竟Conv1层的size还是比较大的,所包含的特征跟直接的图片像素信息也过于接近)。另外还对这些反映不同级别图片信息的各层自上向下进行了再处理以能更好地组合从而形成较好的特征表达(详细过程会在下面章节中进一步介绍)。而此方法正是我们本文中要讲的FPN CNN特征提取方法。
图(e)中的方法是采用类似自上而下和skip connection的方法,例如TDM, SharpMask, RED-Net, U-Net, 这些结构尽在会后一层输出预测。
FPN会使用CNN网络中每一层的信息来生成最后的表达特征组合。上图是它的基本架构。自下而上其实就是简单的特征提取过程,和传统的没有区别。自上而下是从最高层开始进行上采样,这里的上采样直接使用的是最近邻上采样,而不是使用反卷积操作,一方面简单,另外一方面可以减少训练参数。横向连接则是将上采样的结果和自底向上生成的相同大小的feature map进行融合。具体就是,对每一层经过一个conv 1x1操作(1x1卷积用于降低通道数),无激活函数操作,输出通道全部设置为相同的256通道,然后和上采样的feature map进行加和操作。在融合之后还会再采用3*3的卷积核对已经融合的特征进行处理,目的是消除上采样的混叠效应(aliasing effect)
从中我们能看到FPN会模型每个CNN层的特征输出进行处理以生成反映此维度信息的特征。而自上至下处理后所生成出的特征之间也有个关联关系,即上层high level的特征会影响下一层次的low level特征表达。最终所有的特征一起用来作为下一步的目标检测或类别分析等任务的输入。(BTW,和FPN一起出来的还有Google一个工作 Top-Down Modulation for Object Detection.)
RPN
FPN后产生特征金字塔会经过RPN,之后会产生很多region proposal。Region Proposal Network (RPN)是在Faster RCNN中为了解决上一代Fast RCNN的selective search耗时问题提出的。先来看一下Faster R-CNN是怎么做的。
首先使用共享的卷积层为全图提取特征,然后将得到的feature maps送入RPN,RPN生成待检测框(指定RoI的位置)并对RoI的包围框进行第一次修正。之后就是Fast R-CNN的架构了,RoI Pooling Layer根据RPN的输出在feature map上面选取每个RoI对应的特征,并将维度置为定值。最后,使用全连接层(FC Layer)对框进行分类,并且进行目标包围框的第二次修正。
要理解Mask R-CNN,只有先理解Faster R-CNN。这里附上一张Faster RCNN结构图(别人画的,我看着挺好,借来学习学习)
Faster R-CNN的结构主要分为三大部分,第一部分是共享的卷积层-backbone,第二部分是候选区域生成网络-RPN,第三部分是对候选区域进行分类的网络-classifier。RPN依靠一个在共享特征图上滑动的窗口,为每个位置生成9种预先设置好长宽比与面积的anchor。这9种初始anchor包含三种面积(128×128,256×256,512×512),每种面积又包含三种长宽比(1:1,1:2,2:1)。附上一张画烂的图。(详细的实现可以参考源码,这里不赘述)
在介绍classifier部分之前,我们先来看看RoI Pooling到底做了什么?
首先第一个问题是为什么需要RoI Pooling?答案是在Fast R-CNN中,特征被共享卷积层一次性提取。因此,对于每个RoI而言,需要从共享卷积层上摘取对应的特征,并且送入全连接层进行分类。因此,RoI Pooling主要做了两件事,第一件是为每个RoI选取对应的特征,第二件事是为了满足全连接层的输入需求,将每个RoI对应的特征的维度转化成某个定值。RoI Pooling示意图如下所示:
对于每一个RoI,RoI Pooling Layer将其对应的特征从共享卷积层上拿出来,并转化成一样的大小(6×6)。在RoI Pooling Layer之后,就是Fast R-CNN的分类器和RoI边框修正训练。分类器主要是分这个提取的RoI具体是什么类别(人,车,马等等),一共C+1类(包含一类背景)。RoI边框修正和RPN中的anchor边框修正原理一样,同样也是SmoothL1 Loss,值得注意的是,RoI边框修正也是对于非背景的RoI进行修正,对于类别标签为背景的RoI,则不进行RoI边框修正的参数训练。
ROI Pooling问题
问题1:
从输入图上的RoI到特征图上的RoI feature,RoI Pooling是直接通过四舍五入取整得到的结果。对于分割问题,RoI Pooling过后的得到的输出可能和原图像上的RoI对不上。如下图中蓝色部分表示包含了轿车主体的信息的方格,RoI Pooling Layer的四舍五入取整操作导致其进行了偏移。
问题2:
再将每个RoI对应的特征转化为固定大小的维度时,又采用了取整操作。在从RoI得到对应的特征图时,进行了问题1描述的取整,在得到特征图后,将RoI对应的特征图分成6×6块,然后直接从每块中找到最大值。在图中的例子中,比如原图上的的RoI大小是280×480,得到对应的特征图是18×30。将特征图分成6块,每块大小是3×5,然后在每一块中分别选择最大值放入6×6的对应区域中。在将特征图分块的时候,又用到了取整。
这种取整操作(在Mask R-CNN中被称为quantization)对RoI分类影响不大,可是对逐像素的预测目标是有害的,因为对每个RoI取得的特征并没有与RoI对齐。因此,Mask R-CNN对RoI Pooling做了改进并提出了RoI Align。
知乎上有个简单的解释,在Faster RCNN中,有两次整数化的过程:
- region proposal的xywh通常是小数,但是为了方便操作会把它整数化。
- 将整数化后的边界区域平均分割成 k x k 个单元,对每一个单元的边界进行整数化。
两次整数化的过程如下图所示:
RoI Align
RoI Align的主要创新点是:
- 针对问题1,不再进行取整操作。
- 针对问题2,使用双线性插值来更精确地找到每个块对应的特征。
在实际操作中,ROI Align并不是简单地补充出候选区域边界上的坐标点,然后进行池化,而是重新进行设计。总的来说,RoI Align的作用主要就是剔除了RoI Pooling的取整操作,并且使得为每个RoI取得的特征能够更好地对齐原图上的RoI区域。
下面通过一个例子来讲解ROI Align操作。如下图所示,虚线部分表示feature map,实线表示ROI,这里将ROI切分成2x2的单元格。如果采样点数是4,那我们首先将每个单元格子均分成四个小方格(如红色线所示),每个小方格中心就是采样点。这些采样点的坐标通常是浮点数,所以需要对采样点像素进行双线性插值(如四个箭头所示),就可以得到该像素点的值了。然后对每个单元格内的四个采样点进行maxpooling,就可以得到最终的ROIAlign的结果。
需要说明的是,在相关实验中,作者发现将采样点设为4会获得最佳性能,甚至直接设为1在性能上也相差无几。事实上,ROI Align 在遍历取样点的数量上没有ROIPooling那么多,但却可以获得更好的性能,这主要归功于解决了misalignment的问题。
到此,Mask RCNN 结构就介绍差不多了。这里附上整体结构图。注意的是,在Mask R-CNN中的RoI Align之后有一个"head"部分,主要作用是将RoI Align的输出维度扩大,这样在预测Mask时会更加精确。
在Mask Branch的训练环节,作者没有采用FCN式的SoftmaxLoss,反而是输出了K个Mask预测图(为每一个类都输出一张),并采用average binary cross-entropy loss训练,当然在训练Mask branch的时候,输出的K个特征图中,也只是对应ground truth类别的那一个特征图对Mask loss有贡献。也就是说,Mask RCNN定义多任务损失为
与与Faster RCNN没区别。中,假设一共有K个类别,则mask分割分支的输出维度是 ,对于中的每个点,都会输出K个二值Mask(每个类别使用sigmoid输出)。需要注意的是,计算loss的时候,并不是每个类别的sigmoid输出都计算二值交叉熵损失,而是该像素属于哪个类,哪个类的sigmoid输出才要计算损失(如图红色方形所示)。并且在测试的时候,我们是通过分类分支预测的类别来选择相应的mask预测。这样,mask预测和分类预测就彻底解耦了。
[参考链接]
Github:https://github.com/matterport/Mask_RCNN
Paper:https://arxiv.org/abs/1703.06870
https://zhuanlan.zhihu.com/p/37998710
https://blog.csdn.net/heavenpeien/article/details/80534963
https://blog.csdn.net/wangdongwei0/article/details/83110305
https://blog.athelas.com/a-brief-history-of-cnns-in-image-segmentation-from-r-cnn-to-mask-r-cnn-34ea83205de4
https://towardsdatascience.com/review-fpn-feature-pyramid-network-object-detection-262fc7482610