深度学习--图像分割UNet介绍及代码分析
UNet介绍
参考文献
以UNet为核心的网络架构解析
基本架构设计
UNet的工作原理概述
输入端的处理流程解析
编码器模块(下采样模块)
使用卷积层进行特征提取
实现信息的多尺度表示
中间特征图的生成与融合
解码器模块(上采样模块)
通过反卷积或 transpose卷积恢复空间维度
实现特征图的重建与融合
输出层的设计与激活函数应用
- 代码解析
-
- UNet++与UNet之间的关联
- 上采样组件——unetUp
- 用于图像分割的卷积神经网络架构组件——Unet
-
-
类的具体定义
-
构造函数
-
上采样组件
-
特别适用于ResNet50架构的额外上采样卷积层(仅在ResNet50中)
-
最终卷积层
-
正向传播过程
-
调节主干网络状态
-
完整代码
-
参考
UNet是一种用于医学图像分割的卷积神经网络模型。(输入文章名自行查找)参考博客:
UNet网络介绍
整体架构

该网络架构基于最小分辨率(假设为32x32像素)设计,在各个层级中使用多通道特征图进行信息传递与融合。各通道数量标注在蓝框顶端位置。
各蓝框代表多通道特征层。
其中,
深色方块表示经过3×3卷积后的结果层,
灰色方块标记该处下采样后的区域,
红色方块表明最大池化处理的结果,
绿色方块表明反向扩散时所采用的比例因子,
青色方块则用于提取特定尺度下的细节信息。
因该网络结构如同字母U而得名。
参考博客(网络结构很清晰)
UNet过程
输入
U-Net 的输入是一张单色的图像,默认尺寸为572×572像素。经过连续的有效卷积操作后会导致图片尺寸不断缩小,在处理前需对图像进行镜像补全以防止信息丢失。
编码器(下采样)
U-Net 编码器中的输入图像经卷积层提取特征,这些卷积层多采用 3×3 大小的卷积核,逐步提取空间信息并缩减尺寸.
随后,通过池化层(通常是最大池化)将图像的空间维度进一步缩减,如从 572×572 下降到 286×286.
这一过程会循环执行,在每一次迭代中既减少空间尺寸又提升通道数量.
中间特征表示
- 在编码器的最深一层中进行处理后,在此层我们生成了一个中间特征表示(通常称为深层特征编码),其本质上是一个复杂的多维表征。
- 该模块通过提取图像中的高层次抽象特性信息,并结合这些信息进行进一步分析与处理。
解码器(上采样)
- U-Net 的编码器模块能够恢复中间特征表示并逐步实现空间维度的重建。
- 在编码器模块中使用上采样操作使特征张量的空间维度得以扩展。
- 接着,在编码器模块中使用卷积层对低级和高级特征进行融合。
- 最终,在编码器模块中经过具有64个输出通道的卷积层对分割结果进行深度映射。
输出
- U-Net 生成的分割图尺寸一致于输入图(常为572x572像素),该分割图划分为若干区域,在这些区域中各区域被赋予特定的标签或类别。
- 在该分割图中每个像素均分配到预设的类别中;通过这一过程可明确确定每个像素所属的组织结构或解剖学区域。这种图可应用于医学影像识别任务,在此任务中可识别出如肿瘤和器官等关键解剖结构。
代码详解
unetUP和Unet关系
unetUp :
-
UNetUpClass, 也就是著名的PyTorch自定义模块(继承于nn.Module),专门用于实现U-Net模型中的 upsampling过程。 -
它是一个接收两个空间特征图 input1 和 input2 的模块,在完成upsampling后会与原始特征图进行拼接融合,并执行卷积操作生成新的空间信息。
-
在U-Net架构中,“UNetUp”的作用就是把低分辨率的空间信息放大至与高分辨率区域相匹配的空间尺度。
Unet :
Unet 是 U-Net 模型的核心组件,在其架构中包含了多个 UNet 模块。
根据所选的 backbone(可以选择 VGG 或 ResNet-50),Unet 会采用相应的基础网络来提取图像特征。
在 Unet 的前馈传播流程中包含了多层次特征融合、上采样以及卷积操作等关键步骤,并最终实现语义分割目标。
上采样模块——unetUp
这段代码中定义了一个名为unetUp的类,在UNet架构中扮演着上采样模块的角色。其中一个是负责将低分辨率特征图经过上采样处理后与高分辨率特征图进行融合组合,从而生成更高分辨率的输出图像。
# 定义unetUp类,unetUp类继承自nn.Module,是PyTorch中所有神经网络模块的基类。
class unetUp(nn.Module):
# 初始化方法
in_size 和 out_size 是输入和输出通道的数量。
self.conv1 和 self.conv2 是两个二维卷积层,卷积核大小为3,填充为1。
self.up 是一个最近邻插值的上采样层,放大倍数为2。
self.relu 是一个ReLU激活函数。
def __init__(self, in_size, out_size):
super(unetUp, self).__init__()
self.conv1 = nn.Conv2d(in_size, out_size, kernel_size=3, padding=1)
self.conv2 = nn.Conv2d(out_size, out_size, kernel_size=3, padding=1)
self.up = nn.UpsamplingNearest2d(scale_factor=2)
self.relu = nn.ReLU(inplace=True)
# 前向传播方法
# inputs1 和 inputs2 是前向传播时的输入张量。
# torch.cat([inputs1, self.up(inputs2)], 1) 将 inputs1 和上采样后的 inputs2 在通道维度上拼接。
# outputs = self.conv1(outputs) 对拼接后的张量进行第一次卷积。
# outputs = self.relu(outputs) 对卷积结果应用ReLU激活函数。
# outputs = self.conv2(outputs) 对激活后的张量进行第二次卷积。
# outputs = self.relu(outputs) 再次应用ReLU激活函数。最终返回处理后的 outputs。
# 这段代码的主要功能是将低分辨率特征图上采样并与高分辨率特征图结合,经过两次卷积和激活函数处理后,生成更高分辨率的输出特征图。
def forward(self, inputs1, inputs2):
outputs = torch.cat([inputs1,self.up(inputs2)],1)
outputs = self.conv1(outputs)
outputs = self.relu(outputs)
outputs = self.conv2(outputs)
outputs = self.relu(outputs)
return outputs
python

用于图像分割的卷积神经网络(CNN)架构模块——Unet
这段代码中定义了名为 Unet 的一个类,在图像分割任务中展现出强大的性能优势。该类不仅支持多种基础网络结构(如VGG16和ResNet50),还集成了一种高效的上采样机制来提升输出质量。此外,在实现过程中特别注意了注释部分的专业性和清晰度。
类的定义
class Unet(nn.Module):
python
Unet 类继承自 nn.Module,这是PyTorch中所有神经网络模块的基类。
初始化方法
def __init__(self, num_classes=21, pretrained=False, backbone='vgg'):
super(Unet, self).__init__()
if backbone == 'vgg':
self.vgg = VGG16(pretrained=pretrained)
in_filters = [192, 384, 768, 1024]
elif backbone == "resnet50":
self.resnet = resnet50(pretrained=pretrained)
in_filters = [192, 512, 1024, 3072]
else:
raise ValueError('Unsupported backbone - `{}`, Use vgg, resnet50.'.format(backbone))
out_filters = [64, 128, 256, 512]
python

num_classes是输出类别的数量。pretrained指示是否使用预训练的权重。backbone指定使用的骨干网络,可以是VGG16或ResNet50。- 根据选择的骨干网络,初始化相应的网络并设置输入过滤器的数量。
in_filters(输入通道数):在卷积神经网络(CNN)中,in_filters 表示输入图像的通道数或特征图的数量。在输入层,如果是灰度图片,那就只有一个 feature map;如果是彩色图片,一般就是 3 个 feature map(对应红、绿、蓝通道)。在其他层,每个卷积核(也称为过滤器)与上一层的每个 feature map 做卷积,产生下一层的一个 feature map。因此,如果有 N 个卷积核,下一层就会产生 N 个 feature map。out_filters(输出通道数):在卷积神经网络中,out_filters 表示卷积核的数量或输出的特征图数量。卷积核的个数决定了下一层的 feature map 数量。每个卷积核可以提取一种特征,并生成一个新的特征图。在多层卷积网络中,下一层的卷积核的通道数等于上一层的 feature map 数量。如果通道数不相等,就无法继续进行卷积操作。
上采样模块
# upsampling
self.up_concat4 = unetUp(in_filters[3], out_filters[3])
self.up_concat3 = unetUp(in_filters[2], out_filters[2])
self.up_concat2 = unetUp(in_filters[1], out_filters[1])
self.up_concat1 = unetUp(in_filters[0], out_filters[0])
python
- 建立四个上采样组件, 每个组件负责将低分辨率特征图放大至更高分辨率并与其对应的高分辨率特征图进行融合。
self.up_concat4, self.up_concat3, self.up_concat2 , self.up_concat1作为上采样操作的一个组成部分, 它们通过级联不同层次的特征图来丰富表征
额外的上采样卷积层(仅用于ResNet50)
if backbone == 'resnet50':
self.up_conv = nn.Sequential(
# 使用双线性插值(nn.UpsamplingBilinear2d)将特征图的大小放大两倍
nn.UpsamplingBilinear2d(scale_factor=2),
# 通过两个卷积层对特征图进行处理,以获得更好的特征表示
nn.Conv2d(out_filters[0], out_filters[0], kernel_size=3, padding=1),
# 使用 nn.ReLU() 激活函数来确保非线性变换
nn.ReLU(),
nn.Conv2d(out_filters[0], out_filters[0], kernel_size=3, padding=1),
nn.ReLU(),
)
else:
self.up_conv = None
python

- 如果使用ResNet50作为骨干网络,定义一个额外的上采样卷积层。
最终卷积层
# self.final 是一个卷积层,用于生成最终的输出。它将高分辨率的特征图映射到类别数(num_classes)
self.final = nn.Conv2d(out_filters[0], num_classes, 1)
python
- 定义一个最终的卷积层,将输出通道数转换为类别数。
前向传播方法
阐述模型如何执行前向传播步骤,并说明输入数据依次经过网络中的各个层次进行计算从而得到输出结果
def forward(self, inputs):
# 根据 self.backbone 的值,选择不同的模型(VGG 或 ResNet-50)进行前向传播。
# 通过卷积层和池化层对输入数据进行处理,得到特征图 feat1、feat2、feat3、feat4 和 feat5
if self.backbone == "vgg":
[feat1, feat2, feat3, feat4, feat5] = self.vgg.forward(inputs)
elif self.backbone == "resnet50":
[feat1, feat2, feat3, feat4, feat5] = self.resnet.forward(inputs)
# 通过上采样操作将这些特征图进行级联,得到更高分辨率的特征图 up4、up3、up2 和 up1
up4 = self.up_concat4(feat4, feat5)
up3 = self.up_concat3(feat3, up4)
up2 = self.up_concat2(feat2, up3)
up1 = self.up_concat1(feat1, up2)
# 如果存在上采样卷积层 self.up_conv,则对 up1 进行进一步处理
if self.up_conv != None:
up1 = self.up_conv(up1)
# 通过 self.final 层获得最终的输出
final = self.final(up1)
return final
python

- 通过选定的主要网络架构来提取各个层级的特征图。
- 利用上采样模块来进行层次间的特征重建与融合。
- 若在系统中设置了额外的上采样卷积层,则可采用该层进行处理。
- 最后一步是通过一个卷积操作来生成所需的输出结果。
冻结和解冻骨干网络
def freeze_backbone(self):
if self.backbone == "vgg":
for param in self.vgg.parameters():
param.requires_grad = False
elif self.backbone == "resnet50":
for param in self.resnet.parameters():
param.requires_grad = False
def unfreeze_backbone(self):
if self.backbone == "vgg":
for param in self.vgg.parameters():
param.requires_grad = True
elif self.backbone == "resnet50":
for param in self.resnet.parameters():
param.requires_grad = True
python

freeze_backbone方法用于固定骨干网络的权重,在训练过程中避免被更新。unfreeze_backbone方法用于恢复骨干网络的权重,在训练过程中允许被更新。
冻结或解冻神经网络模型的特定层–更好地进行迁移学习或微调
迁移学习 :
在迁移学习中,采用经过大规模数据集训练的预处理神经网络模型来应对新的学习任务。
通过固定模型架构中的底层层(例如卷积层),可以继承其在原始任务中学习到的关键特征表示,并在此基础上进行微调学习。
这种策略不仅有助于减少新任务上的过拟合风险,还能有效提升模型的学习效率。
微调 :
- 参数微调指的是在预训练模型的基础上进一步优化以契合新任务的具体数据。
- 释放底层参数使其权重能够根据新的数据进行更精准地契合。
- 一般情况下我们只会对模型的一部分参数进行微调而不会调整全部参数以免丢失预先学习到的重要特征。
完整代码
import torch
import torch.nn as nn
from nets.resnet import resnet50
from nets.vgg import VGG16
class unetUp(nn.Module):
def __init__(self, in_size, out_size):
super(unetUp, self).__init__()
self.conv1 = nn.Conv2d(in_size, out_size, kernel_size=3, padding=1)
self.conv2 = nn.Conv2d(out_size, out_size, kernel_size=3, padding=1)
self.up = nn.UpsamplingNearest2d(scale_factor=2)
self.relu = nn.ReLU(inplace=True)
def forward(self, inputs1, inputs2):
outputs = torch.cat([inputs1,self.up(inputs2)],1)
outputs = self.conv1(outputs)
outputs = self.relu(outputs)
outputs = self.conv2(outputs)
outputs = self.relu(outputs)
return outputs
class Unet(nn.Module):
def __init__(self, num_classes = 2, pretrained = False, backbone = 'vgg'):
super(Unet, self).__init__()
if backbone == 'vgg':
self.vgg = VGG16(pretrained=pretrained)
in_filters = [192, 384, 768, 1024]
elif backbone == 'resnet50'
self.resnet = resnet50(pretrained=pretrained)
in_filters = [192, 512, 1024, 3072]
else:
raise ValueError('Unsupported backbone -`{}`, Use vgg, resnet50.'.format(backbone))
out_filters = [64, 128, 256, 512]
#???
self.up_concat4 = unetUp(in_filters[3], out_filters[3])
self.up_concat3 = unetUp(in_filters[2], out_filters[2])
self.up_concat2 = unetUp(in_filters[1], out_filters[1])
self.up_concat1 = unetUp(in_filters[0], out_filters[0])
if backbone == 'resnet50':
self.up_conv = nn.Sequential(
nn.UpsamplingNearest2d(scale_factor=2),
nn.Conv2d(out_filters[0],out_filters[0], kernel_size=3, padding=1),
nn.ReLU(),
nn.Conv2d(out_filters[0], out_filters[0], kernel_size=3, padding=1),
nn.ReLU(),
)
else:
self.up_conv = None
self.final = nn.Conv2d(out_filters[0], num_classes, 1)
self.backbone = backbone
def forward(self, inputs):
if self.backbone == "vgg":
[feat1, feat2, feat3, feat4, feat5] = self.vgg.forward(inputs)
elif self.backbone == "resnet50":
[feat1, feat2, feat3, feat4, feat5] = self.resnet.forward(inputs)
up4 = self.up_concat4(feat4, feat5)
up3 = self.up_concat3(feat3, up4)
up2 = self.up_concat2(feat2, up3)
up1 = self.up_concat1(feat1, up2)
if self.up_conv != None:
up1 = self.up_conv(up1)
final = self.final(up1)
return final
def freeze_backbone(self):
if self.backbone == "vgg":
for param in self.vgg.parameters():
param.requires_grad = False
elif self.backbone == "resnet50":
for param in self.resnet.parameters():
param.requires_grad = False
def unfreeze_backbone(self):
if self.backbone == "vgg":
for param in self.vgg.parameters():
param.requires_grad = True
elif self.backbone == "resnet50":
for param in self.resnet.parameters():
param.requires_grad = True
python

