点击上方“AI算法修炼营”,选择加星标或“置顶”

标题以下,全是干货

1、内容概要

    Mask R-CNN的框架是对Faster R-CNN的扩展,与BBox识别并行的增加一个分支来预测每一个RoI的分割Mask。Mask分支是应用到每一个RoI上的一个小的FCN,以pix2pix的方式预测分割Mask。

    对Mask预测和class预测去耦合。对每个类别独立的预测一个二值Mask,不依赖分类分支的预测结果。

be9e2f97a80bf6cdda1a9a91b5cad038.png

2、部分相关工作说明

2.1、网络结构发展

87e17995944c906541639f27f53c5ec4.png

作为Faster R-CNN的进一步推进,Mask R-CNN网络的发展进程可以如下图所示:

dfab0f1c3ad685e654c10fe47108a1b9.png

2.2、网络结构原理

87e17995944c906541639f27f53c5ec4.pngd9ef0789638d9c1fc423edba93f292ad.png

    为了演示方法的一般性,作者使用多种架构来构建Mask R-CNN。

区分

(i)用于整个图像上的特征提取的卷积主干架构;

(ii) 网络头(head) 用于对每个RoI单独应用的边界框识别(分类和回归) 和掩码预测。

网络架构:

    作者评估了深度为50或101层的ResNet 和ResNeXt网络。Faster R-CNN与ResNets 的实现从第4阶段的最终卷积层中提取了特征,称之为C4。例如, ResNet-50的这个主干由ResNet-50-C4表示。

    作者还使用了另一个更有效的称为特征金字塔网络(FPN)主干。FPN使用具有lateral连接的自上而下架构,从单一尺度输入构建一个网络内特征金字塔。具有FPN主干的Faster R-CNN根据其规模从特征金字塔的不同级别提取RoI特征,随后的方法类似于vanilla ResNet。使用ResNet-FPN主干网通过Mask R-CNN进行特征提取,可以在精度和速度方面获得极佳的提升。

a5ad805a1c158f24bc7a7aede09d84bc.png

算法流程:

    a) 将原始图像中的RoI对应到feature map上,坐标位置可能会存在小数,此时直接取整产生一次量化误差;

    b) 在feature map的RoI区域上,将其划分成nxn的bins,bins的坐标位置可能会有小数,此时也直接取整,产生第二次量化误差;

    c) 最后在每个bin中进行max pooling操作,即可获得固定大小的feature map;

    在Mask-RCNN中使用RoIAlign提取固定大小的feature map。其算法流程与RoIPooling一样,只是在a)和b)步骤中,当坐标位置为小数时,不进行量化取整,而是保留小数。在c)步骤里每个bin中均匀取点,点的数值通过双线性插值获得。

2.3、ROI Align

87e17995944c906541639f27f53c5ec4.png

    实际上,Mask R-CNN中还有一个很重要的改进,就是ROIAlign。

    Faster R-CNN存在的问题是:特征图与原始图像是不对准的(mis-alignment),所以会影响检测精度。而Mask R-CNN提出了RoIAlign的方法来取代ROI pooling,RoIAlign可以保留大致的空间位置。

    RoIPool是从每个RoI中提取特征。先将浮点数表示的RoI量化到与特征图匹配的粒度,然后将量化后的RoI分块后对每个块进行池化。这些量化使RoI与提取的特征错位。虽然可能不会影响分类,但对于预测像素级精确掩码有很大的负面影响。为了解决这个问题文章提出了一个新的层,称为ROIAlign。文章避免对RoI边界进行量化,即用x/16代替[x/16]。然后对分得的每一个块选取分块中的四个常规位置,并使用双线性插值来计算每一个位置上的精确值,并将结果汇总(池化)。

92f28628a04f97e8740edb07c022d0fb.png

   那么,RoI Pooling和RoI Align的区别在于哪里呢?如何能够精确的反向找到对应像素点边缘?对Pooling的划分不能按照Pooling的边缘,而是要按照像素点缩放后的边缘。

de64f95ac7170103eb1bd5b590480102.png89ae0b43ba616c29a21b07176efb4f30.png

2.4、ResNet-FPN网络

87e17995944c906541639f27f53c5ec4.png

    多尺度检测在目标检测中变得越来越重要,对小目标的检测尤其如此。现在主流的目标检测方法很多都用到了多尺度的方法,包括最新的yolo v3。Feature Pyramid Network (FPN)则是一种精心设计的多尺度检测方法,下面就开始简要介绍FPN。

    FPN结构中包括自下而上,自上而下和横向连接三个部分,如下图所示。这种结构可以将各个层级的特征进行融合,使其同时具有强语义信息和强空间信息,在特征学习中算是一把利器了。

83fc4a6c36f01c09a5fa4cf2b984c407.png

ResNet-FPN包括3个部分:

        自下而上连接;

        自上而下连接;

        横向连接。

下面分别介绍:

自下而上连接:

    从下到上路径。可以明显看出,其实就是简单的特征提取过程,和传统的没有区别。具体就是将ResNet作为骨架网络,根据feature map的大小分为5个stage。stage2,stage3,stage4和stage5各自最后一层输出conv2,conv3,conv4和conv5分别定义为C2,C3,C4,C5,他们相对于原始图片的stride是{4,8,16,32}。需要注意的是,考虑到内存原因,stage1的conv1并没有使用。

自上而下和横向连接:

    自上而下是从最高层开始进行上采样,这里的上采样直接使用的是最近邻上采样,而不是使用反卷积操作,一方面简单,另外一方面可以减少训练参数。横向连接则是将上采样的结果和自底向上生成的相同大小的feature map进行融合。具体就是对 C2,C3,C4,C5中的每一层经过一个conv 1x1操作(1x1卷积用于降低通道数),无激活函数操作,输出通道全部设置为相同的256通道,然后和上采样的feature map进行加和操作。在融合之后还会再采用3*3的卷积核对已经融合的特征进行处理,目的是消除上采样的混叠效应(aliasing effect)。

    实际上,上图少绘制了一个分支:M5经过步长为2的max pooling下采样得到 P6,作者指出使用P6是想得到更大的anchor尺度512×512。但P6是只用在 RPN中用来得到region proposal的,并不会作为后续Fast RCNN的输入。

总结一下,ResNet-FPN作为RPN输入的feature map是P2,P3,P4,P5,P6 ,而作为后续Fast RCNN的输入则是P2,P3,P4,P5。

5、基于Mask R-CNN的道路物体检测与分割实践

    本项目基于以上说明的论文进行实践,数据集时Market1501数据集。针对论文中的Baseline网络GoogleNet进行了替换,实践的Baseline网络为ResNet50模型,同时使用了与训练的方式对论文进行了实践。

5.1、数据集制作软件配置

87e17995944c906541639f27f53c5ec4.png9e422bf5bba973954c545bfe733706c2.pngd49f05a8965c927de4940b3a21b1539b.png35ac34059184d5701bcbd5fe2e07444c.png

5.2、 数据集制作

87e17995944c906541639f27f53c5ec4.pngf7675828a3c3cd23324a7e4d1c1fbee1.png

标注完成后执行如下指令:

35ac34059184d5701bcbd5fe2e07444c.png

执行上图的指令后,更改如下数据集得到类似coco数据集的属于自己的数据集:

321cbb170db3603598af080054b92b9c.png

5.3、数据增广

87e17995944c906541639f27f53c5ec4.png
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.import randomimport torchimport torchvisionfrom torchvision.transforms import functional as Fclass Compose(object):    def __init__(self, transforms):        self.transforms = transforms    def __call__(self, image, target):        for t in self.transforms:            image, target = t(image, target)        return image, target    def __repr__(self):        format_string = self.__class__.__name__ + "("        for t in self.transforms:            format_string += "\n"            format_string += "    {0}".format(t)        format_string += "\n)"        return format_stringclass Resize(object):    def __init__(self, min_size, max_size):        if not isinstance(min_size, (list, tuple)):            min_size = (min_size,)        self.min_size = min_size        self.max_size = max_size    # modified from torchvision to add support for max size    def get_size(self, image_size):        w, h = image_size        size = random.choice(self.min_size)        max_size = self.max_size        if max_size is not None:            min_original_size = float(min((w, h)))            max_original_size = float(max((w, h)))            if max_original_size / min_original_size * size > max_size:                size = int(round(max_size * min_original_size / max_original_size))        if (w <= h and w == size) or (h <= w and h == size):            return (h, w)        if w < h:            ow = size            oh = int(size * h / w)        else:            oh = size            ow = int(size * w / h)        return (oh, ow)    def __call__(self, image, target=None):        size = self.get_size(image.size)        image = F.resize(image, size)        if target is None:            return image        target = target.resize(image.size)        return image, targetclass RandomHorizontalFlip(object):    def __init__(self, prob=0.5):        self.prob = prob    def __call__(self, image, target):        if random.random() < self.prob:            image = F.hflip(image)            target = target.transpose(0)        return image, targetclass RandomVerticalFlip(object):    def __init__(self, prob=0.5):        self.prob = prob    def __call__(self, image, target):        if random.random() < self.prob:            image = F.vflip(image)            target = target.transpose(1)        return image, targetclass ColorJitter(object):    def __init__(self,                 brightness=None,                 contrast=None,                 saturation=None,                 hue=None,                 ):        self.color_jitter = torchvision.transforms.ColorJitter(            brightness=brightness,            contrast=contrast,            saturation=saturation,            hue=hue,)    def __call__(self, image, target):        image = self.color_jitter(image)        return image, targetclass ToTensor(object):    def __call__(self, image, target):        return F.to_tensor(image), targetclass Normalize(object):    def __init__(self, mean, std, to_bgr255=True):        self.mean = mean        self.std = std        self.to_bgr255 = to_bgr255    def __call__(self, image, target=None):        if self.to_bgr255:            image = image[[2, 1, 0]] * 255        image = F.normalize(image, mean=self.mean, std=self.std)        if target is None:            return image        return image, target

5.4、FPN网络模型

87e17995944c906541639f27f53c5ec4.png
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.import torchimport torch.nn.functional as Ffrom torch import nnclass FPN(nn.Module):    """    Module that adds FPN on top of a list of feature maps.    The feature maps are currently supposed to be in increasing depth    order, and must be consecutive    """    def __init__(        self, in_channels_list, out_channels, conv_block, top_blocks=None    ):        """        Arguments:            in_channels_list (list[int]): number of channels for each feature map that                will be fed            out_channels (int): number of channels of the FPN representation            top_blocks (nn.Module or None): if provided, an extra operation will                be performed on the output of the last (smallest resolution)                FPN output, and the result will extend the result list        """        super(FPN, self).__init__()        self.inner_blocks = []        self.layer_blocks = []        for idx, in_channels in enumerate(in_channels_list, 1):            inner_block = "fpn_inner{}".format(idx)            layer_block = "fpn_layer{}".format(idx)            if in_channels == 0:                continue            inner_block_module = conv_block(in_channels, out_channels, 1)            layer_block_module = conv_block(out_channels, out_channels, 3, 1)            self.add_module(inner_block, inner_block_module)            self.add_module(layer_block, layer_block_module)            self.inner_blocks.append(inner_block)            self.layer_blocks.append(layer_block)        self.top_blocks = top_blocks    def forward(self, x):        """        Arguments:            x (list[Tensor]): feature maps for each feature level.        Returns:            results (tuple[Tensor]): feature maps after FPN layers.                They are ordered from highest resolution first.        """        last_inner = getattr(self, self.inner_blocks[-1])(x[-1])        results = []        results.append(getattr(self, self.layer_blocks[-1])(last_inner))        for feature, inner_block, layer_block in zip(            x[:-1][::-1], self.inner_blocks[:-1][::-1], self.layer_blocks[:-1][::-1]        ):            if not inner_block:                continue            inner_top_down = F.interpolate(last_inner, scale_factor=2, mode="nearest")            inner_lateral = getattr(self, inner_block)(feature)            # TODO use size instead of scale to make it robust to different sizes            # inner_top_down = F.upsample(last_inner, size=inner_lateral.shape[-2:],            # mode='bilinear', align_corners=False)            last_inner = inner_lateral + inner_top_down            results.insert(0, getattr(self, layer_block)(last_inner))        if isinstance(self.top_blocks, LastLevelP6P7):            last_results = self.top_blocks(x[-1], results[-1])            results.extend(last_results)        elif isinstance(self.top_blocks, LastLevelMaxPool):            last_results = self.top_blocks(results[-1])            results.extend(last_results)        return tuple(results)class LastLevelMaxPool(nn.Module):    def forward(self, x):        return [F.max_pool2d(x, 1, 2, 0)]class LastLevelP6P7(nn.Module):    """    This module is used in RetinaNet to generate extra layers, P6 and P7.    """    def __init__(self, in_channels, out_channels):        super(LastLevelP6P7, self).__init__()        self.p6 = nn.Conv2d(in_channels, out_channels, 3, 2, 1)        self.p7 = nn.Conv2d(out_channels, out_channels, 3, 2, 1)        for module in [self.p6, self.p7]:            nn.init.kaiming_uniform_(module.weight, a=1)            nn.init.constant_(module.bias, 0)        self.use_P5 = in_channels == out_channels    def forward(self, c5, p5):        x = p5 if self.use_P5 else c5        p6 = self.p6(x)        p7 = self.p7(F.relu(p6))        return [p6, p7]

5.5、图片测试结果

87e17995944c906541639f27f53c5ec4.png1f4f2dec3142ca568a05e68d80397160.png

5.6、视频测试结果展示

87e17995944c906541639f27f53c5ec4.pngd2be7046a7844a6f9d45d65b0812e423.gifc82229b88b18046e7694694e858c56a4.gif

参考:

https://zhuanlan.zhihu.com/p/37998710

https://zhuanlan.zhihu.com/p/25954683

https://zhuanlan.zhihu.com/p/66973573

https://github.com/facebookresearch/maskrcnn-benchmark

关注公众号,回复【道路物体分割】即可获得完整的项目代码以及操作流程文档说明。

8cf244127a7f60d18fa283615cb131d0.png

4177d0d3ce501bef00f1a1c23ee511c0.png

目标检测系列

秘籍一:模型加速之轻量化网络

秘籍二:非极大值抑制及回归损失优化

秘籍三:多尺度检测

秘籍四:数据增强

秘籍五:解决样本不均衡问题

秘籍六:Anchor-Free

语义分割系列

一篇看完就懂的语义分割综述

最新实例分割综述:从Mask RCNN 到 BlendMask

面试求职系列

决战春招!算法工程师面试问题及资料超详细合集

一起学C++系列

内存分区模型、引用、函数重载

竞赛与工程项目分享系列

如何让笨重的深度学习模型在移动设备上跑起来

基于Pytorch的YOLO目标检测项目工程大合集

点云配准领域全面资料、课程、数据集合集分享

10万奖金天文数据挖掘竞赛!0.95高分Baseline分享

目标检测应用竞赛:铝型材表面瑕疵检测

SLAM系列

视觉SLAM前端:视觉里程计和回环检测

视觉SLAM后端:后端优化和建图模块

视觉SLAM中特征点法开源算法:PTAM、ORB-SLAM

视觉SLAM中直接法开源算法:LSD-SLAM、DSO

视觉注意力机制系列

Non-local模块与Self-attention之间的关系与区别?

视觉注意力机制用于分类网络:SENet、CBAM、SKNet

Non-local模块与SENet、CBAM的融合:GCNet、DANet

Non-local模块如何改进?来看CCNet、ANN

c1d314200f93bc91dc180c5bf165ffec.png

3cc014424b2cd124de3f81d1fabc12b4.gif

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐