时间:2025-04-18 14:25
人气:
作者:admin
原文作者:aircraft
学习YOLOv5前的准备就是学习DarkNet53网络,FPN特征金字塔网络,PANet路径聚合网络结构,(从SPP到SPPF)SPPF空间金字塔池化等。本篇讲从SPP到SPPF网络结构。。。(其他几篇已经发布在历史博客里------基本YOLO网络的前置学习就在这篇讲的差不多了,后面应该写YOLO目标检测了。。。。)
SPPNet(Spatial Pyramid Pooling Network)是何凯明团队在2014年提出的创新架构,核心是空间金字塔池化(SPP)层,解决了传统卷积神经网络必须固定输入尺寸的限制。以下从技术原理到实践应用进行详解:
传统CNN的痛点:
SPP的核心突破:
[4×4, 2×2, 1×1] 三级网格n×n个子区域win_size = ⌈输入尺寸/n⌉stride = ⌊输入尺寸/n⌋W×H×C(宽×高×通道)n_k × n_k × C(Σ n_k²) × C(固定长度)示例:
[4×4, 2×2, 1×1](16 + 4 + 1) × C = 21C

具体流程:
1、特征图分割:SPP 层将输入的特征图分割成多个不同大小的网格,这些网格的大小通常是 1x1、2x2、4x4 、8x8 等等,形成一个金字塔结构,每个网格的大小决定了池化操作的感受野;
2、池化操作:对每个网格进行池化操作,通常使用最大池化(Max Pooling);最大池化会选择每个网格中的最大值作为输出。这样,每个网格都会生成一个固定大小的特征表示,需要注意的是这个池化操作是一个并发的过程;
3、特征拼接:将所有网格的特征表示拼接起来,形成一个固定长度的特征向量;这个特征向量随后可以作为后续全连接层的输入;
为什么很多带全连接层的网络结构都需要固定归一化图像的大小呢(如AlexNet强制缩放到227×227的图像大小)?举个例子就是我在全连接层输出的网络结构是固定大小的,比如是21*512这个大小:
在最后一层卷积层我们已经固定用了512个卷积核,那么输入到全连接层的数据就是 w * h * 512 (特征图像的长宽乘通道数),那么在通道数固定的情况下,图像大小变化的话就会和全连接层对应不上。接口对接不起来!!!
拿全连接层所需数据大小21*512举例,在卷积层输出的通道数固定为512的情况下,我们构建1x1、2x2、4x4的三层空间金字塔池化:
假设我们有一张 448×448 的输入图像,通过卷积层后得到特征图 56×56×512;接下来,我们使用 SPP 层进行处理:
1.特征图分割:
1x1 网格:整个特征图作为一份数据
2x2 网格:特征图被均匀分割成 4 份数据
4x4 网格:特征图被均匀分割为16份数据
2. 池化操作:
1x1 网格:最大池化后的特征图尺寸为 1×1×512;
2x2 网格:最大池化后的特征图尺寸为 2×2×512=4×512=4×512;
4x4 网格:最大池化后的特征图尺寸为 4×4×512=16×512=16×512;
3. 特征拼接:
拼接后的特征向量长度为:21 x 512 =10752 不管你输入图像的大小是多少227x227 ,512x512,318x318也好,经过三层的空间金字塔网络后提取的特征数目都是21份,加上通道数就是 21x512份。这样就不用在意输入图像数据的大小了。
下图就非常鲜明了,厚度就是通道数:

大概代码实现(任意输入图像尺寸,输出的大小一致):
import torch
import torch.nn as nn
class SpatialPyramidPooling(nn.Module):
def __init__(self, levels=[4, 2, 1]):
super().__init__()
self.levels = levels
def forward(self, x):
N, C, H, W = x.size()
outputs = []
for level in self.levels:
kh = H // level
kw = W // level
pool = nn.MaxPool2d(kernel_size=(kh, kw), stride=(kh, kw))
outputs.append(pool(x).view(N, -1))
return torch.cat(outputs, dim=1)
# 使用示例
spp = SpatialPyramidPooling(levels=[4, 2, 1])
input = torch.randn(1, 256, 13, 13) # 任意尺寸输入
output = spp(input) # 输出固定维度: (1, 21 * 256)
代码隐式要求输入尺寸必须能被所有level整除,否则实际输出网格数会偏离预设level:
(10-2)/2 +1 =5 → 5×5(而非预设的4×4)若想不管这个隐藏条件,还可以这样改,使用自适应池化:
class SpatialPyramidPooling(nn.Module):
def __init__(self, levels=[4, 2, 1]):
super().__init__()
self.pools = nn.ModuleList([
nn.AdaptiveMaxPool2d((level, level)) for level in levels
])
def forward(self, x):
N, C, _, _ = x.size()
outputs = [pool(x).view(N, -1) for pool in self.pools]
return torch.cat(outputs, dim=1)
AdaptiveMaxPool2d直接强制输出目标尺寸(如4×4),无需计算核尺寸
池化信息损失:
计算资源消耗:
SPPNet通过空间金字塔池化,首次实现了CNN对任意尺寸输入的处理,奠定了多尺度特征融合的基础。其核心思想被后续众多模型(如Fast R-CNN、Mask R-CNN)继承发展,是深度学习发展史上的重要里程碑。理解SPP机制对掌握现代目标检测和图像分类模型至关重要。
SPPF(空间金字塔快速池化)是SPPNet的改进版本,由YOLO系列(如YOLOv5、YOLOv7)引入,旨在保持多尺度特征融合能力的同时显著提升计算效率。其核心思想是通过串行重复池化操作替代传统金字塔并行池化,减少计算冗余。以下是详细解析:


| 模块 | 池化方式 | 计算路径 | 参数量 | 输出特征维度 |
|---|---|---|---|---|
| SPP | 并行多级池化(如4×4, 2×2, 1×1) | 独立分支 | 多 | 多级特征拼接 |
| SPPF | 串行重复池化(如三次5×5池化) | 单链叠加 | 少 | 等效金字塔融合 |
| 特性 | SPP | SPPF |
|---|---|---|
| 多尺度特征来源 | 离散分级(4×4, 2×2, 1×1) | 连续叠加(5→9→13感受野) |
| 计算效率 | 低(多分支并行池化) | 高(单链串行池化,GPU优化) |
| 硬件友好性 | 内存碎片化 | 连续内存操作 |
| 适用场景 | 分类网络(需全连接层) | 检测网络(全卷积架构) |
输入特征图尺寸:W×H×C
等效感受野:
第一次池化:
第二次池化:
第三次池化:
拼接结果:
虽然SPPF输出的空间尺寸 (W×H) 仍与输入相关,但后续网络通过 全局池化(Global Pooling) 或 自适应层(Adaptive Layers) 将其转换为固定维度

以YOLOv5的SPPF模块为例(三次5×5池化,步长=1):
| 层数 | 操作 | 核尺寸(k) | 步长(stride) | 感受野计算 | 累计感受野 |
|---|---|---|---|---|---|
| 1 | 输入 | - | - | 初始感受野=1 | 1×1 |
| 2 | 第一次池化 | 5×5 | 1 | 1 + (5-1)*1 = 5 | 5×5 |
| 3 | 第二次池化 | 5×5 | 1 | 5 + (5-1)*1 * 1 = 9 | 9×9 |
| 4 | 第三次池化 | 5×5 | 1 | 9 + (5-1)*1 * 1 * 1 = 13 | 13×13 |
在YOLOv5/v7中,SPPF模块后直接连接卷积层,无需全局池化:
# YOLOv5的C3模块(包含SPPF)
class C3(nn.Module):
def __init__(self, c1, c2):
super().__init__()
self.sppf = SPPF(c1) # 输出H×W×4C1
self.conv = nn.Conv2d(4*c1, c2, 1) # 压缩通道到C2
def forward(self, x):
x = self.sppf(x) # H×W×4C1 → 空间维度保留
x = self.conv(x) # H×W×C2 → 输入检测头
return x
import torch
import torch.nn as nn
class SPPF(nn.Module):
def __init__(self, in_channels):
super().__init__()
self.pool = nn.MaxPool2d(kernel_size=5, stride=1, padding=2)
def forward(self, x):
x1 = self.pool(x)
x2 = self.pool(x1)
x3 = self.pool(x2)
return torch.cat([x, x1, x2, x3], dim=1)
# 输入示例:batch=1, channels=256, size=13x13
input = torch.randn(1, 256, 13, 13)
sppf = SPPF(256)
output = sppf(input) # 输出维度:[1, 1024, 13, 13]
padding=kernel_size//2(保持尺寸不变)
计算效率提升:
多尺度特征融合增强:
硬件友好性:
模型兼容性:
Backbone
├── Focus
├── Conv
├── C3
└── SPPF # 替换原始SPP模块
| 模型 | mAP@0.5 | FPS (Tesla T4) | 参数量 (M) |
|---|---|---|---|
| YOLOv5s | 37.2 | 125 | 7.2 |
| YOLOv5s+SPPF | 37.8 | 142 | 7.3 |
小物体检测精度衰减:
理论感受野与实际差异:
通道膨胀问题:
SPPF+SE:
ASPPF(Atrous SPPF):
轻量化SPPF:
还有SimSPPF,SPPCSPC,SPPFCSPC+都可以去了解一下都是YOLO不断升级中改进出来的。
SPPF通过巧妙的串行池化结构,在保持多尺度特征融合能力的同时,显著提升了计算效率。其设计体现了“简单即有效”的优化哲学,成为实时目标检测模型的标配模块。理解SPPF的工作原理对于优化模型速度和精度平衡至关重要,特别是在边缘计算和实时视频分析场景中具有重要价值。
参考博客:https://blog.csdn.net/CITY_OF_MO_GY/article/details/143897303
https://developer.aliyun.com/article/1509544
https://blog.csdn.net/weixin_38346042/article/details/131796263
转发和使用本文,请注明作者信息和原文地址---本文原作者为aircraft ---大家好我是徐飞机,有没有大佬们的公司招c++开发/图像处理/opengl/opencv/halcon实习的啊,带上我一个呗QAQ。。。hhhhhh 想要免费获取前端,后端,c/c++,matlab,Python,opencv,机器学习,深度学习,安卓,java,等等全套视频教程请关注机器视觉开发公众号,转发集赞28即可百度云获得hhhhhhhh
上一篇:VTK入门系列之一:画一个圆柱体
下一篇:从车道检测项目入门open cv