实例分割--Mask RCNN详解(ROI Align / Loss Fun)

目标检测、语义分割与实例分割:

实例分割是一种在像素层面识别目标轮廓的任务,相比其他相关任务,实例分割是较难解决的计算机视觉任务之一:

实例分割--Mask RCNN详解(ROI Align / Loss Fun)_第1张图片

分类:这张图像中有一个气球。

语义分割:这些全是气球像素。

目标检测:这张图像中的这些位置上有 7 个气球。

实例分割:这些位置上有 7 个气球,并且这些像素分别属于每个气球。


1 总结架构与主要思想

总体架构

Mask-RCNN 大体框架还是 Faster-RCNN 的框架,可以说在基础特征网络之后又加入了全连接的分割子网,由原来的两个任务(分类+回归)变为了三个任务(分类+回归+分割)。Mask R-CNN 是一个两阶段的框架,第一个阶段扫描图像并生成提议(proposals,即有可能包含一个目标的区域),第二阶段分类提议并生成边界框和掩码。

实例分割--Mask RCNN详解(ROI Align / Loss Fun)_第2张图片

其中黑色部分为原来的 Faster-RCNN,红色部分为在 Faster网络上的修改,总体流程如下:

1)输入图像; 
2)将整张图片输入CNN,进行特征提取; 
3)用FPN生成建议窗口(proposals),每张图片生成N个建议窗口; 
4)把建议窗口映射到CNN的最后一层卷积feature map上; 
5)通过RoI Align层使每个RoI生成固定尺寸的feature map; 

6)最后利用全连接分类,边框,mask进行回归。

另一系统图:

实例分割--Mask RCNN详解(ROI Align / Loss Fun)_第3张图片

实例分割--Mask RCNN详解(ROI Align / Loss Fun)_第4张图片

首先对图片做检测,找出图像中的ROI,对每一个ROI使用ROIAlign进行像素校正,然后对每一个ROI使用设计的FCN框架进行预测不同的实例所属分类,最终得到图像实例分割结果。 

与faster RCNN的区别: 
1)使用ResNet101网络 
2)将 Roi Pooling 层替换成了 RoiAlign; 
3)添加并列的 Mask 层; 

4)由RPN网络转变成FPN网络 

主要改进点:

1. 基础网络的增强,ResNeXt-101+FPN的组合可以说是现在特征学习的王牌了;

2. 分割 loss 的改进,由原来的 FCIS 的 基于单像素softmax的多项式交叉熵变为了基于单像素sigmod二值交叉熵。softmax会产生FCIS的 ROI inside map与ROI outside map的竞争。但文章作者确实写到了类间的竞争, 二值交叉熵会使得每一类的 mask 不相互竞争,而不是和其他类别的 mask 比较 ;

3.  ROIAlign解决Misalignment 的问题,说白了就是对 feature map 的插值。直接的ROIPooling的那种量化操作会使得得到的mask与实际物体位置有一个微小偏移,个人感觉这个没什么 insight,就是工程上更好的实现方式。

说明:这么好的效果是由多个阶段的优化实现的,大头的提升还是由数据和基础网络的提升:多任务训练带来的好处其实可以看作是更多的数据带来的好处;FPN 的特征金字塔,ResNeXt更强大的特征表达能力都是基础网络。

其中:

残差网络ResNet参见:残差网络resnet详解

RPN网络参见:目标检测--FPN解析

 Mask-RCNN 的几个特点(来自于 Paper 的 Abstract):

1)在边框识别的基础上添加分支网络,用于语义Mask 识别;
2)训练简单,相对于 Faster 仅增加一个小的 Overhead,可以跑到 5FPS;
3)可以方便的扩展到其他任务,比如人的姿态估计等;
4)不借助 Trick,在每个任务上,效果优于目前所有的 single-model entries,包括 COCO 2016 的Winners。

2 ROI Align

ROI Align 很好地解决了ROI Pooling操作中两次量化造成的区域不匹配(mis-alignment)的问题。实验显示,在检测测任务中将 ROI Pooling 替换为 ROI Align 可以提升检测模型的准确性。

2.1 ROI Pooling

在faster rcnn中,anchors经过proposal layer升级为proposal,需要经过ROI Pooling进行size的归一化后才能进入全连接网络,也就是说ROI Pooling的主要作用是将proposal调整到统一大小。步骤如下:

  1. 将proposal映射到feature map对应位置
  2. 将映射后的区域划分为相同大小的sections
  3. 对每个sections进行max pooling/avg pooling操作

举例说明:

考虑一个8*8大小的feature map,经过一个ROI Pooling,以及输出大小为2*2. 
实例分割--Mask RCNN详解(ROI Align / Loss Fun)_第5张图片

1)输入的固定大小的feature map (图一) 
2)region proposal 投影之后位置(左上角,右下角坐标):(0,4)?,(4,4)(图二) 
3)将其划分为(2*2)个sections(因为输出大小为2*2),我们可以得到(图三) ,不整除时错位对齐(Fast RCNN)

4)对每个section做max pooling,可以得到(图四)

2.2 ROI Pooling 的局限性分析

在常见的两级检测框架(比如Fast-RCNN,Faster-RCNN,RFCN)中,ROI Pooling 的作用是根据预选框的位置坐标在特征图中将相应区域池化为固定尺寸的特征图,以便进行后续的分类和包围框回归操作。由于预选框的位置通常是由模型回归得到的,一般来讲是浮点数,而池化后的特征图要求尺寸固定。故ROI Pooling这一操作存在两次量化的过程。

  • 将候选框边界量化为整数点坐标值。从roi proposal到feature map的映射时,取[x/16],这里x是原始roi的坐标值,而方框代表四舍五入。 
  • 将量化后的边界区域平均分割成 k x k 个单元(bin), 对每一个单元的边界进行量化,每个bin使用max pooling

事实上,经过上述两次量化,此时的候选框已经和最开始回归出来的位置有一定的偏差,这个偏差会影响检测或者分割的准确度。在论文里,作者把它总结为“不匹配问题(misalignment)。

下面我们用直观的例子具体分析一下上述区域不匹配问题。如 图1 所示,这是一个Faster-RCNN检测框架。输入一张800*800的图片,图片上有一个665*665的包围框(框着一只狗)。图片经过主干网络提取特征后,特征图缩放步长(stride)为32。因此,图像和包围框的边长都是输入时的1/32。800正好可以被32整除变为25。但665除以32以后得到20.78,带有小数,于是ROI Pooling 直接将它量化成20

接下来需要把框内的特征池化7*7的大小,因此将上述包围框平均分割成7*7个矩形区域。显然,每个矩形区域的边长为2.86,又含有小数。于是ROI Pooling 再次把它量化到2。经过这两次量化,候选区域已经出现了较明显的偏差(如图中绿色部分所示)。更重要的是,该层特征图上0.1个像素的偏差,缩放到原图就是3.2个像素。那么0.8的偏差,在原图上就是接近30个像素点的差别,这一差别不容小觑。

图 1 (感觉第二次量化画错了,根据ross的源码,不是缩小了,而是部分bin大小和步长发生变化) 

实例分割--Mask RCNN详解(ROI Align / Loss Fun)_第6张图片

简而言之:

做segment是pixel级别的,但是faster rcnn中roi pooling有2次量化操作导致了没有对齐 
实例分割--Mask RCNN详解(ROI Align / Loss Fun)_第7张图片

2.3 ROI Align 的主要思想和具体方法

为了解决ROI Pooling的上述缺点,作者提出了ROI Align这一改进的方法(如图2)。ROI Align的思路很简单:取消量化操作,使用双线性内插的方法获得坐标为浮点数的像素点上的图像数值,从而将整个特征聚集过程转化为一个连续的操作。值得注意的是,在具体的算法操作上,ROI Align并不是简单地补充出候选区域边界上的坐标点,然后将这些坐标点进行池化,而是重新设计了一套比较优雅的流程,如图3 所示:

  • 遍历每一个候选区域,保持浮点数边界不做量化。
  • 将候选区域分割成k x k个单元,每个单元的边界也不做量化。
  • 在每个单元中计算固定四个坐标位置,用双线性内插的方法计算出这四个位置的值,然后进行最大池化操作。

这里对上述步骤的第三点作一些说明:这个固定位置是指在每一个矩形单元(bin)中按照固定规则确定的位置。比如,如果采样点数是1,那么就是这个单元的中心点。如果采样点数是4,那么就是把这个单元平均分割成四个小方块以后它们分别的中心点。显然这些采样点的坐标通常是浮点数,所以需要使用插值的方法得到它的像素值。在相关实验中,作者发现将采样点设为4会获得最佳性能,甚至直接设为1在性能上也相差无几。

事实上,ROI Align 在遍历取样点的数量上没有ROIPooling那么多,但却可以获得更好的性能,这主要归功于解决了misalignment的问题。值得一提的是,我在实验时发现,ROI Align在VOC2007数据集上的提升效果并不如在COCO上明显。经过分析,造成这种区别的原因是COCO上小目标的数量更多,而小目标受misalignment问题的影响更大(比如,同样是0.5个像素点的偏差,对于较大的目标而言显得微不足道,但是对于小目标,误差的影响就要高很多)。

图 2 
实例分割--Mask RCNN详解(ROI Align / Loss Fun)_第8张图片 
图 3 

实例分割--Mask RCNN详解(ROI Align / Loss Fun)_第9张图片

下面插图更加细致地描述roialign:

实例分割--Mask RCNN详解(ROI Align / Loss Fun)_第10张图片 
实例分割--Mask RCNN详解(ROI Align / Loss Fun)_第11张图片 
实例分割--Mask RCNN详解(ROI Align / Loss Fun)_第12张图片 
实例分割--Mask RCNN详解(ROI Align / Loss Fun)_第13张图片 
实例分割--Mask RCNN详解(ROI Align / Loss Fun)_第14张图片

2.4 ROI Align 的反向传播

常规的ROI Pooling的反向传播公式如下:

图片标题

这里,xi代表池化前特征图上的像素点;yrj代表池化后的第r个候选区域的第j个点;i*(r,j)代表点yrj像素值的来源(最大池化的时候选出的最大像素值所在点的坐标)。由上式可以看出,只有当池化后某一个点的像素值在池化过程中采用了当前点Xi的像素值(即满足i=i*(r,j)),才在xi处回传梯度。

类比于ROIPooling,ROIAlign的反向传播需要作出稍许修改:首先,在ROIAlign中,xi*(r,j)是一个浮点数的坐标位置(前向传播时计算出来的采样点),在池化前的特征图中,每一个与 xi*(r,j) 横纵坐标均小于1的点都应该接受与此对应的点yrj回传的梯度,故ROI Align 的反向传播公式如下: 
   
图片标题

上式中,d(.)表示两点之间的距离,Δh和Δw表示 xi 与 xi*(r,j) 横纵坐标的差值,这里作为双线性内插的系数乘在原始的梯度上。

roi-align总结:对于每个roi,映射之后坐标保持浮点数,在此基础上再平均切分成k*k个bin,这个时候也保持浮点数。再把每个bin平均分成4个小的空间(bin中更小的bin),然后计算每个更小的bin的中心点的像素点对应的概率值。这个像素点大概率是一个浮点数,实际上图像的浮点是没有像素值的,但这里假设这个浮点数的位置存储一个概率值,这个值由相邻最近的整数像素点存储的概率值经过双线性插值得到,其实也就是根据这个中心点所在的像素值找到所在的大bin对应的4个整数像素存储的值,然后乘以多个参数进行插值。这些参数其实就是那4个整数像素点和中心点的位置距离关系构成参数。最后再在每个大bin中对4个中心点进行max或者mean的pooling。

2.5 ROI Pooling 、ROI Align和RoIWarp

下图对比了三种方法的不同,其中roiwarp来自:J. Dai, K. He, and J. Sun. Instance-aware semantic segmentation via multi-task network cascades。RoIWarp第一次量化了,第二次没有,RoIAlign两次都没有量化 。

实例分割--Mask RCNN详解(ROI Align / Loss Fun)_第15张图片

2.6 实例

输出7*7的fix featrue:

  1. 划分7*7的bin(我们可以直接精确的映射到feature map来划分bin,不用第一次量化) 
    实例分割--Mask RCNN详解(ROI Align / Loss Fun)_第16张图片

  2. 每个bin中采样4个点,双线性插值 
    实例分割--Mask RCNN详解(ROI Align / Loss Fun)_第17张图片

  3. 对每个bin4个点做max或average pooling

 
  
# pytorch
# 这是pytorch做法先采样到14*14,然后max pooling到7*7
pre_pool_size = cfg.POOLING_SIZE * 2
grid = F.affine_grid(theta, torch.Size((rois.size(0), 1, pre_pool_size, pre_pool_size)))
crops = F.grid_sample(bottom.expand(rois.size(0), bottom.size(1), bottom.size(2), bottom.size(3)), grid, mode=mode)
crops = F.max_pool2d(crops, 2, 2)
# tensorflow
pooled.append(tf.image.crop_and_resize(
                feature_maps[i], level_boxes, box_indices, self.pool_shape,
                method="bilinear"))


2.7 更多

前面我们介绍RoI Align是在每个bin中采样4个点,双线性插值,但也是一定程度上解读了mismatch问题,而旷视科技PLACES instance segmentation比赛中所用的是更精确的解决这个问题,对于每个bin,RoIAlign只用了4个值求平均,而旷视则直接利用积分(把bin中所有位置都插值出来)求和出这一块的像素值和然后求平均,这样更精确了但是很费时。

实例分割--Mask RCNN详解(ROI Align / Loss Fun)_第18张图片

实例分割--Mask RCNN详解(ROI Align / Loss Fun)_第19张图片

来源旷视科技peng chao分享的video和slides

(部分转自: 详解 ROI Align 的基本原理和实现细节, mask rcnn解读)

3 损失函数

介绍一下网络使用的损失函数为分类误差+检测误差+分割误差 
这里写图片描述

分类误差和检测误差在farster R-CNN当中已经介绍过了,参看前面文章。

分割误差为新的东西,对于每一个ROI,mask分支定义一个K*m*2维的矩阵表示K个不同的分类对于每一个m*m的区域,对于每一个类都有一个。对于每一个像素,都是用sigmod函数进行求相对熵,得到平均相对熵误差Lmask。对于每一个ROI,如果检测得到ROI属于哪一个分类,就只使用哪一个分支的相对熵误差作为误差值进行计算。(举例说明:分类有3类(猫,狗,人),检测得到当前ROI属于“人”这一类,那么所使用的Lmask为“人”这一分支的mask。)这样的定义使得我们的网络不需要去区分每一个像素属于哪一类,只需要去区别在这个类当中的不同分别小类。最后可以通过与阈值0.5作比较输出二值mask。这样避免了类间的竞争,将分类的任务交给专业的classification分支。

而Lmask对于每一个像素使用二值的sigmoid交叉熵损失。

参考theano的文档,二值的交叉熵定义如下: 这里的o就是sigmoid输出。 
实例分割--Mask RCNN详解(ROI Align / Loss Fun)_第20张图片

Lmask(Cls_k) = Sigmoid (Cls_k),平均二值交叉熵 (average binary cross-entropy)Loss,通过逐像素的 Sigmoid 计算得到。Why K个mask?通过对每个 Class 对应一个 Mask 可以有效避免类间竞争(其他 Class 不贡献 Loss )。

        实例分割--Mask RCNN详解(ROI Align / Loss Fun)_第21张图片

通过结果对比来看(Table2 b),也就是作者所说的 Decouple 解耦,要比多分类的Softmax 效果好很多。

代码:

# Losses
rpn_class_loss = KL.Lambda(lambda x: rpn_class_loss_graph(*x), name="rpn_class_loss")(
    [input_rpn_match, rpn_class_logits])
rpn_bbox_loss = KL.Lambda(lambda x: rpn_bbox_loss_graph(config, *x), name="rpn_bbox_loss")(
    [input_rpn_bbox, input_rpn_match, rpn_bbox])
class_loss = KL.Lambda(lambda x: mrcnn_class_loss_graph(*x), name="mrcnn_class_loss")(
    [target_class_ids, mrcnn_class_logits, active_class_ids])
bbox_loss = KL.Lambda(lambda x: mrcnn_bbox_loss_graph(*x), name="mrcnn_bbox_loss")(
    [target_bbox, target_class_ids, mrcnn_bbox])
mask_loss = KL.Lambda(lambda x: mrcnn_mask_loss_graph(*x), name="mrcnn_mask_loss")(
    [target_mask, target_class_ids, mrcnn_mask])
rpn_class_loss :RPN网络分类损失函数 
rpn_bbox_loss :RPN网络回归损失函数 
class_loss :分类损失函数 
bbox_loss :回归损失函数 

mask_loss:Mask回归损失函数

4 分割掩码

分割掩码网络是 Mask R-CNN 的论文引入的附加网络,在气球分割中:

实例分割--Mask RCNN详解(ROI Align / Loss Fun)_第22张图片

掩码分支是一个卷积网络,取 ROI 分类器选择的正区域为输入,并生成它们的掩码。其生成的掩码是低分辨率的:28x28 像素。但它们是由浮点数表示的软掩码,相对于二进制掩码有更多的细节。掩码的小尺寸属性有助于保持掩码分支网络的轻量性。在训练过程中,我们将真实的掩码缩小为 28x28 来计算损失函数,在推断过程中,我们将预测的掩码放大为 ROI 边框的尺寸以给出最终的掩码结果,每个目标有一个掩码。

代码提示:掩码分支网络在 build_fpn_mask_graph() 中。


5 网络结构-head

实例分割--Mask RCNN详解(ROI Align / Loss Fun)_第23张图片 

图中灰色部分是 原来的 RCNN 结合 ResNet or FPN 的网络,下面黑色部分为新添加的并联 Mask层,这个图本身与上面的图也没有什么区别,旨在说明作者所提出的Mask RCNN 方法的泛化适应能力 - 可以和多种 RCNN框架结合,表现都不错

这里实际上有两个网络结构:

一个就是Fater R-CNN with ResNet/ResNeXt: overview的那副图(或者如上左边)。使用resnet-c4作为前面的卷积网络,将rpn生成的roi映射到C4的输出,并进行roi pooling,最后进行分叉预测三个目标。

另一个网络就是faster rcnn with FPN。

作为特征提取器。底层检测的是低级特征(边缘和角等),较高层检测的是更高级的特征(汽车、人、天空等)。


参考:

1. FPN(feature pyramid networks)算法讲解:

http://blog.csdn.net/u014380165/article/details/72890275 

 2.【目标检测】Mask RCNN算法详解

https://blog.csdn.net/disiwei1012/article/details/79508839

 3.Mask RCNN笔记

https://blog.csdn.net/xiamentingtao/article/details/78598511

 4.先理解Mask R-CNN的工作原理,然后构建颜色填充器应用

http://baijiahao.baidu.com/s?id=1595621180643410921&wfr=spider&for=pc

5.Mask-RCNN技术解析

https://blog.csdn.net/linolzhang/article/details/71774168

6.详解 ROI Align 的基本原理和实现细节

http://blog.leanote.com/post/[email protected]/b5f4f526490b

7.Mask R-CNN论文导读

https://blog.csdn.net/crazyice521/article/details/65448935

8.Mask R-CNN

https://www.jianshu.com/p/e8e445b38f6f

9.目标检测分割--Mask R-CNN

https://blog.csdn.net/zhangjunhit/article/details/64920075?locationNum=6&fps=1

10.Facebook 最新论文:Mask R-CNN实例分割通用框架,检测,分割和特征点定位一次搞定(多图)

https://www.leiphone.com/news/201703/QU1einPqSPJEffog.html

11.mask rcnn解读

https://blog.csdn.net/u013010889/article/details/78588227

你可能感兴趣的:(场景理解,深度学习)