简介:医学常见9种皮肤疾病检测数据集,面向医学影像AI开发与目标检测任务,涵盖Actinic Keratosis、基底细胞癌、黑素瘤、痣等9个类别,共11294张已增强的皮肤病变图片,并提供YOLO与VOC两种格式标注,适合用于皮肤病智能识别、目标检测模型训练和迁移学习研究。压缩包约380.8MB,按JPEGImages、Annotations、labels三个文件夹组织,分别存放jpg原图、xml标注框和txt标签文件;其中xml标注为资源主体,内容预览展示约2000个文件(含1999个xml与1个txt说明),目录结构清晰,便于按类别检索使用。每个类别均给出对应标注框数,如黑素瘤1544框、基底细胞癌1539框、光化性角化病1335框,方便分析类别分布与筛选数据;图像和标注一一对应,可直接接入现有YOLO/VOC训练流程。目前已有167人学习下载,适合需要高质量医学皮肤影像数据的算法工程师、研究者和学生使用。
1. 皮肤疾病检测的痛点:11294 张图能干什么
做皮肤疾病检测的人应该都能体会,这类医学图像数据集最让人头疼的不是模型结构,而是标注。临床图片拍摄环境乱、光照不统一、病灶边界模糊,再加上医疗标注成本高,很多公开数据集要么类别太少,要么单类样本严重不均衡。这个压缩包解压后是 11294 张图、9 类常见皮肤疾病,同时给了 YOLO 和 VOC 两种标注格式,训练入口几乎没有门槛。适合准备做医疗图像目标检测的工程师、做皮肤影像 AI 的算法同学,以及带学生做医学图像课题的导师。它解决的核心问题很直接:省掉从零收集、清洗、标注皮肤病图像的大半个月时间,让你直接进入模型训练环节。
2. 先看清楚数据:目录结构与双格式标注怎么对
2.1 解压后的目录结构
拿到压缩包,第一件事不是急着训练,而是先把整个目录结构梳理清楚。我解压后看到的布局是这种常见组织方式,你可以参照着核对一下自己的包:
skin_disease/ ├── images/ │ ├── train/ # 训练集图像 │ ├── val/ # 验证集图像 │ └── test/ # 测试集图像 ├── labels/ │ ├── train/ # 与 images/train 对应的 YOLO 标注 │ ├── val/ │ └── test/ ├── annotations/ # VOC XML 格式标注,文件名与图片一一对应 ├── classes.txt # 类别清单,一行一个类 ├── data.yaml # YOLO 可直接引用的数据集配置文件 └── README.mdimages 和 labels 是 YOLO 训练需要的一对目录,annotations 里存的是 VOC XML。很多人在这一步会踩一个认知坑:以为 VOC 和 YOLO 标注是同一份文件的不同编码。实际不是,它们是两套完全独立的数据,同一张图片在 labels 下有一个同名的 .txt 文件,在 annotations 下有一个同名 .xml 文件。两者描述的是同一批目标框,但坐标形式完全不同,后面转换时需要确保两类文件是同步的,不能只转换其中一部分。
2.2 VOC 与 YOLO 标注格式的本质差异
打开一个 VOC 的 XML 文件,你能看到非常直观的像素坐标:
<annotation> <filename>eczema_0012.jpg</filename> <size> <width>800</width> <height>600</height> <depth>3</depth> </size> <object> <name>eczema</name> <bndbox> <xmin>120</xmin> <ymin>85</ymin> <xmax>420</xmax> <ymax>390</ymax> </bndbox> </object> </annotation>而 YOLO 的 .txt 标注是这样一段归一化相对坐标:
6 0.337500 0.395833 0.375000 0.508333坐标换算规则很固定:x_center = (xmin + xmax) / 2 / width,y_center = (ymin + ymax) / 2 / height,框宽高同理除以图像宽高。这里 width 和 height 必须以 XML 里<size>标签的实际值为准,而不是凭图片名后缀猜。我见过有人直接用 640 当分母去算归一化坐标,结果训练出的预测框全部偏移,而且偏移量随图像原始尺寸变化,排查了很久才发现是换算分母错了。
2.3 类别清单的核对优先级
classes.txt 是这份资源里最重要的一个文件。我建议一解压就先打开它,确认 9 个类别的顺序,因为 YOLO 的标签编号严格依赖这个顺序。我拿到的这份,9 个类大致覆盖了痤疮、湿疹、银屑病、荨麻疹、脂溢性皮炎、带状疱疹、体癣、手足口病、白癜风这类临床高频病种,但如果你手里的包命名不同,以包内 classes.txt 为准。
常见错误是用 VOC 里 object 的 name 字符串作为类别编号写入 txt 文件,这是不可行的。YOLO 只认整数 ID,这个 ID 必须是 classes.txt 中类别名字符串的下标。转换脚本最核心的环节就是建立 name 到 ID 的映射表,稍有一点错位,整个数据集的标注就全错了,而且模型不会报错,AP 会低得莫名其妙。
3. 把 VOC 吃透再转 YOLO:转换脚本与三个会翻车的地方
3.1 转换脚本:从 XML 到 txt 的最小实现
如果你拿到的包没有带 labels 目录,或者你想自己重新生成一套 YOLO 标注,这个脚本可以直接用。它完成的工作是遍历所有 XML 文件,解析目标框,按 classes.txt 的顺序映射类别编号,最后写出一行一个目标框的 txt 文件:
import os import xml.etree.ElementTree as ET from pathlib import Path def voc_to_yolo(xml_path, out_dir, class_map): tree = ET.parse(xml_path) root = tree.getroot() width = float(root.find("size/width").text) height = float(root.find("size/height").text) filename = root.find("filename").text lines = [] for obj in root.findall("object"): name = obj.find("name").text if name not in class_map: continue cat_id = class_map[name] box = obj.find("bndbox") xmin = float(box.find("xmin").text) ymin = float(box.find("ymin").text) xmax = float(box.find("xmax").text) ymax = float(box.find("ymax").text) x_center = (xmin + xmax) / 2 / width y_center = (ymin + ymax) / 2 / height box_w = (xmax - xmin) / width box_h = (ymax - ymin) / height lines.append(f"{cat_id} {x_center:.6f} {y_center:.6f} {box_w:.6f} {box_h:.6f}") out_path = Path(out_dir) / (Path(filename).stem + ".txt") out_path.write_text("\n".join(lines), encoding="utf-8") xml_dir = Path("annotations") out_dir = Path("labels") out_dir.mkdir(exist_ok=True) class_map = {} with open("classes.txt", encoding="utf-8") as f: for idx, line in enumerate(f): class_map[line.strip()] = idx for xml_path in xml_dir.glob("*.xml"): voc_to_yolo(xml_path, out_dir, class_map)这里 class_map 的构建是关键,用 enumerate 取索引,确保类别名对应到 classes.txt 中的实际位置。坐标计算必须用 float 而不是 int,否则一旦 xmin 与 xmax 差值不是整数,小数部分会被截断,框宽产生微小偏差。write_text 指定 utf-8 编码,避免 Windows 默认编码问题写出来的乱码。输出文件名用Path(filename).stem,与图片名保持一致,YOLO 训练器依赖这个命名对齐关系。
3.2 坐标越界:训练器不报错但效果变差
VOC 标注里经常出现目标框边缘超出图像边界的情况,比如 xmax 标成 810 而图像宽只有 800。直接用原始值算归一化坐标,YOLO 的 txt 里就会写进大于 1 的数字,训练时某些增强操作会产生负坐标或越界框,模型虽然能跑完,但损失值跳动非常厉害。
解决方式是在转换脚本里加一段坐标修正逻辑,把 xmin、ymin 夹在 0 与 width/height 之间,xmax、ymax 同理。如果修正后 xmax <= xmin,说明这个框本身是无效的,可以直接丢弃,并记录到日志里人工复核。检查方法可以写后缀在脚本末尾,把越界图像名输出到一个文件里。
3.3 类别错位是最隐蔽的坑
转换时如果跳过缺失类别,或 classes.txt 顺序和 XML 里 name 的排序不一致,就会发生类别错位。常见场景是数据集的原始类别编号写在 XML 的name字段里,而 classes.txt 是按首字母排序的,两者不匹配,转换脚本却用了 dict 映射,结果训练时类别语义和标注内容对不上。
这类问题训练中几乎看不出来,loss 正常下降,mAP 却一直很低。我在拿到新数据集时有一个习惯:转换完成后,随机挑 5 张图打印标注框和图片编号,用 OpenCV 画出来人工目视验证一遍。花五分钟能避免后面浪费十个小时。
import random import cv2 from pathlib import Path image_dir = Path("images/train") label_dir = Path("labels/train") sample = list(image_dir.glob("*.jpg")) random.seed(7) sample_ids = random.sample(sample, 5) for img_path in sample_ids: img = cv2.imread(str(img_path)) txt_path = label_dir / (img_path.stem + ".txt") if not txt_path.exists(): continue for line in txt_path.read_text(encoding="utf-8").strip().splitlines(): cat_id, xc, yc, w, h = line.split() xc, yc, w, h = map(float, (xc, yc, w, h)) x1 = int((xc - w / 2) * img.shape[1]) y1 = int((yc - h / 2) * img.shape[0]) x2 = int((xc + w / 2) * img.shape[1]) y2 = int((yc + h / 2) * img.shape[0]) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(img, str(cat_id), (x1, y1 - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 2) cv2.imwrite(f"check_{img_path.name}", img)这段代码把标注框直接画在图上,可以快速确认框是不是框住了病灶位置、类别编号是否合理。yolo 训练前很少有人做这一步,但中药可以提前暴露绝大多数坐标和类别问题。
4. 在 YOLO 上训练:数据集配置与增强参数调法
4.1 一份可以直接用的 dataset.yaml
YOLO 训练的第一步是写数据集配置文件。YOLOv5、YOLOv8、YOLOv9、YOLO11 的数据接口基本兼容,格式如下:
path: ./skin_disease train: images/train val: images/val test: images/test names: 0: acne 1: eczema 2: psoriasis 3: urticaria 4: seborrheic_dermatitis 5: herpes_zoster 6: tinea_corporis 7: hand_foot_mouth 8: vitiligopath 最好写绝对路径或相对当前工作目录的路径,train 和 val 写的是相对 path 的子目录。YOLO 训练器会去 path 下拼接 images/train 目录找图片,labels 目录的定位方式则是把 images 替换为 labels,也就是它默认标签文件夹与图片文件夹同名结构。如果你的包目录名与这个不一致,需要手动改成自己的实际目录名。
一个很常见的错误是 names 的顺序和 labels 里的类别 ID 不一致。这里面的 names 索引必须与 classes.txt 完全一一对应,第 0 项是 classes.txt 第一行,以此类推。如果有一个错位,模型会把不同疾病学成另一个疾病的特征,验证阶段根本看不出来。
4.2 训练命令与关键参数
用 YOLOv8 训练,命令行最简洁,我一般这样起:
yolo detect train \ model=yolov8s.pt \ data=skin_disease.yaml \ imgsz=640 \ batch=32 \ epochs=150 \ lr0=0.01 \ lrf=0.01 \ warmup_epochs=3 \ cos_lr=True \ project=run_skin \ name=baselinemodel=yolov8s.pt 会从官方源拉取预训练权重,这就是 yolo 预训练模型下载的常见入口,服务器无法联网时,可以提前下载 .pt 文件放到本地目录,再用绝对路径指向它。imgsz=640 是输入分辨率,皮肤病灶大小差异很大,如果后续发现小目标检不出来,优先把 imgsz 提到 768,而不是急着改模型结构。batch=32 需要显存支撑,8G 显存建议降到 16。epochs=150 对医学图像标注数据量来说不算长,主要看模型的 loss 曲线什么时候收敛。
训练过程中最值得关注的是 val mAP 曲线和 loss 曲线。如果 train loss 持续下降但 val mAP 一直不动,多半是数据问题而不单纯是模型问题。如果想深入理解 yolo 损失函数的变化趋势,可以把plots=True加进命令行,训练器会输出 loss 曲线图和三通道的预测样本,观察后期收敛情况比盯终端日志直观得多。
4.3 已增强的数据集是否还要再增强
“已增强”三个字意味着数据里已经包含了旋转、翻转、亮度调整等操作生成的样本。此时 YOLO 训练器默认开启的 mosaic 增强会造成增强叠加,容易让模型看到太多极端样本,反而影响泛化。
我一般会把增强参数调得比默认保守一档:
mosaic: 0.5 mixup: 0.1 hsv_h: 0.01 hsv_s: 0.3 hsv_v: 0.2 degrees: 5 translate: 0.1 scale: 0.3 fliplr: 0.5 flipud: 0.0flipud 要关掉,皮肤疾病图像上下翻转会改变病灶的体表位置语义,医学应用里没有倒置图像先例。degrees 控制在 5 度以内,避免旋转产生不自然的病灶形状。验证集不能做任何增强,YOLO 默认在验证时关闭这些操作,不需要额外配置。
4.4 环境配置与最快验证路径
YOLO 的环境配置基本只需要一行:pip install ultralytics,前提是环境中已有 PyTorch 和 NVIDIA GPU 驱动。CPU 机器也能跑,但 11294 张图用 CPU 跑一遍 epoch 需要十几分钟,不建议用 CPU 做完整训练。先猜几个超参数跑通流程,确认数据没问题后再用完整参数训练。
如果不想改代码,直接从 GitHub 克隆 ultralytics 仓库,设置工作目录后运行上面的命令行即可。yolo 环境配置这件事本身不复杂,复杂度主要来自 CUDA 版本与 PyTorch 的匹配。建议把 CUDA 和 PyTorch 版本对应关系查清楚再装。库装好后跑yolo detect --help能输出帮助信息,就说明入口没有问题。
5. 避坑指南:从坐标越界到 BN 崩溃的五条排查记录
5.1 BN 崩溃:loss 突然变成 NaN
现象:训练进行到中途某一个 epoch,损失函数输出变成 nan,重启训练后可能在同一个位置或者更早的位置再次崩溃。
原因:这个现象在 yolo 训练中经常被称为 bn 崩溃,本质是 BatchNorm 的统计量在某个批次里出现极端值,通常由学习率偏大、batch size 过小、样本中存在全黑或全白的退化图像共同触发。皮肤图像中黑白滤镜、暗光环境拍摄的样本较多,更容易触发。
解决:第一步降低 lr0 到 0.005 或 0.001,并把 warmup_epochs 加到 5;第二步把 batch 从 16 提升到 32;第三步在数据侧过滤掉信息量过低的退化图像,比如灰度方差小于阈值的图片。按这个顺序排查,多数 BN 崩溃都能解决。
5.2 混淆矩阵总和不是 1
现象:训练结束后,YOLO 输出的混淆矩阵每行加起来不等于 1,甚至相差很多,有人误以为模型输出有问题。
原因:混淆矩阵的每一行代表该真实类别样本被划分到各预测类别的比例,但它没有计入正确分类的样本,所以行和不是 1 是正常现象。另外如果验证集里存在重复图像,矩阵数值会被异常放大。
解决:不用混淆矩阵判断模型整体精度,它只用来观察两个具体类别之间的互相误检关系。训练指标以 mAP 和每类 AP 为准,混淆矩阵定位问题,但不参与量化比较。
5.3 训练时提示框越界或坐标异常
现象:训练时报错信息里出现 “All bounding boxes are invalid” 或 “box coordinates out of bounds”。
原因:VOC 转 YOLO 时没有处理越界坐标,或 XML 中 bndbox 字段缺失、填写了空值,增强时产生负数坐标。
解决:写一个数据清洗脚本,检查所有 labels 下的 txt 文件,将坐标小于 0 或大于 1 的行丢弃,同时统计无标注文件的图片数量。这类数据问题不解决,训练多少次都只会得到同样的报错。
5.4 类别错位导致 AP 大面积偏低
现象:训练过程正常,loss 很漂亮,但最终每个类别的 AP 都只有 0.3 以下,甚至有的类识别不出来。
原因:classes.txt 的顺序与 labels 中的类别 ID 不一致。常见原因是数据集在制作时曾多次重新排序类别,而 labels 是旧版本生成的,转换脚本没有同步更新。
解决:强制校验类别顺序。训练前把 data.yaml 中的 names 打印出来,同时解析一个 labels 下的 txt 文件,查看最大类别 ID,确认它们一致。如果你把第五步可视化脚本执行一遍,这类问题 3 分钟内暴露。
5.5 增强导致小目标彻底消失
现象:训练集里标注了不少小病灶,但模型对这类目标几乎不响应,召回率很低。
原因:已增强的数据里若包含大量随机裁剪增强,小目标可能被裁剪成只剩局部特征,标注框变得极小,训练时容易被当成背景过滤掉。
解决:把 imgsz 从 640 提到 768,同时调低 mosaic 概率到 0.3 以下。另一个有效手段是对小目标样本过采样,从数据层面增加小框在每个 batch 中的占比。皮肤病早期病灶普遍偏小,这个小目标问题直接关系到产品落地时的检测能力。
6. 验证与进阶:用混淆矩阵定位 9 类瓶颈
训练完成后,验证工作我习惯分成三层。第一层是看合成结果:
yolo detect val \ model=run_skin/baseline/weights/best.pt \ data=skin_disease.yaml这个命令会输出整体的 mAP50、mAP50-95,以及每个类别的精确率、召回率和平均精度。把每类的 AP 单独列出来,才能看出 9 类里哪些是真实瓶颈。
第二层是打开混淆矩阵图。皮肤疾病类别里,银屑病和湿疹、脂溢性皮炎之间的视觉特征重叠严重,混淆矩阵上通常会看到相邻类别之间的深色格子。此时有两个选择:把类别合并成粗粒度任务,或是搜集更多这类样本做增量训练。如果模型把 A 类大量误检成 B 类,盲目调阈值是无效的,本质是这两个类的特征空间在骨干网络中分离度不够。
第三层是固定一个统一 imgsz 做终评。我见过两次不同输入分辨率下 mAP 相差 8 个百分点的情况,问题不在模型而在验证设置不统一。从那以后我每次做医学图像检测实验,都会在同一份测试集、同一个 imgsz 下强制走一遍完整验证流程,把 AP 差异控制到只由模型决定。这个习惯帮我避开了很多次“改了一堆参数但效果没变化”的无效迭代。希望帮到你。
本文还有配套的精品资源,点击获取