
简介本资源面向医学图像分割方向的初学者与算法实践者提供一套乳腺细胞癌症分割的二分类数据集可用于训练与验证 U-Net 等分割网络。图像统一为 512×512 的 png 格式mask 为 0/255 阈值图像0 表示背景、255 表示癌症细胞具体类别可在 classes 文本中查看。数据划分为训练集与测试集训练集含 40 张原图及 40 张对应 mask测试集含 10 张原图及 10 张对应 mask并附一份图像分割可视化脚本随机抽取一张图片展示原图、GT 图像及 GT 在原图上的蒙板效果无需修改即可直接运行。资源包共 103 个文件以 101 个 png 图像与掩膜为主另含 1 个 txt 标签说明和 1 个 py 可视化脚本压缩包约 23.91MB。目前已有 208 人学习适合快速上手医学图像分割任务、验证模型效果并直观检查标注质量。1. 乳腺细胞癌症分割数据集从病理切片到二分类掩码这套数据能跑通什么病理科医生在显微镜下数细胞核的时代正在被像素级分割取代。乳腺细胞癌症分割数据集要解决的核心问题很具体给定一张乳腺组织切片图像逐像素判断它是癌细胞还是正常细胞输出一张二值掩码。这不是目标检测画框也不是图像分类打标签而是语义分割级别的密集预测。数据集包含原始图像、对应的标签文件以及一套数据可视化代码让你在训练之前就能肉眼确认图像和掩码是否对齐。适合谁用做医学图像分割入门的研究生、需要快速验证 U-Net 类模型效果的算法工程师、以及想从自然图像分割迁移到病理领域的从业者。2 类分割意味着输出通道数为 2背景/癌细胞或正常/癌变比多类器官分割简单但病理图像的染色差异、细胞重叠、边界模糊会让 naive 的阈值分割直接翻车。这套数据的价值在于省去你从零标注的几周时间直接进入模型迭代。2. 先搞懂 2 类分割的数据组织图像、掩码、可视化代码怎么配合2.1 病理切片分割和自然图像分割的本质差异自然图像分割比如 COCO 或 VOC的物体边界通常由颜色或纹理突变定义人和车有明确轮廓。乳腺病理切片不一样细胞核密集排列癌细胞和正常细胞的差异体现在核大小、染色质纹理、核浆比这些微观特征上。HE 染色后癌细胞核通常更大、染色更深、形状不规则但在 40 倍镜下两个相邻细胞核的边界可能只差几个像素。这意味着你的分割模型不能只靠边缘检测必须学到纹理和上下文。2 类分割的输出通常是一个 shape 为 (H, W) 的整数掩码像素值 0 表示背景或正常1 表示癌细胞。有些数据集用 0/255 存储读取时需要归一化。标签文件常见格式是 PNG 或 TIF因为这两种格式支持无损压缩且不改变像素值。如果你拿到的是 JSON 多边形标注需要先转成掩码这一步后面会讲。提示病理图像的分辨率通常很大一张全切片扫描WSI可能是 100000×80000 像素。这套数据集大概率已经切成了小 patch但拿到手第一件事是确认图像尺寸和掩码尺寸是否严格一致。2.2 目录结构假设与加载验证由于没有原始仓库可看我按最常见的组织方式给你一个可运行的加载脚本。假设目录如下breast_cancer_seg/ ├── images/ │ ├── case_001.png │ ├── case_002.png │ └── ... ├── masks/ │ ├── case_001.png │ ├── case_002.png │ └── ... └── visualize.py用 Python 验证图像和掩码的配对关系、尺寸、像素值分布import os import numpy as np from PIL import Image import matplotlib.pyplot as plt img_dir breast_cancer_seg/images mask_dir breast_cancer_seg/masks # 取第一对文件检查 img_files sorted(os.listdir(img_dir)) mask_files sorted(os.listdir(mask_dir)) assert len(img_files) len(mask_files), 图像和掩码数量不一致 img_path os.path.join(img_dir, img_files[0]) mask_path os.path.join(mask_dir, mask_files[0]) img np.array(Image.open(img_path)) mask np.array(Image.open(mask_path)) print(f图像 shape: {img.shape}, dtype: {img.dtype}) print(f掩码 shape: {mask.shape}, dtype: {mask.dtype}) print(f掩码唯一值: {np.unique(mask)}) # 可视化叠加 fig, axes plt.subplots(1, 3, figsize(15, 5)) axes[0].imshow(img) axes[0].set_title(原始图像) axes[1].imshow(mask, cmapgray) axes[1].set_title(标签掩码) axes[2].imshow(img) axes[2].imshow(mask, alpha0.4, cmapjet) axes[2].set_title(叠加显示) plt.show()这段代码做了三件事确认文件数量匹配、打印图像和掩码的 shape 与 dtype、用叠加图肉眼检查对齐。关键参数是np.unique(mask)如果输出是[0 1]说明已经是二值掩码如果是[0 255]训练前需要除以 255 或做阈值处理。如果掩码 shape 是(H, W, 3)说明是 RGB 存储的伪彩色掩码需要转成单通道。2.3 标签文件从多边形到掩码的转换有些乳腺细胞数据集提供的是 JSON 格式的多边形标注每个细胞核一圈点坐标。转成掩码用opencv-python或PIL.ImageDrawimport json import numpy as np import cv2 def polygon_to_mask(json_path, image_shape): with open(json_path) as f: data json.load(f) mask np.zeros(image_shape[:2], dtypenp.uint8) for shape in data[shapes]: points np.array(shape[points], dtypenp.int32) label shape[label] # 假设 label 为 cancer 时赋值为 1 value 1 if label.lower() cancer else 0 cv2.fillPoly(mask, [points], value) return mask # 使用示例 mask polygon_to_mask(case_001.json, img.shape)cv2.fillPoly的第二个参数必须是 list of array即使只有一个多边形也要包一层。value的赋值逻辑取决于你的标签命名常见的有cancer/normal、malignant/benign、tumor/stroma。转换后建议用np.unique再确认一次防止某些类别被覆盖。2.4 可视化代码该看什么三个必查项数据集自带的可视化代码通常就是叠加显示但你要额外检查三件事。第一掩码边界是否和细胞核边缘贴合如果掩码明显偏移可能是标注时用了不同的缩放比例。第二图像是否有染色过深或过浅的区域这些区域模型容易预测全 0 或全 1。第三统计正负样本像素比乳腺病理切片中癌细胞区域通常占 10% 到 30%如果低于 5%训练时需要加类别权重或改用 Dice Loss。# 统计正样本像素占比 total_pixels mask.size positive_pixels np.sum(mask 1) print(f正样本占比: {positive_pixels / total_pixels:.4f})如果正样本占比低于 0.1Focal Loss 或带权重的 CrossEntropyLoss 是更稳的选择。这些判断不需要等模型跑完看数据阶段就能定下来。3. 用 U-Net 跑通第一个基线训练脚本、参数和评估指标3.1 为什么 U-Net 仍然是病理分割的首选基线病理图像分割的 SOTA 模型换了一茬又一茬从 U-Net 到 TransUNet 到 SAM 微调但 U-Net 依然是第一个该跑的基线。原因很实际编码器-解码器结构加跳跃连接能在小样本上快速收敛而医学数据集通常只有几百张图。跳跃连接把浅层的高分辨率特征直接传到解码器这对细胞边界这种细粒度结构至关重要。Transformer 类模型需要更多数据才能超过 U-Net如果你手里只有 200 到 500 张标注图U-Net 的性价比最高。实现上我一般用segmentation_models_pytorch库一行代码调出 U-Net编码器用resnet34或efficientnet-b0。不要一上来就上resnet101参数量翻倍但小数据集上容易过拟合。3.2 训练脚本数据加载、损失函数、优化器import torch import torch.nn as nn from torch.utils.data import Dataset, DataLoader import segmentation_models_pytorch as smp import albumentations as A from albumentations.pytorch import ToTensorV2 import cv2 import numpy as np class BreastCancerDataset(Dataset): def __init__(self, img_dir, mask_dir, transformNone): self.img_dir img_dir self.mask_dir mask_dir self.transform transform self.files sorted(os.listdir(img_dir)) def __len__(self): return len(self.files) def __getitem__(self, idx): name self.files[idx] img cv2.imread(os.path.join(self.img_dir, name)) img cv2.cvtColor(img, cv2.COLOR_BGR2RGB) mask cv2.imread(os.path.join(self.mask_dir, name), cv2.IMREAD_GRAYSCALE) mask (mask 127).astype(np.float32) # 二值化 if self.transform: augmented self.transform(imageimg, maskmask) img augmented[image] mask augmented[mask] return img, mask.unsqueeze(0) # mask 增加通道维 # 数据增强病理图像慎用垂直翻转 train_transform A.Compose([ A.Resize(256, 256), A.HorizontalFlip(p0.5), A.RandomRotate90(p0.5), A.ColorJitter(brightness0.2, contrast0.2, saturation0.1, hue0.05, p0.3), A.Normalize(mean(0.485, 0.456, 0.406), std(0.229, 0.224, 0.225)), ToTensorV2() ]) # 模型 model smp.Unet( encoder_nameresnet34, encoder_weightsimagenet, in_channels3, classes1 # 二分类输出 1 通道 sigmoid ) # 损失函数Dice BCE 组合 dice_loss smp.losses.DiceLoss(modebinary) bce_loss nn.BCEWithLogitsLoss() def criterion(pred, target): return 0.5 * dice_loss(pred, target) 0.5 * bce_loss(pred, target) optimizer torch.optim.AdamW(model.parameters(), lr1e-4, weight_decay1e-4) scheduler torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max50)数据增强里我特意没加垂直翻转因为病理切片有方向性垂直翻转会造出不符合组织学结构的假样本。ColorJitter的 hue 参数要小HE 染色的色调变化有限调大了反而让模型学偏。损失函数用 Dice BCE 各占一半Dice 处理正负样本不均衡BCE 提供稳定的梯度。3.3 评估指标IoU 和 Dice 之外还要看什么训练时打印 loss 不够分割任务必须看 IoU 和 Dice。但这两个指标在正样本极少时会有欺骗性如果模型全预测背景IoU 可能还有 0.7。所以额外看敏感度和特异度。def compute_metrics(pred_mask, true_mask, threshold0.5): pred_bin (torch.sigmoid(pred_mask) threshold).float() intersection (pred_bin * true_mask).sum() union pred_bin.sum() true_mask.sum() - intersection iou (intersection 1e-6) / (union 1e-6) dice (2 * intersection 1e-6) / (pred_bin.sum() true_mask.sum() 1e-6) tp (pred_bin * true_mask).sum() fp (pred_bin * (1 - true_mask)).sum() fn ((1 - pred_bin) * true_mask).sum() sensitivity tp / (tp fn 1e-6) specificity (true_mask.sum() - tp) / (true_mask.sum() - tp fp 1e-6) return iou.item(), dice.item(), sensitivity.item(), specificity.item()敏感度低说明漏检癌细胞特异度低说明把正常细胞误判为癌。临床上漏检的代价更高所以训练时可以调高正样本权重让模型偏向多预测正类。3.4 训练循环和验证频率train_loader DataLoader(BreastCancerDataset(images, masks, train_transform), batch_size8, shuffleTrue, num_workers4) device torch.device(cuda if torch.cuda.is_available() else cpu) model.to(device) for epoch in range(50): model.train() epoch_loss 0 for img, mask in train_loader: img, mask img.to(device), mask.to(device) optimizer.zero_grad() pred model(img) loss criterion(pred, mask) loss.backward() optimizer.step() epoch_loss loss.item() scheduler.step() print(fEpoch {epoch1}, Loss: {epoch_loss/len(train_loader):.4f})batch_size 从 8 开始试如果显存不够降到 4 或 2。验证集每 5 个 epoch 跑一次保存验证集 Dice 最高的权重。不要只看训练 loss病理图像过拟合来得很快通常 20 个 epoch 后验证指标就平了。4. 避坑与排查乳腺病理分割里最容易翻车的 5 个地方4.1 掩码像素值不是 0/1 导致 loss 爆炸现象训练第一个 epoch loss 就是 NaN 或者几千。原因掩码用 0/255 存储直接送进 BCEWithLogitsLosstarget 值超出 [0,1] 范围。解决加载时统一做mask (mask 127).astype(np.float32)或者用mask / 255.0。在 Dataset 的__getitem__里加一行打印mask.max()确认。4.2 图像和掩码尺寸不匹配现象cv2.resize或A.Resize报错或者叠加可视化时掩码偏移。原因图像是 1024×1024掩码是 512×512或者图像有 alpha 通道而掩码没有。解决在 Dataset 里强制img cv2.resize(img, (256, 256))和mask cv2.resize(mask, (256, 256), interpolationcv2.INTER_NEAREST)。掩码必须用最近邻插值双线性插值会造出 0.5 这种中间值。4.3 染色差异导致模型在验证集上崩掉现象训练集 Dice 0.85验证集 Dice 0.5。原因不同批次的切片染色深浅不同模型学到了训练集的染色风格。解决加颜色归一化常见做法是 Reinhard 归一化或 Macenko 归一化。简单版可以用A.ColorJitter做增强但效果有限。更稳的是在预处理阶段用histogram matching把所有图像对齐到同一张参考图。4.4 正负样本极度不均衡时 Dice Loss 失效现象Dice Loss 降到 0.3 就不动了预测结果全是背景。原因Dice Loss 在正样本极少时梯度不稳定。解决换 Focal Loss 或 Tversky LossTversky 的 alpha/beta 参数可以单独调 FP 和 FN 的权重。或者用带 pos_weight 的 BCEWithLogitsLosspos_weight 负样本数 / 正样本数。4.5 可视化代码用了错误的颜色映射现象叠加图看起来掩码覆盖了整个图像。原因plt.imshow(mask, cmapjet)把 0 和 1 映射到了 jet 色带的两端视觉上分不清。解决用cmapgray看掩码本身叠加时用alpha0.3并指定vmin0, vmax1。如果掩码是 0/255先除以 255。5. 从 2 类到多类标签映射、类别权重和推理后处理5.1 把 2 类掩码扩展成多类时的标签映射表这套数据是 2 类但实际病理分析可能需要区分癌细胞、正常细胞、间质、淋巴细胞。如果你手头有更细的标注映射关系如下原始标签映射后类别 ID说明background0空白区域normal1正常乳腺上皮细胞cancer2癌细胞stroma3间质组织lymphocyte4淋巴细胞映射时用np.vectorize或查表法不要用循环逐像素赋值大图会慢到无法接受。5.2 多类分割的损失函数和类别权重多类用CrossEntropyLoss或smp.losses.DiceLoss(modemulticlass)。类别权重按像素频率的倒数设置class_counts np.bincount(mask.flatten(), minlength5) class_weights 1.0 / (class_counts 1e-6) class_weights class_weights / class_weights.sum() criterion nn.CrossEntropyLoss(weighttorch.tensor(class_weights, dtypetorch.float32))权重不要直接取倒数先归一化否则梯度尺度差异太大。5.3 推理后处理去除小连通域和孔洞填充模型输出的概率图二值化后常有孤立的小噪点。用cv2.connectedComponentsWithStats去掉面积小于 50 像素的连通域再用scipy.ndimage.binary_fill_holes填内部孔洞。这两步在临床可视化时很关键医生不想看到一堆碎点。from scipy import ndimage import cv2 def postprocess(binary_mask, min_area50): num_labels, labels, stats, _ cv2.connectedComponentsWithStats(binary_mask, connectivity8) cleaned np.zeros_like(binary_mask) for i in range(1, num_labels): if stats[i, cv2.CC_STAT_AREA] min_area: cleaned[labels i] 1 filled ndimage.binary_fill_holes(cleaned).astype(np.uint8) return filledmin_area根据你的图像分辨率调256×256 的 patch 用 50 合适1024×1024 的用 200 以上。5.4 一个我反复用的验证习惯每次改完数据增强或损失函数先拿 10 张训练图过一遍模型看预测掩码和真实掩码的叠加图。如果 10 张里有 3 张以上边界明显偏移不要继续训练先回去查数据对齐。这个习惯帮我省过至少两次通宵调参的后悔药。模型指标可以骗人肉眼叠加图不会。希望帮到你。本文还有配套的精品资源点击获取