- 教程
- 人工智能
- 机器学习
- 深度学习
【免费下载链接】AI-For-Beginners
12 Weeks, 24 Lessons, AI for All!
本指南基于 AI For Beginners 课程第 12 课「分割(Segmentierung)」展开,完整讲解语义分割与实例分割的概念区别、编码器-解码器网络架构、分割任务特有的损失函数设计,并带领读者基于 PH² 皮肤镜图像数据集训练真实的分割网络(PyTorch 与 TensorFlow 双版本),最后延伸到人体分割实战练习。读完本文,你将理解分割网络"输入图像 → 输出逐像素掩码"的工作原理,并能直接运行仓库中的 Notebook 复现训练流程。
从目标检测到像素级分类:为什么需要分割
在 目标检测 任务中,我们通过预测边界框(bounding box)来定位图像中的物体。但许多场景需要远比边界框精确得多的定位:医学影像中需要标出病灶的精确轮廓,自动驾驶需要区分道路与行人的每个像素。这种对图像中每个像素赋予语义标签的任务,就是分割(Segmentation)。
分割可以看作一种像素级分类(Pixel Classification):对于图像的每一个像素,都要预测它的类别("背景"本身就是其中一类)。根据输出粒度的不同,分割算法分为两大类:
| 类别 | 说明 |
|---|---|
| 语义分割(Semantic Segmentation) | 只给出每个像素的类别,不区分同一类中的不同个体。例如图片里有 10 只羊,语义分割把所有羊都标成"羊"这一类。 |
| 实例分割(Instance Segmentation) | 在像素分类的基础上把同一类别进一步拆分为不同实例。同样 10 只羊,实例分割会区分出 10 个不同的对象。 |
分割网络的统一结构:Encoder-Decoder
虽然存在多种分割神经网络架构,但它们都遵循同一种结构。从某种意义上说,这与你在自编码器课程中学到的结构非常相似——区别在于:自编码器的目标是重构原始图像,而分割网络的目标是重构一张掩码(Mask)图像。
一个分割网络由两个部分组成:
- 编码器(Encoder):从输入图像中提取特征。通常由一系列卷积层与池化层构成,逐级压缩空间尺寸、加深通道数。
- 解码器(Decoder):将编码器提取的特征逐步上采样,还原为与输入同尺寸的掩码图像,其通道数等于类别数(多类分割输出 one-hot 编码的多通道图;二值分割输出单通道图)。
分割专用的损失函数:交叉熵与 BCE
在经典自编码器中,我们用均方误差(MSE)衡量两张图像像素之间的相似度;但在分割任务中,目标掩码图像的每个像素代表的是类别编号(沿第三维进行 one-hot 编码),因此需要采用专门面向分类问题的损失函数——交叉熵损失(Cross-Entropy Loss),并对所有像素取平均。
当掩码是二值的(例如我们的痣分割案例,只有"病灶/背景"两类),则使用二值交叉熵损失(BCE,Binary Cross-Entropy Loss)。
✅ 补充知识:one-hot 编码是一种把类别标签编码成长度等于类别数的向量的方法,其中只有对应类别的位置为 1,其余为 0。在分割任务中,每个像素的类别标签都会被编码成这样的向量,网络输出因此也自然地采用多通道表示。
实战:医学影像中的痣分割
本课将训练一个网络,从医学图像中识别人类色素痣(Nävi,即俗话说的"痣")。我们使用PH² 皮肤镜图像数据库(PH² Database of dermoscopy images)作为数据源:
- 共200 张图像,分属三类:典型痣(typical nevus)、非典型痣(atypical nevus)与黑色素瘤(melanoma);
- 每张图像都配有对应的掩码,精确勾勒出痣的轮廓;
- 我们要训练模型,把任意一颗痣从背景中分割出来。
这类技术尤其适合医学影像场景。除了皮肤病变分割,你还能联想到哪些现实世界的应用?例如遥感图像中的地物分割、自动驾驶场景解析、卫星云图分析等。
动手实验:两种主流分割架构
仓库中提供了两个可直接运行的 Jupyter Notebook,分别使用 PyTorch 与 TensorFlow 实现,用于学习不同语义分割架构并观察它们的实际效果:
- Semantic Segmentation Pytorch
- Semantic Segmentation TensorFlow
源码级解析(一):SegNet——最朴素的编码器-解码器
SegNet是最简单的编码器-解码器架构:编码器使用带卷积与池化的标准 CNN 逐级提取特征,解码器使用带卷积与上采样的"反卷积 CNN"逐步还原分辨率;同时依靠批归一化(Batch Normalization)来保证多层网络可以成功训练。
从 PyTorch 实现 的代码可以看到它的完整结构:
class SegNet(nn.Module): def __init__(self): super().__init__() # Encoder: 3→16→32→64→128,每级 Conv+ReLU+BN+MaxPool(2x2) self.enc_conv0 = nn.Conv2d(in_channels=3, out_channels=16, kernel_size=(3,3), padding=1) ... self.pool0 = nn.MaxPool2d(kernel_size=(2,2)) # ... enc_conv1(16→32)、enc_conv2(32→64)、enc_conv3(64→128) # 瓶颈层:128→256 self.bottleneck_conv = nn.Conv2d(in_channels=128, out_channels=256, kernel_size=(3,3), padding=1) # Decoder: 双线性上采样 + 卷积,256→128→64→32→1 self.upsample0 = nn.UpsamplingBilinear2d(scale_factor=2) ... self.dec_conv3 = nn.Conv2d(in_channels=32, out_channels=1, kernel_size=(1,1)) self.sigmoid = nn.Sigmoid()PyTorch 版本的训练配置(来自 Notebook 代码)为:
device = 'cuda:0' if torch.cuda.is_available() else 'cpu' train_size = 0.9 # 90% 用于训练 lr = 1e-3 # Adam 学习率 weight_decay = 1e-6 # 权重衰减 batch_size = 32 epochs = 30 model = SegNet().to(device) optimizer = optim.Adam(model.parameters(), lr=lr, weight_decay=weight_decay) loss_fn = nn.BCEWithLogitsLoss()由于是二值掩码(病灶/背景),损失函数使用nn.BCEWithLogitsLoss()。训练循环按常规方式定义:每个 epoch 中,把图像与标签送入device,计算损失后zero_grad → backward → step,再用测试集在torch.no_grad()下评估 loss。Notebook 中 30 个 epoch 训练完成后,train loss 约为 0.593、test loss 约为 0.577;随后用(model(img) > 0.5)的阈值化方式将网络输出转成二值掩码并可视化。
TensorFlow 版本(SemanticSegmentationTF.ipynb)的 SegNet 是同样的思路,用 Keras API 表达:编码器为Conv2D + BatchNormalization + Activation('relu') + MaxPool2D,解码器为UpSampling2D(interpolation='bilinear') + Conv2D,最终输出 1 通道;超参数略有不同:
train_size = 0.8 lr = 3e-4 weight_decay = 8e-9 batch_size = 64 epochs = 100 loss_fn = losses.BinaryCrossentropy(from_logits=True) model.compile(loss=loss_fn, optimizer=optimizer)训练日志显示 SegNet 的 val_loss 从第 1 个 epoch 的 0.696 逐步下降到第 100 个 epoch 的 0.445。
源码级解析(二):U-Net——跳连带来的精度提升
SegNet 结构很自然,但它不是最精确的:金字塔式 CNN 在压缩特征空间分辨率的同时丢失了空间精度,导致重建时难以准确还原像素位置。这引出了在编码器与解码器卷积层之间建立跳连(Skip Connections)的思想,即著名的U-Net架构。
跳连在每个卷积层级把编码器提取的特征直接拼接到对应层级的解码器上,帮助网络不丢失该层级来自原始输入的细节信息。仓库中的 U-Net 实现印证了这一点——解码器的输入通道数包含了拼接(torch.cat)进来的编码器特征:
# U-Net 解码器:384 = 256(上采样后) + 128(编码器第4层特征 cat3) self.dec_conv0 = nn.Conv2d(in_channels=384, out_channels=128, kernel_size=(3,3), padding=1) ... # 前向传播中:把上采样结果与同层级编码器输出拼接 d0 = self.dec_bn0(self.dec_act0(self.dec_conv0(torch.cat((self.upsample0(b), cat3), dim=1)))) d1 = self.dec_bn1(self.dec_act1(self.dec_conv1(torch.cat((self.upsample1(d0), cat2), dim=1))))在 PyTorch 版本中,U-Net 与 SegNet 共用同一训练函数和 BCE 损失,30 个 epoch 后 train loss ≈ 0.595、test loss ≈ 0.572。而在 TensorFlow 版本中,100 个 epoch 后 U-Net 的 val_loss 降至0.1525(远低于 SegNet 的 0.445),直观体现了跳连对分割精度的提升。
提示:本课为了教学清晰使用了较简单的 CNN 编码器;实际应用中 U-Net 的编码器也可以替换为更强的特征提取骨干,例如 ResNet-50。
更正式的评估指标:Pixel Accuracy
除了可视化对比目标掩码与预测掩码,分割模型还有正式的量化评估指标。其中最容易理解的是像素准确率(Pixel Accuracy)——被正确分类的像素所占的百分比,即预测掩码与真实掩码逐像素比对后的正确率。
作业挑战:人体分割实战
分割在视频制作中有典型应用:例如天气预报节目经常需要把人像从摄像头画面中抠出来,叠加到其他背景素材上。传统做法使用色度键(chroma key)技术——让被摄者站在纯色背景前拍摄,再移除背景色。而在本课程的 lab 作业 中,我们将改用神经网络模型直接切出人体剪影:
- 从 Kaggle 手动下载并解压Segmentation Full Body MADS Dataset(包含 1192 张图像及其掩码);
- 打开启动 Notebook BodySegmentation.ipynb,其代码展示了数据组织方式:数据集目录下
images/与masks/两个子目录存放同名文件,plt.imread同时读取图像与掩码进行可视化(示例输出为 512×384 的图像-掩码对); - 复用本课学到的分割网络(SegNet / U-Net)完成训练与推理。
人体分割只是面向人像图像的常见任务之一。另一类重要任务是骨架检测(skeleton detection)与姿态识别(pose detection),你可以尝试使用 OpenPose 库来观察姿态识别是如何实现的。
总结
分割是一种非常强大的图像分类技术:它超越了边界框,实现了像素级分类,在医学影像等领域有着广泛应用。本课的核心要点可归纳为:
- 分割 = 逐像素分类,语义分割不区分同类实例,实例分割进一步拆分实例;
- 主流分割网络 = 编码器(提特征)+ 解码器(生成掩码);
- 分割的损失函数是逐像素平均的交叉熵(二值场景用 BCE),而非 MSE;
- SegNet 是最简基线,U-Net 通过跳连显著提升分割精度;
- 使用 PH² 数据集可完整复现皮肤病变分割训练流程,并使用像素准确率评估效果。
如需进一步探索,可查看本课架构参考图 segnet.png 与 unet.png,以及课程目录 4-ComputerVision 下的其他视觉任务章节。更完整的课件与练习题可参阅仓库根目录 README.md。
- 教程
- 人工智能
- 机器学习
- 深度学习
【免费下载链接】AI-For-Beginners
12 Weeks, 24 Lessons, AI for All!
相关推荐
AI-For-Beginners 语义分割实战指南:从像素级分类到医学图像分割(SegNet 与 U-Net 完整实现)
AI For Beginners 语义分割实战指南:从像素级分类到医学图像分割(SegNet 与 U Net 完整实现) 本篇技术指南基于 AI For Beg
教程人工智能机器学习深度学习语义分割实战指南:用PyTorch实现像素级图像分类
语义分割实战指南:用PyTorch实现像素级图像分类 语义分割是计算机视觉中一种重要的像素级图像分类技术,它能将图像中的每个像素分配到特定的类别。在深度学习领域
示例工程教程AI-For-Beginners 图像分割实战指南:从像素级分类到医学影像病变分割
AI For Beginners 图像分割实战指南:从像素级分类到医学影像病变分割 图像分割(Segmentation)是计算机视觉中比目标检测更进一步的任务:
教程人工智能机器学习深度学习
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考