简介:本资源面向计算机、生物医学工程等专业的毕业设计、课程设计与项目开发人群,提供一套基于 Python 实现的多模态 CMR 图像心肌结构和病灶分割完整源码,帮助读者快速搭建心脏影像分割实验环境,理解多模态数据融合与病灶定位的实现思路。压缩包共 38 个文件,约 36KB,以 19 个 py 源码文件为核心,辅以 10 个 pyc 编译文件、3 个 yaml 配置、2 个 sh 运行脚本、2 个 pth 模型权重及 txt、md 说明文档,覆盖训练、推理与配置全流程。资源已通过严格测试,可直接参考并在此基础上延伸使用,目录中包含 src 源码、configs 配置、outputs 输出与推理、训练脚本等模块,便于读者按需修改网络结构、调整超参数或替换数据集。目前已有 69 人学习,适合作为分割算法入门与二次开发的实践参考。
1. 多模态 CMR 心肌与病灶分割:从数据到可复现的 Python 方案
心脏磁共振(CMR)延迟强化成像(LGE)是临床上评估心肌梗死、心肌炎和肥厚型心肌病等结构性心脏病的关键手段。放射科医生需要在短轴切面上逐层勾画心肌内外膜轮廓,并标注出强化病灶区域,这个过程耗时且高度依赖经验。基于 Python 实现的多模态 CMR 图像心肌结构和病灶分割,本质上就是把这套勾画流程自动化:输入同一患者配准后的多序列 CMR 图像(常见如 LGE、T1 mapping、T2 mapping 或 bSSFP 电影序列),输出心肌区域和病灶区域的像素级掩膜。这个方向适合三类人:做医学图像分割课题的毕业生、需要快速搭建 CMR 分割基线的算法工程师、以及想把多模态融合思路迁移到其他医学影像任务的开发者。它解决的核心问题是单模态信息不足——LGE 上病灶对比度高但心肌边界模糊,电影序列上心肌运动清晰但病灶几乎不可见,只有把多模态信息在特征层面融合,才能同时拿到准确的心肌轮廓和病灶定位。下面从数据准备、模型搭建、训练调参到避坑排查,把一条能跑通的路径拆开讲。
2. 多模态 CMR 数据准备与预处理:配准、裁剪与归一化
2.1 多模态 CMR 数据的典型形态与配准前提
CMR 多模态数据在临床采集时并不是天然对齐的。LGE 序列通常在注射对比剂后 10 到 15 分钟采集,层厚 8 到 10 毫米,层间距可能不连续;T1/T2 mapping 序列层厚更薄,但覆盖范围可能不同;电影序列则是同一个心动周期内多个时相。做多模态分割的第一步,是把同一患者的不同序列在空间上对齐到同一个参考坐标系。常见做法是以 LGE 序列为参考帧,把其他模态通过刚性或仿射配准变换到 LGE 空间。如果数据来自公开数据集(如 EMIDEC、MyoPS 挑战赛数据),通常已经提供了配准后的多模态图像和对应标注,可以直接使用;如果是自己医院采集的原始 DICOM,就需要先做层间插值、重采样到各向同性分辨率(常用 1mm × 1mm × 1mm 或 1.5mm × 1.5mm × 1.5mm),再做跨模态配准。
配准工具方面,Python 生态里常用 SimpleITK 或 ANTsPy。SimpleITK 的刚性配准对同一患者不同序列之间的全局位移和旋转已经够用,计算量也可控。配准质量直接决定后续融合效果——如果 LGE 和 T1 mapping 的心肌区域错位超过 2 到 3 个像素,特征融合时反而会引入噪声。我一般会在配准后叠加显示两个模态的轮廓,肉眼确认心尖、心底和右室插入点这几个关键位置是否对齐。
2.2 用 Python 做重采样、ROI 裁剪和强度归一化
原始 CMR 图像直接送入网络有两个问题:一是图像尺寸和分辨率不统一,二是心脏区域只占整幅图像的一小部分,大量背景像素会稀释有效梯度。标准流程是:重采样到统一间距 → 根据标注或检测框裁剪心脏 ROI → 对每个模态分别做强度归一化。下面是一段可复现的预处理代码,假设输入是 NIfTI 格式的多模态图像和标注:
import numpy as np import SimpleITK as sitk from scipy.ndimage import zoom def resample_to_spacing(image, target_spacing=(1.5, 1.5, 1.5), is_label=False): """将 SimpleITK 图像重采样到目标间距""" original_spacing = image.GetSpacing() original_size = image.GetSize() target_size = [ int(round(original_size[i] * original_spacing[i] / target_spacing[i])) for i in range(3) ] resampler = sitk.ResampleImageFilter() resampler.SetSize(target_size) resampler.SetOutputSpacing(target_spacing) resampler.SetOutputOrigin(image.GetOrigin()) resampler.SetOutputDirection(image.GetDirection()) # 标签用最近邻,图像用线性插值 resampler.SetInterpolator(sitk.sitkNearestNeighbor if is_label else sitk.sitkLinear) return resampler.Execute(image) def crop_roi(image_array, label_array, margin=10): """根据标签非零区域裁剪 ROI,留出 margin 边界""" coords = np.where(label_array > 0) if len(coords[0]) == 0: return image_array, label_array slices = [] for dim in range(3): min_idx = max(0, coords[dim].min() - margin) max_idx = min(image_array.shape[dim], coords[dim].max() + margin + 1) slices.append(slice(min_idx, max_idx)) return image_array[tuple(slices)], label_array[tuple(slices)] def normalize_intensity(image_array, mask=None): """按心脏 ROI 内的均值和标准差做 z-score 归一化""" if mask is not None and mask.sum() > 0: values = image_array[mask > 0] else: values = image_array[image_array > 0] mean = values.mean() std = values.std() + 1e-8 normalized = (image_array - mean) / std return np.clip(normalized, -5, 5)这段代码的逻辑是:先统一空间分辨率,保证不同患者、不同模态的体素物理尺寸一致;再根据标注裁剪出心脏区域,减少背景干扰;最后按 ROI 内统计量做归一化,避免不同扫描仪的强度偏移影响模型。参数方面,target_spacing选 1.5mm 是一个折中——太细会增大显存占用,太粗会丢失病灶边界细节。margin设为 10 个像素,是为了在裁剪后保留足够的心肌周边上下文,尤其是病灶可能延伸到心肌边缘外侧的情况。归一化时用np.clip截断到 ±5,是为了抑制个别高亮伪影对后续卷积的干扰。
注意:如果标注中包含背景类(标签 0),裁剪时不要用
label_array > 0去计算坐标,否则会裁出整幅图像。正确做法是只对前景标签(心肌 + 病灶)取非零坐标。
2.3 多模态通道堆叠与数据增强策略
预处理完成后,把配准好的 LGE、T1、T2 等模态在通道维度堆叠,形成一个[H, W, D, C]的数组,其中 C 是模态数。如果某些患者缺少某个模态,常见做法是用零填充或者用同模态的均值图像替代,但更稳妥的方式是在训练时对缺失模态做随机丢弃增强,让模型学会在模态不完整时也能推理。数据增强方面,CMR 分割常用的有随机旋转(±15 度)、随机缩放(0.9 到 1.1)、随机弹性形变和伽马校正。注意不要做水平翻转——心脏的左右心室位置是固定的,翻转会破坏解剖先验。伽马校正对强度变化鲁棒性有帮助,尤其是 LGE 序列中病灶和血池的对比度差异较大时。
3. 多模态融合分割网络:从 nnU-Net 基线到注意力融合
3.1 为什么选 nnU-Net 作为基线而不是自己搭 U-Net
医学图像分割领域,nnU-Net 是目前公认的强基线。它根据数据集特性自动配置网络深度、卷积核数量、归一化方式和训练策略,在多个 CMR 分割挑战赛上表现稳定。对于多模态 CMR 心肌和病灶分割,直接用 nnU-Net 的多通道输入模式就能跑出一个不错的基线。自己从零搭 U-Net 不是不行,但需要手动调的东西太多——学习率、批大小、数据增强强度、损失函数权重,每一项都会影响最终 Dice。我一般会先用 nnU-Net 跑一版基线,记录下心肌和病灶的 Dice,然后再针对多模态融合部分做改进。这样即使改进效果不明显,至少有一个可用的兜底方案。
nnU-Net 的输入格式是每个病例一个文件夹,里面包含_0000.nii.gz、_0001.nii.gz等模态文件和_seg.nii.gz标注文件。模态编号对应通道顺序,训练时 nnU-Net 会自动读取并堆叠。如果要做自定义融合模块,可以在 nnU-Net 的nnUNetTrainer类里重写build_network方法,替换掉默认的 UNet 结构。
3.2 注意力融合模块的实现与插入位置
多模态融合的常见策略有三种:早期融合(输入层堆叠)、中期融合(编码器各阶段特征相加或拼接)、晚期融合(解码器输出后融合)。对于 CMR 心肌和病灶分割,中期融合通常效果最好,因为不同模态在编码器的不同深度提取的特征粒度不同——浅层特征包含更多纹理和边界信息,深层特征包含更多语义类别信息。一个实用的注意力融合模块可以这样实现:
import torch import torch.nn as nn class CrossModalAttention(nn.Module): """跨模态通道注意力融合:对每个模态的特征做通道加权后相加""" def __init__(self, channels, num_modalities=3, reduction=8): super().__init__() self.num_modalities = num_modalities self.avg_pool = nn.AdaptiveAvgPool3d(1) # 为每个模态生成通道注意力权重 self.fc = nn.Sequential( nn.Linear(channels * num_modalities, channels * num_modalities // reduction), nn.ReLU(inplace=True), nn.Linear(channels * num_modalities // reduction, channels * num_modalities), nn.Sigmoid() ) def forward(self, features): # features: list of [B, C, H, W, D] tensors, length = num_modalities B, C = features[0].shape[:2] # 拼接各模态的全局描述子 descriptors = torch.cat([self.avg_pool(f).view(B, C) for f in features], dim=1) weights = self.fc(descriptors).view(B, self.num_modalities, C) # 对每个模态特征做通道加权 weighted = [features[i] * weights[:, i, :].view(B, C, 1, 1, 1) for i in range(self.num_modalities)] return sum(weighted)这个模块的核心思路是:先对每个模态的特征图做全局平均池化,得到一个通道描述子;把所有模态的描述子拼接后送入两层全连接,学习模态间和通道间的交互关系;最后输出每个模态每个通道的权重,加权求和得到融合特征。插入位置一般选在编码器的每个下采样阶段之后,替换掉原来的单模态特征。参数方面,reduction控制全连接层的压缩比,8 是一个常用值,太小会增加参数量,太大会损失表达能力。num_modalities根据实际使用的模态数设置,如果只有 LGE 和 T1 两个模态就设为 2。
注意:如果某个模态在部分病例中缺失,直接做全局平均池化会把零填充区域也计入统计量,导致注意力权重偏移。解决办法是在池化前用模态可用性掩码把缺失模态的特征置零,或者在训练时对缺失模态做随机丢弃,让模型见到足够多的不完整样本。
3.3 损失函数选择:Dice、交叉熵与病灶不平衡处理
心肌和病灶的分割难度差异很大。心肌区域通常占图像中较大比例,边界相对清晰;病灶区域可能只有几十个像素,且在不同患者之间形态差异极大。如果只用交叉熵损失,模型会倾向于把所有像素预测为背景或心肌,病灶的召回率会很低。常见做法是组合 Dice 损失和交叉熵损失,并对病灶类施加更高的权重。一个可用的损失函数配置如下:
class CombinedLoss(nn.Module): def __init__(self, num_classes=3, dice_weight=0.5, ce_weight=0.5, class_weights=None): super().__init__() self.dice_weight = dice_weight self.ce_weight = ce_weight self.ce = nn.CrossEntropyLoss(weight=class_weights) self.num_classes = num_classes def dice_loss(self, logits, targets): probs = torch.softmax(logits, dim=1) targets_onehot = torch.nn.functional.one_hot( targets, self.num_classes).permute(0, 4, 1, 2, 3).float() intersection = (probs * targets_onehot).sum(dim=(2, 3, 4)) union = probs.sum(dim=(2, 3, 4)) + targets_onehot.sum(dim=(2, 3, 4)) dice = (2 * intersection + 1e-6) / (union + 1e-6) return 1 - dice.mean() def forward(self, logits, targets): return (self.dice_weight * self.dice_loss(logits, targets) + self.ce_weight * self.ce(logits, targets))这里class_weights可以设为[0.1, 1.0, 5.0],分别对应背景、心肌和病灶。背景权重低是因为背景像素占绝大多数,降低权重可以避免梯度被背景主导;病灶权重高是为了提升小目标的召回。Dice 损失和交叉熵各占一半是一个经验性的起点,如果病灶 Dice 仍然偏低,可以把病灶类的交叉熵权重进一步提高到 8 或 10,同时观察心肌 Dice 是否下降太多。如果下降明显,说明模型在心肌和病灶之间产生了混淆,需要检查标注质量或者增加病灶区域的过采样。
4. 训练调参与推理后处理:让病灶 Dice 从 0.3 提到 0.6
4.1 学习率、批大小与迭代次数的实操设置
CMR 多模态分割的训练对超参数比较敏感。以 nnU-Net 的默认配置为起点:初始学习率 0.01,多项式衰减,批大小根据显存设为 2 到 4(3D 网络),迭代次数 250 个 epoch。如果自己搭网络,AdamW 优化器配 1e-4 到 3e-4 的学习率更稳。批大小不建议小于 2,因为 BatchNorm 或 InstanceNorm 在批大小为 1 时统计量不稳定,会引入训练震荡。如果显存不够,可以用梯度累积模拟更大的批大小,或者把输入裁剪到 128×128×64 这样的较小尺寸。
迭代次数方面,CMR 数据集通常不大(几十到几百例),250 到 500 个 epoch 足够收敛。判断收敛不能只看训练损失,要每个 epoch 在验证集上算心肌和病灶的 Dice。如果验证 Dice 连续 30 个 epoch 不提升,就可以停。我一般会保存验证集上病灶 Dice 最高的那个 checkpoint,而不是最后一个 epoch 的权重——因为病灶分割的波动比较大,最后一个 epoch 不一定是最好的。
4.2 推理阶段的滑动窗口与测试时增强
推理时,如果整幅图像直接送入网络,显存可能不够,而且边缘区域的分割精度会下降。标准做法是滑动窗口推理:把图像切成有重叠的子块,逐块预测后把结果拼回去,重叠区域取平均或投票。nnU-Net 默认的滑动窗口重叠率是 0.5,对于 CMR 数据,心肌和病灶区域相对集中,重叠率可以降到 0.3 以减少推理时间,但不要低于 0.25,否则拼接处会出现明显接缝。
测试时增强(TTA)是另一个提点手段。常用的 TTA 包括:对输入做 8 个方向的翻转和旋转,分别推理后把 softmax 概率图变换回原空间取平均。对于 CMR 数据,水平翻转要慎用(解剖位置会反),但上下翻转和 90 度旋转是安全的。TTA 一般能带来 1 到 3 个百分点的 Dice 提升,代价是推理时间翻 4 到 8 倍。如果部署环境对延迟不敏感,建议开启。
4.3 后处理:连通域过滤与病灶小区域合并
网络输出的分割结果往往包含一些孤立的假阳性小区域,尤其是病灶类。后处理的第一步是连通域分析:对每个预测类别,保留体积最大的连通域,或者保留体积超过某个阈值的所有连通域。对于心肌,通常只有一个主要连通域(左心室心肌),可以直接取最大连通域;对于病灶,可能有多发梗死灶,不能简单取最大,而是保留体积大于 10 到 20 个像素的连通域。第二步是形态学闭运算,填补病灶内部的小孔洞。第三步是如果病灶区域和心肌区域没有重叠,说明预测可能有问题——病灶应该位于心肌内部或边缘,完全脱离心肌的病灶预测大概率是假阳性,可以过滤掉。
from scipy import ndimage def postprocess_prediction(pred_mask, min_lesion_size=15, num_classes=3): """对分割结果做连通域过滤和形态学后处理""" processed = pred_mask.copy() # 心肌类:保留最大连通域 myo_mask = (pred_mask == 1) labeled_myo, num_myo = ndimage.label(myo_mask) if num_myo > 1: sizes = ndimage.sum(myo_mask, labeled_myo, range(1, num_myo + 1)) largest = np.argmax(sizes) + 1 processed[(pred_mask == 1) & (labeled_myo != largest)] = 0 # 病灶类:保留体积大于阈值的连通域,并做闭运算 lesion_mask = (pred_mask == 2) labeled_lesion, num_lesion = ndimage.label(lesion_mask) for i in range(1, num_lesion + 1): if (labeled_lesion == i).sum() < min_lesion_size: processed[labeled_lesion == i] = 0 # 闭运算填补小孔 lesion_final = (processed == 2) lesion_final = ndimage.binary_closing(lesion_final, iterations=1) processed[lesion_final & (processed != 2)] = 2 return processedmin_lesion_size设为 15 是一个经验值,对应 1.5mm 各向同性分辨率下大约 50 立方毫米的体积。如果数据分辨率不同,需要按物理体积换算。闭运算的iterations设为 1 是为了避免过度膨胀导致病灶边界外扩。后处理之后,建议再算一遍 Dice,确认后处理没有把真实病灶误删。
注意:后处理参数需要在验证集上单独调,不能直接在测试集上调。我见过有人在测试集上反复试
min_lesion_size直到 Dice 最高,这种做法得到的提升是虚假的,换一批数据就会翻车。
5. 避坑与排查:多模态 CMR 分割中常见的 5 个翻车现场
5.1 配准误差导致模态融合后 Dice 反而下降
现象:单模态 LGE 训练时心肌 Dice 0.85,加入 T1 mapping 做多模态融合后心肌 Dice 掉到 0.78,病灶 Dice 也没提升。
原因:T1 mapping 和 LGE 之间的配准误差超过 3 个像素,融合时两个模态的心肌区域没有对齐,注意力模块学到的跨模态权重实际上是噪声。这种情况在层厚差异大的数据集上尤其常见。
解决:先单独检查配准质量。用 SimpleITK 把两个模态的图像和标注叠加显示,重点看心尖、心底和右室插入点。如果错位明显,重新做配准,或者改用仿射配准代替刚性配准。如果配准后仍然对不齐,考虑放弃该模态,只用配准质量好的模态组合。
5.2 病灶类权重过高导致心肌边界被侵蚀
现象:为了提高病灶召回,把病灶类交叉熵权重从 5 提到 15,结果病灶 Dice 从 0.45 提到 0.52,但心肌 Dice 从 0.88 掉到 0.80,心肌外膜边界出现明显凹陷。
原因:过高的病灶权重让模型倾向于把心肌边缘的模糊区域预测为病灶,因为这样可以在病灶类上获得更高的梯度回报。心肌和病灶在 LGE 上的边界本身就不清晰,权重失衡会放大这种混淆。
解决:病灶权重不要超过心肌权重的 5 倍。如果病灶 Dice 仍然低,优先从数据层面解决——增加病灶区域的过采样比例,或者在损失函数里对病灶边界像素单独加权,而不是全局提高病灶类权重。
5.3 验证集 Dice 很高但测试集一塌糊涂
现象:验证集上心肌 Dice 0.90,病灶 Dice 0.65,但换一家医院的数据测试时心肌 Dice 只有 0.70,病灶 Dice 不到 0.30。
原因:训练集和测试集来自不同扫描仪或不同采集协议,图像强度分布、分辨率和对比度差异大。模型学到了训练集的强度先验,换一台机器就失效。这是医学图像分割里最典型的域偏移问题。
解决:在预处理阶段做更强的强度归一化,比如直方图匹配或者 CycleGAN 风格的风格迁移。如果条件允许,在测试集上做无监督域适应,用测试图像的统计量重新校准归一化参数。另外,数据增强里加入随机伽马校正和随机偏置场,可以提升模型对强度变化的鲁棒性。
5.4 滑动窗口拼接处出现分割断层
现象:推理结果在子块边界处出现明显的分割断层,心肌轮廓在拼接位置突然中断或错位。
原因:滑动窗口重叠率设得太低(比如 0.1),或者拼接时直接取最大值而不是平均概率。子块边缘区域的预测精度本身低于中心区域,低重叠率下边缘误差被直接带入拼接结果。
解决:把重叠率提高到 0.3 到 0.5,拼接时对重叠区域的 softmax 概率取平均,而不是对硬标签做投票。如果显存允许,改用高斯权重拼接——子块中心权重高,边缘权重低,可以进一步平滑拼接边界。
5.5 病灶标注不一致导致模型学偏
现象:同一批数据里,不同标注者对病灶范围的勾画差异很大,有的只勾强化核心,有的把水肿区也勾进去。模型在训练时一会儿学核心,一会儿学水肿,最终病灶 Dice 卡在 0.4 上不去。
原因:病灶标注本身存在主观性,LGE 上的强化区域和水肿区域的边界在临床上也没有绝对标准。如果训练数据里标注风格不统一,模型无法收敛到一个稳定的决策边界。
解决:先做标注一致性检查。随机抽 10 到 20 个病例,让同一个标注者间隔一周重新勾画,计算两次勾画的 Dice。如果一致性低于 0.7,说明标注规范需要统一。常见做法是明确病灶定义——只勾画 LGE 上信号强度超过 remote myocardium 均值加 5 个标准差的区域,水肿区不纳入病灶类。如果数据已经无法重新标注,考虑用软标签训练,把不同标注者的结果做概率平均,让模型学习标注的不确定性。
6. 进阶技巧:用不确定性估计筛选需要人工复核的病例
6.1 蒙特卡罗 Dropout 做推理期不确定性估计
模型在病灶边界模糊的病例上容易出错,如果能自动识别出哪些病例的预测不可靠,就可以优先把这些病例推给医生复核,减少漏诊风险。蒙特卡罗 Dropout 是一种实现简单且效果稳定的不确定性估计方法:在推理时保持 Dropout 层开启,对同一输入做多次前向传播(通常 10 到 30 次),得到一组预测概率图,计算每个像素的预测方差作为不确定性。方差高的区域就是模型“拿不准”的地方。
def mc_dropout_predict(model, input_tensor, num_samples=20): """蒙特卡罗 Dropout 推理,返回均值概率和不确定性图""" model.train() # 保持 Dropout 开启 preds = [] with torch.no_grad(): for _ in range(num_samples): logits = model(input_tensor) probs = torch.softmax(logits, dim=1) preds.append(probs.cpu().numpy()) preds = np.stack(preds, axis=0) # [N, B, C, H, W, D] mean_probs = preds.mean(axis=0) uncertainty = preds.var(axis=0) # 每个像素每个类别的方差 return mean_probs, uncertaintynum_samples设为 20 是一个平衡点——再少方差估计不稳定,再多推理时间线性增长。得到不确定性图后,可以计算病灶区域内平均不确定性,如果超过某个阈值(比如 0.05),就把这个病例标记为需要复核。这个阈值需要在验证集上根据复核率和漏诊率的权衡来定。
6.2 把不确定性图叠加到分割结果上做可视化
不确定性图本身也可以作为质控工具。把不确定性高的区域用热力图叠加到原始 LGE 图像上,医生可以快速看到模型在哪些位置犹豫。我一般会把不确定性图和分割轮廓一起显示:轮廓用红色表示心肌,黄色表示病灶,不确定性超过阈值的像素用半透明热力图覆盖。这样医生复核时不需要逐像素检查,只需要看热力图集中的区域。
6.3 一个我踩过的坑:不确定性高不等于预测错误
需要提醒的是,不确定性高和预测错误并不是一回事。模型可能在病灶边界处给出高不确定性,但预测结果仍然在可接受范围内;也可能在某个区域给出低不确定性,但预测完全错误(比如把伪影当成病灶)。所以不确定性估计只能作为复核优先级的参考,不能替代医生的最终判断。我在早期项目里曾经把不确定性阈值设得太低,导致 80% 的病例都被标记为需要复核,医生根本看不过来。后来把阈值调到只标记 15% 到 20% 的病例,复核效率才提上来。这个阈值没有通用值,必须根据自己数据集的分布和医生的复核能力来调。
6.4 从单中心到多中心:域泛化的几个实用手段
如果要把模型推广到多个中心的数据,除了前面提到的强度归一化和数据增强,还有两个手段值得试。一是模态 dropout:训练时随机丢弃某个模态的输入,让模型不依赖特定模态组合。二是特征对齐:在编码器末端加一个域判别器,用对抗训练的方式让不同中心的特征分布尽量接近。这两个手段都会增加训练复杂度,建议先在单中心数据上把基线做扎实,再逐步加入。
最后说一个我自己的习惯:每次跑完一个新配置,不管 Dice 是涨是跌,我都会把验证集上病灶 Dice 最低的 5 个病例单独拿出来看。涨了要知道为什么涨,跌了更要知道为什么跌。很多时候,病灶 Dice 上不去不是因为模型不够复杂,而是因为那几个病例的标注本身就有问题,或者图像质量太差。把这些病例挑出来单独分析,比盲目调参有效得多。希望帮到你。
本文还有配套的精品资源,点击获取