源码:https://github.com/matterport/Mask_RCNN
接着前一篇训练部分
预测部分
模型输入:
input_image (batch_size, height, width, channels) #默认(2, 1024, 1024, 3)
input_image_meta (batch_size, 1 + 3 + 3 + 4 + 1 + config.NUM_CLASSES) #默认(2, 93)
input_anchors (batch_size, num_anchors, 4) #默认(2, 216888, 4)
第一步:resnet_graph网络
C2, C3, C4, C5 为resnet_graph的四个stage输出,输出尺寸依次为:
C2: (batch_size, config.IMAGE_SHAPE[0] / config.BACKBONE_STRIDES[0], config.IMAGE_SHAPE[1] / config.BACKBONE_STRIDES[0], 256) #默认(2, 256, 256, 256)
C3: (batch_size, config.IMAGE_SHAPE[0] / config.BACKBONE_STRIDES[1], config.IMAGE_SHAPE[1] / config.BACKBONE_STRIDES[1], 512) #默认(2, 128, 128, 512)
C4: (batch_size, config.IMAGE_SHAPE[0] / config.BACKBONE_STRIDES[2], config.IMAGE_SHAPE[1] / config.BACKBONE_STRIDES[2], 1024) #默认(2, 64, 64, 1024)
C5: (batch_size, config.IMAGE_SHAPE[0] / config.BACKBONE_STRIDES[3], config.IMAGE_SHAPE[1] / config.BACKBONE_STRIDES[3], 2048) #默认(2, 32, 32, 2048)
P5: 对C5做(1, 1)卷积filters=256,效果就是在维度不变的情况下,将特征图数量由2048降为256 #默认(2, 32, 32, 256)
P4: 对P5做(2, 2)上采样,并且将C4做(1, 1)卷积filters=256,然后将两者相加,效果就是P5尺寸加倍与C4卷积后尺寸相等,将两者相加作为P4 #默认(2, 64, 64, 256)
P3: 对P4做(2, 2)上采样,并且将C3做(1, 1)卷积filters=256,然后将两者相加,效果就是P4尺寸加倍与C3卷积后尺寸相等,将两者相加作为P3 #默认(2, 128, 128, 256)
P2: 同P3,P4 #默认(2, 256, 256, 256)
对P2, P3, P4, P5做(3, 3)卷积filters=256,padding="SAME",所以尺寸不变,只是对特征抽象级别提升了一下。
这里多出来一个P6,P6是对P5做了(1, 1)池化stride=2,所以尺寸减半,效果是对原图像做了隔像素采样。#默认(2, 16, 16, 256)
[P2, P3, P4, P5, P6]用于RPN网络
[P2, P3, P4, P5]用于classifier heads
第二步:生成ANCHORS
anchors = input_anchors #默认(2, 216888, 4)
第三步:创建RPN模型,对每一层特征图做预测
输入:
rpn_feature_maps #默认[(2, 256, 256, 256), (2, 128, 128, 256), (2, 64, 64, 256), (2, 32, 32, 256), (2, 16, 16, 256)]
输出:
rpn_class_logits #默认(2, 261888, 2)
rpn_class #默认(2, 261888, 2)
rpn_bbox #默认(2, 261888, 4)
思路:通过对每个特征图做卷积操作,分别得到class和bbox偏移量的回归网络分支
shared:对特征图做(3, 3)卷积,filters=512, strides=anchor_stride,padding='same' #默认(以P2特征图为例)(2, 256, 256, 512)
x:对shared做(1, 1)卷积,filters=2 * anchors_per_location,padding='valid' #默认(以P2特征图为例)(2, 256, 256, 6)
rpn_class_logits:对上步x做reshape(batch_size, -1, 2) #默认(以P2特征图为例)(2, 196608, 2)
rpn_probs:对上步rpn_class_logits做softmax #默认(以P2特征图为例)(2, 196608, 2)
x: 对shared做(1, 1)卷积,filters=4 * anchor_per_location, padding='valid' #默认(以P2特征图为例)(2, 256, 256, 8)
rpn_bbox:对上步x做reshape(batch_size, -1, 4) #默认(以P2特征图为例)(2, 196608, 4)
至此rpn网络生成:输入每层特征图input_feature_map,输出每层特征图预测到的rpn_class_logits, rpn_probs, rpn_bbox
对每层输出做KL.Concatenate操作后最终得到的输出
第四步:创建ProposalLayer层,对上一步结果通过NMS获取rpn_rois
计算proposal_count #默认1000
ProposalLayer层:
输入:
rpn_class #默认(2, 261888, 2)
rpn_bbox #默认(2, 261888, 4)
anchors #默认(2, 261888, 4)
输出:
rpn_rois #默认(2, 1000, 4)
思路:
scores: rpn_class代表此实例是前景(fg)或者背景(bg)的概率预测,[batch, num_anchors, (bg prob, fg prob)],所以第三维的第二项可以看成这个实例检测到物体的分数 scores = rpn_class[:, :, 1] #默认(2, 261888, 1)
deltas: rpn_bbox代表原anchor与真实实例位置的偏移量,deltas = rpn_bbox * np.reshape(self.config.RPN_BBOX_STD_DEV, [1, 1, 4]) #默认(2, 261888, 4)
anchors即上边通过feature_map得到的所有anchor #默认(2, 261888, 4)
首先通过scores倒排序,取前pre_nms_limit个(去除分数低的实例),得到scores,deltas,pre_nms_anchors。#默认pre_nms_limit为6000
scores #默认(2, 6000, 1)
deltas #默认(2, 6000, 4)
pre_nms_anchors #默认(2, 6000, 4)
boxes: 然后用deltas调整pre_nms_anchors位置,并且剪切超出边界的anchor,得到boxes #默认(2, 6000, 4)
proposals:对boxes做NMS操作,并且根据scores取其中前proposal_count个,不足用0padding #默认(2, 1000, 4)
target_rois: rpn_rois: 等于proposals #默认(2, 1000, 4)
第五步:创建fpn_classifier_graph (FPN)
fpn_classifier_graph:
输入rois, feature_maps, image_meta, pool_size, num_classes, train_bn=True, fc_layers_size=1024
rois: rpn_rois #默认(2, 1000, 4)
feature_maps: mrcnn_feature_maps #默认[P2, P3, P4, P5] [(2, 256, 256, 256), (2, 128, 128, 256), (2, 64, 64, 256), (2, 32, 32, 256)]
image_meta: input_image_meta #默认(2, 93)
pool_size #默认7
num_classes #默认81
输出mrcnn_class_logits, mrcnn_class, mrcnn_bbox
mrcnn_class_logits #默认(2, 1000, 81)
mrcnn_class #默认(2, 1000, 81)
mrcnn_bbox #默认(2, 1000, 81, 4)
思路:
首先通过PyramidROIAlign 层对每个feature_map用roi提取区域特征:
***PyramidROIAlign 开始***
输入:
boxes: rois #默认(2, 1000, 4)
image_meta #默认(2, 93)
feature_maps #默认[P2, P3, P4, P5] [(2, 256, 256, 256), (2, 128, 128, 256), (2, 64, 64, 256), (2, 32, 32, 256)]
输出:
pooled regions,即roi坐标对应的feature_map上的一小块区域的集合 #默认(2, 1000, 7, 7, 3)
思路:
首先根据每个roi对应的面积计算roi_level #默认(2, 1000)
循环依次处理2~5层特征图
处理方法:以P2层为例,先在roi_level中过滤出level=2的索引ix, 然后将这些roi从boxes中取出,得到level_boxes,获取level_boxes中每个roi对应的batch索引box_indices,用tf.image.crop_and_resize获得level_boxes在feature_map上的区域特征并存入pooled,以此类推获取每层中提取的区域特征。
ix #默认(level=2的roi个数, 2)
level_boxes #默认(level=2的roi个数, 4)
box_indices #默认(level=2的roi个数)
理解上边的处理思路可以参考这几句代码:
>>> import tensorflow as tf
>>> import numpy as np
>>> roi_level = np.array([[1, 0, 3], [2, 0, 0]])
>>> boxes = np.array([[[1,2,3,4],[5,6,7,8],[9,10,11,12]], [[13,14,15,16],[17,18,19,20],[21,22,23,24]]])
>>> ix = tf.where(tf.equal(roi_level, 0))
>>> ix
<tf.Tensor: shape=(3, 2), dtype=int64, numpy=
array([[0, 1],
[1, 1],
[1, 2]])>
>>> level_boxes = tf.gather_nd(boxes, ix)
>>> level_boxes
<tf.Tensor: shape=(3, 4), dtype=int64, numpy=
array([[ 5, 6, 7, 8],
[17, 18, 19, 20],
[21, 22, 23, 24]])>
>>> box_indices = tf.cast(ix[:, 0], tf.int32)
>>> box_indices
<tf.Tensor: shape=(3,), dtype=int32, numpy=array([0, 1, 1], dtype=int32)>
pooled #默认(2 * 1000, 7, 7, 3)
box_range #默认(2 * 1000, 1)
box_to_level #默认(2 * 1000, (batch_index, roi_index, box_range_value))
ix:对box_to_level根据batch_index * 10000 + roi_index倒序排序以保持原boxes倒序排序 #默认(2 * 1000, 1)
pooled 根据ix重排序 #默认(2 * 1000, 7, 7, 3)
对pooled reshape(2, 1000, 7, 7, 3)作为输出,至此PyramidROIAlign完成,输出x #默认(2, 1000, 7, 7, 3)
***PyramidROIAlign 结束***
x: 用TimeDistributed对batch中每个sample(特征图)的每个roi做(7, 7)卷积,filters=1024,padding=valid,然后做batchnorm操作 #默认(2, 1000, 1, 1, 1024)
x: 用TimeDistributed对batch中每个sample(特征图)的每个roi做(1, 1)卷积,filters=1024,然后做batchnorm操作 #默认(2, 1000, 1, 1, 1024)
shared: 对x做squeeze操作 #默认(2, 1000, 1024)
mrcnn_class_logits: 对shared每个roi做dense(81)操作 #默认(2, 1000, 81)
mrcnn_probs: 对mrcnn_class_logits中每个roi做softmax操作 #默认(2, 1000, 81)
x: 对shared中每个roi做dense(4 * 81)操作 #默认(2, 1000, 4 * 81)
mrcnn_bbox: 对x做reshape #默认(2, 1000, 81, 4)
第六步:DetectionLayer
输入:
rpn_rois #默认(2, 1000, 4)
mrcnn_class #默认(2, 1000, 81)
mrcnn_bbox #默认(2, 1000, 81, 4)
input_image_meta #默认(2, 93)
输出:
detections: (batch, num_detections, (y1, x1, y2, x2, class_id, score)) #默认(2, 100, 6)
***refine_detections_graph 开始***
refine_detections_graph
*注意:此函数是针对batch内某个sample的操作,所以不包括batch维
输入:
rois: rpn_rois #默认(1000, 4)
probs: mrcnn_class #默认(1000, 81)
deltas: mrcnn_bbox #默认(1000, 81, 4)
window #默认(4)
输出:
detections #默认(100, 6)
思路:
首先通过上一步回归预测到的probs得到每一个roi对应的预测class_ids,以及对应的预测偏移量deltas_specific,将偏移量应用到roi上矫正,通过probs对每个roi的预测得分class_scores,过滤掉score<config.DETECTION_MIN_CONFIDENCE(0.7)的roi,分别对每一类(class_id)检测目标应用nms,并且合并结果得到nms_keep,从nms_keep中保留class_scores分数较高项
中间变量维度为,class_ids(1000, ),indices(1000, 2),class_scores(1000, ),deltas_specific(1000, 4),refined_rois(1000, 4),keep(可信度高的正例样本个数, ),pre_nms_class_ids(可信度高的正例样本个数, ),pre_nms_scores(可信度高的正例样本个数, ),pre_nms_rois(可信度高的正例样本个数, 4),unique_pre_nms_class_ids(可信度高的正例样本个数, ),nms_keep(100, ),detections(100, 6)
***refine_detections_graph 结束***
第七步:build_fpn_mask_graph
输入:
rois: detection_boxes #默认(2, 100, 4)
feature_maps: mrcnn_feature_maps #默认[P2, P3, P4, P5] [(2, 256, 256, 256), (2, 128, 128, 256), (2, 64, 64, 256), (2, 32, 32, 256)]
image_meta: input_image_meta #默认(2, 93)
pool_size #默认14
num_classes #默认81
train_bn #默认False
输出:
mrcnn_mask #默认(2, 100, 28, 28, 81)
思路:
x: 首先通过PyramidROIAlign层对每个feature_map用roi提取区域特征,具体过程参照第六步中相同操作 #默认(2, 100, 14, 14, 3)
x: 对x中每个roi做(3, 3)卷积,filters=256,padding=same,然后batchnorm,重复4次 #默认(2, 100, 14, 14, 256)
x: 对x中每个roi做(2, 2)反卷积,filters=256,strides=2 #默认(2, 100, 28, 28, 256)
#计算反卷积的尺寸公式
new_rows = (rows - 1) * strides[0] + kernel_size[0] - 2 * padding[0] + output_padding[0]
new_cols = (cols - 1) * strides[1] + kernel_size[1] - 2 * padding[1] + output_padding[1]
#默认
new_rows = (14 - 1) * 2 + 2 - 2 * 0 + 0 = 28
new_cols = (14 - 1) * 2 + 2 - 2 * 0 + 0 = 28
x: mrcnn_mask 对x每个roi做(1, 1)卷积,filters=81,strides=1,以此作为mrcnn_mask输出 #默认(2, 100, 28, 28, 81)
第八步:创建模型
inputs=[input_image, input_image_meta, input_anchors]
outputs=[detections, mrcnn_class, mrcnn_bbox, mrcnn_mask, rpn_rois, rpn_class, rpn_bbox]
第九步:detect
输入:
images #默认(2, origin_h, origin_w, 3)
输出:
results =
[{
"rois": final_rois, #默认(实际正样本个数, 4)
"class_ids": final_class_ids, #默认(实际正样本个数, )
"scores": final_scores, #默认(实际正样本个数, )
"masks": final_masks, #默认(28, 28, 实际正样本个数)
}]
思路:
***mold_inputs开始***
输入:
images #默认(2, origin_h, origin_w, 3)
输出:
molded_images #默认(2, 1024, 1024, 3)
image_metas #默认(2, 93)
windows #默认(2, 4)
***mold_inputs结束***
***获取anchors开始***
anchors=get_anchors(image_shape) #默认(2, 261888, 4)
***获取anchors结束***
***predict开始***
输入:
molded_images #默认(2, 1024, 1024, 3)
image_metas #默认(2, 93)
anchors #默认(2, 261888, 4)
输出:
detections #默认(2, 100, 6)
mrcnn_mask #默认(2, 100, 28, 28, 81)
***predict结束***
***unmold_detections开始***
*注意:此操作为batch内单sample操作,所以不带batch维
输入:
detections: (N, (y1, x1, y2, x2, class_id, score)) #默认(100, 6)
mrcnn_mask: #默认(100, 28, 28, 81)
original_image_shape #默认(3, ) 值(origin_h, origin_w, origin_c)
image_shape #默认(3, ) 值(1024, 1024, 3)
window: [y1, x1, y2, x2] #默认(1)
输出:
boxes #默认(实际正样本个数, 4)
class_ids #默认(实际正样本个数, )
scores #默认(实际正样本个数, )
masks #默认(28, 28, 实际正样本个数)
***unmold_detections结束***