目录
1 前言
1.1 图1展示了Faster RCNN的4个主要内容
1.2 图2展示了基于VGG16模型的网络结构
1.3 Faster RCNN目标
1.4 新出炉的pytorch官方Faster RCNN代码导读
2 Conv layers
3 Region Proposal Networks(RPN)
3.1 RPN整体介绍
3.2 RPN模块介绍
3.3 单通道与多通道图像卷积基础知识介绍以及1×1卷积核介绍
单通道
多通道
1×1卷积
3.4 Anchor简介
3.4.1 为什么提出anchor?
3.4.2 RPN的输出跟anchors是什么关系呢?
3.5 Bounding box regression原理
3.6 Proposal Layer
4 ROI Pooling
4.1 为何需要ROI Pooling?
4.2 ROI Pooling原理
4.3 为何有ROI Pooling还要把输入图片resize到固定大小的MxN
5 Classification
6 Faster RCNN训练
6.1 训练RPN网络
7 Faster RCNN预测
8 拓展
Faster RCNN将特征抽取(feature extraction),proposal提取,bounding box regression,classification都整合在了一个网络中,使得综合性能有较大提高,在检测速度方面尤为明显。
图1 Faster RCNN基本结构(来自原论文)
从图中可以看出:
接下来,以上面四部分内容为切入点,详细介绍Faster RCNN网络。
从图中可以看出:
图2 faster RCNN网络结构
图2网络的详细版如下:
给定一张图片, 找出图中的有哪些对象,以及这些对象的位置和置信概率:
图3 目标检测样例
https://zhuanlan.zhihu.com/p/145842317
Conv layers包含了conv,pooling,relu三种层。以python版本中的VGG16模型中的faster_rcnn_test.pt的网络结构为例,如图2,Conv layers部分共有13个conv层,13个relu层,4个pooling层。这里有一个非常容易被忽略但是又无比重要的信息,在Conv layers中:
为何重要?在Faster RCNN Conv layers中对所有的卷积都做了扩边处理( pad=1,即填充一圈0),导致原图变为 (M+2)x(N+2)大小,再做3x3卷积后输出MxN 。正是这种设置,导致Conv layers中的conv层不改变输入和输出矩阵大小。如图4:
图4 卷积示意图
类似的是,Conv layers中的pooling层kernel_size=2,stride=2。这样每个经过pooling层的MxN矩阵,都会变为(M/2)x(N/2)大小。综上所述,在整个Conv layers中,conv和relu层不改变输入输出大小,只有pooling层使输出长宽都变为输入的1/2。
那么,一个MxN大小的矩阵经过Conv layers固定变为(M/16)x(N/16),这样Conv layers生成的feature map中都可以和原图对应起来。
经典的检测方法生成检测框都非常耗时,如OpenCV adaboost使用滑动窗口+图像金字塔生成检测框;或如R-CNN使用SS(Selective Search)方法生成检测框。而Faster RCNN则抛弃了传统的滑动窗口和SS方法,直接使用RPN生成检测框,这也是Faster R-CNN的巨大优势,能极大提升检测框的生成速度。
图5 RPN网络
上图5展示了RPN网络的具体结构。
可以看到,feature map经过3×3卷积后,分成了两条线:
第一条线:通过softmax分类anchors获得foreground(positive)和background(negative),其中检测目标是foreground。因为是2分类,所以它的维度是2k score。
第二条线:计算对于anchors的bounding box regression偏移量,以获得精确的proposal。它的维度是4k coordinates。
而最后的Proposal层则负责综合positive anchors和对应bounding box regression偏移量获取proposals,同时剔除太小和超出边界的proposals。其实整个网络到了Proposal Layer这里,就完成了相当于目标定位的功能。
现在详细看一下第二条线,如下图所示:
上图的卷积,在caffe prototxt中的定义格式如下:
layer {
name: "rpn_bbox_pred"
type: "Convolution"
bottom: "rpn/output"
top: "rpn_bbox_pred"
convolution_param {
num_output: 36 # 4 * 9(anchors)
kernel_size: 1 pad: 0 stride: 1
}
}
可以看到,其 num_output=36,即经过该卷积输出图像为WxHx36,在caffe blob存储为[1, 4x9, H, W],这里相当于feature maps每个点都有9个anchors,每个anchors又都有4个用于回归的变换量,即:
对应VGG输出的feature map,大小为的特征,对应设置个anchors,而RPN输出为:
- 大小为的positive/negative softmax分类特征矩阵
- 大小为 的regression坐标回归特征矩阵
恰好满足RPN完成positive/negative分类+bounding box regression坐标回归.
img_info的解释:
对于一副任意大小PxQ图像,传入Faster RCNN前首先reshape到固定MxN,im_info=[M, N, scale_factor]则保存了此次缩放的所有信息。然后经过Conv Layers,经过4次pooling变为WxH=(M/16)x(N/16)大小,其中feature_stride=16则保存了该信息,用于计算anchor偏移量。
图6 RPN模块介绍
anchor的数量和feature map相关,不同的feature map对应的anchor数量也不一样。
RPN在Extractor
输出的feature maps的基础之上,先增加了一个卷积,然后利用两个1x1的卷积分别进行二分类(是否为正样本)和位置回归。
进行分类的卷积核通道数为9×2(9个anchor,每个anchor二分类,使用交叉熵损失),进行回归的卷积核通道数为9×4(9个anchor,每个anchor有4个位置参数)。
RPN是一个全卷积网络(fully convolutional network),这样对输入图片的尺寸就没有要求了。
接下来,RPN做的事情就是利用(AnchorTargetCreator
)将20000多个候选的anchor选出256个anchor进行分类和回归位置。选择过程如下:
gt_bbox
),选择和它重叠度(IoU)最高的一个anchor作为正样本gt_bbox
重叠度超过0.7的anchor,作为正样本,正样本的数目不超过128个gt_bbox
重叠度小于0.3的anchor作为负样本。负样本和正样本的总数为256RPN网络的整体流程如下:
生成anchors -> softmax分类器提取positvie anchors -> bbox reg回归positive anchors -> Proposal Layer生成proposals
对于单通道图像+单卷积核做卷积,图4的讲解已经说明。
对于多通道图像+多卷积核做卷积,计算方式如下:
图7 多通道卷积计算方式
如图7所示,输入有3个通道(见图6第一列),同时有2个卷积核(见图6第四列)。
对于每个卷积核,先在输入3个通道分别作卷积(见图6第二列),再将3个通道结果加起来(见图6第三列)得到卷积输出。
所以,对于某个卷积层,无论输入图像有多少个通道,输出图像通道数总是等于卷积核数量!
如果卷积的输出输入都只是一个平面,那么1x1卷积核并没有什么意义,它是完全不考虑像素与周边其他像素关系。
如果卷积的输出输入是长方体,那么1x1卷积核有什么意义。对于1x1卷积的意义,实际上是对每个像素点,在不同的channels上进行线性组合(信息整合),且保留了图片的原有平面结构,调控depth,从而完成升维或降维的功能。如8所示
图8
对于多通道图像做1×1卷积,其实就是将输入的图像的每个通道乘以卷积系数加在一起,即相当于把原图像中本来各个独立通道"联通"在一起。
Anchor优点:
它只依赖于单个 scale 的 images 和 feature map, 滑动窗口也只使用一个尺寸的 filter. 不过却能解决 multiple scales and sizes的问题。
Anchor定义:
anchor 其实就是预训练网络卷积层的最后一层 feature map 上的一个像素,以该 anchor 为中心,可以生成 k 种 anchor boxes; 每个 anchor box 对应有一组缩放比例(scale)和宽高比(aspect)。论文中,共使用 3 种 scale(128, 256, 512)和3 种 aspect(1:2, 1:1, 2:1), 所以每个 anchor 位置产生 9 个 anchor boxs.
具体来讲,anchor实际上就是一组由rpn/generate_anchors.py生成的矩形。直接运行作者demo中的generate_anchors.py:
# FasterRCNN.__init__(...)
if rpn_anchor_generator is None:
anchor_sizes = ((32,), (64,), (128,), (256,), (512,))
aspect_ratios = ((0.5, 1.0, 2.0),) * len(anchor_sizes)
rpn_anchor_generator = AnchorGenerator(
anchor_sizes, aspect_ratios
)
可以得到以下输出:
[[ -84. -40. 99. 55.]
[-176. -88. 191. 103.]
[-360. -184. 375. 199.]
[ -56. -56. 71. 71.]
[-120. -120. 135. 135.]
[-248. -248. 263. 263.]
[ -36. -80. 51. 95.]
[ -80. -168. 95. 183.]
[-168. -344. 183. 359.]]
其中,每行的4个值(x1,y1,x2,y2)代表矩形左上角和右下角点的坐标。9个矩形共有3种形状,3 scale with box areas分别是{128*128,256*256,512*512}和 3 aspect ratios分别是近似{(1:1 1:2 2:1)},所以共9种矩形。实际上通过anchors就引入了检测中常用的多尺度方法。
对于超参scale和aspect的敏感性,验证结果如下:
Anchor生成多少个?
原图800x600,VGG下采样16倍,feature map每个点设置9个Anchor,所以:
其中ceil()表示向上取整,是因为VGG输出的feature map size= 50*38
图9
Achors的生成规则:
前面提到经过Conv layers后,图片大小变成了原来的1/16
令feat_stride=16,在生成Anchors时,我们先定义一个base_anchor,大小为16*16的box(特征图(50*38)上的一个点,可以对应到原图(800*600)上一个16*16大小的区域)
源码中转化为[0,0,15,15]的数组,参数ratios=[0.5, 1, 2] scales=[8, 16, 32]
先看[0,0,15,15],面积保持不变,长、宽比分别为[0.5, 1, 2]是产生的Anchors box
图10
如果经过scales变化,即长、宽分别均为 (16*8=128)、(16*16=256)、(16*32=512),对应anchor box如图
图11
综合以上两种变换,最后生成9个Anchor box
图12
所以,最终base_anchor=[0,0,15,15]生成的9个Anchor box坐标如下:
[[ -84. -40. 99. 55.]
[-176. -88. 191. 103.]
[-360. -184. 375. 199.]
[ -56. -56. 71. 71.]
[-120. -120. 135. 135.]
[-248. -248. 263. 263.]
[ -36. -80. 51. 95.]
[ -80. -168. 95. 183.]
[-168. -344. 183. 359.]]
为什么Anchor坐标中有负数
回顾anchor生成步骤:
首先生成9个base anchor,然后通过坐标偏移在大小的下采样Feature map, 每个点都放上这9个base anchor,就形成了个anchors。
显然这里面有一部分边缘anchors会超出图像边界,而真实中不会有超出图像的目标,所以会有clip anchor步骤。
图13 clip anchor
Anchor到底与网络输出如何对应
VGG输出的特征,对应设置个anchors,而RPN输出的分类特征矩阵和的坐标回归特征矩阵
图14 anchor与网络输出如何对应方式
其实在实现过程中,每个点的个分类特征与回归特征,与个anchor逐个对应即可,这实际是一种“人为设置的逻辑映射”。当然,也可以不这样设置,但是无论如何都需要保证在训练和测试过程中映射方式必须一致。
图15
解释一下上面的图:
在原文中使用的是ZF model中,其Conv Layers中最后的conv5层num_output=256,对应生成256张特征图(feature maps),所以相当于feature map每个点都是256-dimensions
在conv5之后,做了rpn_conv/3x3卷积且num_output=256,相当于每个点又融合了周围3x3的空间信息,同时256-d不变
假设在conv5 feature map中每个点上有k个anchor(原文如上k=9),而每个anhcor要分foreground和background,所以每个点由256d feature转化为cls=2k scores;而每个anchor都有[x, y, w, h]对应4个偏移量,所以reg=4k coordinates(scores和coordinates为RPN的最终输出)
补充一点,全部anchors拿去训练太多了,训练程序会在合适的anchors中随机选取128个postive anchors+128个negative anchors进行训练
图16
如图15所示,绿色框为飞机的Ground Truth(GT),红色为提取的positive anchors,即便红色的框被分类器识别为飞机,但是由于红色的框定位不准,这张图相当于没有正确的检测出飞机。所以,我们希望采用一种方法对红色的框进行微调,使得positive anchors和GT更加接近。
对于窗口一般使用四维向量表示,分别表示窗口的中心点坐标、宽和高。对于图16,红色的框A代表原始的positive Anchors,绿色的框G代表目标的GT,我们的目标是寻找一种变换,使得输入原始的anchor A,经过映射得到一个跟真实窗口G更接近的回归窗口G'
图17 回归窗口
那么,经过何种变换,才能从图中的anchor A变成G'呢?
观察上面4个公式发现,需要学习的是,,,这四个变换。
当输入的anchor A与GT相差较小时,可以认为上述变换是一种线性变换, 那么就可以用线性回归来建模对窗口进行微调(注意,只有当anchors A和GT比较接近时,才能使用线性回归模型,否则就是复杂的非线性问题了)。
接下来的问题,就是如何通过线性回归获得,,,了。
线性回归就是给定输入的特征向量X, 学习一组参数W, 使得经过线性回归后的值跟真实值Y非常接近,即。
对于上述问题,输入X是Feature map,定义为;同时,还有训练传入A与GT之间的变换量,即,输出是,,,四个变换,那么,目标函数可以表示为:
其中,是对应anchor的feature map组成的特征向量,是需要学习的参数,是得到的预测值(*表示x, y, w, h, 也就是每一个变换对应一个上述目标函数)
为了让预测值与真实值差距最小,设计L1损失函数:
函数优化目标为:
作者论文中,使用的smooth-L损失。
需要说明,只有在GT与需要回归框位置比较接近时,才可近似认为上述线性变换成立。
对应于Faster RCNN原文,positive anchor与ground truth之间的平移量与尺度因子如下:
其中,对应两组框的中心点坐标和它的宽和高,变量分别对应predicted box, anchor box和ground-truth box中心点的横坐标,都是同理。
Proposal Layer负责综合所有变换量和positive anchors,计算出精准的proposal,送入后续RoI Pooling Layer。
先来看看Proposal Layer的caffe prototxt定义:
layer {
name: 'proposal'
type: 'Python'
bottom: 'rpn_cls_prob_reshape'
bottom: 'rpn_bbox_pred'
bottom: 'im_info'
top: 'rois'
python_param {
module: 'rpn.proposal_layer'
layer: 'ProposalLayer'
param_str: "'feat_stride': 16"
}
}
图18
Proposal Layer forward(caffe layer的前传函数)按照以下顺序依次处理:
之后输出proposal=[x1, y1, x2, y2],注意,由于在第三步中将anchors映射回原图判断是否超出边界,所以这里输出的proposal是对应MxN输入图像尺度的,这点在后续网络中有用。另外我认为,严格意义上的检测应该到此就结束了,后续部分应该属于识别了。
ROI Pooling功能:
而RoI Pooling层则负责收集proposal,并计算出proposal feature maps,送入后续网络。
如上图所示,该层有2个输入:
先来看一个问题:对于传统的CNN(如AlexNet和VGG),当网络训练好后输入的图像尺寸必须是固定值,同时网络输出也是固定大小的vector or matrix。
如果输入图像大小不定,这个问题就变得比较麻烦。有2种解决办法:
图19 crop与warp破坏图像原有结构信息
两种办法的示意图如图18,可以看到无论采取那种办法都不好,要么crop后破坏了图像的完整结构,要么warp破坏了图像原始形状信息。
回忆RPN网络生成的proposals的方法:对positive anchors进行bounding box regression,那么这样获得的proposals也是大小形状各不相同,即也存在上述问题。所以Faster R-CNN中提出了RoI Pooling解决这个问题。不过RoI Pooling确实是从Spatial Pyramid Pooling发展而来,但是限于篇幅这里略去不讲,有兴趣的读者可以自行查阅相关论文。
分析之前,先来看看RoI Pooling Layer的caffe prototxt的定义:
layer {
name: "roi_pool5"
type: "ROIPooling"
bottom: "conv5_3"
bottom: "rois"
top: "pool5"
roi_pooling_param {
pooled_w: 7
pooled_h: 7
spatial_scale: 0.0625
}
}
RoI Pooling layer forward过程:
这样处理后,即使大小不同的proposal,输出结果都是固定大小,实现了固定长度输出。
图20 proposal示意图
由于引入ROI Pooling,从原理上说Faster R-CNN确实能够检测任意大小的图片。但是由于在训练的时候需要使用大batch训练网络,而不同大小输入拼batch在实现的时候代码较为复杂,而且当时以Caffe为代表的第一代深度学习框架也不如Tensorflow和PyTorch灵活,所以作者选择了把输入图片resize到固定大小的800x600。这应该算是历史遗留问题。
Classification部分利用已经获得的proposal feature maps,通过full connect层与softmax计算每个proposal具体属于那个类别(如人,车,电视等),输出cls_prob概率向量;同时再次利用bounding box regression获得每个proposal的位置偏移量bbox_pred,用于回归更加精确的目标检测框。Classification部分网络结构如图20。
图21 Classification部分网络结构图
经过roi pooling层之后,batch_size=300, proposal feature map的大小是7*7,512-d,对特征图进行全连接,参照图20,最后同样利用Softmax Loss和L1 Loss完成分类和定位。
即从PoI Pooling获取到7x7大小的proposal feature maps后,通过全连接主要做了如下2件事:
Faster RCNN采用交替优化训练,其实是在已经训练好的model(如VGG,ZF)的基础上,继续训练,且分为6个步骤:
下面是一张训练过程流程图:
图22 训练流程图
在该步骤中,首先读取预训练好的模型(文章使用的VGG),开始迭代训练。
来看一下训练的网络结构:
图23 网络模型(图中,Conv layers中的所有层都画在了一起,如红框所示,后续图都是如此处理)
整个网络使用的损失,如下所示:
公式符号解释:
i: anchor index
: foreground softmax predict概率
: 对应GT predict概率(即,当第i个anchors与GT间IoU>0.7,认为该anchor是foreground,=1,反之,IOU<0.3时,认为该anchors是background,=0;对于大于0.3小于0.7的anchors,则不参与训练;一般一张图片取256个anchors,且bg:fg=1:1)
t: predict bounding box
: foreground anchors对应的GT box
: 平衡参数,用于平衡和(如=256,=2400,则设置为10)
可以看到,整个loss分为2个部分:
。
了解上述数学原理,再看图21:
Faster R-CNN还有一种end-to-end的训练方式,可以一次完成train,有兴趣请自己看作者GitHub
检测算法的评价指标:https://www.zhihu.com/question/288198143/answer/482600906
Faster RCNN在文字检测中的应用:CTPN
https://zhuanlan.zhihu.com/p/34757009
Faster RCNN在高德导航中的应用:https://mp.weixin.qq.com/s/IJUMCOBhgXHv7VC1YT4q_g
参考:https://zhuanlan.zhihu.com/p/31426458