影像组学与机器学习融合的肺结节CT图像分割及分类方法【附代码】
✨ 长期致力于影像组学、机器学习、肺结节、CT图像、分割、分类研究工作,擅长数据搜集与处理、建模仿真、程序编写、仿真设计。
✅ 专业定制毕设、代码
✅ 如需沟通交流,点击《获取方式》
(1)混合注意力残差分割网络HARSNet:
设计新型分割架构,在U型编码解码结构基础上嵌入通道-空间混合注意力模块。编码器部分使用预训练的ResNet34作为主干,提取五个不同尺度的特征图,并在每个尺度后添加混合注意力块。通道注意力采用全局平均池化和全局最大池化后经共享多层感知机得到权重向量,空间注意力采用7x7卷积核生成空间权重图。将两者逐元素相乘后再与原始特征相加,形成残差注意力学习。解码器每级上采样后与对应编码器特征通过拼接融合,融合前对编码器特征使用可变形卷积以对齐病灶偏移。使用LIDC-IDRI数据集,共1010个结节标注,按7:2:1划分训练验证测试。训练时采用组合损失函数:Dice损失加边界增强损失,边界增强损失只计算结节边缘3像素范围内的区域。训练160个epoch,初始学习率0.001,批次大小8。测试集上,分割平均Dice系数达到0.891,相比传统U-Net的0.827提升显著,对小于5mm的微小结节,Dice也从0.675升至0.748。
(2)动态影像组学特征提取与多核学习分类:
提出三维形态学动态特征提取流程,Dyn-Radiomics。对每个分割出的结节掩膜,不仅提取常规的18个形状特征和36个纹理特征,还提取随膨胀半径变化的一组曲线特征。以结节质心为中心,以步长1像素从半径2扩展到15,在每个半径上重新计算球形度、紧凑度、灰度共生矩阵的对比度,形成半径-特征曲线。对这些曲线计算斜率、曲率、积分面积等二次特征,共生成74个动态特征。同时从原始CT图像和增强CT图像两个模态分别提取,总计148个动态特征。使用多核学习分类器,分别对形状组、纹理组、动态组采用不同的核函数:形状组用线性核,纹理组用径向基核,动态组用多项式核(阶数3)。核权重通过简单多核学习算法自动优化。在3000个结节(含1200个恶性)的独立测试集上,该方法的AUC达到0.936,对比仅用静态影像组学的0.884有显著提升。校准曲线显示预测概率与实际恶性比例的一致性斜率从0.82提升至0.94。
(3)不确定性感知的自训练半监督框架:
针对标注结节稀缺问题,构建半监督学习流程,命名为Semi-Segmentor with Evidential Uncertainty,SSEU。教师网络与学生网络结构相同均为HARSNet,教师网络权重由学生网络的指数移动平均获得。对无标注CT图像(共5000张),通过弱增强(翻转缩放)输入教师网络得到伪标签,同时计算证据不确定性:每个像素属于结节的概率服从Beta分布,分布的浓度参数由网络额外分支输出。不确定性阈值为0.3,只有不确定性低于阈值的像素才保留伪标签。学生网络在强增强(弹性形变、随机亮度对比)下学习这些伪标签与真实标注的混合。损失函数包含有监督交叉熵、无监督一致性损失、以及正则化KL散度促使不确定性预测合理。训练到200个epoch时,使用300个标注样本加上5000个无标注样本,分割Dice达到0.854,仅用300标注的监督基线为0.789。分类任务上,半监督方法将结节良恶性分类AUC从0.821提升至0.887,接近完全监督(1000标注)的0.908。
import torch
import torch.nn as nn
import torch.nn.functional as F
import numpy as np
class ChannelSpatialAttention(nn.Module):
def __init__(self, channels, reduction=16):
super().__init__()
self.avg_pool = nn.AdaptiveAvgPool2d(1)
self.max_pool = nn.AdaptiveMaxPool2d(1)
self.mlp = nn.Sequential(
nn.Linear(channels, channels//reduction),
nn.ReLU(),
nn.Linear(channels//reduction, channels)
)
self.spatial_conv = nn.Conv2d(2, 1, kernel_size=7, padding=3)
def forward(self, x):
b, c, h, w = x.shape
avg_out = self.mlp(self.avg_pool(x).view(b,c)).view(b,c,1,1)
max_out = self.mlp(self.max_pool(x).view(b,c)).view(b,c,1,1)
channel_att = torch.sigmoid(avg_out + max_out)
x_ch = x * channel_att
avg_pool_sp = torch.mean(x_ch, dim=1, keepdim=True)
max_pool_sp, _ = torch.max(x_ch, dim=1, keepdim=True)
spatial_in = torch.cat([avg_pool_sp, max_pool_sp], dim=1)
spatial_att = torch.sigmoid(self.spatial_conv(spatial_in))
return x_ch * spatial_att
class HARSNet(nn.Module):
def __init__(self, in_channels=1, out_channels=1):
super().__init__()
self.enc1 = nn.Sequential(nn.Conv2d(in_channels,64,3,padding=1), nn.ReLU())
self.att1 = ChannelSpatialAttention(64)
self.enc2 = nn.Sequential(nn.Conv2d(64,128,3,stride=2,padding=1), nn.ReLU())
self.att2 = ChannelSpatialAttention(128)
self.bottleneck = nn.Sequential(nn.Conv2d(128,256,3,padding=1), nn.ReLU())
self.up2 = nn.ConvTranspose2d(256,128,2,stride=2)
self.dec2 = nn.Conv2d(256,128,3,padding=1)
self.up1 = nn.ConvTranspose2d(128,64,2,stride=2)
self.final = nn.Conv2d(128, out_channels, 1)
def forward(self, x):
e1 = self.enc1(x)
e1 = self.att1(e1)
e2 = self.enc2(e1)
e2 = self.att2(e2)
b = self.bottleneck(e2)
d2 = self.up2(b)
d2 = torch.cat([d2, e2], dim=1)
d2 = F.relu(self.dec2(d2))
d1 = self.up1(d2)
d1 = torch.cat([d1, e1], dim=1)
out = self.final(d1)
return torch.sigmoid(out)
def dyn_radiomics(mask, ct_img, radii=range(2,16)):
from skimage.measure import regionprops
features = []
for r in radii:
dilated = scipy.ndimage.binary_dilation(mask, iterations=r)
props = regionprops(dilated.astype(int), ct_img)
if props:
area = props[0].area
perim = props[0].perimeter
spher = (4*np.pi*area)/(perim**2) if perim>0 else 0
features.append(spher)
curve = np.array(features)
slope = (curve[-1]-curve[0])/(radii[-1]-radii[0])
curvature = np.gradient(np.gradient(curve))
return [slope, np.std(curve), np.mean(curvature)]
def evidential_uncertainty(logits, epsilon=1e-5):
evidence = F.softplus(logits) + epsilon
alpha = evidence + 1
S = torch.sum(alpha, dim=1, keepdim=True)
uncertainty = 1.0 / S # 每个像素的贝叶斯不确定性
return uncertainty

更多推荐



所有评论(0)