
简介这份脸部皮肤病检测数据集面向计算机视觉入门与目标检测实践者尤其适合正在做医学图像识别、课程设计或算法练手的学习者。数据集聚焦粉刺、丘疹、结节、脓疱四类皮肤病灶标签名称分别为comedones、nodules、papules、pustules可用于训练和验证目标检测模型。压缩包共约2000个文件以1590个xml标注文件和410个txt标签文件为主另含对应jpg图片整体约35.93MB同时提供YOLO与VOC两种标注格式方便直接接入不同检测框架。全部图片清晰且未做数据增强标注均为矩形框总框数达8827个其中comedones 3875个、papules 3041个、pustules 1297个、nodules 614个类别分布信息完整便于分析样本均衡性。目前已有103人学习适合用来快速搭建皮肤病检测基线、验证数据加载流程或对比不同模型效果。资源来源于网络分享仅限学习交流请勿用于商业用途。1. 从一堆皮肤镜照片到可训练标注这份数据集到底省掉了哪几步手上有一批皮肤科门诊拍下来的脸部皮损照片想跑一个目标检测模型把红斑、丘疹、色素痣这些区域框出来真正动手时才发现最耗时的根本不是选模型而是把几百上千张图变成模型能吃的标注。这份脸部皮肤病检测数据集直接给了 YOLO 和 VOC 两套标注格式等于把「找图—清洗—画框—转格式—校验」这条链路里最枯燥的部分替你走完了。它适合三类人刚入门目标检测、想拿真实医学场景练手的学习者需要快速搭一个皮损检测 baseline 做对比实验的算法工程师以及手上只有原始照片、想省掉标注环节直接验证想法的人。数据集本身不解决诊断问题它解决的是「让你今天就能开始训练」这件事。2. 先搞懂 YOLO 与 VOC 两套标注的差异选错格式等于白干拿到数据集第一件事不是急着train.py而是先确认你要用哪套标注。同一个数据集给了两种格式不是让你随便挑而是对应两条不同的技术路线。选错了轻则训练时 loss 不降重则模型把背景当目标学最后 mAP 惨不忍睹。2.1 两种格式的坐标系与文件组织VOC 格式的核心是每张图配一个同名 XML 文件框的位置用左上角和右下角的绝对像素坐标表示长这样annotation filenameskin_0001.jpg/filename size width640/width height480/height depth3/depth /size object nameerythema/name !-- 类别名注意大小写 -- bndbox xmin112/xmin !-- 左上角 x绝对像素 -- ymin88/ymin !-- 左上角 y -- xmax305/xmax !-- 右下角 x -- ymax270/ymax !-- 右下角 y -- /bndbox /object /annotationYOLO 格式则是一张图配一个同名 txt每行一个目标格式是类别索引 中心x 中心y 宽 高而且这四个值全部是相对图像宽高的归一化值0~1 之间0 0.325781 0.372917 0.301563 0.379167 1 0.712500 0.604167 0.187500 0.229167这里有个新手最容易翻车的点VOC 的xmax/ymax是右下角坐标而 YOLO 的宽高是框的实际尺寸两者换算时xmax不能直接当宽用。转换公式是w (xmax - xmin) / img_wh (ymax - ymin) / img_h中心点cx (xmin xmax) / 2 / img_w。我见过有人直接把xmax当宽塞进去训练时框全部偏到右下角排查了一下午才发现是这里错了。2.2 类别索引映射那个容易被忽略的 classes.txtYOLO 格式里写的是数字索引模型本身不知道 0 代表什么。数据集通常会带一个classes.txt或data.yaml里面按行列出类别名行号就是索引。这个文件的顺序必须和标注里的数字严格对应一旦你手动调整了类别顺序却没同步改标注模型学到的就是错位的类别。# data.yaml 常见结构 path: ./skin_dataset train: images/train val: images/val nc: 4 # 类别数量必须和 names 长度一致 names: 0: erythema # 红斑 1: papule # 丘疹 2: nevus # 色素痣 3: plaque # 斑块nc和names长度不一致是另一个高频报错来源YOLO 训练时会在加载阶段直接抛异常报错信息往往只写「label index out of range」不会告诉你到底是哪个文件的问题。2.3 该选哪套按你的训练框架倒推判断标准很简单用 YOLO 系列v5/v8/v11 等就选 YOLO 格式用 Detectron2、MMDetection、Faster R-CNN 这类就选 VOC 格式。如果你用的是 YOLO 但只有 VOC 标注需要自己写转换脚本反过来也一样。数据集同时提供两套省掉的就是这个转换和校验环节。但要注意两套标注的划分train/val必须一致不能 YOLO 用一套划分、VOC 用另一套否则对比实验没有意义。3. 把数据集接进 YOLOv8 训练目录结构、配置与首轮跑通格式确认之后下一步是让数据真正被框架读进去。这一步的坑集中在目录结构和路径配置上跟模型本身没关系但卡住的人最多。3.1 标准目录结构与文件放置YOLO 对目录结构有隐式约定图片和标签分开放但文件名不含扩展名必须一一对应。推荐这样组织skin_dataset/ ├── data.yaml ├── images/ │ ├── train/ │ │ ├── skin_0001.jpg │ │ └── ... │ └── val/ │ ├── skin_0201.jpg │ └── ... └── labels/ ├── train/ │ ├── skin_0001.txt # 与图片同名只是扩展名不同 │ └── ... └── val/ ├── skin_0201.txt └── ...关键点images/train/skin_0001.jpg对应的标签必须是labels/train/skin_0001.txtYOLO 在加载时会自动把路径里的images替换成labels、扩展名替换成.txt去找。如果你把标签和图片放同一个目录或者文件名对不上框架会直接把这个样本当负样本没有目标处理训练时表现为「明明有框却学不到」非常隐蔽。3.2 启动训练与关键参数目录就绪后一条命令就能跑起来yolo detect train \ dataskin_dataset/data.yaml \ modelyolov8n.pt \ # 先用 nano 版验证流程别一上来就上大模型 epochs100 \ imgsz640 \ # 与标注时的图像尺寸匹配别乱改 batch16 \ # 显存不够就降到 8 或 4 patience20 \ # 20 轮无提升就早停省时间 projectruns/skin \ namebaseline参数说明imgsz要和数据集图像的原始尺寸或你预处理后的尺寸一致如果原图是 640×480 而你设成 640×640YOLO 会做 letterbox 填充框的坐标会被同步缩放一般没问题但如果你自己做过裁剪却没更新标注就会错位。batch根据显存调8G 显存跑yolov8n用 16 基本够跑yolov8m就得降到 8。patience是早停轮数医学数据量通常不大设 20 能避免过拟合后白跑。3.3 训练日志怎么看loss 与 mAP 的对应关系跑起来之后终端会刷一堆指标新手容易只盯着box_loss看。实际上判断训练是否正常重点看三个box_loss持续下降说明框回归在收敛cls_loss下降说明分类在学mAP50上升说明整体检测质量在提升。如果box_loss降但mAP50不涨大概率是标注框质量有问题比如框太松或太紧如果cls_loss震荡不降检查类别是否不平衡——皮肤病数据里某些类别样本极少是常态这时候要考虑加权重或做数据增强。提示首轮训练建议先用yolov8n加 10 个 epoch 快速验证流程通不通确认 loss 在降、mAP 在涨再换大模型跑完整训练。直接上大模型跑几小时才发现路径配错时间成本太高。4. 从 VOC 转 YOLO 或反向转换脚本、校验与三个易错点数据集虽然给了两套格式但实际项目里你经常需要自己转——比如拿到的是 VOC 标注但团队统一用 YOLO 训练或者你想用 MMDetection 做对比实验又得转回 VOC。转换脚本不长但细节决定成败。4.1 VOC 转 YOLO 的完整脚本import xml.etree.ElementTree as ET import os from PIL import Image # 类别名到索引的映射顺序必须和 data.yaml 里的 names 一致 CLASS_MAP {erythema: 0, papule: 1, nevus: 2, plaque: 3} def voc_to_yolo(xml_path, img_dir, out_dir): tree ET.parse(xml_path) root tree.getroot() # 从 XML 里读图像尺寸不要用 PIL 再开一次图省 IO size root.find(size) img_w int(size.find(width).text) img_h int(size.find(height).text) lines [] for obj in root.findall(object): cls_name obj.find(name).text.strip() if cls_name not in CLASS_MAP: continue # 跳过未定义类别避免索引越界 cls_id CLASS_MAP[cls_name] bbox obj.find(bndbox) xmin float(bbox.find(xmin).text) ymin float(bbox.find(ymin).text) xmax float(bbox.find(xmax).text) ymax float(bbox.find(ymax).text) # 归一化并转成中心点宽高 cx (xmin xmax) / 2.0 / img_w cy (ymin ymax) / 2.0 / img_h w (xmax - xmin) / img_w h (ymax - ymin) / img_h # 裁剪到 [0,1]防止标注越界导致训练报错 cx, cy min(max(cx, 0), 1), min(max(cy, 0), 1) w, h min(max(w, 0), 1), min(max(h, 0), 1) lines.append(f{cls_id} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}) base os.path.splitext(os.path.basename(xml_path))[0] with open(os.path.join(out_dir, base .txt), w) as f: f.write(\n.join(lines))逻辑说明先解析 XML 拿到图像宽高再逐个 object 取类别和框坐标按公式转成归一化的中心点加宽高。CLASS_MAP必须和data.yaml的names顺序完全一致这是最容易出错的地方。裁剪到[0,1]是防御性写法实际标注里偶尔会出现xmax超过图像宽度的情况不裁剪的话 YOLO 加载时会警告甚至丢弃该样本。4.2 转换后的校验三行代码查异常转完不要直接开训先跑一遍校验import glob for txt in glob.glob(labels/train/*.txt): with open(txt) as f: for i, line in enumerate(f): parts line.strip().split() if len(parts) ! 5: print(f{txt} 第{i}行字段数不对: {line}) continue vals list(map(float, parts[1:])) if any(v 0 or v 1 for v in vals): print(f{txt} 第{i}行坐标越界: {vals})这段脚本能揪出三类问题字段数不对通常是空行或格式错乱、坐标越界转换时没裁剪、以及隐含的类别索引超范围如果parts[0]大于等于nc训练时才会报错这里可以提前加判断。4.3 三个高频易错点第一个是图像尺寸不一致。VOC 的 XML 里记录了宽高但如果你在转换前对图像做了缩放却没同步改 XML转换出来的归一化坐标就是错的。常见做法是转换前统一用原始 XML 里的尺寸不要相信当前图片文件的实际尺寸。第二个是类别名大小写和空格。XML 里写的是Erythema而CLASS_MAP里是erythemaif cls_name not in CLASS_MAP会直接跳过导致这个类别的框全部丢失训练时表现为某个类别完全学不到。建议转换前先set一下所有出现过的类别名人工核对一遍。第三个是文件名带特殊字符或中文。YOLO 加载时对路径里的非 ASCII 字符处理不一致某些版本会静默跳过。转换时统一重命名成skin_0001这种纯英文加数字的格式最稳妥。5. 避坑与排查训练不收敛、mAP 异常、标注错位的实战记录这一章集中写我在用这类皮肤病数据集时踩过的坑每条按「现象 → 原因 → 解决」来方便你对照排查。现象一训练 loss 从第一轮就不降一直卡在某个值附近震荡。原因最常见的是data.yaml里的nc和实际类别数不一致或者names顺序和标注里的索引对不上。YOLO 在加载标签时如果发现索引超出nc范围会跳过该标签但不报错导致大量样本变成无目标的负样本模型学不到东西。 解决先跑一遍 4.2 的校验脚本确认所有parts[0]都小于nc再核对names顺序和转换时的CLASS_MAP是否一致。改完重新生成标签不要在原标签上手动改。现象二mAP50 能到 0.7 以上但 mAP50-95 很低框的位置总是不准。原因标注框质量参差。皮肤病损边界本身就模糊不同标注人员画的框松紧不一模型学到的框回归目标不一致。另外如果数据集里小目标多比如小丘疹而imgsz设得偏小小目标在特征图上几乎消失。 解决把imgsz提到 640 或更高前提是显存够检查标注里有没有框宽高接近 0 的极端样本过滤掉如果类别内框的尺度差异大开 YOLO 的多尺度训练scale参数或加 Mosaic 增强。现象三验证集指标正常但拿真实新图片推理时框全偏了。原因训练时的预处理和推理时的预处理不一致。比如训练用了 letterbox 填充到 640×640推理时直接 resize 到 640×640 没保持宽高比坐标映射就错了。另一种情况是数据集里的图片 EXIF 带了旋转信息训练框架读了旋转后的图而你推理时用的库没处理 EXIF。 解决推理时严格复用训练时的预处理流程YOLO 的predict默认会做 letterbox但如果你自己写了推理脚本就要手动对齐。EXIF 问题用PIL.ImageOps.exif_transpose统一处理一遍再存图。现象四某个类别始终检测不到其他类别正常。原因类别样本极度不平衡。皮肤病数据里罕见病损可能只有几十个框而常见类别有上千个模型倾向于忽略少数类。也可能是这个类别的标注名在转换时被CLASS_MAP漏掉了框全部丢失。 解决先确认这个类别的标注数量如果确实少用过采样或 copy-paste 增强补样本同时在 loss 里给少数类加权YOLO 支持通过cls参数调分类 loss 权重。如果是漏映射回到 4.1 的脚本检查CLASS_MAP。现象五训练到一半突然报 CUDA out of memory。原因不是模型太大而是batch设大了加上某些批次的图像尺寸偏大如果开了多尺度训练。皮肤病照片分辨率差异大偶尔混进几张 4K 图就会爆显存。 解决训练前统一把所有图像 resize 到固定尺寸比如长边 640或者把batch降到 4 并开梯度累积。别指望框架自动处理尺寸差异提前统一最省心。6. 用验证集反查标注质量一个我每次都会跑的小脚本训练跑通只是开始真正决定模型上限的是标注质量。皮肤病数据集的标注尤其容易出问题因为皮损边界主观性强不同人画的框可能差出一大截。我后来养成了一个习惯每次拿到新数据集先不训练而是用验证集的可视化反查一遍标注把明显有问题的样本挑出来。具体做法是写一个脚本把验证集的图片和对应的 YOLO 标注框画在一起人工扫一遍。重点看三类框明显偏离皮损区域的、框把正常皮肤也圈进去的、以及同一张图里漏标的。这个步骤花半小时能省掉后面几小时的无用训练。import cv2 import os def draw_yolo_boxes(img_path, label_path, out_path): img cv2.imread(img_path) h, w img.shape[:2] if os.path.exists(label_path): with open(label_path) as f: for line in f: cls, cx, cy, bw, bh map(float, line.split()) # 反归一化回像素坐标 x1 int((cx - bw / 2) * w) y1 int((cy - bh / 2) * h) x2 int((cx bw / 2) * w) y2 int((cy bh / 2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(img, str(int(cls)), (x1, y1 - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2) cv2.imwrite(out_path, img)参数说明cx, cy, bw, bh是 YOLO 格式的归一化值反算像素坐标时先减半宽高得到左上角再加半宽高得到右下角。cls直接标在框左上角方便对照类别。跑完之后打开输出目录快速翻一遍如果发现某张图的框明显不对就去检查对应的 XML 或 txt 源文件大概率是转换时出的问题。除了可视化还有一个量化指标值得看每张图的平均框数量。如果某个类别的平均框数远高于其他类别可能是标注时把一个大区域拆成了多个小框也可能是这个类别确实多发。结合可视化一起判断比单纯看数字靠谱。从那以后我每次拿到新数据集都强制先跑一遍可视化再开训宁可前期多花半小时也不想在训练日志里猜到底是模型不行还是标注不行。希望帮到你。本文还有配套的精品资源点击获取