前言
R-CNN是基于深度学习目标检测的开创之作,是带有CNN特征的区域(Region with CNN features)的缩写,它较之前传统目标检测方法在平均精度上(mAP)提高了30%以上。从本文开始将逐渐介绍R-CNN。
一、R-CNN结构
如图,R-CNN分为三步 :
1、生成候选区域(2000左右)
2、利用CNN对每个候选区域进行特征提取
3、利用SVM分类
二、生成候选区域
R-CNN生成候选区域的方法是使用Selective Search算法。论文地址如下:
http://www.huppelen.nl/publications/selectiveSearchDraft.pdf
在这对该算法进行简单介绍,
生成候选区域大致分为两种方法:
1)穷举搜索
自然而然,我们想到利用大小不同的滑窗遍历整张图片,寻找目标在图片上的位置。但显然这样计算量会很大,而且不会很准确的得到目标位置
2)分割
那能不能考虑目标自身特征呢?图像分割应运而生,它根据灰度、颜色、纹理和形状等图像特征把图像划分成若干互不交迭的区域,并使这些特征在同一区域内呈现出相似性,而在不同区域间呈现出明显的差异性。但是分割常常依赖于一种单一的强算法来识别区域。
而Selective Search结合两者优点生成候选区域。受分割启发,利用图像特征来生成目标位置。受穷举搜索启发,捕捉尽可能多的对象位置。同样,不是使用单一的采样特征,而是使采样特征多样化,以尽可能多地考虑各种图像情况。
结合图具体说明一下:
对于图(a)若使用穷举搜索,目标位置不会很准确,因为目标之间是分层级的,沙拉和勺子在碗中,碗又在桌上。最自然的解决方法是使用分层分区进行分割,但使用一种单一的强算法是行不通的。如在图(b)中两只猫纹理相同,颜色不同。而在图(c)中变色龙和周围颜色相同,而纹理不同。最后在图(d)中,车身和轮胎颜色、纹理都不同,但显然都属于车。而Selective Search算法结合两者优点,先利用分层分区形成初始化很多分割区域,再利用贪心算法,根据多种特征计算区域间相似性,过程不断重复,直至形成一个区域。
文章通过互补的色彩空间转换、互补的相似性计算方式,互补的初始化区域来保证特征的多样性。
互补的相似性计算公式如下:
- 颜色距离
根据色彩空间,计算颜色的距离。
距离的计算方式很简单,就是对各个通道计算颜色直方图,然后取各个对应bins的直方图最小值。这样做的话两个区域合并后的直方图也很好计算,直接通过直方图大小加权区域大小然后除以总区域大小就好了。
2.纹理距离
纹理距离计算方式和颜色距离几乎一样,我们计算每个区域的快速sift特征,其中方向个数为8,3个通道,每个通道bins为10,对于每幅图像得到240维的纹理直方图,然后通过上式计算距离。
3.优先合并小的区域
如果仅仅是通过颜色和纹理特征合并的话,很容易使得合并后的区域不断吞并周围的区域,后果就是多尺度只应用在了那个局部,而不是全局的多尺度。因此我们给小的区域更多的权重,这样保证在图像每个位置都是多尺度的在合并。
4.区域的合适度距离
不仅要考虑每个区域特征的吻合程度,区域的吻合度也是重要的,吻合度的意思是合并后的区域要尽量规范,不能合并后出现断崖的区域,这样明显不符合常识,体现出来就是区域的外接矩形的重合面积要大。因此区域的合适度距离定义为:
5.综合各种距离
现在各种距离都计算出来,我们要做的就是整合这些距离,通过多种策略去得到区域建议,最简单的方法当然是加权:
整体算法如下:
OpenCV实现:
import cv2
if __name__ == '__main__':
# 读取照片
filename = 'longmao.png'
im = cv2.imread(filename)
# 重塑照片大小
newHeight = 200
newWidth = int( im.shape[1] * 200 / im.shape[0] )
im = cv2.resize( im, (newWidth, newHeight) )
# 使用Selective Search算法
ss = cv2.ximgproc.segmentation.createSelectiveSearchSegmentation()
ss.setBaseImage(im)
ss.switchToSelectiveSearchFast()
rects = ss.process()
#总共的候选区域
print( 'Total Number of Region Proposals: {}'.format( len( rects ) ) )
#设定指定边界框数
numShowRects = 100
#增加或减少额定的边界框数
increment = 50
while True:
imOut = im.copy()
# 迭代边界框
for i, rect in enumerate( rects ):
#画出numShowRects数量的边界框
if (i < numShowRects):
x, y, w, h = rect
cv2.rectangle(imOut, (x, y), (x + w, y + h), (0, 255, 0), 1, cv2.LINE_AA )
else:
break
cv2.imshow('Output',imOut)
k = cv2.waitKey(0)
# m键增加边框数
if k == 109:
numShowRects += increment
# l键减少边框数
elif k == 108 and numShowRects > increment:
numShowRects -= increment
# q键退出
elif k == 113:
break
cv2.destroyAllWindows()
效果如下:
特征提取
R-CNN利用Alexnet网络对候选区域进行特征提取,注意到Alexnet的输入图像大小为227*227,如上图所示,我们生成的候选区域大小是不一样的。在论文中,采用简单粗暴的方法,将所有候选区域统一重塑为227*227。当然这样做肯定会丢失很多图像信息,论文中有这样一个小技巧:区域进行裁剪前,首先对这些区域进行膨胀处理,在其周围附加了 p 个像素,也就是人为添加了边框,在这里 p=16。还有一个问题是,目标标签训练数据少,如果要直接采用随机初始化CNN参数的方法,那么目前的训练数据量是远远不够的,所以会直接采用训练好的网络参数作为初始化参数,再用我们的数据集进行fine-tuning。fine-tuning时只需去掉最后的softmax层,会生成4096维的特征向量
SVM训练
得到特征向量后,我们需要训练SVM。为何要训练SVM呢?直接用CNN的softmax不好吗?因为cnn在训练的时候,对训练数据做了比较宽松的标注,比如一个bounding box可能只包含物体的一部分,那么就把它标注为正样本,用于训练cnn;采用这个方法的主要原因在于因为CNN容易过拟合,所以需要大量的训练数据,所以在CNN训练阶段我们是对Bounding box的位置限制条件限制的比较松(比如IOU只要大于0.5都被标注为正样本了);然而svm训练的时候,因为svm适用于少样本训练,所以对于训练样本数据的IOU要求比较严格,我们只有当bounding box把整个物体都包含进去了,我们才把它标注为物体类别,然后训练svm
假设我们要检测车辆。我们知道只有当bounding box把整量车都包含在内,那才叫正样本;如果bounding box 没有包含到车辆,那么我们就可以把它当做负样本。但问题是当我们的检测窗口只有部分包好物体,那该怎么定义正负样本呢?作者测试了IOU阈值各种方案数值0,0.1,0.2,0.3,0.4,0.5。最后通过训练发现,如果选择IOU阈值为0.3效果最好(选择为0精度下降了4个百分点,选择0.5精度下降了5个百分点),即当重叠度小于0.3的时候,我们就把它标注为负样本。一旦CNN f7层特征被提取出来,那么我们将为每个物体训练一个svm分类器。当我们用CNN提取2000个候选框,可以得到2000*4096这样的特征向量矩阵,然后我们只需要把这样的一个矩阵与svm权值矩阵4096*N点乘(N为分类类别数目,因为我们训练的N个svm,每个svm包好了4096个W),就可以得到结果了。
具体代码实现可以参考:
https://github.com/yangxue0827/RCNN
总结
利用Selective Search提取候选框,然后利用CNN进行特征提取,最后训练SVM得出结果。可见R-CNN过程繁琐,而且非常耗时。下节将要介绍对R-CNN的改进算法,spp-net.
参考:
https://blog.csdn.net/hjimce/article/details/50187029