转载请联系: https://blog.csdn.net/u011974639/article/details/78053203
物体检测
物体检测是图像处理学科的一个重要难题。再探讨物体检测之前,我们先说一下图片分类问题,在前面的blog中我们讲解了AlexNet,VGGNet等深度卷积网络。因为有着ImageNet这样的大规模高质量的数据集,在图像分类问题下深度神经网络表现出极好的效果,但是ImageNet的另一项比赛-物体检测定位Task,成绩一直不是很好,也没有什么大的起伏。这种现象直到R-CNN的出现。
图片分类和物件检测之间有很多联系,但是我觉得物体检测比图片分类更难,而且高质量的数据集更少。
图片分类和物体检测的区别
输出不同
正如下图,分类任务和检测任务在输入和输出上的不同:
任务 | description |
---|---|
分类(Classification) | 在固定的类别下对一张图片进行分类 输入:图片 输出:类别标签 评价标准:准确率(top1,top5) |
Detection+Localization(检测和定位) | 在输入图片中找出存在的物体类别和位置.(可能存在多种物体) 输入:图片 输出:类别标签和bbox(x,y,w,h) 评价标准: IoU(Intersection over Union) mAP(Mean Average Precision) |
检测的目标不同
物体检测可能会存在多个检测目标,这不仅需要我们判别出各个物体的类别,而且还要准确定位出物体的位置
下面图片上有猫,有狗,还有小黄鸭,这是多物体检测:
物体检测算法常用到的概念
下面我们讲解一下在物体检测算法中常用到的几个概念:Bbox,IoU,非极大值抑制。
Bounding Box(bbox)
bbox是包含物体的最小矩形,该物体应在最小矩形内部.
下图的包围小猫的红色矩形框:
这里注意到物体检测中关于物体位置的信息输出是一组(x,y,w,h)(x,y,w,h)可以唯一的确定一个定位框。
Intersection over Union(IoU)
对于两个区域RR
在训练网络的时候,我们常依据侯选区域和标定区域的IoU值来确定正负样本。
非极大值抑制 (Non-Maximum Suppression)
非极大值抑制(NMS)可以看做是局部最大值的搜索问题,NMS是许多计算机视觉算法的部分。如何设计高效的NMS算法对许多应用是十分关键的,例如视频跟踪、数据挖掘、3D重建、物体识别以及纹理分析等。
这里我们主要针对非极大值抑制在物体检测上的应用,非极大值抑制就是把不是极大值的抑制掉,在物体检测上,就是对一个目标有多个标定框,使用极大值抑制算法滤掉多余的标定框。
下图一个小猫有多个红框标定:
我们使用非极大值抑制算法后:
算法的实现可以看Matlab下的非极大值抑制实现。
R-CNN
R-CNN经典论文《Rich feature hierarchies for Accurate Object Detection and Segmentation
》。
RGB大神的(Ross Girshick)这篇paper,改变了图像领域检测物体的实现思路,R-CNN是以深度学习为基础的物体检测的模型,以R-CNN为基点,后续的SPPNet、Fast R-CNN、Faster R-CNN模型都是照着这个思路,下面就来细致学习R-CNN模型。
在R-CNN模型出现之前,较好的物体检测模型DPM使用了一堆传统的算法模型,在VOC数据集上表现大约在30mAP左右。R-CNN开创性的提出了用深度神经网络来做物体检测。并且提出了一系列在较少标注集下的训练模型方法。
Introduction
R-CNN在物体检测上的解决方案
R-CNN模型的处理流程:
- 找出图片中可能存在目标的侯选区域
- 通过CNN对候选区域提取特征向量
- 在候选区域的特征向量上训练分类器,分类器用于判别物体并得到bbox
- 修正bbox,对bbox做回归微调
下面就来一个一个步骤讲解~
找出图片中可能存在目标的侯选区域
一张图片上存在的物体,大小/位置不固定,如果我们用滑窗的方法去寻找可能存在物体,工作量非常大,且很难实现。R-CNN模型使用的方法是先使用“传统成熟”的方法找出一组图像中可能存在目标的侯选区域(region proposals),产生侯选区域的方案可减少在一张图片上寻找物体的复杂度,且很大可能的保存了图片上所有存在物体的区域,这一方案发展的较为成熟,实现起来较为简便。
常用的产生侯选区域的方法:
R-CNN最终选择的产生候选区域产生的方法是selective search,
Selective Search
selective search的实现过程详情参考论文Selective Search for Object Recognition。
下图是selective search在图片上提取侯选区域的过程:
Selective Search在一张图片上提取出来大概2000个侯选区域,需要注意的是这些候选区域的长宽不固定。而在下一层使用CNN提取特征向量时,需要接受固定长度的输入,故我们需要对候选区域做一些长度上的修改。
论文对图片做了两种方法的比较:
- 各向异性缩放,即直接缩放到指定大小,这可能会造成不必要的图像失真
- 各向同性缩放,在原图上出裁剪侯选区域,在边界用固定的背景颜色(采用侯选区域的像素颜色均值)填充到指定大小
经过一系列的实验,作者发现采用各向异性缩放的实验精度最高,就采用这个方法了。
通过CNN对候选区域提取特征向量
用CNN的作用是在侯选区域的基础上提取出更高级、更抽象的特征,高级特征的作用是为下一步的分类器作为输入数据,分类器依据高级特征回归出物品的位置和种类。
在讲到CNN用作分类问题时,CNN的多个卷积层可以宽泛的认为是对原图像的特征提取,并且这样的特征提取具有平移不变性。我们把CNN当做特征提取的模板,把需要提取特征的图片塞给它,训练好我们需要的CNN,就可以获取到我们想要的特征向量。
网络的训练
CNN的训练分为以下几个过程:
有监督的预训练
一般的CNN模型层数多,模型的容量大,在标定数据少的情况下,这样的数据量是不够从新训练一个CNN模型的。故我们采用已训练好的AlexNet/VGG16模型的卷积层参数,使用这样已训练好的网络参数,可以较好的提取图片的特征。这样的操作有一个专业的名词-迁移学习(这里不做讨论)。
fine-tuning
AlexNet是针对ImageNet训练出来的模型,AlexNet的卷积部分可以作为一个好的特征提取器,后面的全连接层可以理解为一个好的分类器。这里把AlexNet的softmax层替换为一个N+1神经元的输出层(N为存在物体的种类,即正样本;1为背景,即负样本)。然后做微调训练。
关于fine-tuning的训练细节
原本ImageNet的输出类别有1000个,这里把1000个替换为21个(N=20,1为背景).
使用的SGD,每个mini-batch取128.样本 数量 描述 正样本 32 候选区域与ground-truth(图片物体标注区域)的IoU>0.5 负样本 96 候选区域与ground-truth(图片物体标注区域)的IoU<0.5 这里取IoU的阈值为0.5是因为CNN模型容量大,需要的数据多,故放宽限制,获取到更多的数据,防止模型过拟合。
需要注意的是,我们在训练CNN的时候会在网络的后面加上一个分类层,在训练完毕后,我们会移除最后的分类层,直接提取到前面的FC层,AlexNet的FC层为4096维。
对于一张图片,使用训练好的CNN基础上,将所有的图片的所有侯选区域塞到CNN里面,把得到的pool5 feature存到硬盘里面(这里一存,后面训练一取,非常耗费时间)
在候选区域的特征向量上训练分类器
前面的CNN在侯选区域上提取出了特征向量,例如2000个侯选区域,那么提取出来的就是2000*4096这样的特征向量(AlexNet的第一个FC层维度为4096,故pool5的输出为4096)。用这些特征向量训练同时训练N个二分类的SVM,SVM的权重矩阵为4096xN(N为分类种类)。
这里你可能会问什么要使用SVM?而不是CNN过FC层直接softmax出来得了?
这是因为我们在训练CNN的时候选的数据比较宽泛(选择样本不够准确),直接使用softmax输出的效果不是很好,而这里在特征向量的基础上,再训练一组SVM效果会比较棒。但是需要注意的是,在训练SVM的时候,我们使用的样本是有变换的。
训练SVM时使用的样本
样本 描述 正样本 候选区域与ground-truth(图片物体标注区域)的IoU>0.7 负样本 候选区域与ground-truth(图片物体标注区域)的IoU<0.3 这个0.7的阈值是做实验得出来的经验值。
在经过SVM分类后,会输出一堆的候选框得分(是一个2000x20的得分矩阵),这时候我们需要用的非极大值抑制得到想要的候选框了.大概步骤如下:
- 对矩阵按列从大到小排序
- 每列的最大值向下做非极大值抑制,遍历完所有列
- 依据阈值,得到候选区域的类型
注意到这里,我们得到的是一组预测好类别的候选区域了。
修正bbox,对bbox做回归微调
我们使用一个简单的bounding-box回归用于提高定位的表现。这个bbox回归应用在SVM分类器给每个候选区域打分后,bbox回归认为候选区域和ground-truth之间是线性关系(因为在最后从SVM内确定出来的区域比较接近ground-truth,这里近似认为可以线性关系)
Bounding-box regression(边框回归详解)
从候选框P到预测框G^G^的平移、放缩思路是:
先做平移(Δx,Δy)(Δx,Δy)
即R-CNN论文里面的:
Gx^=Pwdx(P)+Px , Gy^=Phdy(P)+PyGx^=Pwdx(P)+Px , Gy^=Phdy(P)+PyGw^=Pwedw(P) , Gh^=Phedh(P)Gw^=Pwedw(P) , Gh^=Phedh(P)这四个变换.
输入:
样本值: 这里输入不是Proposal的P=(Px,Py,Pw,Ph)P=(Px,Py,Pw,Ph)
目标函数表示为:
d∗(P)=wT∗Θ5(P)d∗(P)=w∗TΘ5(P)故损失函数为:(带正则化)
Loss=argminw∗^∑i=0N(ti∗−w∗^TΘ5(Pi))2+λ||w∗^||2Loss=argminw∗^∑i=0N(t∗i−w∗^TΘ5(Pi))2+λ||w∗^||2注意:只有当Proposal和Ground Truth比较接近时(线性问题),我们才能将其作为训练样本训练我们的线性回归模型,否则会导致训练的回归模型不work(当Proposal跟G离得较远,就是复杂的非线性问题了,此时用线性回归建模显然不合理)
到这里,我们算是把R-CNN整个流程讲完了,后面还有一堆R-CNN的进化版~
SPPNet
SPPNet的论文《Spatial Pyramid Pooling in Deep Convolutional
Networks for Visual Recognition》。Kaiming男神的(Kaiming He)的这篇paper,是在R-CNN的基础上提出了空间金字塔变换层(Spatial Pyramid Pooling),SPPNet大幅度提高了R-CNN的训练速度和测试速度,同时算法的精度也上升了,后续的Fast R-CNN、Faster R-CNN模型也是以这个思路,下面就来细致学习SPPNet模型。
Introduction
SPPNet主要针对两点提出改进:
- CNN的固定输入大小,导致不必要的精度损失
- R-CNN模型候选区域在CNN内的重复计算,造成的计算冗余
为什么CNN需要固定大小的输入?
因为一个CNN一般分为两个部分,前面的部分是卷积层,后面的部分是FC层,卷积层不要求固定大小的输入;但是FC层在设计时就固定了神经元的个数,故需要固定长度的输入。这也就是CNN需要固定输入的问题所在。
R-CNN模型 SPPNet模型 将侯选区域送到CNN里面提取特征向量时,因为CNN的输入图像需要固定大小,而候选区域的长宽都是不固定的,故需要对候选区域做填充到固定大小,当我们对侯选区域做cropping或者warping操作,可能会让图片不完整包含物体,一些我们不想见到的几何失真,这都会造成识别精度损失。 SPPNet的解决办法是使用“空间金字塔变换层”将接收任意大小的图像输入,输出固定长度的输出向量,这样就能让SPPNet可接受任意大小的输入图片,不需要对图像做crop/wrap操作。 R-CNN模型候选区域在CNN内的重复计算
R-CNN模型 SPPNet模型 在R-CNN中,每个候选区域都要塞到CNN内提取特征向量,一张图片有2000个候选区域,也就是一张图片需要经过2000次CNN的前向传播,这2000重复计算过程会有大量的计算冗余,耗费大量的时间。 SPPNet提出了一种从候选区域到全图的特征映射(feature map)之间的对应关系,通过此种映射关系可以直接获取到候选区域的特征向量,不需要重复使用CNN提取特征,从而大幅度缩短训练时间。 我们通俗的理解一下R-CNN和SPPNet的实现过程:
R-CNN模型 SPPNet模型 R-CNN是让每个候选区域经过crop/wrap等操作变换成固定大小的图像
固定大小的图像塞给CNN
CNN输出获得固定大小的feature map
feature map传给后面的层做训练回归分类操作。
这里每个候选区域是需要单独过一下CNN,2000个候选区域过2000次CNN,耗费时间啊SPPNet让一张图片完整的塞给CNN得到全图的feature map
让2000个候选区域与feature map直接映射,直接得到2000个候选区域的特征向量(这里不需要再过CNN了).
但是候选区域的特征向量大小不固定,所以把候选区域的特征向量塞给SPP层,SPP层输入接收任何大小,输出固定大小的特征向量
经过这样映射+SPP转换,简化了计算,不但速度上去了,精确度也上去了看完上面的介绍,可以定位这篇论文的两个难点了。
- SPPNet怎么就能把候选区域从全图的feature map 直接整出来特征向量了?
- 空间金字塔变换层怎么可以接收任意大小的输入,输出固定的向量?
空间金字塔变换层
首先我们要连接一下Spatial Pyramid Pooling,关于Spatial Pyramid Pooling详细可参考论文
Beyond bags of features: Spatial pyramid matching for recognizing natural scene categories[C]//CVPR 2006。空间金字塔变换层(Spatial Pyramid Pooling,SPP)可以对图片提取特征,例如下图就是1*1,2*2,4*4大小的bin块,将一张图片以三种方式切割并提取特征,这样我们可以得到一共1+4+16=21种特征,这就是以不同的大小的bin块来提取特征的过程就是空间金字塔变换。
将SPP应用在不同大小的侯选区域候选区域特征变换:
上图spp layer分成1x1(塔底),2x2(塔中),4x4(塔顶)三张子图,对每个子图的每个区域作max pooling(论文使用的),出来的特征再连接到一起,就是(16+4+1)x256的特征向量。
无论输入图像大小如何,出来的特征固定是(16+4+1)x256维度。这样就实现了不管图像尺寸如何,SPP层的输出永远是(16+4+1)x256特征向量。
把候选区域从全图的feature map映射出特征向量
这部分在论文的Appendix A里面有介绍,看了后非常的迷茫。
网上查了一堆资料。这里引用知乎-晓雷机器学习笔记。machineLearning-blog.
并添加一些自己的理解。感受野的计算
感受野我们在CNN里面讲过关于卷积参数计算。
在CNN中感受野(receptive fields)是指某一层输出结果中一个元素所对应的上一层的区域大小.
在卷积参数计算过程中,我们给出上一层/下一层/滤波器尺寸、步长和填充大小的关系:
类型 大小 输入尺寸 W1×H1W1×H1 关系式如下:
W2=W1−F+2PS+1 , H2=H1−F+2PS+1W2=W1−F+2PS+1 , H2=H1−F+2PS+1这是上一层到下一层的推导,如果现在反过来,给你下一层的大小,问你上一层的感受野多大,该怎么计算?
我们把上面公式变个形式:
W1=(W2−1)S−2P+FW1=(W2−1)S−2P+F直观的感受一下从后向前计算感受野的过程.
考虑一下如何计算多层之间的感受野?
感受野计算时有下面的几个情况需要说明:
- 第一层卷积层的输出特征图像素的感受野的大小等于滤波器的大小
- 深层卷积层的感受野大小和它之前所有层的滤波器大小和步长有关系
- 计算感受野大小时,忽略了图像边缘的影响,即不考虑padding的大小
这里的每一个卷积层还有一个strides的概念,这个strides是之前所有层stride的乘积。
stridesi=stride1∗stride2...stridei−1stridesi=stride1∗stride2...stridei−1关于感受野大小的计算采用down to top的方式: 即先计算最深层在前一层上的感受野,然后逐渐传递到第一层,使用的公式可以表示如下:
RF = 1 #待计算的feature map上的感受野大小 for layer in (down layer To top layer): RF = ((RF -1)* stride) + fsize
- 1
- 2
- 3
例如我们计算AlexNet的第四层到第一层的感受野:
下面是一个程序计算了AlexNet、VGG16、ZF-5的输入输出尺寸的变化和感受野变化过程:
net_struct = {'alexnet': {'net':[[11,4,0],[3,2,0],[5,1,2],[3,2,0],[3,1,1],[3,1,1],[3,1,1],[3,2,0]], 'name':['conv1','pool1','conv2','pool2','conv3','conv4','conv5','pool5']}, 'vgg16': {'net':[[3,1,1],[3,1,1],[2,2,0],[3,1,1],[3,1,1],[2,2,0],[3,1,1],[3,1,1],[3,1,1], [2,2,0],[3,1,1],[3,1,1],[3,1,1],[2,2,0],[3,1,1],[3,1,1],[3,1,1],[2,2,0]], 'name':['conv1_1','conv1_2','pool1','conv2_1','conv2_2','pool2','conv3_1','conv3_2', 'conv3_3', 'pool3','conv4_1','conv4_2','conv4_3','pool4','conv5_1','conv5_2','conv5_3','pool5']}, 'zf-5':{'net': [[7,2,3],[3,2,1],[5,2,2],[3,2,1],[3,1,1],[3,1,1],[3,1,1]], 'name': ['conv1','pool1','conv2','pool2','conv3','conv4','conv5']}} imsize = 224 def outFromIn(isz, net, layernum):#从前向后算输出维度 totstride = 1 insize = isz for layer in range(layernum): fsize, stride, pad = net[layer] outsize = (insize - fsize + 2*pad) / stride + 1 insize = outsize totstride = totstride * stride return outsize, totstride def inFromOut(net, layernum):#从后向前算感受野 返回该层元素在原始图片中的感受野 RF = 1 for layer in reversed(range(layernum)): fsize, stride, pad = net[layer] RF = ((RF -1)* stride) + fsize return RF if __name__ == '__main__': print("layer output sizes given image = %dx%d" % (imsize, imsize)) for net in net_struct.keys(): print( '************net structrue name is %s**************'% net) for i in range(len(net_struct[net]['net'])): p = outFromIn(imsize,net_struct[net]['net'], i+1) rf = inFromOut(net_struct[net]['net'], i+1) print( "Layer Name = %s, Output size = %3d, Stride = % 3d, RF size = %3d" % (net_struct[net]['name'][i], p[0], p[1], rf))
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
基于感受野的特征映射
通常,我们需要知道网络里面任意两个feature map之间的坐标映射关系(一般是中心点之间的映射),如下图,我们想得到map3上的点p3映射回map2所在的位置p2(橙色框的中心点)
计算公式:
- 对于 Conv/Pool layer: pi=si⋅pi+1+((ki−1)/2−padding)pi=si⋅pi+1+((ki−1)/2−padding)
上面是计算任意一个layer输入输出的坐标映射关系,如果是计算任意feature map之间的关系,只需要用简单的组合就可以得到,下图是一个简单的例子:
下图是Kaiming He 在ICCV15上的汇报用的PPT,讲解如何求解Receptive Field:
A simple solution:
何凯明在SPP-net中采用的方法。巧妙的化简一下公式pi=si⋅pi+1+((ki−1)/2−padding)pi=si⋅pi+1+((ki−1)/2−padding)都为padding=⌊ki/2⌋⇒pi=si⋅pi