实例分割经典:Mask-RCNN及pytorch代码

前沿

不同于语义分割,实例分割需要将每个类别的单个目标单独打上id。MaskRCNN是何凯明大神2017年在ICCV发表的一篇用于目标检测、实例分割、人体关键点检测的经典工作。

设计思想

实例分割具有挑战性,因为它需要正确检测图像中的所有对象,同时还要精确分割每个实例。因此既需要对目标进行边界框的检测定位,又需要对边界框内的目标进行像素级的前后景分割。
文章的方法称为Mask R-CNN,它通过在FasterRCNN中添加一个分支来预测每个感兴趣区域(RoI)上的分割蒙版,与现有的用于分类和边界框回归的分支并行,扩展了FasterRCNN。获取分割掩码Mask的分支是应用于每个RoI的小型FCN(全卷积网络),以像素到像素的方式预测分段掩码。同时为了解决FasterRCNN中(ROIPool)存在的像素对齐问题,文章设计了ROIAlign做特征图到原图的像素对齐

其中的RoIpool,会造成量化误差。也就是说,由于特征图的分辨率变小,可能出现不能够刚好对齐的情况。比如31×31,如果经过步长stride为2的pool,size会变为15×15。这就是量化损失。这就造成特征图与原图对齐的误差。

也就是说,论文将backbone得到的特征图进行ROI区域生成(具体去看RPN网络的介绍),然后再经过三个branch,分别是目标框的回归,目标的分类,mask的生成(FCN分支),当然,也就对应了三个损失:Lbox,Lcls,Lmask。
实例分割经典:Mask-RCNN及pytorch代码_第1张图片

具体细节

  • Mask分支

mask分支采用FCN对每个RoI的分割输出维数为K×m×m(其中:m表示RoI Align特征图的大小),即K个类别的m×m的二值mask;保持m×m的空间布局,pixel-to-pixel操作需要保证RoI特征映射到原图的对齐性,这也是使用RoIAlign解决对齐问题原因,减少像素级别对齐的误差。

Kmm二值mask结构解释:最终的FCN输出一个K层的mask,每一层为一类,Log输出,用0.5作为阈值进行二值化,产生背景和前景的分割Mask

这样,Lmask 使得网络能够输出每一类的 mask,且不会有不同类别 mask 间的竞争. 分类网络分支预测 object 类别标签,以选择输出 mask,对每一个ROI,如果检测得到ROI属于哪一个分类,就只使用哪一个分支的相对熵误差作为误差值进行计算。(举例说明:分类有3类(猫,狗,人),检测得到当前ROI属于“人”这一类,那么所使用的Lmask为“人”这一分支的mask,即,每个class类别对应一个mask可以有效避免类间竞争(其他class不贡献Loss)
对每一个像素应用sigmoid,然后取RoI上所有像素的交叉熵的平均值作为Lmask。

  • ROIAlign 参考见这儿

实例分割经典:Mask-RCNN及pytorch代码_第2张图片

我们来说一下具体的细节,如图我们输入的是一张800x800的图像,在图像中有两个目标(猫和狗),狗的BB大小为665x665,经过VGG16网络后,我们可以获得对应的feature map,如果我们对卷积层进行Padding操作,我们的图片经过卷积层后保持原来的大小,但是由于池化层的存在,我们最终获得feature map 会比原图缩小一定的比例,这和Pooling层的个数和大小有关。在该VGG16中,我们使用了5个池化操作,每个池化操作都是2Pooling,因此我们最终获得feature map的大小为800/32 x 800/32 = 25x25(是整数),但是将狗的BB对应到feature map上面,我们得到的结果是665/32 x 665/32 = 20.78 x 20.78,结果是浮点数,含有小数,但是我们的像素值可没有小数,那么作者就对其进行了量化操作(即取整操作),即其结果变为20 x 20,在这里引入了第一次的量化误差;然而我们的feature map中有不同大小的ROI,但是我们后面的网络却要求我们有固定的输入,因此,我们需要将不同大小的ROI转化为固定的ROI feature,在这里使用的是7x7的ROI feature,那么我们需要将20 x 20的ROI映射成7 x 7的ROI feature,其结果是 20 /7 x 20/7 = 2.86 x 2.86,同样是浮点数,含有小数点,我们采取同样的操作对其进行取整吧,在这里引入了第二次量化误差。其实,这里引入的误差会导致图像中的像素和特征中的像素的偏差,即将feature空间的ROI对应到原图上面会出现很大的偏差。原因如下:比如用我们第二次引入的误差来分析,本来是2.86,我们将其量化为2,这期间引入了0.86的误差,看起来是一个很小的误差呀,但是你要记得这是在feature空间,我们的feature空间和图像空间是有比例关系的,在这里是1:32,那么对应到原图上面的差距就是0.86 x 32 = 27.52。这个差距不小吧,这还是仅仅考虑了第二次的量化误差。这会大大影响整个检测算法的性能,因此是一个严重的问题。

简而言之:

做segment是pixel级别的,但是faster rcnn中roi pooling有2次量化操作导致了没有对齐 .

为了解决ROI Pooling的上述缺点,作者提出了ROI Align这一改进的方法。ROI Align的思路很简单:取消量化操作,使用双线性插值的方法获得坐标为浮点数的像素点上的图像数值,从而将整个特征聚集过程转化为一个连续的操作。如下图所示:
实例分割经典:Mask-RCNN及pytorch代码_第3张图片

蓝色的虚线框表示卷积后获得的feature map,黑色实线框表示ROI feature,最后需要输出的大小是2x2,那么我们就利用双线性插值来估计这些蓝点(虚拟坐标点,又称双线性插值的网格点)处所对应的像素值,最后得到相应的输出。这些蓝点是2x2Cell中的随机采样的普通点,作者指出,这些采样点的个数和位置不会对性能产生很大的影响,你也可以用其它的方法获得。然后在每一个橘红色的区域里面进行max pooling或者average pooling操作,获得最终2x2的输出结果。我们的整个过程中没有用到量化操作,没有引入误差,即原图中的像素和feature map中的像素是完全对齐的,没有偏差,这不仅会提高检测的精度,同时也会有利于实例分割。

双线性插值

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

这里对上述步骤的第三点作一些说明:这个固定位置是指在每一个矩形单元(bin)中按照固定规则确定的位置。比如,如果采样点数是1,那么就是这个单元的中心点。如果采样点数是4,那么就是把这个单元平均分割成四个小方块以后它们分别的中心点。显然这些采样点的坐标通常是浮点数,所以需要使用插值的方法得到它的像素值。在相关实验中,作者发现将采样点设为4会获得最佳性能,甚至直接设为1在性能上也相差无几。
实例分割经典:Mask-RCNN及pytorch代码_第4张图片
如图所示,为了得到为了得到固定大小(7X7)的feature map,ROIAlign技术并没有使用量化操作,即我们不想引入量化误差,比如665 / 32 = 20.78,我们就用20.78,不用什么20来替代它,比如20.78 / 7 = 2.97,我们就用2.97,而不用2来代替它。这就是ROIAlign的初衷。那么我们如何处理这些浮点数呢,我们的解决思路是使用“双线性插值”算法。双线性插值是一种比较好的图像缩放算法,它充分的利用了原图中虚拟点(比如20.56这个浮点数,像素位置都是整数值,没有浮点值)四周的四个真实存在的像素值来共同决定目标图中的一个像素值,即可以将20.56这个虚拟的位置点对应的像素值估计出来

ROI-Align总结:

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

Pytorch代码

参见https://github.com/CuberrChen/pytorch-mask-rcnn

你可能感兴趣的:(图像分割系列,计算机视觉,图像处理,卷积神经网络,深度学习,pytorch)