ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

ISIC皮肤病变分割数据集实战:从数据解压到模型训练全流程

ISIC皮肤病变分割数据集实战:从数据解压到模型训练全流程 简介本资源为ISIC皮肤病变图像分割数据集面向医学图像分割方向的算法工程师、研究生及竞赛选手可用于细粒度分割模型的训练与验证。数据图像分辨率在1000至2000之间原图为jpg格式mask标签为png格式标签中0代表背景、255代表皮肤病具体类别可在classes文本中查看并附带show脚本可同时可视化图像与标签。压缩包共约2000个文件以1279个png标签、719个jpg图像为主另含1个txt类别说明与1个py可视化脚本整体约819.28MB。数据集划分为训练集与验证集训练集含900张图像及900张对应mask验证集含379张图像及379张对应mask目录结构清晰便于直接接入分割网络。目前已有797人学习下载适合需要快速搭建皮肤病变分割实验、验证模型效果或进行细粒度分割研究的读者参考使用。1. ISIC 皮肤病变分割数据集从一堆图片到能训的掩码中间差了什么皮肤病变分割这个方向很多人第一次上手就卡在数据上。ISIC 档案库里的原始图像是临床或皮肤镜拍摄的彩色照片分辨率、光照、毛发遮挡、病灶边界模糊程度差异极大而模型要学的是「哪里是病变、哪里是正常皮肤」这种像素级判断。ISIC 皮肤病变图像分割数据集之所以被反复提起是因为它把原始图像、对应的二值掩码、以及病灶类别标注打包在一起让分割任务有了可监督的信号。它适合两类人一类是想验证 U-Net、DeepLab、SegFormer 这类分割网络在医学场景下表现的算法工程师另一类是皮肤科方向的研究者需要一套公开、可复现的数据来对比不同方法。但拿到压缩包不等于能直接开训图像与掩码的命名对应、掩码的像素值约定、类别信息的读取方式每一步都有坑。这篇笔记就按我实际处理这套数据的顺序把从解压到跑通第一个 epoch 的路径拆开讲。2. 先搞清楚 ISIC 数据集的目录结构与标注约定2.1 图像、掩码、类别信息三者的对应关系ISIC 数据集的常见组织方式是按任务分目录。分割任务下通常有两个平行文件夹一个放原始图像扩展名多为.jpg或.png另一个放对应的分割掩码扩展名多为.png。掩码文件名的主干部分与图像文件名一致这是建立对应关系的唯一依据。类别信息一般单独放在一个 CSV 或 JSON 文件里字段包含图像文件名、病灶类型如黑色素瘤、基底细胞癌、良性痣等、以及可能的其他元数据。这里第一个容易翻车的地方是图像文件名可能带ISIC_前缀和一串数字而掩码文件名有时会多一个_segmentation后缀。如果你直接按os.path.splitext去配对就会漏掉一批。我一般会先写一段脚本把两个目录的文件名主干提取出来做集合运算确认交集大小是否等于图像总数。import os img_dir data/ISIC/images mask_dir data/ISIC/masks def stem(name): # 去掉扩展名再去掉常见的 _segmentation 后缀 base os.path.splitext(name)[0] if base.endswith(_segmentation): base base[:-len(_segmentation)] return base img_stems {stem(f) for f in os.listdir(img_dir) if f.lower().endswith((.jpg, .png))} mask_stems {stem(f) for f in os.listdir(mask_dir) if f.lower().endswith(.png)} print(图像数:, len(img_stems)) print(掩码数:, len(mask_stems)) print(交集:, len(img_stems mask_stems)) print(有图无掩码:, len(img_stems - mask_stems)) print(有掩码无图:, len(mask_stems - img_stems))这段代码的逻辑是先统一文件名主干再比较两个集合。参数上img_dir和mask_dir要换成你本地解压后的实际路径。如果「有图无掩码」不为零说明要么掩码缺失要么命名规则比预想的更复杂需要打印几个具体文件名人工核对。这一步不做后面 DataLoader 里会随机报FileNotFoundError而且很难定位是哪个样本。2.2 掩码像素值的二值化处理ISIC 掩码常见的是 8 位灰度 PNG病灶区域像素值可能是 255背景是 0但也有的版本用 1 表示病灶、0 表示背景甚至出现过 128 作为前景的情况。如果你不确认就直接除以 255模型学到的可能是全零或全一。我习惯在写 Dataset 之前先统计一批掩码的像素值分布。import numpy as np from PIL import Image import os mask_dir data/ISIC/masks sample_masks os.listdir(mask_dir)[:20] for m in sample_masks: arr np.array(Image.open(os.path.join(mask_dir, m))) unique, counts np.unique(arr, return_countsTrue) print(m, dict(zip(unique.tolist(), counts.tolist())))如果输出里只有 0 和 255那二值化就是arr 127。如果出现 1 和 0就直接用。如果出现 128 或其他中间值说明掩码可能经过压缩或有抗锯齿边缘需要先做阈值化再转成 0/1。这个检查花两分钟能省掉后面调半天 loss 不下降的玄学时间。2.3 类别信息的读取与过滤类别信息文件通常是 CSV字段名可能是image、diagnosis、benign_malignant等。不同年份的 ISIC 档案库字段名有差异不要硬编码列名先读表头。如果你只做二分类分割病灶 vs 背景类别信息主要用来做数据分层或后续分类头如果你要做多类别分割那类别信息就直接决定掩码的通道数。常见做法是先用 pandas 读进来看diagnosis的取值分布再决定是否合并稀有类别。import pandas as pd meta pd.read_csv(data/ISIC/metadata.csv) print(meta.columns.tolist()) print(meta[diagnosis].value_counts().head(10))参数上metadata.csv的路径按实际调整。如果diagnosis列里出现unknown或空值要么剔除这些样本要么单独归为一类。我一般会保留但标记训练时用ignore_index跳过避免噪声标签把模型带偏。3. 把 ISIC 数据喂给分割网络Dataset 与增强的落地写法3.1 自定义 Dataset 的最小实现PyTorch 的Dataset是绕不开的。核心就三件事读图、读掩码、做同步增强。图像和掩码必须用同一组随机参数做几何变换否则掩码对不上图像模型直接学废。下面是一个最小可运行的实现假设掩码已经是 0/255图像是 RGB。import os import numpy as np import torch from torch.utils.data import Dataset from PIL import Image import torchvision.transforms.functional as TF import random class ISICDataset(Dataset): def __init__(self, img_dir, mask_dir, img_size256, trainTrue): self.img_dir img_dir self.mask_dir mask_dir self.img_size img_size self.train train self.names sorted([ f for f in os.listdir(img_dir) if f.lower().endswith((.jpg, .png)) ]) def __len__(self): return len(self.names) def __getitem__(self, idx): name self.names[idx] stem os.path.splitext(name)[0] img_path os.path.join(self.img_dir, name) mask_path os.path.join(self.mask_dir, stem .png) if not os.path.exists(mask_path): mask_path os.path.join(self.mask_dir, stem _segmentation.png) img Image.open(img_path).convert(RGB) mask Image.open(mask_path).convert(L) # 统一尺寸 img TF.resize(img, [self.img_size, self.img_size]) mask TF.resize(mask, [self.img_size, self.img_size], interpolationTF.InterpolationMode.NEAREST) # 同步随机翻转 if self.train and random.random() 0.5: img TF.hflip(img) mask TF.hflip(mask) img TF.to_tensor(img) # [0,1] mask (TF.to_tensor(mask) 0.5).float() # 二值化到 0/1 return img, mask逻辑说明__getitem__里先按主干名找掩码兼容两种命名。TF.resize对掩码用最近邻插值避免产生中间灰度值。翻转用同一个随机判断保证图像和掩码同步。最后掩码转成 0/1 的 float 张量配合BCEWithLogitsLoss使用。参数上img_size我一般从 256 起步显存够再上 384 或 512ISIC 图像原始分辨率差异大统一缩放是必要的但要注意病灶占比小的样本缩放后可能只剩几十个像素这时候可以考虑随机裁剪而不是直接缩放。3.2 医学图像增强的边界哪些能加哪些别碰通用增强里水平翻转、垂直翻转、小角度旋转±15°、亮度对比度微调在皮肤病变分割里都是安全的。但颜色抖动要克制因为病灶的颜色是重要判别特征大幅改变色调可能让标签语义漂移。Cutout 和 CutMix 在自然图像里好用在医学分割里要慎用尤其是 CutMix 把两块不同病灶拼在一起掩码边界会变得没有临床意义。我一般会加一个 ElasticTransform模拟皮肤形变但参数要小alpha控制在 30 以内sigma控制在 5 左右。再大就会把病灶形状扭曲得不像真实病变。另外ISIC 图像里常见毛发遮挡有人会专门做毛发去除预处理但我的经验是如果训练集里毛发样本足够多模型自己能学到一定鲁棒性强行去除反而可能引入伪影。如果要做用 DullRazor 这类经典方法别用太激进的形态学操作。3.3 损失函数与评价指标的选择二值分割最常用的是BCEWithLogitsLoss加DiceLoss的组合。ISIC 数据里病灶区域通常只占图像的一小部分纯 BCE 会被背景主导Dice 能拉回一些。我一般用0.5 * BCE 0.5 * Dice如果小病灶漏检严重把 Dice 权重提到 0.7。评价指标别只看像素准确率。背景占 90% 以上时全预测背景也能拿高准确率但 Dice 是 0。所以报告里至少要有 Dice 系数和 IoU。计算 Dice 时预测值要先过 sigmoid 再阈值化到 0/1阈值一般取 0.5但如果你发现模型偏向保守可以扫一下 0.3 到 0.7 看哪个 Dice 最高。def dice_coeff(pred, target, threshold0.5, eps1e-6): pred (torch.sigmoid(pred) threshold).float() inter (pred * target).sum(dim(1, 2, 3)) union pred.sum(dim(1, 2, 3)) target.sum(dim(1, 2, 3)) dice (2 * inter eps) / (union eps) return dice.mean().item()参数上eps防止除零threshold按验证集表现调。注意pred和target的 shape 要一致都是[B, 1, H, W]。4. 训练 ISIC 分割模型时最容易踩的五个坑4.1 掩码和图像尺寸不一致导致广播错误现象DataLoader 返回的img是[3, 256, 256]mask是[1, 256, 256]但拼接 batch 时报错说尺寸不匹配。原因某张掩码原始尺寸和图像不同TF.resize只对图像做了掩码漏了或者掩码是单通道但被读成了三通道。解决在__getitem__里对掩码强制convert(L)并且 resize 时显式指定[img_size, img_size]不要依赖默认行为。另外可以在 Dataset 初始化时先遍历一遍所有样本检查图像和掩码的原始尺寸比例把异常样本列出来。4.2 掩码像素值不是 0/255 导致 loss 不收敛现象训练几个 epochloss 一直在 0.69 附近晃Dice 接近 0。原因掩码里前景像素值是 1但代码里用 127二值化结果全变成 0模型学到的全是背景。解决先跑 2.2 节的像素值统计确认前景值。如果是 1就用 0.5如果是 255就用 127。更稳妥的做法是先把掩码归一化到 [0,1]再统一用 0.5。4.3 类别信息里的文件名和图像文件名对不上现象想按类别分层采样结果metadata.csv里的image列是ISIC_0000001而图像文件名是ISIC_0000001.jpg直接 merge 全变 NaN。原因CSV 里存的是不带扩展名的主干而文件系统里带扩展名。解决在 pandas 里先对image列做str.replace去掉扩展名或者给图像文件名也生成一个主干列两边对齐后再 merge。如果还有_segmentation后缀差异一并处理。4.4 验证集 Dice 高但测试集崩了现象验证集 Dice 0.85换一批外部数据或留出测试集Dice 掉到 0.6。原因ISIC 数据里不同来源的子集分布差异大比如皮肤镜图像和临床照片的色调、分辨率、病灶占比都不同。如果验证集和训练集来自同一子集指标会虚高。解决划分数据时按diagnosis或数据来源做分层确保验证集覆盖各类。另外增强不要只加在训练集验证集至少做 resize 和归一化保持和训练一致的预处理。4.5 显存不够时盲目降分辨率现象把img_size从 512 降到 128显存够了但小病灶分割 Dice 掉得厉害。原因ISIC 里有些病灶只占图像很小一块降分辨率后只剩几个像素模型根本学不到边界。解决优先用梯度累积或混合精度而不是直接砍分辨率。如果必须降先统计病灶面积分布确保降采样后前景像素不少于 100 个。另一个办法是随机裁剪出病灶周围区域而不是整图缩放。5. 用 ISIC 数据做迁移与验证的进阶技巧5.1 预训练权重的选择与微调策略ISIC 数据量不算大从头训分割网络容易过拟合。常见做法是加载 ImageNet 预训练的编码器比如 ResNet34 或 EfficientNet-B0解码器随机初始化。微调时前几个 epoch 冻结编码器只训解码器等 loss 稳定后再解冻全部用更小的学习率。我一般用1e-3训解码器解冻后降到1e-4。如果显存允许用 SegFormer 的 MIT-B0 主干在 ISIC 上收敛比 U-Net 快但要注意它的输入归一化参数和 ImageNet 不同别直接套。5.2 用交叉验证代替单次划分ISIC 数据里类别不平衡单次划分可能让某些稀有类别全落在训练集或验证集。5 折分层交叉验证更稳。每折按diagnosis分层确保每类在每折里的比例接近全局。跑完 5 折后报告 Dice 的均值和标准差而不是只报最好那一折。如果标准差超过 0.05说明模型对数据划分敏感需要检查是不是某些折里小病灶样本过多。5.3 可视化验证别只看数字训练完一定要把预测掩码叠回原图看。我习惯每跑完一个 epoch从验证集里抽 8 张画成 4 行 2 列原图、真实掩码、预测掩码、叠加图。叠加图里真值用绿色轮廓预测用红色轮廓重合部分变黄。这样一眼能看出是漏检、过分割还是边界偏移。数字指标只能告诉你「差多少」可视化告诉你「差在哪」。如果发现模型在毛发多的样本上边界特别碎就回去加毛发相关的增强如果发现小病灶整块漏就调 Dice 权重或加 focal loss。5.4 一个我常犯的错误早期我总想把所有能加的增强都堆上去结果训练集和验证集的分布被拉得太开验证 Dice 反而降了。后来我给自己定了个规矩每加一种增强先单独跑一轮看验证 Dice 有没有提升没有就撤掉。ISIC 数据本身多样性已经够强增强的目的是补足短板不是炫技。另外掩码的边界像素在训练时不要忽略有些实现会把边界像素的 loss 权重调低但在皮肤病变分割里边界恰恰是最难也最重要的部分我一般保持等权重或者用边界加权的 Dice。希望帮到你。本文还有配套的精品资源点击获取
返回列表