简介:面向医学图像分割、深度学习入门与科研场景,这份大脑磁共振脑瘤图像分割数据集专注于二值图像分割任务,可直接用作训练与测试的基准数据。数据划分为训练集与测试集两个部分,训练集含1099张原始图片与1099张对应掩膜,测试集含274张原始图片与274张对应掩膜,图像与GT文件一一对应,无需额外转换或清洗。压缩包共2000个文件,其中1999个为TIF格式影像与掩膜,另有1个Python可视化脚本,可随机抽取一张样本并将原始图片、GT图像以及GT叠加蒙板图同时展示保存,便于直观核验标注效果。整个资源包约130.8MB,文件命名基于TCGA脑瘤公开来源,目录结构清晰,适合快速开展分割模型训练、验证与结果可视化。目前已有395人学习下载,面向医学影像处理方向的学生、算法工程师及科研人员均适用。
1. 大脑磁共振脑瘤图像分割的数据集:它到底解决什么问题
做大脑磁共振脑瘤分割时,真正决定模型上限的往往不是网络结构,而是数据集本身。标题里的“二值图像分割任务”,意味着模型只需要判断每个像素属于肿瘤还是背景,但医学图像数据集的构建远没有“准备一批图、配一批标签”这么简单。公开基准里拿到的脑瘤数据大多是 NIfTI 格式的三维体数据,主流分割框架却要求二维切片或特定尺寸输入,这中间横着格式转换、标签对齐和类不平衡三层问题,任何一层出错,U-Net 类模型都可能训出一个“稳定输出全零”的废物。这篇文章会顺着原始数据整理、标注校验、模型训练、结果验证这一条主线,把二值分割数据集从原始文件到训练产物的完整链路讲清楚。适合正在用 U-Net、nnU-Net 做医学图像分割的算法工程师和研究生,也适合想把手里的脑瘤磁共振数据整理成规范数据集的医学影像从业者。
2. 从体数据到二值分割数据集:NIfTI、标签校验与切片导出
2.1 模态选择:二值分割输入该用 T1、T1ce 还是 T2
脑瘤磁共振常见模态包括 T1、T1ce(对比增强)、T2 和 FLAIR。对二值分割这类“只分肿瘤和背景”的任务,模态选择直接决定标签的含义。T1ce 中肿瘤边界清晰,通常用来标注肿瘤核心;T2 和 FLAIR 对水肿区域敏感,如果标签里包含了水肿,就应该用 T2 或 FLAIR 做输入。常见公共数据集的二值标签只标肿瘤核心区域,此时输入选 T1ce 最稳,其他模态可以作为多通道输入,前提是四组体数据已经做过配准。自制数据集时,如果原始 DICOM 序列来自不同扫描时刻,体素坐标系并不对齐,直接用多通道输入会把边界错位当成特征学进去,这一点比少一个通道更致命。
得到 NIfTI 文件之后,第一步不是急着切片,而是先检查体素方向。nibabel 读出来的数组轴序是(x, y, z),而 ITK-SNAP 显示时通常按(row, col, slice)解释。直接用plt.imshow(data[:, :, i])预览没问题,但你用 OpenCV 写成 PNG 时,行列顺序一旦转置,mask 就会跟着旋转 90 度。一个规避思路是切片前后都用 ITK-SNAP 打开原图和导出图各检查一次。
2.2 二值 mask 的 3 个校验点
拿到标注文件后,不要急着训练。先对每一对 volume 和 mask 做静态校验,常见问题集中在维度、数值范围和体素间距三处。下面是我在数据接入阶段固定的检查项:
| 检查项 | 检查方法 | 通过标准 |
|---|---|---|
| 维度一致 | 用 nibabel 读入后比对img.shape[:3] == msk.shape[:3] | 严格相等,否则直接报错 |
| 数值范围 | np.unique(msk)输出 | 二值标签只包含 0 和 1;导出为 PNG 时只包含 0 和 255 |
| 体素间距一致 | 比较img.header['pixdim'][1:4]和msk.header['pixdim'] | 二者完全一致,或在同一重采样管线中统一 |
维度不一致往往发生在标注文件是从另一套体数据导出的场景里,比如医生在 T2 上勾画了病灶,却配给了 T1ce 做标签。数值范围这一项看似低级,但很多全零 mask 就是把原本[0, 255]的标签直接喂进归一化层后被除掉了。而 pixdim 不一致会导致空间坐标偏移,特别是用np.load脱离 NIfTI 头信息后,切片的物理位置不再可复现。我的处理原则是:所有几何变换(重采样、裁剪、旋转)都在 NIfTI 或 ITK 层面完成,导出为 PNG 是最后一步,导出后不再做任何缩放。
2.3 从 NIfTI 切片提取 2D 分割样本的 Python 实现
把三维体数据转成二维样本通常沿轴向切片,代码并不复杂,但有几个参数必须自定义:切片轴、是否保留全背景切片、灰度归一化方式。下面的实现是按轴状面切片,并只保留含前景的层。
import nibabel as nib import numpy as np import cv2 from pathlib import Path def normalize_to_uint8(volume_slice): # 用 1% 和 99% 分位截断,避免个别高亮伪影拉爆整体灰度 lo, hi = np.percentile(volume_slice, (1, 99)) clipped = np.clip(volume_slice, lo, hi) mapped = (clipped - lo) / (hi - lo + 1e-6) return (mapped * 255).astype(np.uint8) def nii_to_slices(nii_path, mask_path, out_img_dir, out_mask_dir, slice_axis=2): img = nib.load(str(nii_path)).get_fdata() msk = nib.load(str(mask_path)).get_fdata() assert img.shape[:3] == msk.shape[:3], \ f"shape mismatch: {nii_path} vs {mask_path}" # 二值化兜底:浮点标签只取 0/1 msk = (msk > 0.5).astype(np.uint8) out_img_dir.mkdir(parents=True, exist_ok=True) out_mask_dir.mkdir(parents=True, exist_ok=True) n_slices = img.shape[slice_axis] for idx in range(n_slices): if slice_axis == 2: img_slice, msk_slice = img[:, :, idx], msk[:, :, idx] elif slice_axis == 0: img_slice, msk_slice = img[idx, :, :], msk[idx, :, :] else: img_slice, msk_slice = img[:, idx, :], msk[:, idx, :] # 只保留前景层:全背景切片会放大类不平衡,还拖慢训练 if msk_slice.max() == 0: continue img_uint8 = normalize_to_uint8(img_slice) cv2.imwrite(str(out_img_dir / f"{Path(nii_path).stem}_{idx:04d}.png"), img_uint8) cv2.imwrite(str(out_mask_dir / f"{Path(nii_path).stem}_{idx:04d}.png"), msk_slice * 255)slice_axis=2表示沿 z 轴切轴状面,这是脑瘤分割最常用的观察平面;如果你的标注是在冠状面或矢状面上完成的,需要改成 0 或 1。保留前景层这一步对二值分割尤其重要:一个 256×256×150 的 volume 里,肿瘤可能只在 20 层出现,若全量导入,训练集有超过 85% 的样本是纯背景。normalize_to_uint8用分位截断代替全局 min-max,是因为磁共振图像的灰度没有绝对物理意义,个别 extreme outlier 会把正常组织压低成一片黑。
3. 构建脑瘤图像分割数据集的实操:标注、增强与类平衡
3.1 ITK-SNAP 标注:从手动勾画到二值 mask 导出
没有现成标签时,标注工具的选择直接影响数据集质量。我用得最多的是 ITK-SNAP,它是 FreeSurfer 社区常用的医学图像标注软件,直接读 NIfTI,标注结果是同尺寸的标签体数据。标注流程一般走四步:打开 volume,加载 T1ce 作为主图;在 Segmentation Label 里把 Label 1 改名为 tumor,颜色设为红色;用 Active Label 模式在轴状面逐层勾画肿瘤边界;最后通过 Segmentation → Save Segmentation Image 导出 NIfTI 格式 mask。保存时格式选 NIfTI-1,不要选 ANALYZE,后者不带完整的 qform 信息,后续配准容易丢方向。
勾画时有两个常见分歧:坏死腔要不要标进去、明暗边界线算不算肿瘤。二值任务里没有中间状态,所以我的处理方法是先和临床医生定好协议:T1ce 高亮区域整体算肿瘤,内部低信号坏死腔算肿瘤;水肿不算。宁可协议早定,也不要让两个标注员各画各的。标注完成后,我会每个 volume 抽查三张轴状面切片,把预测可能出问题的边界层和医生快速复核一遍。这一步虽然原始,但能拦截掉大量“边界偏移 2mm 但不影响肉眼观察”的标签错位。
3.2 用 albumentations 做有节制的离线增强
医学图像样本量通常不大,脑瘤二值分割又高度依赖边界纹理,增强要克制。直接照搬自然图像那套随机裁剪、翻转、色彩抖动容易制造伪影。下面是一组适合脑瘤二值分割的增强管道:
import albumentations as A train_aug = A.Compose([ A.RandomRotate90(p=0.5), A.ShiftScaleRotate( shift_limit=0.05, scale_limit=0.1, rotate_limit=15, p=0.7, ), A.ElasticTransform( alpha=1.0, sigma=20.0, p=0.3, ), A.RandomBrightnessContrast( brightness_limit=0.1, contrast_limit=0.1, p=0.3, ), ], additional_targets={'mask': 'mask'})调用时同时传入image和mask,albumentations 会保证几何变换对二者一致。ShiftScaleRotate的shift_limit=0.05指的是相对图像宽高,对 256×256 的输入约平移 12 像素,超过这个值容易把病灶推出视野。rotate_limit=15也是有意控制的量,脑部解剖结构有对称性,但不是所有病灶都旋转不变,角度过大会让模型去拟合旋转伪影。ElasticTransform模拟的是组织在采集过程中的轻微形变,适合配准后的数据;如果原始 volume 质量一般,deformable 增强反而会把边界揉坏,建议 p 值降到 0.15 以下或直接关闭。
3.3 肿瘤区域占比太低:筛选、采样与损失函数配合
脑瘤区域通常只占整个大脑体积的 3%~8%,按像素算,二值分类的背景远多于前景。单纯用交叉熵训练,模型很快会收敛到全预测背景。常用的处理分三个层面:数据层、采样层和损失函数层。数据层沿用切片导出时的筛选逻辑,只保留含前景的切片;采样层在每个 batch 内强制前景样本占比不低于 30%,做法是构建索引时按“有病灶切片”和“无病灶切片”分组,每个 step 分别从两组中随机取数。损失函数层面,DiceLoss 是比 CrossEntropy 更稳的起点,因为它天然按类别占比平衡了梯度,我对边界轮廓比较在意时会用 Focal Loss 与 DiceLoss 按 0.5:0.5 加权混合,Focal 的 gamma 取 2 可以有效抑制简单背景样本对梯度的主导。
类不平衡不是通过简单增加肿瘤像素权重就能解决,病灶内部的纹理多样性和边界形态变化才是难点。这里更值得花时间做的是把同一病人的多模态信息利用起来,T1ce 与 FLAIR 的融合输入能显著提升边界召回。
4. 把数据集送进医学图像分割模型:预处理、划分与训练落地
4.1 目录划分:按 subject 切,不要按切片切
数据集规范化是训练前最不被重视的一步。二值分割数据集的常规目录结构如下,参考了 nnU-Net 的布局习惯,方便以后直接迁移到其他框架:
dataset/ ├── imagesTr/ ├── labelsTr/ ├── imagesTs/ ├── labelsTs/ └── splits/ └── fold0.json数据泄露是自制数据集最容易踩的坑。同一个病人的相邻切片高度相似,如果按切片随机划分,部分训练切片和验证切片可能来自同一个 volume,Dice 虚高 5 个百分点以上。划分必须按 subject 为粒度,先将所有 volume 的 NIfTI 文件名去重成病人 ID,再对病人列表做随机切分,最后才展开成切片路径。
import json import random from pathlib import Path img_dir = Path("dataset/imagesTr") subjects = sorted({p.name.split("_")[0] for p in img_dir.glob("*.nii.gz")}) random.seed(42) random.shuffle(subjects) val_ratio = 0.2 val_count = max(1, int(len(subjects) * val_ratio)) split = { "train": subjects[val_count:], "val": subjects[:val_count], } Path("dataset/splits").mkdir(exist_ok=True) with open("dataset/splits/fold0.json", "w") as f: json.dump(split, f, indent=2)random.seed(42)是让划分结果可复现的重要细节,医学实验里没有固定划分,论文结果和复现代码对不上是常事。subjects去重逻辑假设文件名前缀就是病人 ID,如果原始命名不规律,先整理文件名再跑这段脚本。按 volume 划分后的 val 集大约 20%,对脑瘤这种强异质性任务已经够用;如果数据总量少于 30 例,可以改成五折交叉验证,取各折 Dice 均值和方差。
4.2 数据加载器的预处理:patch 怎么裁、mask 怎么传
脑瘤分割常用 256×256 输入,而原始磁共振切片通常是 512×512 或 384×384。全局缩放成 256×256 虽然简单,但会丢失精细边界信息,更好的选择是随机裁剪 patch。裁剪策略对训练效果影响很大,固定从中心裁会导致靠近颅骨的病灶采样不到,完全随机裁则大量 patch 落在背景上。折中做法是以 mask 质心为锚点裁切一次,再在该位置附近做小范围随机扰动。
import numpy as np from scipy.ndimage import center_of_mass from torch.utils.data import Dataset class TumorPatchDataset(Dataset): def __init__(self, samples, patch_size=256, aug=None): self.samples = samples self.patch_size = patch_size self.aug = aug def __len__(self): return len(self.samples) def __getitem__(self, idx): img_path, msk_path = self.samples[idx] img = cv2.imread(img_path, cv2.IMREAD_GRAYSCALE) msk = cv2.imread(msk_path, cv2.IMREAD_GRAYSCALE) msk = (msk > 127).astype(np.uint8) h, w = img.shape[:2] ps = self.patch_size if msk.max() > 0: cy, cx = center_of_mass(msk) cy = int(min(max(cy, ps // 2), h - ps // 2)) cx = int(min(max(cx, ps // 2), w - ps // 2)) else: cy, cx = h // 2, w // 2 y0, x0 = cy - ps // 2, cx - ps // 2 img_patch = img[y0:y0 + ps, x0:x0 + ps] msk_patch = msk[y0:y0 + ps, x0:x0 + ps] if self.aug is not None: auged = self.aug(image=img_patch, mask=msk_patch) img_patch, msk_patch = auged["image"], auged["mask"] img_tensor = torch.from_numpy(img_patch).float().unsqueeze(0) / 255.0 msk_tensor = torch.from_numpy(msk_patch).long() return img_tensor, msk_tensorcenter_of_mass对形心位置做裁剪,保证每个 patch 至少包含一部分肿瘤区域。int(min(max(...)))这段边界处理很容易漏掉,不加会导致病灶靠近图像边缘时裁剪越界或直接 index error。归一化在这里统一为/255.0,如果前面导出 PNG 时用的是 0~255,那这里就是唯一归一化入口;如果你保留的是 NIfTI 原始体素值,则应该在读取时做 z-score,而不是在 patch 层再做切片的独立归一化,后者会破坏跨层灰度一致性。
4.3 训练配置:参数设置、损失函数与验证节奏
二值分割模型的训练配置可以浓缩成下面这张表,适合 U-Net 及其变体在单卡 11GB 显存下复跑:
| 参数 | 取值 | 说明 |
|---|---|---|
| patch_size | 256×256 | 与网络下采样层数匹配,四层下采样到 16×16 |
| batch_size | 8 | 显存不够就降到 4,配合梯度累积 |
| max_epochs | 200 | 早停 patience=30,防止小样本过拟合 |
| optimizer | AdamW,初学率 1e-4 | weight_decay 1e-5,正则不用太大 |
| loss | DiceLoss + CE,权重 0.5 : 0.5 | 二值任务建议 monitor 验证集 Dice |
| augment | 按 3.2 配置开启 | 验证集不做增强 |
| metric | Dice,按 volume 汇总 | 切片 Dice 取平均不等于 volume Dice |
训练时要同时保存最优权重和最后一个 epoch 的权重,医学分割里验证集 Dice 最高点往往不是边界最稳的点;很多论文只报最佳 Dice,等你自己复现时会发现波动在 1~2 个点内。学习率调度用 cosine 衰减或 ReduceLROnPlateau 都可以,但 ReduceLROnPlateau 的 patience 要小于早停的 patience,否则模型已经过拟合了才降学习率,改观有限。验证频率每 epoch 一次,小数据集上开销不大。
5. 验证 Dice、可视化预测,排掉二值分割数据集的暗坑
5.1 用 Dice 系数快速评估,和论文数值对齐
二值分割任务最通用的评估指标是 Dice 系数,计算公式是预测 mask 与真实 mask 交集的 2 倍除以两者像素数之和。PyTorch 推理时,注意要做好的两件事:一是sigmoid后的概率图不要先用argmax,二值任务只需要> 0.5阈值即可;二是计算 Dice 时按 volume 累积inter和total,而不是先算每张切片 Dice 再平均,后一种做法会让小病灶切片被大背景切片稀释。
import numpy as np def dice_score(prob_map, gt_mask, threshold=0.5): pred = (prob_map > threshold).astype(np.uint8) gt = (gt_mask > 0.5).astype(np.uint8) inter = (pred * gt).sum() pred_sum = pred.sum() gt_sum = gt.sum() dice = (2 * inter + 1e-6) / (pred_sum + gt_sum + 1e-6) return dice, (pred_sum, gt_sum, inter)smooth=1e-6是为了防止两个 mask 全零时报除零错误,这对二值任务特别重要:验证集里某些切片确实可以全零。通过返回的pred_sum、gt_sum和inter,你可以快速定位那种“预测面积和真实面积差异大但 Dice 不算低”的样本——比如预测 mask 是真实 mask 的两倍大小但形状高度重叠,这类情况仅看 Dice 无法暴露,需要配合可视化检查。
5.2 预测结果的 3 个可视化检查
数值指标之外,我固定做三张可视化图,输出两张 PNG 不是流失在 TensorBoard 里的。第一张是原图与预测 mask 的叠加图,用绿色画预测、红色画真实标签,重叠部分显示黄色,从颜色分布可以直观看到偏移方向。第二张是连通域检查,脑瘤的二值预测通常应该是一个主连通域加少量子灶,如果你得到十几个小碎片,首先怀疑的是阈值取低了,或者是增强把 faint 边界放大了。第三张是边界距离热力图,用 scipy.ndimage.distance_transform_edt 对真实 mask 求边界距离,把预测 mask 的错误像素按“离真实边界多远”着色,可以快速判断错误集中在边界还是孤岛区域,是严重伪影还是轻度边缘偏离。如果没有进行这一步就进入调参,往往会花大量时间在错误的超参数上。
5.3 坐标、方向和灰度归一化的暗坑排查
| 异常现象 | 可能原因 | 排查顺序 |
|---|---|---|
| 预测 mask 整体偏移,Dice 却还有 0.8 | 重采样后标签没同步插值 | 检查原图和标签的 affine 是否一致 |
| 切片旋转 90 度 | 读图库行列顺序与 NIfTI 不一致 | 用 ITK-SNAP 对比原图和导出 PNG |
| 训练 Dice 高、验证 Dice 低 | 按切片划分导致同病人泄露 | 改按 subject 划分,检查 fold0.json |
| 全背景样本预测出零星前景 | 类不平衡 + 阈值过高 | 使用前景 patch 采样并检查阈值 |
| 灰度整体偏低或偏亮 | 分位截断参数不合适 | normalize_to_uint8中(1, 99)改为(0.5, 99.5) |
最后一个暗坑是 torch 和 OpenCV 的通道顺序。灰度图没有三通道问题,但如果后期你要把 T1ce 和 FLAIR 合成两通道输入,建议在 Dataset 内部将两张 PNG 同时读入并拼接,避免在外部用人工拼路径的方式维护多模态数据,那样很容易出现 index 错位。推理完成后,如果要做三维层面的验证,可以把 2D 切片预测拼回原始 volume 的(x, y, z)坐标,重新封装成 NIfTI,再用 open-source 的体绘制工具将 nii 体素数据渲染成 3D 视图;脑瘤分割结果的 3D 渲染可以与原始解剖图像做融合显示,也就是常说的医学图像融合,这一步能直接发现二维切片上不容易暴露的问题,比如连接破碎、空洞残留。建议在交付最终模型之前,至少对 5 个验证集病例做一次这个检查。
本文还有配套的精品资源,点击获取