机器学习18:RPN区域候选网络(转载和整理)
RPN区域候选网络是Faster R-CNN对Fast R-CNN在提取候选区域的时候所做的改进,在上一篇整理Faster R-CNN的时候读了很多关于RPN的文章和资料,只是粗略了解了RPN的功能、优势、创新点等,始终没能真正理解它的实现机制。
直到读到深度学习:RPN(区域候选网络)和RPN(区域生成网络),这两篇文章对RPN区域候选网络的结构和实现讲的是比较清楚,这篇博客整理自这两篇文章。
1.RPN区域候选网络概述:
RPN的本质是 “ 基于滑窗的无类别object检测器 ” ,下图显示的是Faster R-CNN的构造,RPN在其中的作用是从特征提取网络输出的特征图中提取候选区域,获取的候选区域将和特征图一同输入Roi Pooling层。
下面的流程图显示了RPN所在Faster R-CNN结构中的位置,左侧SS算法为R-CNN、Fast R-CNN中一直采用的区域推荐算法,但左侧流程如表示的是Fast R-CNN的检测流程可能缺少了特征图提取部分:
RPN网络的相关事项:
1)在训练阶段,会输出约2000个候选边框,但只会抽取其中256个来训练RPN的cls+reg(分类+回归)结构,在训练过程中cls+reg(分类+回归)可以得到强监督信息(来源于ground truth),即ground truth会告诉cls+reg结构哪些才是真的前景,从而引导cls+reg结构学习正确区分前后景的能力。
到了reference阶段,则直接输出排序分数最高的300个候选边框,此时由于没有了监督信息,RPN并不知道这些候选区域是否为前景,整个过程只是惯性地推送无标签的候选区域给后面的Fast R-CNN网络。
2)RPN的运用使得提取候选区域的额外开销就只有一个两层网络。
3)如果只在最后一层特征图上映射回原图像,且初始产生的anchor被限定了尺寸下限,那么低于最小anchor尺寸的小目标虽然被圈入,在后面的过程中依然容易被漏检,而FPN的出现,大大降低了小目标的漏检率。
2.RPN区域候选网络的结构:
RPN区域候选网络的整体结构如下图所示。
(1)RPN头部结构:
在 RPN头部 ,通过以下结构生成 anchor(一些有编号有坐标的bbox):
论文中的这幅插图对应的就是RPN头部,而不是RPN的整体结构:
(2)RPN中部结构:
在 RPN中部, 分类分支(cls) 和 边框回归分支(bbox reg) 分别对这些anchor进行计算,two stage型的检测算法在RPN 之后 还会进行 再一次 的 分类任务 和 边框回归任务,以进一步提升检测精度。:
(3)RPN末端结构:
在 RPN末端对 两个分支的结果进行汇总,先剔除越界的anchor,再根据cls结果通过NMS算法去重来实现对anchor的 初步筛除,根据bbox reg结果来实现初步偏移,此时输出称为Proposal 。
(4)RPN后操作:
RPN之后,proposal成为RoI(感兴趣区域) ,被输入RoIPooling或RoIAlign中进行尺寸归一化。这些都是RPN网络之后的操作了,严格来说并不属于RPN的范围 。
下图中绿框内
为 RPN
,红圈内
为RoI以及其对应的池化操作
:
3.RPN相关问题及解答:
RPN的整个过程为一个特征图经过sliding window处理,得到256维特征,然后通过两次全连接得到结果2k个分数和4k个坐标:
(1)RPN的输入特征图指的是哪个特征图?
RPN的输入特征图就是Faster R-CNN的公共特征图,由输入待测图经过特征提取部分得到,也称共享Feature Map,主要用以RPN和RoI Pooling共享;
(2)为什么是用sliding window?不是用CNN么?
我们可以把3x3的sliding window看作是对特征图做了一次3x3的卷积操作,最后得到了一个通道数目是256的特征图,尺寸与公共特征图相同,假设为256 x (H x W);
(3)256维特征向量如何获得的?
可以近似的把这个特征图看作有H x W个向量,每个向量是256维,那么图中的256维指的就是其中一个向量,然后要对每个特征向量做两次全连接操作,一个得到2个分数另一个得到4个坐标,由于我们要对每个向量做同样的全连接操作,等同于对整个特征图做两次1 x 1的卷积,得到一个2 x H x W和一个4 x H x W大小的特征图,也就是有H x W个结果,每个结果包含2个分数和4个坐标;
需要解释一下为何是2个分数,因为RPN是提候选框,此处不需要判断类别,所以只要求区分是不是物体就可以,那么就有两个分数,前景(物体)的分数和背景的分数; 我们还需要注意:4个坐标是指针对原图坐标的偏移
(4)2k和4k中的k指的是什么?不同形状的矩形和Anchors如何得到?
H x W个结果中的每一点与原图之前都存在一一映射的关系,由于原图和特征图大小不同,所以特征图上的一个点对应原图是一个框,然而这个框很小。
比如8 x 8的小框,这里8是指原图和特征图的比例,那我们不妨把框的左上角或者框的中心作为锚点(Anchor),然后想象出K个框,这也就是图中所说的K anchor boxes(由锚点产生的K个框);换句话说,H x W个点,每个点对应原图有K个框,那么就有H x W x k个框在原图上,RPN的结果其实就是判断这些框是不是物体以及他们的偏移。
K个框的尺寸和长宽比是预先设定好的,假设共有9种组合,也就是k等于9,那么结果针对这9种组合成H x W x 9个结果,也就是18个分数和36个坐标。
5.RPN流程总结:
首先通过一系列卷积得到公共特征图,假设尺寸为N x 16 x 16;
然后进入RPN阶段,首先经过一个3 x 3的卷积,得到一个256 x 16 x 16的特征图,也可以看作16 x 16个256维特征向量,然后经过两次1 x 1的卷积,分别得到一个18 x 16 x 16的特征图,和一个36 x 16 x 16的特征图,也就是16 x 16 x 9个结果,每个结果包含2个分数和4个坐标;
再结合预先定义的Anchors,经过后处理,就得到候选框。整个流程如下图所示:
4.源代码:
作者的源代码请见:作者的源码 。
#========= RPN ============
layer {
name: "rpn_conv/3x3"
type: "Convolution"
bottom: "conv5"
top: "rpn/output"
param { lr_mult: 1.0 }
param { lr_mult: 2.0 }
convolution_param {
num_output: 256
kernel_size: 3 pad: 1 stride: 1
weight_filler { type: "gaussian" std: 0.01 }
bias_filler { type: "constant" value: 0 }
}
}
layer {
name: "rpn_relu/3x3"
type: "ReLU"
bottom: "rpn/output"
top: "rpn/output"
}
layer {
name: "rpn_cls_score"
type: "Convolution"
bottom: "rpn/output"
top: "rpn_cls_score"
param { lr_mult: 1.0 }
param { lr_mult: 2.0 }
convolution_param {
num_output: 18 # 2(bg/fg) * 9(anchors)
kernel_size: 1 pad: 0 stride: 1
weight_filler { type: "gaussian" std: 0.01 }
bias_filler { type: "constant" value: 0 }
}
}
layer {
name: "rpn_bbox_pred"
type: "Convolution"
bottom: "rpn/output"
top: "rpn_bbox_pred"
param { lr_mult: 1.0 }
param { lr_mult: 2.0 }
convolution_param {
num_output: 36 # 4 * 9(anchors)
kernel_size: 1 pad: 0 stride: 1
weight_filler { type: "gaussian" std: 0.01 }
bias_filler { type: "constant" value: 0 }
}
}
layer {
bottom: "rpn_cls_score"
top: "rpn_cls_score_reshape"
name: "rpn_cls_score_reshape"
type: "Reshape"
reshape_param { shape { dim: 0 dim: 2 dim: -1 dim: 0 } }
}
layer {
name: 'rpn-data'
type: 'Python'
bottom: 'rpn_cls_score'
bottom: 'gt_boxes'
bottom: 'im_info'
bottom: 'data'
top: 'rpn_labels'
top: 'rpn_bbox_targets'
top: 'rpn_bbox_inside_weights'
top: 'rpn_bbox_outside_weights'
python_param {
module: 'rpn.anchor_target_layer'
layer: 'AnchorTargetLayer'
param_str: "'feat_stride': 16"
}
}
layer {
name: "rpn_loss_cls"
type: "SoftmaxWithLoss"
bottom: "rpn_cls_score_reshape"
bottom: "rpn_labels"
propagate_down: 1
propagate_down: 0
top: "rpn_cls_loss"
loss_weight: 1
loss_param {
ignore_label: -1
normalize: true
}
}
layer {
name: "rpn_loss_bbox"
type: "SmoothL1Loss"
bottom: "rpn_bbox_pred"
bottom: "rpn_bbox_targets"
bottom: 'rpn_bbox_inside_weights'
bottom: 'rpn_bbox_outside_weights'
top: "rpn_loss_bbox"
loss_weight: 1
smooth_l1_loss_param { sigma: 3.0 }
}
#========= RoI Proposal ============
layer {
name: "rpn_cls_prob"
type: "Softmax"
bottom: "rpn_cls_score_reshape"
top: "rpn_cls_prob"
}
layer {
name: 'rpn_cls_prob_reshape'
type: 'Reshape'
bottom: 'rpn_cls_prob'
top: 'rpn_cls_prob_reshape'
reshape_param { shape { dim: 0 dim: 18 dim: -1 dim: 0 } }
}
layer {
name: 'proposal'
type: 'Python'
bottom: 'rpn_cls_prob_reshape'
bottom: 'rpn_bbox_pred'
bottom: 'im_info'
top: 'rpn_rois'
# top: 'rpn_scores'
python_param {
module: 'rpn.proposal_layer'
layer: 'ProposalLayer'
param_str: "'feat_stride': 16"
}
}
layer {
name: 'roi-data'
type: 'Python'
bottom: 'rpn_rois'
bottom: 'gt_boxes'
top: 'rois'
top: 'labels'
top: 'bbox_targets'
top: 'bbox_inside_weights'
top: 'bbox_outside_weights'
python_param {
module: 'rpn.proposal_target_layer'
layer: 'ProposalTargetLayer'
param_str: "'num_classes': 21"
}
}
#========= RCNN ============
layer {
name: "roi_pool_conv5"
type: "ROIPooling"
bottom: "conv5"
bottom: "rois"
top: "roi_pool_conv5"
roi_pooling_param {
pooled_w: 6
pooled_h: 6
spatial_scale: 0.0625 # 1/16
}
}
layer {
name: "fc6"
type: "InnerProduct"
bottom: "roi_pool_conv5"
top: "fc6"
param { lr_mult: 1.0 }
param { lr_mult: 2.0 }
inner_product_param {
num_output: 4096
}
}
layer {
name: "relu6"
type: "ReLU"
bottom: "fc6"
top: "fc6"
}
layer {
name: "drop6"
type: "Dropout"
bottom: "fc6"
top: "fc6"
dropout_param {
dropout_ratio: 0.5
scale_train: false
}
}
layer {
name: "fc7"
type: "InnerProduct"
bottom: "fc6"
top: "fc7"
param { lr_mult: 1.0 }
param { lr_mult: 2.0 }
inner_product_param {
num_output: 4096
}
}
layer {
name: "relu7"
type: "ReLU"
bottom: "fc7"
top: "fc7"
}
layer {
name: "drop7"
type: "Dropout"
bottom: "fc7"
top: "fc7"
dropout_param {
dropout_ratio: 0.5
scale_train: false
}
}
layer {
name: "cls_score"
type: "InnerProduct"
bottom: "fc7"
top: "cls_score"
param { lr_mult: 1.0 }
param { lr_mult: 2.0 }
inner_product_param {
num_output: 21
weight_filler {
type: "gaussian"
std: 0.01
}
bias_filler {
type: "constant"
value: 0
}
}
}
layer {
name: "bbox_pred"
type: "InnerProduct"
bottom: "fc7"
top: "bbox_pred"
param { lr_mult: 1.0 }
param { lr_mult: 2.0 }
inner_product_param {
num_output: 84
weight_filler {
type: "gaussian"
std: 0.001
}
bias_filler {
type: "constant"
value: 0
}
}
}
layer {
name: "loss_cls"
type: "SoftmaxWithLoss"
bottom: "cls_score"
bottom: "labels"
propagate_down: 1
propagate_down: 0
top: "cls_loss"
loss_weight: 1
loss_param {
ignore_label: -1
normalize: true
}
}
layer {
name: "loss_bbox"
type: "SmoothL1Loss"
bottom: "bbox_pred"
bottom: "bbox_targets"
bottom: 'bbox_inside_weights'
bottom: 'bbox_outside_weights'
top: "bbox_loss"
loss_weight: 1
}