在了解了RCNN之后,我们知道RCNN 在经过region proposal产生候选框之后,需要将每一个候选框图片输入CNN网络提取特征,比如一张图片会经seletive search会产生2000个候选区域,那么我们就需要进行2000次的特征提取,这样极大的影响了效率。另外由于Alex CNN网络具有固定的输入尺寸,但是实际我们产生的候选区域的尺寸确实多样的,这样我们在输入网络提取特征之前还需要对候选区域进行crop/warp操作,使其尺寸一致。但是这些操作又可能使图形畸变,从而影响结果。我们知道cnn网络之所以需要固定的输入主要是因为最后的全连接层的输入神经元的数目必须是固定的。那么我们可以这样做,将最后一层卷积层后面的池化层做个修正,使其能够满足这样的功能,即不能输入尺寸尺寸如何均能有相同尺寸的输出。这样就算我们卷积网络的输入尺寸不一样,在输入全连接层的时候,仍能达到相同的尺寸。而是实现这个功能的池化层就是SPPNET。下图就是SPPNET的示意图。
大致流程:
1. 首先通过选择性搜索(selective search),对待检测的图片进行搜索出2000个候选窗口。
2.特征提取阶段。把整张待检测的图片,输入CNN中,进行一次性特征提取,得到feature maps,然后在feature maps中找到各个候选框的区域,再对各个候选框采用金字塔空间池化,提取出固定长度的特征向量。而R-CNN输入的是每个候选框,然后在进入CNN,因为SPP-Net只需要一次对整张图片进行特征提取,这个可以大大的提高虚度。因为R-CNN就相当于遍历一个CNN两千次,而SPP-Net只需要遍历1次。最后采用SVM算法进行特征向量分类识别,和R-CNN一样。
![](https://i-blog.csdnimg.cn/blog_migrate/32fafdea2bea1a21d5714d3a4261db71.png)
注意事项:
1):
![](https://i-blog.csdnimg.cn/blog_migrate/09961936ccd60f1b35c3154aa893e63e.png)
2)SPPNET中计算维度用的公式
![](https://i-blog.csdnimg.cn/blog_migrate/04e0f2d7c8833c203ea3b30348970a97.png)
![](https://i-blog.csdnimg.cn/blog_migrate/c92b92a672012431e3b0c30cf6567df4.png)
假设输入数据大小和上面一样是(10,7,11)(10,7,11), 但是池化数量改为(4,4)(4,4):
此时核大小为(2,3)(2,3), 步长大小为(1,2)(1,2),得到池化后的矩阵大小的确是6∗56∗5 ←[简单的计算矩阵大小的方法:(7=2+1*5, 11=3+2*4)],而不是4∗44∗4。
那么问题出在哪呢?我们忽略了padding的存在
![](https://i-blog.csdnimg.cn/blog_migrate/6776701fc86eb885e2e3c576ab82d18a.png)
现在再来检验一下:
假设输入数据大小和上面一样是(10,7,11), 池化数量为(4,4):
Kernel大小为(2,3),Stride大小为(2,3),所以Padding为(1,1)。
利用矩阵大小计算公式: 得到池化后的矩阵大小为: 4∗44∗4 。
python代码实现(pyTorch)
#coding=utf-8
import math
import torch
import torch.nn.functional as F
# 构建SPP层(空间金字塔池化层)
class SPPLayer(torch.nn.Module):
def __init__(self, num_levels, pool_type='max_pool'):
super(SPPLayer, self).__init__()
self.num_levels = num_levels
self.pool_type = pool_type
def forward(self, x):
num, c, h, w = x.size() # num:样本数量 c:通道数 h:高 w:宽
for i in range(self.num_levels):
level = i+1
kernel_size = (math.ceil(h / level), math.ceil(w / level))
stride = (math.ceil(h / level), math.ceil(w / level))
pooling = (math.floor((kernel_size[0]*level-h+1)/2), math.floor((kernel_size[1]*level-w+1)/2))
# 选择池化方式
if self.pool_type == 'max_pool':
tensor = F.max_pool2d(x, kernel_size=kernel_size, stride=stride, padding=pooling).view(num, -1)
else:
tensor = F.avg_pool2d(x, kernel_size=kernel_size, stride=stride, padding=pooling).view(num, -1)
# 展开、拼接
if (i == 0):
x_flatten = tensor.view(num, -1)
else:
x_flatten = torch.cat((x_flatten, tensor.view(num, -1)), 1)
return x_flatten