拓冰建站拓冰建站
首页 / 资讯中心 / 正文

大脑肿瘤分割数据集实战:从2分割标签到可视化全流程

简介本资源面向医学图像分割方向的初学者与算法实践者提供一套416×416分辨率的人脑MRI肿瘤二分割数据集前景为Tumor区域mask以1表示肿瘤、0表示背景标注质量良好、背景简洁适合直接用于U-Net等分割模型的训练与验证。压缩包共2000个文件以1759个png图像与掩膜、240个jpg样本及1个Python可视化脚本为主整体约48.17MB采用7z格式打包。数据按训练集与测试集划分训练集含1632张原图及1632张对应mask测试集含240张原图及240张mask目录结构清晰便于按images与masks配对读取。配套可视化脚本可随机抽取一张图片同时展示原始图像、GT图像以及GT在原图上的蒙板叠加效果并自动保存到当前目录方便快速核验标注质量与分割结果。目前已有1880人学习下载适合需要快速搭建脑肿瘤分割实验基线、验证模型效果的读者参考使用。1. 大脑肿瘤分割数据集从「2分割」标签到可视化一套能直接跑通的方案拿到一个医学图像分割数据集最怕的不是模型跑不动而是标签对不上、可视化出来一片黑、类别编号和掩码像素值错位。大脑肿瘤分割2分割这个方向核心就三件事图像与掩码严格配对、前景背景二分类的标签约定、以及把分割结果叠回原图做可视化验证。它适合两类人一类是想入门医学图像分割但被 BraTS 那套多模态、多区域标签劝退的工程师另一类是要快速验证某个分割网络U-Net、SegFormer、nnU-Net 都行在脑肿瘤二分类任务上到底能不能收敛的从业者。这篇不讲空泛的「医学 AI 前景」只讲这个数据集怎么读、标签怎么定、可视化代码怎么写、训练时哪些参数必须调以及我踩过的那些坑。2. 先搞清楚「2分割」到底分的是什么标签约定与数据组织2.1 二分类掩码的像素值约定别想当然医学图像分割里「2分割」通常指前景肿瘤和背景两类但落到掩码文件上不同来源的约定完全不一样。常见的有三种掩码是 0/1 的 uint8、0/255 的 uint8、或者 0/1 的 float。如果你直接拿mask.max()当类别数遇到 0/255 的掩码就会以为有 256 类训练直接崩。我一般拿到数据先做一次「体检」把每张掩码的唯一值和占比打出来这一步能省掉后面几小时的 debugimport numpy as np import os from pathlib import Path def inspect_masks(mask_dir, sample_n20): 抽查掩码文件的像素值分布确认标签约定 mask_paths sorted(Path(mask_dir).glob(*.png))[:sample_n] for p in mask_paths: m np.array(__import__(PIL.Image).Image.open(p)) uniq, counts np.unique(m, return_countsTrue) ratio counts / m.size # 打印唯一值和前景占比前景占比过小要警惕 print(p.name, dict(zip(uniq.tolist(), np.round(ratio, 4).tolist()))) inspect_masks(./data/masks)逻辑说明np.unique拿到掩码里所有出现过的像素值return_counts给出每个值的像素数除以总像素数就是占比。参数上sample_n抽 20 张足够判断约定不用全量跑。如果输出里出现 255 且占比和 1 差不多说明是 0/255 约定训练前必须除以 255 或做阈值化如果只有 0 和 1那可以直接当类别索引用。这一步不做后面损失函数算出来的值会莫名其妙偏大或梯度爆炸。2.2 图像与掩码的配对规则文件名、尺寸、模态三对齐配对出问题是最隐蔽的翻车点。常见做法是图像和掩码同名不同目录比如images/case_001.png对masks/case_001.png。但有些数据集图像是.png、掩码是.tif或者图像带_img后缀、掩码带_mask后缀。我一般写一个配对检查函数把「有图无掩码」「有掩码无图」「尺寸不一致」三类问题一次性列出来from PIL import Image def check_pairing(img_dir, mask_dir): img_names {p.stem for p in Path(img_dir).glob(*)} mask_names {p.stem for p in Path(mask_dir).glob(*)} only_img img_names - mask_names only_mask mask_names - img_names print(仅有图像:, len(only_img), list(only_img)[:5]) print(仅有掩码:, len(only_mask), list(only_mask)[:5]) # 尺寸对齐检查 for name in list(img_names mask_names)[:10]: img Image.open(next(Path(img_dir).glob(f{name}.*))) msk Image.open(next(Path(mask_dir).glob(f{name}.*))) if img.size ! msk.size: print(尺寸不一致:, name, img.size, msk.size) check_pairing(./data/images, ./data/masks)逻辑说明用stem去掉扩展名做集合运算能同时兼容不同扩展名。尺寸检查只抽前 10 对因为尺寸问题通常是全局性的抽几对就能发现。参数上如果你的数据是 3D 切片导出的 2D 图还要额外确认切片顺序一致否则会出现「图像是第 50 层、掩码是第 51 层」这种错位模型学出来的边界永远是糊的。2.3 数据集划分别用随机划分骗自己医学图像分割的划分有个血泪经验同一个病人的不同切片必须落在同一个集合里。如果你按切片随机划分训练集和验证集会共享同一个病人的相邻切片验证指标虚高得离谱上线就翻车。常见做法是按病人 ID 分组划分7:1:2 或 8:1:1。如果数据集没给病人 ID至少按文件名前缀分组。我一般会写一个分组划分脚本把分组键提取出来再做GroupShuffleSplit或手写划分确保验证集是「没见过的人」而不是「没见过的切片」。3. 把数据喂进模型Dataset 写法与三个必调参数3.1 一个能直接用的 PyTorch Dataset下面这个 Dataset 覆盖了读取、归一化、掩码二值化、增强接口可以直接抄import torch from torch.utils.data import Dataset import numpy as np from PIL import Image import albumentations as A from albumentations.pytorch import ToTensorV2 class BrainTumorDataset(Dataset): def __init__(self, img_dir, mask_dir, img_size256, trainTrue): self.img_paths sorted(Path(img_dir).glob(*)) self.mask_dir Path(mask_dir) self.img_size img_size # 训练用增强验证只做 resize 和归一化 if train: self.tf A.Compose([ A.Resize(img_size, img_size), A.HorizontalFlip(p0.5), A.RandomBrightnessContrast(p0.3), A.Normalize(mean(0.5,), std(0.5,)), ToTensorV2(), ]) else: self.tf A.Compose([ A.Resize(img_size, img_size), A.Normalize(mean(0.5,), std(0.5,)), ToTensorV2(), ]) def __len__(self): return len(self.img_paths) def __getitem__(self, idx): img_path self.img_paths[idx] img np.array(Image.open(img_path).convert(L)) # 灰度按需改 RGB mask_path self.mask_dir / img_path.name mask np.array(Image.open(mask_path).convert(L)) mask (mask 127).astype(np.float32) # 统一成 0/1阈值 127 兼容 0/255 out self.tf(imageimg, maskmask) return out[image], out[mask].unsqueeze(0) # mask 加通道维逻辑说明convert(L)把图像转灰度医学图像多数是单通道如果你的数据是 RGB 就改掉。掩码用 127阈值化这一步同时兼容 0/1 和 0/255 两种约定是省心的写法。unsqueeze(0)给掩码加通道维因为后面 BCE 或 Dice 损失通常要求[B, 1, H, W]。参数上img_size我一般设 256 或 512脑肿瘤区域相对整图偏小256 起步够用显存紧张就降到 224。3.2 归一化参数别照搬 ImageNet 的 mean/std医学图像和自然图像分布差很远照搬 ImageNet 的mean(0.485,0.456,0.406)是常见误用。灰度医学图我一般用mean0.5, std0.5做简单归一化或者先统计训练集的全局均值和方差再填进去。如果你做的是 CT还要注意窗宽窗位HU 值范围可能到 -1000 到 3000直接归一化会把软组织压成一团。常见做法是先做窗宽窗位截断比如脑窗 0~80 HU再归一化。3.3 损失函数与类别不平衡Dice 比 BCE 更稳脑肿瘤在整张图里占比往往很小前景可能只占 1%~5%纯 BCE 会被背景主导模型学会「全预测背景」就能拿到 95% 以上的准确率但 Dice 接近 0。我一般用BCE Dice组合或者直接上DiceLoss。下面是一个能用的 Dice 实现import torch.nn as nn class DiceLoss(nn.Module): def __init__(self, smooth1e-6): super().__init__() self.smooth smooth def forward(self, logits, targets): probs torch.sigmoid(logits) probs probs.view(probs.size(0), -1) targets targets.view(targets.size(0), -1) inter (probs * targets).sum(dim1) union probs.sum(dim1) targets.sum(dim1) dice (2 * inter self.smooth) / (union self.smooth) return 1 - dice.mean()逻辑说明smooth防止分母为 0view把空间维拉平做逐样本计算最后取 batch 平均。参数上smooth设 1e-6 足够设太大会让损失对小的前景不敏感。组合损失一般0.5 * BCE 0.5 * Dice如果前景特别小把 Dice 权重提到 0.7。4. 可视化代码把分割结果叠回原图才算数4.1 三通道叠加可视化一眼看出边界对不对只看 Dice 数字不够必须把预测掩码叠回原图。下面这段代码把原图、真值、预测做成红绿叠加边界错位一眼可见import matplotlib.pyplot as plt import numpy as np def visualize(img, gt_mask, pred_mask, save_pathNone): img: [H,W] 或 [H,W,3]; gt/pred: [H,W] 0/1 img img.squeeze() if img.ndim 2: img np.stack([img] * 3, axis-1) img (img - img.min()) / (img.max() - img.min() 1e-8) overlay img.copy() # 真值用绿色预测用红色重叠区域偏黄 overlay[..., 1] np.where(gt_mask 0, 1.0, overlay[..., 1]) overlay[..., 0] np.where(pred_mask 0, 1.0, overlay[..., 0]) fig, axes plt.subplots(1, 3, figsize(12, 4)) axes[0].imshow(img); axes[0].set_title(image) axes[1].imshow(gt_mask, cmapgray); axes[1].set_title(gt) axes[2].imshow(overlay); axes[2].set_title(overlay) for ax in axes: ax.axis(off) if save_path: plt.savefig(save_path, bbox_inchestight, dpi150) plt.close()逻辑说明先把图像归一化到 0~1再在绿色通道写真值、红色通道写预测重叠处自然偏黄。参数上dpi150够看细节批量可视化时记得plt.close()防止内存泄漏。这一步做完你会立刻发现两类问题预测掩码整体偏移配准或 resize 问题、边界系统性外扩损失函数或阈值问题。4.2 批量可视化与指标联动单张看效率低我一般写一个批量函数按 Dice 从低到高排序优先看最差的 10 张。这样能快速定位是「某些病例特别难」还是「整体都差」。指标计算和可视化联动是排查问题的标准动作。5. 避坑与排查五个真实踩过的坑5.1 掩码全黑Dice 恒为 0现象训练几个 epoch损失不降可视化出来预测全黑。原因掩码读取时用了convert(L)但原掩码是调色板模式P 模式转灰度后前景值被映射成 0。解决先np.array(Image.open(p))看原始值如果是 P 模式用convert(L)前先确认调色板或者直接读原始数组做阈值化。5.2 验证指标虚高上线崩盘现象验证集 Dice 0.9换一批数据掉到 0.5。原因按切片随机划分同一病人的相邻切片泄漏到验证集。解决按病人 ID 分组划分确保验证集病人不出现在训练集。这个坑我踩过两次第二次是因为文件名前缀没提取对分组键写错了。5.3 显存爆了batch size 只能设 2现象256×256 的图batch size 开到 8 就 OOM。原因模型用了高分辨率特征图 大通道数或者没开混合精度。解决开torch.cuda.amp混合精度显存能省 30%~50%或者把img_size降到 224再不行用梯度累积模拟大 batch。5.4 增强把肿瘤「翻」没了现象训练时损失震荡可视化发现有些样本掩码和图像对不上。原因用了A.HorizontalFlip但图像和掩码的增强参数没同步或者用了随机旋转后掩码插值用了双线性导致标签被平滑。解决albumentations 的Compose会同步 image 和 mask但掩码插值必须用最近邻A.Resize默认对 mask 用最近邻自定义增强时要显式指定interpolationcv2.INTER_NEAREST。5.5 阈值 0.5 不是万能药现象Dice 卡在 0.7 上不去调阈值到 0.3 后涨到 0.78。原因模型输出的概率分布偏保守前景概率普遍偏低。解决在验证集上扫一遍阈值0.1~0.9选 Dice 最高的那个再固定到测试集用。这个操作不复杂但很多人忘了做。6. 进阶技巧用 TTA 和阈值搜索把 Dice 再抬一截训练跑通、可视化正常之后想再榨一点指标我一般做两件事测试时增强TTA和阈值搜索。TTA 的做法是对同一张图做水平翻转、多尺度缩放分别推理后把概率图平均再阈值化。多尺度我一般用 0.75、1.0、1.25 三档水平翻转加上就是 6 次推理推理时间涨 6 倍但 Dice 通常能涨 1~3 个点对小目标分割尤其明显。阈值搜索和 TTA 要联动做先对验证集做 TTA 得到平均概率图再在 0.1 到 0.9 之间以 0.05 为步长扫阈值记录每个阈值下的 Dice选最高的。注意阈值要在验证集上选不能拿测试集调否则指标不可信。下面是一个简化的 TTA 阈值搜索骨架def tta_predict(model, img_tensor): img_tensor: [1,C,H,W]返回平均概率图 probs [] for scale in [0.75, 1.0, 1.25]: for flip in [False, True]: x torch.nn.functional.interpolate( img_tensor, scale_factorscale, modebilinear, align_cornersFalse) if flip: x torch.flip(x, dims[3]) with torch.no_grad(): p torch.sigmoid(model(x)) if flip: p torch.flip(p, dims[3]) p torch.nn.functional.interpolate( p, sizeimg_tensor.shape[-2:], modebilinear, align_cornersFalse) probs.append(p) return torch.stack(probs).mean(dim0) def search_threshold(probs, gts, step0.05): best_t, best_d 0.5, 0.0 for t in np.arange(0.1, 0.95, step): d dice_score((probs t).float(), gts) if d best_d: best_t, best_d t, d return best_t, best_d逻辑说明tta_predict对每个尺度和翻转组合推理翻转后再翻回来保证空间对齐最后插值回原尺寸取平均。search_threshold遍历阈值找最优。参数上尺度档位别设太多3 档够用太多收益递减还拖慢推理。这套组合我在几个二分类分割任务上试过稳定涨点但前提是基础模型已经收敛否则 TTA 只是把噪声平均了一下。最后说个习惯每次拿到新数据集我第一件事不是写模型而是花半小时做数据体检——掩码唯一值、配对情况、前景占比、尺寸分布。这半小时能省掉后面一整天。医学图像分割没有玄学坑都在数据里。希望帮到你。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门