最近想弄透faster rcnn,觉得spp应该是非常重要的,先详细总结下:
为什么会有spp
先看下和传统分类的对比图
解释:可以看到传统的操作是先将原图crop(裁剪/变换,也就是类似resize操作),直接送入卷积层,然后进入全连接分类。那么问题就来了,随便一张图目标物体位置和大小不一样,crop肯定会影响特征的准确性啊,但是不crop成统一大小,提取的特征图大小就不一样,没法送进全连接层,那有没有可能不crop直接得到固定维度的特征图呢?ok,这就是spp作用,加入spp之后就不需要提前crop,直接任意图片送进来,把spp核心放在卷积层和全连接层之间就搞定了!
spp具体结构是咋样的?
解释:
上图就是spp结构,从下往上看,第一步卷积提取特征图,第二步金字塔池化,第三步将池化结果送入全连接层。第二步具体啥样子,就是把原来的特征图分别分成44=16块,22=4块,11=1块(不变),总共21块,取每块的最大值作为代表,即每张特征图就有21维的参数,总共卷积出来256个特征图,则送入全连接层的维度就是21256。
可能有些人到这里还是不懂,分块啥又变成21维,太抽象了,其实放到代码上来说,就是对特征图进行不同步长的池化而已,分四块取每块的最大值不就是步长大点,让一张图只池化四次,这下懂了吧。
当然这里输出的21维是可变的,改步长等具体参数就好了
spp的代码实现
具体来看下代码实现:
# -*- coding: utf-8 -*-
import tensorflow as tf
import numpy as np
import pandas as pd
def spp_layer(input_, levels=4, name = 'SPP_layer',pool_type = 'max_pool'):
'''
Multiple Level SPP layer.
Works for levels=[1, 2, 3, 6].
'''
shape = input_.get_shape().as_list()
with tf.variable_scope(name):
for l in range(levels):
#设置池化参数
l = l + 1
ksize = [1, np.ceil(shape[1]/ l + 1).astype(np.int32), np.ceil(shape[2] / l + 1).astype(np.int32), 1]
strides = [1, np.floor(shape[1] / l + 1).astype(np.int32), np.floor(shape[2] / l + 1).astype(np.int32), 1]
if pool_type == 'max_pool':
pool = tf.nn.max_pool(input_, ksize=ksize, strides=strides, padding='SAME')
pool = tf.reshape(pool,(shape[0],-1),)
else :
pool = tf.nn.avg_pool(input_, ksize=ksize, strides=strides, padding='SAME')
pool = tf.reshape(pool,(shape[0],-1))
print("Pool Level {:}: shape {:}".format(l, pool.get_shape().as_list()))
if l == 1:
x_flatten = tf.reshape(pool,(shape[0],-1))
else:
x_flatten = tf.concat((x_flatten,pool),axis=1) #四种尺度进行拼接
print("Pool Level {:}: shape {:}".format(l, x_flatten.get_shape().as_list()))
# pool_outputs.append(tf.reshape(pool, [tf.shape(pool)[1], -1]))
return x_flatten
#x = tf.ones((4,16,16,3))
#x_sppl = spp_layer(x,4)
refer https://www.jianshu.com/p/7f30b5935f3f
https://blog.csdn.net/v1_vivian/article/details/73275259