maskrcnn_maskrcnn 速度 - CSDN
  • Mask R-CNN是一种基于深度学习的图像实例分割方法,可对物体进行目标检测和像素级分割。 本课程将手把手地教大家使用VIA图像标注工具制作自己的数据集,并使用Mask R-CNN训练自己的数据集,从而能开展自己的图像...
  • Mask RCNN -原理解析(一)

    万次阅读 多人点赞 2018-06-08 17:09:30
    Mask RCNN 原理:简单说一下Mask R-CNN 是一个两阶段的框架,第一个阶段扫描图像并生成提议(proposals,即有可能包含一个目标的区域),第二阶段分类提议并生成边界框和掩码。Mask R-CNN 扩展自 Faster R-CNN,由...

    Mask RCNN 原理:

    简单说一下Mask R-CNN 是一个两阶段的框架,第一个阶段扫描图像并生成提议(proposals,即有可能包含一个目标的区域),第二阶段分类提议并生成边界框和掩码。Mask R-CNN 扩展自 Faster R-CNN,由同一作者在去年提出。Faster R-CNN 是一个流行的目标检测框架,Mask R-CNN 将其扩展为实例分割框架。



    Mask R-CNN 的主要构建模块:


    1. 主干架构



    主干网络的简化图示


    这是一个标准的卷积神经网络(通常来说是 ResNet50 和 ResNet101),作为特征提取器。底层检测的是低级特征(边缘和角等),较高层检测的是更高级的特征(汽车、人、天空等)。


    经过主干网络的前向传播,图像从 1024x1024x3(RGB)的张量被转换成形状为 32x32x2048 的特征图。该特征图将作为下一个阶段的输入。

    特征金字塔网络(FPN)



    Feaature Pyramid Networks for Object Detection


    上述的主干网络还可以进一步提升。由 Mask R-CNN 的同一作者引入的特征金字塔网络(FPN)是对该主干网络的扩展,可以在多个尺度上更好地表征目标。


    FPN 通过添加第二个金字塔提升了标准特征提取金字塔的性能,第二个金字塔可以从第一个金字塔选择高级特征并传递到底层上。通过这个过程,它允许每一级的特征都可以和高级、低级特征互相结合。


    在我们的 Mask R-CNN 实现中使用的是 ResNet101+FPN 主干网络。

    2. 区域建议网络(RPN)




    RPN 是一个轻量的神经网络,它用滑动窗口来扫描图像,并寻找存在目标的区域。


    RPN 扫描的区域被称为 anchor,这是在图像区域上分布的矩形,如上图所示。这只是一个简化图。实际上,在不同的尺寸和长宽比下,图像上会有将近 20 万个 anchor,并且它们互相重叠以尽可能地覆盖图像。


    RPN 扫描这些 anchor 的速度有多快呢?非常快。滑动窗口是由 RPN 的卷积过程实现的,可以使用 GPU 并行地扫描所有区域。此外,RPN 并不会直接扫描图像,而是扫描主干特征图。这使得 RPN 可以有效地复用提取的特征,并避免重复计算。通过这些优化手段,RPN 可以在 10ms 内完成扫描(根据引入 RPN 的 Faster R-CNN 论文中所述)。在 Mask R-CNN 中,我们通常使用的是更高分辨率的图像以及更多的 anchor,因此扫描过程可能会更久。


    RPN 为每个 anchor 生成两个输出:




    1. anchor 类别:前景或背景(FG/BG)。前景类别意味着可能存在一个目标在 anchor box 中。

    2. 边框精调:前景 anchor(或称正 anchor)可能并没有完美地位于目标的中心。因此,RPN 评估了 delta 输出(x、y、宽、高的变化百分数)以精调 anchor box 来更好地拟合目标。


    使用 RPN 的预测,我们可以选出最好地包含了目标的 anchor,并对其位置和尺寸进行精调。如果有多个 anchor 互相重叠,我们将保留拥有最高前景分数的 anchor,并舍弃余下的(非极大值抑制)。然后我们就得到了最终的区域建议,并将其传递到下一个阶段。

    3. ROI 分类器和边界框回归器


    这个阶段是在由 RPN 提出的 ROI 上运行的。正如 RPN 一样,它为每个 ROI 生成了两个输出:


    1. 类别:ROI 中的目标的类别。和 RPN 不同(两个类别,前景或背景),这个网络更深并且可以将区域分类为具体的类别(人、车、椅子等)。它还可以生成一个背景类别,然后就可以弃用 ROI 了。

    2. 边框精调:和 RPN 的原理类似,它的目标是进一步精调边框的位置和尺寸以将目标封装。

    ROI 池化


    在我们继续之前,需要先解决一些问题。分类器并不能很好地处理多种输入尺寸。它们通常只能处理固定的输入尺寸。但是,由于 RPN 中的边框精调步骤,ROI 框可以有不同的尺寸。因此,我们需要用 ROI 池化来解决这个问题。




    ROI 池化是指裁剪出特征图的一部分,然后将其重新调整为固定的尺寸。这个过程实际上和裁剪图片并将其缩放是相似的(在实现细节上有所不同)。


    Mask R-CNN 的作者提出了一种方法 ROIAlign,在特征图的不同点采样,并应用双线性插值。在我们的实现中,为简单起见,我们使用 TensorFlow 的 crop_and_resize 函数来实现这个过程。

    4. 分割掩码


    到第 3 节为止,我们得到的正是一个用于目标检测的 Faster R-CNN。而分割掩码网络正是 Mask R-CNN 的论文引入的附加网络。




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



    展开全文
  • 睿智的目标检测19——Keras搭建Mask R-CNN实例分割平台学习前言什么是Mask R-CNN源码下载Mask R-CNN实现思路一、预测部分1、主干网络介绍2、特征金字塔FPN的构建3、获得Proposal建议框4、Proposal建议框的解码5、对...

    学习前言

    来看看很厉害的Mask R-CNN实例分割的原理吧,还是挺有意思的呢!
    在这里插入图片描述

    什么是Mask R-CNN

    在这里插入图片描述
    Mask R-CNN是He Kaiming大神2017年的力作,其在进行目标检测的同时进行实例分割,取得了出色的效果。
    其网络的设计也比较简单,在Faster R-CNN基础上,在原本的两个分支上(分类+坐标回归)增加了一个分支进行语义分割,

    源码下载

    https://github.com/bubbliiiing/mask-rcnn-keras
    喜欢的可以点个star噢。

    Mask R-CNN实现思路

    一、预测部分

    1、主干网络介绍

    在这里插入图片描述
    Mask-RCNN使用Resnet101作为主干特征提取网络,对应着图像中的CNN部分,其对输入进来的图片有尺寸要求,需要可以整除2的6次方。在进行特征提取后,利用长宽压缩了两次、三次、四次、五次的特征层来进行特征金字塔结构的构造。

    ResNet101有两个基本的块,分别名为Conv Block和Identity Block,其中Conv Block输入和输出的维度是不一样的,所以不能连续串联,它的作用是改变网络的维度;Identity Block输入维度和输出维度相同,可以串联,用于加深网络的。
    Conv Block的结构如下:
    在这里插入图片描述
    Identity Block的结构如下:
    在这里插入图片描述
    这两个都是残差网络结构。

    以官方使用的coco数据集输入的shape为例,输入的shape为1024x1024,shape变化如下:
    在这里插入图片描述
    我们取出长宽压缩了两次、三次、四次、五次的结果来进行特征金字塔结构的构造。

    实现代码:

    from keras.layers import ZeroPadding2D,Conv2D,MaxPooling2D,BatchNormalization,Activation,Add
    
    
    def identity_block(input_tensor, kernel_size, filters, stage, block,
                       use_bias=True, train_bn=True):
        nb_filter1, nb_filter2, nb_filter3 = filters
        conv_name_base = 'res' + str(stage) + block + '_branch'
        bn_name_base = 'bn' + str(stage) + block + '_branch'
    
        x = Conv2D(nb_filter1, (1, 1), name=conv_name_base + '2a',
                      use_bias=use_bias)(input_tensor)
        x = BatchNormalization(name=bn_name_base + '2a')(x, training=train_bn)
        x = Activation('relu')(x)
    
        x = Conv2D(nb_filter2, (kernel_size, kernel_size), padding='same',
                      name=conv_name_base + '2b', use_bias=use_bias)(x)
        x = BatchNormalization(name=bn_name_base + '2b')(x, training=train_bn)
        x = Activation('relu')(x)
    
        x = Conv2D(nb_filter3, (1, 1), name=conv_name_base + '2c',
                      use_bias=use_bias)(x)
        x = BatchNormalization(name=bn_name_base + '2c')(x, training=train_bn)
    
        x = Add()([x, input_tensor])
        x = Activation('relu', name='res' + str(stage) + block + '_out')(x)
        return x
    
    def conv_block(input_tensor, kernel_size, filters, stage, block,
                   strides=(2, 2), use_bias=True, train_bn=True):
    
        nb_filter1, nb_filter2, nb_filter3 = filters
        conv_name_base = 'res' + str(stage) + block + '_branch'
        bn_name_base = 'bn' + str(stage) + block + '_branch'
    
        x = Conv2D(nb_filter1, (1, 1), strides=strides,
                      name=conv_name_base + '2a', use_bias=use_bias)(input_tensor)
        x = BatchNormalization(name=bn_name_base + '2a')(x, training=train_bn)
        x = Activation('relu')(x)
    
        x = Conv2D(nb_filter2, (kernel_size, kernel_size), padding='same',
                      name=conv_name_base + '2b', use_bias=use_bias)(x)
        x = BatchNormalization(name=bn_name_base + '2b')(x, training=train_bn)
        x = Activation('relu')(x)
    
        x = Conv2D(nb_filter3, (1, 1), name=conv_name_base +
                      '2c', use_bias=use_bias)(x)
        x = BatchNormalization(name=bn_name_base + '2c')(x, training=train_bn)
    
        shortcut = Conv2D(nb_filter3, (1, 1), strides=strides,
                             name=conv_name_base + '1', use_bias=use_bias)(input_tensor)
        shortcut = BatchNormalization(name=bn_name_base + '1')(shortcut, training=train_bn)
    
        x = Add()([x, shortcut])
        x = Activation('relu', name='res' + str(stage) + block + '_out')(x)
        return x
    
    def get_resnet(input_image,stage5=False, train_bn=True):
        # Stage 1
        x = ZeroPadding2D((3, 3))(input_image)
        x = Conv2D(64, (7, 7), strides=(2, 2), name='conv1', use_bias=True)(x)
        x = BatchNormalization(name='bn_conv1')(x, training=train_bn)
        x = Activation('relu')(x)
        # Height/4,Width/4,64
        C1 = x = MaxPooling2D((3, 3), strides=(2, 2), padding="same")(x)
        # Stage 2
        x = conv_block(x, 3, [64, 64, 256], stage=2, block='a', strides=(1, 1), train_bn=train_bn)
        x = identity_block(x, 3, [64, 64, 256], stage=2, block='b', train_bn=train_bn)
        # Height/4,Width/4,256
        C2 = x = identity_block(x, 3, [64, 64, 256], stage=2, block='c', train_bn=train_bn)
        # Stage 3
        x = conv_block(x, 3, [128, 128, 512], stage=3, block='a', train_bn=train_bn)
        x = identity_block(x, 3, [128, 128, 512], stage=3, block='b', train_bn=train_bn)
        x = identity_block(x, 3, [128, 128, 512], stage=3, block='c', train_bn=train_bn)
        # Height/8,Width/8,512
        C3 = x = identity_block(x, 3, [128, 128, 512], stage=3, block='d', train_bn=train_bn)
        # Stage 4
        x = conv_block(x, 3, [256, 256, 1024], stage=4, block='a', train_bn=train_bn)
        block_count = 22
        for i in range(block_count):
            x = identity_block(x, 3, [256, 256, 1024], stage=4, block=chr(98 + i), train_bn=train_bn)
        # Height/16,Width/16,1024
        C4 = x
        # Stage 5
        if stage5:
            x = conv_block(x, 3, [512, 512, 2048], stage=5, block='a', train_bn=train_bn)
            x = identity_block(x, 3, [512, 512, 2048], stage=5, block='b', train_bn=train_bn)
            # Height/32,Width/32,2048
            C5 = x = identity_block(x, 3, [512, 512, 2048], stage=5, block='c', train_bn=train_bn)
        else:
            C5 = None
        return [C1, C2, C3, C4, C5]
    

    2、特征金字塔FPN的构建

    在这里插入图片描述
    特征金字塔FPN的构建是为了实现特征多尺度的融合,在Mask R-CNN当中,我们取出在主干特征提取网络中长宽压缩了两次C2、三次C3、四次C4、五次C5的结果来进行特征金字塔结构的构造。
    在这里插入图片描述
    提取到的P2、P3、P4、P5、P6可以作为RPN网络的有效特征层,利用RPN建议框网络对有效特征层进行下一步的操作,对先验框进行解码获得建议框。

    提取到的P2、P3、P4、P5可以作为Classifier和Mask网络的有效特征层,利用Classifier预测框网络对有效特征层进行下一步的操作,对建议框解码获得最终预测框;利用Mask语义分割网络对有效特征层进行下一步的操作,获得每一个预测框内部的语义分割结果。

    实现代码如下:

    # 获得Resnet里的压缩程度不同的一些层
    _, C2, C3, C4, C5 = get_resnet(input_image, stage5=True, train_bn=config.TRAIN_BN)
    
    # 组合成特征金字塔的结构
    # P5长宽共压缩了5次
    # Height/32,Width/32,256
    P5 = Conv2D(config.TOP_DOWN_PYRAMID_SIZE, (1, 1), name='fpn_c5p5')(C5)
    # P4长宽共压缩了4次
    # Height/16,Width/16,256
    P4 = Add(name="fpn_p4add")([
        UpSampling2D(size=(2, 2), name="fpn_p5upsampled")(P5),
        Conv2D(config.TOP_DOWN_PYRAMID_SIZE, (1, 1), name='fpn_c4p4')(C4)])
    # P4长宽共压缩了3次
    # Height/8,Width/8,256
    P3 = Add(name="fpn_p3add")([
        UpSampling2D(size=(2, 2), name="fpn_p4upsampled")(P4),
        Conv2D(config.TOP_DOWN_PYRAMID_SIZE, (1, 1), name='fpn_c3p3')(C3)])
    # P4长宽共压缩了2次
    # Height/4,Width/4,256
    P2 = Add(name="fpn_p2add")([
        UpSampling2D(size=(2, 2), name="fpn_p3upsampled")(P3),
        Conv2D(config.TOP_DOWN_PYRAMID_SIZE, (1, 1), name='fpn_c2p2')(C2)])
        
    # 各自进行一次256通道的卷积,此时P2、P3、P4、P5通道数相同
    # Height/4,Width/4,256
    P2 = Conv2D(config.TOP_DOWN_PYRAMID_SIZE, (3, 3), padding="SAME", name="fpn_p2")(P2)
    # Height/8,Width/8,256
    P3 = Conv2D(config.TOP_DOWN_PYRAMID_SIZE, (3, 3), padding="SAME", name="fpn_p3")(P3)
    # Height/16,Width/16,256
    P4 = Conv2D(config.TOP_DOWN_PYRAMID_SIZE, (3, 3), padding="SAME", name="fpn_p4")(P4)
    # Height/32,Width/32,256
    P5 = Conv2D(config.TOP_DOWN_PYRAMID_SIZE, (3, 3), padding="SAME", name="fpn_p5")(P5)
    # 在建议框网络里面还有一个P6用于获取建议框
    # Height/64,Width/64,256
    P6 = MaxPooling2D(pool_size=(1, 1), strides=2, name="fpn_p6")(P5)
    
    # P2, P3, P4, P5, P6可以用于获取建议框
    rpn_feature_maps = [P2, P3, P4, P5, P6]
    # P2, P3, P4, P5用于获取mask信息
    mrcnn_feature_maps = [P2, P3, P4, P5]
    

    3、获得Proposal建议框

    在这里插入图片描述
    由上一步获得的有效特征层在图像中就是Feature Map,其有两个应用,一个是和ROIAsign结合使用、另一个是进入到Region Proposal Network进行建议框的获取。

    在进行建议框获取的时候,我们使用的有效特征层是P2、P3、P4、P5、P6,它们使用同一个RPN建议框网络获取先验框调整参数,还有先验框内部是否包含物体。

    在Mask R-cnn中,RPN建议框网络的结构和Faster RCNN中的RPN建议框网络类似。

    首先进行一次3x3的通道数为512的卷积。

    然后再分别进行一次anchors_per_location x 4的卷积 和一次anchors_per_location x 2的卷积

    anchors_per_location x 4的卷积 用于预测 公用特征层上 每一个网格点上 每一个先验框的变化情况。(为什么说是变化情况呢,这是因为Faster-RCNN的预测结果需要结合先验框获得预测框,预测结果就是先验框的变化情况。)

    anchors_per_location x 2的卷积 用于预测 公用特征层上 每一个网格点上 每一个预测框内部是否包含了物体。

    当我们输入的图片的shape是1024x1024x3的时候,公用特征层的shape就是256x256x256、128x128x256、64x64x256、32x32x256、16x16x256,相当于把输入进来的图像分割成不同大小的网格,然后每个网格默认存在3(anchors_per_location )个先验框,这些先验框有不同的大小,在图像上密密麻麻。

    anchors_per_location x 4的卷积的结果会对这些先验框进行调整,获得一个新的框。
    anchors_per_location x 2的卷积会判断上述获得的新框是否包含物体。

    到这里我们可以获得了一些有用的框,这些框会利用anchors_per_location x 2的卷积判断是否存在物体。

    到此位置还只是粗略的一个框的获取,也就是一个建议框。然后我们会在建议框里面继续找东西。

    实现代码为:

    #------------------------------------#
    #   五个不同大小的特征层会传入到
    #   RPN当中,获得建议框
    #------------------------------------#
    def rpn_graph(feature_map, anchors_per_location):
        
        shared = Conv2D(512, (3, 3), padding='same', activation='relu',
                           name='rpn_conv_shared')(feature_map)
        
        x = Conv2D(2 * anchors_per_location, (1, 1), padding='valid',
                      activation='linear', name='rpn_class_raw')(shared)
        # batch_size,num_anchors,2
        # 代表这个先验框对应的类
        rpn_class_logits = Reshape([-1,2])(x)
    
        rpn_probs = Activation(
            "softmax", name="rpn_class_xxx")(rpn_class_logits)
        
        x = Conv2D(anchors_per_location * 4, (1, 1), padding="valid",
                      activation='linear', name='rpn_bbox_pred')(shared)
        # batch_size,num_anchors,4
        # 这个先验框的调整参数
        rpn_bbox = Reshape([-1,4])(x)
    
        return [rpn_class_logits, rpn_probs, rpn_bbox]
    
    #------------------------------------#
    #   建立建议框网络模型
    #   RPN模型
    #------------------------------------#
    def build_rpn_model(anchors_per_location, depth):
        input_feature_map = Input(shape=[None, None, depth],
                                     name="input_rpn_feature_map")
        outputs = rpn_graph(input_feature_map, anchors_per_location)
        return Model([input_feature_map], outputs, name="rpn_model")
    

    4、Proposal建议框的解码

    通过第二步我们获得了许多个先验框的预测结果。预测结果包含两部分。

    anchors_per_location x 4的卷积 用于预测 有效特征层上 每一个网格点上 每一个先验框的变化情况。**

    anchors_per_location x 1的卷积 用于预测 有效特征层上 每一个网格点上 每一个预测框内部是否包含了物体。

    相当于就是将整个图像分成若干个网格;然后从每个网格中心建立3个先验框,当输入的图像是1024,1024,3的时候,总共先验框数量为196608+49152+12288+3072+768 = 261,888‬

    当输入图像shape不同时,先验框的数量也会发生改变。

    在这里插入图片描述
    先验框虽然可以代表一定的框的位置信息与框的大小信息,但是其是有限的,无法表示任意情况,因此还需要调整。

    anchors_per_location x 4中的anchors_per_location 表示了这个网格点所包含的先验框数量,其中的4表示了框的中心与长宽的调整情况。

    实现代码如下:

    #----------------------------------------------------------#
    #   Proposal Layer
    #   该部分代码用于将先验框转化成建议框
    #----------------------------------------------------------#
    
    def apply_box_deltas_graph(boxes, deltas):
        # 计算先验框的中心和宽高
        height = boxes[:, 2] - boxes[:, 0]
        width = boxes[:, 3] - boxes[:, 1]
        center_y = boxes[:, 0] + 0.5 * height
        center_x = boxes[:, 1] + 0.5 * width
        # 计算出调整后的先验框的中心和宽高
        center_y += deltas[:, 0] * height
        center_x += deltas[:, 1] * width
        height *= tf.exp(deltas[:, 2])
        width *= tf.exp(deltas[:, 3])
        # 计算左上角和右下角的点的坐标
        y1 = center_y - 0.5 * height
        x1 = center_x - 0.5 * width
        y2 = y1 + height
        x2 = x1 + width
        result = tf.stack([y1, x1, y2, x2], axis=1, name="apply_box_deltas_out")
        return result
    
    
    def clip_boxes_graph(boxes, window):
        """
        boxes: [N, (y1, x1, y2, x2)]
        window: [4] in the form y1, x1, y2, x2
        """
        # Split
        wy1, wx1, wy2, wx2 = tf.split(window, 4)
        y1, x1, y2, x2 = tf.split(boxes, 4, axis=1)
        # Clip
        y1 = tf.maximum(tf.minimum(y1, wy2), wy1)
        x1 = tf.maximum(tf.minimum(x1, wx2), wx1)
        y2 = tf.maximum(tf.minimum(y2, wy2), wy1)
        x2 = tf.maximum(tf.minimum(x2, wx2), wx1)
        clipped = tf.concat([y1, x1, y2, x2], axis=1, name="clipped_boxes")
        clipped.set_shape((clipped.shape[0], 4))
        return clipped
    
    class ProposalLayer(Layer):
    
        def __init__(self, proposal_count, nms_threshold, config=None, **kwargs):
            super(ProposalLayer, self).__init__(**kwargs)
            self.config = config
            self.proposal_count = proposal_count
            self.nms_threshold = nms_threshold
        # [rpn_class, rpn_bbox, anchors]
        def call(self, inputs):
    
            # 代表这个先验框内部是否有物体[batch, num_rois, 1]
            scores = inputs[0][:, :, 1]
    
            # 代表这个先验框的调整参数[batch, num_rois, 4]
            deltas = inputs[1]
    
            # [0.1 0.1 0.2 0.2],改变数量级
            deltas = deltas * np.reshape(self.config.RPN_BBOX_STD_DEV, [1, 1, 4])
    
            # Anchors
            anchors = inputs[2]
    
            # 筛选出得分前6000个的框
            pre_nms_limit = tf.minimum(self.config.PRE_NMS_LIMIT, tf.shape(anchors)[1])
            # 获得这些框的索引
            ix = tf.nn.top_k(scores, pre_nms_limit, sorted=True,
                             name="top_anchors").indices
            
            # 获得这些框的得分
            scores = utils.batch_slice([scores, ix], lambda x, y: tf.gather(x, y),
                                       self.config.IMAGES_PER_GPU)
            # 获得这些框的调整参数
            deltas = utils.batch_slice([deltas, ix], lambda x, y: tf.gather(x, y),
                                       self.config.IMAGES_PER_GPU)
            # 获得这些框对应的先验框
            pre_nms_anchors = utils.batch_slice([anchors, ix], lambda a, x: tf.gather(a, x),
                                        self.config.IMAGES_PER_GPU,
                                        names=["pre_nms_anchors"])
    
            # [batch, N, (y1, x1, y2, x2)]
            # 对先验框进行解码
            boxes = utils.batch_slice([pre_nms_anchors, deltas],
                                      lambda x, y: apply_box_deltas_graph(x, y),
                                      self.config.IMAGES_PER_GPU,
                                      names=["refined_anchors"])
    
            # [batch, N, (y1, x1, y2, x2)]
            # 防止超出图片范围
            window = np.array([0, 0, 1, 1], dtype=np.float32)
            boxes = utils.batch_slice(boxes,
                                      lambda x: clip_boxes_graph(x, window),
                                      self.config.IMAGES_PER_GPU,
                                      names=["refined_anchors_clipped"])
    
    
            # 非极大抑制
            def nms(boxes, scores):
                indices = tf.image.non_max_suppression(
                    boxes, scores, self.proposal_count,
                    self.nms_threshold, name="rpn_non_max_suppression")
                proposals = tf.gather(boxes, indices)
                # 如果数量达不到设置的建议框数量的话
                # 就padding
                padding = tf.maximum(self.proposal_count - tf.shape(proposals)[0], 0)
                proposals = tf.pad(proposals, [(0, padding), (0, 0)])
                return proposals
    
            proposals = utils.batch_slice([boxes, scores], nms,
                                          self.config.IMAGES_PER_GPU)
            return proposals
    
        def compute_output_shape(self, input_shape):
            return (None, self.proposal_count, 4)
    

    5、对Proposal建议框加以利用(Roi Align)

    在这里插入图片描述
    让我们对建议框有一个整体的理解:
    事实上建议框就是对图片哪一个区域有物体存在进行初步筛选。

    实际上,Mask R-CNN到这里的操作是,通过主干特征提取网络,我们可以获得多个公用特征层,然后建议框会对这些公用特征层进行截取。

    其实公用特征层里的每一个点相当于原图片上某个区域内部所有特征的浓缩。

    建议框会对其对应的公用特征层进行截取,然后将截取的结果进行resize,在classifier模型里,截取后的内容会resize到7x7x256的大小。在mask模型里,截取后的内容会resize到14x14x256的大小。
    在这里插入图片描述
    在利用建议框对公用特征层进行截取的时候要注意,要找到建议框属于那个特征层,这个要从建议框的大小进行判断。

    在classifier模型里,其会利用一次通道数为1024的7x7的卷积和一次通道数为1024的1x1的卷积对ROIAlign获得的7x7x256的区域进行卷积,两次通道数为1024卷积用于模拟两次1024的全连接,然后再分别全连接到num_classes和num_classes * 4上,分别代表这个建议框内的物体,以及这个建议框的调整参数。

    在mask模型里,其首先会对resize后的局部特征层进行四次3x3的256通道的卷积,再进行一次反卷积,再进行一次通道数为num_classes的卷积,最终结果代表每一个像素点分的类。最终的shape为28x28xnum_classes,代表每个像素点的类别。

    #------------------------------------#
    #   五个不同大小的特征层会传入到
    #   RPN当中,获得建议框
    #------------------------------------#
    def rpn_graph(feature_map, anchors_per_location):
        
        shared = Conv2D(512, (3, 3), padding='same', activation='relu',
                           name='rpn_conv_shared')(feature_map)
        
        x = Conv2D(2 * anchors_per_location, (1, 1), padding='valid',
                      activation='linear', name='rpn_class_raw')(shared)
        # batch_size,num_anchors,2
        # 代表这个先验框对应的类
        rpn_class_logits = Reshape([-1,2])(x)
    
        rpn_probs = Activation(
            "softmax", name="rpn_class_xxx")(rpn_class_logits)
        
        x = Conv2D(anchors_per_location * 4, (1, 1), padding="valid",
                      activation='linear', name='rpn_bbox_pred')(shared)
        # batch_size,num_anchors,4
        # 这个先验框的调整参数
        rpn_bbox = Reshape([-1,4])(x)
    
        return [rpn_class_logits, rpn_probs, rpn_bbox]
    
    #------------------------------------#
    #   建立建议框网络模型
    #   RPN模型
    #------------------------------------#
    def build_rpn_model(anchors_per_location, depth):
        input_feature_map = Input(shape=[None, None, depth],
                                     name="input_rpn_feature_map")
        outputs = rpn_graph(input_feature_map, anchors_per_location)
        return Model([input_feature_map], outputs, name="rpn_model")
    
    
    #------------------------------------#
    #   建立classifier模型
    #   这个模型的预测结果会调整建议框
    #   获得最终的预测框
    #------------------------------------#
    def fpn_classifier_graph(rois, feature_maps, image_meta,
                             pool_size, num_classes, train_bn=True,
                             fc_layers_size=1024):
        # ROI Pooling,利用建议框在特征层上进行截取
        # Shape: [batch, num_rois, POOL_SIZE, POOL_SIZE, channels]
        x = PyramidROIAlign([pool_size, pool_size],
                            name="roi_align_classifier")([rois, image_meta] + feature_maps)
    
        # Shape: [batch, num_rois, 1, 1, fc_layers_size],相当于两次全连接
        x = TimeDistributed(Conv2D(fc_layers_size, (pool_size, pool_size), padding="valid"),
                               name="mrcnn_class_conv1")(x)
        x = TimeDistributed(BatchNormalization(), name='mrcnn_class_bn1')(x, training=train_bn)
        x = Activation('relu')(x)
    
        # Shape: [batch, num_rois, 1, 1, fc_layers_size]
        x = TimeDistributed(Conv2D(fc_layers_size, (1, 1)),
                               name="mrcnn_class_conv2")(x)
        x = TimeDistributed(BatchNormalization(), name='mrcnn_class_bn2')(x, training=train_bn)
        x = Activation('relu')(x)
    
        # Shape: [batch, num_rois, fc_layers_size]
        shared = Lambda(lambda x: K.squeeze(K.squeeze(x, 3), 2),
                           name="pool_squeeze")(x)
    
        # Classifier head
        # 这个的预测结果代表这个先验框内部的物体的种类
        mrcnn_class_logits = TimeDistributed(Dense(num_classes),
                                                name='mrcnn_class_logits')(shared)
        mrcnn_probs = TimeDistributed(Activation("softmax"),
                                         name="mrcnn_class")(mrcnn_class_logits)
    
    
        # BBox head
        # 这个的预测结果会对先验框进行调整
        # [batch, num_rois, NUM_CLASSES * (dy, dx, log(dh), log(dw))]
        x = TimeDistributed(Dense(num_classes * 4, activation='linear'),
                               name='mrcnn_bbox_fc')(shared)
        # Reshape to [batch, num_rois, NUM_CLASSES, (dy, dx, log(dh), log(dw))]
        mrcnn_bbox = Reshape((-1, num_classes, 4), name="mrcnn_bbox")(x)
    
        return mrcnn_class_logits, mrcnn_probs, mrcnn_bbox
    
    
    
    def build_fpn_mask_graph(rois, feature_maps, image_meta,
                             pool_size, num_classes, train_bn=True):
        # ROI Pooling,利用建议框在特征层上进行截取
        # Shape: [batch, num_rois, MASK_POOL_SIZE, MASK_POOL_SIZE, channels]
        x = PyramidROIAlign([pool_size, pool_size],
                            name="roi_align_mask")([rois, image_meta] + feature_maps)
    
        # Shape: [batch, num_rois, MASK_POOL_SIZE, MASK_POOL_SIZE, channels]
        x = TimeDistributed(Conv2D(256, (3, 3), padding="same"),
                               name="mrcnn_mask_conv1")(x)
        x = TimeDistributed(BatchNormalization(),
                               name='mrcnn_mask_bn1')(x, training=train_bn)
        x = Activation('relu')(x)
    
        # Shape: [batch, num_rois, MASK_POOL_SIZE, MASK_POOL_SIZE, channels]
        x = TimeDistributed(Conv2D(256, (3, 3), padding="same"),
                               name="mrcnn_mask_conv2")(x)
        x = TimeDistributed(BatchNormalization(),
                               name='mrcnn_mask_bn2')(x, training=train_bn)
        x = Activation('relu')(x)
    
        # Shape: [batch, num_rois, MASK_POOL_SIZE, MASK_POOL_SIZE, channels]
        x = TimeDistributed(Conv2D(256, (3, 3), padding="same"),
                               name="mrcnn_mask_conv3")(x)
        x = TimeDistributed(BatchNormalization(),
                               name='mrcnn_mask_bn3')(x, training=train_bn)
        x = Activation('relu')(x)
    
        # Shape: [batch, num_rois, MASK_POOL_SIZE, MASK_POOL_SIZE, channels]
        x = TimeDistributed(Conv2D(256, (3, 3), padding="same"),
                               name="mrcnn_mask_conv4")(x)
        x = TimeDistributed(BatchNormalization(),
                               name='mrcnn_mask_bn4')(x, training=train_bn)
        x = Activation('relu')(x)
    
        # Shape: [batch, num_rois, 2xMASK_POOL_SIZE, 2xMASK_POOL_SIZE, channels]
        x = TimeDistributed(Conv2DTranspose(256, (2, 2), strides=2, activation="relu"),
                               name="mrcnn_mask_deconv")(x)
        # 反卷积后再次进行一个1x1卷积调整通道,使其最终数量为numclasses,代表分的类
        x = TimeDistributed(Conv2D(num_classes, (1, 1), strides=1, activation="sigmoid"),
                               name="mrcnn_mask")(x)
        return x
    
    
    #----------------------------------------------------------#
    #   ROIAlign Layer
    #   利用建议框在特征层上截取内容
    #----------------------------------------------------------#
    
    def log2_graph(x):
        return tf.log(x) / tf.log(2.0)
    
    def parse_image_meta_graph(meta):
        """
        将meta里面的参数进行分割
        """
        image_id = meta[:, 0]
        original_image_shape = meta[:, 1:4]
        image_shape = meta[:, 4:7]
        window = meta[:, 7:11]  # (y1, x1, y2, x2) window of image in in pixels
        scale = meta[:, 11]
        active_class_ids = meta[:, 12:]
        return {
            "image_id": image_id,
            "original_image_shape": original_image_shape,
            "image_shape": image_shape,
            "window": window,
            "scale": scale,
            "active_class_ids": active_class_ids,
        }
    
    class PyramidROIAlign(Layer):
        def __init__(self, pool_shape, **kwargs):
            super(PyramidROIAlign, self).__init__(**kwargs)
            self.pool_shape = tuple(pool_shape)
    
        def call(self, inputs):
            # 建议框的位置
            boxes = inputs[0]
    
            # image_meta包含了一些必要的图片信息
            image_meta = inputs[1]
    
            # 取出所有的特征层[batch, height, width, channels]
            feature_maps = inputs[2:]
    
            y1, x1, y2, x2 = tf.split(boxes, 4, axis=2)
            h = y2 - y1
            w = x2 - x1
    
            # 获得输入进来的图像的大小
            image_shape = parse_image_meta_graph(image_meta)['image_shape'][0]
            
            # 通过建议框的大小找到这个建议框属于哪个特征层
            image_area = tf.cast(image_shape[0] * image_shape[1], tf.float32)
            roi_level = log2_graph(tf.sqrt(h * w) / (224.0 / tf.sqrt(image_area)))
            roi_level = tf.minimum(5, tf.maximum(
                2, 4 + tf.cast(tf.round(roi_level), tf.int32)))
            # batch_size, box_num
            roi_level = tf.squeeze(roi_level, 2)
    
            # Loop through levels and apply ROI pooling to each. P2 to P5.
            pooled = []
            box_to_level = []
            # 分别在P2-P5中进行截取
            for i, level in enumerate(range(2, 6)):
                # 找到每个特征层对应box
                ix = tf.where(tf.equal(roi_level, level))
                level_boxes = tf.gather_nd(boxes, ix)
                box_to_level.append(ix)
    
                # 获得这些box所属的图片
                box_indices = tf.cast(ix[:, 0], tf.int32)
    
                # 停止梯度下降
                level_boxes = tf.stop_gradient(level_boxes)
                box_indices = tf.stop_gradient(box_indices)
    
                # Result: [batch * num_boxes, pool_height, pool_width, channels]
                pooled.append(tf.image.crop_and_resize(
                    feature_maps[i], level_boxes, box_indices, self.pool_shape,
                    method="bilinear"))
    
            pooled = tf.concat(pooled, axis=0)
    
            # 将顺序和所属的图片进行堆叠
            box_to_level = tf.concat(box_to_level, axis=0)
            box_range = tf.expand_dims(tf.range(tf.shape(box_to_level)[0]), 1)
            box_to_level = tf.concat([tf.cast(box_to_level, tf.int32), box_range],
                                     axis=1)
    
            # box_to_level[:, 0]表示第几张图
            # box_to_level[:, 1]表示第几张图里的第几个框
            sorting_tensor = box_to_level[:, 0] * 100000 + box_to_level[:, 1]
            # 进行排序,将同一张图里的某一些聚集在一起
            ix = tf.nn.top_k(sorting_tensor, k=tf.shape(
                box_to_level)[0]).indices[::-1]
    
            # 按顺序获得图片的索引
            ix = tf.gather(box_to_level[:, 2], ix)
            pooled = tf.gather(pooled, ix)
    
            # 重新reshape为原来的格式
            # 也就是
            # Shape: [batch, num_rois, POOL_SIZE, POOL_SIZE, channels]
            shape = tf.concat([tf.shape(boxes)[:2], tf.shape(pooled)[1:]], axis=0)
            pooled = tf.reshape(pooled, shape)
            return pooled
    
        def compute_output_shape(self, input_shape):
            return input_shape[0][:2] + self.pool_shape + (input_shape[2][-1], )
    

    6、预测框的解码

    在第四部分获得的建议框也代表了图片上的某一些区域,它在后面的在classifier模型里也起到了先验框的作用。

    也就是classifier模型的预测结果,代表了建议框内部物体的种类和调整参数。

    建议框调整后的结果,也就是最终的预测结果,这个预测结果就可以在图片上进行绘制了。

    预测框的解码过程包括了如下几个步骤:
    1、取出不属于背景,并且得分大于config.DETECTION_MIN_CONFIDENCE的建议框。
    2、然后利用建议框和classifier模型的预测结果进行解码,获得最终预测框的位置。
    3、利用得分和最终预测框的位置进行非极大抑制,防止重复检测。

    建议框解码过程的代码如下:

    #----------------------------------------------------------#
    #   Detection Layer
    #----------------------------------------------------------#
    
    def refine_detections_graph(rois, probs, deltas, window, config):
        """细化分类建议并过滤重叠部分并返回最终结果探测。
        Inputs:
            rois: [N, (y1, x1, y2, x2)] in normalized coordinates
            probs: [N, num_classes]. Class probabilities.
            deltas: [N, num_classes, (dy, dx, log(dh), log(dw))]. Class-specific
                    bounding box deltas.
            window: (y1, x1, y2, x2) in normalized coordinates. The part of the image
                that contains the image excluding the padding.
    
        Returns detections shaped: [num_detections, (y1, x1, y2, x2, class_id, score)] where
            coordinates are normalized.
        """
        # 找到得分最高的类
        class_ids = tf.argmax(probs, axis=1, output_type=tf.int32)
        # 序号+类
        indices = tf.stack([tf.range(probs.shape[0]), class_ids], axis=1)
        # 取出成绩
        class_scores = tf.gather_nd(probs, indices)
        # 还有框的调整参数
        deltas_specific = tf.gather_nd(deltas, indices)
        # 进行解码
        # Shape: [boxes, (y1, x1, y2, x2)] in normalized coordinates
        refined_rois = apply_box_deltas_graph(
            rois, deltas_specific * config.BBOX_STD_DEV)
        # 防止超出0-1
        refined_rois = clip_boxes_graph(refined_rois, window)
    
        # 去除背景
        keep = tf.where(class_ids > 0)[:, 0]
        # 去除背景和得分小的区域
        if config.DETECTION_MIN_CONFIDENCE:
            conf_keep = tf.where(class_scores >= config.DETECTION_MIN_CONFIDENCE)[:, 0]
            keep = tf.sets.set_intersection(tf.expand_dims(keep, 0),
                                            tf.expand_dims(conf_keep, 0))
            keep = tf.sparse_tensor_to_dense(keep)[0]
    
        # 获得除去背景并且得分较高的框还有种类与得分
        # 1. Prepare variables
        pre_nms_class_ids = tf.gather(class_ids, keep)
        pre_nms_scores = tf.gather(class_scores, keep)
        pre_nms_rois = tf.gather(refined_rois,   keep)
        unique_pre_nms_class_ids = tf.unique(pre_nms_class_ids)[0]
    
        def nms_keep_map(class_id):
    
            ixs = tf.where(tf.equal(pre_nms_class_ids, class_id))[:, 0]
    
            class_keep = tf.image.non_max_suppression(
                    tf.gather(pre_nms_rois, ixs),
                    tf.gather(pre_nms_scores, ixs),
                    max_output_size=config.DETECTION_MAX_INSTANCES,
                    iou_threshold=config.DETECTION_NMS_THRESHOLD)
    
            class_keep = tf.gather(keep, tf.gather(ixs, class_keep))
    
            gap = config.DETECTION_MAX_INSTANCES - tf.shape(class_keep)[0]
            class_keep = tf.pad(class_keep, [(0, gap)],
                                mode='CONSTANT', constant_values=-1)
    
            class_keep.set_shape([config.DETECTION_MAX_INSTANCES])
            return class_keep
    
        # 2. 进行非极大抑制
        nms_keep = tf.map_fn(nms_keep_map, unique_pre_nms_class_ids,
                             dtype=tf.int64)
        # 3. 找到符合要求的需要被保留的建议框
        nms_keep = tf.reshape(nms_keep, [-1])
        nms_keep = tf.gather(nms_keep, tf.where(nms_keep > -1)[:, 0])
        # 4. Compute intersection between keep and nms_keep
        keep = tf.sets.set_intersection(tf.expand_dims(keep, 0),
                                        tf.expand_dims(nms_keep, 0))
        keep = tf.sparse_tensor_to_dense(keep)[0]
    
        # 寻找得分最高的num_keep个框
        roi_count = config.DETECTION_MAX_INSTANCES
        class_scores_keep = tf.gather(class_scores, keep)
        num_keep = tf.minimum(tf.shape(class_scores_keep)[0], roi_count)
        top_ids = tf.nn.top_k(class_scores_keep, k=num_keep, sorted=True)[1]
        keep = tf.gather(keep, top_ids)
    
        # Arrange output as [N, (y1, x1, y2, x2, class_id, score)]
        detections = tf.concat([
            tf.gather(refined_rois, keep),
            tf.to_float(tf.gather(class_ids, keep))[..., tf.newaxis],
            tf.gather(class_scores, keep)[..., tf.newaxis]
            ], axis=1)
    
        # 如果达不到数量的话就padding
        gap = config.DETECTION_MAX_INSTANCES - tf.shape(detections)[0]
        detections = tf.pad(detections, [(0, gap), (0, 0)], "CONSTANT")
        return detections
    
    def norm_boxes_graph(boxes, shape):
        h, w = tf.split(tf.cast(shape, tf.float32), 2)
        scale = tf.concat([h, w, h, w], axis=-1) - tf.constant(1.0)
        shift = tf.constant([0., 0., 1., 1.])
        return tf.divide(boxes - shift, scale)
    
    class DetectionLayer(Layer):
    
        def __init__(self, config=None, **kwargs):
            super(DetectionLayer, self).__init__(**kwargs)
            self.config = config
    
        def call(self, inputs):
            rois = inputs[0]
            mrcnn_class = inputs[1]
            mrcnn_bbox = inputs[2]
            image_meta = inputs[3]
    
            # 找到window的小数形式
            m = parse_image_meta_graph(image_meta)
            image_shape = m['image_shape'][0]
            window = norm_boxes_graph(m['window'], image_shape[:2])
    
            # Run detection refinement graph on each item in the batch
            detections_batch = utils.batch_slice(
                [rois, mrcnn_class, mrcnn_bbox, window],
                lambda x, y, w, z: refine_detections_graph(x, y, w, z, self.config),
                self.config.IMAGES_PER_GPU)
    
            # Reshape output
            # [batch, num_detections, (y1, x1, y2, x2, class_id, class_score)] in
            # normalized coordinates
            return tf.reshape(
                detections_batch,
                [self.config.BATCH_SIZE, self.config.DETECTION_MAX_INSTANCES, 6])
    
        def compute_output_shape(self, input_shape):
            return (None, self.config.DETECTION_MAX_INSTANCES, 6)
    

    7、mask语义分割信息的获取

    在第六步中,我们获得了最终的预测框,这个预测框相比于之前获得的建议框更加准确,因此我们把这个预测框作为mask模型的区域截取部分,利用这个预测框对mask模型中用到的公用特征层进行截取。

    截取后,利用mask模型再对像素点进行分类,获得语义分割结果。

    二、训练部分

    Faster-RCNN训练所用的损失函数由几个部分组成,一部分是建议框网络的损失函数,一部分是classifier网络的损失函数,另一部分是mask网络的损失函数。

    1、建议框网络的训练

    公用特征层如果要获得建议框的预测结果,需要再进行一次3x3的卷积后,进行一个anchors_per_location x 1通道的1x1卷积,还有一个anchors_per_location x 4通道的1x1卷积。

    在Mask R-CNN中,anchors_per_location 也就是先验框的数量默认情况下是3,所以两个1x1卷积的结果实际上也就是:

    anchors_per_location x 4的卷积 用于预测 有效特征层上 每一个网格点上 每一个先验框的变化情况。**

    anchors_per_location x 1的卷积 用于预测 有效特征层上 每一个网格点上 每一个建议框内部是否包含了物体。

    也就是说,我们直接利用Mask R-CNN建议框网络预测到的结果,并不是建议框在图片上的真实位置,需要解码才能得到真实位置。

    而在训练的时候,我们需要计算loss函数,这个loss函数是相对于Mask R-CNN建议框网络的预测结果的。我们需要把图片输入到当前的Mask R-CNN建议框的网络中,得到建议框的结果;同时还需要进行编码,这个编码是把真实框的位置信息格式转化为Mask R-CNN建议框预测结果的格式信息

    也就是,我们需要找到 每一张用于训练的图片每一个真实框对应的先验框,并求出如果想要得到这样一个真实框,我们的建议框预测结果应该是怎么样的。

    从建议框预测结果获得真实框的过程被称作解码,而从真实框获得建议框预测结果的过程就是编码的过程。

    因此我们只需要将解码过程逆过来就是编码过程了。

    实现代码如下:

    
    def build_rpn_targets(image_shape, anchors, gt_class_ids, gt_boxes, config):
        # 1代表正样本
        # -1代表负样本
        # 0代表忽略
        rpn_match = np.zeros([anchors.shape[0]], dtype=np.int32)
        # 创建该部分内容利用先验框和真实框进行编码
        rpn_bbox = np.zeros((config.RPN_TRAIN_ANCHORS_PER_IMAGE, 4))
    
        '''
        iscrowd=0的时候,表示这是一个单独的物体,轮廓用Polygon(多边形的点)表示,
        iscrowd=1的时候表示两个没有分开的物体,轮廓用RLE编码表示,比如说一张图片里面有三个人,
        一个人单独站一边,另外两个搂在一起(标注的时候距离太近分不开了),这个时候,
        单独的那个人的注释里面的iscrowing=0,segmentation用Polygon表示,
        而另外两个用放在同一个anatation的数组里面用一个segmention的RLE编码形式表示
        '''
        crowd_ix = np.where(gt_class_ids < 0)[0]
        if crowd_ix.shape[0] > 0:
            non_crowd_ix = np.where(gt_class_ids > 0)[0]
            crowd_boxes = gt_boxes[crowd_ix]
            gt_class_ids = gt_class_ids[non_crowd_ix]
            gt_boxes = gt_boxes[non_crowd_ix]
            crowd_overlaps = utils.compute_overlaps(anchors, crowd_boxes)
            crowd_iou_max = np.amax(crowd_overlaps, axis=1)
            no_crowd_bool = (crowd_iou_max < 0.001)
        else:
            no_crowd_bool = np.ones([anchors.shape[0]], dtype=bool)
    
        # 计算先验框和真实框的重合程度 [num_anchors, num_gt_boxes]
        overlaps = utils.compute_overlaps(anchors, gt_boxes)
    
        # 1. 重合程度小于0.3则代表为负样本
        anchor_iou_argmax = np.argmax(overlaps, axis=1)
        anchor_iou_max = overlaps[np.arange(overlaps.shape[0]), anchor_iou_argmax]
        rpn_match[(anchor_iou_max < 0.3) & (no_crowd_bool)] = -1
        # 2. 每个真实框重合度最大的先验框是正样本
        gt_iou_argmax = np.argwhere(overlaps == np.max(overlaps, axis=0))[:,0]
        rpn_match[gt_iou_argmax] = 1
        # 3. 重合度大于0.7则代表为正样本
        rpn_match[anchor_iou_max >= 0.7] = 1
    
        # 正负样本平衡
        # 找到正样本的索引
        ids = np.where(rpn_match == 1)[0]
        # 如果大于(config.RPN_TRAIN_ANCHORS_PER_IMAGE // 2)则删掉一些
        extra = len(ids) - (config.RPN_TRAIN_ANCHORS_PER_IMAGE // 2)
        if extra > 0:
            ids = np.random.choice(ids, extra, replace=False)
            rpn_match[ids] = 0
        # 找到负样本的索引
        ids = np.where(rpn_match == -1)[0]
        # 使得总数为config.RPN_TRAIN_ANCHORS_PER_IMAGE
        extra = len(ids) - (config.RPN_TRAIN_ANCHORS_PER_IMAGE -
                            np.sum(rpn_match == 1))
        if extra > 0:
            # Rest the extra ones to neutral
            ids = np.random.choice(ids, extra, replace=False)
            rpn_match[ids] = 0
    
        # 找到内部真实存在物体的先验框,进行编码
        ids = np.where(rpn_match == 1)[0]
        ix = 0 
        for i, a in zip(ids, anchors[ids]):
            gt = gt_boxes[anchor_iou_argmax[i]]
            # 计算真实框的中心,高宽
            gt_h = gt[2] - gt[0]
            gt_w = gt[3] - gt[1]
            gt_center_y = gt[0] + 0.5 * gt_h
            gt_center_x = gt[1] + 0.5 * gt_w
            # 计算先验框中心,高宽
            a_h = a[2] - a[0]
            a_w = a[3] - a[1]
            a_center_y = a[0] + 0.5 * a_h
            a_center_x = a[1] + 0.5 * a_w
            # 编码运算
            rpn_bbox[ix] = [
                (gt_center_y - a_center_y) / a_h,
                (gt_center_x - a_center_x) / a_w,
                np.log(gt_h / a_h),
                np.log(gt_w / a_w),
            ]
            # 改变数量级
            rpn_bbox[ix] /= config.RPN_BBOX_STD_DEV
            ix += 1
    
        return rpn_match, rpn_bbox
    

    利用上述代码我们可以获得,真实框对应的所有的iou较大先验框,并计算了真实框对应的所有iou较大的先验框应该有的预测结果。

    Mask R-CNN会忽略一些重合度相对较高但是不是非常高的先验框,一般将重合度在0.3-0.7之间的先验框进行忽略。

    利用建议框网络应该有的预测结果和实际上的预测结果进行对比就可以获得建议框网络的loss。

    2、Classiffier模型的训练

    上一部分提供了RPN网络的loss,在Mask R-CNN的模型中,我们还需要对建议框进行调整获得最终的预测框。在classiffier模型中,建议框相当于是先验框。

    因此,我们需要计算所有建议框和真实框的重合程度,并进行筛选,如果某个真实框和建议框的重合程度大于0.5则认为该建议框为正样本,如果重合程度小于0.5则认为该建议框为负样本

    因此我们可以对真实框进行编码,这个编码是相对于建议框的,也就是,当我们存在这些建议框的时候,我们的Classiffier模型需要有什么样的预测结果才能将这些建议框调整成真实框。

    实现代码如下:

    #----------------------------------------------------------#
    #   Detection Target Layer
    #   该部分代码会输入建议框
    #   判断建议框和真实框的重合情况
    #   筛选出内部包含物体的建议框
    #   利用建议框和真实框编码
    #   调整mask的格式使得其和预测格式相同
    #----------------------------------------------------------#
    
    def overlaps_graph(boxes1, boxes2):
        """
        用于计算boxes1和boxes2的重合程度
        boxes1, boxes2: [N, (y1, x1, y2, x2)].
        返回 [len(boxes1), len(boxes2)]
        """
        b1 = tf.reshape(tf.tile(tf.expand_dims(boxes1, 1),
                                [1, 1, tf.shape(boxes2)[0]]), [-1, 4])
        b2 = tf.tile(boxes2, [tf.shape(boxes1)[0], 1])
        b1_y1, b1_x1, b1_y2, b1_x2 = tf.split(b1, 4, axis=1)
        b2_y1, b2_x1, b2_y2, b2_x2 = tf.split(b2, 4, axis=1)
        y1 = tf.maximum(b1_y1, b2_y1)
        x1 = tf.maximum(b1_x1, b2_x1)
        y2 = tf.minimum(b1_y2, b2_y2)
        x2 = tf.minimum(b1_x2, b2_x2)
        intersection = tf.maximum(x2 - x1, 0) * tf.maximum(y2 - y1, 0)
        b1_area = (b1_y2 - b1_y1) * (b1_x2 - b1_x1)
        b2_area = (b2_y2 - b2_y1) * (b2_x2 - b2_x1)
        union = b1_area + b2_area - intersection
        iou = intersection / union
        overlaps = tf.reshape(iou, [tf.shape(boxes1)[0], tf.shape(boxes2)[0]])
        return overlaps
    
    
    def detection_targets_graph(proposals, gt_class_ids, gt_boxes, gt_masks, config):
        asserts = [
            tf.Assert(tf.greater(tf.shape(proposals)[0], 0), [proposals],
                      name="roi_assertion"),
        ]
        with tf.control_dependencies(asserts):
            proposals = tf.identity(proposals)
    
        # 移除之前获得的padding的部分
        proposals, _ = trim_zeros_graph(proposals, name="trim_proposals")
        gt_boxes, non_zeros = trim_zeros_graph(gt_boxes, name="trim_gt_boxes")
        gt_class_ids = tf.boolean_mask(gt_class_ids, non_zeros,
                                       name="trim_gt_class_ids")
        gt_masks = tf.gather(gt_masks, tf.where(non_zeros)[:, 0], axis=2,
                             name="trim_gt_masks")
    
        # Handle COCO crowds
        # A crowd box in COCO is a bounding box around several instances. Exclude
        # them from training. A crowd box is given a negative class ID.
        crowd_ix = tf.where(gt_class_ids < 0)[:, 0]
        non_crowd_ix = tf.where(gt_class_ids > 0)[:, 0]
        crowd_boxes = tf.gather(gt_boxes, crowd_ix)
        gt_class_ids = tf.gather(gt_class_ids, non_crowd_ix)
        gt_boxes = tf.gather(gt_boxes, non_crowd_ix)
        gt_masks = tf.gather(gt_masks, non_crowd_ix, axis=2)
    
        # 计算建议框和所有真实框的重合程度 [proposals, gt_boxes]
        overlaps = overlaps_graph(proposals, gt_boxes)
    
        # 计算和 crowd boxes 的重合程度 [proposals, crowd_boxes]
        crowd_overlaps = overlaps_graph(proposals, crowd_boxes)
        crowd_iou_max = tf.reduce_max(crowd_overlaps, axis=1)
        no_crowd_bool = (crowd_iou_max < 0.001)
    
        # Determine positive and negative ROIs
        roi_iou_max = tf.reduce_max(overlaps, axis=1)
        # 1. 正样本建议框和真实框的重合程度大于0.5
        positive_roi_bool = (roi_iou_max >= 0.5)
        positive_indices = tf.where(positive_roi_bool)[:, 0]
        # 2. 负样本建议框和真实框的重合程度小于0.5,Skip crowds.
        negative_indices = tf.where(tf.logical_and(roi_iou_max < 0.5, no_crowd_bool))[:, 0]
    
        # Subsample ROIs. Aim for 33% positive
        # 进行正负样本的平衡
        # 取出最大33%的正样本
        positive_count = int(config.TRAIN_ROIS_PER_IMAGE *
                             config.ROI_POSITIVE_RATIO)
        positive_indices = tf.random_shuffle(positive_indices)[:positive_count]
        positive_count = tf.shape(positive_indices)[0]
        # 保持正负样本比例
        r = 1.0 / config.ROI_POSITIVE_RATIO
        negative_count = tf.cast(r * tf.cast(positive_count, tf.float32), tf.int32) - positive_count
        negative_indices = tf.random_shuffle(negative_indices)[:negative_count]
        # 获得正样本和负样本
        positive_rois = tf.gather(proposals, positive_indices)
        negative_rois = tf.gather(proposals, negative_indices)
    
        # 获取建议框和真实框重合程度
        positive_overlaps = tf.gather(overlaps, positive_indices)
        
        # 判断是否有真实框
        roi_gt_box_assignment = tf.cond(
            tf.greater(tf.shape(positive_overlaps)[1], 0),
            true_fn = lambda: tf.argmax(positive_overlaps, axis=1),
            false_fn = lambda: tf.cast(tf.constant([]),tf.int64)
        )
        # 找到每一个建议框对应的真实框和种类
        roi_gt_boxes = tf.gather(gt_boxes, roi_gt_box_assignment)
        roi_gt_class_ids = tf.gather(gt_class_ids, roi_gt_box_assignment)
    
        # 解码获得网络应该有得预测结果
        deltas = utils.box_refinement_graph(positive_rois, roi_gt_boxes)
        deltas /= config.BBOX_STD_DEV
    
        # 切换mask的形式[N, height, width, 1]
        transposed_masks = tf.expand_dims(tf.transpose(gt_masks, [2, 0, 1]), -1)
        
        # 取出对应的层
        roi_masks = tf.gather(transposed_masks, roi_gt_box_assignment)
    
        # Compute mask targets
        boxes = positive_rois
        if config.USE_MINI_MASK:
            # Transform ROI coordinates from normalized image space
            # to normalized mini-mask space.
            y1, x1, y2, x2 = tf.split(positive_rois, 4, axis=1)
            gt_y1, gt_x1, gt_y2, gt_x2 = tf.split(roi_gt_boxes, 4, axis=1)
            gt_h = gt_y2 - gt_y1
            gt_w = gt_x2 - gt_x1
            y1 = (y1 - gt_y1) / gt_h
            x1 = (x1 - gt_x1) / gt_w
            y2 = (y2 - gt_y1) / gt_h
            x2 = (x2 - gt_x1) / gt_w
            boxes = tf.concat([y1, x1, y2, x2], 1)
        box_ids = tf.range(0, tf.shape(roi_masks)[0])
        masks = tf.image.crop_and_resize(tf.cast(roi_masks, tf.float32), boxes,
                                         box_ids,
                                         config.MASK_SHAPE)
        # Remove the extra dimension from masks.
        masks = tf.squeeze(masks, axis=3)
    
        # 防止resize后的结果不是1或者0
        masks = tf.round(masks)
    
        # 一般传入config.TRAIN_ROIS_PER_IMAGE个建议框进行训练,
        # 如果数量不够则padding
        rois = tf.concat([positive_rois, negative_rois], axis=0)
        N = tf.shape(negative_rois)[0]
        P = tf.maximum(config.TRAIN_ROIS_PER_IMAGE - tf.shape(rois)[0], 0)
        rois = tf.pad(rois, [(0, P), (0, 0)])
        roi_gt_boxes = tf.pad(roi_gt_boxes, [(0, N + P), (0, 0)])
        roi_gt_class_ids = tf.pad(roi_gt_class_ids, [(0, N + P)])
        deltas = tf.pad(deltas, [(0, N + P), (0, 0)])
        masks = tf.pad(masks, [[0, N + P], (0, 0), (0, 0)])
    
        return rois, roi_gt_class_ids, deltas, masks
    
    def trim_zeros_graph(boxes, name='trim_zeros'):
        """
        如果前一步没有满POST_NMS_ROIS_TRAINING个建议框,会有padding
        要去掉padding
        """
        non_zeros = tf.cast(tf.reduce_sum(tf.abs(boxes), axis=1), tf.bool)
        boxes = tf.boolean_mask(boxes, non_zeros, name=name)
        return boxes, non_zeros
    
    class DetectionTargetLayer(Layer):
        """找到建议框的ground_truth
    
        Inputs:
        proposals: [batch, N, (y1, x1, y2, x2)]建议框
        gt_class_ids: [batch, MAX_GT_INSTANCES]每个真实框对应的类
        gt_boxes: [batch, MAX_GT_INSTANCES, (y1, x1, y2, x2)]真实框的位置
        gt_masks: [batch, height, width, MAX_GT_INSTANCES]真实框的语义分割情况
    
        Returns: 
        rois: [batch, TRAIN_ROIS_PER_IMAGE, (y1, x1, y2, x2)]内部真实存在目标的建议框
        target_class_ids: [batch, TRAIN_ROIS_PER_IMAGE]每个建议框对应的类
        target_deltas: [batch, TRAIN_ROIS_PER_IMAGE, (dy, dx, log(dh), log(dw)]每个建议框应该有的调整参数
        target_mask: [batch, TRAIN_ROIS_PER_IMAGE, height, width]每个建议框语义分割情况
        """
    
        def __init__(self, config, **kwargs):
            super(DetectionTargetLayer, self).__init__(**kwargs)
            self.config = config
    
        def call(self, inputs):
            proposals = inputs[0]
            gt_class_ids = inputs[1]
            gt_boxes = inputs[2]
            gt_masks = inputs[3]
    
            # 对真实框进行编码
            names = ["rois", "target_class_ids", "target_bbox", "target_mask"]
            outputs = utils.batch_slice(
                [proposals, gt_class_ids, gt_boxes, gt_masks],
                lambda w, x, y, z: detection_targets_graph(
                    w, x, y, z, self.config),
                self.config.IMAGES_PER_GPU, names=names)
            return outputs
    
        def compute_output_shape(self, input_shape):
            return [
                (None, self.config.TRAIN_ROIS_PER_IMAGE, 4),  # rois
                (None, self.config.TRAIN_ROIS_PER_IMAGE),  # class_ids
                (None, self.config.TRAIN_ROIS_PER_IMAGE, 4),  # deltas
                (None, self.config.TRAIN_ROIS_PER_IMAGE, self.config.MASK_SHAPE[0],
                 self.config.MASK_SHAPE[1])  # masks
            ]
    
        def compute_mask(self, inputs, mask=None):
            return [None, None, None, None]
    

    3、mask模型的训练

    mask模型在训练的时候要注意,当我们利用建议框网络在mask模型需要用到的公用特征层进行截取的时候,截取的情况和真实框截下来的不一样,因此还需要算出来我们用于截取的框相对于真实框的位置,获得正确的语义分割信息。

    使用代码如下,中间一大部分用于计算真实框相对于建议框的位置。计算完成后利用这个相对位置可以对语义分割信息进行截取,获得正确的语义信息

    # Compute mask targets
    boxes = positive_rois
    if config.USE_MINI_MASK:
        # Transform ROI coordinates from normalized image space
        # to normalized mini-mask space.
        y1, x1, y2, x2 = tf.split(positive_rois, 4, axis=1)
        gt_y1, gt_x1, gt_y2, gt_x2 = tf.split(roi_gt_boxes, 4, axis=1)
        gt_h = gt_y2 - gt_y1
        gt_w = gt_x2 - gt_x1
        y1 = (y1 - gt_y1) / gt_h
        x1 = (x1 - gt_x1) / gt_w
        y2 = (y2 - gt_y1) / gt_h
        x2 = (x2 - gt_x1) / gt_w
        boxes = tf.concat([y1, x1, y2, x2], 1)
    box_ids = tf.range(0, tf.shape(roi_masks)[0])
    masks = tf.image.crop_and_resize(tf.cast(roi_masks, tf.float32), boxes,
                                        box_ids,
                                        config.MASK_SHAPE)
    

    这样的话,就可以通过上述获得的mask和模型的预测结果进行结合训练模型了。

    训练自己的Mask-RCNN模型

    Mask-RCNN整体的文件夹构架如下:
    在这里插入图片描述

    1、数据集准备

    本文适合训练自己的数据集的同学使用。首先利用labelme标注数据。
    在这里插入图片描述
    将其放在before文件夹里:
    在这里插入图片描述
    本文写了一个labelme到数据集的转换代码,在before外部运行即可。
    在这里插入图片描述
    运行后会生成train_dataset,这个train_dataset放到Mask-RCNN模型的根目录即可
    在这里插入图片描述
    生成代码如下:

    import argparse
    import json
    import os
    import os.path as osp
    import warnings
     
    import PIL.Image
    import yaml
     
    from labelme import utils
    import base64
     
    def main():
        count = os.listdir("./before/") 
        index = 0
        for i in range(0, len(count)):
            path = os.path.join("./before", count[i])
    
            if os.path.isfile(path) and path.endswith('json'):
                data = json.load(open(path))
                
                if data['imageData']:
                    imageData = data['imageData']
                else:
                    imagePath = os.path.join(os.path.dirname(path), data['imagePath'])
                    with open(imagePath, 'rb') as f:
                        imageData = f.read()
                        imageData = base64.b64encode(imageData).decode('utf-8')
                img = utils.img_b64_to_arr(imageData)
                label_name_to_value = {'_background_': 0}
                for shape in data['shapes']:
                    label_name = shape['label']
                    if label_name in label_name_to_value:
                        label_value = label_name_to_value[label_name]
                    else:
                        label_value = len(label_name_to_value)
                        label_name_to_value[label_name] = label_value
                
                # label_values must be dense
                label_values, label_names = [], []
                for ln, lv in sorted(label_name_to_value.items(), key=lambda x: x[1]):
                    label_values.append(lv)
                    label_names.append(ln)
                
                assert label_values == list(range(len(label_values)))
                
                lbl = utils.shapes_to_label(img.shape, data['shapes'], label_name_to_value)
                
                captions = ['{}: {}'.format(lv, ln)
                    for ln, lv in label_name_to_value.items()]
                lbl_viz = utils.draw_label(lbl, img, captions)
    
                if not os.path.exists("train_dataset"):
                    os.mkdir("train_dataset")
                label_path = "train_dataset/mask"
                if not os.path.exists(label_path):
                    os.mkdir(label_path)
                img_path = "train_dataset/imgs"
                if not os.path.exists(img_path):
                    os.mkdir(img_path)
                yaml_path = "train_dataset/yaml"
                if not os.path.exists(yaml_path):
                    os.mkdir(yaml_path)
                label_viz_path = "train_dataset/label_viz"
                if not os.path.exists(label_viz_path):
                    os.mkdir(label_viz_path)
    
                PIL.Image.fromarray(img).save(osp.join(img_path, str(index)+'.jpg'))
    
                utils.lblsave(osp.join(label_path, str(index)+'.png'), lbl)
                PIL.Image.fromarray(lbl_viz).save(osp.join(label_viz_path, str(index)+'.png'))
     
                warnings.warn('info.yaml is being replaced by label_names.txt')
                info = dict(label_names=label_names)
                with open(osp.join(yaml_path, str(index)+'.yaml'), 'w') as f:
                    yaml.safe_dump(info, f, default_flow_style=False)
                index = index+1
                print('Saved : %s' % str(index))
    if __name__ == '__main__':
        main()
    

    2、参数修改

    在数据集生成好之后,根据要求修改train.py文件夹下的参数即可训练。Num_classes的数量是分类的总个数+1。
    在这里插入图片描述
    dataset.py内修改自己要分的类,分别是load_shapes函数和load_mask函数内和类有关的内容,即将原有的circle、square等修改成自己要分的类。

    在train文件夹下面修改ShapesConfig(Config)的内容,NUM_CLASS等于自己要分的类的数量+1。

    IMAGE_MAX_DIM、IMAGE_MIN_DIM、BATCH_SIZE和IMAGES_PER_GPU根据自己的显存情况修改。RPN_ANCHOR_SCALES根据IMAGE_MAX_DIM和IMAGE_MIN_DIM进行修改。

    STEPS_PER_EPOCH代表每个世代训练多少次。

    3、模型训练

    全部修改完成后就可以运行train.py训练了。

    展开全文
  • (Mask RCNN)——论文详解(真的很详细)

    万次阅读 多人点赞 2018-12-02 14:01:20
    本文主要是针对论文的详细解析,选出文章各部分的关键点,方便阅读立即。 目录: 摘要: 1、Introduction ...3、Mask R-CNN 3.1 Implementation Details 4、Experiments: Instance Segmentation 4...

    论文:http://cn.arxiv.org/pdf/1703.06870v3

    本文主要是针对论文的详细解析,选出文章各部分的关键点,方便阅读立即。

    目录:

    摘要:

    1、Introduction

    2、Related Work

    3、Mask R-CNN

    3.1 Implementation Details

    4、Experiments: Instance Segmentation

    4.1 Main Results

    4.2  Ablation Experiments(剥离实验)

    4.3. Bounding Box Detection Results      

    4.4. Timing 

    5. Mask R-CNN for Human Pose Estimation

    Appendix A: Experiments on Cityscapes

    Implementation:

    Results:

    Appendix B: Enhanced Results on COCO

    Instance Segmentation and Object Detection

    Keypoint Detection

     

     


    摘要:

    • Mask RCNN可以看做是一个通用实例分割架构。
    • Mask RCNN以Faster RCNN原型,增加了一个分支用于分割任务。
    • Mask RCNN比Faster RCNN速度慢一些,达到了5fps。
    • 可用于人的姿态估计等其他任务;

    1、Introduction

    • 实例分割不仅要正确的找到图像中的objects,还要对其精确的分割。所以Instance Segmentation可以看做object dection和semantic segmentation的结合。
    • Mask RCNN是Faster RCNN的扩展,对于Faster RCNN的每个Proposal Box都要使用FCN进行语义分割,分割任务与定位、分类任务是同时进行的。
    • 引入了RoI Align代替Faster RCNN中的RoI Pooling。因为RoI Pooling并不是按照像素一一对齐的(pixel-to-pixel alignment),也许这对bbox的影响不是很大,但对于mask的精度却有很大影响。使用RoI Align后mask的精度从10%显著提高到50%,第3节将会仔细说明。
    • 引入语义分割分支,实现了mask和class预测的关系的解耦,mask分支只做语义分割,类型预测的任务交给另一个分支。这与原本的FCN网络是不同的,原始的FCN在预测mask时还用同时预测mask所属的种类。
    • 没有使用什么花哨的方法,Mask RCNN就超过了当时所有的state-of-the-art模型。
    • 使用8-GPU的服务器训练了两天。

    2、Related Work

    • 相比于FCIS,FCIS使用全卷机网络,同时预测物体classes、boxes、masks,速度更快,但是对于重叠物体的分割效果不好。

    3、Mask R-CNN

    • Mask R-CNN基本结构:与Faster RCNN采用了相同的two-state步骤:首先是找出RPN,然后对RPN找到的每个RoI进行分类、定位、并找到binary mask。这与当时其他先找到mask然后在进行分类的网络是不同的。
    • Mask R-CNN的损失函数L = L{_{cls}} + L{_{box}} + L{_{mask}}
    • Mask的表现形式(Mask Representation):因为没有采用全连接层并且使用了RoIAlign,可以实现输出与输入的像素一一对应。
    • RoIAlignRoIPool的目的是为了从RPN网络确定的ROI中导出较小的特征图(a small feature map,eg 7x7),ROI的大小各不相同,但是RoIPool后都变成了7x7大小。RPN网络会提出若干RoI的坐标以[x,y,w,h]表示,然后输入RoI Pooling,输出7x7大小的特征图供分类和定位使用。问题就出在RoI Pooling的输出大小是7x7上,如果RON网络输出的RoI大小是8*8的,那么无法保证输入像素和输出像素是一一对应,首先他们包含的信息量不同(有的是1对1,有的是1对2),其次他们的坐标无法和输入对应起来(1对2的那个RoI输出像素该对应哪个输入像素的坐标?)。这对分类没什么影响,但是对分割却影响很大。RoIAlign的输出坐标使用插值算法得到,不再量化;每个grid中的值也不再使用max,同样使用差值算法。

    • Network Architecture: 为了表述清晰,有两种分类方法

    1. 使用了不同的backbone:resnet-50,resnet-101,resnext-50,resnext-101;

    2. 使用了不同的head Architecture:Faster RCNN使用resnet50时,从CONV4导出特征供RPN使用,这种叫做ResNet-50-C4

    3. 作者使用除了使用上述这些结构外,还使用了一种更加高效的backbone——FPN

    3.1 Implementation Details

    使用Fast/Faster相同的超参数,同样适用于Mask RCNN

    • Training:

               1、与之前相同,当IoU与Ground Truth的IoU大于0.5时才会被认为有效的RoI,L{_{mask}}只把有效RoI计算进去。

               2、采用image-centric training,图像短边resize到800,每个GPU的mini-batch设置为2,每个图像生成N个RoI,对于C4                    backbone的N=64,对于FPN作为backbone的,N=512。作者服务器中使用了8块GPU,所以总的minibatch是16,                      迭代了160k次,初始lr=0.02,在迭代到120k次时,将lr设定到 lr=0.002,另外学习率的weight_decay=0.0001,                            momentum = 0.9。如果是resnext,初始lr=0.01,每个GPU的mini-batch是1。

               3、RPN的anchors有5种scale,3种ratios。为了方便剥离、如果没有特别指出,则RPN网络是单独训练的且不与Mask R-                  CNN共享权重。但是在本论文中,RPN和Mask R-CNN使用一个backbone,所以他们的权重是共享的。

                (Ablation Experiments 为了方便研究整个网络中哪个部分其的作用到底有多大,需要把各部分剥离开)

    • Inference:在测试时,使用C4 backbone情况下proposal number=300,使用FPN时proposal number=1000。然后在这些proposal上运行bbox预测,接着进行非极大值抑制。mask分支只应用在得分最高的100个proposal上。顺序和train是不同的,但这样做可以提高速度和精度。mask 分支对于每个roi可以预测k个类别,但是我们只要背景和前景两种,所以只用k-th mask,k是根据分类分支得到的类型。然后把k-th mask resize成roi大小,同时使用阈值分割(threshold=0.5)二值化

    4、Experiments: Instance Segmentation

    4.1 Main Results

    在下图中可以明显看出,FCIS的分割结果中都会出现一条竖着的线(systematic artifacts),这线主要出现在物体重的部分,作者认为这是FCIS架构的问题,无法解决的。但是在Mask RCNN中没有出现。

    4.2  Ablation Experiments(剥离实验)

    • Architecture:
      从table 2a中看出,Mask RCNN随着增加网络的深度、采用更先进的网络,都可以提高效果。注意:并不是所有的网络都是这样。
    • Multinomial vs. Independent Masks:(mask分支是否进行类别预测)                                                                                    从table 2b中可以看出,使用sigmoid(二分类)和使用softmax(多类别分类)的AP相差很大,证明了分离类别和mask的预测是很有必要的
    • Class-Specific vs. Class-Agnostic Masks:                                                                                                                            目前使用的mask rcnn都使用class-specific masks,即每个类别都会预测出一个mxm的mask,然后根据类别选取对应的类别的mask。但是使用Class-Agnostic Masks,即分割网络只输出一个mxm的mask,可以取得相似的成绩29.7vs30.3
    • RoIAlign:                                                                                                                                                                                    tabel 2c证明了RoIAlign的性能
    • Mask Branch:
      tabel 2e,FCN比MLP性能更好

    4.3. Bounding Box Detection Results      

    • Mask RCNN精度高于Faster RCNN
    • Faster RCNN使用RoI Align的精度更高
    • Mask RCNN的分割任务得分与定位任务得分相近,说明Mask RCNN已经缩小了这部分差距。

    4.4. Timing 

    • Inference:195ms一张图片,显卡Nvidia Tesla M40。其实还有速度提升的空间,比如减少proposal的数量等。
    • Training:ResNet-50-FPN on COCO trainval35k takes 32 hours  in our synchronized 8-GPU implementation (0.72s per 16-image mini-batch),and 44 hours with ResNet-101-FPN。

    5. Mask R-CNN for Human Pose Estimation

    让Mask R-CNN预测k个masks,每个mask对应一个关键点的类型,比如左肩、右肘,可以理解为one-hot形式。

    • 使用cross entropy loss,可以鼓励网络只检测一个关键点;
    • ResNet-FPN结构
    • 训练了90k次,最开始lr=0.02,在迭代60k次时,lr=0.002,80k次时变为0.0002

     

    Appendix A: Experiments on Cityscapes

    包含fine annotations images:2975 train ,500 val,1525 test

    图片大小 2048x1024

    使用COCO的AP作为评价指标

    数据十分不平衡!

    Implementation:

    • 使用ResNet-FPN-50作为back bone,不适用ResNet-FPN-101是因为数据集小,没什么提升。
    • 训练时,图像短边从[800,1024]随机选择,可以减小过拟合。
    • 在预测时,图像短边都是1024
    • 每个GPU的mini-batch为1,共8个GPU
    • 训练24k次,初始lr为0.01,18k时减小到0.001,
    • 总共训练时间8小时

    Results:

    • person和car存在大量的类内重叠,给分割网络提出了挑战,但是Mask-RCNN成功解决了
    • 这个数据集十分不平衡,truck,bus,train的数据量很少,所以使用的coco数据集预训练Mask RCNN,分析上表,其他网络预测准确率低也主要低在truck,bus,train三个类别上,所以使用coco预训练还是很有用的。
    • 验证数据集val和测试数据集test AP的差距较大,主要原因在于truck,bus,train三类训练数据太少,person,car这种训练数据多的类别就不存在这种现象。即使使用coco数据集进行预训练也无法消除这种bias。

    Appendix B: Enhanced Results on COCO

    Instance Segmentation and Object Detection
     

     使用一些技巧,可提高精度。可以在这里找到之后的更新:https://github.com/facebookresearch/Detectron

    • Updated baseline:使用不同的超参数延长迭代次数至180k次,初始学习率不变,在120k和160k时减小10倍,NMS的阈值从默认的0.3改为0.5。

    • End-to-end training:之前的学习都是先训练RPN,然后训练Mask RCNN。

    • ImageNet-5k pre-training: 数据量比coco增加了5倍,预训练更有效

    • Train-time augmentation:训练数据增强

    • Model architecture:使用152-layer的ResNeXt

    • Non-local:

    • Test-time augmentation:

    Keypoint Detection

    用时再看吧。。。


     


     

    展开全文
  • Mask-RCNN 算法及其实现详解

    万次阅读 多人点赞 2018-09-20 23:10:10
    写在前面:经过了10多天对RCNN家族的目标检测算法的探究,从一个小白到了入门阶段,觉得有必要记录下这些天学习的知识,如有理解的不到位的地方,还望各位大佬指教。文章代码量比较大,详细的看可能需要一段的时间,...

    写在前面:经过了10多天对RCNN家族的目标检测算法的探究,从一个小白到了入门阶段,觉得有必要记录下这些天学习的知识,如有理解的不到位的地方,还望各位大佬指教。文章代码量比较大,详细的看可能需要一段的时间,等毕设开题答辩完了之后有时间我再修改修改,望谅解。

     

    MASK RCNN 算法介绍:

        Mask-RCNN 是何凯明大神继Faster-RCNN后的又一力作,集成了物体检测和实例分割两大功能,并且在性能上上也超过了Faster-RCNN。

    整体框架:

    图1. Mask-RCNN 整体架构

              为了能够形成一定的对比,把Faster-RCNN的框架也展示出来,直接贴论文中的原图

    是在predict中用,及其

    图2.Faster-RCNN 整体架构

         对比两张图可以很明显的看出,在Faster-RCNN的基础之上,Mask-RCNN加入了Mask branch(FCN)用于生成物体的掩模(object mask), 同时把RoI pooling 修改成为了RoI Align 用于处理mask与原图中物体不对齐的问题。因为在提取feature maps的主干conv layers中不好把FPN的结构绘制进去,所有在架构中就没有体现出了FPN的作用,将在后面讲述。

       各大部件原理讲解

       遵循自下而上的原则,依次的从backbone,FPN,RPN,anchors,RoIAlign,classification,box     regression,mask这几个方面讲解。

    backbone

        backbone是一系列的卷积层用于提取图像的feature maps,比如可以是VGG16,VGG19,GooLeNet,ResNet50,ResNet101等,这里主要讲解的是ResNet101的结构。

                ResNet(深度残差网络)实际上就是为了能够训练更加深层的网络提供了有利的思路,毕竟之前一段时间里面一直相信深度学习中网络越深得到的效果会更加的好,但是在构建了太深层之后又会使得网络退化。ResNet使用了跨层连接,使得训练更加容易。

    图3.ResNet的一个block

            网络试图让一个block的输出为f(x) + x,其中的f(x)为残差,当网络特别深的时候残差f(x)会趋近于0(我也没整明白为什么会趋近于0,大佬是这么说的....),从而f(x) + x就等于了x,即实现了恒等变换,不管训练多深性能起码不会变差。

            在网络中只存在两种类型的block,在构建ResNet中一直是这两种block在交替或者循环的使用,所有接下来介绍一下这两种类型的block(indetity block, conv block):

    图4. 跳过三个卷积的identity block

            图中可以看出该block中直接把开端的x接入到第三个卷积层的输出,所以该x也被称为shortcut,相当于捷径似得。注意主路上第三个卷积层使用激活层,在相加之后才进行了ReLU的激活。

    图5. 跳过三个卷积并在shortcut上存在卷积的conv block

            与identity block其实是差不多的,只是在shortcut上加了一个卷积层再进行相加。注意主路上的第三个卷积层和shortcut上的卷积层都没激活,而是先相加再进行激活的。

            其实在作者的代码中,主路中的第一个和第三个卷积都是1*1的卷积(改变的只有feature maps的通道大小,不改变长和宽),为了降维从而实现卷积运算的加速;注意需要保持shortcut和主路最后一个卷积层的channel要相同才能够进行相加。

               下面展示一下ResNet101的整体框架:

      

    图6.ResNet101整体架构

            从图中可以得知ResNet分为了5个stage,C1-C5分别为每个Stage的输出,这些输出在后面的FPN中会使用到。你可以数数,看看是不是总共101层,数的时候除去BatchNorm层。注:stage4中是由一个conv_block和22个identity_block,如果要改成ResNet50网络的话只需要调整为5个identity_block.

            ResNet101的介绍算是告一个段落了。

    FPN(Feature Pyramid Network)

            FPN的提出是为了实现更好的feature maps融合,一般的网络都是直接使用最后一层的feature maps,虽然最后一层的feature maps 语义强,但是位置和分辨率都比较低,容易检测不到比较小的物体。FPN的功能就是融合了底层到高层的feature maps ,从而充分的利用了提取到的各个阶段的Z征(ResNet中的C2-C5    )。

    图7.FPN特征融合图         

    来说可能这

    图8.特征融合图7中+的意义解释图

            从图中可以看出+的意义为:左边的底层特征层通过1*1的卷积得到与上一层特征层相同的通道数;上层的特征层通过上采样得到与下一层特征层一样的长和宽再进行相加,从而得到了一个融合好的新的特征层。举个例子说就是:C4层经过1*1卷积得到与P5相同的通道,P5经过上采样后得到与C4相同的长和宽,最终两者进行相加,得到了融合层P4,其他的以此类推。

            注:P2-P5是将来用于预测物体的bbox,box-regression,mask的,而P2-P6是用于训练RPN的,即P6只用于RPN网络中。

    anchors

           anchors英文翻译为锚点、锚框,是用于在feature maps的像素点上产生一系列的框,各个框的大小由scale和ratio这两个参数来确定的,比如scale =[128],ratio=[0.5,1,1.5] ,则每个像素点可以产生3个不同大小的框。这个三个框是由保持框的面积不变,来通过ratio的值来改变其长宽比,从而产生不同大小的框。

            假设我们现在绘制feature maps上一个像素点的anchors,则能得到下图:

    图9.一个像素点上的anchors

            由于使用到了FPN,在论文中也有说到每层的feature map 的scale是保持不变的,只是改变每层的ratio,且越深scale的值就越小,因为越深的话feature map就越小。论文中提供的每层的scale为(32, 64, 128, 256, 512),ratio为(0.5, 1, 2),所有每一层的每一个像素点都会产生3个锚框,而总共会有15种不同大小的锚框。

            对于图像的中心点会有15个不同大小锚框,如下图:

    图10.图像中心点的锚框展示

     

    RPN(Region Proposal Network)

              RNP顾名思义:区域推荐的网络,用于帮助网络推荐感兴趣的区域,也是Faster-RCNN中重要的一部分。

    图11. 论文中RPN介绍图   

     

    1. conv feature map:上文中的P2-P6

    2. kk anchor boxes:在每个sliding window的点上的初始化的参考区域。每个sliding window的点上取得anchor boxes都一样。只要知道sliding window的点的坐标,就可以计算出每个anchor box的具体坐标。每个特征层的k=3k,先确定一个base anchor,如P6大小为32×3216×16,保持面积不变使其长宽比为(0.5,1,2)(0.5,1,2),得到3个anchors。 
    3. intermediate layer:作者代码中使用的是512d的conv中间层,再通过1×11×1的卷积获得2k2k scores和4k4k cordinates。作者在文中解释为用全卷积方式替代全连接。 
    4. 2k2k scores:对于每个anchor,用了softmax layer的方式,会或得两个置信度。一个置信度是前景,一个置信度是背景

    5. 4k4k cordinates:每个窗口的坐标。这个坐标并不是anchor的绝对坐标,而是与ground_truth偏差的回归。

            在作者代码中RPN的网络具体结构如下:

    图12. RPN结构图

            注:在开始看作者代码的时候也是有些蒙圈的,为什么给RPN只传入了feature map和k值就可以,而没有给出之前创建好的anchors,后来才明白作者在数据产生那一块做了修改,他在产生数据的时候就给每一个创建好的anchors标注好了是positive还是negative以及需要回归的box值,所有只需要训练RPN就好了。

    RoIAlign

               Mask-RCNN中提出了一个新的idea就是RoIAlign,其实RoIAlign就是在RoI pooling上稍微改动过来的,但是为什么在模型中不能使用RoI pooling呢?现在我们来直观的解释一下。

        图13. RoIAlign与RoIpooling对比

            可以看出来在RoI pooling中出现了两次的取整,虽然在feature maps上取整看起来只是小数级别的数,但是当把feature map还原到原图上时就会出现很大的偏差,比如第一次的取整是舍去了0.78,还原到原图时是0.78*32=25,第一次取整就存在了25个像素点的偏差,在第二次的取整后的偏差更加的大。对于分类和物体检测来说可能这不是一个很大的误差,但是对于实例分割而言,这是一个非常大的偏差,因为mask出现没对齐的话在视觉上是很明显的。而RoIAlign的提出就是为了解决这个问题,解决不对齐的问题。

            RoIAlign的思想其实很简单,就是取消了取整的这种粗暴做法,而是通过双线性插值(听我师姐说好像有一篇论文用到了积分,而且性能得到了一定的提高)来得到固定四个点坐标的像素值,从而使得不连续的操作变得连续起来,返回到原图的时候误差也就更加的小。

            1.划分7*7的bin(可以直接精确的映射到feature map上来划分bin,不用第一次ROI的量化)

    图14. ROI分割7*7的bin

            2.接着是对每一个bin中进行双线性插值,得到四个点(在论文中也说到过插值一个点的效果其实和四个点的效果是一样的,在代码中作者为了方便也就采用了插值一个点)

    图15.插值示意图

            3.通过插完值之后再进行max pooling得到最终的7*7的ROI,即完成了RoIAlign的过程。是不是觉得大佬提出来的高大上名字的方法还是挺简单的。

    classifier

             其中包括了物体检测最终的classes和bounding boxes。该部分是利用了之前检测到了ROI进行分类和回归(是分别对每一个ROI进行)。

    图16. classifier的结构

            论文中提到用1024个神经元的全连接网络,但是在代码中作者用卷积深度为1024的卷积层来代替这个全连接层。

    mask

             mask的预测也是在ROI之后的,通过FCN(Fully Convolution Network)来进行的。注意这个是实现的语义分割而不是实例分割。因为每个ROI只对应一个物体,只需对其进行语义分割就好,相当于了实例分割了,这也是Mask-RCNN与其他分割框架的不同,是先分类再分割。

    图17. mask的结构

            

            对于每一个ROI的mask都有80类,因为coco上的数据集是80个类别,并且这样做的话是为了减弱类别间的竞争,从而得到更加好的结果。

     

            该模型的训练和预测是分开的,不是套用同一个流程。在训练的时候,classifier和mask都是同时进行的;在预测的时候,显示得到classifier的结果,然后再把此结果传入到mask预测中得到mask,有一定的先后顺序。

     

    Mask-RCNN 代码实现

         文中代码的作者是Matterport: 代码github地址,文中详细的介绍了各个部分,以及给了demo和各个实现的步骤及其可视化。

    代码总体框架

           先贴出我对作者代码流程的理解,及其画出的流程图。

    图18.代码中training的流程图

    图19.代码中predict的流程图

                两张流程图其实已经把作者的代码各个关键部分都展示出来了,并写出了哪些层是在training中用,哪些层是在predict中用,及其层的输出和需要的输入。可以清晰的看出training和predict过程是存在较大的差异的,也是之前说过的,training的时候mask与classifier是并行的,predict时候是先classifier再mask,并且两个模型的输入输出差异也较大。

       

             已经有一篇博客写的很好,对作者代码的那几个ipynb都运行了一遍,并且加上了自己的理解。非常的感谢那位博主,之前在探究Mask-RCNN的时候那边博文对我的帮助很大,有兴趣的可以看看那片博文:博文链接

     

             我这里就主要的介绍一下作者中的几个.py文件:visualize.py,utils.py,model.py,最后再实现一下如何使用该代码处理视频

     

               因为代码量比较大,我就挑一些本人认为重要的代码贴出来。

    visualize.py

    ##利用不同的颜色为每个instance标注出mask,根据box的坐标在instance的周围画上矩形
    ##根据class_ids来寻找到对于的class_names。三个步骤中的任何一个都可以去掉,比如把mask部分
    ##去掉,那就只剩下box和label。同时可以筛选出class_ids从而显示制定类别的instance显示,下面
    ##这段就是用来显示人的,其实也就把人的id选出来,然后记录它们在输入ids中的相对位置,从而得到
    ##相对应的box与mask的准确顺序
    def display_instances_person(image, boxes, masks, class_ids, class_names,
                          scores=None, title="",
                          figsize=(16, 16), ax=None):
        """
        the funtion perform a role for displaying the persons who locate in the image
        boxes: [num_instance, (y1, x1, y2, x2, class_id)] in image coordinates.
        masks: [height, width, num_instances]
        class_ids: [num_instances]
        class_names: list of class names of the dataset
        scores: (optional) confidence scores for each box
        figsize: (optional) the size of the image.
        """
        #compute the number of person
        temp = []
        for i, person in enumerate(class_ids):
            if person == 1:
                temp.append(i)
            else:
                pass
        person_number = len(temp)
        
        person_site = {}
        
        for i in range(person_number):
            person_site[i] = temp[i]
        
        
        NN = boxes.shape[0]   
        # Number of person'instances
        #N = boxes.shape[0]
        N = person_number
        if not N:
            print("\n*** No person to display *** \n")
        else:
           # assert boxes.shape[0] == masks.shape[-1] == class_ids.shape[0]
            pass
    
        if not ax:
            _, ax = plt.subplots(1, figsize=figsize)
    
        # Generate random colors
        colors = random_colors(NN)
    
        # Show area outside image boundaries.
        height, width = image.shape[:2]
        ax.set_ylim(height + 10, -10)
        ax.set_xlim(-10, width + 10)
        ax.axis('off')
        ax.set_title(title)
    
        masked_image = image.astype(np.uint32).copy()
        for a in range(N):
            
            color = colors[a]
            i = person_site[a]
            
    
            # Bounding box
            if not np.any(boxes[i]):
                # Skip this instance. Has no bbox. Likely lost in image cropping.
                continue
            y1, x1, y2, x2 = boxes[i]
            p = patches.Rectangle((x1, y1), x2 - x1, y2 - y1, linewidth=2,
                                  alpha=0.7, linestyle="dashed",
                                  edgecolor=color, facecolor='none')
            ax.add_patch(p)
    
            # Label
            class_id = class_ids[i]
            score = scores[i] if scores is not None else None
            label = class_names[class_id]
            x = random.randint(x1, (x1 + x2) // 2)
            caption = "{} {:.3f}".format(label, score) if score else label
            ax.text(x1, y1 + 8, caption,
                    color='w', size=11, backgroundcolor="none")
            
             # Mask
            mask = masks[:, :, i]
            masked_image = apply_mask(masked_image, mask, color)
    
            # Mask Polygon
            # Pad to ensure proper polygons for masks that touch image edges.
            padded_mask = np.zeros(
                (mask.shape[0] + 2, mask.shape[1] + 2), dtype=np.uint8)
            padded_mask[1:-1, 1:-1] = mask
            contours = find_contours(padded_mask, 0.5)
            for verts in contours:
                # Subtract the padding and flip (y, x) to (x, y)
                verts = np.fliplr(verts) - 1
                p = Polygon(verts, facecolor="none", edgecolor=color)
                ax.add_patch(p)
           
        ax.imshow(masked_image.astype(np.uint8))
        plt.show()

    utils.py

    ##因为一个自定义层的输入的batch只能为1,所以需要把input分成batch为1的输入,
    ##然后通过graph_fn计算出output,最终再合在一块,即间接的实现了计算了一个batch的操作
    # ## Batch Slicing
    # Some custom layers support a batch size of 1 only, and require a lot of work
    # to support batches greater than 1. This function slices an input tensor
    # across the batch dimension and feeds batches of size 1. Effectively,
    # an easy way to support batches > 1 quickly with little code modification.
    # In the long run, it's more efficient to modify the code to support large
    # batches and getting rid of this function. Consider this a temporary solution
    def batch_slice(inputs, graph_fn, batch_size, names=None):
        """Splits inputs into slices and feeds each slice to a copy of the given
        computation graph and then combines the results. It allows you to run a
        graph on a batch of inputs even if the graph is written to support one
        instance only.
    
        inputs: list of tensors. All must have the same first dimension length
        graph_fn: A function that returns a TF tensor that's part of a graph.
        batch_size: number of slices to divide the data into.
        names: If provided, assigns names to the resulting tensors.
        """
        if not isinstance(inputs, list):
            inputs = [inputs]
    
        outputs = []
        for i in range(batch_size):
            inputs_slice = [x[i] for x in inputs]
            output_slice = graph_fn(*inputs_slice)
            if not isinstance(output_slice, (tuple, list)):
                output_slice = [output_slice]
            outputs.append(output_slice)
        # Change outputs from a list of slices where each is
        # a list of outputs to a list of outputs and each has
        # a list of slices
        outputs = list(zip(*outputs))
    
        if names is None:
            names = [None] * len(outputs)
    
        result = [tf.stack(o, axis=0, name=n)
                  for o, n in zip(outputs, names)]
        if len(result) == 1:
            result = result[0]
    
        return result
    ############################################################
    #  Anchors
    ############################################################
    ##对特征图上的pixel产生anchors,根据anchor_stride来确定pixel产生anchors的密度
    ##即是每个像素点产生anchors,还是每两个产生,以此类推
    def generate_anchors(scales, ratios, shape, feature_stride, anchor_stride):
        """
        scales: 1D array of anchor sizes in pixels. Example: [32, 64, 128]
        ratios: 1D array of anchor ratios of width/height. Example: [0.5, 1, 2]
        shape: [height, width] spatial shape of the feature map over which
                to generate anchors.
        feature_stride: Stride of the feature map relative to the image in pixels.
        anchor_stride: Stride of anchors on the feature map. For example, if the
            value is 2 then generate anchors for every other feature map pixel.
        """
        # Get all combinations of scales and ratios
        scales, ratios = np.meshgrid(np.array(scales), np.array(ratios))
        scales = scales.flatten()
        ratios = ratios.flatten()
    
        # Enumerate heights and widths from scales and ratios
        heights = scales / np.sqrt(ratios)
        widths = scales * np.sqrt(ratios)
    
        # Enumerate shifts in feature space
        shifts_y = np.arange(0, shape[0], anchor_stride) * feature_stride
        shifts_x = np.arange(0, shape[1], anchor_stride) * feature_stride
        shifts_x, shifts_y = np.meshgrid(shifts_x, shifts_y)
    
        # Enumerate combinations of shifts, widths, and heights
        box_widths, box_centers_x = np.meshgrid(widths, shifts_x)
        box_heights, box_centers_y = np.meshgrid(heights, shifts_y)
    
        # Reshape to get a list of (y, x) and a list of (h, w)
        box_centers = np.stack(
            [box_centers_y, box_centers_x], axis=2).reshape([-1, 2])
        box_sizes = np.stack([box_heights, box_widths], axis=2).reshape([-1, 2])
    
        # Convert to corner coordinates (y1, x1, y2, x2)
        boxes = np.concatenate([box_centers - 0.5 * box_sizes,
                                box_centers + 0.5 * box_sizes], axis=1)
        return boxes
    
    #调用generate_anchors()为每一层的feature map都生成anchors,最终在合成在一块。自己层中的scale是相同的
    def generate_pyramid_anchors(scales, ratios, feature_shapes, feature_strides,
                                 anchor_stride):
        """Generate anchors at different levels of a feature pyramid. Each scale
        is associated with a level of the pyramid, but each ratio is used in
        all levels of the pyramid.
    
        Returns:
        anchors: [N, (y1, x1, y2, x2)]. All generated anchors in one array. Sorted
            with the same order of the given scales. So, anchors of scale[0] come
            first, then anchors of scale[1], and so on.
        """
        # Anchors
        # [anchor_count, (y1, x1, y2, x2)]
        anchors = []
        for i in range(len(scales)):
            anchors.append(generate_anchors(scales[i], ratios, feature_shapes[i],
                                            feature_strides[i], anchor_stride))
        return np.concatenate(anchors, axis=0)
    

    model.py

     

    ###建立ResNet101网络的架构,其中identity_block和conv_block就是上文中讲解。
    def resnet_graph(input_image, architecture, stage5=False):
        assert architecture in ["resnet50", "resnet101"]
        # Stage 1
        x = KL.ZeroPadding2D((3, 3))(input_image)
        x = KL.Conv2D(64, (7, 7), strides=(2, 2), name='conv1', use_bias=True)(x)
        x = BatchNorm(axis=3, name='bn_conv1')(x)
        x = KL.Activation('relu')(x)
        C1 = x = KL.MaxPooling2D((3, 3), strides=(2, 2), padding="same")(x)
        # Stage 2
        x = conv_block(x, 3, [64, 64, 256], stage=2, block='a', strides=(1, 1))
        x = identity_block(x, 3, [64, 64, 256], stage=2, block='b')
        C2 = x = identity_block(x, 3, [64, 64, 256], stage=2, block='c')
        # Stage 3
        x = conv_block(x, 3, [128, 128, 512], stage=3, block='a')
        x = identity_block(x, 3, [128, 128, 512], stage=3, block='b')
        x = identity_block(x, 3, [128, 128, 512], stage=3, block='c')
        C3 = x = identity_block(x, 3, [128, 128, 512], stage=3, block='d')
        # Stage 4
        x = conv_block(x, 3, [256, 256, 1024], stage=4, block='a')
        block_count = {"resnet50": 5, "resnet101": 22}[architecture]
        for i in range(block_count):
            x = identity_block(x, 3, [256, 256, 1024], stage=4, block=chr(98 + i))
        C4 = x
        # Stage 5
        if stage5:
            x = conv_block(x, 3, [512, 512, 2048], stage=5, block='a')
            x = identity_block(x, 3, [512, 512, 2048], stage=5, block='b')
            C5 = x = identity_block(x, 3, [512, 512, 2048], stage=5, block='c')
        else:
            C5 = None
        return [C1, C2, C3, C4, C5]
    
    

    Proposal Layer:

    class ProposalLayer(KE.Layer):
        """
        Inputs:
            rpn_probs: [batch, anchors, (bg prob, fg prob)]
            rpn_bbox: [batch, anchors, (dy, dx, log(dh), log(dw))]
    
        Returns:
            Proposals in normalized coordinates [batch, rois, (y1, x1, y2, x2)]
        """
    
        def __init__(self, proposal_count, nms_threshold, anchors,
                     config=None, **kwargs):
            """
            anchors: [N, (y1, x1, y2, x2)] anchors defined in image coordinates
            """
            super(ProposalLayer, self).__init__(**kwargs)
            self.config = config
            self.proposal_count = proposal_count
            self.nms_threshold = nms_threshold
            self.anchors = anchors.astype(np.float32)
    
        def call(self, inputs):
            ###实现了将传入的anchors,及其scores、deltas进行topK的推荐和nms的推荐,最终输出
            ###数量为proposal_counts的proposals。其中的scores和deltas都是RPN网络中得到的
            # Box Scores. Use the foreground class confidence. [Batch, num_rois, 1]
            scores = inputs[0][:, :, 1]
            # Box deltas [batch, num_rois, 4]
            deltas = inputs[1]
            deltas = deltas * np.reshape(self.config.RPN_BBOX_STD_DEV, [1, 1, 4])
            # Base anchors
            anchors = self.anchors
    
            # Improve performance by trimming to top anchors by score
            # and doing the rest on the smaller subset.
            pre_nms_limit = min(6000, self.anchors.shape[0])
            ix = tf.nn.top_k(scores, pre_nms_limit, sorted=True,
                             name="top_anchors").indices
            scores = utils.batch_slice([scores, ix], lambda x, y: tf.gather(x, y),
                                       self.config.IMAGES_PER_GPU)
            deltas = utils.batch_slice([deltas, ix], lambda x, y: tf.gather(x, y),
                                       self.config.IMAGES_PER_GPU)
            anchors = utils.batch_slice(ix, lambda x: tf.gather(anchors, x),
                                        self.config.IMAGES_PER_GPU,
                                        names=["pre_nms_anchors"])
    
            # Apply deltas to anchors to get refined anchors.
            # [batch, N, (y1, x1, y2, x2)]
            ##利用deltas在anchors上,得到精化的boxs
            boxes = utils.batch_slice([anchors, deltas],
                                      lambda x, y: apply_box_deltas_graph(x, y),
                                      self.config.IMAGES_PER_GPU,
                                      names=["refined_anchors"])
    
            # Clip to image boundaries. [batch, N, (y1, x1, y2, x2)]
            height, width = self.config.IMAGE_SHAPE[:2]
            window = np.array([0, 0, height, width]).astype(np.float32)
            boxes = utils.batch_slice(boxes,
                                      lambda x: clip_boxes_graph(x, window),
                                      self.config.IMAGES_PER_GPU,
                                      names=["refined_anchors_clipped"])
    
            # Filter out small boxes
            # According to Xinlei Chen's paper, this reduces detection accuracy
            # for small objects, so we're skipping it.
    
            # Normalize dimensions to range of 0 to 1.
            normalized_boxes = boxes / np.array([[height, width, height, width]])
    
            # Non-max suppression
            def nms(normalized_boxes, scores):
                indices = tf.image.non_max_suppression(
                    normalized_boxes, scores, self.proposal_count,
                    self.nms_threshold, name="rpn_non_max_suppression")
                proposals = tf.gather(normalized_boxes, indices)
                # Pad if needed
                padding = tf.maximum(self.proposal_count - tf.shape(proposals)[0], 0)
                ##填充到与proposal_count的数量一样,往下填充。
                proposals = tf.pad(proposals, [(0, padding), (0, 0)])
                return proposals
            proposals = utils.batch_slice([normalized_boxes, scores], nms,
                                          self.config.IMAGES_PER_GPU)
            return proposals

     

    RoIAlign Layer:

     

    class PyramidROIAlign(KE.Layer):
        """Implements ROI Pooling on multiple levels of the feature pyramid.
    
        Params:
        - pool_shape: [height, width] of the output pooled regions. Usually [7, 7]
        - image_shape: [height, width, chanells]. Shape of input image in pixels
    
        Inputs:
        - boxes: [batch, num_boxes, (y1, x1, y2, x2)] in normalized
                 coordinates. Possibly padded with zeros if not enough
                 boxes to fill the array.
        - Feature maps: List of feature maps from different levels of the pyramid.
                        Each is [batch, height, width, channels]
    
        Output:
        Pooled regions in the shape: [batch, num_boxes, height, width, channels].
        The width and height are those specific in the pool_shape in the layer
        constructor.
        """
    
        def __init__(self, pool_shape, image_shape, **kwargs):
            super(PyramidROIAlign, self).__init__(**kwargs)
            self.pool_shape = tuple(pool_shape)
            self.image_shape = tuple(image_shape)
    
        def call(self, inputs):
            ##计算在不同层的ROI下的ROIalig pooling,应该是计算了每一个lever的所有channels
            # Crop boxes [batch, num_boxes, (y1, x1, y2, x2)] in normalized coords
            boxes = inputs[0]
    
            # Feature Maps. List of feature maps from different level of the
            # feature pyramid. Each is [batch, height, width, channels]
            feature_maps = inputs[1:]
    
            # Assign each ROI to a level in the pyramid based on the ROI area.
            y1, x1, y2, x2 = tf.split(boxes, 4, axis=2)
            h = y2 - y1
            w = x2 - x1
            # Equation 1 in the Feature Pyramid Networks paper. Account for
            # the fact that our coordinates are normalized here.
            # e.g. a 224x224 ROI (in pixels) maps to P4
            ###计算ROI属于FPN中的哪一个level
            image_area = tf.cast(
                self.image_shape[0] * self.image_shape[1], tf.float32)
            roi_level = log2_graph(tf.sqrt(h * w) / (224.0 / tf.sqrt(image_area)))
            roi_level = tf.minimum(5, tf.maximum(
                2, 4 + tf.cast(tf.round(roi_level), tf.int32)))
            roi_level = tf.squeeze(roi_level, 2)
    
            # Loop through levels and apply ROI pooling to each. P2 to P5.
            pooled = []
            box_to_level = []
            for i, level in enumerate(range(2, 6)):
                ##应该是一个二维的array,存储这哪一层的哪些box的indicies
                ix = tf.where(tf.equal(roi_level, level))
                level_boxes = tf.gather_nd(boxes, ix)
    
                # Box indicies for crop_and_resize.
                ##应该是只存储当前lever的box的indices
                box_indices = tf.cast(ix[:, 0], tf.int32)
    
                # Keep track of which box is mapped to which level
                box_to_level.append(ix)
    
                # Stop gradient propogation to ROI proposals
                level_boxes = tf.stop_gradient(level_boxes)
                box_indices = tf.stop_gradient(box_indices)
    
                # 因为插值一个点和四个点的性能影响不大故插一个点
                pooled.append(tf.image.crop_and_resize(
                    feature_maps[i], level_boxes, box_indices, self.pool_shape,
                    method="bilinear"))
    
            # Pack pooled features into one tensor
            pooled = tf.concat(pooled, axis=0)
    
            # Pack box_to_level mapping into one array and add another
            # column representing the order of pooled boxes
            box_to_level = tf.concat(box_to_level, axis=0)
            box_range = tf.expand_dims(tf.range(tf.shape(box_to_level)[0]), 1)
            box_to_level = tf.concat([tf.cast(box_to_level, tf.int32), box_range],
                                     axis=1)
    
            # Rearrange pooled features to match the order of the original boxes
            # Sort box_to_level by batch then box index
            # TF doesn't have a way to sort by two columns, so merge them and sort.
            sorting_tensor = box_to_level[:, 0] * 100000 + box_to_level[:, 1]
            ix = tf.nn.top_k(sorting_tensor, k=tf.shape(
                box_to_level)[0]).indices[::-1]
            ix = tf.gather(box_to_level[:, 2], ix)
            pooled = tf.gather(pooled, ix)
    
            # Re-add the batch dimension
            pooled = tf.expand_dims(pooled, 0)
            return pooled

     

    Detection_Target_Layer的主要函数:Detection_targets_graph()

     

     #根据proposal和gt_box的overlap来确定正样本和负样本,并按照sample_ratio和train_anchor_per_image
        #的大小进行采样,最终得出rois(n&p),class_id,delta,masks,其中进行了padding
    def detection_targets_graph(proposals, gt_class_ids, gt_boxes, gt_masks, config):
        #Subsamples 抽样
        """Generates detection targets for one image. Subsamples proposals and
        generates target class IDs, bounding box deltas, and masks for each.
    
        Inputs:
        proposals: [N, (y1, x1, y2, x2)] in normalized coordinates. Might
                   be zero padded if there are not enough proposals.
        gt_class_ids: [MAX_GT_INSTANCES] int class IDs
        gt_boxes: [MAX_GT_INSTANCES, (y1, x1, y2, x2)] in normalized coordinates.
        gt_masks: [height, width, MAX_GT_INSTANCES] of boolean type.
    
        Returns: Target ROIs and corresponding class IDs, bounding box shifts,
        and masks.
        rois: [TRAIN_ROIS_PER_IMAGE, (y1, x1, y2, x2)] in normalized coordinates
        class_ids: [TRAIN_ROIS_PER_IMAGE]. Integer class IDs. Zero padded.
        deltas: [TRAIN_ROIS_PER_IMAGE, NUM_CLASSES, (dy, dx, log(dh), log(dw))]
                Class-specific bbox refinments.
        masks: [TRAIN_ROIS_PER_IMAGE, height, width). Masks cropped to bbox
               boundaries and resized to neural network output size.
    
        Note: Returned arrays might be zero padded if not enough target ROIs.
        """
        # Assertions
        asserts = [
            tf.Assert(tf.greater(tf.shape(proposals)[0], 0), [proposals],
                      name="roi_assertion"),
        ]
        with tf.control_dependencies(asserts):
            proposals = tf.identity(proposals)
    
        # Remove zero padding
        proposals, _ = trim_zeros_graph(proposals, name="trim_proposals")
        gt_boxes, non_zeros = trim_zeros_graph(gt_boxes, name="trim_gt_boxes")
        gt_class_ids = tf.boolean_mask(gt_class_ids, non_zeros,
                                       name="trim_gt_class_ids")
        gt_masks = tf.gather(gt_masks, tf.where(non_zeros)[:, 0], axis=2,
                             name="trim_gt_masks")
    
        # Handle COCO crowds
        # A crowd box in COCO is a bounding box around several instances. Exclude
        # them from training. A crowd box is given a negative class ID.
        crowd_ix = tf.where(gt_class_ids < 0)[:, 0]
        non_crowd_ix = tf.where(gt_class_ids > 0)[:, 0]
        crowd_boxes = tf.gather(gt_boxes, crowd_ix)
        crowd_masks = tf.gather(gt_masks, crowd_ix, axis=2)
        gt_class_ids = tf.gather(gt_class_ids, non_crowd_ix)
        gt_boxes = tf.gather(gt_boxes, non_crowd_ix)
        gt_masks = tf.gather(gt_masks, non_crowd_ix, axis=2)
    
        # Compute overlaps matrix [proposals, gt_boxes]
        overlaps = overlaps_graph(proposals, gt_boxes)
    
        # Compute overlaps with crowd boxes [anchors, crowds]
        crowd_overlaps = overlaps_graph(proposals, crowd_boxes)
        crowd_iou_max = tf.reduce_max(crowd_overlaps, axis=1)
        no_crowd_bool = (crowd_iou_max < 0.001)
    
        # Determine postive and negative ROIs
        roi_iou_max = tf.reduce_max(overlaps, axis=1)
        # 1. Positive ROIs are those with >= 0.5 IoU with a GT box
        positive_roi_bool = (roi_iou_max >= 0.5)
        positive_indices = tf.where(positive_roi_bool)[:, 0]
        # 2. Negative ROIs are those with < 0.5 with every GT box. Skip crowds.
        negative_indices = tf.where(tf.logical_and(roi_iou_max < 0.5, no_crowd_bool))[:, 0]
    
        # Subsample ROIs. Aim for 33% positive
        # Positive ROIs
        positive_count = int(config.TRAIN_ROIS_PER_IMAGE *
                             config.ROI_POSITIVE_RATIO)
        positive_indices = tf.random_shuffle(positive_indices)[:positive_count]
        positive_count = tf.shape(positive_indices)[0]
        # Negative ROIs. Add enough to maintain positive:negative ratio.
        r = 1.0 / config.ROI_POSITIVE_RATIO
        negative_count = tf.cast(r * tf.cast(positive_count, tf.float32), tf.int32) - positive_count
        negative_indices = tf.random_shuffle(negative_indices)[:negative_count]
        # Gather selected ROIs
        positive_rois = tf.gather(proposals, positive_indices)
        negative_rois = tf.gather(proposals, negative_indices)
    
        # Assign positive ROIs to GT boxes.
        positive_overlaps = tf.gather(overlaps, positive_indices)
        ##最终需要的indices
        roi_gt_box_assignment = tf.argmax(positive_overlaps, axis=1)
        roi_gt_boxes = tf.gather(gt_boxes, roi_gt_box_assignment)
        roi_gt_class_ids = tf.gather(gt_class_ids, roi_gt_box_assignment)
    
        # Compute bbox refinement for positive ROIs
        deltas = utils.box_refinement_graph(positive_rois, roi_gt_boxes)
        deltas /= config.BBOX_STD_DEV
    
        # Assign positive ROIs to GT masks
        # Permute masks to [N, height, width, 1]
        transposed_masks = tf.expand_dims(tf.transpose(gt_masks, [2, 0, 1]), -1)
        # Pick the right mask for each ROI
        roi_masks = tf.gather(transposed_masks, roi_gt_box_assignment)
    
        # Compute mask targets
        boxes = positive_rois
        if config.USE_MINI_MASK:
            # Transform ROI coordinates from normalized image space
            # to normalized mini-mask space.
            y1, x1, y2, x2 = tf.split(positive_rois, 4, axis=1)
            gt_y1, gt_x1, gt_y2, gt_x2 = tf.split(roi_gt_boxes, 4, axis=1)
            gt_h = gt_y2 - gt_y1
            gt_w = gt_x2 - gt_x1
            y1 = (y1 - gt_y1) / gt_h
            x1 = (x1 - gt_x1) / gt_w
            y2 = (y2 - gt_y1) / gt_h
            x2 = (x2 - gt_x1) / gt_w
            boxes = tf.concat([y1, x1, y2, x2], 1)
        box_ids = tf.range(0, tf.shape(roi_masks)[0])
        masks = tf.image.crop_and_resize(tf.cast(roi_masks, tf.float32), boxes,
                                         box_ids,
                                         config.MASK_SHAPE)
        # Remove the extra dimension from masks.
        masks = tf.squeeze(masks, axis=3)
    
        # Threshold mask pixels at 0.5 to have GT masks be 0 or 1 to use with
        # binary cross entropy loss.
        masks = tf.round(masks)
        ##进行填充
        # Append negative ROIs and pad bbox deltas and masks that
        # are not used for negative ROIs with zeros.
        rois = tf.concat([positive_rois, negative_rois], axis=0)
        N = tf.shape(negative_rois)[0]
        P = tf.maximum(config.TRAIN_ROIS_PER_IMAGE - tf.shape(rois)[0], 0)
        rois = tf.pad(rois, [(0, P), (0, 0)])
        roi_gt_boxes = tf.pad(roi_gt_boxes, [(0, N + P), (0, 0)])
        roi_gt_cliass_ids = tf.pad(roi_gt_class_ids, [(0, N + P)])
        deltas = tf.pad(deltas, [(0, N + P), (0, 0)])
        masks = tf.pad(masks, [[0, N + P], (0, 0), (0, 0)])
    
        return rois, roi_gt_class_ids, deltas, masks

    DetectionLayer的主要函数:refine_detetions()

    #根据rios和probs(每个ROI都有总类别个数的probs)和deltas进行检测的精化,得到固定数量的精化目标。
    def refine_detections(rois, probs, deltas, window, config):
        """Refine classified proposals and filter overlaps and return final
        detections.
        #输入为N个rois、N个具有num_classes的probs,scores由probs得出
        Inputs:
            rois: [N, (y1, x1, y2, x2)] in normalized coordinates
            probs: [N, num_classes]. Class probabilities.
            deltas: [N, num_classes, (dy, dx, log(dh), log(dw))]. Class-specific
                    bounding box deltas.
            window: (y1, x1, y2, x2) in image coordinates. The part of the image
                that contains the image excluding the padding.
    
        Returns detections shaped: [N, (y1, x1, y2, x2, class_id, score)]
        """
        # Class IDs per ROI
        class_ids = np.argmax(probs, axis=1)
        # Class probability of the top class of each ROI
        class_scores = probs[np.arange(class_ids.shape[0]), class_ids]
        # Class-specific bounding box deltas
        deltas_specific = deltas[np.arange(deltas.shape[0]), class_ids]
        # Apply bounding box deltas
        # Shape: [boxes, (y1, x1, y2, x2)] in normalized coordinates
        refined_rois = utils.apply_box_deltas(
            rois, deltas_specific * config.BBOX_STD_DEV)
        # Convert coordiates to image domain
        # TODO: better to keep them normalized until later
        height, width = config.IMAGE_SHAPE[:2]
        refined_rois *= np.array([height, width, height, width])
        # Clip boxes to image window
        refined_rois = clip_to_window(window, refined_rois)
        # Round and cast to int since we're deadling with pixels now
        refined_rois = np.rint(refined_rois).astype(np.int32)
    
        # TODO: Filter out boxes with zero area
    
        # Filter out background boxes
        keep = np.where(class_ids > 0)[0]
        # Filter out low confidence boxes
        if config.DETECTION_MIN_CONFIDENCE:
            keep = np.intersect1d(
                keep, np.where(class_scores >= config.DETECTION_MIN_CONFIDENCE)[0])
            
            
        #留下既满足是前景又满足scores大于MIN_CONFIDENCE的
        # Apply per-class NMS
        pre_nms_class_ids = class_ids[keep]
        pre_nms_scores = class_scores[keep]
        pre_nms_rois = refined_rois[keep]
        nms_keep = []
        #分类别的进行NMS。
        for class_id in np.unique(pre_nms_class_ids):
            # Pick detections of this class
            ixs = np.where(pre_nms_class_ids == class_id)[0]
            # Apply NMS
            class_keep = utils.non_max_suppression(
                pre_nms_rois[ixs], pre_nms_scores[ixs],
                config.DETECTION_NMS_THRESHOLD)
            # Map indicies
            class_keep = keep[ixs[class_keep]]
            nms_keep = np.union1d(nms_keep, class_keep)
        keep = np.intersect1d(keep, nms_keep).astype(np.int32)
    
        # Keep top detections
        roi_count = config.DETECTION_MAX_INSTANCES
        top_ids = np.argsort(class_scores[keep])[::-1][:roi_count]
        keep = keep[top_ids]
    
        # Arrange output as [N, (y1, x1, y2, x2, class_id, score)]
        # Coordinates are in image domain.
        result = np.hstack((refined_rois[keep],
                            class_ids[keep][..., np.newaxis],
                            class_scores[keep][..., np.newaxis]))
        return result

            像RPN、fpn_classifier_graph、bulid_fpn_mask_graph等网络结构都和论文中描述的一样,这里就不贴出代码赘述了。

            因为论文中添加了mask这个分支,这里就单独的把mask的loss代码贴出来,也是之前Faster-RCNN中没有的。

    ##根据预测的mask和真实的mask来计算binary_cross_entropy的loss,且只有positive ROIS 贡献
    ##loss,且每一个ROI只能对应一个类别的mask(因为防止种类竞争,每个ROIS预测了num_class个的MASK)
    def mrcnn_mask_loss_graph(target_masks, target_class_ids, pred_masks):
        """Mask binary cross-entropy loss for the masks head.
    
        target_masks: [batch, num_rois, height, width].
            A float32 tensor of values 0 or 1. Uses zero padding to fill array.
        target_class_ids: [batch, num_rois]. Integer class IDs. Zero padded.
        pred_masks: [batch, proposals, height, width, num_classes] float32 tensor
                    with values from 0 to 1.
        """
        # Reshape for simplicity. Merge first two dimensions into one.
        target_class_ids = K.reshape(target_class_ids, (-1,))
        mask_shape = tf.shape(target_masks)
        target_masks = K.reshape(target_masks, (-1, mask_shape[2], mask_shape[3]))
        pred_shape = tf.shape(pred_masks)
        #shape:[batch*proposal, height, width, number_class]
        pred_masks = K.reshape(pred_masks,
                               (-1, pred_shape[2], pred_shape[3], pred_shape[4]))
        # Permute predicted masks to [N, num_classes, height, width]
        pred_masks = tf.transpose(pred_masks, [0, 3, 1, 2])
    
        # Only positive ROIs contribute to the loss. And only
        # the class specific mask of each ROI.
        positive_ix = tf.where(target_class_ids > 0)[:, 0]
        positive_class_ids = tf.cast(
            tf.gather(target_class_ids, positive_ix), tf.int64)
        indices = tf.stack([positive_ix, positive_class_ids], axis=1)
    
        # Gather the masks (predicted and true) that contribute to loss
        y_true = tf.gather(target_masks, positive_ix)
        y_pred = tf.gather_nd(pred_masks, indices)
    
        # Compute binary cross entropy. If no positive ROIs, then return 0.
        # shape: [batch, roi, num_classes]
        loss = K.switch(tf.size(y_true) > 0,
                        K.binary_crossentropy(target=y_true, output=y_pred),
                        tf.constant(0.0))
        loss = K.mean(loss)
        loss = K.reshape(loss, [1, 1])
        return loss
    

                在Date Generate 这一块中,含有三个主要的函数:

                第一个是load_image_gt(dataset, config, image_id, augment=False,use_mini_mask=False)  该函数继承了utils.py中的Dataset类,主要的功能是根据image_id来读取图片的gt_masks,gt_boxes,instances,gt_class_ids。不熟悉的可以看看Dataset父类中的函数。

               第二个是build_detection_target(),这个函数的作用其实和DetectionTargetLayer的作用差不多,但是他是用来帮助我们读者可视化的时候调用的,或者用来在不使用RPN的情况下来调试和训练Mask-RCNN网络的。

           第三个是bulid_rpn_target(image_shape, anchors, gt_class_ids, gt_boxes, config),利用overlap的大小给anchors寻找对应的gt_boxs,对anchors再进行抽样,去除一半以上的positive anchors再计算需要留下的negative anchors,最终计算留下的positive anchors与所对应的gt_box的deltas,返回的rpn_match中-1是negative,0是neutral,1是positive,这个在data_generator()中有用处。

                接下来是这块的主角data_generator(),是一个数据的生成器,用于产生数据,用于之后的训练和各层之间的调用等。可以留意一下这个生成器的返回值。

    ###产生一系列的数据的generator
    def data_generator(dataset, config, shuffle=True, augment=True, random_rois=0,
                       batch_size=1, detection_targets=False):
        """
        - images: [batch, H, W, C]
        - image_meta: [batch, size of image meta]
        - rpn_match: [batch, N] Integer (1=positive anchor, -1=negative, 0=neutral)
        - rpn_bbox: [batch, N, (dy, dx, log(dh), log(dw))] Anchor bbox deltas.
        - gt_class_ids: [batch, MAX_GT_INSTANCES] Integer class IDs
        - gt_boxes: [batch, MAX_GT_INSTANCES, (y1, x1, y2, x2)]
        - gt_masks: [batch, height, width, MAX_GT_INSTANCES]. The height and width
                    are those of the image unless use_mini_mask is True, in which
                    case they are defined in MINI_MASK_SHAPE.
    
        outputs list: Usually empty in regular training. But if detection_targets
            is True then the outputs list contains target class_ids, bbox deltas,
            and masks.
        """
        b = 0  # batch item index
        image_index = -1
        image_ids = np.copy(dataset.image_ids)
        error_count = 0
    
        # Anchors
        # [anchor_count, (y1, x1, y2, x2)]
        anchors = utils.generate_pyramid_anchors(config.RPN_ANCHOR_SCALES,
                                                 config.RPN_ANCHOR_RATIOS,
                                                 config.BACKBONE_SHAPES,
                                                 config.BACKBONE_STRIDES,
                                                 config.RPN_ANCHOR_STRIDE)
    
        # Keras requires a generator to run indefinately.
        while True:
            try:
                ##只有在epoch的时候进行打乱
                # Increment index to pick next image. Shuffle if at the start of an epoch.
                image_index = (image_index + 1) % len(image_ids)
                if shuffle and image_index == 0:
                    np.random.shuffle(image_ids)
                #利用第一个函数得到该图像所对应的所有groundtruth值
                # Get GT bounding boxes and masks for image.
                image_id = image_ids[image_index]
                image, image_meta, gt_class_ids, gt_boxes, gt_masks = \
                    load_image_gt(dataset, config, image_id, augment=augment,
                                  use_mini_mask=config.USE_MINI_MASK)
    
                # Skip images that have no instances. This can happen in cases
                # where we train on a subset of classes and the image doesn't
                # have any of the classes we care about.
                if not np.any(gt_class_ids > 0):
                    continue
    
                # RPN Targets
                ##返回锚点中positive,neutral,negative分类信息和positive的anchors与gt_boxes的delta
                rpn_match, rpn_bbox = build_rpn_targets(image.shape, anchors,
                                                        gt_class_ids, gt_boxes, config)
    
                # Mask R-CNN Targets
                if random_rois:
                    rpn_rois = generate_random_rois(
                        image.shape, random_rois, gt_class_ids, gt_boxes)
                    if detection_targets:
                        rois, mrcnn_class_ids, mrcnn_bbox, mrcnn_mask =\
                            build_detection_targets(
                                rpn_rois, gt_class_ids, gt_boxes, gt_masks, config)
    
                # Init batch arrays
                if b == 0:
                    batch_image_meta = np.zeros(
                        (batch_size,) + image_meta.shape, dtype=image_meta.dtype)
                    batch_rpn_match = np.zeros(
                        [batch_size, anchors.shape[0], 1], dtype=rpn_match.dtype)
                    batch_rpn_bbox = np.zeros(
                        [batch_size, config.RPN_TRAIN_ANCHORS_PER_IMAGE, 4], dtype=rpn_bbox.dtype)
                    batch_images = np.zeros(
                        (batch_size,) + image.shape, dtype=np.float32)
                    batch_gt_class_ids = np.zeros(
                        (batch_size, config.MAX_GT_INSTANCES), dtype=np.int32)
                    batch_gt_boxes = np.zeros(
                        (batch_size, config.MAX_GT_INSTANCES, 4), dtype=np.int32)
                    if config.USE_MINI_MASK:
                        batch_gt_masks = np.zeros((batch_size, config.MINI_MASK_SHAPE[0], config.MINI_MASK_SHAPE[1],
                                                   config.MAX_GT_INSTANCES))
                    else:
                        batch_gt_masks = np.zeros(
                            (batch_size, image.shape[0], image.shape[1], config.MAX_GT_INSTANCES))
                    if random_rois:
                        batch_rpn_rois = np.zeros(
                            (batch_size, rpn_rois.shape[0], 4), dtype=rpn_rois.dtype)
                        if detection_targets:
                            batch_rois = np.zeros(
                                (batch_size,) + rois.shape, dtype=rois.dtype)
                            batch_mrcnn_class_ids = np.zeros(
                                (batch_size,) + mrcnn_class_ids.shape, dtype=mrcnn_class_ids.dtype)
                            batch_mrcnn_bbox = np.zeros(
                                (batch_size,) + mrcnn_bbox.shape, dtype=mrcnn_bbox.dtype)
                            batch_mrcnn_mask = np.zeros(
                                (batch_size,) + mrcnn_mask.shape, dtype=mrcnn_mask.dtype)
                #超过了config中instance的最大数量则进行删减。
                # If more instances than fits in the array, sub-sample from them.
                if gt_boxes.shape[0] > config.MAX_GT_INSTANCES:
                    ids = np.random.choice(
                        np.arange(gt_boxes.shape[0]), config.MAX_GT_INSTANCES, replace=False)
                    gt_class_ids = gt_class_ids[ids]
                    gt_boxes = gt_boxes[ids]
                    gt_masks = gt_masks[:, :, ids]
                ##把每张图片的信息添加到一个batch中,直到满为止
                # Add to batch
                batch_image_meta[b] = image_meta
                batch_rpn_match[b] = rpn_match[:, np.newaxis]
                batch_rpn_bbox[b] = rpn_bbox
                batch_images[b] = mold_image(image.astype(np.float32), config)
                batch_gt_class_ids[b, :gt_class_ids.shape[0]] = gt_class_ids
                batch_gt_boxes[b, :gt_boxes.shape[0]] = gt_boxes
                batch_gt_masks[b, :, :, :gt_masks.shape[-1]] = gt_masks
                if random_rois:
                    batch_rpn_rois[b] = rpn_rois
                    if detection_targets:
                        batch_rois[b] = rois
                        batch_mrcnn_class_ids[b] = mrcnn_class_ids
                        batch_mrcnn_bbox[b] = mrcnn_bbox
                        batch_mrcnn_mask[b] = mrcnn_mask
                b += 1
    
                # Batch full?
                if b >= batch_size:
                    inputs = [batch_images, batch_image_meta, batch_rpn_match, batch_rpn_bbox,
                              batch_gt_class_ids, batch_gt_boxes, batch_gt_masks]
                    outputs = []
    
                    if random_rois:
                        inputs.extend([batch_rpn_rois])
                        if detection_targets:
                            inputs.extend([batch_rois])
                            # Keras requires that output and targets have the same number of dimensions
                            batch_mrcnn_class_ids = np.expand_dims(
                                batch_mrcnn_class_ids, -1)
                            outputs.extend(
                                [batch_mrcnn_class_ids, batch_mrcnn_bbox, batch_mrcnn_mask])
    
                    yield inputs, outputs
    
                    # start a new batch
                    b = 0
            except (GeneratorExit, KeyboardInterrupt):
                raise
            except:
                # Log it and skip the image
                logging.exception("Error processing image {}".format(
                    dataset.image_info[image_id]))
                error_count += 1
                if error_count > 5:
                    raise

             接下里最重要的一个步骤就是构建Mask-RCNN的模型,又论文我们也知道,训练和预测需要分开的构建,因为两者存在差异的。这一段可以对着那几个流程图看看。

        def build(self, mode, config):
            """Build Mask R-CNN architecture.
                input_shape: The shape of the input image.
                mode: Either "training" or "inference". The inputs and
                    outputs of the model differ accordingly.
            """
            assert mode in ['training', 'inference']
    
            # Image size must be dividable by 2 multiple times
            h, w = config.IMAGE_SHAPE[:2]
            if h / 2**6 != int(h / 2**6) or w / 2**6 != int(w / 2**6):
                raise Exception("Image size must be dividable by 2 at least 6 times "
                                "to avoid fractions when downscaling and upscaling."
                                "For example, use 256, 320, 384, 448, 512, ... etc. ")
            ##构建所有需要的输入,并且都为神经网络的输入,可用KL.INPUT来转化
            # Inputs
            input_image = KL.Input(
                shape=config.IMAGE_SHAPE.tolist(), name="input_image")
            input_image_meta = KL.Input(shape=[None], name="input_image_meta")
            if mode == "training":
                # RPN GT
                input_rpn_match = KL.Input(
                    shape=[None, 1], name="input_rpn_match", dtype=tf.int32)
                input_rpn_bbox = KL.Input(
                    shape=[None, 4], name="input_rpn_bbox", dtype=tf.float32)
    
                # Detection GT (class IDs, bounding boxes, and masks)
                # 1. GT Class IDs (zero padded)
                input_gt_class_ids = KL.Input(
                    shape=[None], name="input_gt_class_ids", dtype=tf.int32)
                # 2. GT Boxes in pixels (zero padded)
                # [batch, MAX_GT_INSTANCES, (y1, x1, y2, x2)] in image coordinates
                input_gt_boxes = KL.Input(
                    shape=[None, 4], name="input_gt_boxes", dtype=tf.float32)
                # Normalize coordinates
                h, w = K.shape(input_image)[1], K.shape(input_image)[2]
                image_scale = K.cast(K.stack([h, w, h, w], axis=0), tf.float32)
                gt_boxes = KL.Lambda(lambda x: x / image_scale)(input_gt_boxes)
                # 3. GT Masks (zero padded)
                # [batch, height, width, MAX_GT_INSTANCES]
                if config.USE_MINI_MASK:
                    input_gt_masks = KL.Input(
                        shape=[config.MINI_MASK_SHAPE[0],
                               config.MINI_MASK_SHAPE[1], None],
                        name="input_gt_masks", dtype=bool)
                else:
                    input_gt_masks = KL.Input(
                        shape=[config.IMAGE_SHAPE[0], config.IMAGE_SHAPE[1], None],
                        name="input_gt_masks", dtype=bool)
            ##实现FPN的多层特征融合
            # Build the shared convolutional layers.
            # Bottom-up Layers
            # Returns a list of the last layers of each stage, 5 in total.
            # Don't create the thead (stage 5), so we pick the 4th item in the list.
            _, C2, C3, C4, C5 = resnet_graph(input_image, "resnet101", stage5=True)
            # Top-down Layers
            # TODO: add assert to varify feature map sizes match what's in config
            P5 = KL.Conv2D(256, (1, 1), name='fpn_c5p5')(C5)
            P4 = KL.Add(name="fpn_p4add")([
                KL.UpSampling2D(size=(2, 2), name="fpn_p5upsampled")(P5),
                KL.Conv2D(256, (1, 1), name='fpn_c4p4')(C4)])
            P3 = KL.Add(name="fpn_p3add")([
                KL.UpSampling2D(size=(2, 2), name="fpn_p4upsampled")(P4),
                KL.Conv2D(256, (1, 1), name='fpn_c3p3')(C3)])
            P2 = KL.Add(name="fpn_p2add")([
                KL.UpSampling2D(size=(2, 2), name="fpn_p3upsampled")(P3),
                KL.Conv2D(256, (1, 1), name='fpn_c2p2')(C2)])
            # Attach 3x3 conv to all P layers to get the final feature maps.
            P2 = KL.Conv2D(256, (3, 3), padding="SAME", name="fpn_p2")(P2)
            P3 = KL.Conv2D(256, (3, 3), padding="SAME", name="fpn_p3")(P3)
            P4 = KL.Conv2D(256, (3, 3), padding="SAME", name="fpn_p4")(P4)
            P5 = KL.Conv2D(256, (3, 3), padding="SAME", name="fpn_p5")(P5)
            # P6 is used for the 5th anchor scale in RPN. Generated by
            # subsampling from P5 with stride of 2.
            P6 = KL.MaxPooling2D(pool_size=(1, 1), strides=2, name="fpn_p6")(P5)
    
            # Note that P6 is used in RPN, but not in the classifier heads.
            rpn_feature_maps = [P2, P3, P4, P5, P6]
            mrcnn_feature_maps = [P2, P3, P4, P5]
    
            # Generate Anchors
            self.anchors = utils.generate_pyramid_anchors(config.RPN_ANCHOR_SCALES,
                                                          config.RPN_ANCHOR_RATIOS,
                                                          config.BACKBONE_SHAPES,
                                                          config.BACKBONE_STRIDES,
                                                          config.RPN_ANCHOR_STRIDE)
            #构建RPN 网络,用来接受上一级的feature maps
            #BACKBONE_SHAPES:[N,2]
            # RPN Model :RPN_ANCHOR_STRIDE为产生anchors的pixels,len(config.RPN_ANCHOR_RATIOS)为每个pixels产生anchors的数量
            #256为接受feature maps的channel
            rpn = build_rpn_model(config.RPN_ANCHOR_STRIDE,
                                  len(config.RPN_ANCHOR_RATIOS), 256)
            # Loop through pyramid layers
            layer_outputs = []  # list of lists
            for p in rpn_feature_maps:
                layer_outputs.append(rpn([p]))
            # Concatenate layer outputs
            # Convert from list of lists of level outputs to list of lists
            # of outputs across levels.
            # e.g. [[a1, b1, c1], [a2, b2, c2]] => [[a1, a2], [b1, b2], [c1, c2]]
            output_names = ["rpn_class_logits", "rpn_class", "rpn_bbox"]
            outputs = list(zip(*layer_outputs))
            outputs = [KL.Concatenate(axis=1, name=n)(list(o))
                       for o, n in zip(outputs, output_names)]
            ##
            rpn_class_logits, rpn_class, rpn_bbox = outputs
            ##利用proposal_layer来产生一系列的ROIS,输入为RPN网络中得到的输出:rpn_class, rpn_bbox
            # Generate proposals
            # Proposals are [batch, N, (y1, x1, y2, x2)] in normalized coordinates
            # and zero padded.
            proposal_count = config.POST_NMS_ROIS_TRAINING if mode == "training"\
                else config.POST_NMS_ROIS_INFERENCE
            rpn_rois = ProposalLayer(proposal_count=proposal_count,
                                     nms_threshold=config.RPN_NMS_THRESHOLD,
                                     name="ROI",
                                     anchors=self.anchors,
                                     config=config)([rpn_class, rpn_bbox])
    
            if mode == "training":
                # Class ID mask to mark class IDs supported by the dataset the image
                # came from.
                #active_class_ids表示的是当前数据集下含有的class类别
                _, _, _, active_class_ids = KL.Lambda(lambda x: parse_image_meta_graph(x),
                                                      mask=[None, None, None, None])(input_image_meta)
    
                if not config.USE_RPN_ROIS:
                    # Ignore predicted ROIs and use ROIs provided as an input.
                    input_rois = KL.Input(shape=[config.POST_NMS_ROIS_TRAINING, 4],
                                          name="input_roi", dtype=np.int32)
                    # Normalize coordinates to 0-1 range.
                    target_rois = KL.Lambda(lambda x: K.cast(
                        x, tf.float32) / image_scale[:4])(input_rois)
                else:
                    target_rois = rpn_rois
    
                # Generate detection targets
                # Subsamples proposals and generates target outputs for training
                # Note that proposal class IDs, gt_boxes, and gt_masks are zero
                # padded. Equally, returned rois and targets are zero padded.
                rois, target_class_ids, target_bbox, target_mask =\
                    DetectionTargetLayer(config, name="proposal_targets")([
                        target_rois, input_gt_class_ids, gt_boxes, input_gt_masks])
    
                # Network Heads
                # TODO: verify that this handles zero padded ROIs
                mrcnn_class_logits, mrcnn_class, mrcnn_bbox =\
                    fpn_classifier_graph(rois, mrcnn_feature_maps, config.IMAGE_SHAPE,
                                         config.POOL_SIZE, config.NUM_CLASSES)
    
                mrcnn_mask = build_fpn_mask_graph(rois, mrcnn_feature_maps,
                                                  config.IMAGE_SHAPE,
                                                  config.MASK_POOL_SIZE,
                                                  config.NUM_CLASSES)
    
                # TODO: clean up (use tf.identify if necessary)
                output_rois = KL.Lambda(lambda x: x * 1, name="output_rois")(rois)
    
                # 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])
    
                # Model
                inputs = [input_image, input_image_meta,
                          input_rpn_match, input_rpn_bbox, input_gt_class_ids, input_gt_boxes, input_gt_masks]
                if not config.USE_RPN_ROIS:
                    inputs.append(input_rois)
                outputs = [rpn_class_logits, rpn_class, rpn_bbox,
                           mrcnn_class_logits, mrcnn_class, mrcnn_bbox, mrcnn_mask,
                           rpn_rois, output_rois,
                           rpn_class_loss, rpn_bbox_loss, class_loss, bbox_loss, mask_loss]
                model = KM.Model(inputs, outputs, name='mask_rcnn')
            else:
                # Network Heads
                # Proposal classifier and BBox regressor heads
                mrcnn_class_logits, mrcnn_class, mrcnn_bbox =\
                    fpn_classifier_graph(rpn_rois, mrcnn_feature_maps, config.IMAGE_SHAPE,
                                         config.POOL_SIZE, config.NUM_CLASSES)
    
                # Detections
                # output is [batch, num_detections, (y1, x1, y2, x2, class_id, score)] in image coordinates
                detections = DetectionLayer(config, name="mrcnn_detection")(
                    [rpn_rois, mrcnn_class, mrcnn_bbox, input_image_meta])
    
                # Convert boxes to normalized coordinates
                # TODO: let DetectionLayer return normalized coordinates to avoid
                #       unnecessary conversions
                h, w = config.IMAGE_SHAPE[:2]
                detection_boxes = KL.Lambda(
                    lambda x: x[..., :4] / np.array([h, w, h, w]))(detections)
    
                # Create masks for detections
                mrcnn_mask = build_fpn_mask_graph(detection_boxes, mrcnn_feature_maps,
                                                  config.IMAGE_SHAPE,
                                                  config.MASK_POOL_SIZE,
                                                  config.NUM_CLASSES)
    
                model = KM.Model([input_image, input_image_meta],
                                 [detections, mrcnn_class, mrcnn_bbox,
                                     mrcnn_mask, rpn_rois, rpn_class, rpn_bbox],
                                 name='mask_rcnn')
    
            # Add multi-GPU support.
            if config.GPU_COUNT > 1:
                from parallel_model import ParallelModel
                model = ParallelModel(model, config.GPU_COUNT)

            构建完了之后,其他的编译和训练函数的编写就比较简单而且好理解了,就不贴上来了。

    其实看到这里我觉得代码mask-RCNN的框架和一些具体的细节应该是了解了挺多了,但就我个人而言的话,这些代码我是看了2到3遍才看懂的,只能应了一句话,好事多磨.....

           最后我把我处理视频的代码贴上来,其实处理视频就是把视频切割成帧,然后用模型处理,再合成为视频,但这样确实很耗时间。

        

    import os
    import sys
    import random
    import math
    import numpy as np
    import skimage.io
    import matplotlib
    import matplotlib.pyplot as plt
    import cv2
    import coco
    import utils
    import model as modellib
    import video_visualize
    
    %matplotlib inline 
    
    # Root directory of the project
    ROOT_DIR = os.getcwd()
    
    # Directory to save logs and trained model
    MODEL_DIR = os.path.join(ROOT_DIR, "logs")
    
    # Local path to trained weights file
    COCO_MODEL_PATH = os.path.join(ROOT_DIR, "mask_rcnn_coco.h5")
    # Download COCO trained weights from Releases if needed
    if not os.path.exists(COCO_MODEL_PATH):
        utils.download_trained_weights(COCO_MODEL_PATH)
    
    # Directory of images to run detection on
    IMAGE_DIR = os.path.join(ROOT_DIR, "images")
    
    
    class InferenceConfig(coco.CocoConfig):
        # Set batch size to 1 since we'll be running inference on
        # one image at a time. Batch size = GPU_COUNT * IMAGES_PER_GPU
        GPU_COUNT = 1
        IMAGES_PER_GPU = 1
    
    config = InferenceConfig()
    config.display()
    
    
    # Create model object in inference mode.
    model = modellib.MaskRCNN(mode="inference", model_dir=MODEL_DIR, config=config)
    
    # Load weights trained on MS-COCO
    model.load_weights(COCO_MODEL_PATH, by_name=True)
    
    
    # COCO Class names
    # Index of the class in the list is its ID. For example, to get ID of
    # the teddy bear class, use: class_names.index('teddy bear')
    class_names = ['BG', 'person', 'bicycle', 'car', 'motorcycle', 'airplane',
                   'bus', 'train', 'truck', 'boat', 'traffic light',
                   'fire hydrant', 'stop sign', 'parking meter', 'bench', 'bird',
                   'cat', 'dog', 'horse', 'sheep', 'cow', 'elephant', 'bear',
                   'zebra', 'giraffe', 'backpack', 'umbrella', 'handbag', 'tie',
                   'suitcase', 'frisbee', 'skis', 'snowboard', 'sports ball',
                   'kite', 'baseball bat', 'baseball glove', 'skateboard',
                   'surfboard', 'tennis racket', 'bottle', 'wine glass', 'cup',
                   'fork', 'knife', 'spoon', 'bowl', 'banana', 'apple',
                   'sandwich', 'orange', 'broccoli', 'carrot', 'hot dog', 'pizza',
                   'donut', 'cake', 'chair', 'couch', 'potted plant', 'bed',
                   'dining table', 'toilet', 'tv', 'laptop', 'mouse', 'remote',
                   'keyboard', 'cell phone', 'microwave', 'oven', 'toaster',
                   'sink', 'refrigerator', 'book', 'clock', 'vase', 'scissors',
                   'teddy bear', 'hair drier', 'toothbrush']
    
    
    
    #处理视频需要用到的文件及其文件夹
    input_path = os.path.join(ROOT_DIR, "luozx")
    
    frame_interval = 1
    ##列出所有的视频文件名字
    filenames = os.listdir(input_path)
    ##得到文件夹的名字
    video_prefix = input_path.split(os.sep)[-1]
    frame_path = "{}_frame".format(input_path)
    
    if not os.path.exists(frame_path):
        os.mkdir(frame_path)
        
    #读取图片并且保存其每一帧
    cap = cv2.VideoCapture()
    # for filename in filenames:
    for filename in filenames:
    # if 1 == 1:
    #     filename = 'huan.mp4'
        filepath = os.sep.join([input_path, filename])
        flag = cap.open(filepath)
        assert flag == True
        ##获取视频帧
        n_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
        #或者直接用n_frames = cap.
        print(n_frames)
        if n_frames > 800:
            n_frames = 800
            
    #     for i in range(42):
    #         cap.read()
        for i in range(n_frames):
            ret, frame = cap.read()
            #assert ret == True
            if i % frame_interval == 0:
                #存储图片的路径及其名字
                imagename = '{}_{}_{:0>6d}.jpg'.format(video_prefix, filename.split('.')[0], i)
                imagepath = os.sep.join([frame_path, imagename])
                print("export{}!".format(imagepath))
                cv2.imwrite(imagepath, frame)
            
    fps = cap.get(5)
    
    cap.release()
    
    ##处理视频中的每一帧图片并且进行保留
     for i in range(n_frames):
        #find the direction of the images
        imagename = '{}_{}_{:0>6d}.jpg'.format(video_prefix, filename.split('.')[0], i)
        imagepath = os.sep.join([frame_path, imagename])
        print(imagepath)
        #load the image
        image = skimage.io.imread(imagepath)
        
        # Run detection
        results = model.detect([image], verbose=1)
        r = results[0]
        
        # save the dealed image 
    
        video_visualize.save_dealed_image(filename, video_prefix, i, image, r['rois'], r['masks'], r['class_ids'], 
                          class_names, r['scores'], title="",
                          figsize=(16, 16), ax=None)
    ##其中video_visaulize.save_dealed_imag函数就是把display_instance()函数小小的改动了一下,存储了一下处理完后的相片。
    
    ##把处理完的图像进行视频合成
    #把处理好的每一帧再合成视频
    import os
    import cv2
    import skimage.io
    fps = 22
    n_frames = 200
    ROOT = os.getcwd()
    save_path = os.path.join(ROOT,"save_images")
    fourcc = cv2.VideoWriter_fourcc(*'MJPG')
    #get the width and height of processed image
    imagepath = "/home/xiongliang/python/python_project/Mask_RCNN/save_images/luozx_promise_000001.jpg"
    image = skimage.io.imread(imagepath)
    width, height, _ = image.shape
    videoWriter = cv2.VideoWriter("save_video.mp4", fourcc, fps, (width, height))
    
    video_prefix = "luozx"
    filename = "promise.mp4"
    for i in range(int(n_frames)):
        imagename = '{}_{}_{:0>6d}.jpg'.format(video_prefix, filename.split('.')[0], i)
        imagepath = os.sep.join([save_path, imagename])
        frame = cv2.imread(imagepath)
        videoWriter.write(frame)
    videoWriter.release()
    
    ###对视频进行播放
    ROOT = os.getcwd()
    path = os.path.join(ROOT, "save_video.mp4")
    cap = cv2.VideoCapture(path)
    assert cap.isOpened() == True
    while(cap.isOpened()):
        ret, frame = cap.read()
        cv2.imshow('frame',frame)
        if cv2.waitKey(1) & 0xFF == ord('q'):  # 适当调整等待时间
            break
     
    

    结语

             这些基本上就是这十多天学习到的所有的东西了,这条漫长的路还有很远要走,加油了。第一次发博客,文中肯定有很多不对的地方,望大佬们指正 。溜了溜了,  后天还得毕设开题答辩,开题报告还没写

    哭哭

    展开全文
  • 目标检测(或分隔)算法Mask R-CNN简介

    千次阅读 2019-02-17 18:22:29
    在博文https://blog.csdn.net/fengbingchun/article/details/87195597 中对Faster R-CNN...Mask R-CNN是faster R-CNN的扩展形式,能够有效地检测图像中的目标,并且Mask R-CNN训练简单,只需要在Faster R-CNN的基...
  • 目标检测 图像分割 MASK-RCNN

    万次阅读 多人点赞 2018-06-01 13:07:37
    原文链接 https://blog.csdn.net/jiongnima/article/details/79094159Mask R-CNN是ICCV 2017的best paper,彰显了机器学习计算机视觉领域在2017年的最新成果。...Mask R-CNN就是典型的代表。本篇...
  • mask rcnn训练自己的数据集

    万次阅读 多人点赞 2019-11-12 10:07:46
    最近迷上了mask rcnn,也是由于自己工作需要吧,特意研究了其源代码,并基于自己的数据进行训练~ 本博客参考https://blog.csdn.net/disiwei1012/article/details/79928679#commentsedit 实验目的 ...
  • Mask R-CNN原理详细解读

    千次阅读 2019-04-02 11:55:56
    Mask R-CNN是在Faster R-CNN的基础上添加了一个预测分割mask的分支,如上图所示。其中黑色部分为原来的Faster-RCNN,红色部分为在Faster-RCNN网络上的修改。将RoI Pooling 层替换成了RoIAlign层;添加了并列的FCN层...
  • mask-rcnn解读

    千次阅读 多人点赞 2019-08-23 19:01:33
    文章目录原理解读简介总体架构架构分解backboneFPN(Feature Pyramid Networks)FPN解决了什么问题?自下而上的路径自上而下的路径和横向连接应用RPN中的特征金字塔网络Fast R-CNN 中的特征金字塔网络总结ROIAlignROI ...
  • Mask RCNN 实战(一)--代码详细解析

    万次阅读 多人点赞 2019-05-13 17:19:52
    Mask RCNN:(大家有疑问的请在评论区留言) 如果对原理不了解的话,可以花十分钟先看一下我的这篇博文,在来进行实战演练,这篇博文将是让大家对mask rcnn 进行一个入门,我在后面的博文中会介绍mask rcnn 如何用于...
  • 大名鼎鼎的 Mask RCNN 一举夺得 ICCV2017 Best Paper,名声大造。Mask RCNN 是何恺明基于以往的 Faster RCNN 架构提出的新的卷积网络,实现高效地检测图中的物体,并同时生成一张高质量的每个个体的分割掩码,即有效...
  • Mask RCNN 源代码解析 (1) - 整体思路

    万次阅读 多人点赞 2018-07-28 11:26:05
    Mask RCNN 属于 RCNN这一系列的应该是比较最终的版本,融合多种算法的思想,这里对Mask RCNN从源代码进行解析,主要写几篇文章,一个总结大的思路,其他文章整理细节。 这篇文章为了简单,主要从前向传播和后向传播...
  • Mask RCNN 实现视频和图片中的多人姿态检测

    万次阅读 多人点赞 2018-06-08 19:20:22
    Mask RCNN是目标分割检测框架--扩展到人体关键点检测对于原理不清晰的同学,建议你去看一下Kaming He的论文:https://arxiv.org/pdf/1703.06870.pdf我的博客里也有论文的翻译版:Mask R-CNN 论文翻译对于视频中的多人...
  • 自己精心整理的目标检测系列视频讲解mp4,从RCNN>FastRCNN>FasterRCNN>FPN>MaskRCNN,华文讲解,很详细! 01懒人学RCNN.mp4 02懒人学FastRCNN.mp4 03懒人讲FasterRCNN之简介.mp4 04懒人学FasterRCNN之融合.mp4 05懒人...
  • 小白学习深度学习坑一mask rcnn安装及测试 基本设施 小白一只,因之前学习需要,需要安装mask...第一步输入conda create -n maskrcnn python=3.6第二步输入y 第三步activate maskrcnn 接下来就是安装了,依次输入pip in
  • MaskRCNN-Benchmark框架训练自己的数据集

    万次阅读 多人点赞 2018-11-29 13:12:19
     Facebook AI Research 开源了 Faster R-CNN 和 Mask R-CNN 的 PyTorch 1.0 实现基准:MaskRCNN-Benchmark。相比 Detectron 和 mmdetection,MaskRCNN-Benchmark 的性能相当,并拥有更快的训练速度和更低的 GPU ...
  • Mask Rcnn使用篇-训练自己的数据集

    万次阅读 热门讨论 2018-10-31 13:52:18
    看了Mask Rcnn后有种豁然开朗的感觉,除了膜拜没别的想法了。这篇只写怎么使用,原理后面在写吧。 必要的开发环境我就不啰嗦了,在代码链接里有,如果只是训练自己的训练集,coco tools就不用下了,windows下安装还...
  • MaskRCNN-Keypoints

    2019-07-03 05:26:17
    图片选自mask rcnn的论文,这里由于时间的关系,就不多叙述技术细节了,网上有很多关于mask rcnn的博客,这里的keypoints是在mask rcnn上又添加了一个keypoints分支,总的模型结构图就变成如下形式了 展示一下具体...
  • Maskrcnn_benchmark最新安装教程以及错误解决方法(踩坑Ubuntu16.04) 0、序言 ​ 最近要做一些目标检测、识别的比赛,用到了何大神的maskrcnn,但是在配maskrcnn环境问题上出了很多错误,浪费了很多时间,特此做...
1 2 3 4 5 ... 20
收藏数 7,506
精华内容 3,002
关键字:

maskrcnn