查看全课大纲(第 75 / 93 节)
- 1.计算机视觉概述
- 2.计算机视觉应用场景
- 3.困难与挑战
- 4.开源库介绍
- 5.图像显示与存储
- 6.CLAHE
- 7.腐蚀膨胀与开闭运算
- 8.滤波与边缘填充
- 9.各种滤波
- 10.图像金字塔
- 11.傅里叶变换
- 12.短时傅里叶变换与小波变换
- 13.颜色特征与颜色直方图
- 14.几何特征_边缘
- 15.几何特征-角点与斑点
- 16.局部特征-SIFT特征描述子
- 17.局部特征-SURF特征描述子
- 18.局部特征-ORB特征描述子
- 19.其他特征描述子
- 20.传统图像分割技术
- 21.GraphCut
- 22.GrabCut
- 23.人脸检测的传统方法
- 24.行人检测的传统方法HOG+SVM
- 25.行人检测DPM
- 26.人工神经网络的架构
- 27.目标函数与梯度下降
- 28.误差反向传播
- 29.可手算的神经网络
- 30.改进目标函数与克服梯度消散
- 31.神经网络之传统神经网络与深度网络的差异
- 32.各种梯度下降算法
- 33.如何选择合适的梯度下降算法
- 34.避免过适应
- 35.CNN的架构
- 36.CNN的误差反向传播_卷积层
- 37.CNN的误差反向传播_池化层
- 38.分类所用的数据集
- 39.Alexnet详解
- 40.VGG详解
- 41.GoogLeNet_v1
- 42.BatchNormalization
- 43.GoogLeNet_v3v4
- 44.ResNet
- 45.ResNeXt
- 46.CNN设计准则
- 47.图像检索与推土机距离
- 48.基于颜色相似性
- 49.基于形状相似性
- 50.基于纹理相似性
- 51.基于局部特征相似性
- 52.DL的二值哈希编码
- 53.高维空间中检索KDTree
- 54.高维空间中检索LSH
- 55.目标识别任务综述
- 56.RCNN
- 57.SPP-Net
- 58.Fast R-CNN
- 59.Faster R-CNN
- 60.R-CNN系列模型回顾
- 61.R-FCN的设计思路
- 62.R-FCN的实现
- 63.YOLOv1
- 64.YOLOv2_9000
- 65.YOLOv3
- 66.图像分割应用场景与数据集
- 67.上采样的实现方式
- 68.FCN网络架构的由来
- 69.FCN构建细节
- 70.DeepLabv1
- 71.CRF
- 72.DeepLabv2
- 73.DeepLabv3
- 74.特定的医学影像分割任务
- 75.U-Net
- 76.3D U-Net
- 77.V-Net
- 78.DenseNet
- 79.病理切片分析任务及数据集
- 80.病理切片分析
- 81.图说任务及其传统处理方法
- 82.深度语言模型与RNN
- 83.LSTM及其变种
- 84.NIC模型
- 85.SAT模型
- 86.图说的性能指标
- 87.判别模型与生成模型
- 88.KL散度与JS散度
- 89.VAE
- 90.GAN的基本原理
- 91.DCGAN与CGAN
- 92.Wasserstein GAN
- 93.SRGAN
U-Net
U-Net:医疗影像分割的经典网络
小象实战讲义 · 基于深度学习的计算机视觉
在上一节中,我们介绍了医学影像分割的任务与数据集。面对数据量有限但网络参数庞大的挑战,一个精心设计的网络架构至关重要。本节我们将深入讲解医疗影像分割领域的里程碑式网络——U-Net。我们将从经典的U-Net结构入手,分析其设计思想,并重点剖析一个在PROMISE12挑战赛中取得优异成绩的改进版U-Net,理解其如何通过架构优化、数据增强等技巧,在有限数据下实现高效分割。
💡 核心导读
- U-Net的设计哲学:理解其独特的“U”形编码器-解码器结构,以及跳跃连接(Skip Connection)如何解决FCN中上采样信息丢失的问题。
- 改进版U-Net的架构细节:学习一个实战中用于前列腺分割的U-Net变体,包括其卷积块、上采样方式和输出层设计。
- 医学影像分割的专用技巧:掌握曲率驱动去噪、弹性变形等针对医学影像特点的预处理与数据增强方法。
- Dice系数损失函数:学习在分割任务中常用的评估与优化指标,理解其与IoU(交并比)的关系。
U-Net:从FCN的不足到解决方案
在介绍FCN时,我们提到其一个主要不足是上采样过程相对“粗暴”。FCN-32s 通过单次 32 倍反卷积,把低分辨率的特征图直接放大到原始输入尺寸;FCN-16s/8s 虽然把 conv7 的 2 倍上采样结果分别与 pool4、pool3 的预测相加,做了一点跨层融合,但融合的层级很少,最终仍要靠一次大步长反卷积(16 倍或 8 倍)放大到输入尺寸。这种以“大步长一步到位”为主的放大方式,容易丢失细节信息,导致分割边界模糊。
U-Net正是针对这一问题进行了关键改进。其核心思路是渐进式上采样。U-Net不再一次性将特征图放大很多倍,而是通过一个对称的“U”形结构,逐步将特征图的尺寸恢复。在这个过程中,网络有更多的层来学习和重建细节。
更巧妙的是,U-Net引入了跳跃连接。在编码器(下采样路径)的每一层,特征图在池化缩小之前,会被复制并连接到解码器(上采样路径)对应尺寸的层。这样,解码器在进行上采样时,不仅能利用来自更深层、语义更丰富的特征,还能融合来自编码器同层、空间位置信息更精确的特征。这极大地有助于保护边界信息,让分割出的器官轮廓更加清晰。
典型的U-Net结构由三位研究者(Ronneberger, Fischer, Brox)提出,其名称来源于其网络形状:左侧的编码器路径使图像尺度逐渐减小、通道数增加(变“深”变“窄”),右侧的解码器路径使图像尺度逐渐恢复、通道数减少(变“浅”变“宽”),整体形状像一个字母“U”。
改进版U-Net:PROMISE12挑战赛实战解析
在实际的医疗影像分割任务中,研究者们往往会对经典U-Net进行改造以适应特定数据和任务。这里我们重点分析一个在PROMISE12前列腺分割挑战赛中取得第8名的开源实现(作者:mirzaevinom)。该方案的成功得益于三个关键部分:预处理、网络架构和数据增强。
1. 曲率驱动的图像去噪
医学影像(如MRI)常含有噪声,直接分割会影响精度。传统去噪方法如高斯滤波在平滑噪声的同时也会模糊边缘,而中值滤波效果虽好,但仍有改进空间。
该方案采用了一种基于等照度线曲率的去噪方法,其原理非常符合视觉直觉:
- 等照度线:可以理解为图像中亮度相同的点连成的线。在一个光滑的物体表面(如器官),其等照度线应该是平滑的曲线,即曲率较小。
- 噪声的影响:噪声会导致局部像素灰度值随机起伏,使得等照度线产生不规则的剧烈震荡,从而在局部形成曲率很大的线段。
- 去噪策略:基于上述观察,算法通过检测并调整那些曲率过大的等照度线片段,使其变得平滑,从而达到在去除噪声的同时,更好地保留原始边缘结构的目的。实践证明,这种方法在医学影像上取得了良好效果。
2. 网络架构详解
这个用于生物影像分割的改进版U-Net是一个全卷积网络,输入和输出都是图像,没有全连接层。这一点与FCN一致,在分割任务中,全卷积结构是主流。
其具体架构如下图所示(我们用文字和符号描述):
输入: (256, 256, 1) # 单通道MRI图像
|
|-- 下采样路径 (编码器)
| |
| |-- ConvBlock (输出: 256x256, 通道=8)
| |-- 2x2 MaxPooling
| |
| |-- ConvBlock (输出: 128x128, 通道=16)
| |-- 2x2 MaxPooling
| |
| |-- ConvBlock (输出: 64x64, 通道=32)
| |-- 2x2 MaxPooling
| |
| |-- ConvBlock (输出: 32x32, 通道=64)
| |-- 2x2 MaxPooling
| |
| |-- ConvBlock (输出: 16x16, 通道=128)
| |-- 2x2 MaxPooling
| |
| |-- ConvBlock (输出: 8x8, 通道=256)
| |-- 2x2 MaxPooling
| |
| `-- ConvBlock (输出: 4x4, 通道=512)
| -- 2x2 MaxPooling
| `-- ConvBlock (输出: 2x2, 通道=1024) # 最底层,高语义编码
|
`-- 上采样路径 (解码器)
|
|-- 上采样 + 与编码器对应层Copy & Concatenate
|-- ConvBlock (输出: 4x4, 通道=512)
|
|-- 上采样 + 与编码器对应层Copy & Concatenate
|-- ConvBlock (输出: 8x8, 通道=256)
|
|-- 上采样 + 与编码器对应层Copy & Concatenate
|-- ConvBlock (输出: 16x16, 通道=128)
|
|-- 上采样 + 与编码器对应层Copy & Concatenate
|-- ConvBlock (输出: 32x32, 通道=64)
|
|-- 上采样 + 与编码器对应层Copy & Concatenate
|-- ConvBlock (输出: 64x64, 通道=32)
|
|-- 上采样 + 与编码器对应层Copy & Concatenate
|-- ConvBlock (输出: 128x128, 通道=16)
|
`-- 上采样 + 与编码器对应层Copy & Concatenate
-- ConvBlock (输出: 256x256, 通道=8)
`-- 1x1 卷积 + Sigmoid激活 (输出: 256x256, 通道=1)输出:分割掩码(Mask),白色区域代表前列腺组织。
关键组件解析:
- 卷积块:不再是简单的卷积层堆叠。这里的
ConvBlock是一个残差块,内部包含两个3x3卷积,每个卷积后接批归一化和ReLU激活,中间还有Dropout层,最后通过短路连接与输入相加。这借鉴了ResNet的思想,有助于训练更深的网络。(课程口述为“ReLU 后再 BN”;实际 Keras 实现通常采用 Conv→BN→ReLU 的顺序,这里以标准实现为准。) - 上采样:通常使用转置卷积(反卷积)来实现。
- 跳跃连接:图中的“Copy & Concatenate”操作。它将编码器路径中对应尺度的特征图复制过来,与解码器上采样后的特征图沿通道维度拼接。这是信息融合的关键。
- 输出层:使用
1x1卷积将通道数降至1,然后通过Sigmoid激活函数。因为我们的任务是二分类(前景/背景),Sigmoid将每个像素的输出映射到[0,1]区间,可以理解为该像素属于前景(前列腺)的概率。通常以0.5为阈值进行二值化,得到最终的分割掩码。
这个网络共有7个下采样和7个上采样阶段,参数总量在数千万级(按本节通道图约 3000 万;原实现按完整配置报告 5000 万+)。对于PROMISE12仅50例的训练数据来说,直接训练这样的网络必然严重过拟合。因此,强大的数据增强至关重要。
3. 目标函数:Dice系数
在分割任务中,常用的损失函数是Dice系数损失。Dice系数用于度量两个样本集合的相似度,其定义为:
其中,A是模型预测的前景像素集合,B是真实标注的前景像素集合。Dice系数的取值范围是[0, 1],值越大表示重叠度越高,分割效果越好。
Dice系数与我们在目标检测中熟悉的交并比有密切关系:
在训练时,我们通常最小化1 - Dice作为损失函数,直接优化模型预测与真实标注之间的重叠区域。
4. 针对医学影像的数据增强
PROMISE12训练集仅50例,每例包含数十幅切片,按切片展开后总计约一千余幅图像。要训练一个数千万参数的深度网络,必须进行大规模数据增强。该方案除了使用Keras自带的平移、旋转、缩放等常规增强外,还特别采用了弹性变形技术。
为什么需要弹性变形? 人体内脏器官(如前列腺)并非刚体,其形状会因体位、重力等因素发生非线性形变。为了模拟这种自然变化,增强模型的泛化能力,我们需要一种能产生逼真形变的数据增强方法。
弹性变形的原理:
- 生成两个随机的小位移场(
dx,dy),分别代表每个像素在x和y方向上的随机偏移量。这些位移值通常从高斯分布中采样,并经过平滑处理。 - 将原始图像的坐标网格
(x, y)加上这个位移场,得到一个新的、扭曲的坐标网格(x', y')。 - 根据扭曲后的坐标网格
(x', y'),通过双线性插值等方法,从原始图像中采样像素值,生成变形后的新图像。 - 关键:对原始图像进行弹性变形时,必须同步对其对应的标注掩码(Mask)进行完全相同的变形操作,以保证数据的一致性。
下面我们用一个小示例演示弹性变形的核心思想(简化版,仅展示坐标偏移):
import numpy as np
# 创建一个简单的5x5网格作为“图像”的坐标索引
height, width = 5, 5
y_coords, x_coords = np.meshgrid(np.arange(height), np.arange(width), indexing='ij')
# 网格坐标
print("原始坐标网格 (y, x):")
print(y_coords)
print(x_coords)
# 1. 生成简单的随机位移场(这里为了演示,使用固定的小位移)
np.random.seed(42) # 固定随机种子确保结果可重现
# 在实际弹性变形中,位移场是平滑的随机场,这里简化为每个点加一个固定范围的随机数
displacement_y = np.random.uniform(-0.3, 0.3, (height, width))
displacement_x = np.random.uniform(-0.3, 0.3, (height, width))
print("\nY方向位移场:")
print(np.round(displacement_y, 2))
print("\nX方向位移场:")
print(np.round(displacement_x, 2))
# 2. 将原始坐标网格加上位移场,得到扭曲后的坐标网格
y_coords_deformed = y_coords + displacement_y
x_coords_deformed = x_coords + displacement_x
print("\n扭曲后的Y坐标网格:")
print(np.round(y_coords_deformed, 2))
print("\n扭曲后的X坐标网格:")
print(np.round(x_coords_deformed, 2))
# 注意:得到的是浮点数坐标,后续需要通过插值(如双线性插值)来获取该位置的像素值。
# 这个示例仅展示坐标如何变化。原始坐标网格 (y, x):
[[0 0 0 0 0]
[1 1 1 1 1]
[2 2 2 2 2]
[3 3 3 3 3]
[4 4 4 4 4]]
[[0 1 2 3 4]
[0 1 2 3 4]
[0 1 2 3 4]
[0 1 2 3 4]
[0 1 2 3 4]]
Y方向位移场:
[[-0.08 0.27 0.14 0.06 -0.21]
[-0.21 -0.27 0.22 0.06 0.12]
[-0.29 0.28 0.2 -0.17 -0.19]
[-0.19 -0.12 0.01 -0.04 -0.13]
[ 0.07 -0.22 -0.12 -0.08 -0.03]]
X方向位移场:
[[ 0.17 -0.18 0.01 0.06 -0.27]
[ 0.06 -0.2 -0.26 0.27 0.28]
[ 0.19 -0.12 -0.24 0.11 -0.04]
[-0.23 -0. -0.28 0.25 -0.14]
[ 0.1 -0.11 0.01 0.03 -0.19]]
扭曲后的Y坐标网格:
[[-0.08 0.27 0.14 0.06 -0.21]
[ 0.79 0.73 1.22 1.06 1.12]
[ 1.71 2.28 2.2 1.83 1.81]
[ 2.81 2.88 3.01 2.96 2.87]
[ 4.07 3.78 3.88 3.92 3.97]]
扭曲后的X坐标网格:
[[ 0.17 0.82 2.01 3.06 3.73]
[ 0.06 0.8 1.74 3.27 4.28]
[ 0.19 0.88 1.76 3.11 3.96]
[-0.23 1. 1.72 3.25 3.86]
[ 0.1 0.89 2.01 3.03 3.81]]可以看到,每个点的坐标(y, x)都根据位移场发生了偏移。在实际图像处理中,我们会根据这些新的、非整数的坐标,从原始图像中插值出像素值,从而生成一张看起来自然扭曲的新图像。通过这种方式,可以从有限的原始数据中生成近乎无限多的、形态各异的训练样本。
训练效果与局限
在PROMISE12数据集上,使用上述改进版U-Net、结合强大的数据增强(将数据量扩展至约15万张图像)进行训练后,取得了以下效果:
- 训练集正确率:95.93%
- 验证集正确率:87.08%
验证集上的正确率表明模型具备一定的泛化能力。通过可视化分割结果可以发现:
- 效果好的样例:模型预测的红色轮廓与医生标注的蓝色轮廓高度重合。
- 效果差的样例:两者差异较大。分析原因,这些往往出现在前列腺组织的顶层或底层切片。因为这些位置的器官截面与中部常见的截面形态差异较大,且在训练数据中出现的频次较低,导致模型未能充分学习到这些“特殊”形态的特征。
这揭示了医学影像分割的一个常见挑战:器官形态的连续性和变异性。解决思路包括收集更多包含边界案例的数据,或设计能更好建模3D上下文信息的网络(如后续会讲到的3D U-Net)。
📝 动手练一练
- 概念辨析:U-Net中的“跳跃连接”与ResNet中的“短路连接”在目的和实现方式上有何异同?
- 代码实践:假设我们有一个4x4的二进制分割掩码
mask_true和模型预测的mask_pred(经过Sigmoid后,值在0~1之间)。请计算它们之间的Dice系数。设定阈值为0.5将预测值二值化。
👉 点击查看参考答案
异同点分析:
- 相同点:两者都旨在解决深度网络训练中的信息流通问题,通过建立捷径来缓解梯度消失/爆炸,帮助网络学习恒等映射。
- 不同点:
- 目的:ResNet的短路连接主要为了让网络更容易优化,解决深度网络的退化问题。U-Net的跳跃连接主要为了融合多尺度特征,将编码器路径中包含丰富空间细节的浅层特征,与解码器路径中包含高级语义的深层特征结合起来,以精确恢复目标边界和位置。
- 实现方式:ResNet通常采用逐元素相加。U-Net采用通道维度拼接。相加会融合信息但可能丢失一部分,而拼接保留了来自编码器的全部特征信息,使解码器能够自主选择有用的部分。
Dice系数计算:
import numpy as np
# 模拟真实标注和模型预测(sigmoid输出)
mask_true = np.array([[1, 1, 0, 0],
[1, 1, 0, 0],
[0, 0, 1, 1],
[0, 0, 1, 1]], dtype=np.float32)
mask_pred_sigmoid = np.array([[0.9, 0.8, 0.3, 0.1],
[0.7, 0.6, 0.2, 0.1],
[0.2, 0.1, 0.8, 0.9],
[0.1, 0.05, 0.7, 0.85]], dtype=np.float32)
# 以0.5为阈值进行二值化
threshold = 0.5
mask_pred = (mask_pred_sigmoid > threshold).astype(np.float32)
print("二值化后的预测掩码:")
print(mask_pred)
# 计算交集,以及|A|与|B|之和
intersection = np.sum(mask_true * mask_pred) # 对应位置相乘,只有都为1时才为1
sum_true_pred = np.sum(mask_true) + np.sum(mask_pred)
# 计算Dice系数
dice = (2.0 * intersection) / (sum_true_pred + 1e-7) # 加一个极小值防止除零
print(f"\nDice系数: {dice:.4f}")二值化后的预测掩码:
[[1. 1. 0. 0.]
[1. 1. 0. 0.]
[0. 0. 1. 1.]
[0. 0. 1. 1.]]
Dice系数: 1.0000在这个完美预测的例子中,Dice系数为1。你可以尝试修改mask_pred_sigmoid的值,观察Dice系数如何变化。
本章小结
本节我们深入剖析了医疗影像分割的经典网络U-Net及其一个成功的改进版本。我们看到了U-Net如何通过其独特的“U”形对称结构和跳跃连接,解决了FCN在细节恢复上的不足。更重要的是,我们学习了一个完整的实战方案,它告诉我们,在数据稀缺的医疗领域取得成功,不仅需要优秀的网络架构,还需要:
- 针对性的预处理(如曲率去噪)。
- 精心设计的网络模块(如融入残差思想的卷积块)。
- 强大的、符合领域特点的数据增强(如弹性变形)。
- 合适的损失函数(Dice系数损失)。
这些策略的组合,使得深度学习模型能够在有限的标注数据上,学习到鲁棒的特征表示,最终实现精准的分割。
今天,U-Net 仍是医学影像分割的默认基线,并演进为 3D U-Net、U-Net++、nnU-Net、Swin-UNet 等更强变体;Dice 损失与弹性变形也仍是医学分割中的常用手段。
📋 行动清单
- 在白纸上画出经典U-Net的结构图,标出编码器、解码器、跳跃连接和各层特征图尺寸的变化。
- 理解Dice系数与IoU的公式关系,并尝试手动推导它们之间的转换公式。
- 思考:如果让你设计一个用于皮肤镜图像(2D)色素痣分割的网络,你会借鉴本节U-Net方案的哪些部分?可能需要做哪些调整?
—— 小象教研组