读Mask R-CNN源码备忘录(训练部分)

此文为读Mask RCNN源码过程中的随笔,很“流水账”,我想价值在于对照着源码把每个步骤的“输入”、“输出”张量的维度标注了一下,会有助于对整体代码的理解。可能有些错误或遗漏,希望发现者指正,以期共同进步。

源码: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_rpn_match (batch_size, num_anchors, 1) #默认(2, 261888, 1)

# num_anchors计算
import numpy as np
BACKBONE_STRIDES = [4,8,16,32,64] #基础cnn网络(resnet101)输出的五层特征图对应输入图像的缩放比例
IMAGE_SHAPE = (1024, 1024) #输入图像尺寸
RPN_ANCHOR_RATIOS = [0.5, 1, 2] #每个像素取三种width/height比例的anchor

# 每张特征图所有像素取三个尺寸的anchor
num_anchors = sum([x[0][0] * x[0][1] / np.square(x[1]) * len(RPN_ANCHOR_RATIOS) for x in zip([IMAGE_SHAPE] * len(BACKBONE_STRIDES), BACKBONE_STRIDES)])
print(num_anchors)

input_rpn_bbox (batch_size, config.RPN_TRAIN_ANCHORS_PER_IMAGE, 4) #默认(2, 256, 4)

input_gt_class_ids (batch_size, config.MAX_GT_INSTANCES) #默认(2, 100)

 

* 注意:这里用norm_boxes_graph函数将原始坐标做了归一化处理

input_gt_boxes (batch_size, config.MAX_GT_INSTANCES, 4) -> gt_boxes (batch_size, config.MAX_GT_INSTANCES, 4) #默认(2, 100, 4)

* 注意:这里需要判断config.USE_MINI_MASK是True或者False

input_gt_masks (batch_size, config.MINI_MASK_SHAPE[0], config.MINI_MASK_SHAPE[1], config.MAX_GT_INSTANCES) #默认(2, 56, 56, 100)

input_gt_masks (batch_size, config.IMAGE_SHAPE[0], config.IMAGE_SHAPE[1], config.MAX_GT_INSTANCES) #默认(2, 1024, 1024, 100)

第一步: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)

rpn_feature_maps: [P2, P3, P4, P5, P6]用于RPN网络

mrcnn_feature_maps: [P2, P3, P4, P5]用于classifier heads(FPN网络)

第二步:生成ANCHORS

对[P2, P3, P4, P5, P6]各层anchor数量可以这样计算:

[int(x[0][0] * x[0][1] / np.square(x[1]) * config.IMAGE_CHANNEL_COUNT / config.RPN_ANCHOR_STRIDE) for x in zip([config.IMAGE_SHAPE] * len(config.BACKBONE_STRIDES), config.BACKBONE_STRIDES)]

每一层anchor数量是,(原图像尺寸高/本层stride) * (原图像尺寸宽/本层stride) * 每个像素3个长宽比 / anchor_stride,以P2层为例:(1024/4)*(1024/4)*3/1 = 196608 #此处默认anchor_stride=1

anchors: 最终[P2, P3, P4, P5, P6]对应的anchors为[196608, 49152, 12288, 3072, 768],总共anchors数量为261888 #默认(2, 261888, 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, 12)

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操作后最终得到的输出:

rpn_class_logits #默认(2, 261888, 2)

rpn_class #默认(2, 261888, 2)

rpn_bbox #默认(2, 261888, 4)

第四步:创建ProposalLayer层,对上一步结果通过NMS获取rpn_rois

计算proposal_count #默认2000

ProposalLayer层:

输入:

rpn_class #默认(2, 261888, 2)

rpn_bbox #默认(2, 261888, 4)

anchors #默认(2, 261888, 4)

输出:

rpn_rois #默认(2, 2000, 4)

思路:

scores: rpn_class代表此实例是前景(fg)或者背景(bg)的概率预测,[batch, num_anchors, (bg prob, fg prob)],所以第三维的第二项可以看成这个实例检测到物体的分数 scores = rpn_class[:, :, 1] #默认(2, 261888)

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, 2000, 4)

target_rois: rpn_rois 等于proposals #默认(2, 2000, 4)

第五步:创建DetectionTargetLayer,生成检测目标

DetectionTargetLayer:

输入:

proposals: target_rois #默认(2, 2000, 4)

gt_class_ids: input_gt_class_ids #默认(2, 100) 

gt_boxes #默认(2, 100, 4)

gt_masks: input_gt_masks #默认(2, 1024, 1024, 100)

输出:

rois #默认(2, 200, 4)

target_class_ids #默认(2, 200)

target_deltas: target_bbox #默认(2, 200, 4)

target_mask #默认(2, 200, 28, 28)

*注意,detection_targets_graph是对单个特征图做操作的,所以不包含batch_size

detection_targets_graph

输入:

proposals: target_rois #默认(2000, 4)

gt_class_ids: input_gt_class_ids #默认(100, ) 

gt_boxes #默认(100, 4)

gt_masks: input_gt_masks #默认(1024, 1024, 100)

输出:

rois #默认(200, 4)

target_class_ids #默认(200, )

target_deltas: target_bbox #默认(200, 4)

target_mask #默认(200, 28, 28)

思路:

首先去除0padding后

proposals #默认(<=2000, 4)

gt_boxes #默认(<=100, 4)

gt_class_ids #默认(<=100)

gt_masks #默认(<=100, 1024, 1024) 或者 (<=100, 56, 56)

将gt_boxes分为crowd实例和非crowd实例

将proposals中与非crowd gt_boxes iou >= 0.5的实例作为正实例

将proposals中与非crowd gt_boxes iou < 0.5并且与crowd gt_boxes iou < 0.001的实例作为负实例

提取positive_count=int(config.TRAIN_ROIS_PER_IMAGE * config.ROI_POSITIVE_RATIO)个正实例positive_rois,和对应比例的负实例negative_rois。 #默认postitive_rois(<=int(200 * 0.33), 4), negative_rois(<=int(200 * 0.67), 4)

获取与positive_rois对应的roi_gt_boxes, roi_gt_class_ids, roi_masks, 然后用positive_rois与roi_gt_boxes计算proposal的偏移量deltas #默认roi_gt_boxes(<=66, 4), roi_gt_class_ids(<=66), roi_masks(<=66, 1024, 1024)或者(<=66, 56, 56),deltas(<=66, 4)

boxes=positive_rois #默认(<=66, 4)

masks 是roi_masks通过boxes裁剪和缩放得到的 #默认(<=66, 28, 28)

rois:postitive_rois与negative_rois合并在一起 #默认(<=200, 4)

最后将rois, roi_gt_class_ids, deltas, masks用0padding补齐第一维200

第六步:创建fpn_classifier_graph (FPN)

fpn_classifier_graph:

输入rois, feature_maps, image_meta, pool_size, num_classes, train_bn=True, fc_layers_size=1024

rois  #默认(2, 200, 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 #默认(2, 93)

pool_size #默认7

num_classes #默认81

输出mrcnn_class_logits, mrcnn_class, mrcnn_bbox

mrcnn_class_logits #默认(2, 200, 81)

mrcnn_class #默认(2, 200, 81)

mrcnn_bbox #默认(2, 200, 81, 4)

思路:

首先通过PyramidROIAlign 层对每个feature_map用roi提取区域特征:

***PyramidROIAlign 开始***

输入:

boxes: rois #默认(2, 200, 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, 200, 7, 7, 3)

思路:

首先根据每个roi对应的面积计算roi_level #默认(2, 200)

循环依次处理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 * 200, 7, 7, 3)

box_range #默认(2 * 200, 1)

box_to_level #默认(2 * 200, (batch_index, roi_index, box_range_value))

ix:对box_to_level根据batch_index * 10000 + roi_index倒序排序以保持原boxes倒序排序 #默认(2 * 200, 1)

pooled 根据ix重排序 #默认(2 * 200, 7, 7, 3)

对pooled reshape(2, 200, 7, 7, 3)作为输出,至此PyramidROIAlign完成,输出x #默认(2, 200, 7, 7, 3)

***PyramidROIAlign 结束***

x: 用TimeDistributed对batch中每个sample(特征图)的每个roi做(7, 7)卷积,filters=1024,padding=valid,然后做batchnorm操作 #默认(2, 200, 1, 1, 1024)

x: 用TimeDistributed对batch中每个sample(特征图)的每个roi做(1, 1)卷积,filters=1024,然后做batchnorm操作 #默认(2, 200, 1, 1, 1024)

shared: 对x做squeeze操作  #默认(2, 200, 1024)

mrcnn_class_logits: 对shared每个roi做dense(81)操作 #默认(2, 200, 81)

mrcnn_probs: 对mrcnn_class_logits中每个roi做softmax操作 #默认(2, 200, 81)

x: 对shared中每个roi做dense(4 * 81)操作 #默认(2, 200, 4 * 81)

mrcnn_bbox: 对x做reshape #默认(2, 200, 81, 4)

第七步:创建build_fpn_mask_graph

输入:

rois #默认(2, 200, 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, 200, 28, 28, 81)

思路:

x: 首先通过PyramidROIAlign层对每个feature_map用roi提取区域特征,具体过程参照第六步中相同操作 #默认(2, 200, 14, 14, 3)

x: 对x中每个roi做(3, 3)卷积,filters=256,padding=same,然后batchnorm,重复4次 #默认(2, 200, 14, 14, 256)

x: 对x中每个roi做(2, 2)反卷积,filters=256,strides=2 #默认(2, 200, 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, 200, 28, 28, 81)

第八步:计算loss

rpn_class_loss: 通过rpn_class_loss_graph获取

输入:

rpn_match: input_rpn_match #默认(2, 261888, 1) target

rpn_class_logits: rpn_class_logits #默认(2, 261888, 2)

输出:

loss #默认(1)

思路:过滤掉neutral anchors,得到只有正负样本的anchors,然后用交叉熵损失函数求loss,过程中的中间量维度为,rpn_match(2, 261888),anchor_class(2, 261888),indices(正负样本个数, 1),rpn_class_logits(正负样本个数, 2),anchor_class(正负样本个数, 1)

rpn_bbox_loss: 通过rpn_bbox_loss_graph获取

输入:

target_bbox: input_rpn_bbox (batch_size, config.RPN_TRAIN_ANCHORS_PER_IMAGE, (dy, dx, log(dh), log(dw))) #默认(2, 256, 4) target

rpn_match: input_rpn_match #默认(2, 261888, 1) 辅助target 

rpn_bbox #默认(2, 261888, 4)

输出:

loss #默认(1)

思路:分别从target_bbox和rpn_bbox中过滤出正样本,然后用smooth_l1_loss求loss,过程中间变量维度为,rpn_match(2, 261888),indices(正例样本个数, 1),rpn_bbox(正例样本个数, 4),batch_counts(2, 1),target_bbox(正例样本个数, 4)

*注意:这里batch_pack_graph中counts取正样例的方式,是因为target_bbox中正例样本都在最前边

class_loss: 通过mrcnn_class_loss_graph获取

输入:

target_class_ids #默认(2, 200) target

pred_class_logits: mrcnn_class_logits #默认(2, 200, 81)

active_class_ids #默认(2, 81) 辅助target

输出:

loss #默认(1)

思路:先对pred_class_logits利用argmax求pred_class_ids,然后用交叉熵损失求损失值,最后过滤掉dataset中不存在的类别,中间变量维度为,pred_class_ids(2, 200),pred_active(2, 200)

bbox_loss: 通过mrcnn_bbox_loss_graph获取

输入:

target_bbox #默认(2, 200, 4) target

target_class_ids #默认(2, 200) 辅助target

pred_bbox: mrcnn_bbox #默认(2, 200, 81, 4)

输出:

loss #默认(1)

思路:先把前两维合并(2, 200, ...) -> (2 * 200, ...),然后从target_bbox和pred_bbox中分别过滤出正例样本,用smooth_l1_loss求loss,中间变量维度为,target_class_ids(2 * 200, ),target_bbox(2 * 200, 4),pred_bbox(2 * 200, 81, 4),positive_roi_ix(正例样本个数, 1),positive_roi_class_ids(正例样本个数, 1),indices(正例样本个数, 2),target_bbox(正例样本个数, 4),pred_bbox(正例样本个数, 4)

mask_loss: 通过mrcnn_mask_loss_graph获取

输入:

target_masks: target_mask #默认(2, 200, 28, 28)

target_class_ids #默认(2, 200) 

pred_masks: mrcnn_mask #默认(2, 200, 28, 28, 81)

输出:

loss #默认(1)

思路:与mrcnn_bbox_loss_graph基本一样

第九步:创建model

inputs = [input_image, input_image_meta, input_rpn_match, input_rpn_bbox, input_gt_class_ids, input_gt_boxes, input_gt_masks]

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]

第十步:data_generator

anchors #默认(261888, 4)

image #默认(1024, 1024, 3)

image_meta #默认(1 + 3 + 3 + 4 + 1 + 81)

gt_class_ids #默认(instance_count, ) 注意:instance_count是根据图片中实际标注的样本个数一致的

gt_boxes #默认(instance_count, 4)

gt_masks #默认(1024, 1024, instance_count) 或者 (56, 56, instance_count)

build_rpn_targets:

输入:

image_shape: image.shape #默认(1024, 1024, 3)

anchors #默认(261888, 4)

gt_class_ids #默认(instance_count, )

gt_boxes #默认(instance_count, 4)

输出:

rpn_match #默认(216888, )

rpn_bbox: (config.RPN_TRAIN_ANCHORS_PER_IMAGE, 4) #默认(256, 4)

思路:

anchors与crowd gt_bbox的iou<0.001的作为no_crowd_bool,anchors与非crowd gt_bbox的iou<0.3和no_crowd_bool的交集作为负样本,anchors与非crowd gt_bbox的iou>0.7的为正样本,对于每个非crowd gt_bbox,与之iou最大的那个anchor为正样本。使正样本数量不大于config.RPN_TRAIN_ANCHORS_PER_IMAGE / 2,并且正负样本数量之和为config.RPN_TRAIN_ANCHORS_PER_IMAGE,最终得到rpn_match,其中包含config.RPN_TRAIN_ANCHORS_PER_IMAGE个正负样本,正样本不超过config.RPN_TRAIN_ANCHORS_PER_IMAGE/2。用anchors正样本与gt_boxs计算出偏移rpn_bbox。config.RPN_TRAIN_ANCHORS_PER_IMAGE默认值为256

循环batch_size次,组成一个batch后返回,batch_image_meta(2, 93),batch_rpn_match(2, 216888, 1),batch_rpn_bbox(2, 256, 4),batch_images(2, 1024, 1024, 3),batch_gt_class_ids(2, 100),batch_gt_boxes(2, 100, 4),batch_gt_masks(2, 1024, 1024, 100),inputs=[batch_images, batch_image_meta, batch_rpn_match, batch_rpn_bbox, batch_gt_class_ids, batch_gt_boxes, batch_gt_masks]

*注意:

要区分各个阶段的bbox指的是坐标还是偏移量,rpn_bbox指的偏移量,gt_bbox指的坐标,target_bbox是偏移量,input_rpn_bbox是偏移量,mrcnn_bbox是偏移量

所有的roi都是坐标

 

 

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值