Faster-RCNN tensorflow 程序细节
tf-faster-rcnn github:https://github.com/endernewton/tf-faster-rcnn
backbone,例如vgg,conv层不改变feature大小,pooling层输出(w/2, h/2),有4个pooling所以featuremap变为原图1/16大小。
检测RPN模块:
例如任意图片reshape到800*600,输入网络过vgg,conv5_3->rpn_conv/3*3->rpn_relu 得到feature map (1,512,50,38),接下来两个1*1的卷积分别用于每个点9个anchor前背景分类(1,18,50,38),anchor偏移量的预测(1,36,50,38),50 x 38 x 9 = 17100 从原图中扣出来的anchor数。(rpn_bbox_pred(偏移)+rpn_cls_prob_reshape(前背景))->proposal_layer 修正后的proposal,滤掉超出原图的proposal后NMS以及概率排序等操作后获得最终的boundingbox。输出大小为:(N,4),这里的N与NMS以及概率排序阈值有关,得到的就是boundingbox的四个坐标。
- 生成anchors,利用tx, ty, tw, th对所有的anchors做bbox regression回归(这里的anchors生成和训练时完全一致)
- 按照输入的foreground softmax scores由大到小排序anchors,提取前pre_nms_topN(e.g. 6000)个anchors,即提取修正位置后的foreground anchors。
- 限定超出图像边界的foreground anchors为图像边界(防止后续roi pooling时proposal超出图像边界)
- 剔除非常小(width<threshold or height<threshold)的foreground anchors
- 进行non maximum suppression
- 再次按照nms后的foreground softmax scores由大到小排序fg anchors,提取前post_nms_topN(e.g. 300)结果作为proposal输出。
1. 生成anchors,首先生成一个base anchor,然后以base anchor为基础,在原图像中移动,生成原图像中的anchors。
2. 在conv5-3这一层中,第一个feature的点,对应的原图像中[0,0]的点,第二个feature的点,对应原图像中的[16,0]的点,通过乘以步长,可以建立feature map上的特征和原图像中anchors的映射关系。
3. RPN层的第一步是用[3, 3, 512]的卷积核在conv5-3上进行卷积操作,conv5-3上的每一个像素点,对应的是原图中的一个近似于16*16的区域,所以这也就是为什么文章中说的把每一个中心点的像素转换为256-D的vector,但TensorFlow实现这里用了更厚的feature map,个数变为了512,于是比如[0, 0, :]就是一个512-D的vector。
4. 在这个512-D的vector基础上又有两个全卷积网络,一个是[1, 1]的卷积核,但是输出是9*2,因为9个anchor,每个anchor都有两个值,有目标还是没有目标,所以是18,所以这个输出的大小的height和width与conv5-4的大小一致。用于定义objectness。
5. 同样的,在这个512-D向量的基础上,定义了一个[1, 1]的卷积核,输出为9*4,因为9个anchor,每个anchor都有4个值,这四个值为预测的tx,ty,tw,th。所以这个输出feature的height和width与conv5-3的大小一致。这是用于定义pred-box的。
6.
rois, roi_scores = self._proposal_layer(rpn_cls_prob, rpn_bbox_pred, "rois") rpn_cls_prob: RPN层输出的objectness的值 rpn_bbox_pred: RPN层输出的box的取值,即:tx,ty,tw,th
该方法首先根据rpn_bbox_pred来生成原始图像中的anchor的预测坐标,由于rpn_bbox_pred是tx,ty,tw, th,这四个值的计算方法可看论文。对rpn_cls_prob进行排序,根据objectness分数的高低排序,然后选出需要保留的proposal的个数,论文中设置的为6000。然后从这些proposal中使用nms算法,筛选出最后的proposal。返回这些proposal和scores。注:scores和proposal都是排序后的。
7.
rpn_labels = self._anchor_target_layer(rpn_cls_score, "anchor") rpn_cls_score: RPN层输出的box的取值,即:tx, ty, tw, th
该方法首先把越过边界的anchor都过滤掉,保留都在图像范围内的anchor。然后创建一个全部是-1的labels。接着计算每个anchor和ground-truth的overlap,overlap返回一个二维数组,行数代表的是anchor个数,列数代表的ground-truth的个数。从中选择max-overlap,如果max-overlap大于某个阈值,那么这个anchor的label就设置为包含目标,用1表示,如果max-overlap小于某个阈值,那么这个anchor的label就设置为0。然后再找到每个ground-truth和anchor覆盖最大的anchor的index,把这些anchor设置为1。避免某个ground-truth没有对应的anchor。对每个anchor都设置是否含有目标后,利用anchors和每个anchors对应的max-overlap的ground-truth来计算该anchor对应的tx*, ty*, tw*, th*。然后设置bbox_inside_weights,这个权值起到的作用是论文中公式(1)中的pi*。bbox_outside_weights该权值用来设置在所有样本中,positive和negitive的权值。由于上述所有操作都是在没有越界的anchor中进行的,所以需要还原回到所有的anchors中。于是使用方法_unmap。
该方法最后返回:
rpn_labels:这是真实的每个anchor是含有目标还是没有目标. 用于loss计算。
rpn_bbox_targets:这个是真实的每个anchor与其覆盖最大的ground-truth来计算得到的tx, ty, tw, th。
8.
rois, _ = self._proposal_target_layer(rois, roi_scores, "rpn_rois") rois: 为第6步方法中生成的rois roi_scores: 为第6步中生成的roi的scores
该方法首先计算fg_rois_per_image也就是在一个batch中认为是前景的roi的个数,剩余的被认为是背景。然后计算每个rois和ground-truth的overlap,该overlap返回的数组形式为[roi_size, gt_size]。从这些roi中选择随机选择一些正样本和负样本,max-overlap大于某个阈值的roi被认为是正样本,找到正样本后,建立label,label是每个正样本的类别标签,由于20类,所以就是某个数字。按照比率设定背景样本,背景样本的标签为0。该方法中调用了一个_sample_rois的方法,该方法返回值为:labels,每个roi的类别标签,rois,是对原来所有rois进行正负样本过滤,选择出来的正样本和负样本。roi_scores, 对应选择出来的正负样本的objectness scores。bbox_targets,该返回值为数组:target_nums = [num_rois, num_class*4]的数组,取其中的一行作为例子,比如target_nums[0],该vector的长度为80, 首先设置全部为0, 如果target_nums[0]的类别为3,那么设置target_nums[0, 3*4:(3*4+4)]的取值为tx,ty,tw,th。这个方法返回的rois会接着送到后面的fast-rcnn网络。该方法中计算出来的labels,boxs都作为真实值,rois from feature conv5_3
9. loss
# RPN, class loss
# 计算RPN的objectness的loss,首先获取rpn网络输出的logits,然后从anchor和ground-truth中计算得到的label中选择不为-1的anchors。然后对这些anchor来计算cross-entropy
rpn_cls_score = tf.reshape(self._predictions[‘rpn_cls_score_reshape’], [-1,2])
rpn_label = tf.reshape(self._anchor_targets[‘rpn_labels’], [-1])
rpn_select = tf.where(tf.not_equal(rpn_label,-1))
rpn_cls_score = tf.reshape(tf.gather(rpn_cls_score, rpn_select),[-1,2])
rpn_label = tf.reshape(tf.gather(rpn_label, rpn_select),[-1])
rpn_cross_entropy = tf.reduce_mean(tf.nn.sparse_softmax_cross_entropy_with_logits(logits=rpn_cls_score, labels=rpn_label))
# RPN, bbox loss
# 采用smooth_l1_loss来计算bbox的loss。rpn_bbox_inside_weights用于把是object的box过滤出来,因为并不是所有的anchors都是有object的。rpn_bbox_inside_weights用于设置标记为1的box和标记为0的box的权值比率。
rpn_bbox_pred = self._predictions[‘rpn_bbox_pred’]
rpn_bbox_targets = self._anchor_targets[‘rpn_bbox_targets’]
rpn_bbox_inside_weights = self._anchor_targets[‘rpn_bbox_inside_weights’]
rpn_bbox_outside_weights = self._anchor_targets[‘rpn_bbox_outside_weights’]
1 rpn_loss_box = self._smooth_l1_loss(rpn_bbox_pred, rpn_bbox_targets, rpn_bbox_inside_weights, rpn_bbox_outside_weights, sigma=sigma_rpn, dim=[1,2,3]) 2 3 # RCNN, class loss 4 cls_score = self._predictions["cls_score"] 5 label = tf.reshape(self._proposal_targets["labels"],[-1]) 6 cross_entropy = tf.reduce_mean(tf.nn.sparse_softmax_cross_entropy_with_logits(logits=cls_score, labels=label)) 7 8 # RCNN, bbox loss 9 bbox_pred = self._predictions['bbox_pred'] 10 bbox_targets = self._proposal_targets['bbox_targets'] 11 bbox_inside_weights = self._proposal_targets['bbox_inside_weights'] 12 bbox_outside_weights = self._proposal_targets['bbox_outside_weights'] 13 14 loss_box = self._smooth_l1_loss(bbox_pred, bbox_targets, bbox_inside_weights, bbox_outside_weights) 15 16 self._losses['cross_entropy'] = cross_entropy 17 self._losses['loss_box'] = loss_box 18 self._losses['rpn_cross_entropy'] = rpn_cross_entropy 19 self._losses['rpn_loss_box'] = rpn_loss_box 20 21 loss = cross_entropy + loss_box + rpn_cross_entropy + rpn_loss_box 22 self._losses['total_loss'] = loss 23 24 self._event_summaries.update(self._losses) 25 26 return loss
10 RoI pooling
对每一个RoI,将RoI的坐标从原图映射到feature map就是简单除以原图到feature的放缩尺度16,从而得到feature map上的box坐标,由于box大小不一,所以要逆向考虑,在Pooling的过程中需要计算Pooling后的结果对应到feature map上所占的范围,在这个范围内做max pooling。
计算RoI在feature map上的宽高与pooled宽高的比值求得bin的大小[即pooling后featuremap上一个点与RoI上一个patch的映射关系,更具体的就是把feature map分割为 pooled_w*pooled_h 这么多份],由于roi的大小不一致,所以每次都需要计算一次bin的大小。最后在pooled上面循环遍历channel,h,w这3个维度,将映射后的区域平动到RoI对应的位置[hstart, wstart, hend, wend],统计该bin区域的最大值。
11. 输入处理
一般resize到固定尺寸,如果要求任意尺寸的输入那么限制条件就是fc层,如何处理呢?方法一:采用spp-net,固定size的特征金字塔;方法二:直接把fc换为global average pooling