目标检测 -- R-CNN,Fast R-CNN,Faster R-CNN
R-CNN,Fast R-CNN,Faster R-CNN这些是深度学习目标检测的鼻祖。看各种博客分析,东看看西看看,不系统。这里准备系统的记录一下深度学习目标检测的发展史。这里大部分摘录其他博客。参考链接见下。
R-CNN,Fast R-CNN,Faster R-CNN原理及执行与训练的实例+实现自己的目标检测 https://blog.csdn.net/m0_37407756/article/details/80810364
一文读懂Faster RCNN https://zhuanlan.zhihu.com/p/31426458
R-CNN的原理
全称是Region-CNN,它可以说是第一个成功地将深度学习应用到目标检测上的算法。
后面将要学习的Fast R-CNN, Faster R-CNN全部都是建立在R-CNN基础上的。
传统的目标检测方法大多以图像识别为基础。一般可以在图片上使用穷举去选出所有物体可能出现的区域框,对这些区域框提取特征并使用图像识别万法分类,得到所有分类成功的区域后,通过非极大值抑制(Non-maximum suppression)输出结果。
R-CNN遵循传统目标检测的思路,同样采用提取框、对每个框提取特征、图像分类、非极大值抑制四个步骤进行目标检测。只不过在提取特征这一步,将传统的特征(如SIFT, HOG特征等)换成了深度卷积网络提取的特征。
对于原始图像,首先使用Selective Search搜寻可能存在物体的区域。
Selcctivc Search可以从图像中启发式地搜索出可能包合物体的区域。相比穷举而言,Selective Search可以减少一部分计算量。
下一步,将取出的可能含有物体的区域送入CNN中提取特征。CNN通常是接受一个固定大小的图像,而取出的区域大小却各有不同。
对此,R-CNN的做法是将区域缩放到统一大小,再使用CNN提取特征。提取出特征后使用SVM进行分类,最后通过非极大值抑制输出结果。
R-CNN的训练可分成下白四步:
(1)在数据集上训练CNN 。R-CNN论文中使用的CNN网络是AlexNet,数据集为ImageNet 。
(2)在目标检测的数据集上,对训练好的CNN做微调。
(3)用Selective Search搜索候选区域,统一使用微调后的CNN对这些区域提取特征,并将提取到的特征存储起来。
(4)使用存储起来的特征,训练SVM分类器。
R-CNN的缺点是计算量太大。在一张图片中,通过Selective Search得到的有效区域往往在1000个以上,这意味着要重复计算1000多次神经网络非常耗时。另外,在训练阶段,还需要把所有特征保存起来。再通过SVM进行训练,这也是非常耗时且麻烦的。下面将要介绍的Fast R-CNN和Faster R-CNN在一定程度上改进了R-CNN计算量大的缺点,不仅速度变快不少,识别准确率也得到了提高。
SPPNet的原理
在学习R-CNN的改进版Fast R-CNN之前,作为前置知识。有必要学习SPPNet的原理。
SPPNet的英文全称是Spatial Pyramid Pooling Convolutional Networks,翻译成中文是“空间金字塔池化卷积网络”。
听起来十分高深,实际上原理并不难,简单来讲,SPPNet主要做了一件事情:
将CNN的输入从固定尺寸改进为任意尺寸。例如,在普通的CNN结构中,输入图像的尺寸往往是固定的(如224x224像素),输出可以看做是一个固定维数的向量。SIPPNet在普通的CNN结构中加入了ROI池化层(ROI Pooling ),使得网络的输入图像可以是任意尺寸的,输出则不变,同样是一个固定维数的向量。ROI池化层一般跟在卷积层后面,它的输入是任意大小的卷积,输出是固定维数的向量。
ROI池化层
为了说清楚为什么ROI池化层能够把任意大小的卷积特征转换成固定长度的向量,不妨设卷积层输出的宽度为w,高度为h,通道为c。不管输入的图像尺寸是多少,卷积层的通道数都不会变,也就是说c是一个常数。而w、h会随着输入图像尺寸的变化而变化,可以看作是两个变量。
以上图中的ROI池化层为例,它首先把卷积层划分为4x4的网格,每个网格的宽是w/4、高是h/4、通道数为c。当不能整除时,需要取整。接着,对每个网格中的每个通道,都取出其最大值,换句话说,就是对每个网格内的特征做最大值池化(Max Pooling )。这个4x4的网格最终就形成了16c维的特征。接着,再把网络划分成2x2的网格,用同样的方法提取特征,提取的特征的长度为4c。再把网络划分为1x1的网格,提取的特征的长度就是c,最后的1x1的划分实际是取出卷积中每个通道的最大值。最后,将得到的特征拼接起来,得到的特征是16c+4c+c = 21c维的特征。很显然,这个输出特征的长度与w, h两个值是无关的,因此ROI池化层可以把任意宽度、高度的卷积特征转换为固定长度的向量。
应该怎么把ROI池化层用到目标检测中来呢,其实,可以这样考虑该问题:网络的输入是一张图像,中间经过若干卷积形成了卷积特征,这个卷积特征实际上和原始图像在位置上是有一定对应关系的。原始图像的目标会使得卷积特征在同样位置产生激活。因此,原始图像中的候选框,实际上也可以对应到卷积特征中相同位置的框。由于候选框的大小千变万化,对应到卷积特征的区域形状也各有不同,但是不用担心利用ROI池化层可以把卷积特征中的不同形状的区域对应到同样长度的向量特征。综合上述步骤,就可以将原始图像中的不同长宽的区域都对应到一个固定长度的向量特征,这就完成了各个区域的特征提取工作。
在R-CNN中,对于原始图像的各种候选区域框,必须把框中的图像缩放到统一大小,再对每一张缩放后的图片提取特征。使用ROI池化层后,就可以先对图像进行一遍卷积计算,得到整个图像的卷积特征;接着,对于原始图像中的各种候选框,只需要在卷积特征中找到对应的位置框,再使用ROI池化层对位置框中的卷积提取特征,就可以完成特征提取工作。
R-CNN和SPPNet的不同点在于,R-CNN要对每个区域计算卷积,而SPPNet只需要计算一次,因此SPPNet的效率比R-CNN高得多。
R-CNN和SPPNet的相同点在于,它们都遵循着提取候选框、提取特征、分类几个步骤。在提取特征后,它们都使用了SVM进行分类。
Fast R-CNN的原理
在SPPNet中,实际上特征提取和区域分类两个步掇还是分离的。只是使用ROI池化层提取了每个区域的特征,在对这些区域分类时,还是使用传统的SVM作为分类器。Fast R-CNN相比SPPNet更进一步,不再使用SVM作为分类器,而是使用神经网络进行分类,这样就可以同时训练特征提取网络和分类网络,从而取得比SPPNet更高的准确度。
对于原始图片中的候选框区域,和SPPNet中的做法一样,都是将它映射到卷积特征的对应区域,然后使用ROI池化层对该区域提取特征。在这之后,SPPNet是使用SVM对特征进行分类,而Fast R-CNN则是直接使用全连接层。全连接层有两个输出,一个输出负责分类,另一个输出负责框回归。
先说分类,假设要在图像中检测K类物体,那么最终的输出应该是K+l个数,每个数都代表该区域为某个类别的概率。之所以是K+1个输出而不是K个输出,是因为还需要一类“背景类”,针对该区域无目标物体的情况。
Fast R-CNN与SPPNet最大的区别就在于,Fast R-CNN不再使用SVM进行分类,而是使用一个网络同时完成了提取特征、判断类别、框回归三项工作。
Faster R-CNN的原理
Fast R-CNN看似很完美了,但在Fast R-CNN中还存在着一个有点尴尬的问题:它需要先使用Selective Search提取框,这个方法比较慢,有时检测一张图片,大部分时间不是花在计算神经网络分类上,而是花在Selective Search提取框上。在Fast R-CNN升级版Faster R-CNN中,用RPN网络(Region Proposal Network)取代了Selective Search,不仅速度得到大大提高而且还获得了更加精确的结果。
RPN还是需要先使用一个CNN网络对原始图片提取特征。为了方便读者理解,不妨设这个前置的CNN提取的特征为51 x39x256,即高为51、宽39、通道数为256。对这个卷积特征再进行一次卷积计算,保持宽、高、通道不变,再次得到一个51x39x256的特征。为了方便叙述,先来定义一个“位置”的概念:对于一个51x39x256的卷积特征,称它一共有51x39个“位置”。让新的卷积特征的每一个“位置”都“负责”原图中对应位置9种尺寸的框的检测,检测的目标是判断框中是否存在一个物体,因此共有51x39x9个“框”。在Faster R-CNN的原论文中,将这些框都统一称为“anchor".
ancho:的9种尺寸,它们的面积分别128128, 256256, 512*512。每种面积又分为3种长宽比,分别是2:1, 1:2, 1:1。 anchor的尺寸实际是属于可调的参数,不同任务可以选择不同的尺寸。
对于这51x39个位置和51x39x9个anchor,下图展示了接下来每个位置的计算步骤。设k为单个位置对应的ancho:的个数,此时k=9。首先使用一个3x3的滑动窗口,将每个位置转换为一个统一的256维的特征,这个特征对应了两部分的输出。一部分表示该位置的anchor为物体的概率,这部分的总输出长度为2xk(一个anchor对应两个输出:是物体的概率+不是物体的概率)。另一部分为框回归,框回归的含义与Fast R-CNN中一样。一个anchor对应4个框回归参数,因此框回归部分的总输出的长度为4xk。
Faster R-CNN使用RPN生成候选框后,剩下的网络结构和Fast R-CNN中的结构一模一样。在训练过程中,需要训练两个网络,一个是RPN网络一个是在得到框之后使用的分类网络。通常的做法是交替训练,即在一个batch内,先训练RPN网络一次,再训练分类网络一次。
R-CNN(Selective Search + CNN + SVM)
SPP-net(ROI Pooling)
Fast R-CNN(Selective Search + CNN + ROI)
Faster R-CNN(RPN + CNN + ROI)
这里最大的贡献就是RPN。因为我看后面的ssd,yolo都有rpn的影子。
rpn --> Region Proposal Networks
经典的检测方法生成检测框都非常耗时,如OpenCV adaboost使用滑动窗口+图像金字塔生成检测框;或如R-CNN使用SS(Selective Search)方法生成检测框。而Faster RCNN则抛弃了传统的滑动窗口和SS方法,直接使用RPN生成检测框,这也是Faster R-CNN的巨大优势,能极大提升检测框的生成速度。
上图4展示了RPN网络的具体结构。可以看到RPN网络实际分为2条线,上面一条通过softmax分类anchors获得positive和negative分类,下面一条用于计算对于anchors的bounding box regression偏移量,以获得精确的proposal。而最后的Proposal层则负责综合positive anchors和对应bounding box regression偏移量获取proposals,同时剔除太小和超出边界的proposals。其实整个网络到了Proposal Layer这里,就完成了相当于目标定位的功能。
softmax判定positive与negative
一副MxN大小的矩阵送入Faster RCNN网络后,到RPN网络变为(M/16)x(N/16),不妨设 W=M/16,H=N/16。在进入reshape与softmax之前,先做了1x1卷积,如图9:
该1x1卷积的caffe prototxt定义如下:
layer {
name: "rpn_cls_score"
type: "Convolution"
bottom: "rpn/output"
top: "rpn_cls_score"
convolution_param {
num_output: 18 # 2(positive/negative) * 9(anchors)
kernel_size: 1 pad: 0 stride: 1
}
}
可以看到其num_output=18,也就是经过该卷积的输出图像为WxHx18大小(注意第二章开头提到的卷积计算方式)。这也就刚好对应了feature maps每一个点都有9个anchors,同时每个anchors又有可能是positive和negative,所有这些信息都保存WxHx(9*2)大小的矩阵。为何这样做?后面接softmax分类获得positive anchors,也就相当于初步提取了检测目标候选区域box(一般认为目标在positive anchors中)。
reshape是caffe需要的格式。
综上所述,RPN网络中利用anchors和softmax初步提取出positive anchors作为候选区域(另外也有实现用sigmoid代替softmax,输出[1, 1, 9xH, W]后接sigmoid进行positive/negative二分类,原理一样)。
bounding box regression原理
如图9所示绿色框为飞机的Ground Truth(GT),红色为提取的positive anchors,即便红色的框被分类器识别为飞机,但是由于红色的框定位不准,这张图相当于没有正确的检测出飞机。所以我们希望采用一种方法对红色的框进行微调,使得positive anchors和GT更加接近。
对于窗口一般使用四维向量 [公式] 表示,分别表示窗口的中心点坐标和宽高。对于图 11,红色的框A代表原始的positive Anchors,绿色的框G代表目标的GT,我们的目标是寻找一种关系,使得输入原始的anchor A经过映射得到一个跟真实窗口G更接近的回归窗口G',即:
对proposals进行bounding box regression
在了解bounding box regression后,再回头来看RPN网络第二条线路,如图12。
先来看一看上图11中1x1卷积的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
}
}