SPP-Net是一种可以不用考虑图像大小,输出图像固定长度网络结构,并且可以做到在图像变形情况下表现稳定。SSP-net的效果已经在不同的数据集上面得到验证,速度上比R-CNN快24-102倍。在ImageNet 2014的比赛中,此方法检测中第二,分类中第三。
简介SPP-Net是出自论文《Spatial Pyramid Pooling in Deep Convolutional Networks for Visual Recognition》。
在此之前,所有的神经网络都是需要输入固定尺寸的图片,比如224*224(ImageNet)、32*32(LenNet)、96*96等。这样对于我们希望检测各种大小的图片的时候,需要经过crop,或者warp等一系列操作,这都在一定程度上导致图片信息的丢失和变形,限制了识别精确度。而且,从生理学角度出发,人眼看到一个图片时,大脑会首先认为这是一个整体,而不会进行crop和warp,所以更有可能的是,我们的大脑通过搜集一些浅层的信息,在更深层才识别出这些任意形状的目标。
SPP-Net对这些网络中存在的缺点进行了改进,基本思想是,输入整张图像,提取出整张图像的特征图,然后利用空间关系从整张图像的特征图中,在spatial pyramid pooling layer提取各个region proposal的特征。
一个正常的深度网络由两部分组成,卷积部分和全连接部分,要求输入图像需要固定size的原因并不是卷积部分而是全连接部分。所以SPP层就作用在最后一层卷积之后,SPP层的输出就是固定大小。
SPP-net不仅允许测试的时候输入不同大小的图片,训练的时候也允许输入不同大小的图片,通过不同尺度的图片同时可以防止overfit。
相比于R-CNN提取2000个proposal,SPP-net只需要将整个图扔进去获取特征,这样操作速度提升了100倍左右。1
SPP Layer介绍
卷积层的参数和输入大小无关,它仅仅是一个卷积核在图像上滑动,不管输入图像多大都没关系,只是对不同大小的图片卷积出不同大小的特征图,但是全连接层的参数就和输入图像大小有关,因为它要把输入的所有像素点连接起来,需要指定输入层神经元个数和输出层神经元个数,所以需要规定输入的feature的大小。因此,固定长度的约束仅限于全连接层。 SPP-Net在最后一个卷积层后,接入了金字塔池化层,使用这种方式,可以让网络输入任意的图片,而且还会生成固定大小的输出。
从整体过程来看,就是如图二所示。黑色图片代表卷积之后的特征图,接着我们以不同大小的块来提取特征,分别是4*4,2*2,1*1,将这三张网格放到下面这张特征图上,就可以得到16+4+1=21种不同的块(Spatial bins),我们从这21个块中,每个块提取出一个特征,这样刚好就是我们要提取的21维特征向量。这种以不同的大小格子的组合方式来池化的过程就是空间金字塔池化(SPP)。比如,要进行空间金字塔最大池化,其实就是从这21个图片块中,分别计算每个块的最大值,从而得到一个输出单元,最终得到一个21维特征的输出。所以Conv5计算出的feature map也是任意大小的,经过SPP之后,就可以变成固定大小的输出了,以上图为例,一共可以输出(16+4+1)*256的特征。
总结而言,当网络输入的是一张任意大小的图片,这个时候我们可以一直进行卷积、池化,直到网络的倒数几层的时候,也就是我们即将与全连接层连接的时候,就要使用金字塔池化,使得任意大小的特征图都能够转换成固定大小的特征向量,这就是空间金字塔池化的意义(多尺度特征提取出固定大小的特征向量)。
训练过程作者提出两种训练方式:一种是single-size,一种是Multi-size。
single-size理论上说,SPP-net支持直接以多尺度的原始图片作为输入后直接BP即可。实际上,caffe等实现中,为了计算的方便,GPU,CUDA等比较适合固定尺寸的输入,所以训练的时候输入是固定了尺度了的。以224*224的输入为例:在conv5之后的特征图为:13x13(a*a),金字塔层bins:n*n,将pooling层作为slidingwindow pooling。
windows_size=[a/n]向上取整 ,stride_size=[a/n]向下取整。
Multi-size training使用两个尺度进行训练:224*224 和180*180。训练的时候,224x224的图片通过crop得到,180x180的图片通过缩放224x224的图片得到。之后,迭代训练,即用224的图片训练一个epoch,之后180的图片训练一个epoch,交替地进行。
两种尺度下,在SSP后,输出的特征维度都是(9+4+1)x256,参数是共享的,之后接全连接层即可。
SPP层代码分析代码中存放的是spp layer中的目标输出大小,代码中bins=[1,2,3],经过处理之后就可以得到对应的(1*1+2*2+3*3)*256=14*256=3584个神经元,即无论前面的feature map是多大的,经过spp layer处理之后得到固定大小的神经元,然后就可以和全连接层进行矩阵运算了。
import tensorflow as tfimport math class SPPLayer(): def __init__(self,bins,feature_map_size): self.strides = [] self.filters = []# print(type(feature_map_size)) self.a = float(feature_map_size) self.bins = bins self.n = len(bins) def spatial_pyramid_pooling(self,data): self.input = data self.batch_size = self.input.get_shape().as_list()[0] for i in range(self.n): x = int(math.floor(self.a/float(self.bins[i]))) self.strides.append(x) x = int (math.ceil(self.a/float(self.bins[i]))) self.filters.append(x) self.pooled_out = [] for i in range(self.n): self.pooled_out.append(tf.nn.max_pool(self.input, ksize=[1, self.filters[i], self.filters[i], 1], strides=[1, self.strides[i], self.strides[i], 1], padding='VALID')) for i in range(self.n): self.pooled_out[i] = tf.reshape(self.pooled_out[i], [self.batch_size, -1]) self.output = tf.concat(1, [self.pooled_out[0], self.pooled_out[1], self.pooled_out[2]]) return self.output测试阶段作者将SPP-Net测试效果与R-CNN对比。
对于R-CNN,整个过程是:
首先通过选择性搜索,对待检测的图片进行搜索出~2000个候选窗口。
把这2k个候选窗口的图片都缩放到227*227,然后分别输入CNN中,每个proposal提取出一个特征向量,也就是说利用CNN对每个proposal进行提取特征向量。
把上面每个候选窗口的对应特征向量,利用SVM算法进行分类识别。
可以看出R-CNN的计算量是非常大的,因为2k个候选窗口都要输入到CNN中,分别进行特征提取。
而对于SPP-Net,整个过程是:
首先通过选择性搜索,对待检测的图片进行搜索出2000个候选窗口。这一步和R-CNN一样。
特征提取阶段。这一步就是和R-CNN最大的区别了,这一步骤的具体操作如下:把整张待检测的图片,输入CNN中,进行一次性特征提取,得到feature maps,然后在feature maps中找到各个候选框的区域,再对各个候选框采用金字塔空间池化,提取出固定长度的特征向量。而R-CNN输入的是每个候选框,然后在进入CNN,因为SPP-Net只需要一次对整张图片进行特征提取,速度会大大提升。
最后一步也是和R-CNN一样,采用SVM算法进行特征向量分类识别。
检测算法对于检测算法,论文中是这样做到:使用ss生成~2k个候选框,缩放图像之后提取特征,每个候选框使用一个4层的空间金字塔池化特征,网络使用的是ZF-5的SPPNet形式。之后将12800d的特征输入全连接层,SVM的输入为全连接层的输出。
这个算法可以应用到多尺度的特征提取:先将图片resize到五个尺度:480,576,688,864,1200,加自己6个。然后在map window to feature map一步中,选择ROI框尺度在{6个尺度}中大小最接近224x224的那个尺度下的feature maps中提取对应的roi feature。这样做可以提高系统的准确率。
本词条内容贡献者为:
王慧维 - 副研究员 - 西南大学