空间金字塔池化(Spatial Pyramid Pooling,SPP)

基于空间金字塔池化的卷积神经网络物体检测

原文地址:http://blog.csdn.net/hjimce/article/details/50187655

作者:hjimce

一、相关理论

本篇博文主要讲解大神何凯明2014年的paper:《Spatial Pyramid Pooling in Deep Convolutional Networks for Visual Recognition》,这篇paper主要的创新点在于提出了空间金字塔池化。paper主页:http://research.microsoft.com/en-us/um/people/kahe/eccv14sppnet/index.html  这个算法比R-CNN算法的速度快了n多倍。

我们知道在现有的CNN中,对于结构已经确定的网络,需要输入一张固定大小的图片,比如224*224,32*32,96*96等。这样对于我们希望检测各种大小的图片的时候,需要经过裁剪,或者缩放等一系列操作,这样往往会降低识别检测的精度,于是paper提出了“空间金字塔池化”方法,这个算法的牛逼之处,在于使得我们构建的网络,可以输入任意大小的图片,不需要经过裁剪缩放等操作,只要你喜欢,任意大小的图片都可以。不仅如此,这个算法用了以后,精度也会有所提高,总之一句话:牛逼哄哄。

空间金字塔池化,又称之为“SPP-Net”,记住这个名字,因为在以后的外文文献中,你会经常遇到,特别是物体检测方面的paper。这个就像什么:OverFeat、GoogleNet、R-CNN、AlexNet……为了方便,学完这篇paper之后,你就需要记住SPP-Net是什么东西了。空间金子塔以前在特征学习、特征表达的相关文献中,看到过几次这个算法。

既然之前的CNN要求输入固定大小的图片,那么我们首先需要知道为什么CNN需要输入固定大小的图片?CNN大体包含3部分,卷积、池化、全连接。

首先是卷积,卷积操作对图片输入的大小会有要求吗?比如一个5*5的卷积核,我输入的图片是30*81的大小,可以得到(26,77)大小的图片,并不会影响卷积操作。我输入600*500,它还是照样可以进行卷积,也就是卷积对图片输入大小没有要求,只要你喜欢,任意大小的图片进入,都可以进行卷积。

池化:池化对图片大小会有要求吗?比如我池化大小为(2,2)我输入一张30*40的,那么经过池化后可以得到15*20的图片。输入一张53*22大小的图片,经过池化后,我可以得到26*11大小的图片。因此池化这一步也没对图片大小有要求。只要你喜欢,输入任意大小的图片,都可以进行池化。

全连接层:既然池化和卷积都对输入图片大小没有要求,那么就只有全连接层对图片结果又要求了。因为全连接层我们的连接劝值矩阵的大小W,经过训练后,就是固定的大小了,比如我们从卷积到全连层,输入和输出的大小,分别是50、30个神经元,那么我们的权值矩阵(50,30)大小的矩阵了。因此空间金字塔池化,要解决的就是从卷积层到全连接层之间的一个过度。

也就是说在以后的文献中,一般空间金子塔池化层,都是放在卷积层到全连接层之间的一个网络层。

二、算法概述

OK,接着我们即将要讲解什么是空间金字塔池化。我们先从空间金字塔特征提取说起(这边先不考虑“池化”),空间金字塔是很久以前的一种特征提取方法,跟Sift、Hog等特征息息相关。为了简单起见,我们假设一个很简单两层网络:

输入层:一张任意大小的图片,假设其大小为(w,h)。

输出层:21个神经元。

也就是我们输入一张任意大小的特征图的时候,我们希望提取出21个特征。空间金字塔特征提取的过程如下:

图片尺度划分

如上图所示,当我们输入一张图片的时候,我们利用不同大小的刻度,对一张图片进行了划分。上面示意图中,利用了三种不同大小的刻度,对一张输入的图片进行了划分,最后总共可以得到16+4+1=21个块,我们即将从这21个块中,每个块提取出一个特征,这样刚好就是我们要提取的21维特征向量。

第一张图片,我们把一张完整的图片,分成了16个块,也就是每个块的大小就是(w/4,h/4);

第二张图片,划分了4个块,每个块的大小就是(w/2,h/2);

第三张图片,把一整张图片作为了一个块,也就是块的大小为(w,h)

空间金字塔最大池化的过程,其实就是从这21个图片块中,分别计算每个块的最大值,从而得到一个输出神经元。最后把一张任意大小的图片转换成了一个固定大小的21维特征(当然你可以设计其它维数的输出,增加金字塔的层数,或者改变划分网格的大小)。上面的三种不同刻度的划分,每一种刻度我们称之为:金字塔的一层,每一个图片块大小我们称之为:windows size了。如果你希望,金字塔的某一层输出n*n个特征,那么你就要用windows size大小为:(w/n,h/n)进行池化了。

当我们有很多层网络的时候,当网络输入的是一张任意大小的图片,这个时候我们可以一直进行卷积、池化,直到网络的倒数几层的时候,也就是我们即将与全连接层连接的时候,就要使用金字塔池化,使得任意大小的特征图都能够转换成固定大小的特征向量,这就是空间金字塔池化的奥义(多尺度特征提取出固定大小的特征向量)。具体的流程图如下:

三、算法源码实现

理论学的再多,终归要实践,实践是检验理论的唯一标准,caffe中有关于空间金字塔池化的源码,我这边就直接把它贴出来,以供学习使用,源码来自https://github.com/BVLC/caffe

//1、输入参数pyramid_level:表示金字塔的第几层。我们将对这一层,进行划分为2^n个图片块。金字塔从第0层开始算起,0层就是一整张图片

//第1层就是把图片划分为2*2个块,第2层把图片划分为4*4个块,以此类推……,也就是说我们块的大小就是[w/(2^n),h/(2^n)]

//2、参数bottom_w、bottom_h是我们要输入这一层网络的特征图的大小

//3、参数spp_param是设置我们要进行池化的方法,比如最大池化、均值池化、概率池化……

LayerParameter SPPLayer<Dtype>::GetPoolingParam(const int pyramid_level,

const int bottom_h, const int bottom_w, const SPPParameter spp_param)

{

LayerParameter pooling_param;

int num_bins = pow(2, pyramid_level);//计算可以划分多少个刻度,最后我们图片块的个数就是num_bins*num_bins

//计算垂直方向上可以划分多少个刻度,不足的用pad补齐。然后我们最后每个图片块的大小就是(kernel_w,kernel_h)

int kernel_h = ceil(bottom_h / static_cast<double>(num_bins));//向上取整。采用pad补齐,pad的像素都是0

int remainder_h = kernel_h * num_bins - bottom_h;

int pad_h = (remainder_h + 1) / 2;//上下两边分摊pad

//计算水平方向的刻度大小,不足的用pad补齐

int kernel_w = ceil(bottom_w / static_cast<double>(num_bins));

int remainder_w = kernel_w * num_bins - bottom_w;

int pad_w = (remainder_w + 1) / 2;

pooling_param.mutable_pooling_param()->set_pad_h(pad_h);

pooling_param.mutable_pooling_param()->set_pad_w(pad_w);

pooling_param.mutable_pooling_param()->set_kernel_h(kernel_h);

pooling_param.mutable_pooling_param()->set_kernel_w(kernel_w);

pooling_param.mutable_pooling_param()->set_stride_h(kernel_h);

pooling_param.mutable_pooling_param()->set_stride_w(kernel_w);

switch (spp_param.pool()) {

case SPPParameter_PoolMethod_MAX://窗口最大池化

pooling_param.mutable_pooling_param()->set_pool(

PoolingParameter_PoolMethod_MAX);

break;

case SPPParameter_PoolMethod_AVE://平均池化

pooling_param.mutable_pooling_param()->set_pool(

PoolingParameter_PoolMethod_AVE);

break;

case SPPParameter_PoolMethod_STOCHASTIC://随机概率池化

pooling_param.mutable_pooling_param()->set_pool(

PoolingParameter_PoolMethod_STOCHASTIC);

break;

default:

LOG(FATAL) << "Unknown pooling method.";

}

return pooling_param;

}

template <typename Dtype>

//这个函数是为了获取我们本层网络的输入特征图、输出相关参数,然后设置相关变量,比如输入特征图的图片的大小、个数

void SPPLayer<Dtype>::LayerSetUp(const vector<Blob<Dtype>*>& bottom,

const vector<Blob<Dtype>*>& top) {

SPPParameter spp_param = this->layer_param_.spp_param();

num_ = bottom[0]->num();//batch size 大小

channels_ = bottom[0]->channels();//特征图个数

bottom_h_ = bottom[0]->height();//特征图宽高

bottom_w_ = bottom[0]->width();

reshaped_first_time_ = false;

CHECK_GT(bottom_h_, 0) << "Input dimensions cannot be zero.";

CHECK_GT(bottom_w_, 0) << "Input dimensions cannot be zero.";

pyramid_height_ = spp_param.pyramid_height();//金子塔有多少层

split_top_vec_.clear();//清空相关数据

pooling_bottom_vecs_.clear();

pooling_layers_.clear();

pooling_top_vecs_.clear();

pooling_outputs_.clear();

flatten_layers_.clear();

flatten_top_vecs_.clear();

flatten_outputs_.clear();

concat_bottom_vec_.clear();

//如果金字塔只有一层,那么我们其实是对一整张图片进行pooling,也就是文献所提到的:global pooling

if (pyramid_height_ == 1) {

// pooling layer setup

LayerParameter pooling_param = GetPoolingParam(0, bottom_h_, bottom_w_,spp_param);

pooling_layers_.push_back(shared_ptr<PoolingLayer<Dtype> > (new PoolingLayer<Dtype>(pooling_param)));

pooling_layers_[0]->SetUp(bottom, top);

return;

}

//这个将用于保存金子塔每一层

for (int i = 0; i < pyramid_height_; i++) {

split_top_vec_.push_back(new Blob<Dtype>());

}

// split layer setup

LayerParameter split_param;

split_layer_.reset(new SplitLayer<Dtype>(split_param));

split_layer_->SetUp(bottom, split_top_vec_);

for (int i = 0; i < pyramid_height_; i++) {

// pooling layer input holders setup

pooling_bottom_vecs_.push_back(new vector<Blob<Dtype>*>);

pooling_bottom_vecs_[i]->push_back(split_top_vec_[i]);

pooling_outputs_.push_back(new Blob<Dtype>());

pooling_top_vecs_.push_back(new vector<Blob<Dtype>*>);

pooling_top_vecs_[i]->push_back(pooling_outputs_[i]);

// 获取金字塔每一层相关参数

LayerParameter pooling_param = GetPoolingParam(i, bottom_h_, bottom_w_, spp_param);

pooling_layers_.push_back(shared_ptr<PoolingLayer<Dtype> > (new PoolingLayer<Dtype>(pooling_param)));

pooling_layers_[i]->SetUp(*pooling_bottom_vecs_[i], *pooling_top_vecs_[i]);

//每一层金字塔输出向量

flatten_outputs_.push_back(new Blob<Dtype>());

flatten_top_vecs_.push_back(new vector<Blob<Dtype>*>);

flatten_top_vecs_[i]->push_back(flatten_outputs_[i]);

// flatten layer setup

LayerParameter flatten_param;

flatten_layers_.push_back(new FlattenLayer<Dtype>(flatten_param));

flatten_layers_[i]->SetUp(*pooling_top_vecs_[i], *flatten_top_vecs_[i]);

// concat layer input holders setup

concat_bottom_vec_.push_back(flatten_outputs_[i]);

}

// 把所有金字塔层的输出,串联成一个特征向量

LayerParameter concat_param;

concat_layer_.reset(new ConcatLayer<Dtype>(concat_param));

concat_layer_->SetUp(concat_bottom_vec_, top);

}

  

函数GetPoolingParam是我们需要细读的函数,里面设置了金子塔每一层窗口大小的计算,其它的函数就不贴了,对caffe底层实现感兴趣的,可以自己慢慢细读。

四、算法应用之物体检测

在SPP-Net还没出来之前,物体检测效果最牛逼的应该是RCNN算法了,下面跟大家简单讲一下R-CNN的总算法流程,简单回顾一下:

1、首先通过选择性搜索,对待检测的图片进行搜索出2000个候选窗口。

2、把这2k个候选窗口的图片都缩放到227*227,然后分别输入CNN中,每个候选窗台提取出一个特征向量,也就是说利用CNN进行提取特征向量。

3、把上面每个候选窗口的对应特征向量,利用SVM算法进行分类识别。

可以看到R-CNN计算量肯定很大,因为2k个候选窗口都要输入到CNN中,分别进行特征提取,计算量肯定不是一般的大。

OK,接着回归正题,如何利用SPP-Net进行物体检测识别?具体算法的大体流程如下:

1、首先通过选择性搜索,对待检测的图片进行搜索出2000个候选窗口。这一步和R-CNN一样。

2、特征提取阶段。这一步就是和R-CNN最大的区别了,同样是用卷积神经网络进行特征提取,但是SPP-Net用的是金字塔池化。这一步骤的具体操作如下:把整张待检测的图片,输入CNN中,进行一次性特征提取,得到feature maps,然后在feature maps中找到各个候选框的区域,再对各个候选框采用金字塔空间池化,提取出固定长度的特征向量。而R-CNN输入的是每个候选框,然后在进入CNN,因为SPP-Net只需要一次对整张图片进行特征提取,速度是大大地快啊。江湖传说可一个提高100倍的速度,因为R-CNN就相当于遍历一个CNN两千次,而SPP-Net只需要遍历1次。

3、最后一步也是和R-CNN一样,采用SVM算法进行特征向量分类识别。

算法细节说明:看完上面的步骤二,我们会有一个疑问,那就是如何在feature maps中找到原始图片中候选框的对应区域?因为候选框是通过一整张原图片进行检测得到的,而feature maps的大小和原始图片的大小是不同的,feature maps是经过原始图片卷积、下采样等一系列操作后得到的。那么我们要如何在feature maps中找到对应的区域呢?这个答案可以在文献中的最后面附录中找到答案:APPENDIX A:Mapping a Window to Feature Maps。这个作者直接给出了一个很方便我们计算的公式:假设(x’,y’)表示特征图上的坐标点,坐标点(x,y)表示原输入图片上的点,那么它们之间有如下转换关系:

(x,y)=(S*x’,S*y’)

其中S的就是CNN中所有的strides的乘积。比如paper所用的ZF-5:

S=2*2*2*2=16

而对于Overfeat-5/7就是S=12,这个可以看一下下面的表格:

需要注意的是Strides包含了池化、卷积的stride。自己计算一下Overfeat-5/7(前5层)是不是等于12。

反过来,我们希望通过(x,y)坐标求解(x’,y’),那么计算公式如下:

因此我们输入原图片检测到的windows,可以得到每个矩形候选框的四个角点,然后我们再根据公式:

Left、Top:

Right、Bottom:

参考文献:

1、https://github.com/BVLC/caffe

2、《Spatial Pyramid Pooling in Deep Convolutional Networks for Visual Recognition》

3、http://research.microsoft.com/en-us/um/people/kahe/eccv14sppnet/index.html

4、http://caffe.berkeleyvision.org/

**********************作者:hjimce   时间:2015.12.5  联系QQ:1393852684   地址:http://blog.csdn.net/hjimce   原创文章,转载请保留原文地址、作者等信息****************

原文地址:https://www.cnblogs.com/sddai/p/10204899.html

时间: 2024-09-29 21:08:45

空间金字塔池化(Spatial Pyramid Pooling,SPP)的相关文章

空间金字塔池化(Spatial Pyramid Pooling, SPP)原理和代码实现(Pytorch)

想直接看公式的可跳至第三节 3.公式修正 一.为什么需要SPP 首先需要知道为什么会需要SPP. 我们都知道卷积神经网络(CNN)由卷积层和全连接层组成,其中卷积层对于输入数据的大小并没有要求,唯一对数据大小有要求的则是第一个全连接层,因此基本上所有的CNN都要求输入数据固定大小,例如著名的VGG模型则要求输入数据大小是 (224*224) . 固定输入数据大小有两个问题: 1.很多场景所得到数据并不是固定大小的,例如街景文字基本上其高宽比是不固定的,如下图示红色框出的文字. 2.可能你会说可以

SPPNet论文翻译-空间金字塔池化Spatial Pyramid Pooling in Deep Convolutional Networks for Visual Recognition

http://www.dengfanxin.cn/?p=403 原文地址 我对物体检测的一篇重要著作SPPNet的论文的主要部分进行了翻译工作.SPPNet的初衷非常明晰,就是希望网络对输入的尺寸更加灵活,分析到卷积网络对尺寸并没有要求,固定尺寸的要求完全来源于全连接层部分,因而借助空间金字塔池化的方法来衔接两者,SPPNet在检测领域的重要贡献是避免了R-CNN的变形.重复计算等问题,在效果不衰减的情况下,大幅提高了识别速度. 用于视觉识别的深度卷积网络空间金字塔池化方法 Spatial Py

Spatial pyramid pooling (SPP)-net (空间金字塔池化)笔记(转)

在学习r-cnn系列时,一直看到SPP-net的身影,许多有疑问的地方在这篇论文里找到了答案. 论文:Spatial Pyramid Pooling in Deep Convolutional Networks for Visual Recognition 转自:http://blog.csdn.net/xzzppp/article/details/51377731 另可参考:http://zhangliliang.com/2014/09/13/paper-note-sppnet/ http:/

目标检测论文解读2——Spatial Pyramid Pooling in Deep Convolutional Networks for Visual Recognition

背景 用ConvNet方法解决图像分类.检测问题成为热潮,但这些方法都需要先把图片resize到固定的w*h,再丢进网络里,图片经过resize可能会丢失一些信息.论文作者发明了SPP pooling(空间金字塔池化)层,让网络可以接受任意size的输入. 方法 首先思考一个问题,为什么ConvNet需要一个固定size的图片作为输入,我们知道,Conv层只需要channel固定(彩色图片3,灰度图1),但可以接受任意w*h的输入,当然输出的w*h也会跟着变化:然而,后面的FC层却需要固定长度的

SPP(Spatial Pyramid Pooling)详解

一直对Fast RCNN中ROI Pooling层不解,不同大小的窗口输入怎么样才能得到同样大小的窗口输出呢,今天看到一篇博文讲得挺好的,摘录一下,方便查找. Introduction 在一般的CNN结构中,在卷积层后面通常连接着全连接.而全连接层的特征数是固定的,所以在网络输入的时候,会固定输入的大小(fixed-size).但在现实中,我们的输入的图像尺寸总是不能满足输入时要求的大小.然而通常的手法就是裁剪(crop)和拉伸(warp). 这样做总是不好的:图像的纵横比(ratio aspe

Spatial Pyramid Pooling in Deep Convolutional Networks for Visual Recognition

一.本文的主要思想 考虑到传统的CNN构架的输入图像的尺寸都是固定的(例如:256*256),这种人工改变输入图像的尺寸破坏了输入图像的尺度和长宽比例.作者认为卷积层的输入的尺寸可以是任意,全连接层的输入是固定不变.针对这个问题,作者提出了spatial pyramid pooling(SPP-net)结构,在目标检测方面,比R-CNN快30-170倍. 二.spatial pyramid pooling(SPP-net)的优势 1.针对不同尺寸的输入可以得到相同维度的输出,而siding wi

Tensorflow 池化层(pooling)和全连接层(dense)

一.池化层(pooling) 池化层定义在 tensorflow/python/layers/pooling.py. 有最大值池化和均值池化. 1. 最大池化层 tf.layers.max_pooling2d max_pooling2d( inputs, pool_size, strides, padding='valid', data_format='channels_last', name=None ) inputs: 进行池化的数据.pool_size: 池化的核大小(pool_heigh

SPP Net(Spatial Pyramid Pooling in Deep Convolutional Networks for Visual Recognition)论文理解

论文地址:https://arxiv.org/pdf/1406.4729.pdf 论文翻译请移步:http://www.dengfanxin.cn/?p=403 一.背景: 传统的CNN要求输入图像尺寸是固定的(因为全连接网络要求输入大小是固定的) crop处理,可能不包含整个物体,还会丢失上下文信息 warping处理,会导致图像变形 以上都会导致CNN对不同scale/size泛化能力不强 于是SPP做了如下改进,即将SPP层加在最后一个卷积层后面,然后再送入FC网络. 优点 不管输入尺寸为

池化层的作用和种类

原连接:https://blog.csdn.net/XX_123_1_RJ/article/details/86677482 池化的原理或者是过程:pooling是在不同的通道上分开执行的(就是池化操作不改变通道数),且不需要参数控制.然后根据窗口大小进行相应的操作.一般有max pooling.average pooling等. 一. 池化层主要的作用 首要作用,下采样(downsamping) 降维.去除冗余信息.对特征进行压缩.简化网络复杂度.减少计算量.减少内存消耗等等.各种说辞吧,总的