news 2026/10/2 2:44:20

肝脏癌症2D分割数据集实战:从预处理到训练避坑指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
肝脏癌症2D分割数据集实战:从预处理到训练避坑指南

简介:本资源为面向医学图像分割任务的肝脏癌症数据集,适合从事肝脏及肿瘤分割研究的学生、算法工程师与科研人员使用。原始数据为Liver3d的nii.gz文件,已在x轴方向切分为2D切片,并剔除前景区域不足0.05的样本,共提取8千余张图像,mask像素分为0背景、1肝脏、2癌症三类,可直接用于训练与评估分割模型。资源包共约2000个文件,以1998张png图像为主,另含1个py可视化脚本与1个json配置文件,压缩包整体约937.6MB。数据已划分训练集与测试集:训练集含6227张图像及6227个对应mask,测试集含2668张图像及2668个对应mask,目录结构清晰。附带的可视化脚本可随机抽取一张图片,展示原始图像、GT图像及GT在原图上的蒙板效果并保存至当前目录,便于快速核验标注质量。目前已有877人学习下载。

1. 肝脏癌症 2D 分割数据集:从拿到手到能训练,中间隔着多少坑

肝脏肿瘤分割是医学图像分割里最经典也最容易被低估的任务之一。很多人第一次接触这个方向,拿到一份标注好的 Liver 肝脏癌症数据,看到已经切成了 2D 切片、还贴心地分好了训练集和测试集,第一反应是「这不直接开训就行了」。结果真正跑起来才发现,切片之间的肝脏边界忽大忽小、肿瘤区域在部分切片里几乎不可见、训练集和测试集的分布对不上,模型在验证集上 Dice 看着还行,换到测试集直接掉十几个点。这份数据集的价值不在于「有数据」,而在于它把 3D 体数据拆成 2D 之后,逼着你去处理切片级的不均衡和上下文丢失问题。它适合已经跑通过基础分割网络、想认真做医学图像分割落地的人,也适合做 B 超、CT 相关课题、需要一份结构清晰的 2D 训练/测试划分来复现 baseline 的从业者。下面我按自己实际处理这类数据的顺序,把选型、预处理、训练、排错讲透。

2. 先搞清楚这份 Liver 数据到底长什么样

2.1 2D 切片划分意味着什么

3D 的腹部 CT 或 MRI 体数据,本质是一摞沿 Z 轴堆叠的切片。把它转成 2D 数据,通常有两种做法:一种是逐切片导出成独立图像,每个切片单独作为一个样本;另一种是保留切片索引,训练时按切片取样。这份数据既然明确说了「划分了 2D 数据」,大概率是前者——每张切片是一张图,配一张对应的掩码。这么做的直接好处是显存压力小,2D 分割网络(U-Net、DeepLab、SegFormer 这类)可以直接吃;代价是丢掉了切片间的空间连续性,肝脏在 Z 轴上的形状约束没了,模型只能靠单张切片的纹理和灰度判断。

理解这一点很关键,因为它决定了你后面所有参数怎么设。2D 切片里,靠近肝脏顶部和底部的切片,肝脏面积很小,甚至只有几个像素;中间层切片肝脏面积最大。如果训练时不做处理,模型会被中间层主导,顶部底部切片的边界学得很差。常见做法是按切片位置做加权采样,或者干脆把面积过小的切片过滤掉——但过滤要谨慎,测试集里如果保留了这些小切片,你训练时扔掉就会造成分布不一致。

2.2 训练集/测试集已经分好,还要不要自己再切验证集

数据给了训练集和测试集,很多人就直接拿训练集训、测试集评。这在工程上不够。训练过程中你需要一个验证集来选模型、调超参、判断什么时候停。直接从训练集里再切一部分出来做验证是最省事的做法,但要注意两点:第一,切分要按病人/病例切,不能按切片随机切,否则同一个病人的相邻切片会同时出现在训练和验证里,验证指标虚高,这就是典型的数据泄漏;第二,切分比例别太随意,医学数据样本量本来就小,验证集占 15%~20% 比较稳妥,切太多训练数据不够,切太少验证指标抖动大。

如果这份数据没有提供病人 ID,只有切片,那你只能退而求其次按切片随机切,但心里要清楚这个验证指标是偏乐观的,最终还是要看测试集。我一般会在训练脚本里固定随机种子,保证每次切分一致,方便复现。

2.3 标签体系:肝脏和肿瘤是不是同一类

肝脏癌症数据通常涉及两个层次:肝脏区域(liver)和肿瘤区域(lesion/tumor)。有的数据集掩码里肝脏是 1、肿瘤是 2,有的只标肿瘤,有的把肿瘤包含在肝脏里。你拿到数据第一件事不是写模型,是把掩码的像素值分布统计出来。用几行代码就能看清:

import numpy as np from PIL import Image import glob # 统计所有掩码里出现的像素值 mask_paths = glob.glob("data/train/masks/*.png") values = set() for p in mask_paths: m = np.array(Image.open(p)) values.update(np.unique(m).tolist()) print("掩码中出现的像素值:", sorted(values))

这段代码把训练集所有掩码读一遍,收集出现过的像素值。如果结果是[0, 1],说明是二分类,肝脏或肿瘤其中一个;如果是[0, 1, 2],大概率是背景/肝脏/肿瘤三类。参数上注意Image.open读 PNG 默认是 RGB 或灰度,如果掩码是调色板模式(P 模式),np.array出来的可能是索引值而不是真实类别,需要先convert("L")或convert("P")再确认。这一步不做,后面损失函数选错、类别数设错,训练半天全是白干。

3. 把 2D 切片喂进分割网络:预处理和增强怎么做

3.1 灰度归一化和尺寸统一

医学图像的灰度范围和自然图像完全不同。CT 的 HU 值范围可能从 -1000 到 3000,直接送进网络,激活函数直接饱和。标准做法是先做窗宽窗位截断,再归一化到 [0,1]。腹部 CT 看肝脏常用窗宽 150~200、窗位 30~60,但这个不是死的,要看你的数据来源。如果数据已经是 8 位 PNG,那大概率已经做过窗变换,你只需要除以 255。

尺寸方面,2D 切片原始分辨率可能是 512×512,也可能更大。分割网络通常要求输入是 16 的倍数(下采样几次就要求是 2 的几次方倍数)。我一般统一缩放到 256×256 或 512×512,看显存。缩放时图像用双线性插值,掩码必须用最近邻插值,否则会出现 0.5 这种不存在的类别值。

import cv2 import numpy as np def preprocess(img, mask, size=256): # 图像双线性缩放,掩码最近邻,避免产生非法类别 img = cv2.resize(img, (size, size), interpolation=cv2.INTER_LINEAR) mask = cv2.resize(mask, (size, size), interpolation=cv2.INTER_NEAREST) # 归一化到 [0,1] img = img.astype(np.float32) / 255.0 return img, mask

interpolation这个参数是血泪经验:掩码用双线性,边缘会出现 0 和 1 之间的过渡值,转成整型后要么全变 0 要么全变 1,边界直接烂掉。最近邻虽然看起来粗糙,但类别值不会错。

3.2 针对小肿瘤的增强策略

肝脏肿瘤在切片里往往只占几十到几百个像素,正负样本极度不均衡。如果只做随机翻转、旋转,模型很快学会「全预测背景」也能拿到很高的准确率,但 Dice 惨不忍睹。有效的增强要围绕「让肿瘤出现得更频繁、更显眼」来做:

  • 随机裁剪时以肿瘤为中心裁剪的概率调高,比如 50% 的裁剪以肿瘤质心为中心,50% 随机;
  • 弹性形变对肝脏这种软组织很有效,但形变幅度别太大,否则肿瘤形状失真;
  • 亮度对比度扰动模拟不同扫描设备,幅度控制在 ±10% 以内;
  • 不要用随机擦除(Random Erasing)去盖肿瘤区域,那是自毁。

我一般用 Albumentations 这套组合,配置写在一个字典里,训练和验证用不同 pipeline。验证集只做 resize 和归一化,不做任何随机增强,否则验证指标没有可比性。

3.3 损失函数:Dice 和 BCE 怎么配

二分类肝脏肿瘤分割,最稳的还是 Dice Loss 加 BCE 的组合。BCE 提供稳定的梯度,Dice 直接优化重叠度,两者按 1:1 加权是常见起点。如果肿瘤特别小,可以把 Dice 的权重调高到 2,或者换成 Tversky Loss,通过调 alpha/beta 控制假阳假阴的惩罚。多分类(背景/肝脏/肿瘤)时,Dice 要按类别算再平均,注意背景类通常不参与 Dice 计算,否则背景占比太大,Dice 会被拉到 0.9 以上,掩盖真实问题。

import torch import torch.nn as nn class DiceBCELoss(nn.Module): def __init__(self, dice_weight=1.0, bce_weight=1.0): super().__init__() self.dice_weight = dice_weight self.bce_weight = bce_weight self.bce = nn.BCEWithLogitsLoss() def forward(self, logits, targets): # logits: (B,1,H,W) 未过 sigmoid bce_loss = self.bce(logits, targets) probs = torch.sigmoid(logits) # 按 batch 内所有像素算 Dice,避免小目标被平均掉 intersection = (probs * targets).sum() dice_loss = 1 - (2 * intersection + 1e-6) / (probs.sum() + targets.sum() + 1e-6) return self.dice_weight * dice_loss + self.bce_weight * bce_loss

注意BCEWithLogitsLoss内部已经带 sigmoid,不要再手动加。Dice 计算里加1e-6是防止分母为 0,这个平滑项在肿瘤极小的切片上很关键,不加会出 NaN。

4. 训练、验证、测试:指标怎么读才不被骗

4.1 Dice 高不代表模型能用

Dice 系数是分割任务最常用的指标,但它对类别不均衡非常敏感。如果一张切片里肿瘤只占 1%,模型全预测背景,Dice 可能是 0,但如果肿瘤占 30%,全预测背景 Dice 也能有 0.7 左右。所以看 Dice 一定要分切片看分布,不能只看平均值。我习惯在验证时同时输出:整体 Dice、按肿瘤面积分组的 Dice(小/中/大)、以及每张切片的 Dice 列表,然后看有多少切片 Dice 低于 0.5。如果低 Dice 切片集中在肿瘤很小的层,说明模型对小目标不行,需要调损失或增强;如果随机分布,可能是数据标注质量问题。

4.2 训练集和测试集分布对不上怎么办

这是 2D 切片数据集最常见的问题。训练集可能来自一批扫描设备,测试集来自另一批,灰度分布、噪声水平、甚至层厚都不一样。表现就是训练 loss 一直降,验证 loss 降到某个点开始反弹,测试集指标远低于验证集。处理办法有几个层次:

  • 先做直方图匹配,把测试集的灰度分布对齐到训练集,这是最轻量的域适应;
  • 训练时加强灰度增强,让模型对亮度对比度不敏感;
  • 如果差距实在大,考虑在测试集上做无监督域适应,但这已经超出「直接训练」的范畴了。

我一般先跑一版 baseline,把训练/验证/测试的 Dice 都打出来,差距在 5 个点以内可以接受,超过 10 个点就要回头查数据分布。

4.3 早停和模型选择的依据

医学数据小,过拟合来得快。早停不能只看验证 loss,因为 loss 和 Dice 的走势不一定一致。我一般以验证集 Dice 为准,连续 10 个 epoch 不提升就停,同时保存 Dice 最高的那个 checkpoint。注意验证 Dice 的波动可能很大,尤其是验证集小的时候,所以 patience 别设太小,5 以下容易误停。另外,如果用了学习率调度,早停的 patience 要跟调度周期匹配,别调度还没降学习率你就停了。

5. 避坑:处理 Liver 2D 数据时最容易翻车的几件事

5.1 掩码像素值读错,训练全白干

现象:训练 loss 一直不降,或者降了但预测全是背景。原因:掩码是调色板 PNG,np.array读出来是索引值 0/1/2,但你以为 255 是前景,做了mask > 127的二值化,结果全变成背景。解决:读掩码后先np.unique看值,确认类别编码,再决定怎么转。如果是 P 模式,用convert("L")或直接按索引映射。

5.2 训练集和测试集切片来自同一病人

现象:验证 Dice 高得离谱,测试集一跑就崩。原因:切分时按切片随机分,同一个病人的相邻切片同时进了训练和验证,模型记住了这个病人的肝脏形状。解决:如果有病人 ID,按病人切分;没有的话,至少按切片序号做间隔切分,别让相邻切片跨集。

5.3 图像和掩码增强不同步

现象:训练一段时间后,预测的肿瘤位置总是偏。原因:图像做了随机旋转,掩码没跟着转,或者两者用了不同的随机种子。解决:用 Albumentations 这类支持 image+mask 同步增强的库,或者自己写增强时确保对图像和掩码施加完全相同的几何变换。

5.4 忽略切片层厚和像素间距

现象:模型在训练集上很好,换一台设备的数据就废。原因:不同设备的层厚和像素间距不同,同一个肿瘤在不同数据里物理尺寸一样但像素尺寸不一样。解决:如果数据里有 spacing 信息,重采样到统一 spacing;没有的话,至少在预处理时记录并做尺度归一化。

5.5 测试集里混入了空切片

现象:测试 Dice 被拉低,但看预测结果又没大问题。原因:测试集里有些切片完全没有肝脏或肿瘤,模型预测全背景,Dice 计算时分母为 0 或极小,产生异常值。解决:评估时过滤掉没有前景的切片,或者对空切片单独处理,不纳入 Dice 平均。

6. 一个能直接跑的验证脚本和我的使用习惯

训练跑完,别急着看测试集 Dice 就收工。我一般会写一个独立的验证脚本,把测试集每张切片的预测结果、Dice、以及原图叠在一起存下来,肉眼过一遍。很多问题指标看不出来,但看图一眼就知道——比如模型把胃壁当成肿瘤、或者边界总是往肝脏外扩。下面这个脚本把预测掩码、原图、真值拼成三通道图保存,方便快速翻看:

import os import cv2 import numpy as np import torch def visualize_prediction(model, img_path, mask_path, save_path, size=256): model.eval() img = cv2.imread(img_path, cv2.IMREAD_GRAYSCALE) mask = cv2.imread(mask_path, cv2.IMREAD_GRAYSCALE) img_r = cv2.resize(img, (size, size), interpolation=cv2.INTER_LINEAR) mask_r = cv2.resize(mask, (size, size), interpolation=cv2.INTER_NEAREST) inp = torch.from_numpy(img_r.astype(np.float32) / 255.0)[None, None].cuda() with torch.no_grad(): pred = torch.sigmoid(model(inp))[0, 0].cpu().numpy() pred_bin = (pred > 0.5).astype(np.uint8) * 255 # 原图、真值、预测叠成 RGB,红色为预测,绿色为真值 vis = cv2.cvtColor(img_r, cv2.COLOR_GRAY2BGR) vis[mask_r > 0] = [0, 255, 0] vis[pred_bin > 0] = [0, 0, 255] cv2.imwrite(save_path, vis)

这个脚本里pred > 0.5的阈值不是固定的,肿瘤分割有时候 0.4 比 0.5 召回更好,具体要看你的任务更怕漏检还是更怕误检。我一般会在验证集上扫一遍 0.3 到 0.7 的阈值,选 Dice 最高的那个,再拿到测试集用。另外model.eval()和torch.no_grad()别漏,不然显存爆得莫名其妙。

最后说个习惯:每次处理一份新的医学分割数据,我都会先花半小时把掩码值、切片数量、前景占比、训练测试分布这几项统计出来,写成一个简短的报告。这半小时能省掉后面至少两天的无效训练。肝脏癌症 2D 数据看着简单,但切片级的不均衡和分布差异是绕不过去的,把预处理和验证做扎实,比换更复杂的网络结构管用得多。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/2 2:44:03

从bash到Zsh:Oh My Zsh插件与主题配置实战指南

如果你问我过去几年里最划算的终端升级是什么,我会直接答:把默认 shell 换成 Zsh,再用 Oh My Zsh 做一套趁手的终端配置。这句话我在技术社区里说过很多次,每次都有刚从 bash 迁移过来的人回来说“相见恨晚”。原因很简单&#xf…

作者头像 李华
网站建设 2026/10/2 2:43:59

Python金融风控建模实战:从数据到评分卡部署

简介:这份资源面向金融风控方向的学生与开发者,提供一套基于机器学习的Python大数据风控建模实战项目,可直接用于毕业设计、期末大作业或课程设计。项目围绕信贷违约预测等典型场景展开,涵盖数据清洗、特征工程、模型训练与评估的…

作者头像 李华
网站建设 2026/10/2 2:43:42

DeepSeek Harness实战:用Vibe Coding构建可复用AI编码工作流

DeepSeek Harness 最近在开发圈里讨论度不低,但很多人下载完只是把它当成一个“聊天窗口”来用,点两下启动就不知道下一步了。它真正值得用的地方,是把 DeepSeek 的模型能力接进本地开发工作流,用自然语言直接推进编码任务&#x…

作者头像 李华
网站建设 2026/10/2 2:43:08

电池SOH与剩余寿命预测:多模型融合与深度学习实战

简介:这是一份面向人工智能、数据科学与车辆工程方向学习者的动力电池健康状态评估与剩余寿命预测项目,利用SVR、ElasticNet、KernelRidge、XGBRegressor、GradientBoostingRegressor五种机器学习模型与深度学习模型做平均融合,解决电池SOH估…

作者头像 李华
网站建设 2026/10/2 2:41:45

LeetCode Python题解实战:从环境配置到高频题型避坑指南

简介:该资源收录LeetCode题库的Python完整解答,覆盖数组、链表、树、动态规划、回溯、图论等核心算法专题,适合正在备战技术面试、希望系统梳理算法知识体系的中级及以上Python开发者。包内共1160个文件,主体为579个.py源码与580个…

作者头像 李华
网站建设 2026/10/2 2:41:42

Python数据可视化实战:网易云音乐歌单分析系统全拆解

简介:一套基于Python数据可视化的网易云音乐歌单分析系统源码及文档说明,面向Python期末大作业、数据分析与可视化课程设计,适合需要快速完成高质量项目的在校学生。系统功能完善,覆盖歌单数据采集、清洗、统计分析及多角度可视化…

作者头像 李华