今日目标 :掌握空洞卷积和ASPP的核心思想,理解DeepLab v1→v3+的进化过程,学会用torchvision做语义分割
预计阅读 :10分钟 | 动手操作:40分钟
一、语义分割的核心矛盾
分割的核心矛盾:
分类需要大感受野(看到全局) → 需要下采样/大卷积核
分割需要高分辨率(像素级预测) → 需要保持分辨率
下采样 → 感受野↑ 但 分辨率↓
不下采样 → 分辨率↑ 但 感受野↓
U-Net的解决方案:下采样+跳跃连接恢复细节
DeepLab的解决方案:空洞卷积,不降低分辨率的同时增大感受野!
两种思路,殊途同归。
python
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torch.utils.data import DataLoader
import numpy as np
torch.manual_seed(42)
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
二、核心知识点
2.1 空洞卷积:不降分辨率,增大感受野
python
class DilatedConvDemo:
"""
空洞卷积 (Atrous Convolution / Dilated Convolution)
普通卷积的问题:
要增大感受野 → 下采样(Pooling) → 分辨率降低 → 丢失空间信息
空洞卷积的解决方案:
在卷积核的元素之间插入"空洞"(0值)
不增加参数,不降低分辨率,但感受野成倍增大!
空洞率(dilation rate):
dilation=1: 普通卷积,感受野=3×3
dilation=2: 元素间距为2,感受野=5×5
dilation=4: 元素间距为4,感受野=9×9
dilation=6: 感受野=13×13!
"""
@staticmethod
def receptive_field(kernel_size, dilation):
"""计算空洞卷积的有效感受野"""
return (kernel_size - 1) * dilation + 1
@staticmethod
def demo():
print("空洞卷积感受野:")
for d in [1, 2, 4, 6, 8, 12]:
rf = DilatedConvDemo.receptive_field(3, d)
bar = '█' * d
print(f" dilation={d:2d}: 感受野={rf:2d}×{rf:2d} {bar}")
print("\n关键洞察:")
print(" dilation=1: 3×3卷积 → 感受野 3×3")
print(" dilation=6: 3×3卷积 → 感受野 13×13!")
print(" 参数量: 完全相同!都是9个参数")
print(" 计算量: 完全相同!")
print(" 但感受野差了4倍!这就是空洞卷积的魔力")
DilatedConvDemo.demo()
2.2 DeepLab v1:空洞卷积的首次应用
python
"""
DeepLab v1: Semantic Image Segmentation with Deep Convolutional Nets and Fully Connected CRFs
论文:https://arxiv.org/abs/1412.7062
核心改进:
1. 用空洞卷积替代VGG最后的Pooling层
VGG原始: 5次Pooling → 分辨率缩小32倍 → 7×7
DeepLab: 最后两次Pooling去掉,用空洞卷积替代 → 分辨率只缩小8倍 → 28×28
2. CRF后处理(条件随机场)
CNN输出粗糙的分割结果
CRF利用像素颜色和位置关系精细化边界
但CRF太慢,后续版本被更好的网络结构替代
空洞卷积的应用:
原来:Conv → Pool → Conv → Pool → Conv → Pool → ...
改后:Conv → Pool → Conv → Pool → Conv(dilation=2) → Conv(dilation=4)
保持分辨率:224 → 112 → 56 → 28 → 28 → 28
感受野不变:因为空洞卷积补偿了去掉的Pooling
"""
class DeepLabV1:
def __init__(self):
self.backbone = "VGG16 (修改最后两层)"
self.key_innovation = "空洞卷积替代Pooling,保持分辨率"
self.output_stride = "8 (原图缩小8倍,而非32倍)"
self.post_process = "CRF (DenseCRF) 精细化边界"
def describe(self):
print("DeepLab v1:")
print(f" Backbone: {self.backbone}")
print(f" 核心创新: {self.key_innovation}")
print(f" 输出步长: {self.output_stride}")
print(f" 后处理: {self.post_process}")
print(f" 问题: CRF太慢,单独使用效果一般")
DeepLabV1().describe()
2.3 DeepLab v2:ASPP多尺度上下文
python
"""
DeepLab v2: DeepLab: Semantic Image Segmentation with Deep Convolutional Nets,
Atrous Convolution, and Fully Connected CRFs
论文:https://arxiv.org/abs/1606.00915
核心创新:ASPP (Atrous Spatial Pyramid Pooling)
问题:同一个物体在不同图像中大小不同
一个3×3卷积的感受野是固定的,怎么同时处理大小物体?
ASPP的解决方案:
用多个不同dilation rate的空洞卷积并行处理
每个分支有不同的感受野,捕捉不同尺度的信息
ASPP模块:
┌──────────┐
│ 输入特征图 │
└─────┬────┘
┌─────┼─────┬──────────┬──────────┐
│ │ │ │ │
▼ ▼ ▼ ▼ ▼
Conv Conv Conv Conv Pool
d=1 d=2 d=6 d=12 Global
│ │ │ │ │
└─────┴─────┴──────────┴──────────┘
│
▼
Concat + 1×1 Conv
│
▼
输出
"""
class ASPP(nn.Module):
"""
ASPP模块完整实现
多尺度特征提取:不同dilation rate捕捉不同感受野的信息
"""
def __init__(self, in_channels, out_channels):
super().__init__()
# 分支1: 1×1卷积 (等价于dilation很大的3×3)
self.conv1x1 = nn.Sequential(
nn.Conv2d(in_channels, out_channels, 1, bias=False),
nn.BatchNorm2d(out_channels),
nn.ReLU(inplace=True),
)
# 分支2-4: 不同dilation rate的3×3卷积
self.atrous6 = self._make_atrous(in_channels, out_channels, 6)
self.atrous12 = self._make_atrous(in_channels, out_channels, 12)
self.atrous18 = self._make_atrous(in_channels, out_channels, 18)
# 分支5: 全局平均池化(图像级特征)
self.image_pool = nn.Sequential(
nn.AdaptiveAvgPool2d(1),
nn.Conv2d(in_channels, out_channels, 1, bias=False),
nn.BatchNorm2d(out_channels),
nn.ReLU(inplace=True),
)
# 融合层
self.project = nn.Sequential(
nn.Conv2d(out_channels * 5, out_channels, 1, bias=False),
nn.BatchNorm2d(out_channels),
nn.ReLU(inplace=True),
nn.Dropout(0.1),
)
def _make_atrous(self, in_ch, out_ch, dilation):
return nn.Sequential(
nn.Conv2d(in_ch, out_ch, 3, padding=dilation, dilation=dilation, bias=False),
nn.BatchNorm2d(out_ch),
nn.ReLU(inplace=True),
)
def forward(self, x):
h, w = x.shape[2:]
# 5个分支
feat1 = self.conv1x1(x)
feat2 = self.atrous6(x)
feat3 = self.atrous12(x)
feat4 = self.atrous18(x)
feat5 = F.interpolate(self.image_pool(x), size=(h, w), mode='bilinear', align_corners=True)
# 拼接
out = torch.cat([feat1, feat2, feat3, feat4, feat5], dim=1)
return self.project(out)
# 测试ASPP
def test_aspp():
aspp = ASPP(256, 256)
x = torch.randn(1, 256, 32, 32)
y = aspp(x)
print(f"ASPP测试:")
print(f" Input: {x.shape}")
print(f" Output: {y.shape}")
print(f" 5个分支: 1×1 + d=6 + d=12 + d=18 + GlobalPool")
print(f" 参数量: {sum(p.numel() for p in aspp.parameters()):,}")
test_aspp()
2.4 DeepLab v3:改进的ASPP
python
"""
DeepLab v3: Rethinking Atrous Convolution for Semantic Image Segmentation
论文:https://arxiv.org/abs/1706.05587
核心改进:
1. 级联/并行空洞卷积模块
Block1 → Block2 → Block3 → Block4
(d=1) (d=2) (d=4) (d=8)
感受野越来越大,但分辨率不变!
2. 改进ASPP(加入全局池化和BN)
相比v2,ASPP加上了BatchNorm和全局平均池化
3. 去掉CRF
网络结构已经足够好,不需要CRF后处理了
DeepLab v3的多网格策略 (Multi-Grid):
在最后一个Block中,三个卷积的dilation = (1, 2, 4) × rate
例如 rate=2 → dilation = (2, 4, 8)
"""
class DeepLabV3Concept:
def __init__(self):
self.backbone = "ResNet-50/101 (修改最后一个Block)"
self.aspp = "改进ASPP (加入BN + Global Pooling)"
self.output_stride = "8 或 16"
self.key = "去掉CRF,纯网络端到端训练"
def describe(self):
print("DeepLab v3:")
print(f" Backbone: {self.backbone}")
print(f" ASPP: {self.aspp}")
print(f" 输出步长: {self.output_stride}")
print(f" 关键: {self.key}")
print(f" 多网格: dilation=(1,2,4)×rate")
DeepLabV3Concept().describe()
2.5 DeepLab v3+:加入解码器
python
"""
DeepLab v3+: Encoder-Decoder with Atrous Separable Convolution
论文:https://arxiv.org/abs/1802.02611
核心创新:
1. 编码器-解码器结构
v3只有编码器 → 直接上采样16倍 → 细节丢失
v3+加入解码器 → 逐步上采样,融合浅层特征 → 细节更好!
2. 空洞可分离卷积 (Atrous Separable Conv)
深度可分离卷积 + 空洞卷积
大幅减少参数量和计算量
3. Xception/Modified Aligned Xception backbone
更强的backbone
DeepLab v3+ = DeepLab v3 + U-Net式的解码器
架构:
编码器:
Backbone (Xception/ResNet) → ASPP
解码器:
浅层特征 (low-level) ──┐
├──→ Concat → Conv → 上采样4倍
ASPP输出 (上采样4倍) ──┘
"""
class DeepLabV3PlusArchitecture:
def __init__(self):
self.encoder = {
'backbone': 'Xception-65 / ResNet-101',
'aspp': 'ASPP (d=6,12,18 + Global Pool)',
'output_stride': '16',
}
self.decoder = {
'low_level': 'backbone的浅层特征 (stride=4)',
'fusion': 'Concat + 3×3 Conv',
'upsample': '×4 上采样',
}
def describe(self):
print("DeepLab v3+ 架构:")
print(" 编码器:")
for k, v in self.encoder.items():
print(f" {k}: {v}")
print(" 解码器:")
for k, v in self.decoder.items():
print(f" {k}: {v}")
print("\n 核心创新: 编码器-解码器 + 空洞可分离卷积")
DeepLabV3PlusArchitecture().describe()
三、动手实践
3.1 实战:用torchvision的DeepLabV3做分割
python
def demo_deeplabv3_torchvision():
"""使用torchvision预训练的DeepLabV3"""
from torchvision.models.segmentation import deeplabv3_resnet50, deeplabv3_resnet101
from torchvision.models.segmentation import DeepLabV3_ResNet50_Weights, DeepLabV3_ResNet101_Weights
# 加载预训练模型
weights = DeepLabV3_ResNet50_Weights.DEFAULT
model = deeplabv3_resnet50(weights=weights)
model.eval()
print("torchvision DeepLabV3 (ResNet-50):")
print(f" 参数量: {sum(p.numel() for p in model.parameters()):,}")
print(f" 类别数: 21 (PASCAL VOC)")
print(f" 预训练: COCO + VOC")
print(f"\n 模型结构:")
print(f" backbone: ResNet-50 (修改了最后两个block)")
print(f" classifier: ASPP + 1×1 Conv")
print(f" aux_classifier: 辅助分类器(训练时)")
# 推理示例
# from PIL import Image
# img = Image.open('test.jpg')
# preprocess = weights.transforms()
# batch = preprocess(img).unsqueeze(0)
# with torch.no_grad():
# output = model(batch)['out']
# segmentation = output.argmax(1) # 每个像素的类别
return model
demo_deeplabv3_torchvision()
3.2 实战:DeepLab系列全面对比
python
def deeplab_comparison():
"""DeepLab v1→v3+ 全面对比"""
versions = {
'DeepLab v1': {
'年份': 2014,
'Backbone': 'VGG16',
'核心创新': '空洞卷积替代Pooling',
'多尺度': '✗',
'解码器': '✗',
'后处理': 'CRF',
'mIoU(VOC)': '71.6%',
},
'DeepLab v2': {
'年份': 2016,
'Backbone': 'ResNet-101',
'核心创新': 'ASPP',
'多尺度': '✓ (ASPP)',
'解码器': '✗',
'后处理': 'CRF',
'mIoU(VOC)': '79.7%',
},
'DeepLab v3': {
'年份': 2017,
'Backbone': 'ResNet-101',
'核心创新': '改进ASPP + Multi-Grid',
'多尺度': '✓ (改进ASPP)',
'解码器': '✗',
'后处理': '✗ (不需要)',
'mIoU(VOC)': '85.7%',
},
'DeepLab v3+': {
'年份': 2018,
'Backbone': 'Xception-65',
'核心创新': '编码器-解码器 + 空洞可分离卷积',
'多尺度': '✓ (ASPP)',
'解码器': '✓',
'后处理': '✗',
'mIoU(VOC)': '89.0%',
},
}
print("DeepLab系列进化:")
print(f"{'版本':<14s} {'年份':<6s} {'多尺度':<10s} {'解码器':<8s} {'mIoU':<10s} {'核心创新'}")
print('-' * 80)
for name, info in versions.items():
print(f"{name:<14s} {info['年份']:<6s} {info['多尺度']:<10s} {info['解码器']:<8s} {info['mIoU(VOC)']:<10s} {info['核心创新']}")
deeplab_comparison()
3.3 实战:U-Net vs DeepLab 选型
python
def unet_vs_deeplab():
"""两大分割架构的对比和选型"""
comparison = {
'U-Net': {
'架构': '对称编码器-解码器',
'跳跃连接': 'Concat (保留细节)',
'多尺度': '通过下采样实现',
'参数量': '可大可小(灵活)',
'训练数据': '少(几百张即可)',
'适用场景': '医学图像、小数据集、简单分割',
'端侧': '轻量版可部署',
'优点': '简单、数据效率高、细节好',
'缺点': '感受野有限、大物体分割弱',
},
'DeepLab v3+': {
'架构': '编码器-解码器 + ASPP',
'跳跃连接': 'Concat + 轻量解码器',
'多尺度': 'ASPP(显式多尺度)',
'参数量': '较大(backbone决定)',
'训练数据': '多(建议几千张+)',
'适用场景': '街景分割、复杂场景、多类别',
'端侧': 'MobileNet backbone可部署',
'优点': '多尺度好、精度高、大物体强',
'缺点': '参数量大、小数据集容易过拟合',
},
}
print("U-Net vs DeepLab 选型指南:")
print("=" * 50)
for name, info in comparison.items():
print(f"\n[{name}]")
for k, v in info.items():
print(f" {k}: {v}")
print("\n\n选型建议:")
print(" 小数据集 + 简单场景 → U-Net")
print(" 大数据集 + 复杂场景 → DeepLab v3+")
print(" 端侧部署 → U-Net轻量版 或 DeepLab+MobileNet")
print(" 医学图像 → U-Net(标配!)")
print(" 自动驾驶 → DeepLab v3+ 或 BiSeNet")
unet_vs_deeplab()
四、端侧AI分割进阶
4.1 空洞可分离卷积:端侧分割的加速器
python
class AtrousSeparableConv(nn.Module):
"""
空洞可分离卷积 (Atrous Separable Convolution)
深度可分离卷积 + 空洞卷积的组合
参数量大幅减少,但保持多尺度能力
标准卷积: K×K×C_in×C_out
可分离卷积: K×K×C_in + 1×1×C_in×C_out
参数量减少约 1/K² + 1/C_out ≈ 1/9
"""
def __init__(self, in_ch, out_ch, kernel_size=3, dilation=1):
super().__init__()
# Depthwise: 每个通道独立卷积(空洞)
self.depthwise = nn.Conv2d(
in_ch, in_ch, kernel_size,
padding=dilation, dilation=dilation,
groups=in_ch, bias=False
)
# Pointwise: 1×1卷积融合通道
self.pointwise = nn.Conv2d(in_ch, out_ch, 1, bias=False)
self.bn = nn.BatchNorm2d(out_ch)
self.relu = nn.ReLU(inplace=True)
def forward(self, x):
x = self.depthwise(x)
x = self.pointwise(x)
return self.relu(self.bn(x))
@staticmethod
def compare_params(in_ch, out_ch, k=3):
# 标准卷积
standard = k * k * in_ch * out_ch
# 可分离卷积
separable = k * k * in_ch + in_ch * out_ch
ratio = separable / standard if standard > 0 else 0
print(f"\n空洞可分离卷积参数量对比:")
print(f" 标准卷积: {standard:,}")
print(f" 可分离卷积: {separable:,}")
print(f" 比例: {ratio:.2%} (减少 {1-ratio:.1%})")
AtrousSeparableConv.compare_params(256, 256)
4.2 端侧分割模型选型
python
def edge_seg_comparison():
"""端侧AI分割模型对比"""
models = {
'U-Net (轻量)': {
'params': '~7M',
'mIoU': '中等',
'FPS': '30-60',
'场景': '医学/简单分割',
},
'DeepLab+MobileNetV3': {
'params': '~5M',
'mIoU': '中高',
'FPS': '20-50',
'场景': '通用分割',
},
'BiSeNet V2': {
'params': '~2M',
'mIoU': '中高',
'FPS': '60-150',
'场景': '实时分割',
},
'SegFormer B0': {
'params': '~3.7M',
'mIoU': '高',
'FPS': '15-30',
'场景': '精度优先',
},
'PIDNet S': {
'params': '~7M',
'mIoU': '高',
'FPS': '30-50',
'场景': '自动驾驶',
},
}
print("端侧AI分割模型选型:")
for name, info in models.items():
print(f"\n [{name}]")
for k, v in info.items():
print(f" {k}: {v}")
edge_seg_comparison()
五、常见坑点
坑1:空洞卷积的网格效应
python
# 问题:连续使用相同dilation的空洞卷积
# 有的像素始终没有被"看到" → 网格效应 (gridding effect)
# ❌ 错误:连续使用dilation=2
# 像素参与卷积的模式像棋盘格,有些像素永远被跳过
# ✅ 正确:使用不同dilation的混合
# 或者使用HDC (Hybrid Dilated Convolution) 原则:
# dilation rate的最大公约数=1
# 例如:(1, 2, 3) ✓ (2, 4, 8) ✗
print("空洞卷积设计原则:")
print(" HDC原则: 连续几层的dilation rate不能有公因子")
print(" ✓ (1, 2, 3) ✓ (1, 2, 5) ✓ (1, 3, 5)")
print(" ✗ (2, 4, 8) ✗ (2, 2, 2)")
坑2:ASPP中dilation rate太大
python
# dilation=24, 特征图只有32×32
# → 3×3卷积的有效感受野 = (3-1)×24+1 = 49
# → 感受野49 > 特征图32 → 退化为1×1卷积!
# 规则:dilation rate < 特征图尺寸 / 2
坑3:输出步长(output_stride)选错
python
# output_stride=8: 精度高,但速度慢、显存大
# output_stride=16: 精度略低,速度快、显存小
# 端侧推荐:output_stride=16
# 服务器推荐:output_stride=8
坑4:DeepLabV3的辅助损失
python
# DeepLabV3有一个辅助分类器(aux_classifier)
# 训练时需要计算aux_loss
# 推理时不需要
# 如果忘记加aux_loss,训练效果会差一些
# ✅ 训练时正确做法
# output = model(x)
# loss = criterion(output['out'], target) + 0.4 * criterion(output['aux'], target)
六、今日作业
- 手写ASPP:从零实现ASPP模块,理解5个分支的设计逻辑
- 空洞卷积:用不同dilation rate做实验,可视化感受野变化
- DeepLabV3:用torchvision的DeepLabV3跑一张自己的图片,查看分割结果
- 打卡 :评论区发你的分割结果,格式:"Day 32/100 打卡:DeepLab系列已掌握!"
今日小结
今天你学会了:
✅ 空洞卷积:不降分辨率,感受野翻倍
✅ DeepLab v1: 空洞卷积替代Pooling + CRF
✅ DeepLab v2: ASPP 多尺度上下文
✅ DeepLab v3: 改进ASPP + Multi-Grid + 去掉CRF
✅ DeepLab v3+: 编码器-解码器 + 空洞可分离卷积
✅ ASPP模块完整实现(5个分支)
✅ U-Net vs DeepLab 选型指南
✅ 空洞可分离卷积:端侧分割加速器
✅ 端侧AI分割模型选型
✅ 4个经典坑点(网格效应/dilation太大/output_stride/辅助损失)
明日预告
Day 33:目标跟踪基础 --- SORT与DeepSORT
卡尔曼滤波、匈牙利算法、ReID特征、SORT→DeepSORT
🔥 关注我,每天解锁一个端侧AI技能!
微信公众号:xxx | 小红书:xxx | CSDN:xxx
评论区打卡,一起坚持100天!
附:小红书图文版
封面标题建议:DeepLab系列 | 空洞卷积+ASPP,分割精度天花板 🎯
P1 --- 封面
标题:DeepLab系列
副标题:空洞卷积 / ASPP / 编码器-解码器
关键词:DeepLab / 空洞卷积 / 语义分割
P2 --- 空洞卷积:分割的魔法
普通卷积下采样 → 分辨率降低
空洞卷积 → 分辨率不变,感受野翻倍!
dilation=6: 感受野13×13
参数量不变!计算量不变!
P3 --- ASPP:多尺度并行
5个分支并行:
1×1 + d=6 + d=12 + d=18 + Global Pool
不同感受野,不同尺度信息
Concat融合 → 多尺度特征
P4 --- DeepLab进化史
v1: 空洞卷积 + CRF
v2: ASPP多尺度
v3: 改进ASPP + 去掉CRF
v3+: 编码器-解码器 + 可分离卷积
mIoU: 71.6% → 89.0%
P5 --- U-Net vs DeepLab
U-Net: 小数据集 + 简单场景
DeepLab: 大数据集 + 复杂场景
端侧: U-Net轻量 或 DeepLab+MobileNet
医学标配: U-Net
P6 --- 今日作业
手写ASPP + 跑DeepLabV3分割
评论区打卡 Day 32/100
标签:#DeepLab #空洞卷积 #ASPP #语义分割
CSDN发布提示:CSDN版本建议在空洞卷积部分放普通卷积vs空洞卷积的可视化对比图,在ASPP部分放5分支并行结构图,在DeepLab进化部分放版本对比表,在空洞可分离卷积部分放参数量对比图。