
简介本资源为面向医学影像分析与深度学习语义分割方向的数据集适用于腹部多脏器自动分割模型的训练与验证可支撑脾脏、肝肾、胆囊、胃、胰腺、主动脉及肾上腺等13类器官的像素级标注任务。包内共约2000个文件以png掩膜图、jpg原始图像为主另含1个py脚本与1个json配置压缩包约74.85MB训练集约900张图像及对应mask验证集约200张数据已做对比度拉伸等增广处理运行show脚本即可查看掩膜叠加效果。目前已有476人学习下载。读者可据此搭建完整的分割实验流程结合配套网络分割博文快速复现训练与评估并借助classes文本核对标签类别适合医学影像入门与进阶研究者参考使用。1. 腹部多脏器语义分割数据集从标注格式到训练落地的完整路径拿到一个腹部多脏器语义分割数据集第一反应往往不是「怎么训」而是「这堆标注到底能不能直接用」。CT 腹部扫描里同时出现肝脏、脾脏、胰腺、肾脏、胃壁这些结构灰度接近、边界模糊不同医院的扫描协议和层厚还不一样。语义分割要做的就是给每个像素分配一个器官类别标签让模型学会「这个像素属于脾那个像素属于左肾」。这件事在临床上对应器官体积测量、术前规划、放疗靶区勾画在工程上则是一个典型的多类别 dense prediction 问题。适合已经跑通过二分类分割、想往多器官多类别推进的读者也适合手里有 B 超或 CT 数据、想搞清楚标注和训练全流程的人。下面按数据、模型、训练、排错、进阶五段讲透。2. 腹部多脏器数据集长什么样标注格式、类别体系与划分策略2.1 语义分割标注的三种常见存储形式腹部多脏器数据集的标注落到磁盘上通常有三种形态选错一种后面全在还债。第一种是掩膜图mask每个像素的值就是类别 id背景为 0肝脏为 1脾脏为 2以此类推。这种格式最省事直接喂给分割框架即可。第二种是每类一张二值 PNG一个器官一个文件夹训练前需要自己合成多通道或单通道索引图。第三种是 JSON 或 COCO 风格的 polygon 标注需要 rasterize 成掩膜。腹部 CT 因为器官是三维连续的很多数据集按切片导出每层一张 PNG命名里带 slice 序号。我一般会先写个脚本把标注统一成单通道 uint8 索引图因为后续无论用 nnU-Net、MONAI 还是自己写的 Dataset索引图都是最通用的中间格式。转换时最怕的是类别 id 对不上——有的数据集把肝脏标成 1有的标成 6直接混用会让模型学出一个「薛定谔的肝脏」。import numpy as np from PIL import Image import os # 把多张二值 mask 合成为单通道索引图 # 约定背景 0肝脏 1脾脏 2左肾 3右肾 4胰腺 5 ORGAN_MAP { liver: 1, spleen: 2, left_kidney: 3, right_kidney: 4, pancreas: 5, } def merge_masks(mask_dir, out_path, shape): index np.zeros(shape, dtypenp.uint8) for name, cid in ORGAN_MAP.items(): p os.path.join(mask_dir, f{name}.png) if not os.path.exists(p): continue m np.array(Image.open(p).convert(L)) # 二值化阈值取 127避免抗锯齿边缘产生中间值 index[m 127] cid Image.fromarray(index).save(out_path) merge_masks(masks/case_001, index/case_001.png, (512, 512))这段代码的关键在阈值那一步。很多标注工具导出的 PNG 边缘有灰度过渡如果直接m 0会把半透明的边缘也算进器官导致相邻器官在边界处互相覆盖。用 127 做硬阈值再按固定顺序覆盖能保证每个像素只有一个类别。顺序也有讲究先写大器官再写小器官或者反过来取决于你希望边界争议时谁赢。我一般让胰腺这种小器官后写避免被肝脏吞掉。2.2 类别不平衡与背景主导问题腹部 CT 切片里背景体外的空气、扫描床经常占 60% 以上肝脏可能占 15%胰腺可能只有 1% 到 2%。如果损失函数用普通交叉熵模型很快学会「全预测背景」也能拿到不错的 accuracy但 Dice 惨不忍睹。这是多脏器分割最典型的翻车点。常见做法是组合损失交叉熵加 Dice或者交叉熵加 Tversky。Dice 对前景敏感能拉住小器官交叉熵提供稳定的梯度。权重上我一般让 Dice 占 0.6交叉熵占 0.4胰腺这类小器官再单独加权。也有用 focal loss 的但对多类别分割focal 的 alpha 调起来比较玄学不如 Dice 直接。划分策略上腹部数据集必须按病人划分不能按切片随机划分。同一个病人的相邻切片高度相似如果切片随机分到训练和验证验证集里全是训练集见过的「近亲」指标虚高上线就崩。标准做法是 train/val/test 按 7:1:2 或 8:1:1 在病人层面切分并且保证每个集合里各器官都有出现。2.3 数据预处理窗宽窗位、归一化与重采样CT 的 HU 值范围是 -1000 到 3000 左右直接送进网络梯度会被大数值主导。腹部软组织观察常用窗宽 400、窗位 40也就是把 [-160, 240] 映射到 [0, 1]。这一步叫窗宽窗位调整是腹部分割的标配。def window_normalize(hu_array, ww400, wl40): lo, hi wl - ww // 2, wl ww // 2 hu np.clip(hu_array, lo, hi) return (hu - lo) / (hi - lo) # 归一到 [0,1]参数说明ww 是窗宽wl 是窗位。腹部常用 400/40如果想同时看肝脏和骨骼可以放宽到 500/50。归一化后数据在 [0,1]配合 BatchNorm 或 InstanceNorm 都稳。重采样同样重要。不同扫描的层厚可能是 1mm、3mm、5mm像素间距也不一样。如果直接 resize 到 512×512器官的物理尺寸就乱了。正确做法是按物理间距重采样到统一 spacing比如 1.5×1.5×2.0 mm再做裁剪或 padding。MONAI 的Spacing和Orientation变换能直接干这件事自己写也不难就是插值方式要选对图像用三线性掩膜用最近邻否则会插出 1.5 这种不存在的类别 id。3. 模型选型U-Net 系、Transformer 系与 nnU-Net 的取舍3.1 U-Net 及其变体为什么仍是腹部分割基线腹部多脏器分割的公开基准上U-Net 系长期占主导。原因不复杂医学图像数据量小U-Net 的编码器-解码器加跳跃连接能在少量样本下保住空间细节。跳跃连接把浅层的高分辨率特征直接送到解码器对器官边界这种细结构特别关键。常见变体里ResU-Net 把卷积块换成残差块训练更深也不退化Attention U-Net 在跳跃连接上加注意力门抑制无关区域U-Net 用密集连接缩小语义鸿沟。我一般先用标准 U-Net 跑通确认数据和损失没问题再换变体对比。直接上复杂结构出问题时分不清是数据还是模型。编码器可以用随机初始化也可以用 ImageNet 预训练。腹部 CT 是灰度图和 ImageNet 的 RGB 自然图像差异大预训练收益有限但比随机初始化还是稳一点。如果数据量超过几百例随机初始化也能训得很好。3.2 Transformer 与混合架构在腹部数据上的实际表现Swin-UNet、TransUNet 这类 Transformer 分割网络在公开腹部数据集上确实能刷高 Dice但代价是显存和训练时间。Transformer 的全局注意力对器官之间的相对位置关系建模有优势比如「脾在胃的左后方」这种先验卷积要靠堆深度才能隐式学到。实际落地时如果单卡显存只有 12G 到 16GSwin-UNet 的 3D 版本基本跑不动只能退到 2D 切片训练再在推理时做切片间融合。混合架构比如 CNN 编码器加 Transformer 瓶颈层是个折中显存占用比纯 Transformer 低又能拿到一部分全局建模能力。我的建议是数据少于 200 例优先 U-Net 系数据超过 500 例且有充足算力再考虑 Transformer。别为了追新架构把时间耗在调 batch size 和梯度累积上。3.3 nnU-Net 开箱即用的边界与自定义空间nnU-Net 是腹部分割里绕不开的工具。它自动分析数据集的 spacing、器官大小、类别分布然后自配置网络深度、patch size、batch size 和训练策略。在多个腹部多脏器挑战赛上nnU-Net 的默认配置就能打到前三。但它的边界也明显默认流程对数据格式要求严格必须是特定的文件夹结构和命名自定义损失或特殊预处理需要改源码推理速度偏慢因为用了多尺度集成。如果你的任务是标准的多脏器分割直接用 nnU-Net 省事如果要做实时推理或嵌入式部署还是自己搭轻量 U-Net 更可控。方案数据量要求显存需求调参成本适用场景标准 U-Net100 例起8G 可跑 2D中快速基线、嵌入式Attention U-Net200 例起10G 可跑 2D中高边界要求高Swin-UNet500 例起16G 起高刷指标、有算力nnU-Net50 例起11G 起低标准任务、省心4. 训练流程从数据加载到指标监控的可复现配置4.1 自定义 Dataset 与增强策略腹部分割的数据增强不能照搬自然图像的翻转旋转。水平翻转要小心因为肝脏在右、脾在左翻转后解剖位置就反了如果类别标签不跟着换模型会学乱。垂直翻转和旋转一般安全。常用的增强有随机旋转 ±15 度、随机缩放 0.9 到 1.1、弹性形变、随机亮度对比度扰动、以及模拟不同窗宽窗位。import random import numpy as np from scipy.ndimage import rotate, zoom def augment(image, mask): # 随机旋转图像双线性掩膜最近邻 angle random.uniform(-15, 15) image rotate(image, angle, order1, reshapeFalse, modeconstant) mask rotate(mask, angle, order0, reshapeFalse, modeconstant) # 随机缩放 scale random.uniform(0.9, 1.1) image zoom(image, scale, order1) mask zoom(mask, scale, order0) # 裁回原尺寸 h, w mask.shape image image[:h, :w] mask mask[:h, :w] return image, mask逻辑说明旋转和缩放都用 scipy 的 ndimage图像用 order1 双线性掩膜用 order0 最近邻保证类别 id 不被插值破坏。reshapeFalse保持尺寸不变边缘用常数填充。缩放后尺寸会变需要裁回原大小。这套增强在几百例数据上能把 Dice 拉高 2 到 3 个点。4.2 损失函数与优化器参数前面提过组合损失是主流。下面是一个可直接用的 Dice CE 实现。import torch import torch.nn as nn import torch.nn.functional as F class DiceCELoss(nn.Module): def __init__(self, num_classes, dice_weight0.6): super().__init__() self.num_classes num_classes self.dice_weight dice_weight def forward(self, logits, target): # logits: [B, C, H, W], target: [B, H, W] ce F.cross_entropy(logits, target) probs F.softmax(logits, dim1) target_onehot F.one_hot(target, self.num_classes).permute(0, 3, 1, 2).float() dims (0, 2, 3) inter (probs * target_onehot).sum(dims) union probs.sum(dims) target_onehot.sum(dims) dice (2 * inter 1e-5) / (union 1e-5) dice_loss 1 - dice.mean() return self.dice_weight * dice_loss (1 - self.dice_weight) * ce参数说明num_classes包含背景比如 5 个器官就是 6。dice_weight控制 Dice 占比小器官多就调到 0.7。平滑项 1e-5 防止除零。优化器用 AdamW学习率 1e-3 起步配合余弦退火weight decay 1e-4。batch size 在 2D 下可以到 16 或 323D 下通常只能 2 到 4靠梯度累积补。4.3 训练循环与验证指标训练循环里每个 epoch 后在验证集上算 Dice 和 Hausdorff 距离。Dice 看重叠Hausdorff 看边界最远偏差后者对器官边界质量更敏感。监控时按器官分别记录别只看平均 Dice——平均 0.85 可能意味着肝脏 0.95、胰腺 0.6后者才是瓶颈。def compute_dice(pred, target, num_classes): pred pred.argmax(1) scores [] for c in range(1, num_classes): # 跳过背景 p (pred c) t (target c) inter (p t).sum().item() union p.sum().item() t.sum().item() if union 0: continue scores.append(2 * inter / union) return sum(scores) / len(scores) if scores else 0.0验证时用滑动窗口或全图推理取决于显存。2D 模型直接全图3D 模型用 patch 滑窗重叠 50%重叠区域取概率平均。注意验证阶段不要开增强否则指标不可比。5. 避坑与排查腹部多脏器分割最常见的五类翻车5.1 现象训练 loss 正常下降验证 Dice 始终 0.3 以下原因最常见的是类别 id 错位。训练时掩膜里肝脏是 1验证时数据集肝脏是 6模型预测的 1 在验证里对应别的器官Dice 自然崩。其次是归一化不一致训练用了窗宽窗位验证直接送原始 HU。解决写一个check_label_mapping脚本打印训练和验证掩膜的 unique 值确认类别 id 一致。归一化参数存成配置文件训练和推理共用同一份。5.2 现象胰腺 Dice 常年在 0.5 徘徊其他器官正常原因胰腺体积小、边界模糊、周围脂肪和肠道干扰大是腹部分割公认的难点。加上类别不平衡模型倾向于忽略它。解决给胰腺单独加权损失里把胰腺的 Dice 权重提到 2 到 3 倍增强时对含胰腺的 patch 做 oversampling后处理阶段对胰腺预测做形态学闭运算填补内部空洞。5.3 现象推理时整张图全预测成背景原因验证集图像没做和训练一致的预处理比如没做窗宽窗位HU 值范围差了几千网络输入分布完全变了。或者模型最后一层 softmax 前 logits 数值异常被背景类主导。解决把预处理封装成一个函数训练和推理都调它。检查推理时输入的最小最大值和训练时的统计对比。如果 logits 异常检查是否有 NaN 或 inf通常是学习率太大导致。5.4 现象相邻器官边界处互相「吞并」肝脏预测覆盖到右肾原因交叉熵对边界像素的梯度弱加上标注本身在器官交界处可能有 1 到 2 像素的模糊模型学不到清晰边界。解决损失里加边界加权用形态学梯度提取标注边界给边界像素更高权重或者用 Tversky loss调整 FP 和 FN 的权重让模型对边界更敏感。后处理可以用分水岭或条件随机场细化边界但会增加推理时间。5.5 现象换一台机器或换一批数据指标断崖下跌原因域偏移。不同扫描仪、不同重建核、不同对比剂方案都会让图像分布变化。模型在源域过拟合目标域泛化差。解决训练时加入强增强模拟不同窗宽窗位和噪声用 Domain Adaptation 方法比如在目标域做测试时自适应或者最直接在目标域标注少量数据做微调。腹部分割没有银弹域偏移只能靠数据多样性压。6. 进阶技巧用测试时增强和后处理把 Dice 再抬两个点训练收敛后别急着上线测试时增强TTA和后处理往往还能再挤 1 到 3 个 Dice。TTA 的做法是对同一张输入做多种变换比如原图、水平翻转、旋转 90 度分别推理后把概率图逆变换回来取平均。对腹部数据水平翻转要谨慎因为解剖位置会反但如果模型是在翻转增强下训练的TTA 用翻转也安全。def tta_predict(model, image): preds [] # 原图 preds.append(torch.softmax(model(image), dim1)) # 旋转 90 度 img_rot torch.rot90(image, 1, dims(2, 3)) p_rot torch.softmax(model(img_rot), dim1) preds.append(torch.rot90(p_rot, -1, dims(2, 3))) # 水平翻转 img_flip torch.flip(image, dims(3,)) p_flip torch.softmax(model(img_flip), dim1) preds.append(torch.flip(p_flip, dims(3,))) return torch.stack(preds).mean(0)逻辑说明每个变换推理后把概率图逆变换回原坐标系再平均。注意是概率平均不是 argmax 后投票前者保留更多信息。TTA 的代价是推理时间翻 3 倍如果延迟敏感可以只保留原图和翻转两种。后处理方面最有效的是连通域过滤。腹部器官在切片上通常是单连通或少数几个连通域如果模型预测出很多碎小区域按体积排序保留最大的 1 到 2 个连通域其余归背景。这一步对胰腺和肾脏特别管用能去掉大量假阳性。from scipy.ndimage import label def remove_small_components(mask, min_size100): out mask.copy() for c in np.unique(mask): if c 0: continue binary (mask c) labeled, n label(binary) for i in range(1, n 1): if (labeled i).sum() min_size: out[labeled i] 0 return out参数说明min_size按像素数设512×512 切片上胰腺最小连通域可能只有几百像素设 100 到 200 比较稳。设太大可能把真实的小器官区域也删掉需要看验证集上的体积分布来定。还有一个容易被忽略的点推理时的插值。如果训练时图像重采样到了统一 spacing推理时也要做同样处理预测完再插值回原始尺寸。插值掩膜必须用最近邻否则会出现 1.5 这种非法类别。我见过有人用双线性插值掩膜结果 Dice 掉了 5 个点排查了一整天才发现是插值方式的问题血泪经验。最后说个习惯每次实验都固定随机种子记录数据版本、增强参数、损失权重、学习率曲线。腹部分割的调参周期长没有记录两周后你根本想不起来哪个配置对应哪个结果。我现在每个实验都存一个 config.yaml 加训练日志复现和对比省太多事。希望帮到你。本文还有配套的精品资源点击获取