本文参考自 tensorflow model 中的 object detection ,同时实现了 FastRCNN, FasterRCNN, MaskRCNN
约定:
关于坐标
- 绝对坐标 :表示没有除以图片的 height 和 width
- 归一化坐标 :表示已经除以图片的 height 和 width
- 中心化坐标 : 格式为 [y, x, h, w]
- 对角坐标:[ymin, xmin, ymax, xmax]
以上中心化绝对坐标,中心化归一坐标,对角绝对坐标,对角归一化坐标四种组合。
关于图片大小变化
- 原图
- resize 之后的图片 true_image_shape,此时图片最短边为 600,最长边为 1200
- 特征提取器预处理之后的大小 image_shape,此时图片边长在 [1200],这是由于在 resize 的时候,对于不够最长边长的补 0。
- 特征提取网络预处理之后的图片 h, w
- 经过特征提取网络之后的 feature_map
预测值
RPN 网络每张图片的预测
- rpn_box_encodings : [batch_size, num_valid_anchors, 4] 其中 4 依次为 [y, x, h, w]
- rpn_objectness_predictions_with_bg : [batch_size, num_valid_anchors, 2],其中 2,分别为 2 元素的 one-hot,[1,0] 表示背景,[0,1] 表示包含对象
注:其中 h,w 为特征图的 height 和 width,num_valid_anchors <= h * w * 9
检测网络每张图片的预测
- refined_box_encodings
- class_predictions_with_background
- proposal_boxes
- mask_predictions
标签
RPN 网络每张图片的标签
- reg_target : 每个元素的维度为 [num_boxes, 4] 其中 4 为 [y, x, h, w],而且都小于 [0,1]
- reg_cls : 每个元素的维度为 [num_boxes, num_classes],其中 num_class 为分类的数量,为 one_hot 向量(只有 box 对应的分类才为 1,其余都为 0)。
- reg_weights : 包含 box 的权重
- reg_cls : 包含物体的权重
检测网络每张图片的标签
- reg_targets
- reg_weight
- cls_targets_with_background
- cls_weight
- mask_targets
- mask_targets_weight
训练
验证
注:去掉上述图片中框起来的部分,就是 Faster RCNN
训练
box_coding : [N, 4] 其中 4 依次为 [x_center, y_center, w, h]
anchor : [ymin, xmin, ymax, xmax]
注:实际在用之前都需要做处理的,参考 _format_groundtruth_data
CNN 特征提取网络:FasterRCNN 特有的术语
图片预处理
对应配置 image_resizer 部分
采用 BILINEAR 方法进行等比例的 resize, 最后图片最长 1200,最短 600。具体算法参考附录预处理部分。
特征提取器预处理
不同的 CNN 特征提取网络需要不同的预处理. 如 inception_v2 如 resnet
经过预处理之后,图片变为固定大小,如 [batch_size, 224, 224, 3]
前半部分
将 preprocessed_inputs 经过 CNN 提取网络得到 h * w * c 的特征图 rpn_features_to_crop
具体说明见附录
Anchor 生成
根据 rpn_features_to_crop 的 h, w 生成 anchors(h * w * 9 个),anchors 的维度为 [batch_size, h * w * 9]。 生成算法参考附录。
注: 生成 anchors 的个数为 h * w * 9,其中 9 为 scale 和 aspect_ratios 不同组合,在
paper 中为 scales=(0.5, 1.0, 2.0), aspect_ratios=(0.5, 1.0, 2.0) 两两共 9 种组合,anchor 为中心化绝对坐标。
box 坐标预测
- rpn_box_predictor_features 经过卷积核为 1 * 1, depth 为 9 * 4 的卷积,得到 rpn_box_encodings
- 将的输出 rpn_box_coding 维度修改为 (batch_size, h * w * 9, 4),其中 4 为 box 的坐标 [ymin, xmin, ymax, xmax] 的相对坐标。
物体存在预测
- rpn_box_predictor_features 经过卷积核 1 * 1 ,depth 为 9 * 2 得到 rpn_objectness_predictions_with_background
- 将输出 rpn_objectness_predictions_with_background 维度修改为 (batch_size, h * w * 9, 2) 其中 2 代表分类,第一个列为背景,第二维为非背景
注:rpn_objectness_predictions_with_background, rpn_box_predictor_features 的第二维为 h * w * 9 与 anchors 的数量一致。即每一个 anchor 对应一个 objectness 和 box
如果当前是训练阶段
anchor 过滤
- 将 anchors 中四个角都在 image_shape 内的 anchors 保留,其余都删除
索引过滤
- 由于 anchor 与 box_encoding 和 rpn_objectness_predictions_with_background 在第二维度上相同,如果 anchors 中对应元素被删除,那么在 rpn_box_coding 和 rpn_objectness_predictions_with_background 中对应的元素也删除
如果不是训练阶段
anchor 修正
- 将 anchors 中每个 anchor 修改为与 input_image 的交集,去掉与预处理之后图片没有交集的 anchor
至此,第一阶段 RPN 网络预测部分完成。
rpn_box_coding : [batch_size, num_valid_anchors, 4] 其中 4 依次代表 [y, x, h, w]
rpn_objectness_predictions_with_background : [batch_size, num_valid_anchors, 2]
anchors_boxlist [batch_size, num_valid_anchors]
其中 num_valid_anchors 为删除不满足条件的 anchor 之后有效 anchor 数量。
如果只运行第一阶段,求 Loss
标签预处理
- 将 groundtruth_boxeslist 每个 box 转为绝对坐标,即横轴乘以 true_image_shape[1], 纵轴乘以 true_image_shape[0]
- 将 groundtruth_classes_list 每个 box 第一列前增加一列,为背景列。 维度为 [num_boxes, 1 + 1 ]
- 用 NEAREST_NEIGHBOR 算法 将 groundtruth_masks_list 每个元素 resize 为 true_image_shape 的尺寸,并对齐
至此,标签和预测都准备好了,但是它们的维度不匹配,无法直接计算,因此,需要将标签映射到预测值的坐标系。也就是找到标签的每一个 box 与位于特征图的哪一个 grid cell,在该 grid cell 与哪个 anchor 匹配。
第一次 IoU 过滤
- 计算 groundtruth_boxeslist 与 anchors 的 IoU 矩阵 match_quality_matrix(可以理解为[num_boxes, 1] 与 [1, num_anchor] 的矩阵乘,得到 [num_boxes, num_anchor] 的矩阵。其中 row 索引为 groundtruth_boxeslist 元素索引, colum 索引为 anchors 元素索引)
- 记录 match_quality_matrix 每列最大值对应的行索引得到 match1。此时 match1 本身的索引为列索引,存储的值为行索引。因此,通过 match1 就能定位到 match_quality_matrix 对应的 IoU 值。
- 如果 match1[i] 中元素对应 IoU 大于 0.7,match1[i] 不变,如果 match1[i] 中元素对应 IoU 小于 0.7,大于 0.3,match1[i] 为 -2. 如果 match1[i] 对应 IoU 小于0.3,match1[i] 为 -1。
- 记录 match_quality_matrix 每行最大值对应的列索引得到 match2。此时 match2 本身的索引为行索引,存储的值为列索引。因此,通过 match1 就能定位到 match_quality_matrix 对应的 IoU 值。
- matches 为 num_anchor 个元素的数组,从 0 到 num_anchor 的任意值 i,如果 i 存在于 match2,matches[i] 为 i 在 match2 中的索引(假设为 k,ground_truth[k] 与 anchors[i] 的 IoU 大于其他 anchor 与 ground_truth[k] 的 IoU)。否则 matches[i] = match1[i] (备注:这是整个实现的一个非常绕的点,要仔细推敲)。至此 matches 中大于 -1 的元素为满足条件,小于 0 为不满足条件。实际上 matches 小于 0,只可能取 -1, -2,
备注:其中,大于 -1,表示对于任意 machor[i] > -1,存在 groundth truth box 与 anchors[i] 的 IoU 大于 0.7 或 groundtruth box 与 anchors[i] 的 IoU 大于其他 anchor。
生成标签
- reg_targets : 对满足条件的 anchors 和 groundtruth_boxlists 进行编码,不满足的设置为 [0, 0, 0, 0]。维度为 [num_valid_anchors, 4 ], 4 依次代表 [y, x, h, w]
输入:Anchors 与 gt_box 编码
#anchors 表示为 [ycenter_a, xcenter_a, ha, wa]
#gt_box 表示为 [ycenter, xcenter, h, w]
tx = (xcenter - xcenter_a) / wa
ty = (ycenter - ycenter_a) / ha
tw = tf.log(w / wa)
th = tf.log(h / ha)
输出 [tx, ty, tw, th]
- cls_targets : 对满足条件的 groundtruth_boxlists 保留,不满足条件的设置为 [0]。 [num_valid_anchors, 1]
- reg_weights : 满足条件 1,不满足条件 0。 [num_anchors]
- cls_weights : 如果 matches[i] = -2,cls_weights[i] = 0,其余 cls_weight 置为 1。 [num_anchors]
注:num_anchors <= num_valid_anchors
采样
- 按照 BalancedPositiveNegativeSampler 方法,从 cls_targets 中随机采样 256 个样本,正负比例为 0.5,得到数据中采用数据的索引(这里显然是一个优化点,对于不同的数据集,显然不能无脑 1:1 采样,还与正负样本权重有关)。
- 将 cls_targets 变为 one-hot 矩阵。两列,第一列表示背景,第二列表示存在物体
- 计算分类和位置的 loss,回归用 Smooth L1,分类用 softmax,之后取均值。
注:
- 以上 reg_targets, cls_targets 是针对 一张图片而言,实际是批量操作。
- 实际总的表示数量为 num_valid_anchors,通过第一次 IoU 过滤和采样,使得计算 loss 的时候,又有一部分不参与计算 loss。这块部分多次看才理解了,因此需要注意。
比如
每列 IoU 最大依次为 [0.6, 0.8, 0.3, 0.7, 0.3, 0.8] 其中阈值为 0.5
match [3, 2, -1, 4, -1, 8]
cls_targets
[1, 1, 0, 1, 0, 1]
cls_weights
[1, 1, 0, 1, 0, 1]
reg_targets
[0.1, 0.2, 0.3, 0.4]
[0.1, 0.2, 0.3, 0.4]
[0, 0, 0, 0]
[0.1, 0.2, 0.3, 0.4]
[0, 0, 0, 0]
[0.1, 0.2, 0.3, 0.4]
reg_weights
[1, 1, 0, 1, 0, 1]
第二阶段
解码
- rpn_box_encodings 和 anchors 解码,得到 proposal_boxes
输入 Anchors 与 box_encoding 编码
#anchors 表示为 [ycenter_a, xcenter_a, ha, wa]
#box_encoding 表示为 [ty, tx, th, tw]
w = tf.exp(tw) * wa
h = tf.exp(th) * ha
ycenter = ty * ha + ycenter_a
xcenter = tx * wa + xcenter_a
ymin = ycenter - h / 2.
xmin = xcenter - w / 2.
ymax = ycenter + h / 2
xmax = xcenter + w / 2
输出 [ymin, xmin, ymax, xmax]
NMS
- 对 proposal_boxes 计算 NMS 得到 proposal_boxes,proposal_scores,num_proposals(其中 scores 为rpn_objectness_softmax_without_background,score_threshold 为 0.0, iou_threshold 为 0.7, max_proposals 为 300)
如果当前是训练
标签预处理
- 将 groundtruth_boxeslist 每个 box 转为绝对坐标,即横轴乘以 true_image_shape[1], 纵轴乘以 true_image_shape[0]
- 将 groundtruth_classes_list 每个 box 第一列前增加一列,为背景列。 维度为 [num_boxes, 1 + num_classes ]
- 用 NEAREST_NEIGHBOR 算法 将 groundtruth_masks_list 每个元素 resize 为 true_image_shape 的尺寸,并对齐
第二次过滤与采样
第一阶段生成的 proposal 是第一阶段 groundtruth box 和 anchor 的近似拟合,因此,在第二阶段,再次与 groundtruth box 进行 IoU 计算(经过第一阶段,proposal box 以及很
接近 groundtruth box 啦,所以,第二阶段 IoU 阈值 0.5 就够了)可以认为第一阶段生成的
proposal 可以认为是扮演了第二阶段 anchor 的角色。可以理解为 proposal box 是优化版的
anchor。在 Yolo 中 anchor 用 k-mean 方法获取,而在 faster RCNN 中, 用 RPN 网
络对一个粗糙版本的 anchor 进行调优。
- 计算 groundtruth_boxeslist 与 proposal_boxes 的 IoU 矩阵 match_quality_matrix(可以理解为[num_boxes, 1] 与 [1, num_anchor] 的矩阵乘,得到 [num_boxes, num_anchor] 的矩阵。其中 row 索引为 groundtruth_boxeslist 元素索引, colum 索引为 anchors 元素索引)
- 记录 match_quality_matrix 每列最大值对应的行索引得到 match。此时 match 本身的索引为列索引,存储的值为行索引。因此,通过 match 就能定位到 match_quality_matrix 对应的 IoU 值。
- 如果 match[i] 中元素对应 IoU 大于 0.5,match[i] 不变,如果 match[i] 中元素对应 IoU 小于 0.5 为 -1。
- 找到 match 中所有正样本和负样本中随机采样 64 (正:负 =1:4) 个,当然正样本也许不够 16 个,那么,有多少就采多少,剩余都为负样本。 proposal_boxes 中选择采样的元素,如果不够就用 0 补足。并设置 proposal_scores,num_proposals
这次 IoU 过滤主要的目的在于采样。
归一化
- 对 proposal_boxes 归一化得到 normalized_proposal_boxes(宽除以 image_shape[1], 高除以 image_shape[0])
RoI Pooling
- proposal_boxes_normalized 和 rpn_features_to_crop 经过 crop_and_resize 得到 cropped_regions
后半部分
- flattened_proposal_feature_maps 经过网络的后半部分得到 box_classifier_features
具体说明见附录
绝对化
将 proposal_boxes_normalized 变为绝对值 absolute_proposal_boxes
后半部分经过两个全连接层得到 refined_box_encodings,class_predictions_with_background,至此,第二阶段预测部分完成
- refined_box_encodings: [batch_size, num_boxes, 4]
- class_predictions_with_background 维度为 [batch_size, num_classes + 1]
- absolute_proposal_boxes
第三阶段
如果是训练
- box_classifier_features 经 resize_bilinear 和两个 conv2d 得到 mask_predictions([total_num_proposals, num_classes, mask_height, mask_width])
1. S = 2 ** (round(log(image_features.channel)*2 + 3*log(num_class) / (2 + 3)))
2. 需要注意的这里就是 MASK-RCNN 中提到的 RoI Align。在实现上就是一个参数,即设置tf.image.resize_bilinear 的参数 align_corners=True 即可。
3. Mask RCNN 与 Faster RCNN 在训练阶段的唯一区别就在于是否包含 Mask 预测部分。
如果是验证
解码
- refined_box_encodings 与 absolute_proposal_boxes 解码得到 refined_decoded_boxes_batch
- mask_predict 经过 sigmoid 得到 mask_predictions_batch
NMS
- 对 refined_decoded_boxes_batch 应用 NMS,其中 score 为 class_predictions_batch,score_threshold 为 0.0,iou_threshold 为 0.6, max_detections_per_class 为 100,max_total_detections 为 300 得到 nmsed_boxes,nmsed_scores,nmsed_classes,nmsed_masks, num_detections
RoIPooling
- nmsed_boxes 与 rpn_features_to_crop 经过 crop_and_resize,再经过 max_pool 得到 flattened_detected_feature_maps
后半部分
flattened_detected_feature_maps 经过基础网络后半部分得到 curr_box_classifier_features
curr_box_classifier_features 经过 reduce_mean, flatten, resize_bilinear, conv2d (3x3) 的 mask_predictions
第三次 IoU 过滤
- 计算 groundtruth_boxeslist 与 proposal_boxes 的 IoU 矩阵 match_quality_matrix(可以理解为[num_boxes, 1] 与 [1, num_anchor] 的矩阵乘,得到 [num_boxes, num_anchor] 的矩阵。其中 row 索引为 groundtruth_boxeslist 元素索引, colum 索引为 anchors 元素索引)
- 记录 match_quality_matrix 每列最大值对应的行索引得到 match。此时 match 本身的索引为列索引,存储的值为行索引。因此,通过 match 就能定位到 match_quality_matrix 对应的 IoU 值。
- 如果 match[i] 中元素对应 IoU 大于 0.5,match[i] 不变,如果 match[i] 中元素对应 IoU 小于 0.5 为 -1。
- 根据 absolute_proposal_boxes, groundtruth_boxlist,match 创建 reg_target, reg_weight, cls_target, cls_weight
这次 IoU 的过滤就是为了生成标签。
分类过滤1
refined_box_encodings 增加背景分类得到 refined_box_encodings_with_background,并用 cls_target 进行过滤得到 refined_box_encodings_masked_by_class_targets
分类过滤2
prediction_masks 增加背景分类得到 prediction_masks_with_background,并用 cls_target 进行过滤得到 prediction_masks_masked_by_class_targets
第四次 IoU 过滤
- 计算 groundtruth_boxeslist 与 proposal_boxes 的 IoU 矩阵 match_quality_matrix(可以理解为[num_boxes, 1] 与 [1, num_anchor] 的矩阵乘,得到 [num_boxes, num_anchor] 的矩阵。其中 row 索引为 groundtruth_boxeslist 元素索引, colum 索引为 anchors 元素索引)
- 记录 match_quality_matrix 每列最大值对应的行索引得到 match。此时 match 本身的索引为列索引,存储的值为行索引。因此,通过 match 就能定位到 match_quality_matrix 对应的 IoU 值。
- 如果 match[i] 中元素对应 IoU 大于 0.5,match[i] 不变,如果 match[i] 中元素对应 IoU 小于 0.5 为 -1。
- 根据 absolute_proposal_boxes, groundtruth_boxlist,match 创建 reg_target, reg_weight, cls_target, cls_weight
这次 IoU 的过滤就是为了生成标签。
附录
图片预处理
原图片:[orig_height, orig_width, num_channels]
large_scale_factor = min_dimension / float(min(orig_height, orig_width))
large_size = [int(round(orig_height * large_scale_factor)), int(round(orig_width * large_scale_factor)), num_channels]
small_scale_factor = max_dimension / float(max(orig_height, orig_width))
small_size = [int(round(orig_height * small_scale_factor)), int(round(orig_width * small_scale_factor)), num_channels]
如果 max(large_size) > max_dimension,新图片的 shape 为 [small_size]
否则,new_shape 为 [large_size]
例 1:输入图片为 [600, 800],min_dimension 为 600,max_dimension 为 1200。
此时
large_scale_factor = 1.0 large_size 为 [600,800]
small_scale_factor 为 1.5 small_size 为 [900, 1200]
因此,new_shape 为 [600, 800]
例2:
输入图片为 [300, 800],min_dimension 为 600,max_dimension 为 1200。
此时
large_scale_factor = 2.0 large_size 为 [600,1600]
small_scale_factor 为 1.5 small_size 为 [450, 1200]
因此,new_shape 为 [450, 1200]
总结:
- width, height 同比例放缩
- 最后图片尺寸,较小值不低于 min_dimension,较大值不高于 max_dimension
前半部分与后半部分
由于 Faster RCNN 是典型的两阶段网络,因此,将整个网络分为前半部分和后半部分
(支持 inception_v2, resnet_v1, nasnet, inception_resnet_v2)
对于 InceptionV2,前部分为 Mixed4e 之前, 后半部分为 Mixed4e 之后部分
对于 InceptionResnetV2,前部分为 PreAuxLogits 之前, 后半部分为 PreAuxLogits 之后部分
对于 MobilenetV1,前部分为 Conv2d_11_pointwise 之前, 后半部分为 Conv2d_11_pointwise 之后部分
对于 resnet_v1_50、resnet_v1_101、resnet_v1_152,前部分为 block3 之前, 后半部分为 block3 之后部分