
简介肾脏肿瘤语义分割数据集面向医学影像分析与深度学习研究者聚焦医学影像中的肾脏与肿瘤区域提取已明确划分为训练集约2000张与验证集约800张像素标签包含背景、肾脏、肿瘤三个类别可直接用于U-Net、SwinUNet等语义分割网络的训练与评估。压缩包为7z格式共2000个文件以PNG图像为主原始图像与对应掩码另包含1个类别说明文本和1个Python可视化脚本整体约88.6MB。目前已有175人学习/下载。可视化脚本可随机抽取一张样本将原图、GT分割图和GT叠加蒙版图保存到当前目录便于直观检查标注质量与模型输入。资源中训练集和验证集的划分方式清晰图像与掩码一一对应免去了自行整理数据集的成本既可作为医学图像分割实验的基准数据也适合初学者快速上手语义分割标注格式与模型训练流程。1. 肾脏肿瘤语义分割数据集2800张标注图能做什么、不能做什么某实验室接手过一个肾脏肿瘤AI辅助筛查的课题。最初的训练集只有三百多张CT跑出来的U-Net在验证集上Dice只有0.2肿瘤几乎全被当成背景。换上一套约2800张的肾脏肿瘤语义分割数据后同一个网络、同一套预处理Dice直接抬到0.6以上。这套资源提供CT影像和逐像素精标注典型用途是训练U-Net这类分割网络做肾脏肿瘤的自动勾画、体积测量和术前辅助评估。它不提供端到端诊疗结论也不自带训练框架作为一份标注整齐、类别明确的训练原料适合正在做医学图像分割入门、课题实验和算法选型对比的开发者。拿到它的研究者通常是研究生、算法工程师或刚进入医学影像AI方向的从业者想快速验证一个分割想法又不想从头整理几百个病例的私有数据。2. 先看懂这套肾脏数据目录结构、标签格式和CT图像的预处理前提下载之后别急着开训先花半小时把数据组织和像素值语义摸清楚。训练翻车往往不是模型位置写错了而是连标签格式都读错了。2.1 目录结构一张图对应一张掩码的组织方式同类CT分割数据集的常见结构是图像与掩码分目录平级存放命名前缀一一对应。解压后通常是kidney_tumor_dataset/ ├── train/ │ ├── images/ │ │ ├── P001_CT_0001.png │ │ ├── P001_CT_0002.png │ │ └── ... │ └── masks/ │ ├── P001_CT_0001_mask.png │ ├── P001_CT_0002_mask.png │ └── ... ├── val/ │ ├── images/ │ └── masks/ └── test/train/val/test的划分比例常见为8:1:1或7:2:1。命名上强制做到一个图像文件对应一个掩码文件P001_CT_0001.png对应P001_CT_0001_mask.png。这样写PyTorch数据集类时只需要拼接路径不需要额外解析索引表。拿到数据后我习惯先跑一段目录校验脚本确认每个split下的图像和掩码数量一致、尺寸一致from pathlib import Path from PIL import Image for split in [train, val, test]: img_dir Path(f/path/to/{split}/images) mask_dir Path(f/path/to/{split}/masks) imgs sorted(img_dir.glob(*.png)) masks sorted(mask_dir.glob(*_mask.png)) assert len(imgs) len(masks), f{split} 图像与掩码数量不一致 for img in imgs: im Image.open(img) m Image.open(mask_dir / (img.stem _mask.png)) assert im.size m.size, f{img.stem} 图像与掩码尺寸不一致 print(目录校验通过)逻辑说明这段脚本把所有“图像和掩码对不齐”的问题暴露在训练之前。CT切片通常是单通道灰度图掩码也是单通道但内容为类别ID。路径里用的是stem即不带后缀的文件名掩码命名中的_mask是常见惯例如果你的包不是这个命名规则只需要改这里的拼接方式。参数说明glob(*.png)会读取全部PNG文件医学数据集里如果混有DICOM或NIfTI文件这里会直接漏掉所以建议先ls看一眼目录再改后缀匹配规则。2.2 标签格式PNG灰度掩码、类别ID与可视化检查医学图像分割里最常见的掩码格式不是RGB彩色图而是PNG灰度图每个像素的数值直接等于类别ID。有人第一次拿到掩码当三通道彩色图读结果数据集类输入维度变成3通道训练直接崩。这套数据的常见类别约定如下像素值含义0背景1肾脏实质2肿瘤检查标签是否有异常值用一个小脚本就能扫完整个目录import numpy as np from PIL import Image mask np.array(Image.open(P001_CT_0001_mask.png).convert(L)) print(np.unique(mask)) # 输出应只在 [0 1 2] 中出现逻辑说明convert(L)会把可能带调色板的PNG转为单通道灰度避免掩码变成三通道后引入维度混乱np.unique统计像素类别一眼能看出标签是二分类还是三分类。注意点有的版本会把标签做成“背景、肾脏、肿瘤、囊肿”四类或者把肾脏实质和肿瘤合并成一类。如果你要跑二分类任务需要把标签里1和2都合并成前景。进一步检查边界是否干净可以把掩码叠加在原图上输出一张PNGimport cv2 import numpy as np from PIL import Image image np.array(Image.open(P001_CT_0001.png).convert(L)) mask np.array(Image.open(P001_CT_0001_mask.png).convert(L)) overlay cv2.cvtColor(image, cv2.COLOR_GRAY2BGR) overlay[mask 1] (0, 200, 0) # 肾脏标注为绿色 overlay[mask 2] (0, 0, 255) # 肿瘤标注为红色 cv2.imwrite(overlay_check.png, overlay)这段代码能直接看到肾脏与肿瘤的空间位置、边界是否贴合。抽查几十张比看统计数值更靠谱。掩码质量问题最常见的表现是边界抖动也就是一张图内某个区域断断续续模型训练时会反复纠结这些像素。2.3 CT模态与预处理Hounsfield单位、窗宽窗位和重采样CT图像的像素值本质是射线衰减值映射专业上叫Hounsfield单位HU。不同厂商设备、不同扫描协议同一患者在不同时期扫描的数值分布都可能不同。模型不关心绝对数值但希望输入分布稳定。常见做法是先按软组织窗截断再归一化到0~1区间def preprocess_ct(img, wl70, ww340): lower wl - ww / 2 upper wl ww / 2 img np.clip(img, lower, upper) img (img - lower) / (upper - lower) return img.astype(np.float32)逻辑说明wl是窗位即CT值中心ww是窗宽即显示的CT值范围。肾脏软组织窗一般取窗位70、窗宽340。clip会把超出窗外的数值压到边界把骨骼和金属伪影的高密度干扰排除在有效输入之外归一化后网络接收的输入就稳定在0~1之间。参数说明如果源数据是PNG且已经由发布者裁好、归一化过这段预处理可以跳过如果源数据是DICOM或NIfTI序列还需要做体素重采样。不同扫描的z轴层厚可能不同常见做法是把体素重采样到各向同性1mm左右保证网络看到的解剖结构尺度一致。提示拿到NIfTI/DICOM时不要直接按层编号读成2D图片层与层之间的spacing信息一旦丢失后续的归一化、后处理都会受影响。这套数据若以PNG切片形式存在最省事的做法是保持原分辨率读入训练时再在数据增强阶段resize到固定输入尺寸。3. 把数据喂进网络PyTorch加载、数据增强和损失函数选择目录和标签摸清后进入模型训练环节。这里最常出问题的是数据加载三个细节读图方式、掩码类型、图像与掩码的几何变换是否同步。3.1 自定义Dataset类从读图到在线增强一次做对训练分割网络的第一关是写对Dataset类。常见做法是直接继承torch.utils.data.Datasetimport torch import numpy as np from PIL import Image from torch.utils.data import Dataset class KidneyTumorDataset(Dataset): def __init__(self, image_dir, mask_dir, transformNone): self.image_paths sorted(image_dir.glob(*.png)) self.mask_dir mask_dir self.transform transform def __len__(self): return len(self.image_paths) def __getitem__(self, idx): img_path self.image_paths[idx] mask_path self.mask_dir / (img_path.stem _mask.png) image np.array(Image.open(img_path).convert(L), dtypenp.float32) mask np.array(Image.open(mask_path).convert(L), dtypenp.int64) if self.transform: augmented self.transform(imageimage, maskmask) image augmented[image] mask augmented[mask] image torch.from_numpy(image).unsqueeze(0) # [1, H, W] mask torch.from_numpy(mask) # [H, W] return image, mask逻辑说明图像转成float32并增加通道维度掩码转成int64因为交叉熵损失的target要求长整型。图像与掩码在transform中同步处理这是几何变换能对齐的关键。单独对图像做旋转而掩码原地不动会导致训练标签错位推理时输出和原图对不上。增强建议用albumentations而不是torchvision因为后者不会自动把掩码和图像做完全一致的几何变换。一个稳定可复现的增强配置import albumentations as A train_transform A.Compose([ A.HorizontalFlip(p0.5), A.VerticalFlip(p0.5), A.RandomRotate90(p0.5), A.ShiftScaleRotate(shift_limit0.05, scale_limit0.1, rotate_limit10, p0.5), A.RandomBrightnessContrast(brightness_limit0.1, contrast_limit0.1, p0.3), ]) val_transform A.Compose([ A.Resize(256, 256), ])参数说明shift_limit是平移比例scale_limit是缩放比例rotate_limit是旋转角度范围。CT图像在做这些几何增强时解剖语义不会改变肾脏不会因为翻转就变成肝脏。RandomBrightnessContrast幅度控制在0.1以内主要对抗扫描参数的亮度漂移。在线增强和离线增强的差别在线增强每个epoch生成不同的样本相当于变相扩充数据量离线增强只做一次、占磁盘且容易过拟合。2800张原始数据配在线增强训练效果通常好于预先把它翻成5倍。3.2 损失函数选型从BCE、Dice到混合损失训练分割网络时损失函数的选择比网络结构更容易被忽略。单独用BCEWithLogitsLoss对类不平衡敏感因为肿瘤区域通常只占全图的1%到5%逐像素损失很容易被背景主导。单独用DiceLoss对目标形状敏感小目标出现时梯度容易震荡。我常用的工程折中是把两者按比例相加import torch import torch.nn as nn import torch.nn.functional as F class CombinedLoss(nn.Module): def __init__(self, bce_weight0.5, dice_weight0.5): super().__init__() self.bce_weight bce_weight self.dice_weight dice_weight self.bce nn.BCEWithLogitsLoss() def forward(self, logits, targets): bce_loss self.bce(logits.float(), targets.float()) probs torch.sigmoid(logits) dice_loss 1 - (2 * (probs * targets).sum() 1) / \ (probs.sum() targets.sum() 1) return self.bce_weight * bce_loss self.dice_weight * dice_loss逻辑说明这个混合损失在肿瘤像素占比极小的数据集上比纯交叉熵稳定。Dice公式中的1是平滑项避免预测和真实掩码都为全零时分母为0。参数说明bce_weight和dice_weight可以动态调。当Dice曲线震荡剧烈时把dice_weight从0.5降到0.3当背景误检多把bce_weight调高。经验值范围在0.3到0.7之间不建议某一项完全为零。训练参数参考表参数建议值说明输入尺寸256x256 或 512x512显存充足用512快速实验用256batch size8 或 16显存不够时先降到4优化器AdamW / Adamlr1e-4起步epoch100左右医学小数据不需要500轮早停 patience10验证集Dice连续10轮不升就停3.3 评估指标验证集上的Dice、IoU和肿瘤体积偏差训练日志里的loss不能直接当指标要在验证集上分别计算每类的Dice和IoUdef dice_coef(pred_mask, gt_mask, smooth1e-6): intersection (pred_mask * gt_mask).sum() return (2.0 * intersection smooth) / (pred_mask.sum() gt_mask.sum() smooth) def iou_score(pred_mask, gt_mask): intersection (pred_mask * gt_mask).sum() union pred_mask.sum() gt_mask.sum() - intersection return intersection / (union 1e-6)逻辑说明pred_mask要先经过sigmoid加argmax或阈值化为0/1再和GT做逐元素相乘。直接对整个CT图算Dice会高估肾脏这类占比大的类别建议单独对“肿瘤”这个类计算或者裁剪到肾脏ROI区域再评估。额外指标肿瘤体积差即预测的肿瘤像素数乘以体素体积与GT体积的百分比偏差。这个指标在写横向对比实验、报给临床团队时更有说服力。4. 进阶训练路线nnU-Net自动配置与预训练迁移U-Net基线跑通后节下来的问题是怎么榨干数据价值。两个常用路线用nnU-Net做自动化配置或者用预训练权重做迁移学习。4.1 用nnU-Net给2800张数据做自动配置nnU-Net是现阶段医学图像分割的事实基线。它能自动统计spacing、确定目标尺寸、选择合适的归一化和损失策略省掉大量人工调参。接入这套数据需要把目录整理成指定结构nnUNet_raw/Dataset118_KidneyTumor/ ├── imagesTr/ │ └── P001_CT_0001_0000.png ├── labelsTr/ │ └── P001_CT_0001.png ├── imagesTs/ └── dataset.jsonimagesTr里的文件名后缀必须带_0000这是nnU-Net识别通道的约定。labelsTr不需要后缀。dataset.json内容{ channel_names: {0: CT}, labels: {background: 0, kidney: 1, tumor: 2}, numTraining: 2240, file_ending: .png }逻辑说明channel_names声明单通道CTlabels与标签像素值对齐numTraining填训练集实际数量。如果不写完整plan阶段会报KeyError。执行命令nnUNetv2_plan_and_preprocess -d 118 -pl nnUNetPlannerResEncM nnUNetv2_train 118 2d 0参数说明-d指数据集编号2d表示用二维模型训练0是fold编号。如果原始数据是二维PNG切片直接用2d模型比3d模型更稳。显存吃紧时加--max_num_epochs 100限制训练轮数。nnU-Net输出的是带softmax概率图推理得到概率后需要再做argmax得到类别掩码。它的plan会生成一个plans.json里面包含重采样目标尺寸和归一化参数训练日志里能看到每步使用的实际值。4.2 预训练迁移学习什么时候用、怎么接如果不走nnU-Net想在自建U-Net里加预训练编码器常见做法是使用segmentation_models_pytorchfrom segmentation_models_pytorch import Unet model Unet( encoder_nameresnet34, encoder_weightsimagenet, classes3, activationNone )逻辑说明encoder_weightsimagenet会加载在ImageNet上预训练的resnet34编码器解码器仍然随机初始化。这个方案对2D切片有效因为自然图像上学习到的边缘纹理特征可以迁移到CT灰度图上。参数说明encoder_name可以换resnet50、efficientnet-b0等classes要跟标签类别数一致activationNone表示输出logits方便后续混合损失。个人经验几百张数据时预训练权重有明显加速收敛作用到了2800张这个量级预训练的增益不再显著但能节省1到2天的训练时间。如果CT值已经被你的窗宽窗位改写过预训练特征与目标域的gap会增大这时候从头训练有时反而更稳。提示迁移学习不等于零调参。加载预训练后仍需把学习率降到常规值的一半否则主干网络的预训练权重大幅更新等于白加载。5. 避坑指南五个“常态性翻车”实例这五种情况不是偶发bug而是所有读这套数据的人大概率会踩一遍的路。每一条我都按现象到原因到解决顺序写。5.1 现象训练Dice在0.8和0.3之间大幅震荡同一套代码epoch 20时Dice能到0.8epoch 25又掉回0.3再跑几轮又回升。原因通常是学习率过大或者验证集里混进了同一个患者的相邻切片导致评估结果波动。解决学习率从1e-4降到1e-5或5e-5并开启余弦退火验证时按患者ID分组统计指标而不是按切片统计这样每一轮评估的分布更稳定。5.2 现象肿瘤小目标完全学不到整体Dice虚高肿瘤像素占比太小即使网络把所有肿瘤都预测为肾脏整体Dice可能还保持在0.9以上让人误以为模型很好。原因在于损失函数被大类别主导。解决在损失里加入Tversky损失或Focal损失提高小目标的惩罚权重更有效的做法是先把图像裁剪到肾脏周围区域再在这个ROI上做二分类分割肿瘤占比从1%提升到10%左右模型很快能学到。5.3 现象推理时掩码和原图错位训练集上指标正常但推理单张图时分割掩码和原始CT错开几个像素到几十个像素。原因通常是训练阶段图像做了resize或有几何增强推理阶段没有做同步操作或者是用cv2缩放时用了不同插值方式。解决推理时把输入resize到训练时的同一尺寸输出再resize回原图尺寸resize操作用满足等距性的插值。切勿训练用线性插值、推理用最近邻两者会带来系统性偏移。5.4 现象同一个患者的不同切片被分到训练集和验证集指标虚高CT一个序列通常有几十到上百层切片同一患者的相邻层在解剖结构上高度相似。如果只按文件名随机split同一个患者可能同时出现在训练集和验证集网络等于见过近乎重复的测试样本验证指标自然虚高。解决在做数据划分之前先提取患者ID用GroupKFold按患者分组保证同一患者的切片全部落在一个折里不跨训练和验证。5.5 现象Dice很高临床团队却觉得画出来的肿瘤边界不对Dice把所有像素等权计算即使边界偏移几毫米只要重叠区域多分数也不低。临床上更在意边界是否平滑、是否覆盖真实病灶边缘。解决评估指标加上Hausdorff距离度量两个区域边界的最大差距这个指标能更敏感地暴露边界粗糙问题。后处理阶段加连通域筛选和空洞填充移除小假阳区填掉正常肾脏内部的细洞边界会明显更干净。6. 笔者实战习惯5折交叉验证与加权软投票集成把单个模型调到Dice不再涨之后性价比最高的提分手段是交叉验证加集成。2800张数据做5折交叉验证每折训练一次再对5个模型的预测做软投票肿瘤类别的Dice通常能再提升3到5个百分点。6.1 按患者分组的5折交叉验证划分时不能用随机打乱必须用GroupKFold按患者ID分组。这样同一患者的切片不会被分到不同折评估结果才可信。from sklearn.model_selection import GroupKFold image_paths sorted(img_dir.glob(*.png)) patient_ids [p.stem.split(_)[0] for p in image_paths] # 假设文件名以患者ID开头 gkf GroupKFold(n_splits5) for fold, (tr_idx, va_idx) in enumerate(gkf.split(image_paths, groupspatient_ids)): train_paths [image_paths[i] for i in tr_idx] val_paths [image_paths[i] for i in va_idx] print(ffold {fold}: train{len(train_paths)}, val{len(val_paths)})逻辑说明GroupKFold会保证patient_ids相同的样本始终落在同一个折避免信息泄漏。如果文件名不是按患者ID开头需要改解析逻辑但分组原则不能省。6.2 加权软投票集成5折训练完成后推理阶段让每个模型输出像素概率再按Dice表现加权平均final_prob sum(w[i] * probs[i] for i in range(5)) / sum(w) final_mask final_prob.argmax(1)逻辑说明w[i]是第i折模型在验证集上的Dice权重probs[i]是该模型输出的softmax概率。加权平均比分数的硬投票更平滑。如果某一折模型明显偏低它的权重自动压低不会把最终结果带偏。从那以后我每次拿到新的分割数据集都会先写一遍数据划分校验确认同一患者不会同时出现在训练和验证集再考虑模型结构。这个习惯让很多本会翻车的实验少走了弯路。希望帮到你。本文还有配套的精品资源点击获取