
简介本资源为面向医学图像分割任务的Liver肝脏癌症2D数据集适合从事肝脏及肿瘤分割研究的学生、算法工程师与科研人员使用。原始数据为Liver3d的nii.gz文件沿x轴切分并剔除前景区域不足0.05的切片共提取8千余张图像mask像素分为0背景、1肝脏、2癌症三类可直接用于训练与评估分割模型。压缩包共约2000个文件以1998张png图像为主另含1个py可视化脚本与1个json配置文件整体约937.6MB已按训练集6227张图片及对应mask、测试集2668张图片及对应mask划分完毕目录结构清晰。附带的可视化脚本可随机抽取一张图片展示原始图像、GT图像及GT在原图上的蒙板效果并保存至当前目录便于快速核验标注质量。目前已有877人学习下载适合需要现成2D肝脏癌症分割数据与可视化工具的研究者参考使用。1. 肝脏分割数据集怎么选从 3D 原始体数据到 8k 张 2D 切片的取舍做腹部 CT 肝脏及肿瘤分割的同行大概都有体会公开数据里 LiTS、MSD Task03 这类 3D 体数据是主流但真到训练阶段显存和 IO 往往先把你卡死。这份 Liver 肝脏癌症数据集走的是另一条路把原始 nii.gz 体数据沿 x 轴切片剔除前景占比不足 0.05 的空白层最终留下 8k 多张 2D 图像mask 用 0/1/2 三值标注背景、肝脏、癌症并按 6227 训练 2668 测试切分好。它解决的不是数据从哪来而是拿到就能直接喂给 2D 分割网络——适合显存有限、想快速跑通肝脏肿瘤分割 baseline 的人也适合做数据增强、损失函数对比这类实验。下面按我实际拆包的顺序讲清楚怎么用、参数怎么设、哪里会翻车。2. 数据组织与标签语义先搞懂 0/1/2 和目录结构再动手2.1 目录结构与文件命名规律解压后你会看到训练集和测试集各自独立每个集合下都是images和masks两个平行目录图片一一对应。命名形如liver_108_519.png中间的数字段是原始体数据的切片编号最后一段是层内序号。这种命名不是随便起的——它保留了切片在原始 3D 体数据里的位置信息后面做 3D 重建或者按病例划分验证集时靠这个编号就能把同一病例的切片聚回去。Liver_Dataset/ ├── train/ │ ├── images/ # 6227 张 PNG │ └── masks/ # 6227 张 PNG与 images 同名 ├── test/ │ ├── images/ # 2668 张 PNG │ └── masks/ # 2668 张 PNG ├── dataset.json # 数据集元信息 └── visualize.py # 可视化脚本dataset.json里通常记录了类别映射、图像尺寸、划分数量这类元信息动手前先读一遍别急着写 DataLoader。常见做法是用它来校验你读到的类别数和 mask 像素值是否一致避免标签错位这种低级但致命的错误。2.2 三值 mask 的语义与读取陷阱mask 的像素分布是 0 背景、1 肝脏、2 癌症。这里有个血泪经验PNG 存灰度图时如果保存环节用了调色板或者做了归一化读出来可能变成 0/85/170 或者 0/128/255 这种映射值。所以第一步不是训练是验证像素值。import numpy as np from PIL import Image import os mask_dir Liver_Dataset/train/masks # 抽查若干张 mask确认像素值只有 0/1/2 sample os.listdir(mask_dir)[:20] for name in sample: m np.array(Image.open(os.path.join(mask_dir, name))) uniq np.unique(m) if not set(uniq).issubset({0, 1, 2}): print(f{name} 异常像素值: {uniq}) else: print(f{name} OK, 前景占比: {(m 0).mean():.4f})这段代码做两件事一是确认 mask 只有 0/1/2 三个值二是顺手统计前景占比。如果发现某张图前景占比极低比如低于 0.01说明切片过滤阈值 0.05 在边缘层可能漏掉了一些训练时这类样本对 loss 贡献很小可以考虑在 sampler 里降采样。参数上set(uniq).issubset({0,1,2})是硬校验别省前景占比统计用来判断数据分布是否均衡。2.3 为什么按 x 轴切分而不是 z 轴原始 3D 数据是 nii.gz沿 x 轴切分意味着每张 2D 图是冠状面或矢状面视角取决于原始朝向而不是常见的横断面。这个选择直接影响你后续的预处理横断面切片里肝脏和肿瘤的形态更符合放射科阅片习惯冠状面则对上下边界更敏感。如果你打算迁移到横断面数据上做推理得先确认朝向一致否则模型学到的空间先验会对不上。我一般会在训练前用可视化脚本抽几张图看一眼解剖朝向确认无误再往下走。3. 训练集/测试集加载写一个能直接跑的 Dataset 和划分校验3.1 自定义 Dataset 与同步增强images 和 masks 必须同名对应增强时图像和 mask 要同步变换这是分割任务最容易翻车的地方。下面这个 Dataset 用 albumentations 做同步增强注意 mask 的插值必须用最近邻否则会把 1/2 的边界插出 1.5 这种非法值。import os import numpy as np import cv2 import torch from torch.utils.data import Dataset import albumentations as A class LiverSegDataset(Dataset): def __init__(self, root, splittrain, size256): self.img_dir os.path.join(root, split, images) self.mask_dir os.path.join(root, split, masks) self.names sorted(os.listdir(self.img_dir)) self.size size # 训练用增强验证/测试只做 resize if split train: self.tf A.Compose([ A.Resize(size, size), A.HorizontalFlip(p0.5), A.RandomBrightnessContrast(p0.3), A.ShiftScaleRotate(shift_limit0.05, scale_limit0.1, rotate_limit15, p0.5), ]) else: self.tf A.Compose([A.Resize(size, size)]) def __len__(self): return len(self.names) def __getitem__(self, idx): name self.names[idx] img cv2.imread(os.path.join(self.img_dir, name), cv2.IMREAD_GRAYSCALE) mask cv2.imread(os.path.join(self.mask_dir, name), cv2.IMREAD_GRAYSCALE) aug self.tf(imageimg, maskmask) img, mask aug[image], aug[mask] # 归一化到 [0,1]mask 保持整数类别 img img.astype(np.float32) / 255.0 img np.expand_dims(img, axis0) # (1,H,W) mask mask.astype(np.int64) return torch.from_numpy(img), torch.from_numpy(mask)逻辑说明sorted(os.listdir())保证 images 和 masks 顺序严格一致这是同名对应的前提A.Resize统一到 256显存友好增强只加在训练集验证集只 resize避免评估时引入随机性。参数上size按你的显存调256 是 8G 显存的稳妥选择512 需要更大显存或梯度累积。mask 用IMREAD_GRAYSCALE读避免三通道干扰。3.2 训练/测试划分的校验与病例级泄漏排查数据集已经分好 train/test但你要确认一件事同一病例的切片有没有同时出现在训练集和测试集里。如果按切片随机划分同一病人的相邻切片会泄漏测试指标虚高。用文件名里的病例编号来查import os, re from collections import defaultdict def case_id(fname): # liver_108_519.png - 108 m re.match(rliver_(\d)_\d\.png, fname) return m.group(1) if m else None train_ids {case_id(f) for f in os.listdir(Liver_Dataset/train/images)} test_ids {case_id(f) for f in os.listdir(Liver_Dataset/test/images)} overlap train_ids test_ids print(f训练病例数: {len(train_ids)}, 测试病例数: {len(test_ids)}) print(f重叠病例: {overlap if overlap else 无泄漏})如果overlap非空说明存在病例级泄漏测试集指标不可信需要按病例重新划分。这一步很多人跳过等到论文复现对不上才回头查属于典型的后悔药场景。参数上正则liver_(\d)_\d要按你实际命名调整如果命名规则不同先打印几个文件名确认。3.3 类别不均衡下的采样与损失选择肝脏占比较大癌症区域往往很小0/1/2 三类极度不均衡。直接上交叉熵模型会倾向于全预测背景或肝脏。常见做法是组合损失Dice 交叉熵或者对癌症类加权。import torch.nn as nn class DiceCELoss(nn.Module): def __init__(self, weightNone): super().__init__() self.ce nn.CrossEntropyLoss(weightweight) def forward(self, logits, target): ce self.ce(logits, target) # 多类 Dice probs torch.softmax(logits, dim1) dice 0.0 for c in range(1, logits.shape[1]): # 跳过背景 p probs[:, c] t (target c).float() inter (p * t).sum() dice 1 - (2 * inter 1e-6) / (p.sum() t.sum() 1e-6) return ce dice / (logits.shape[1] - 1)参数说明weight可以传torch.tensor([0.1, 1.0, 5.0])这种把癌症类权重拉高Dice 部分跳过背景类只算肝脏和癌症避免背景主导。这个组合在肝脏肿瘤分割里是经过验证的稳妥起点比单纯 Focal Loss 更好调。4. 可视化脚本拆解随机抽图看原图、GT 和叠加蒙板4.1 脚本做了什么数据集自带一个可视化脚本随机抽一张图把原始图像、GT 标注、GT 在原图上的蒙板三张图并排展示并保存到当前目录。这个脚本看着简单但它是你验证数据质量的第一道关。我一般拿到任何分割数据集第一件事就是跑可视化确认图像和 mask 对得上、类别颜色正确、没有错位。4.2 自己写一个更可控的可视化自带脚本够用但如果你想指定某张图、或者批量抽图看分布自己写一个更灵活。下面这个版本支持指定文件名、叠加半透明彩色蒙板、保存对比图。import os import numpy as np import cv2 import matplotlib.pyplot as plt def visualize(root, splittrain, nameNone, savevis_result.png): img_dir os.path.join(root, split, images) mask_dir os.path.join(root, split, masks) if name is None: name np.random.choice(os.listdir(img_dir)) img cv2.imread(os.path.join(img_dir, name), cv2.IMREAD_GRAYSCALE) mask cv2.imread(os.path.join(mask_dir, name), cv2.IMREAD_GRAYSCALE) # 彩色蒙板肝脏绿色癌症红色 overlay cv2.cvtColor(img, cv2.COLOR_GRAY2BGR) overlay[mask 1] (0, 255, 0) overlay[mask 2] (0, 0, 255) blend cv2.addWeighted(cv2.cvtColor(img, cv2.COLOR_GRAY2BGR), 0.6, overlay, 0.4, 0) fig, axes plt.subplots(1, 3, figsize(12, 4)) axes[0].imshow(img, cmapgray); axes[0].set_title(原图) axes[1].imshow(mask, cmapjet); axes[1].set_title(GT) axes[2].imshow(blend[..., ::-1]); axes[2].set_title(叠加蒙板) for ax in axes: ax.axis(off) plt.tight_layout() plt.savefig(save, dpi150) print(f已保存: {save}, 样本: {name}) visualize(Liver_Dataset, splittrain)逻辑说明addWeighted做半透明叠加0.6 原图 0.4 蒙板既能看清解剖结构又能看清标注区域blend[..., ::-1]是因为 OpenCV 是 BGRmatplotlib 要 RGB。参数上nameNone时随机抽想看特定样本就传文件名。跑完看一眼如果癌症区域红色位置明显不对说明 mask 和 image 错位得回去查读取逻辑。4.3 从可视化里能看出什么除了确认对齐可视化还能帮你判断数据质量如果某些图肝脏边界模糊、癌症区域几乎看不见这些样本在训练时容易产生噪声梯度。我一般会抽 50 张左右快速过一遍把明显异常的样本记下来训练时要么剔除要么降权。这一步花十分钟能省掉后面调参时怀疑人生的几个小时。5. 避坑与常见问题排查数据加载和训练里的五个翻车点5.1 现象mask 读出来是 0/85/170 而不是 0/1/2原因PNG 保存时用了调色板模式或者做了灰度映射PIL 或 OpenCV 读出来是映射后的值。解决用np.unique先查实际像素值如果是 0/85/170手动映射回去mask mask // 85更稳妥的做法是确认保存环节用IMREAD_GRAYSCALE且不做归一化。5.2 现象训练 loss 一直降但验证 Dice 不涨原因大概率是病例级泄漏同一病人的切片同时进了训练和测试模型在背病人特征。解决用 3.2 的脚本查病例编号重叠如果有重叠按病例重新划分确保同一病例只出现在一个集合里。5.3 现象癌症类 Dice 始终接近 0原因类别极度不均衡交叉熵被背景和肝脏主导癌症类梯度被淹没。解决换 Dice CE 组合损失给癌症类加权或者在 sampler 里对含癌症的切片过采样。参数上癌症类权重从 5.0 起步试太高会导致肝脏类性能下降。5.4 现象增强后 mask 出现 1.5 这种非整数值原因mask 用了双线性插值。解决albumentations 里 mask 的插值默认是最近邻但如果你手动用 cv2.resize必须指定interpolationcv2.INTER_NEAREST。这个坑很隐蔽训练时不会报错但类别会悄悄变多。5.5 现象测试集指标比训练集高很多原因除了泄漏还可能是测试集切片集中在肝脏形态规整的层面而训练集包含大量边缘层。解决统计两个集合的前景占比分布如果差异大说明划分不是随机的需要重新分层抽样。6. 进阶技巧用切片编号做 3D 重建验证分割连续性2D 切片训练有个天然缺陷相邻切片的分割结果可能不连续出现锯齿或空洞。这份数据保留了切片编号你可以把测试集的预测结果按编号拼回 3D 体数据沿 x 轴看连续性。具体做法是对测试集每张图推理按liver_病例号_层号分组按层号排序堆叠成 3D 数组然后沿切片方向看某一行的类别变化。import re import numpy as np def stack_by_case(pred_dict): # pred_dict: {filename: 2D prediction array} cases {} for fname, pred in pred_dict.items(): m re.match(rliver_(\d)_(\d)\.png, fname) cid, sid m.group(1), int(m.group(2)) cases.setdefault(cid, []).append((sid, pred)) volumes {} for cid, slices in cases.items(): slices.sort(keylambda x: x[0]) volumes[cid] np.stack([s[1] for s in slices], axis0) return volumes # 假设 preds 是你的推理结果字典 # vols stack_by_case(preds) # 沿切片方向检查癌症类(2)是否出现孤立单层 # for cid, v in vols.items(): # cancer_per_slice (v 2).sum(axis(1, 2)) # print(cid, cancer_per_slice)逻辑说明按病例分组、按层号排序后堆叠成 3D然后统计每层癌症像素数。如果出现有-无-有这种跳变说明 2D 预测不连续可以考虑在推理后做 3D 连通域后处理或者训练时加入相邻切片作为额外通道。参数上层号排序是关键命名规则变了要同步改正则。我现在的习惯是任何 2D 切片数据集训练前必跑一遍可视化确认对齐训练后必做一次 3D 连续性检查。这两个动作各花不到半小时但能挡掉大部分指标好看、实际不能用的情况。这份 Liver 数据集把切分和标注都做完了省下的时间正好用来做这些验证。希望帮到你。本文还有配套的精品资源点击获取