图像分割是像素级的视觉理解——对每个像素分类。本课从语义分割到实例分割,理解U-Net等经典架构。
| 类型 | 任务 | 输出 |
|---|---|---|
| 语义分割 | 每个像素分类 | H×W类别图 |
| 实例分割 | 区分同类不同实例 | H×W实例ID |
| 全景分割 | 语义+实例 | 两者结合 |
编码器(下采样):捕获上下文信息
解码器(上采样):恢复空间分辨率
跳跃连接:融合细节和语义信息
普通卷积:大图→小图(下采样)
转置卷积:小图→大图(上采样)
输出尺寸 = (输入-1)×stride - 2×padding + kernel_size
import torch
import torch.nn as nn
# U-Net实现
class DoubleConv(nn.Module):
def __init__(self, in_ch, out_ch):
super().__init__()
self.conv = nn.Sequential(
nn.Conv2d(in_ch, out_ch, 3, padding=1), nn.BatchNorm2d(out_ch), nn.ReLU(),
nn.Conv2d(out_ch, out_ch, 3, padding=1), nn.BatchNorm2d(out_ch), nn.ReLU(),
)
def forward(self, x):
return self.conv(x)
class UNet(nn.Module):
def __init__(self, in_ch=1, out_ch=1):
super().__init__()
# 编码器
self.enc1 = DoubleConv(in_ch, 64)
self.enc2 = DoubleConv(64, 128)
self.enc3 = DoubleConv(128, 256)
self.pool = nn.MaxPool2d(2)
# 瓶颈层
self.bottleneck = DoubleConv(256, 512)
# 解码器
self.up3 = nn.ConvTranspose2d(512, 256, 2, stride=2)
self.dec3 = DoubleConv(512, 256)
self.up2 = nn.ConvTranspose2d(256, 128, 2, stride=2)
self.dec2 = DoubleConv(256, 128)
self.up1 = nn.ConvTranspose2d(128, 64, 2, stride=2)
self.dec1 = DoubleConv(128, 64)
self.final = nn.Conv2d(64, out_ch, 1)
def forward(self, x):
# 编码
e1 = self.enc1(x) # 64
e2 = self.enc2(self.pool(e1)) # 128
e3 = self.enc3(self.pool(e2)) # 256
# 瓶颈
b = self.bottleneck(self.pool(e3)) # 512
# 解码 + 跳跃连接
d3 = self.up3(b)
d3 = self.dec3(torch.cat([d3, e3], dim=1))
d2 = self.up2(d3)
d2 = self.dec2(torch.cat([d2, e2], dim=1))
d1 = self.up1(d2)
d1 = self.dec1(torch.cat([d1, e1], dim=1))
return self.final(d1)
# 测试
model = UNet(in_ch=1, out_ch=2)
x = torch.randn(2, 1, 64, 64)
out = model(x)
params = sum(p.numel() for p in model.parameters())
print(f"U-Net: 输入{list(x.shape)} → 输出{list(out.shape)}, 参数量={params:,}")
# 转置卷积验证
print("\n=== 转置卷积尺寸变化 ===")
x_small = torch.randn(1, 1, 4, 4)
conv_trans = nn.ConvTranspose2d(1, 1, 3, stride=2, padding=1)
out_up = conv_trans(x_small)
print(f"转置卷积(4×4→{out_up.shape[-2]}×{out_up.shape[-1]})")
# 不同上采样方法对比
print("\n=== 上采样方法对比 ===")
x_test = torch.randn(1, 64, 4, 4)
methods = {
"最近邻插值": nn.Upsample(scale_factor=2, mode='nearest'),
"双线性插值": nn.Upsample(scale_factor=2, mode='bilinear', align_corners=False),
"转置卷积": nn.ConvTranspose2d(64, 64, 2, stride=2),
}
for name, method in methods.items():
out = method(x_test)
params = sum(p.numel() for p in method.parameters()) if hasattr(method, 'parameters') else 0
print(f"{name}: {list(x_test.shape)} → {list(out.shape)}, 参数量={params:,}")
# 分割损失:Dice Loss
def dice_loss(pred, target, smooth=1.0):
pred = torch.sigmoid(pred)
intersection = (pred * target).sum()
return 1 - (2 * intersection + smooth) / (pred.sum() + target.sum() + smooth)
pred = torch.randn(1, 1, 8, 8)
target = torch.randint(0, 2, (1, 1, 8, 8)).float()
print(f"\nDice Loss: {dice_loss(pred, target).item():.4f}")
print(f"BCE Loss: {nn.BCEWithLogitsLoss()(pred, target).item():.4f}")
本课的核心知识点构成了CNN学习的重要一环。让我们从更高维度审视这些知识之间的关系:
⚠️ 初学者常犯的错误:
💡 最佳实践:
深度学习是一个整体——每一课的知识都不是孤立的:
💡 继续深入的学习路径:
生成简单的圆形/矩形分割数据,训练U-Net。
对比两种损失函数在类别不平衡分割任务上的效果。
理解空洞空间卷积池化金字塔(ASPP)的实现和作用。
⚠️ 需要避免的典型错误:
💡 提升模型性能的系统方法:
理解本课内容需要将其置于更大的知识网络中。每个核心概念都不是孤立的,它们相互关联、相互支撑:
建立正确的直觉比记住公式更重要:
1. 维度直觉:高维空间中,数据分布与低维直觉截然不同(维度诅咒)
2. 信息瓶颈:神经网络逐层压缩信息,保留任务相关的、丢弃无关的
3. 流形假设:高维数据实际分布在低维流形上,学习即发现流形结构
4. 偏差-方差权衡:简单模型欠拟合(高偏差),复杂模型过拟合(高方差)
本课作为CNN阶段的核心内容,与前后课程紧密关联:
| 关联课程 | 关联点 | 协同效应 |
|---|---|---|
| 前序课程 | 提供了本课的基础知识 | 循序渐进的理解 |
| 后续课程 | 本课内容是后续的基础 | 逐步构建能力 |
| 平行课程 | 同一阶段的互补知识 | 多角度深入理解 |
| 实战项目 | 综合运用所有知识 | 理论与实践结合 |
💡 准备面试时,确保能回答以下问题:
掌握本课内容后,可以通过以下方式继续深入:
图像分割是像素级的理解——你掌握了精细视觉分析!