一、SPP源码
class SPP(nn.Module):
# Spatial pyramid pooling layer used in YOLOv3-SPP
def __init__(self, c1, c2, k=(5, 9, 13)):
super(SPP, self).__init__()
c_ = c1 // 2 # hidden channels
self.cv1 = Conv(c1, c_, 1, 1)
self.cv2 = Conv(c_ * (len(k) + 1), c2, 1, 1)
self.m = nn.ModuleList([nn.MaxPool2d(kernel_size=x, stride=1, padding=x // 2) for x in k])
def forward(self, x):
x = self.cv1(x)
return self.cv2(torch.cat([x] + [m(x) for m in self.m], 1))
二、SPP结构图
二、尺度不变解析
1、宽高维度不变
表1 SPP模块参数
池化核(kernel_size,k) | 步长(strides,s) | 填充(Padding,p) |
---|---|---|
5 | 1 | 2 |
9 | 1 | 4 |
13 | 1 | 6 |
关键:三个最大池化模块的步长和填充不同,导致三个分支特征图在[宽高维度]均没有发生改变
可经过以下公式验证:
- 设 F in \mathrm{F}_{\text {in }} Fin 初始大小为:H=20,W=20
- 计算公式: F out = ( F in − k + 2 ∗ p ) / s + 1 \mathrm{F}_{\text {out}}=\left(\mathrm{F}_{\text {in}}-\mathrm{k}+2^* \mathrm{p}\right) / \mathrm{s}+1 Fout=(Fin−k+2∗p)/s+1
表2 SPP各个分支输出F的尺寸
分支 | 计算过程 | F out \mathrm{F}_{\text {out }} Fout |
---|---|---|
Maxpool1(k5,s1,p2) | 20-5+2×2+1 | 20 |
Maxpool2(k9,s1,p4) | 20-9+2×4+1 | 20 |
Maxpool3(k13,s1,p6) | 20-13+2×6+1 | 20 |
- 计算可得 F in = F out F_{\text {in}}=F_{\text {out }} Fin=Fout
2、通道维度不变
关键:SPP模块中conv1、conv2的卷积核数量的设置:
通道维度没有变化的话,看源码
- 假设输入SPP模块之前c = 40,那经过conv1每个分支输入的c都为20。
- 将4分支沿通道维度拼接(dim=1),得到的特征图 c = 20*4。
- 经过conv2后c为40
因此,从整体看SPP模块并没有改变特征图的通道数量。
总结
---->通过控制最大池化操作的步长和填充,保证特征图尺度维度不变性。
----->通过控制内部的卷积模块核的数量,保证特征图通道维度不变性。
综上所述,SPP模块处理后的特征图,宽高通道维度均没改变。