ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

基于Transformer的医学图像分割flask框架机器学习算法

基于Transformer的医学图像分割flask框架机器学习算法

✅源码获取:
🍅--------------------【点击左上方头像,在置顶文章上方的wx】联系我们-------------🍅

✌网站介绍:✌10年+项目辅导经验、专注于计算机技术领域学生项目实战辅导。

✌服务范围:大数据、机器学习、Java(SpringBoo/SSM)、Python、PHP、Nodejs、爬虫、数据可视化、小程序、安卓app等设计与开发。

随着医学影像技术的快速发展,医学图像分割在临床诊断、治疗规划以及医学研究中扮演着越来越重要的角色。准确的图像分割能够为医生提供更精确的病灶信息,有助于制定个性化的治疗方案。然而,医学图像分割面临诸多挑战,如图像噪声、复杂的解剖结构以及类间模糊边界等。近年来,基于Transformer的深度学习模型在自然语言处理领域取得了显著成功,其在图像处理领域的应用也逐渐受到关注。本文基于Flask框架和Transformer模型,构建了一个医学图像分割系统。背景和意义在于,通过引入先进的Transformer模型,提高医学图像分割的准确性和鲁棒性,为临床诊断和治疗提供更有力的支持。本文的主要内容包括:利用Transformer模型提取医学图像的深层次特征,结合Flask框架实现Web端的图像分割结果展示和用户交互。本项目采取的技术路线是:首先收集医学图像数据集,然后使用预训练的Transformer模型进行特征提取,接着构建图像分割模型,最后通过Flask框架实现系统的Web端展示和用户交互功能。具体情况是:系统首先从医学图像数据库中获取原始图像数据,使用预训练的Transformer模型进行特征提取,生成特征向量。然后,采用TransUNet 和 MobileViT等图像分割网络进行模型训练和优化,生成图像分割模型。最后,通过Flask框架搭建Web服务器,使用HTML、CSS和JavaScript实现前端页面设计,展示图像分割结果,并提供用户交互功能,如图像上传、分割结果展示等。本文详细阐述了系统的架构设计、关键技术和实现流程,并通过实验验证了系统的有效性和实用性。该系统不仅提高了医学图像分割的准确性和鲁棒性,还为医生和研究人员提供了便捷的图像分割工具,具有重要的应用价值。

    1. 模型训练与优化
      1. TransUNet的训练过程

TransUNet模型结合了Transformer和UNet的特点,用于图像分割。模型首先通过CNN编码器(类似ResNet)提取多尺度特征,然后利用Transformer编码器处理这些特征,增强特征表示能力。解码器部分采用简化的UNet结构,结合跳跃连接恢复空间信息。模型支持内存优化,可在必要时减少Transformer层数和释放中间变量。输出通过分类头生成分割结果,确保与输入尺寸一致。该模型适用于需要高效特征提取和精细分割任务的场景。

class TransUNet(nn.Module):
def __init__(
self,
img_size=416,
in_channels=3,
out_channels=1,
embed_dim=768,
patch_size=16,
num_transformer_layers=12,
num_heads=12,
mlp_ratio=4,
dropout_rate=0.1,
attn_dropout_rate=0.0,
decoder_channels=(256, 128, 64, 32, 16),
memory_efficient=True, # 添加内存优化选项
):
"""
TransUNet模型

参数:
img_size (int): 输入图像大小
in_channels (int): 输入通道数
out_channels (int): 输出通道数(分类数)
embed_dim (int): Transformer嵌入维度
patch_size (int): 分块大小
num_transformer_layers (int): Transformer层数
num_heads (int): 注意力头数
mlp_ratio (int): MLP隐藏层维度与嵌入维度的比率
dropout_rate (float): Dropout比率
attn_dropout_rate (float): 注意力Dropout比率
decoder_channels (tuple): 解码器通道数
memory_efficient (bool): 是否使用内存优化模式
"""
super(TransUNet, self).__init__()

# 简化初始化信息打印
print(f"初始化TransUNet: {img_size}x{img_size}, 输入={in_channels}, 输出={out_channels}, Transformer层={num_transformer_layers}")

self.img_size = img_size
self.patch_size = patch_size
self.embed_dim = embed_dim
self.memory_efficient = memory_efficient

# CNN编码器 (ResNet-like)
self.encoder1 = ConvBlock(in_channels, 32, kernel_size=3) # 416x416
self.pool1 = nn.MaxPool2d(kernel_size=2, stride=2)

self.encoder2 = ConvBlock(32, 64, kernel_size=3) # 208x208
self.pool2 = nn.MaxPool2d(kernel_size=2, stride=2)

self.encoder3 = ConvBlock(64, 128, kernel_size=3) # 104x104
self.pool3 = nn.MaxPool2d(kernel_size=2, stride=2)

self.encoder4 = ConvBlock(128, 256, kernel_size=3) # 52x52
self.pool4 = nn.MaxPool2d(kernel_size=2, stride=2)

self.encoder5 = ConvBlock(256, 512, kernel_size=3) # 26x26

# 定义跳跃连接的通道数,与编码器输出通道数一致
self.skip_channels = (256, 128, 64, 32) # e4, e3, e2, e1

# Transformer编码器
self.patch_embed = PatchEmbed(
img_size=img_size // 16, # 26x26
patch_size=1,
in_channels=512,
embed_dim=embed_dim
)

# 位置编码
self.pos_embed = nn.Parameter(torch.zeros(1, (img_size // 16) ** 2, embed_dim))
self.pos_drop = nn.Dropout(dropout_rate)

# Transformer块 - 如果内存优化模式,则减少Transformer层数
if memory_efficient and num_transformer_layers > 6:
print(f"内存优化:将Transformer层数从{num_transformer_layers}减少到6")
num_transformer_layers = 6

mlp_dim = int(embed_dim * mlp_ratio)
self.transformer_blocks = nn.Sequential(*[
TransformerBlock(
dim=embed_dim,
heads=num_heads,
dim_head=embed_dim // num_heads,
mlp_dim=mlp_dim,
dropout=dropout_rate
)
for _ in range(num_transformer_layers)
])

# 解码器
self.decoder_channels = decoder_channels

# 修改:确保conv_more输出通道数与第一个解码器通道数匹配
self.conv_more = ConvBlock(embed_dim, decoder_channels[0])

# 使用新的简化解码器
self.decoder = SimpleUNetDecoder(self.skip_channels, self.decoder_channels)

# 分类头
self.segmentation_head = nn.Conv2d(decoder_channels[-1], out_channels, kernel_size=1)

def forward(self, x):
# 保存输入尺寸,用于验证输出尺寸
input_size = x.shape[2:]
B = x.size(0) # 批量大小
# CNN编码器
e1 = self.encoder1(x) # 416x416
e1_pool = self.pool1(e1)
e2 = self.encoder2(e1_pool) # 208x208
e2_pool = self.pool2(e2)
e3 = self.encoder3(e2_pool) # 104x104
e3_pool = self.pool3(e3)
e4 = self.encoder4(e3_pool) # 52x52
e4_pool = self.pool4(e4)
e5 = self.encoder5(e4_pool) # 26x26

# 只在第一次前向传播或调试模式时打印
if not hasattr(self, 'printed_shapes'):
print(f"编码器特征图尺寸: e1={e1.shape}, e2={e2.shape}, e3={e3.shape}, e4={e4.shape}, e5={e5.shape}")
actual_skip_channels = [e4.size(1), e3.size(1), e2.size(1), e1.size(1)]
print(f"实际跳跃连接通道数: {actual_skip_channels}, 配置的跳跃连接通道数: {self.skip_channels}")
self.printed_shapes = True

# 内存优化:在不需要时释放中间变量
if self.memory_efficient:
del x
torch.cuda.empty_cache() if torch.cuda.is_available() else None

# Transformer编码器
x = self.patch_embed(e5) # (B, N, embed_dim)
x = x + self.pos_embed
x = self.pos_drop(x)

# 内存优化:在不需要时释放中间变量
if self.memory_efficient:
del e5
torch.cuda.empty_cache() if torch.cuda.is_available() else None

x = self.transformer_blocks(x)

# 重塑为2D特征图
x = x.transpose(1, 2).reshape(B, -1, self.img_size
# 解码器路径
x = self.conv_more(x)
# 跳跃连接 - 从大到小排序
skips = [e4, e3, e2, e1]
# 使用解码器
x = self.decoder(x, skips)
# 打印分割头信息
print(f"分割头预期输入通道数: {self.decoder_channels[-1]}, 实际输入: x.shape={x.shape}")
# 分类头
logits = self.segmentation_head(x)

# 检查输出尺寸是否与输入尺寸匹配
if logits.shape[2:] != input_size:
print(f"调整输出尺寸从 {logits.shape[2:]} 到 {input_size}")
logits = F.interpolate(logits, size=input_size, mode='bilinear', align_corners=True)
# 只在第一次前向传播时打印
if not hasattr(self, 'printed_output'):
print(f"最终输出尺寸: {logits.shape}")
self.printed_output = True
return logits

      1. MobileViT的训练过程

MobileViT模型结合了MobileNet和Transformer的优势,适用于图像分割任务。模型首先通过卷积层提取基础特征,然后通过多个阶段的MobileNet块和MobileViT块逐步降低特征图尺寸并增加通道数,同时利用Transformer增强特征表示。解码器部分采用U-Net风格,通过上采样和跳跃连接恢复空间信息,确保输出与输入尺寸一致。模型支持灵活的参数配置,如维度、通道数和深度等,可根据任务需求调整。最终通过分类头生成分割结果,适用于需要高效且精确分割的场景。

class MobileViT(nn.Module):
def __init__(
self,
img_size=416,
in_channels=3,
out_channels=1,
dims=[96, 120, 144],
channels=[16, 32, 48, 64, 80, 96, 384],
num_classes=1000,
expansion=4,
kernel_size=3,
patch_size=(2, 2),
depths=[2, 4, 3],
**kwargs
):
"""
MobileViT模型

参数:
img_size (int): 输入图像大小
in_channels (int): 输入通道数
out_channels (int): 输出通道数(分类数)
dims (list): Transformer维度列表
channels (list): 卷积通道列表
num_classes (int): 分类数量
expansion (int): 扩展比例
kernel_size (int): 卷积核大小
patch_size (tuple): 分块大小
depths (list): Transformer深度列表
"""
super().__init__()

# 打印初始化信息,用于调试
print(f"初始化MobileViT模型,输入大小: {img_size}x{img_size},输入通道: {in_channels},输出通道: {out_channels}")

self.img_size = img_size
self.channels = channels # 将channels保存为实例变量

# 初始卷积层
self.conv1 = nn.Conv2d(in_channels, channels[0], kernel_size=3, stride=2, padding=1)
self.bn1 = nn.BatchNorm2d(channels[0])
self.relu = nn.ReLU(inplace=True)

# MobileNetV2 + MobileViT架构
self.stage1 = nn.Sequential(
MobileNetBlock(channels[0], channels[1], stride=1, expansion=expansion),
MobileNetBlock(channels[1], channels[2], stride=2, expansion=expansion),
MobileNetBlock(channels[2], channels[3], stride=1, expansion=expansion)
)

self.stage2 = nn.Sequential(
MobileNetBlock(channels[3], channels[4], stride=2, expansion=expansion),
MobileViTBlock(
dim=384,
depth=depths[0],
channel=channels[4],
kernel_size=kernel_size,
patch_size=patch_size,
mlp_dim=int(384 * 2),
transformer_dim=384 * patch_size[0] * patch_size[1]
)
)

self.stage3 = nn.Sequential(
MobileNetBlock(channels[4], channels[5], stride=2, expansion=expansion),
MobileViTBlock(
dim=384,
depth=depths[1],
channel=channels[5],
kernel_size=kernel_size,
patch_size=patch_size,
mlp_dim=int(384 * 4),
transformer_dim=384 * patch_size[0] * patch_size[1]
)
)

self.stage4 = nn.Sequential(
MobileNetBlock(channels[5], channels[6], stride=2, expansion=expansion),
MobileViTBlock(
dim=384,
depth=depths[2],
channel=channels[6],
kernel_size=kernel_size,
patch_size=(1, 1),
mlp_dim=int(384 * 4),
transformer_dim=384
)
)

# 解码器 (U-Net风格)
self.decoder_channels = [256, 128, 64, 32, 16]

# 上采样路径
self.upconv1 = UpConv(channels[6], self.decoder_channels[0])
self.dec1 = ConvBlock(self.decoder_channels[0] + channels[5], self.decoder_channels[0])

self.upconv2 = UpConv(self.decoder_channels[0], self.decoder_channels[1])
self.dec2 = ConvBlock(self.decoder_channels[1] + channels[4], self.decoder_channels[1])

self.upconv3 = UpConv(self.decoder_channels[1], self.decoder_channels[2])
self.dec3 = ConvBlock(self.decoder_channels[2] + channels[3], self.decoder_channels[2])

self.upconv4 = UpConv(self.decoder_channels[2], self.decoder_channels[3])
self.dec4 = ConvBlock(self.decoder_channels[3] + channels[1], self.decoder_channels[3])

self.upconv5 = UpConv(self.decoder_channels[3], self.decoder_channels[4])
self.dec5 = ConvBlock(self.decoder_channels[4] + channels[0], self.decoder_channels[4])

# 分类头
self.segmentation_head = nn.Conv2d(self.decoder_channels[4], out_channels, kernel_size=1)

def forward(self, x):
# 打印输入形状,用于调试
print(f"MobileViT input shape: {x.shape}")

# 编码器路径
x0 = self.relu(self.bn1(self.conv1(x))) # [B, 16, 208, 208]

x1 = self.stage1(x0) # [B, 64, 104, 104]
x2 = self.stage2(x1) # [B, 80, 52, 52]
x3 = self.stage3(x2) # [B, 96, 26, 26]
x4 = self.stage4(x3) # [B, 384, 13, 13]

# 打印特征图形状,用于调试
print(f"特征图形状: x0={x0.shape}, x1={x1.shape}, x2={x2.shape}, x3={x3.shape}, x4={x4.shape}")

# 解码器路径
d1 = self.upconv1(x4)
print(f"上采样1后: d1={d1.shape}")

d1 = torch.cat([d1, x3], dim=1)
print(f"拼接1后: d1={d1.shape}")

d1 = self.dec1(d1)
print(f"解码器1后: d1={d1.shape}")

d2 = self.upconv2(d1)
print(f"上采样2后: d2={d2.shape}")

d2 = torch.cat([d2, x2], dim=1)
print(f"拼接2后: d2={d2.shape}")

d2 = self.dec2(d2)
print(f"解码器2后: d2={d2.shape}")

d3 = self.upconv3(d2)
print(f"上采样3后: d3={d3.shape}")

d3 = torch.cat([d3, x1], dim=1)
print(f"拼接3后: d3={d3.shape}")

d3 = self.dec3(d3)
print(f"解码器3后: d3={d3.shape}")

d4 = self.upconv4(d3)
print(f"上采样4后: d4={d4.shape}")

# 获取x0的通道数
x0_channels = x0.size(1)
print(f"x0的通道数: {x0_channels}")

# 修改: 由于通道数不匹配,我们重新定义dec4
# 首先创建新的解码器块,通道数与实际输入相匹配
actual_input_channels = self.decoder_channels[3] + x0_channels
print(f"实际输入通道数: {actual_input_channels}")

# 使用原始的self.dec4,但是需要确保通道数正确
d4 = torch.cat([d4, torch.nn.functional.interpolate(x0, size=d4.shape[2:], mode='bilinear', align_corners=True)], dim=1)
print(f"拼接4后: d4={d4.shape} (预期通道数: {self.decoder_channels[3] + self.channels[1]})")

# 如果通道数不匹配,我们需要创建一个新的卷积块
if d4.size(1) != self.decoder_channels[3] + self.channels[1]:
print(f"通道数不匹配!创建新的卷积块")
d4_conv = nn.Sequential(
nn.Conv2d(d4.size(1), self.decoder_channels[3], kernel_size=3, padding=1, bias=False),
nn.BatchNorm2d(self.decoder_channels[3]),
nn.ReLU(inplace=True),
nn.Conv2d(self.decoder_channels[3], self.decoder_channels[3], kernel_size=3, padding=1, bias=False),
nn.BatchNorm2d(self.decoder_channels[3]),
nn.ReLU(inplace=True)
).to(d4.device)
d4 = d4_conv(d4)
else:
d4 = self.dec4(d4)

print(f"解码器4后: d4={d4.shape}")

d5 = self.upconv5(d4)
print(f"上采样5后: d5={d5.shape}")

d5 = torch.cat([d5, torch.nn.functional.interpolate(x0, size=d5.shape[2:], mode='bilinear', align_corners=True)], dim=1)
print(f"拼接5后: d5={d5.shape} (预期通道数: {self.decoder_channels[4] + self.channels[0]})")

# 如果通道数不匹配,我们需要创建一个新的卷积块
if d5.size(1) != self.decoder_channels[4] + self.channels[0]:
print(f"通道数不匹配!创建新的卷积块")
d5_conv = nn.Sequential(
nn.Conv2d(d5.size(1), self.decoder_channels[4], kernel_size=3, padding=1, bias=False),
nn.BatchNorm2d(self.decoder_channels[4]),
nn.ReLU(inplace=True),
nn.Conv2d(self.decoder_channels[4], self.decoder_channels[4], kernel_size=3, padding=1, bias=False),
nn.BatchNorm2d(self.decoder_channels[4]),
nn.ReLU(inplace=True)
).to(d5.device)
d5 = d5_conv(d5)
else:
d5 = self.dec5(d5)

print(f"解码器5后: d5={d5.shape}")

# 分类头
logits = self.segmentation_head(d5)

# 打印输出形状,用于调试
print(f"MobileViT输出形状: {logits.shape}")

return logits

    1. 实验结果对比分析
      1. 分割结果可视化展示

在相同的实验环境和参数设置下,对TransUNet 和 MobileViT 算法两种模型在医学图像分割任务中进行了对比实验。实验结果如下图所示:

图 4-1 TransUNet模型分割结果

图 4-2 MobileViT模型分割结果

      1. 模型评估指标对比分析

模型评估界面展示了两个深度学习模型TransUNet和MobileViT在医学图像分割任务上的性能比较。从左到右,列出了每个模型的准确率(Accuracy)、Dice系数、IoU(交并比)、灵敏度(Sensitivity)、特异度(Specificity)、精确度(Precision)以及F1分数等关键评价指标。

具体来说:

TransUNet模型在所有评估维度上均表现出色,其准确率为0.9771,Dice系数为0.7588,IoU为0.6113,灵敏度为0.6500,特异度为0.9612,精确度为0.9000,F1分数为0.7673。

MobileViT模型的整体表现略逊于TransUNet,但其准确率仍高达0.9143,Dice系数为0.7263,IoU为0.5702,灵敏度和特异度分别为0.6500和0.9632,精确度为0.9000,F1分数为0.7250。

综上所述,虽然两个模型都在医学图像分割领域展现出了较高的性能水平,但TransUNet模型在各方面的综合表现更为优异。

图 4-3 模型评估指标对比

图 4-4 模型指标结果可视化

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进