目标检测基础
目标检测算法主要分为两种,一种是one-stage检测算法,一种是two-stage检测算法。而对于two-stage检测算法来讲第一步是进行区域分割,然后第二步才是分类,代表方法有RCNN算法。而one-stage检测算法一般有YOLO和SSD算法。
Selective search 和 RCNN
RCNN这个是比较简单的目标检测算法,一般和Selective search一起使用,selective search一般包含如下步骤。
- 计算所有邻近区域之间的相似性;
- 两个最相似的区域被组合在一起;
- 计算合并区域和相邻区域的相似度;
- 重复2、3过程,直到整个图像变为一个地区。
思路十分简单,就是计算两个区域的周边所有区域的相似度,相似就合并,最后会合成一大块区域。
主要算法可以参见 [Graph-Based Image Segmentation](http://mapdic.com/upload/2020/06/IJCV(2004) Efficient Graph-Based Image Segmentation-0b0c03220c2c4ae6b4330b165b05a23f.pdf)
经过前面的图像切割以后,剩下的就是图像的分类操作。
结构十分简单,通过Selective search切分好的区域,直接过一个CNN进行编码,然后通过分类器进行分类。
RCNN的步骤如下
- 生产类别独立的候选区域,这些候选区域其中包含了 R-CNN 最终定位的结果。
- 神经网络去针对每个候选区域提取固定长度的特征向量。
- 一系列的 SVM 分类器。
而对于我们实际训练的时候是这样的步骤,先针对目标训练一系列的分类器,例如上图中马和人等等,然后实际应用的时候是将图像进行切割然后过我们的svm分类器,Selective search能够记录目标未知,而svm分类器能够识别类别,这样正好就构成了目标检测的任务难题。
SPPNet
对于上面的RCNN算法,你可能觉得已经很不错啦,不过还是有一些问题。
- 计算冗余,因为RCNN是先生成候选去然后在进行卷积编码,所以会生成很多无效的区域并且编码,减少性能
- 候选区缩放问题,RCNN会将所有的区域缩放到同一个比例进行分类,而Selective search会产生大小不一的图片,造成目标变形难以区分。
之所以要缩放到统一比例,是因为全连接层的输入是必须是标准化的,所以必须规范化大小,但是规范化以后就会造成图像失真和信息丢失。为了解决这个问题,我们引入一个特殊的池化层,就是标题提到的SPP来解决,它主要失实现了任意尺度的特征图都能够通过池化变成固定大小的输出,从而去掉原图像的剪切/缩放的操作。这个结构就是spatial pyramid pooling layer。
我们先来看上面的图片,conv5是最后一层的卷积层,我们先看最左边有16个蓝色小格子的图,它的意思是将从(conv_5)得到的特征映射分成16份(不一定是等比分),另外16X256中的256表示的是channel,其他颜色的格子也是同样的作用,通过这样的等分我们就将原始的feather map进行池化就好啦,一般我们都使用最大池化的策略。其实就是在原有的卷积核中使用44,22,1*1卷积核对特征图进行操作,就强制的标准化为16+4+1的维度。
通过SPP层,特征映射被转化成了16×256+4×256+1×256=21×256的矩阵。在送入全连接时可以扩展成一维矩阵,即1×10752,所以第一个全连接层的参数就可以设置成10752了,这样也就解决了输入数据大小任意的问题了。
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'):
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)
print x_sppl
Output:
Pool Level 1: shape [4, 3]
Pool Level 1: shape [4, 3]
Pool Level 2: shape [4, 12]
Pool Level 2: shape [4, 15]
Pool Level 3: shape [4, 27]
Pool Level 3: shape [4, 42]
Pool Level 4: shape [4, 48]
Pool Level 4: shape [4, 90]
Tensor("SPP_layer/concat_2:0", shape=(4, 90), dtype=float32)
最后将我们的输入维度4,16,16,3标准化程4×90,这样就能输入一个360维度的全链接神经网络啦。