尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

乳腺肿瘤细胞核分割数据集实战:从掩码可视化到U-Net训练

发布时间:2026/9/28 1:34:32

资讯中心
01
ARTICLE

乳腺肿瘤细胞核分割数据集实战:从掩码可视化到U-Net训练

乳腺肿瘤细胞核分割数据集实战:从掩码可视化到U-Net训练
简介面向医学图像分割任务这份数据集聚焦乳腺肿瘤细胞核分割2类适用于深度学习分割模型的训练、验证与算法对比。图像与mask均为png格式分辨率涵盖256×256与1000×1000mask采用0/1阈值模板分别对应背景与肿瘤细胞核训练集含66对图像与掩膜测试集单独划分可直接支持U-Net、DeepLab等常见分割网络的训练与评估。样本文件名带TCGA病例标识便于追溯来源与后续扩展。压缩包共167个文件以png图片为主体另含1个txt类别说明和1个py可视化脚本脚本无需修改即可运行随机抽取一张样本同屏展示原始图像、GT掩膜及GT叠加效果帮助快速核对标签质量。资源整体约62MB已有194人学习适合医学影像分析方向的研究者、竞赛选手及深度学习初学者可作为模型训练与基准测试的现成数据基础。1. 乳腺肿瘤细胞核分割2类数据集的定位一个能直接上手的医学图像分割起手式做乳腺病理图像的细胞核分割第一步往往不是选网络而是找一个标注靠谱、结构简单的数据集练手。这个标题给的就是这类东西乳腺肿瘤细胞核的 2 类分割两类指「背景」和「细胞核」不是把上皮、淋巴细胞、间质等核类型细分。它自带图像、标签文件和数据可视化代码意味着你能在几小时内跑通「读图—看掩码—训练—评估」的闭环不用把时间耗在格式转换和手动标注上。适合刚进医学图像分割方向的研究生也适合想把数字病理快速落地验证的工程团队。这类二分类核分割是病理 AI 里最值得先做透的基础任务核密度、核形态、Ki67 阳性率这些下游指标全都建立在它上面。2. 拆解数据集与标签文件目录结构、掩码取值与格式转换拿到数据集先别急着训练用五分钟把目录和标签的真实格式看清楚后面能省几小时排查。乳腺肿瘤细胞核分割数据集最常见的组织方式有两种一种是images/与masks/两个平级目录图像和掩码同名不同后缀另一种是全部放在同一目录靠文件名后缀区分。先用一条命令看全貌。tree -L 2 dataset/# Windows 下没有 tree 时 dir /s dataset逻辑说明tree -L 2只看两层够判断目录结构不会因为病理扫描图的大文件把终端刷爆。Windows 上dir /s是等效替代。看的时候重点确认三件事图像和掩码是否一一对应、掩码是不是单通道灰度图、文件名是否带病例前缀。乳腺肿瘤数据集的命名通常类似case12_slide03_region07.png这个前缀后面做数据划分有大用。2.1 标签文件的真实格式单通道掩码和 0/255 约定很多第一次接触分割数据集的人会在掩码读取上翻车。乳腺肿瘤细胞核分割的标签文件一般是 PNG 格式的单通道灰度图像素只有两种取值0 表示背景255 表示细胞核。有的版本存成 0 和 1有的是三通道彩色图但三个通道内容完全一样。读取方式必须显式指定灰度模式import cv2 import numpy as np mask cv2.imread(dataset/masks/mask_001.png, cv2.IMREAD_GRAYSCALE) print(np.unique(mask)) # 输出可能是 [0 255]也可能是 [0 1]逻辑说明IMREAD_GRAYSCALE强制以单通道读入。很多教程里直接cv2.imread(path)默认按三通道 BGR 读灰度 PNG 会被复制成三通道np.unique出来的是[0 255]的三维组合后面不管训练还是可视化都会踩坑。这里要养成的习惯是所有用到掩码的地方读入后先print(np.unique(mask))确认取值。参数说明0/255 是标注工具导出时的常见格式但训练时多数框架要求类别索引从 0 开始因此读入后要么除以 255要么用astype(np.uint8)后重映射。我一般统一转成 0/1少一层换算。2.2 分割掩码与目标检测标注的本质差别如果你之前用 YOLO 训练过自己的数据集对.txt里的一串归一化框坐标很熟。分割不一样标签是逐像素的没有「框住目标」这回事。用 COCO 2017 数据集结构来类比会更清楚COCO 的实例分割标签是 JSON 里存多边形点坐标训练前要把点栅格化成 mask而这个标题下的数据集已经帮你做完了栅格化打开就是 PNG 掩码省掉了 polygon-to-mask 这一步。这既是优点也是风险——你看不到原始标注过程只能信任掩码本身。所以拿到数据后的第一件事是可视化抽查而不是直接开训。另外要留意「2类」的定义边界。有的公开数据比如多类核标注版本会把上皮核、淋巴细胞核、基质核分开标标题里说 2 类说明已经合并成「背景 细胞核」的语义。但如果原始素材是多类标合并时不能简单mask 0一把梭见下一节。2.3 把多类别掩码重映射成 2 类一个通用转换脚本从朋友那拷来的数据、或从公开项目如 MoNuSeg 系列拿到的原始标注常常是多类的。要做 2 类分割就得先合并。一个足够通用的脚本如下import cv2 import glob import os import numpy as np src_masks sorted(glob.glob(raw_masks/*.png)) os.makedirs(masks, exist_okTrue) for p in src_masks: m cv2.imread(p, cv2.IMREAD_GRAYSCALE) m2 (m 0).astype(np.uint8) * 255 out os.path.join(masks, os.path.basename(p)) cv2.imwrite(out, m2) print(f{p} - {out}, classes{np.unique(m2)})逻辑说明m 0把所有非背景像素统一变成 Trueastype(np.uint8)得到 0/1* 255后存成 0/255 的 PNG。这一段最关键的假设是所有非零像素都算「细胞核」。如果你的原始标注里 1 是上皮核、2 是纤维核、3 是炎性核想保留全部这个脚本成立但如果原始标注里有 255 表示「待确认」或「背景遮罩」直接合并会把噪声也当成前景。参数说明输出格式建议统一 0/255方便cv2.imwrite直接落盘。如果之后用 PyTorch 的CrossEntropyLoss训练时再除以 255 即可。合并前用np.unique(m)看一眼原始取值确认没有中间灰阶这是最容易忽略的一步。2.4 遇到 HDF5 打包的数据集先解包再动手有些医学图像分割数据集为了减少海量小文件的 IO 开销会打包成单个.h5文件。结构通常是images和masks两个 datasetshape 是(样本数, 高, 宽, 通道数)。读取和拆包用h5pyimport h5py import cv2 import numpy as np import os os.makedirs(images, exist_okTrue) os.makedirs(masks, exist_okTrue) with h5py.File(dataset.h5, r) as f: print(keys:, list(f.keys())) n f[masks].shape[0] for i in range(n): img f[images][i] mask f[masks][i] img (img * 255).astype(np.uint8) if img.max() 1.01 else img.astype(np.uint8) mask (mask * 255).astype(np.uint8) if mask.max() 1.01 else mask.astype(np.uint8) cv2.imwrite(fimages/img_{i:04d}.png, img) cv2.imwrite(fmasks/mask_{i:04d}.png, mask)逻辑说明HDF5 的好处是单文件、支持按索引随机读取但实际训练时 PyTorch 的DataLoader不太喜欢直接读 h5频繁随机访问会卡。我一般先解包成 PNG训练时用小文件随机读速度更稳。代码里的归一化判断很关键医学数据集的 HDF5 里图像经常存成 0~1 的 float掩码也如此直接astype(np.uint8)会得到全黑图。参数说明i:04d保证文件名按四位序号补零排序不乱。解包后务必抽查几张确认 mask 和 image 的内容对齐不要只看 shape 就对上了。3. 数据可视化代码落地叠加显示、类别统计与超大图浏览标题里明确带了「数据可视化代码」说明这个数据集希望你在训练前先把数据看透。这个环节做扎实后面训练中的很多反常现象一眼就能认出来。3.1 图像与掩码叠加显示最小可运行脚本先给一个最直接的叠加可视化脚本。它能把细胞核区域用半透明红色盖在原图上三张子图分别是原图、掩码、融合结果一张图看清标签质量。import cv2 import numpy as np from matplotlib import pyplot as plt img cv2.imread(dataset/images/img_001.png) img cv2.cvtColor(img, cv2.COLOR_BGR2RGB) mask cv2.imread(dataset/masks/mask_001.png, cv2.IMREAD_GRAYSCALE) overlay img.copy() overlay[mask 0] (255, 0, 0) # 核区域染红 blend cv2.addWeighted(img, 0.6, overlay, 0.4, 0) fig, axes plt.subplots(1, 3, figsize(15, 5)) axes[0].imshow(img) axes[0].set_title(Image) axes[1].imshow(mask, cmapgray) axes[1].set_title(Mask) axes[2].imshow(blend) axes[2].set_title(Overlay) plt.show()逻辑说明overlay直接拷贝原图把掩码非零像素位改成纯红再用addWeighted做 alpha 融合。核心是addWeighted(img, alpha1, overlay, alpha2, 0)输出强度由两个权重加权既能看清核的位置又不遮挡原图纹理。参数说明0.6 / 0.4是原图与红层的混合比例。细胞核排布密集时把红层权重调到 0.3 以下否则红色连成片看不出单个核的轮廓核稀疏时用 0.5~0.6视觉上更清楚。mask 0兼容 0/1 和 0/255 两种掩码不用特意改。另外注意cvtColor那行OpenCV 读图是 BGRplt.imshow按 RGB 解释不转的话整张图会偏蓝偏红这是叠加可视化最常见的翻车点。3.2 类别占比统计训练前先判断类别失衡程度乳腺肿瘤细胞核分割里背景占比通常远高于核。不量化这个值就选损失函数大概率要走弯路。用几行脚本把前景占比算出来import cv2 import glob import numpy as np ratios [] for p in sorted(glob.glob(dataset/masks/*.png)): m cv2.imread(p, cv2.IMREAD_GRAYSCALE) fg (m 0).sum() total m.size ratios.append(fg / total) ratios np.array(ratios) print(f样本数: {len(ratios)}, 前景占比均值: {ratios.mean():.4f}, 中位数: {np.median(ratios):.4f}) print(f前景占比最低: {ratios.min():.4f}, 最高: {ratios.max():.4f})逻辑说明m 0统计前景像素除以总像素就是每张图的前景占比。均值和中位数同时看能区分「整体都低」和「个别图高、多数图极低」两种情况。病理图中核密度差异很大乳腺肿瘤切片里不同视野的核占比可以差出 5 倍以上。参数说明如果前景占比均值低于 0.1整图喂进去训练大概率被背景主导。这个脚本的输出是你决定「整图训练还是 patch 采样」的直接依据别跳过。3.3 超大病理图的切片浏览网格预览避免黑屏和 OOM病理扫描图经常是几千乘几千像素直接plt.imshow(img)要么内存爆掉要么显示出来糊成一片。常见做法是把图像切成固定大小的 tile网格显示前几块def grid_preview(img, mask, tile512, max_cols3): h, w mask.shape[:2] rows min(h // tile, 3) cols min(w // tile, max_cols) fig, axes plt.subplots(rows, cols, figsize(4 * cols, 4 * rows)) for i in range(rows): for j in range(cols): p img[i*tile:(i1)*tile, j*tile:(j1)*tile] m mask[i*tile:(i1)*tile, j*tile:(j1)*tile] axes[i, j].imshow(p) axes[i, j].imshow(m, cmapjet, alpha0.3, interpolationnearest) plt.show() grid_preview(img, mask, tile512, max_cols3)逻辑说明按tile尺寸把图切成网格只显示前三行前几列。用cmapjet叠加在图上核区域被高亮成伪彩色边界看得比纯红更清楚。interpolationnearest避免缩放时出现插值模糊让缩略图保持锐利。参数说明tile512适合显示中等细节想看核形态用 256想看组织排布用 512 或 1024。max_cols3控制列数网格总数控制在 9 个以内显示速度才跟得上。这个函数后续每次训练迭代后输出预测结果时也能复用。4. 把乳腺肿瘤细胞核分割2类数据集跑进 U-Net数据划分、损失函数与评估指标可视化确认过标签质量之后再进训练。这一章给出能直接抄的数据划分、Dataset、损失函数和评估代码按顺序组合就能跑通一个基础 U-Net。4.1 数据划分按病例分组而不是随机打散乳腺病理图像有一个容易被忽略的问题同一个病人的多张切片非常相似随机打散会让同一病例出现在训练集和验证集造成指标虚高。正确做法是按文件名前缀里的病例 ID 分组。import glob import os from collections import defaultdict imgs sorted(glob.glob(dataset/images/*.png)) groups defaultdict(list) for p in imgs: base os.path.basename(p) case base.split(_)[0] # 按文件名前缀取病例 ID groups[case].append(p) cases sorted(groups.keys()) print(f病例数: {len(cases)}, 图像总数: {len(imgs)}) train_cases cases[:int(len(cases) * 0.8)] val_cases cases[int(len(cases) * 0.8):int(len(cases) * 0.9)] test_cases cases[int(len(cases) * 0.9):] train_imgs [p for c in train_cases for p in groups[c]] val_imgs [p for c in val_cases for p in groups[c]] test_imgs [p for c in test_cases for p in groups[c]]逻辑说明base.split(_)[0]依赖文件命名带病例前缀。实际数据集命名如果不规则先打印前 20 个文件名人工确认分隔符。按病例分组划分后同病人的所有切片只进一个集合验证结果才具备参考意义。参数说明8:1:1 是医学图像分割的常见比例样本量小于几百张时可以把测试集并进验证集留出更多训练数据。cases列表要先排序再切片保证每次运行划分一致。4.2 预处理与在线增强翻转旋转安全随机裁剪要谨慎病理图像没有上下左右的方向性翻转和 90 度旋转是绝对安全的增强。随机裁剪则要小心整图里背景太多时随机裁到的地方大概率不含核相当于给模型喂了大量空白样本。import random import numpy as np def train_aug(img, mask): if random.random() 0.5: img img[:, ::-1] mask mask[:, ::-1] k random.choice([0, 1, 2, 3]) img np.rot90(img, k) mask np.rot90(mask, k) return img.copy(), mask.copy()逻辑说明水平翻转和 90 度旋转只改变像素位置、不改变像素值对染色组织切片的语义完全友好。np.rot90的k取 0~3相当于随机旋转 0/90/180/270 度。返回拷贝是防止后续 numpy 操作共享内存导致的隐式修改。参数说明这套增强只适合训练阶段。验证和测试时不要做任何随机操作最多做固定的中心裁剪。医学图像分割里「翻转增强」几乎无成本但不要加过强的颜色抖动——病理图的染色有一定标准颜色扰动过大反而破坏核与背景的判别特征。4.3 损失函数单独用 BCE 为什么会在核分割上失效细胞核分割的前景占比常低于 10%直接BCEWithLogitsLoss会让模型倾向于把一切预测为背景因为这样 loss 已经很低。Dice Loss 直接优化重合度对类别失衡天然稳健。常用做法是 BCE 和 Dice 组合。import torch import torch.nn as nn class DiceLoss(nn.Module): def __init__(self, smooth1.0): super().__init__() self.smooth smooth def forward(self, logits, target): prob torch.sigmoid(logits).view(logits.size(0), -1) target target.view(target.size(0), -1).float() intersection (prob * target).sum(dim1) dice (2 * intersection self.smooth) / ( prob.sum(dim1) target.sum(dim1) self.smooth ) return 1 - dice.mean() class BCEPlusDice(nn.Module): def __init__(self, bce_weight0.5): super().__init__() self.bce nn.BCEWithLogitsLoss() self.dice DiceLoss() self.bce_weight bce_weight def forward(self, logits, target): return self.bce_weight * self.bce(logits, target) (1 - self.bce_weight) * self.dice(logits, target)逻辑说明DiceLoss把 logits 经 sigmoid 转成概率然后压平成(batch, -1)计算每张图的 Dice 系数取平均后返回1 - dice作为损失。BCEPlusDice把两个损失按权重相加BCE 提供像素级梯度Dice 缓解类别失衡。参数说明smooth1.0防止分子分母都为 0 时的除零错误。bce_weight常见取 0.5如果你的数据集前景占比特别低低于 5%可以把bce_weight调到 0.3让 Dice 主导前景占比较高时回到 0.5~0.7。4.4 评估指标Dice、IoU 与像素精度别只看准确率细胞核分割这种极不平衡场景准确率没有任何参考价值——全预测成背景也能到 90% 以上。评估至少看三个指标def compute_metrics(pred, target, threshold0.5): pred_bin (pred threshold).astype(np.uint8) target_bin (target 0).astype(np.uint8) inter np.logical_and(pred_bin, target_bin).sum() pred_sum pred_bin.sum() target_sum target_bin.sum() dice 2 * inter / (pred_sum target_sum 1e-6) iou inter / (pred_sum target_sum - inter 1e-6) acc (pred_bin target_bin).mean() return {dice: dice, iou: iou, acc: acc}逻辑说明pred是模型输出的概率图 0~1target是掩码。比较前先把预测按阈值二值化把 target 统一成 0/1。三个指标一起看尤其是 IoU 和 Dice 同时偏低而 acc 很高时说明模型在退化到背景预测。参数说明threshold0.5不是唯一选择。核分割里提高阈值到 0.6~0.7 能显著减少假阳性代价是召回下降。判断阈值是否合理的方法很简单看验证集上预测概率的直方图如果大部分预测值集中在 0.1 以下和 0.9 以上说明模型很自信阈值怎么选都不太影响如果大量预测值在 0.4~0.6 徘徊调阈值就是在碰运气不如回去修损失函数。这一步很玄学但先把数据看透就能少调参。5. 避坑与排查乳腺肿瘤细胞核分割数据集上的 5 个翻车点5.1 图像和掩码错位训练正常但可视化完全对不上现象训练 loss 在下降但把原图和掩码叠加显示发现掩码的核位置和图像里的核位置根本对不上有时掩码里出现图像中没有的结构。原因两个常见来源。一是glob.glob和os.listdir的排序不一致图像按文件名排序、掩码按目录读入顺序拼接时错位二是数据集本身在打包时 images 和 masks 不是按同一顺序导出的。解决用文件名显式匹配而不是依赖目录读入顺序。统一走sorted(glob.glob())并且训练脚本里加一个断言检查图像与掩码的文件名一一对应后把路径配对。进阶做法是随机抽 20 组图叠加保存成一张大图人眼扫一遍比任何断言都可靠。5.2 背景占比太高准确率 98% 但 Dice 为 0现象训练完成后验证集准确率很高Dice 却是 0 或接近 0。看起来不可思议其实模型把整张图都预测成了背景。原因背景像素占比超过 90% 时BCEWithLogitsLoss会走进「全预测为背景」的局部最优因为这个策略已经把 loss 压得很低。准确率高只是类别失衡的假象。解决换用 4.3 节的 DiceLoss 或 BCEDice 组合损失更彻底的办法是放弃整图训练改为按核密度采样 patch。我一般让可视化统计脚本先输出前景占比低于 10% 就直接走 patch 采样路线不进整图训练。5.3 掩码边界粗细不一致Dice 卡在 0.7 左右上不去现象验证 Dice 从头到尾卡在 0.7 附近换更强的网络也突破不了。可视化预测结果发现预测的核边界普遍比真实掩码细或粗一圈。原因标注源的边界一致性差。显微镜图像里细胞核边界本身就模糊标注工具导出的轮廓线粗细不一致加上不同标注人员的标准不同会导致 GT 边界出现系统性偏差。模型学到的「正确边界」和 GT 之间有固定偏移Dice 被抬高不了。解决先做标注一致性检查——统计所有掩码里连通域的面积分布如果同一类结构的面积标准差异常大考虑对掩码统一做形态学腐蚀或膨胀校准。训练层面可以引入边界损失或在 Dice 基础上加边界感知项。最务实的做法是把 GT 统一腐蚀 1~2 个像素再做训练牺牲一点边界精度换取训练稳定性。5.4 同一病例被随机划分到训练和验证集训练指标虚高现象训练时验证 Dice 接近 0.9自认为模型已经收敛拿到新数据上一测只有 0.6落差巨大。原因文件命名带病例前缀时随机划分会把同一病人的多张切片分到训练集和验证集。病理切片不同区域纹理高度相似模型在训练时已经「见过」验证集的同源图像评估结果虚高。解决按 4.1 节的病例分组划分保证同一个case前缀的所有图只进一个集合。如果数据集本身没有病例 ID按图像采集时间或批次近似分组也行——重点是不让模型「背题」。5.5 推理输出全黑或全是噪点归一化和阈值问题现象训练正常推理时积压出的掩码要么全黑、要么全白、要么满是孤立噪点。同一个脚本在验证集上表现正常换到新图上就失控。原因三个典型来源。一是推理时输入的预处理和训练不一致比如训练用的 0~1 归一化推理时忘了除以 255二是模型输出的是 logits 而不是概率直接用 0.5判阈值logits 都在 0 附近时全被判成背景三是滑窗推理拼接时没有处理重叠区的边界。解决把预处理和推理封装成同一个函数禁止在推理脚本里手动写一遍。模型输出先过sigmoid再判阈值。滑窗推理时重叠区域取平均值而不是覆盖能显著减少拼接接缝处的噪点。如果二值化后仍有大量孤立小点加一个基于连通域面积的后处理去掉小于 10 像素的连通域。6. 进阶验证技巧用错误叠加图和滑窗推理反推数据集质量6.1 错误叠加图十秒定位难例和标注缺陷只在下游用「预测对了几张」来评价数据集是不够的。我习惯把每个验证样本输出成一张错误叠加图真阳性画绿、假阴性画红、假阳性画黄。看分布比看数字有信息量得多。def error_map(gt, pred): h, w gt.shape vis np.zeros((h, w, 3), dtypenp.uint8) tp np.logical_and(gt 1, pred 1) fn np.logical_and(gt 1, pred 0) fp np.logical_and(gt 0, pred 1) vis[tp] (0, 255, 0) vis[fn] (255, 0, 0) vis[fp] (0, 255, 255) return vis逻辑说明三通道分别标记三类区域。红色集中在某个固定角落基本可以判断是染色不均或漏标黄色连成片说明阈值偏低模型在正常组织区域产生了系统性误报。6.2 滑窗推理处理超大病理图重叠融合去接缝整张病理图直接推理显存不够切块推理又看这种错位现象。常见做法是滑窗带重叠输出时对重叠区域取平均值。tile 取 512、重叠 64能平衡显存和拼接质量重叠太小接缝明显重叠太大推理时间翻倍。我一般保留最后一张实验记录卡包含数据版本、病例划分方式、增强开关、损失函数、优化器、学习率和三次重复实验的 Dice 均值。项记录值数据版本解包后 PNG v1前景占比 11.2%划分方式按 case 前缀8:1:1损失BCEDicebce_weight0.4优化器AdamWlr1e-4Dice / IoU0.842 / 0.731这套验证习惯帮我避过很多次「指标好看但实际不可用」的假收尾。最后给一点个人经验拿到这类数据集先花一小时做可视化和统计再决定网络和损失函数这个顺序省下的返工时间比想象中多得多。希望帮到你。本文还有配套的精品资源点击获取
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。