图像分割技术及经典实例分割网络Mask R-CNN(含Python源码界说)
1. 图像分割技术概述
图像分割技术是可以浅近的理解为风雅化的目标检测过程,由于之前的目标检测算法只能使用标定框框定规则地区,从而举行分类,标出目标的大题地区,但是,在譬如主动驾驶范畴,仅仅只有一个规则的地区去框定目标还是不敷的,好比遇到车道线,那么仅仅用一个矩形地区框定车道线并不能正确地引导车辆的下一步动向,以是我们需要一个可以大概追溯细节的新应用范畴,来将检测到的目标风雅化。
图像分割即为图片的每个对象创建一个像素级的掩膜,这样可以追溯到目标外貌的更多细节。下图为细菌的图像分割例子,为每个细菌做了图像分割。

图像分类有两类:
- 语义分割:语义分割关注种别,忽略个体实例。

- 实例分割:力气分割关注个体实例。

为了使得概念更加直观,下图分析白分类、目标检测、语义分割、实例分割的差异任务直观示意图。

2. FCN与语义分割
2.1 FCN简介
FCN即Fully Convolutional Networks,全卷积网络,FCN将传统卷积网络后面的全连接层换成了卷积层,这样网络输出不再是种别而是heatmap;同时为了办理由于卷积和池化对图像尺寸的影响,提出使用上采样的方式恢复尺寸。
传统分类使用的网络结构一样平常通过在末了连接全连接层,它会将原来二维的图像信息“压扁”成一维的(分类),从而丢失了空间信息,末了训练输出一个标量,这就是我们的分类标签。FCN网络和一样平常的网络的最大差异是,FCN产生的输出和输入的维度保持同等,即改变原本的CNN网络末了的全连接层,将其调整为卷积层,这样原本的分类网络最终输出一个热度图类型的图像。

着实在笔者的上一篇博文就提过全卷积,全卷积网络最大的特点是可以顺应恣意尺寸的输入,由于其不受最终分类数量的限制。
2.2 反卷积
反卷积又称转置卷积,它并不是正向卷积的完全逆过程。反卷积是一种特殊的正向卷积,先按照一
定的比例通过补0来扩大输入图像的尺寸,接着旋转卷积核,再举行正向卷积。
具体参考此文章《反卷积(Transposed Convolution)具体推导》
反卷积的处理处罚步调概括:
- 将上一层的卷积核反转(上下左右方向举行反转)。
- 将上一层卷积的效果作为输入,做补0扩充操纵,即往每一个元素后面补0。这一步是根据步长来的,对于每个元素沿着步长方向补(步长减一)个0。比方,步长为1就不用补0了
- 在扩充后的输入根本上再对整体补0。以原始输入的shape作为输出shape,按照卷积padding规则,计算pading的补0的位置及个数,得到补0的位置及个数。
- 将补0后的卷积效果作为真正的输入,反转后的卷积核为filter,举行步长为1的卷积操纵。
计算padding按规则补0时,同一按照padding=‘SAME’、步长为 1 × 1 1\times 1 1×1的方式来计算


卷积输出通道公式:
o = i − k + 2 × p s + 1 o=\frac{i-k+2\times p}{s}+1 o=si−k+2×p+1
反卷积输入输出通道关系计算公式:
i = ( o − 1 ) × s + k − 2 × p i=(o-1)\times s+k-2\times p i=(o−1)×s+k−2×p
留意
通过反卷积操纵并不能还原出卷积之前的图片,只能还原出卷积之前图片的尺寸。卷积和反卷积,并没有什么关系,操纵的过程也都是不可逆的。
反卷积的应用场景:
- 反卷积/转置卷积在语义分割范畴应用很广,假如说pooling层用于特征降维,那么在多个pooling
层后,就需要用转置卷积来举行分辨率的恢复。
- 假如up-sampling采用双线性插值举行分辨率的提拔,这种提拔好坏学习的。采用反卷积来完成上采样的工作,就可以通过学习的方式得到更高的精度

反卷积的范围性:
矩阵稀疏,有大量的0元素,因此大量的信息是无用的,反卷积所用的转置矩阵计算好坏常斲丧计算资源的。
2.2 FCN与语义分割的关系
FCN对图像举行像素级的分类,从而办理了语义级别的图像分割(semantic segmentation)问题。FCN可以担当恣意尺寸的输入图像,采用反卷积层对末了一个卷积层的feature map举行上采样, 使它恢复到输入图像相同的尺寸,从而可以对每个像素都产生了一个猜测, 同时保存了原始输入图像中的空间信息, 末了在上采样的特征图上举行逐像素分类。末了逐个像素计算softmax分类的丧失, 相称于每一个像素对应一个训练样本。
对全卷积网络的末了再举行upsampling(上采样),即可得到和原图巨细一样的输出,这就是
热度图了。这里上采样采用了deconvolutional(反卷积)的方法。
反最大池化、反均匀池化
池化操纵中最常见的最大池化和均匀池化,因此最常见的反池化操纵有反最大池化和反均匀池化。
反最大池化需要纪录池化时最大值的位置,反均匀池化不需要此过程。

语义分割的一种实现-DeconvNet

3. Mask R-CNN
3.1 实例分割的难点
实例分割(instance segmentation)的难点在于:需要同时检测出目标的位置而且对目标举行分割,以是这就需要融合目标检测(框出目标的位置)以及语义分割(对像素举行分类,分割出目标)方法。

3.2 FPN(特征金字塔)
FPN即Feature Pyramid Networks,特征金字塔,其提出的配景为目标检测任务和语义分割任务内里经常需要检测小目标。但是当小目标比力小时,大概在原图内里只有几十个像素点。对于深度卷积网络,从一个特征层卷积到另一个特征层,无论步长是1还是2还是更多,卷积核都要遍布整个图片举行卷积,大的目标所占的像素点比小目标多,以是大的目标被颠末卷积核的次数远比小的目标多,以是在下一个特征层里,会更多的反应大目标的特点。特别是在步长大于便是2的情况下,大目标的特点更轻易得到保存,小目标的特征点轻易被跳过。因此,颠末许多层的卷积之后,小目标的特点会越来越少。
比方此图中的猫,对于周边的落叶来说,显着差距很大。

特征图(feature map)用蓝色外貌表现,较粗的外貌表现语义上更强的特征图。
- 对于(a),使用图像金字塔构建特征金字塔。特征是根据每个差异巨细比例的图像独立计算的,每计算一次特征都需要resize一下图片巨细,耗时,速率很慢。
- 对于(b),检测体系都在采用的为了更快地检测而使用的单尺度特征检测。
- 对于©,由卷积计算的金字塔特征层次来举行目标位置猜测,但底层feature map特征表达本领不敷。
- 对于(d),特征金字塔网络(FPN)和b,c一样快,但更正确。

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

3.2 Mask R-CNN
Mask R-CNN可算作是Faster R-CNN的升级版。Faster R-CNN广泛用于目标检测。对于给定图像,它会给图中每个对象加上种别标签与边界框坐标。Mask R-CNN框架是以Faster R-CNN为根本而架的。因此,针对给定图像, Mask R-CNN不光会给每个对象添加类标签与边界框坐标,还会返回其对象掩膜。

Mask R-CNN的网络结构


在Mask R-CNN的计划中网络在举行目标检测的同时举行了实例分割。
Mask R-CNN的大要框架与Faster-RCNN框架相似,可以说在根本特征网络之后又参加了全连接的分割子网,由原来的两个任务(分类+回归)变为了三个任务(分类+回归+分割)。Mask R-CNN 是一个两阶段的框架,第一个阶段扫描图像并天生候选地区(proposals,即有大概包罗一个目标的地区),第二阶段分类候选地区并天生边界框和掩码。
Faster R-CNN请参考笔者的上一篇博文《CV学习笔记-Faster-RCNN》

Mask R-CNN的差异
- 使用ResNet网络作为backbone
- 将 Roi Pooling 层更换成了 RoiAlign
- 添加并列的 Mask 层
- 引入FPN 和 FCN

Mask R-CNN的处理处罚流程:
- 输入图像,举行预处理处罚
- 将预处理处罚后的图像信息输入到预训练好的神经网络中(比方ResNet)得到feature map
- 对这个feature map中的每一点设定预定个的ROI,从而得到多个候选ROI
- 将这些候选的ROI送入RPN网络举行二值分类(positive或negative)和BB回归,过滤掉一部分候选的ROI(停止到现在,Mask和Faster完全相同);
- 对这些剩下的ROI举行ROIAlign操纵(ROIAlign为Mask R-CNN创新点1,比ROIPooling有长足进步);
- 末了,对这些ROI举行分类(N种别分类)、BB回归和MASK天生(在每一个ROI内里举行FCN操纵)(引入FCN天生Mask是创新点2,使得此网络可以举行分割型任务)。
Mask R-CNN使用Resnet101作为backbone对应为CNN提取主干特征网络部分
ResNet的干系内容请参照笔者之前的博客《CV学习笔记-ResNet》
特征金字塔FPN的构建是为了实现特征多尺度的融合,在Mask R-CNN当中,我们取出在主干特征提取网络中长宽压缩了两次C2、三次C3、四次C4、五次C5的效果来举行特征金字塔结构的构造。
具体构造过程如下:

P2-P5是未来用于猜测物体的bbox,box-regression,mask的。P2-P6是用于训练RPN的,即P6只用于RPN网络中。
3.3 RoiAlign
Mask-RCNN中提出了一个新的头脑就是RoIAlign,着实RoIAlign就是在RoI pooling上轻微改动过来的,但是为什么在模型中不继续使用RoI pooling呢?
Roi pooling请参考笔者博客CV学习笔记-Faster-RCNN 3.3节

在RoI pooling中出现了两次的取整,固然在feature maps上取整看起来只是小数级别的数,但是当
把feature map还原到原图上时就会出现很大的毛病,好比第一次的取整是舍去了0.78
(665/32=20.78),还原到原图时是20*32=640,第一次取整就存在了25个像素点的毛病,在第二
次的取整后的毛病更加的大。对于分类和物体检测来说大概这不是一个很大的误差,但是对于实
例分割而言,这是一个非常大的毛病,由于mask出现没对齐的话在视觉上是很显着的。而RoIAlign
的提出就是为了办理这个不对齐问题。

简单概括就是RoiAlign取消了取整的简单取舍,使用双线性插值的做法得到固定四个点坐标的像素值,从而使得不连续的操纵变得连续起来返回到原图的时候误差将会更小。
它充实的利用了原图中假造点(好比20.56这个浮点数。像素位置都是整数值,没有浮点值)附近
的四个真实存在的像素值来共同决定目标图中的一个像素值,即可以将20.56这个假造的位置点对
应的像素值估计出来。
示例:
蓝色的虚线框表现卷积后得到的feature map,玄色实线框表现ROI feature。
末了需要输出的巨细是2x2,那么我们就利用双线性插值来估计这些蓝点(假造坐标点,又称双线性插值的网格点)处所对应的像素值,末了得到相应的输出。
然后在每一个橘赤色的地区内里举行max pooling大概average pooling操纵,得到最终2x2的输出效果。我们的整个过程中没有效到量化操纵,没有引入误差,即原图中的像素和feature map中的像素是完全对齐的,没有毛病,这不光会进步检测的精度,同时也会有利于实例分割。

RoiAlign的输出将作为分割掩膜的依据。
3.4 分割掩膜
得到感爱好地区(ROI)后,给已有框架加上一个掩膜分支,每个囊括特定对象的地区都会被赋予一个掩膜。每个地区都会被赋予一个 m × m m\times m m×m掩膜,并按比例放大以便推断。


mask语义分割信息的获取
在之前的步调中,我们得到了猜测框,我们把这个猜测框作为mask模型的地区截取部分,利用这个猜测框对mask模型中用到的公用特征层举行截取。截取后,利用mask模型再对像素点举行分类,得到语义分割效果。
mask分支采用FCN对每个RoI产生一个 K × m × m K\times m \times m K×m×m的输出,即K个分辨率为 m × m m\times m m×m的二值的掩膜,K为分类物体的种类数量。
K × m × m K\times m \times m K×m×m二值mask结构解释:最终的FCN输出一个K层的mask,每一层为一类。用0.5作为阈值进
行二值化,产生配景和远景的分割Mask。

对于猜测的二值掩膜输出,我们对每个像素点应用sigmoid函数(或softmax等),整体丧失界说
为交叉熵。引入猜测K个输出的机制,允许每个类都天生独立的掩膜,避免类间竞争。这样做解
耦了掩膜和种类猜测。

Mask R-CNN的丧失函数为:
L = L c l s + L b o x + L m a s k L=L_{cls}+L_{box}+L_{mask} L=Lcls+Lbox+Lmask
Lmask 使得网络可以大概输出每一类的 mask,且不会有差异种别 mask 间的竞争:
- 分类网络分支猜测 object 种别标签,以选择输出 mask。对每一个ROI,假如检测得到的ROI属于哪一个分类,就只使用哪一个分支的交叉熵误差作为误差值举行计算。
- 举例分析:分类有3类(猫,狗,人),检测得到当前ROI属于“人”这一类,那么所使用的Lmask为“人”这一分支的mask,即每个class种别对应一个mask可以有效避免类间竞争(其他class不贡献Loss)
- 对每一个像素应用sigmoid,然后取RoI上全部像素的交叉熵的均匀值作为Lmask。
网络输出怎样将 14 × 14 14\times 14 14×14大概 28 × 28 28\times 28 28×28巨细的mask映射到原图是个问题
着实一个后处理处罚,将模型猜测的mask通过resize得到与proposal中目标相同巨细的mask即可。
4. 工程实践
代码工程代码稍大,以是摘取网络界说的焦点源码环节展示:
- from keras.layers import Input,ZeroPadding2D,Conv2D,MaxPooling2D,BatchNormalization,Activation,UpSampling2D,Add,Lambda,Concatenate
- from keras.layers import Reshape,TimeDistributed,Dense,Conv2DTranspose
- from keras.models import Model
- import keras.backend as K
- from nets.resnet import get_resnet
- from nets.layers import ProposalLayer,PyramidROIAlign,DetectionLayer,DetectionTargetLayer
- from nets.mrcnn_training import *
- from utils.anchors import get_anchors
- from utils.utils import norm_boxes_graph,parse_image_meta_graph
- import tensorflow as tf
- import numpy as np
- '''
- TimeDistributed:
- 对FPN网络输出的多层卷积特征进行共享参数。
- TimeDistributed的意义在于使不同层的特征图共享权重。
- '''
- #------------------------------------#
- # 五个不同大小的特征层会传入到
- # 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 Align,利用建议框在特征层上进行截取
- # 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
- def get_predict_model(config):
- 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. ")
-
- # 输入进来的图片必须是2的6次方以上的倍数
- input_image = Input(shape=[None, None, config.IMAGE_SHAPE[2]], name="input_image")
- # meta包含了一些必要信息
- input_image_meta = Input(shape=[config.IMAGE_META_SIZE],name="input_image_meta")
- # 输入进来的先验框
- input_anchors = Input(shape=[None, 4], name="input_anchors")
- # 获得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]
- anchors = input_anchors
- # 建立RPN模型
- rpn = build_rpn_model(len(config.RPN_ANCHOR_RATIOS), config.TOP_DOWN_PYRAMID_SIZE)
- rpn_class_logits, rpn_class, rpn_bbox = [],[],[]
- # 获得RPN网络的预测结果,进行格式调整,把五个特征层的结果进行堆叠
- for p in rpn_feature_maps:
- logits,classes,bbox = rpn([p])
- rpn_class_logits.append(logits)
- rpn_class.append(classes)
- rpn_bbox.append(bbox)
- rpn_class_logits = Concatenate(axis=1,name="rpn_class_logits")(rpn_class_logits)
- rpn_class = Concatenate(axis=1,name="rpn_class")(rpn_class)
- rpn_bbox = Concatenate(axis=1,name="rpn_bbox")(rpn_bbox)
- # 此时获得的rpn_class_logits、rpn_class、rpn_bbox的维度是
- # rpn_class_logits : Batch_size, num_anchors, 2
- # rpn_class : Batch_size, num_anchors, 2
- # rpn_bbox : Batch_size, num_anchors, 4
- proposal_count = config.POST_NMS_ROIS_INFERENCE
- # Batch_size, proposal_count, 4
- # 对先验框进行解码
- rpn_rois = ProposalLayer(
- proposal_count=proposal_count,
- nms_threshold=config.RPN_NMS_THRESHOLD,
- name="ROI",
- config=config)([rpn_class, rpn_bbox, anchors])
- # 获得classifier的结果
- mrcnn_class_logits, mrcnn_class, mrcnn_bbox =\
- fpn_classifier_graph(rpn_rois, mrcnn_feature_maps, input_image_meta,
- config.POOL_SIZE, config.NUM_CLASSES,
- train_bn=config.TRAIN_BN,
- fc_layers_size=config.FPN_CLASSIF_FC_LAYERS_SIZE)
-
- detections = DetectionLayer(config, name="mrcnn_detection")(
- [rpn_rois, mrcnn_class, mrcnn_bbox, input_image_meta])
-
-
- detection_boxes = Lambda(lambda x: x[..., :4])(detections)
- # 获得mask的结果
- mrcnn_mask = build_fpn_mask_graph(detection_boxes, mrcnn_feature_maps,
- input_image_meta,
- config.MASK_POOL_SIZE,
- config.NUM_CLASSES,
- train_bn=config.TRAIN_BN)
- # 作为输出
- model = Model([input_image, input_image_meta, input_anchors],
- [detections, mrcnn_class, mrcnn_bbox,
- mrcnn_mask, rpn_rois, rpn_class, rpn_bbox],
- name='mask_rcnn')
- return model
- def get_train_model(config):
- 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. ")
- # 输入进来的图片必须是2的6次方以上的倍数
- input_image = Input(shape=[None, None, config.IMAGE_SHAPE[2]], name="input_image")
- # meta包含了一些必要信息
- input_image_meta = Input(shape=[config.IMAGE_META_SIZE],name="input_image_meta")
- # RPN建议框网络的真实框信息
- input_rpn_match = Input(
- shape=[None, 1], name="input_rpn_match", dtype=tf.int32)
- input_rpn_bbox = Input(
- shape=[None, 4], name="input_rpn_bbox", dtype=tf.float32)
- # 种类信息
- input_gt_class_ids = Input(shape=[None], name="input_gt_class_ids", dtype=tf.int32)
- # 框的位置信息
- input_gt_boxes = Input(shape=[None, 4], name="input_gt_boxes", dtype=tf.float32)
- # 标准化到0-1之间
- gt_boxes = Lambda(lambda x: norm_boxes_graph(x, K.shape(input_image)[1:3]))(input_gt_boxes)
- # mask语义分析信息
- # [batch, height, width, MAX_GT_INSTANCES]
- if config.USE_MINI_MASK:
- input_gt_masks = Input(shape=[config.MINI_MASK_SHAPE[0],config.MINI_MASK_SHAPE[1], None],name="input_gt_masks", dtype=bool)
- else:
- input_gt_masks = Input(shape=[config.IMAGE_SHAPE[0], config.IMAGE_SHAPE[1], None],name="input_gt_masks", dtype=bool)
- # 获得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]
-
- anchors = get_anchors(config,config.IMAGE_SHAPE)
- # 拓展anchors的shape,第一个维度拓展为batch_size
- anchors = np.broadcast_to(anchors, (config.BATCH_SIZE,) + anchors.shape)
- # 将anchors转化成tensor的形式
- anchors = Lambda(lambda x: tf.Variable(anchors), name="anchors")(input_image)
- # 建立RPN模型
- rpn = build_rpn_model(len(config.RPN_ANCHOR_RATIOS), config.TOP_DOWN_PYRAMID_SIZE)
- rpn_class_logits, rpn_class, rpn_bbox = [],[],[]
- # 获得RPN网络的预测结果,进行格式调整,把五个特征层的结果进行堆叠
- for p in rpn_feature_maps:
- logits,classes,bbox = rpn([p])
- rpn_class_logits.append(logits)
- rpn_class.append(classes)
- rpn_bbox.append(bbox)
- rpn_class_logits = Concatenate(axis=1,name="rpn_class_logits")(rpn_class_logits)
- rpn_class = Concatenate(axis=1,name="rpn_class")(rpn_class)
- rpn_bbox = Concatenate(axis=1,name="rpn_bbox")(rpn_bbox)
- # 此时获得的rpn_class_logits、rpn_class、rpn_bbox的维度是
- # rpn_class_logits : Batch_size, num_anchors, 2
- # rpn_class : Batch_size, num_anchors, 2
- # rpn_bbox : Batch_size, num_anchors, 4
- proposal_count = config.POST_NMS_ROIS_TRAINING
- # Batch_size, proposal_count, 4
- rpn_rois = ProposalLayer(
- proposal_count=proposal_count,
- nms_threshold=config.RPN_NMS_THRESHOLD,
- name="ROI",
- config=config)([rpn_class, rpn_bbox, anchors])
- active_class_ids = Lambda(
- lambda x: parse_image_meta_graph(x)["active_class_ids"]
- )(input_image_meta)
- if not config.USE_RPN_ROIS:
- # 使用外部输入的建议框
- input_rois = Input(shape=[config.POST_NMS_ROIS_TRAINING, 4],
- name="input_roi", dtype=np.int32)
- # Normalize coordinates
- target_rois = Lambda(lambda x: norm_boxes_graph(
- x, K.shape(input_image)[1:3]))(input_rois)
- else:
- # 利用预测到的建议框进行下一步的操作
- target_rois = rpn_rois
- """找到建议框的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]每个建议框语义分割情况
- """
- rois, target_class_ids, target_bbox, target_mask =\
- DetectionTargetLayer(config, name="proposal_targets")([
- target_rois, input_gt_class_ids, gt_boxes, input_gt_masks])
- # 找到合适的建议框的classifier预测结果
- mrcnn_class_logits, mrcnn_class, mrcnn_bbox =\
- fpn_classifier_graph(rois, mrcnn_feature_maps, input_image_meta,
- config.POOL_SIZE, config.NUM_CLASSES,
- train_bn=config.TRAIN_BN,
- fc_layers_size=config.FPN_CLASSIF_FC_LAYERS_SIZE)
- # 找到合适的建议框的mask预测结果
- mrcnn_mask = build_fpn_mask_graph(rois, mrcnn_feature_maps,
- input_image_meta,
- config.MASK_POOL_SIZE,
- config.NUM_CLASSES,
- train_bn=config.TRAIN_BN)
- output_rois = Lambda(lambda x: x * 1, name="output_rois")(rois)
- # Losses
- rpn_class_loss = Lambda(lambda x: rpn_class_loss_graph(*x), name="rpn_class_loss")(
- [input_rpn_match, rpn_class_logits])
- rpn_bbox_loss = Lambda(lambda x: rpn_bbox_loss_graph(config, *x), name="rpn_bbox_loss")(
- [input_rpn_bbox, input_rpn_match, rpn_bbox])
- class_loss = Lambda(lambda x: mrcnn_class_loss_graph(*x), name="mrcnn_class_loss")(
- [target_class_ids, mrcnn_class_logits, active_class_ids])
- bbox_loss = Lambda(lambda x: mrcnn_bbox_loss_graph(*x), name="mrcnn_bbox_loss")(
- [target_bbox, target_class_ids, mrcnn_bbox])
- mask_loss = 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 = Model(inputs, outputs, name='mask_rcnn')
- return model
复制代码 来源:https://blog.csdn.net/qq_38853759/article/details/129909778
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |