R-CNN
的问世让卷积神经网络在目标检测领域崭露头角,尽管它具有不俗的准确率,但是其检测效率过于低下,以至于很难得到实际的应用。为了提高R-CNN
的检测效率,SPP-Net
与空间金字塔池化Spatial Pyramid Pooling, SPP
应运而生。
一、R-CNN
的不足与改进
R-CNN
模型进行目标检测的效率很低,不仅训练过程十分繁琐,检测过程也是非常的慢。其原因之一就是生成的约2000
个候选区域需要一个一个地输入卷积神经网络以进行特征提取,而这一过程则相当的耗时。
为了解决这一问题,SPP-Net
提出将分割区域步骤与提取特征步骤的顺序进行颠倒。即,先通过CNN
对原图进行一次卷积以生成一个特征图,然后再从特征图中提取每个候选区域对应的子特征图,从而大大减少卷积的次数以提高模型效率。
想要实现上述改进,SPP-Net
主要面临着两个难题:
如何在特征图中找到候选区域映射的子特征图?
提取的子特征图尺度不一,如何统一输入全连接层?
二、从特征图找到候选区域的映射
关于如何从特征图上找出候选区域映射的子特征图,只需要找到原图像上坐标到特征图上坐标的映射即可。而这一映射关系作者已经在论文中给出:
对于原图像上的坐标
(
x
,
y
)
(x,\ y)
(x, y) 和特征图上的坐标
(
x
′
,
y
′
)
(x',\ y')
(x′, y′) ,它们之间有如下映射关系:
(
x
,
y
)
=
(
S
x
′
,
S
y
′
)
(x,\ y)=(Sx',\ Sy')
(x, y)=(Sx′, Sy′) 。反过来则有:
(
x
′
,
y
′
)
l
e
f
t
,
t
o
p
=
⌊
(
x
,
y
)
/
S
⌋
+
1
,
(
x
′
,
y
′
)
r
i
g
h
t
,
b
o
t
t
o
m
=
⌈
(
x
,
y
)
/
S
⌉
−
1
(x',\ y')_{left,\ top}=\lfloor(x,\ y)/S\rfloor+1,\quad (x',\ y')_{right,\ bottom}=\lceil(x,\ y)/S\rceil-1
(x′, y′)left, top=⌊(x, y)/S⌋+1,(x′, y′)right, bottom=⌈(x, y)/S⌉−1
其中,
S
S
S为CNN
中所有卷积层和池化层的stride
的乘积。
那么上述映射是怎么来的呢?对于如下卷积变换:
已知卷积变换的尺度公式如下(
k
k
k 为卷积核大小,
p
p
p 为padding
,
s
s
s 为stride
):
w
′
=
w
+
2
p
−
k
s
+
1
w'=\frac{w+2p-k}{s}+1
w′=sw+2p−k+1
变形可得:
w
=
(
w
′
−
1
)
s
−
2
p
+
k
w=(w'-1)s-2p+k
w=(w′−1)s−2p+k
对于卷积后的某一像素点,则其
w
′
=
1
w'=1
w′=1 ,计算可得这个像素点在上一层的感受野大小如下:
w
=
(
1
−
1
)
s
−
2
p
+
k
=
k
−
2
p
w=(1-1)s-2p+k=k-2p
w=(1−1)s−2p+k=k−2p
假设卷积核大小为奇数,则感受野中心点的相对坐标为(未考虑感受野的偏移量
d
d
d):
(
k
−
2
p
−
1
)
/
2
(k-2p-1)/2
(k−2p−1)/2
如果卷积后的这个像素点的坐标为
x
x
x(从0
开始计),那么可以计算感受野的偏移量
d
d
d 为:
d
=
x
′
s
d=x's
d=x′s
结合起来可以得到,卷积后某一像素点坐标
x
′
x'
x′ 到卷积前感受野中心点坐标
x
x
x 的映射为:
x
=
x
′
s
+
(
k
−
2
p
−
1
)
2
x=x's+\frac{(k-2p-1)}{2}
x=x′s+2(k−2p−1)
以上便是卷积前后坐标的映射关系。在SPP-Net
中,为了简化上述映射,将
p
p
p 固定为了
⌊
k
/
2
⌋
\lfloor k/2\rfloor
⌊k/2⌋,则有:
x
=
x
′
s
+
(
k
−
2
⌊
k
/
2
⌋
−
1
)
2
=
{
x
′
s
−
0.5
,
if
k
=
2
n
,
n
∈
N
x
′
s
,
if
k
=
2
n
+
1
,
n
∈
N
x=x's+\frac{(k-2\lfloor k/2\rfloor-1)}{2}= \begin{cases} x's-0.5,\quad &\text{if}\ k=2n,\ n\in N\\ x's,&\text{if}\ k=2n+1,\ n\in N \end{cases}
x=x′s+2(k−2⌊k/2⌋−1)={x′s−0.5,x′s,if k=2n, n∈Nif k=2n+1, n∈N
又因为坐标
x
x
x 必须为整数,所以便可以近似认为
x
=
x
′
s
x=x's
x=x′s 。当有多层卷积时,则有:
(
x
,
y
)
=
(
S
x
′
,
S
y
′
)
,
S
=
s
1
s
2
s
3
.
.
.
(x,\ y)=(Sx',\ Sy'),\quad S=s_1s_2s_3...
(x, y)=(Sx′, Sy′),S=s1s2s3...
由此便得到了SPP-Net
中卷积变换坐标的映射关系。
三、空间金字塔池化
空间金字塔池化Spatial Pyramid Pooling, SPP
是SPP-Net
的第二个核心内容。当从特征图上提取到每个候选区域对应的特征子图后,这些特征子图的尺度可能并不一致,从而无法直接输入到全连接层。因此,SPP-Net
在全连接层前添加了一个特殊的池化层,即空间金字塔池化,能够将不同尺度的输入图像池化为相同长度的向量。
所谓空间金字塔池化,就是对一张输入图像同时进行三种池化,分别输出[c, 4, 4], [c, 2, 2], [c, 1, 1]
三个特征图,再将这三个特征图展平,最后得到一个长度固定为c * (16 + 4 + 1)
的特征向量。
具体做法是,假设输入图像形状为[c, h, w]
,池化输出为[c, n, n]
,那么池化层的kernel_size
则可以设置为[ceil(h / n), ceil(w / n)]
,stride
可以设置为[floor(h / n), floor(w / n)]
。代码示例如下所示:
import torch
from torch import nn
from math import ceil, floor
a = torch.rand([4, 3, 15, 63])
size = [ceil(a.shape[2] / 4), ceil(a.shape[3] / 4)]
stride = [floor(a.shape[2] / 4), floor(a.shape[3] / 4)]
print(size)
print(stride)
pool = nn.MaxPool2d(kernel_size=size, stride=stride)
b = pool(a)
print(b.shape)
如上示例便可以将不同尺度的图片统一池化为[c, 4, 4]
的形状。
四、写在最后
以上便是SPP-Net
最核心的内容,剩余的部分则和R-CNN
一样了,详见博客目标检测模型(一):R-CNN。SPP-Net
主要是在特征提取阶段对R-CNN
进行了优化,通过计算卷积前后坐标的映射关系和空间金字塔池化大大减少了卷积提取特征的次数,从而大幅地提高了模型进行目标检测的效率。除此之外,SPP-Net
的思想还被后来的Fast R-CNN
所借用,让目标检测的效率更上了一个阶梯。