(如下为建议阅读顺序)
1【Faster R-CNN论文精度系列】从Faster R-CNN源码中,我们“学习”到了什么?
2【Faster R-CNN论文精度系列】代码解读并深入理解Region Proposal Network
3【Faster R-CNN论文精度系列】代码解读并深入理解Anchor和Anchor Box
4【Faster R-CNN论文精度系列】原文精析
5 Faster R-CNN: Down the rabbit hole of modern object detection
从Github上搜索faster rcnn代码,可以看到很多版本,star数最多的版本都是论文的作者,对源码框架文件的分析很有必要的,可以弄清楚很多我们无法从论文中读懂的点。
在最初的学习之中,我总是强调对论文的解读,对原文中原理的理解,但是我在今天的学习中我发现,论文与代码同步进行是一个很好的选择,具体的代码可以让你很好的理解这篇文章做了些什么东西,可以加深你对论文的理解,这个学习方法很值得推荐。
找到在目录py-faster-rcnn/models/pascal_voc/VGG16/faster_rcnn_end2end下有一个train.prototxt文件,这是整个论文的框架理解,本文选取经典模型进行解读,ZF的框架可以在源码中找到必看!
阅读代码前必须要理解的参数:
- lr_mult: 学习率的系数,最终的学习率是这个数乘以solver.prototxt配置文件中的base_lr。如果有两个lr_mult, 则第一个表示权值的学习率,第二个表示偏置项的学习率。一般偏置项的学习率是权值学习率的两倍。
- num_output: 过滤器(filfter)的个数
- weight_filler: 权值初始化。 默认为“constant",值全为0,很多时候我们用"xavier"算法来进行初始化,也可以设置为”gaussian"
- bias_filler: 偏置项的初始化。一般设置为"constant",值全为0
- bias_term: 是否开启偏置项,默认为true, 开启
Relu激活函数解读
- 对于修正线性单元(Rectified linear unit,ReLU)优点如下:
①单侧抑制 ②相对宽阔的兴奋边界 ③稀疏激活性- 资料参考
https://blog.csdn.net/cherrylvlei/article/details/53149381
https://www.cnblogs.com/qw12/p/6294430.html
name: "VGG_ILSVRC_16_layers"
layer {
name: 'input-data'
type: 'Python'
top: 'data'
top: 'im_info'
top: 'gt_boxes' # top指输入的数据,包括data、image & gt
python_param
{
module: 'roi_data_layer.layer'
layer: 'RoIDataLayer'
param_str: "'num_classes': 21" # 21个分类
}
}
# conv1_1
layer {
name: "conv1_1"
type: "Convolution"
bottom: "data" # bottom是输入
top: "conv1_1" # top是输出
param {
lr_mult: 0
decay_mult: 0
}
param {
lr_mult: 0
decay_mult: 0
}
convolution_param {
num_output: 64
pad: 1
kernel_size: 3
}
}
# relu1_1
layer {
name: "relu1_1"
type: "ReLU"
bottom: "conv1_1"
top: "conv1_1"
}
# conv1_2
layer {
name: "conv1_2"
type: "Convolution"
bottom: "conv1_1"
top: "conv1_2"
param {
lr_mult: 0
decay_mult: 0
}
param {
lr_mult: 0
decay_mult: 0
}
convolution_param {
num_output: 64
pad: 1
kernel_size: 3
}
}
# relu1_2
layer {
name: "relu1_2"
type: "ReLU"
bottom: "conv1_2"
top: "conv1_2"
}
# pool1
layer {
name: "pool1"
type: "Pooling"
bottom: "conv1_2"
top: "pool1"
pooling_param {
pool: MAX # max pooling
kernel_size: 2
stride: 2
}
}
# conv2_1
layer {
name: "conv2_1"
type: "Convolution"
bottom: "pool1"
top: "conv2_1"
param {
lr_mult: 0
decay_mult: 0
}
param {
lr_mult: 0
decay_mult: 0
}
convolution_param {
num_output: 128
pad: 1
kernel_size: 3
}
}
# relu2_1
layer {
name: "relu2_1"
type: "ReLU"
bottom: "conv2_1"
top: "conv2_1"
}
# conv2_2
layer {
name: "conv2_2"
type: "Convolution"
bottom: "conv2_1"
top: "conv2_2"
param {
lr_mult: 0
decay_mult: 0
}
param {
lr_mult: 0
decay_mult: 0
}
convolution_param {
num_output: 128
pad: 1
kernel_size: 3
}
}
# relu2_2
layer {
name: "relu2_2"
type: "ReLU"
bottom: "conv2_2"
top: "conv2_2"
}
# pool2
layer {
name: "pool2"
type: "Pooling"
bottom: "conv2_2"
top: "pool2"
pooling_param {
pool: MAX # max pooling
kernel_size: 2
stride: 2
}
}
# conv3_1
layer {
name: "conv3_1"
type: "Convolution"
bottom: "pool2"
top: "conv3_1"
param {
lr_mult: 1 # weight项的学习率(从这里开始了学习率的调整更新)
}
param {
lr_mult: 2 # bias项的学习率,一般是weight的两倍
}
convolution_param {
num_output: 256
pad: 1
kernel_size: 3
}
}
# relu3_1
layer {
name: "relu3_1"
type: "ReLU"
bottom: "conv3_1"
top: "conv3_1"
}
# conv3_2
layer {
name: "conv3_2"
type: "Convolution"
bottom: "conv3_1"
top: "conv3_2"
param {
lr_mult: 1
}
param {
lr_mult: 2
}
convolution_param {
num_output: 256
pad: 1
kernel_size: 3
}
}
# relu3_2
layer {
name: "relu3_2"
type: "ReLU"
bottom: "conv3_2"
top: "conv3_2"
}
# conv3_3
layer {
name: "conv3_3"
type: "Convolution"
bottom: "conv3_2"
top: "conv3_3"
param {
lr_mult: 1
}
param {
lr_mult: 2
}
convolution_param {
num_output: 256
pad: 1
kernel_size: 3
}
}
layer {
name: "relu3_3"
type: "ReLU"
bottom: "conv3_3"
top: "conv3_3"
}
layer {
name: "pool3"
type: "Pooling"
bottom: "conv3_3"
top: "pool3"
pooling_param {
pool: MAX
kernel_size: 2
stride: 2
}
}
# conv4_1
layer {
name: "conv4_1"
type: "Convolution"
bottom: "pool3"
top: "conv4_1"
param {
lr_mult: 1
}
param {
lr_mult: 2
}
convolution_param {
num_output: 512
pad: 1
kernel_size: 3
}
}
# relu4_1
layer {
name: "relu4_1"
type: "ReLU"
bottom: "conv4_1"
top: "conv4_1"
}
# conv4_2
layer {
name: "conv4_2"
type: "Convolution"
bottom: "conv4_1"
top: "conv4_2"
param {
lr_mult: 1
}
param {
lr_mult: 2
}
convolution_param {
num_output: 512
pad: 1
kernel_size: 3
}
}
# relu4_2
layer {
name: "relu4_2"
type: "ReLU"
bottom: "conv4_2"
top: "conv4_2"
}
# conv4_3
layer {
name: "conv4_3"
type: "Convolution"
bottom: "conv4_2"
top: "conv4_3"
param {
lr_mult: 1
}
param {
lr_mult: 2
}
convolution_param {
num_output: 512
pad: 1
kernel_size: 3
}
}
# relu4_3
layer {
name: "relu4_3"
type: "ReLU"
bottom: "conv4_3"
top: "conv4_3"
}
# pool4
layer {
name: "pool4"
type: "Pooling"
bottom: "conv4_3"
top: "pool4"
pooling_param {
pool: MAX # max pooling
kernel_size: 2
stride: 2
}
}
# conv5_1
layer {
name: "conv5_1"
type: "Convolution"
bottom: "pool4"
top: "conv5_1"
param {
lr_mult: 1
}
param {
lr_mult: 2
}
convolution_param {
num_output: 512
pad: 1
kernel_size: 3
}
}
# relu5_1
layer {
name: "relu5_1"
type: "ReLU"
bottom: "conv5_1"
top: "conv5_1"
}
# conv5_2
layer {
name: "conv5_2"
type: "Convolution"
bottom: "conv5_1"
top: "conv5_2"
param {
lr_mult: 1
}
param {
lr_mult: 2
}
convolution_param {
num_output: 512
pad: 1
kernel_size: 3
}
}
# relu5_2
layer {
name: "relu5_2"
type: "ReLU"
bottom: "conv5_2"
top: "conv5_2"
}
# conv5_3
layer {
name: "conv5_3"
type: "Convolution"
bottom: "conv5_2"
top: "conv5_3"
param {
lr_mult: 1
}
param {
lr_mult: 2
}
convolution_param {
num_output: 512
pad: 1
kernel_size: 3
}
}
# relu5_3
layer {
name: "relu5_3"
type: "ReLU"
bottom: "conv5_3"
top: "conv5_3"
}
# ============ RPN ==============
# rpn_conv/3x3
# 对卷积网络传来的feature map做RPN的第一步操作:卷积
layer {
name: "rpn_conv/3x3"
type: "Convolution"
bottom: "conv5_3" # 接在conv5_3后接了一个RPN-layer
top: "rpn/output"
param { lr_mult: 1.0 }
param { lr_mult: 2.0 }
convolution_param {
num_output: 512
kernel_size: 3 pad: 1 stride: 1 # conv参数设定
weight_filler { type: "gaussian" std: 0.01 }
bias_filler { type: "constant" value: 0 }
}
}
# 接relu激活函数,增加其非线性
layer {
name: "rpn_relu/3x3"
type: "ReLU"
bottom: "rpn/output"
top: "rpn/output"
}
# # 开始cls和reg
# rpn_cls_score
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(label+prob) * 9(anchors)
kernel_size: 1 pad: 0 stride: 1
weight_filler { type: "gaussian" std: 0.01 }
bias_filler { type: "constant" value: 0 }
}
}
# rpn_bbox_pred
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)
# 含有36个输出,每个anchor有4个坐标值
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 } }
}
# rpn-data
# 这一块进行了很多的操作,在送入回归之前,对很多框进行筛选
# 比如在边缘上出界的框怎么处理,多个框重叠怎么处理(NMS)
layer {
name: 'rpn-data'
type: 'Python'
# 输入
bottom: 'rpn_cls_score' # 分类得分
bottom: 'gt_boxes'
bottom: 'im_info'
bottom: 'data' # 输入data
# 输出
top: 'rpn_labels' # 利用对9个anchor给一个label(>0.7为前景)(<0.3为背景)
top: 'rpn_bbox_targets' # 算出原文中3.1.2节中给出的8个参数
top: 'rpn_bbox_inside_weights'
top: 'rpn_bbox_outside_weights' # 俩weight为了计算loss而设置的
python_param {
module: 'rpn.anchor_target_layer'
layer: 'AnchorTargetLayer'
param_str: "'feat_stride': 16"
}
}
# rpn_loss_cls
# 分类的loss
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
}
}
# rpn_loss_bbox
# b.box的回归的loss
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' # 输入了三个东西reshape后的分类概率、预测的框和im信息
top: 'rpn_rois' # 产生一些region,
# top: 'rpn_scores'
python_param {
module: 'rpn.proposal_layer'
layer: 'ProposalLayer'
param_str: "'feat_stride': 16"
}
}
#layer {
# name: 'debug-data'
# type: 'Python'
# bottom: 'data'
# bottom: 'rpn_rois'
# bottom: 'rpn_scores'
# python_param {
# module: 'rpn.debug_layer'
# layer: 'RPNDebugLayer'
# }
#}
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 ============
# 分类任务层
# 对大小不同的框进行roi-pooling
layer {
name: "roi_pool5"
type: "ROIPooling"
bottom: "conv5_3"
bottom: "rois"
top: "pool5"
# 对不同的框pooling到固定的尺度大小(参数设定),便于做最后的分类
roi_pooling_param {
pooled_w: 7
pooled_h: 7
spatial_scale: 0.0625 # 1/16
}
}
# fc6
# pooling后当然是接上一些全连接层
layer {
name: "fc6"
type: "InnerProduct"
bottom: "pool5"
top: "fc6"
param {
lr_mult: 1
}
param {
lr_mult: 2
}
inner_product_param {
num_output: 4096
}
}
layer {
name: "relu6"
type: "ReLU"
bottom: "fc6"
top: "fc6"
}
# 做了一个dropout
layer {
name: "drop6"
type: "Dropout"
bottom: "fc6"
top: "fc6"
dropout_param {
dropout_ratio: 0.5
}
}
layer {
name: "fc7"
type: "InnerProduct"
bottom: "fc6"
top: "fc7"
param {
lr_mult: 1
}
param {
lr_mult: 2
}
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
}
}
layer {
name: "cls_score"
type: "InnerProduct"
bottom: "fc7"
top: "cls_score"
param {
lr_mult: 1
}
param {
lr_mult: 2
}
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
}
param {
lr_mult: 2
}
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: "loss_cls"
loss_weight: 1
}
layer {
name: "loss_bbox"
type: "SmoothL1Loss"
bottom: "bbox_pred"
bottom: "bbox_targets"
bottom: "bbox_inside_weights"
bottom: "bbox_outside_weights"
top: "loss_bbox"
loss_weight: 1
}
下面给出vgg16网络结构图(使用PPT绘制,不会画的可以留言给我)
# 在rpn_cls_score中给出了:
num_output: 18 # 2(bg/fg) * 9(anchors)
# 在rpn_bbox_pred中给出了
num_output: 36 # 4 * 9(anchors)
但是机器在此时以及此时之前,是不知道anchor是啥,更不会知道anchor背后对应的18和36个数字代表的是啥,对于此时的机器来说,只知道18+36个参数。
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" # 引入Smooth L1 Loss函数
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 }
}
即经过了Loss Function后,机器对其参数值(18个或者36个)计算与label之间的“距离”(即:loss,同理越小越好),这个“距离”越小越好。在这个不断迭代、学习的过程之中,学到了当这18或36个参数带入运算时,能够达到最小的Loss,即可以“学习”到了其内涵。
所以在实际的网络中,这些框不是被截取出来的一块区域,而是在计算loss之前,被送进入的data,而是在loss的过程中学习到的,这是我们的anchor,所以这个anchor以及这些框不是实际存在的,是人为的想象出来而设定的
现在整体来理一下上文中的逻辑关系,机器在Loss步骤之前是不知道anchor是什么的,也不知道为什么会有18/36个参数在面前,当时给了一个Loss后,会进行迭代修正,直到拟合到了True Answer上或者接近于它的时候,会达到一个最小的Loss值,在这个不断迭代(机器学习的本质:迭代=学习)的过程中,机器学习到了一个东西:只有这样调整参数才能使我们预先设定的Loss下降到最小。
也可以结合梯度下降算法来看,目标函数(Loss Function)通过GD算法(SGD、BSGD)下降到一个函数的最优解(可能是局部最优,也可能是全局最优),在机器中这个“下降”的过程就是对应人脑中“学习”的过程,这就是机器“学习”的本质。
Faster R-CNN: Towards Real-Time Object Detection with Region Proposal Networks
Shaoqing Ren, Kaiming He, Ross Girshick, and Jian Sun
分析论文,不能拿到手从头开始看起,要有所整体认知,鄙人认为,应给出几个看论文的理由,才会有动力让你看到最后,这也就是我的3.1节不选择分析摘要的原因。
其次对于看论文,得有精度得有泛读,泛读适合读摘要读图片读表格,弄清他的思路;而对于大牛实验室的著作,我们需要从源码入手,解读其内涵,分析其思路,模仿其文体。选择Faster R-CNN,我给出了以下几个理由:
摘要给出了论文的核心凝练内容(黄色部分标出),对于2015年的这篇论文,着实是state-of-the-art的相当高的水准了,文章创造性的提出了一个RPN网络,并在多个数据集上去的了优秀的成绩,且开源。
很简单,目标检测 = 目标 + 检测。对于图像分类、物体检测、物体分割、实例分割、语义分割的区别,在笔者的这篇文章中有所提及,推荐阅读,入门必备:
https://blog.csdn.net/Gerwels_JI/article/details/82990189
对于人眼的极限是20 FPS算是极限了,一般我们玩的LOL和PUBG游戏是70 FPS左右,而在本文中可以达到5 FPS的成绩。当然这个成绩在2018年的今天不算太优秀:现在two stage方法的有rcnn、Fast rcnn、Faster rcnn、Mask rcnn;One shot 的方法有ssd和yolo。速度快请选yolo大法,精度高,请选择rcnn系列。
人脸检测可以通过这个算法做(比如从96%提升到97%,小米在FDDB上得到了第一名的准确率)。也就是Faster R-CNN的应用面非常广,对于车牌识别、人脸检测、以及传统的物体识别,都可以带入Faster RCNN这个框架中来。
Object Detection, Region Proposal, Convolutional Neural Network
百度百科给出的定义(不愿意看直接看下面的精简版):图像金字塔是图像多尺度表达的一种,是一种以多分辨率来解释图像的有效但概念简单的结构。一幅图像的金字塔是一系列以金字塔形状排列的分辨率逐步降低,且来源于同一张原始图的图像集合。其通过梯次向下采样获得,直到达到某个终止条件才停止采样。我们将一层一层的图像比喻成金字塔,层级越高,则图像越小,分辨率越低。
注意:上采样和下采样是非线性处理,不可逆,有损的处理!
简单来说一幅图像的金字塔是一系列以金字塔形状排列的分辨率逐步降低,且来源于同一张原始图的图像集合。图1 (a)中构建了图像和特征映射图金字塔,分类器在各种尺度图上运行。在经过Scale变换操作后,图像大小不同其中框住的内容是不同的(这会导致处理速度变慢:相同的filter在不同像素大小的同一张图片上滑动,其每一个感受野所感受到的内容是不一样的,速度也是不一样的)。总之,事物是发展的,老的事物有其存在的意义,也有其被替代的理由,他的速度太慢了。
在同一张feature image上做不同的multiple fliter sizes(即:在特征映射图上run具有多个比例/大小的Filter所组成的金字塔),从而得到了不同的感受野的大小。
以上都是传统算法的表现,本文中在回归函数中使用金字塔边界框参数进行回归操作,并引入了anchor的概念,至于怎么实现,请看下文
怎么通过引入anchor来提取出我们所需要的不同大小的框?
在feature map中每个特征点(也叫锚点anchor),每个锚点上有k个anchor boxes(论文中k=9=3*3,k个都是基于multiple scales(像素值) and aspect ratios(1:1/1:2/2:1))
图3左图是RPN网络,在feature map上的每个点有9个anchor box
默认情况下,我们使用3个尺度和3个长宽比,在每个滑动位置产生 k = 9 k=9 k=9个Anchors boxes。对于大小为 W × H W×H W×H(通常约为2400)的convolutional feature map,总计 W × H × k W×H×k W×H×k个Anchors boxes。
具体实现解读和代码分析请戳此链接:
为了训练RPN,我们为每个anchor分配一个binary class label (of being an object or not),label的分配规则如下:
b.box regression:首先理解啥是回归?(一点一点的迭代拟合到true answer(GT)),这里通过loss来回归(需要找一个衡量的标准),这里不用欧式距离或者曼哈顿距离来衡量,而是用 t x t_x tx和 t y t_y ty(坐标差除以w或者h)来表示。
conv layer(vgg-16或者ZF-net)、RPN、RoI Pooling层,具体分析就是围绕以上展开的
传统的卷积网络中(CONV-FC-Classification):从conv送到到FC中的图像的大小是固定的,但本文中从input就是任意的,anchor box也是任意的,当然最终进行分类的,不是对图像进行分类,而是对框内的东西进行分类。
图2展示了python版本中的VGG16模型中的faster_rcnn_test.prototxt的网络结构,可以清晰的看到该网络: