简介这是一套面向医学影像分析场景的乳腺肿瘤细胞核分割数据集附带完整标签文件与一键式数据可视化脚本。图像与掩膜均为PNG格式涵盖256×256和1000×1000两种分辨率mask以0/1阈值表示背景与肿瘤区域适用于图像分割、语义分割模型的训练与验证。压缩包内含167个文件主要包括165张原始切片图像与对应GT掩膜、1个类别说明txt及1个可视化py脚本整体约62MB结构简洁便于直接解压使用。数据集划分清晰其中训练集包含66张图像测试集包含167张图像并额外提供自动随机抽图、叠加展示原图与GT蒙版效果的可视化程序无需修改参数即可运行有助于快速检查标注质量与模型效果。目前该资源已有194人学习适合从事医学图像分割研究、病理切片智能分析或深度学习方向的开发者参考使用。1. 这类乳腺肿瘤数据集的定位先别急着改网络把数据看清楚第一次拿到乳腺肿瘤细胞核分割数据集的人很容易掉进两个极端一种是把所有图片一股脑丢给网络跑完只看一个准确率数字另一种是盯着密密麻麻的细胞核边界怀疑标签文件标得不准迟迟不敢开工。这个标题里写到的“医学图像分割数据集”要解决的正是这个开局问题。数据集把背景作为第一类、肿瘤细胞核作为第二类不引入多标签和实例分割的高门槛同时把标签文件和数据可视化代码一起配好。适合刚接触医学图像分割的工程人员也适合想快速验证 U-Net 这类模型到底能在这个任务上跑出什么效果的从业者。所谓“2类”不是指良性和恶性两个细胞类型而是指每个像素只有两种值是核或者不是核。这个简化让问题边界清晰后面排查数据、调损失函数都容易下手。2. 物理视角看这份数据原图、掩膜和清单是三个独立文件先说一个总原则拿到任何一个数据集第一件事不是打开深度学习框架而是把目录结构完整列一遍。医学图像分割项目的数据绝大多数不是一个单独文件而是原图、标签、说明各自独立的多文件集合。标题里的“数据集 标签文件 数据可视化代码”就是这种思路的典型打包方式一份肉眼可读的原始图一份程序可读的掩膜一段帮你把两者对齐的辅助脚本。2.1 一份样本在磁盘上由哪几个文件组成一位典型的乳腺肿瘤病理切片样本原始数据是一张高分辨率 HE 染色图尺寸可能达到几千乘几千像素里面密密麻麻排着细胞核。和它对应的标签文件通常是一张同名 PNG目标不是给人看而是让程序逐像素判断“这个位置是核这个位置不是核”。第三个文件是元数据表用来记录每个样本来自哪张切片、哪个病例有时还会标注染色批次和扫描倍率。常见目录结构是这样文件内容典型格式images/sample_001.pngHE 染色原图RGB 三通道uint8labels/sample_001.png乳腺肿瘤细胞核掩膜单通道灰度 PNGmeta.csv样本与病例、批次的对应关系CSV 表格原图和标签文件名通常保持前缀一致方便脚本用os.path.basename直接配对。如果数据集把原图和标签平铺在同一个文件夹里常见做法是先跑一段整理脚本把文件按后缀或命名前缀归到两个子目录。这里要留个心眼同一张图片在命名时可能写sample_001.png在元数据表里写sample_001如果不做一次.replace(.png, )的标准化后续加载标签时就会频繁出现 KeyError。2.2 “2类”到底指什么逐像素语义分类不是实例分割在病理图像里肿瘤细胞核往往聚成一团核与核之间的边界在灰度上并不清晰甚至一个细胞核的一部分被另一个核遮住。如果做实例分割需要区分每一个单独的核标注成本极高模型训练也容易被粘连区域干扰。这个数据集的 2 类设计实际上是把问题降成逐像素二分类标签图上每一个像素要么属于“细胞核区域”要么属于“背景区域”。这样做的好处有两点一是标注标准和人工复核都简单二是训练阶段不需要设计复杂的实例匹配策略直接套用 U-Net、DeepLab 这类语义分割框架就能跑通。这种 2 类语义和很多目标检测数据集的思路不同。目标检测数据集通常只提供一个矩形框框内既有核又有背景模型学到的是“被框住的东西”。分割数据集要求每个像素都表态所以对标签文件的精度要求直接决定了模型的边界质量。如果你拿到的数据里某些掩膜边缘画得很粗糙宁可先修数据也不要让网络去硬拟合那些不准确的边界。2.3 标签灰度值的两种常见编码0 和 1还是 0 和 255最容易被忽略的细节是标签文件里像素值的编码方式。在程序内存里标签数组一般被写成0和1方便做 one-hot 和交叉熵计算但在 PNG 这种可视化格式里普遍用0和255表示因为 255 在白底下更显眼。如果不做映射直接拿0/255的数组去算损失BCE 会对 255 这种大数值产生意外放大Dice Loss 也会因为分母奇大而表现异常。所以训练代码的第一道工序永远是打印np.unique(mask)import numpy as np import cv2 mask cv2.imread(labels/sample_001.png, cv2.IMREAD_GRAYSCALE) print(mask shape:, mask.shape) print(mask unique:, np.unique(mask))这段代码的输出通常只有两种可能[0 1]或[0 255]。如果是后者后续加载时统一执行mask_binary (mask 0).astype(np.uint8)把 255 降到 1。为什么数据集里会出现两种编码不同标注软件导出习惯不一样有的工具为了兼容看图软件写 255有的后端直接从 npy 格式写 0/1。你唯一能依仗的是脚本确认而不是看文件名的后缀。一旦发现有些样本是 0/1有些是 0/255不要写两套 loader而是所有样本统一走一遍mask.astype(np.uint8)再入库。3. 跑通数据可视化代码这三栏对比图能帮你发现一半问题训练一个分割模型之前最划算的操作是先看 30 张原图和掩膜叠加后的效果。很多时候模型预测效果差一开始就是标签边界偏移、染色批次差异、文件名错位这三类问题。可视化代码不用写得复杂核心目标只有一个把原始图、灰度掩膜、叠加图并排打印出来让人眼快速判断标签和图像内容是否对齐。3.1 最小可运行脚本原图、掩膜、叠加三栏输出下面这段脚本可以放在数据集根目录下运行假设 images 和 labels 是两个并列子目录文件名前缀一致import os import numpy as np import cv2 import matplotlib.pyplot as plt from glob import glob images_root dataset/images labels_root dataset/labels image_paths sorted(glob(os.path.join(images_root, *.png))) def visualize_one(img_path, mask_path, figsize(18, 6)): # 病理原图是彩图OpenCV 默认读成 BGR转 RGB 便于显示 img cv2.imread(img_path) img cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # mask 必须用灰度模式读取否则会得到三通道伪彩色 mask cv2.imread(mask_path, cv2.IMREAD_GRAYSCALE) mask_binary (mask 0).astype(np.uint8) # 在原始图上用红色填充标记细胞核区域 overlay img.copy() overlay[mask_binary 1] (255, 0, 0) fig, axes plt.subplots(1, 3, figsizefigsize) axes[0].imshow(img) axes[0].set_title(original) axes[1].imshow(mask, cmapgray) axes[1].set_title(label) axes[2].imshow(overlay) axes[2].set_title(overlay) plt.tight_layout() plt.show() if image_paths: sample_img image_paths[0] sample_mask os.path.join(labels_root, os.path.basename(sample_img)) visualize_one(sample_img, sample_mask)这段代码的核心参数有三个img_path决定原始图路径mask_path决定标签路径mask_binary用来把 0/255 的灰度掩膜统一成 0/1 布尔数组。cv2.cvtColor是必须的因为 OpenCV 默认把彩色图读成 BGR 格式直接交给 matplotlib 会把红色和蓝色通道互换。overlay的红色填充只是辅助观察不需要太精细。如果掩膜是 0/1 编码mask 0同样能正确判断因为 1 也大于 0所以这段代码对两种编码都兼容。3.2 批量可视化时要留意的三个参数单张可视化只能证明脚本能跑真正有价值的是批量输出所有样本的对比图。常见做法是把上面代码放进一个循环并对每张图执行plt.savefig()生成一个“可视化检查目录”。这样你可以在训练间隙快速翻看几百张图而不是每次都用plt.show()手动点开。批量处理时还会遇到三个比较常见的参数问题。第一是文件名匹配。有的数据集里原图叫sample_001.png标签叫sample_001_mask.png这时os.path.basename就不适用了。常见做法是先把后缀基名取出来再去标签目录里用glob前缀匹配。第二是大图尺寸。整张病理切片可能超过 2000x2000直接一次性加载还能忍但统一显示到屏幕上会缩小失真。我一般会加一个max_side参数超过 1000 就等比缩小。第三是plt.show()在无桌面环境会直接报错。如果你在用远程服务器跑脚本记得把plt.show()注释掉换成plt.savefig(viz/sample_001.png)。3.3 当原图和标签尺寸不一致时怎么办可视化脚本最常见的报错是ValueError: x and y must have same first dimension。原因是原图和掩膜采样分辨率不同比如原图 2048x2048掩膜只剩 1024x1024。处理方式有两种一种是把掩膜上采样到原图尺寸用最近邻插值保持硬边界不变另一种是把原图下采样到掩膜尺寸两种都可行。但我更推荐把掩膜上采样到原图分辨率因为病理细胞核边界一旦用线性插值容易产生灰色过渡像素给后续二值化带来麻烦。def align_mask_to_image(mask, target_shape): if mask.shape ! target_shape: mask cv2.resize( mask, (target_shape[1], target_shape[0]), interpolationcv2.INTER_NEAREST ) return mask这里target_shape[1]对应宽target_shape[0]对应高是 OpenCV 和 numpy 的典型行列顺序差异numpy 里是(高, 宽)OpenCV 的dsize是(宽, 高)。这个参数写反后不会报错但会把图形转置如果发现可视化结果里细胞核方向颠倒了先检查这一步。4. 把它喂给分割网络前数据划分、染色归一化、标签映射和损失函数跳过可视化直接训练是对数据集资源的最大浪费。病理图像的训练流程和自然图像明显不同你基本不需要关心随机裁剪会不会把某个物体的语义截断但要关心样本间的染色差异、病例重叠和标签编码。把数据变成模型能吃的格式至少要走完四个环节。4.1 按病人分组划分训练集和验证集不能随机切分乳腺肿瘤病理切片通常是一张全切片图切出多个 patch 存成的样本。同一批 patch 来自同一个病例染色风格、组织密度、细胞形态高度相似。如果随机打散划分模型在验证集里很容易撞见训练集里同源样本的“近亲”Dice 虚高。稳妥做法是按case_id做 GroupKFold保证一批病例不会同时出现在训练集和验证集from sklearn.model_selection import GroupKFold import pandas as pd meta pd.read_csv(dataset/meta.csv) gkf GroupKFold(n_splits5) for train_idx, val_idx in gkf.split(meta, groupsmeta[case_id]): train_files meta.iloc[train_idx][sample_id].tolist() val_files meta.iloc[val_idx][sample_id].tolist() # 用 sample_id 去 images 和 labels 目录里找具体文件名这里最容易踩坑的地方是groups参数。它接收的不是sample_id而是病例的唯一标识。如果你把sample_id传进去等于没分组结果还是一般随机切分。另外GroupKFold返回的是行索引不是文件名所以需要再通过meta.iloc去映射一次。做医学图像分割训练数据泄漏往往是看不到的Dice 分数漂亮但换一个新病例数据就崩塌根源大多在分组这一步。4.2 染色归一化比通用标准化更关键医学图像和自然图像的预处理差异很大。自然图像的 RGB 颜色只是视觉表现但病理图像里的颜色本身是染色反应的结果苏木精把细胞核染成蓝紫色伊红把细胞质染成粉色。不同切片、不同扫描仪的染色深浅差异明显直接做普通的 ImageNet 均值和方差归一化效果通常一般因为模型会把不同染色风格当作不同特征去学。常见做法是统计整个训练集在每个通道上的均值和标准差做全局 z-scoremean train_images.mean(axis(0, 1, 2)) std train_images.std(axis(0, 1, 2)) norm_img (img - mean) / (std 1e-8)这里强调“整个训练集”而不是“单张图”。如果对每一张图单独做零均值和单位方差会把染色信息抹掉造成类内方差变小、类间区分度变差。实操时先统计训练集的像素统计量存成 json 文件验证和测试阶段加载同一份统计量不要重新统计。更高级的做法是染色分离把 RGB 图像分解成苏木精和伊红两个染色通道去做归一化但效果提升并不总能抵消额外复杂度。先跑通 z-score再考虑染色分离是更务实的顺序。4.3 标签映射统一成 0/1 单通道再转 one-hot标签进入模型之前需要从灰度图矩阵变成模型预期的形状。单类分割的常见做法是加载灰度 mask转成[0,1]的uint8再变成torch.LongTensor最后转 one-hot 变成[C, H, W]。你可能会在别人代码里看到直接用mask / 255我建议不要用除法而是用阈值化mask cv2.imread(labels/sample_001.png, cv2.IMREAD_GRAYSCALE) mask (mask 0).astype(np.uint8)原因在于如果标签里存在一些非 0、非 255 的杂散像素比如标注工具的边缘平滑值 128直接除以 255 会得到 0.5 这种非整数语义模型不知道该归到哪一类。用mask 0会把任何非背景像素都视为细胞核虽然也会引入一点噪声但至少语义是明确且可解释的。实际使用时可以结合 3.3 节的align_mask_to_image先把 mask 尺寸与原图对齐再做阈值化。4.4 损失函数选择BCE 与 Dice Loss 的组合更稳乳腺肿瘤细胞核分割的正负样本极端不平衡。细胞核面积在整张图上可能只占 5%~20%纯用 BCE 会让模型倾向于把整张图预测为背景loss 照样下降但想要的分割轮廓一张没有。Dice Loss 是区域级别度量不受绝对像素数量影响能稳定放大“预测空白”的错误。经验上我常用两者组合把 BCE 的逐像素可解释性和 Dice 的类别均衡能力合到一起import torch import torch.nn.functional as F def bce_dice_loss(pred, target, eps1e-6): # pred 是未经过 sigmoid 的 logitstarget 是 0/1 float bce F.binary_cross_entropy_with_logits(pred, target) pred_prob torch.sigmoid(pred) inter (pred_prob * target).sum(dim(1, 2)) union pred_prob.pow(2).sum(dim(1, 2)) target.sum(dim(1, 2)) dice 1 - (2 * inter eps) / (union eps) return 0.4 * bce 0.6 * dice.mean()这段代码里eps是为了避免某些全背景区域union为 0 时除零错误。target必须是 floatpred 保持 logits 不能提前过 sigmoid因为binary_cross_entropy_with_logits自带 sigmoid 运算提前转换会让数值不稳定。权重0.4/0.6不是固定真理但如果数据集里细胞核占比极低可以适当把 Dice 权重提高到 0.7。不要盲目照搬至少先跑一个 epoch观察验证集 Dice 对两类权重的敏感度再定。5. 避坑乳腺细胞核分割容易翻车的五个地方这类任务里翻车不是某一行代码写错而是问题在训练过半后才暴露。下面五条是我在类似数据集上反复遇到的现象、原因和解决顺序按排查优先级排列。5.1 现象训练 loss 持续下降但验证 Dice 不涨模型训练到第 20 个 epoch 后看训练集 BCE 已经很低验证集 Dice 却一直徘徊在 0.5 以下。打开预测结果发现几乎所有验证图片都被预测成全背景或全前景。原因通常是正负样本不平衡加上 BCE 主导模型掌握了“全部预测为背景”这种偷懒解loss 可以降到很低但想分割的区域一个都没出来。解决路径是先确认当前预测图的像素分布再提高 Dice Loss 权重。另外还要检查验证集切分是否按病例分组如果验证集里混入同病例的相似切片Dice 也不会稳定。5.2 现象预测结果出现大面积空洞和破碎斑块模型预测出来的细胞核区域边界还算准确但核内部多处空心边缘还粘连着细碎的小块。这类问题更像标签噪声标注人员对核边界模糊区域判断犹豫把某些核内部画成背景导致模型学到的正样本分布非常不稳定。解决时先做形态学后处理把空洞闭合、去掉离散小连通域kernel cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5)) closed cv2.morphologyEx(pred.astype(np.uint8), cv2.MORPH_CLOSE, kernel)如果后处理无法根治就要把对应样本的 mask 叠加在原始图上人工检查确认标注质量。不要一上来就换复杂模型先修数据。5.3 现象可视化脚本在中文路径下报错本地代码在 Windows 下路径带中文时plt.imread直接报cannot identify image file。原因是 matplotlib 的 imread 对 Windows 默认编码兼容性有问题一张图都读不出来。解决方式是全面改用 OpenCV 的imread配合pathlib.Path处理路径字符串from pathlib import Path img cv2.imread(str(Path(训练数据/images/核分割样本.png)))多进程运行时尤其要注意别把中文路径传给 matplotlib。用 OpenCV 能处理大部分情况不过如果路径变量本身是 bytes 类型则先解码为 str。5.4 现象标签灰度图被误转成三通道伪彩色读取标签时发现mask.shape是(H, W, 3)而不是(H, W)。常见原因是用 PIL 的Image.open()打开灰度 PNG 后有少数情况会返回RGB模式或者数据从某个图像管理软件导出时被合并成三通道伪彩色图。原因是在加载阶段没有指定灰度模式。解决方式是先读取再做通道压缩if mask.ndim 3 and mask.shape[2] 3: # 先看三个通道是否完全一致一致才安全取单通道 assert np.all(mask[:, :, 0] mask[:, :, 1]) mask mask[:, :, 0]如果三个通道不一致那标签大概率已经被转成伪彩色单纯取单通道会丢失信息需要回到来源重新导出。这一条看起来蠢但真实训练里碰到的概率极高。5.5 现象细胞核只占图幅 5%验证指标虚高但实际无用某次训练在验证集上 Dice 达到 0.94看起来效果很好但模型预测的核区域明显比真值大一圈。原因是背景像素占比 92% 以上模型只要多预测一点背景Dice 分母变大、分子变大整体数值被“稀释”到虚高。判断模型真实水平别只盯着整体 Dice要看类别可分离度至少把肿瘤核单独当正样本计算precision和recall。细胞核区域太小整体指标对边界质量不敏感正确的做法是在验证阶段同时输出按样本的 Dice 分布看是不是仅靠少数大核样本拉高了平均值。6. 进阶技巧先画一张边界重叠图再谈 Dice 高低当模型训练接近尾声不要只看指标数字多画一张预测边界与真值边界的重叠图能发现很多被平均指标掩盖的问题。6.1 边界重叠可视化代码用cv2.findContours从二值掩膜中提取轮廓画到原始图上就能直接对比预测和真值边界的位置关系def draw_boundary_overlay(img, pred_mask, true_mask, save_pathNone): overlay img.copy() # 真值边界用绿色 contours_gt, _ cv2.findContours( (true_mask 0).astype(np.uint8), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE ) cv2.drawContours(overlay, contours_gt, -1, (0, 255, 0), 1) # 预测边界用红色 contours_pr, _ cv2.findContours( (pred_mask 0).astype(np.uint8), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE ) cv2.drawContours(overlay, contours_pr, -1, (255, 0, 0), 1) if save_path: cv2.imwrite(save_path, cv2.cvtColor(overlay, cv2.COLOR_RGB2BGR)) return overlay这里面RETR_EXTERNAL只取最外层轮廓适合细胞核粘连严重、内部还有空腔的情况。如果想把内部空腔也画出来可以换成RETR_CCOMP但会带来大量琐碎短线看图时很难分辨主次。边界重叠图的价值比 Dice 数值更直观你能一眼看出预测是哪边偏移、是整体膨胀还是右侧偏压。6.2 指标顺序建议先看 Dice再看 IOU最后才看准确率医学图像分割里准确率容易被背景像素数量拉高有时 99% 准确率的模型什么也分不出来。正确顺序是先看每个样本的 Dice确认没有大规模坏样本然后再看 IOU因为 IOU 会放大边界偏差对边缘敏感最后再看准确率它只做参考不能作为决策指标。冠心病症状、肿瘤形态这类目标边界偏移一两个像素在临床决策里可能就足以改变判读结果。我现在的习惯是拿到任何一个医学图像分割数据集第一件事永远是打印np.unique、画三栏可视化、抽查 5 到 10 张边界重叠图。这些步骤全部跑完才轮到配置网络和损失函数。这些看起来不起眼的动作帮我把调模型的时间压缩到了原来的三分之一左右。数据和标签的质量没确认之前任何网络结构上的折腾都是空转。希望这份乳腺肿瘤细胞核分割数据的落地思路能帮你少踩几个坑也早点把模型跑到预期效果。希望帮到你。本文还有配套的精品资源点击获取
企业数字化 ERP 产品动态
相关推荐
AURIX TC3XX HSM UCB配置与寄存器操作实战指南 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/28 1:35:07
SSM高校报名系统实战:327并发压测与MySQL5.7事务隔离详解 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/29 8:34:57
TC5020A恒流驱动芯片实战:5个手册没写透的隐藏功能与排查技巧 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/28 1:35:07
Spingboot启动预热的实现 启动预热的适用场景启动预热适合以下情况:数据主要来自第三方接口,无法直接从本地数据库读取。第三方接口响应较慢,首次访问容易超时。一个页面需要调用多个第三方接口或逐项查询。数据读取频繁,但变化不频繁。希望服务启动后&… · 2026/9/29 6:52:37
学Java别走弯路,这5个方向最吃香 学Java的人很多,但学明白的人不多。有人学了半年还在写控制台程序,有人一年就能独当一面。差别不在天赋,而在方向。Java生态太庞大了,什么都学等于什么都没学。选对方向,事半功倍。今天盘点当前最吃香的5个Java方向&am… · 2026/9/29 4:13:53
MATLAB雷达信号脉冲压缩仿真:LFM线性调频、匹配滤波与距离分辨率实现 简介:这套Matlab仿真工具完整呈现雷达信号脉冲压缩过程,从线性调频(LFM)信号生成、目标回波仿真到匹配滤波压缩处理均有可运行代码支撑,面向电子信息工程、计算机、数学等专业学生,适用于课程设计、期末大作… · 2026/9/29 0:45:26
汕头网站建设制作厂家避坑指南:5大注意事项救急 汕头网站建设制作厂家避坑指南:5大注意事项救急 改个需求建站公司拖一周,这种憋屈事我见得太多了。 很多汕头老板找本地建站团队,签合同前看着方案挺美,一上线就变脸。 今天不聊虚的,直接拆解找 汕头网站建设制作厂家 时的5个核心 注意事项… · 2026/9/29 13:54:52
多模态虚假新闻检测实战:BERT+ResNet双塔与对比学习 简介:基于PyTorch的多模态虚假新闻检测项目完整代码包,面向自然语言处理与计算机视觉交叉方向的开发者、科研人员及毕业设计选题者,解决社交媒体中文本与图像联合识别虚假新闻的问题。系统以BERT预训练模型提取文本语义特征,以Res… · 2026/9/29 8:55:58