R-CNN系列其六:Mask_RCNN

介绍

Mask RCNN提出于2018年,是在Faster-RCNN的基础上改进后被用于解决图像instance segmentation的问题。相对于原来的Faster_RCNN主干框架,它在网络的头上引入了另外一条FCN并行分支用来检测ROI的mask map信息。这样最终它的头部共有三条并行的分支分别用来处理ROI区域的类别识别、目标框位置回归及相应的mask map回归。
下图为Mask-RCNN的基本框架描述。

Mask-RCNN基本框架

instance segmentation

instance segmentation = object detection + semantic segmentation。

object detection我们知道是用于检测出图片上目标的位置框。而semantic segmentation我们在之前的FCN模型介绍时也提到过它是用于识别出图片上每一个像素点所对应的物体类别。instance segmentation可视为这两个问题的合集即用于识别出目标框里面目标物体的每个像素点的归属类别(因为已经知道了目标框内存在物体的类别,所以我们这里只需分辨出目标框内的每一个像素点是前景还是背景即可,所以它是个目标框内部像素级别的二分类问题。)
从下图Mask-RCNN的输出结果中我们能更好地理解何为instance segmentation及它与objection detection和semantic segmentation之间的关系。

Instance_segmentation示例

Mask-RCNN

在前面已经说过了本质上Mask-RCNN = Faster-RCNN + FCN。

Mask-RCNN的主干特征提取网络同Faster-RCNN一样可以选用任何CNN网络如VGG16/Resnet系列等。论文中作者使用了基本Resnet与FPN网络来分别作为模型的主干网络。在主干网络最终产生的feature maps集合之上,我们使用RPN生成多个region proposals出来。然后再将这些region proposals分别生成对应的ROI窗口,进而用于后续的分类、目标框定位及目标框Mask map识别等。在这里作者对之前的粗粒度ROI pool进行了改进,提出了更加细粒度的,可在子像素级别上提取ROI窗口区域特征的ROI align层。它可以有效地避免像素错位,能够取得更加准确的区域位置信息。

下图为Mask-RCNN的网络头部结构描述。

Mask-RCNN网络头部结构

Mask-RCNN最终的loss也由三部分来组成即:L = Lcls + Lbox + Lmask

  • Mask分支

关于分类与目标框定位的分支,Mask-RCNN与之前介绍过的Faster-RCNN并无区别,因此我们这里只对另外的Mask分支进行讲解。
Mask分支通过在ROI Align层后吐出的ROI特征上接上一个FCN,最终得到一个与些ROI区域相对应的mask map出来。这个mask map有K个channels。K在这里表示目标可能的类别数目。另外每个channel的mask map上面都是些二元信息,分别表示ROI区域上面的某位置点是前景还是背景。
在计算Lmask时,我们只对这K个maps中的第k个map进行处理,在这里k表示由另外的类别识别分支所定位出的此ROI区域的物体类别。

这样一种设计本质上使得mask分支可与另外两个分支真正是独立进行,互不影响(当然只有在计算training loss进而对模型参数进行优化时才会有所干联)。

  • ROIAlign

不同于之前Faster-RCNN用于提取ROI窗口的RPIPool,Mask-RCNN使用了更加细粒度的可在亚像素级别上进行ROI特征提取的ROIAlign层。
一般,我们使用ROIPool来对某个ROI区域进行处理时,当除不尽时会在像素级别上进行近似。比如若ROIPool的kernel size为7x7即我们最终要得到7x7大小的区域特征,而我们的ROI proposal在feature map上提取的窗口大小为16x16。那么我们在进行ROIPool处理时会将16x16的feature map近似划为[16/7] x [16/7]大小的格子出来。这样得到的7x7的区域特征必将会使得原有的信息在像素级别位置上有所偏离。

如果单纯只是检测目标框的话,RoIPool所引入的这种偏离并不会对最终目标框位置或类别结果产生过多影响。但是Mask map的检测却会因像素级别上的错位而导致整个map出现较大错误。为此作者提出了更为细致的ROIAlign来解决ROIPool在除不尽时近似取整所带来的误差问题。

下图为ROIAlign的具体实现,可以看出它是通过将每个ROI Grid内部进行细致的二分插值,在除不尽时拟合出一定数目的像素点来计算最终的pool值。

ROI Align

Mask-RCNN的训练与推理

  • Mask-RCNN训练

它生成的每个ROI如果最终与某个Ground truth box的IOU为0.5以上,那么就可视为一个positive box,若小于0.5则为negative box。而Lmask的计算则只在positive box上面进行。

训练时将输入图片按短边保持分辨率不变resize为800的大小。每个mini-batch含2 images/GPU。最终每个image经RPN网络生成出N个候选的ROI,其中正负样本区域的比例保持为1:3(自然在这里作者有使用Hard mining的方法来过滤掉大批量的简单负样本区域,此方法与Faster-RCNN中用到的亦相同)。

以naive Resnet为主干网络时,N为64;以FPN为主干网络时,N为256。作者使用了8个GPUs进行训练,最大iterations数目为160,000。初始lr为0.02,然后随着训练进行,以stepwise的方式递减(通过除10)。因为用的是SGD模型,所以还有weight decay系数为0.0001及momentum factor为0.9。

RPN的anchor box共包含5个尺度及3种分辨率的组合。

  • Mask-RCNN推理

推理时,当以naive Resnet为主干网络时,最终产生300个Region proposals;而以FPN为主干时,最终生成1000个Region proposals。在box prediction branch上forward时会作NMS(non-maximum suppression)处理。然后Mask branch会在排名最靠前的100个region proposals上进行forward计算(这样做违背了训练时三分支并行计算的特点,但确实有助于提升inference时的计算性能。)在这些ROI上面,mask分支最终得到K个mask maps / ROI。然后再对由classification分支预测得到的第k个分支进行resize,使它的大小与ROI的大小相符合。最终以0.5为threshold,对mask map作二分值化处理就得到最终的binarized mask map。

Mask-RCNN实验结果

作者尝试使用Mask-RCNN在许多需要在像素级别上定位物体类别的问题。如下为其部分实验结果展示。

  • Instance segmentation

与state-of-art instance segmentation模型FCIS++相比,它在处理overlapped 物体时能取得更好结果。

Mask_RCNN在keypoint detction上的应用
  • object detection
Mask_RCNN在目标识别上的应用及其改进
  • Keypoint detection
Mask_RCNN在keypoint detction上的应用

代码分析

关于mask branch的设计可见于如下的comment当中。

"""Various network "heads" for predicting masks in Mask R-CNN.
The design is as follows:
... -> RoI ----\
                -> RoIFeatureXform -> mask head -> mask output -> loss
... -> Feature /
       Map
The mask head produces a feature representation of the RoI for the purpose
of mask prediction. The mask output module converts the feature representation
into real-valued (soft) masks.
"""

以下为基于caffe2 API的Mask R-CNN的network head及Lmask loss的实现。

# ---------------------------------------------------------------------------- #
# Mask R-CNN outputs and losses
# ---------------------------------------------------------------------------- #

def add_mask_rcnn_outputs(model, blob_in, dim):
    """Add Mask R-CNN specific outputs: either mask logits or probs."""
    num_cls = cfg.MODEL.NUM_CLASSES if cfg.MRCNN.CLS_SPECIFIC_MASK else 1

    if cfg.MRCNN.USE_FC_OUTPUT:
        # Predict masks with a fully connected layer (ignore 'fcn' in the blob
        # name)
        blob_out = model.FC(
            blob_in,
            'mask_fcn_logits',
            dim,
            num_cls * cfg.MRCNN.RESOLUTION**2,
            weight_init=gauss_fill(0.001),
            bias_init=const_fill(0.0)
        )
    else:
        # Predict mask using Conv

        # Use GaussianFill for class-agnostic mask prediction; fills based on
        # fan-in can be too large in this case and cause divergence
        fill = (
            cfg.MRCNN.CONV_INIT
            if cfg.MRCNN.CLS_SPECIFIC_MASK else 'GaussianFill'
        )
        blob_out = model.Conv(
            blob_in,
            'mask_fcn_logits',
            dim,
            num_cls,
            kernel=1,
            pad=0,
            stride=1,
            weight_init=(fill, {'std': 0.001}),
            bias_init=const_fill(0.0)
        )

        if cfg.MRCNN.UPSAMPLE_RATIO > 1:
            blob_out = model.BilinearInterpolation(
                'mask_fcn_logits', 'mask_fcn_logits_up', num_cls, num_cls,
                cfg.MRCNN.UPSAMPLE_RATIO
            )

    if not model.train:  # == if test
        blob_out = model.net.Sigmoid(blob_out, 'mask_fcn_probs')

    return blob_out


def add_mask_rcnn_losses(model, blob_mask):
    """Add Mask R-CNN specific losses."""
    loss_mask = model.net.SigmoidCrossEntropyLoss(
        [blob_mask, 'masks_int32'],
        'loss_mask',
        scale=model.GetLossScale() * cfg.MRCNN.WEIGHT_LOSS_MASK
    )
    loss_gradients = blob_utils.get_loss_gradients(model, [loss_mask])
    model.AddLosses('loss_mask')
    return loss_gradients

以下为宏观上在backend网络之后添加network mask head的代码:

def add_ResNet_roi_conv5_head_for_masks(model, blob_in, dim_in, spatial_scale):
    """Add a ResNet "conv5" / "stage5" head for predicting masks."""
    model.RoIFeatureTransform(
        blob_in,
        blob_out='_[mask]_pool5',
        blob_rois='mask_rois',
        method=cfg.MRCNN.ROI_XFORM_METHOD,
        resolution=cfg.MRCNN.ROI_XFORM_RESOLUTION,
        sampling_ratio=cfg.MRCNN.ROI_XFORM_SAMPLING_RATIO,
        spatial_scale=spatial_scale
    )

    dilation = cfg.MRCNN.DILATION
    stride_init = int(cfg.MRCNN.ROI_XFORM_RESOLUTION / 7)  # by default: 2

    s, dim_in = ResNet.add_stage(
        model,
        '_[mask]_res5',
        '_[mask]_pool5',
        3,
        dim_in,
        2048,
        512,
        dilation,
        stride_init=stride_init
    )

    return s, 2048

关于ROIAlign的实现,已经在caffe2中作为base operator得到了支持,我们可调用如下API来完成ROIAlign的功能添加。

    model.RoIFeatureTransform(
        blob_in,
        blob_out='_[mask]_pool5',
        blob_rois='mask_rois',
        method=cfg.MRCNN.ROI_XFORM_METHOD,
        resolution=cfg.MRCNN.ROI_XFORM_RESOLUTION,
        sampling_ratio=cfg.MRCNN.ROI_XFORM_SAMPLING_RATIO,
        spatial_scale=spatial_scale
    )

参考文献

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,383评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,522评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,852评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,621评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,741评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,929评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,076评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,803评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,265评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,582评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,716评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,395评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,039评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,027评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,488评论 2 361
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,612评论 2 350

推荐阅读更多精彩内容