深色皮肤皮肤病YOLO数据集训练实战:小样本迁移学习与避坑指南
发布时间:2026/9/28 15:49:38来源:尧图网络
简介面向yolo系列目标检测算法学习者的深色皮肤皮肤病检测数据集聚焦白糠疹、炎症后色素沉着过度、特发性黑色素沉着症、汇流和网状白斑与白癜风五类病变可用于医学图像分析和皮肤疾病识别实验。压缩包共703个文件大小仅9.16MB含234张jpg图像及配套的yolo格式txt标签与voc格式xml标签另附data.yaml配置文件可直接适配yolov5、yolov7、yolov8、yolov9、yolov10和yolo11算法。txt标签采用归一化的中心点坐标与宽高记录目标框xml则保留VOC标准结构两种格式分目录存放方便在不同训练框架间切换数据集已经划分好训练集与验证集免去自行分组的步骤。目前已有60人学习下载适合希望快速体验目标检测全流程、开展皮肤病自动识别研究的学生与算法工程师。通过该资源可同时获取图像样本、双格式标注及配置方案省去数据采集与标注整理的重复劳动让模型训练开箱即用。1. 深色皮肤数据集为什么是 yolo 算法落地皮肤病检测的第一道坎拿到这个 zip 的人第一反应往往是“234 张图也太少了”。但真正卡住 yolo 算法落地的不是数量而是“深色皮肤”这四个字。主流开源皮肤病数据集大多以浅肤色为主深肤色样本在公开资源里长期稀缺而同一类皮肤病在深色皮肤上的对比度、色相和边界形态都会变直接套用 ImageNet 预训练权重做迁移学习检测框经常飘。这个数据集把 234 张带标签图像按 YOLO 格式打包覆盖白糠疹、炎症后色素沉着过度、特发性黑色素沉着症、汇流和网状乳头瘤病、白癜风五类适合做皮肤病辅助筛查研究的算法工程师、医学影像方向的学生以及想验证小样本医学检测可行性的开发者。接下来我会把它拆开讲清楚这套数据怎么组织、yolo 怎么训、参数怎么设、坑在哪。2. 拆开 zip 看家底YOLO 标注格式、目录结构与五类标签的语义2.1 images 与 labels 目录YOLO 数据集的标准姿势这个 zip 解压后常见做法是里面直接放着images和labels两个顶层目录再往下按train、val可能还有test划分。无论作者原包怎么组织你自己训练前都要先把它归整成 YOLO 能识别的标准结构否则 ultralytics 的DataLoader会报路径找不到。标准结构长这样dataset/ ├── images/ │ ├── train/ │ │ ├── 001.jpg │ │ └── ... │ └── val/ │ ├── 002.jpg │ └── ... ├── labels/ │ ├── train/ │ │ ├── 001.txt │ │ └── ... │ └── val/ │ ├── 002.txt │ └── ... └── data.yaml每张jpg对应一个同名txt没有标注框的图可以不生成 txt但训练时 ultralytics 会跳过无标签图。txt里每一行代表一个目标框格式固定为class_id x_center y_center width height其中x_center y_center width height全部是相对图像宽高的归一化坐标取值 0 到 1不是像素值。比如一张 1000x800 的图某个病灶框左上角在 (200, 160)宽 300高 200那么这行应该是2 0.35 0.325 0.30 0.25class_id从 0 开始计数。我之前见过有人把分类号从 1 开始写训练时 loss 直接不降。拿到 zip 后建议先用一个脚本把所有标注读一遍确认类别 id 分布和坐标范围是否合法。下面这个脚本可以直接跑import glob import os label_dir dataset/labels/train class_count {} total_boxes 0 invalid_lines 0 for txt_path in glob.glob(os.path.join(label_dir, *.txt)): with open(txt_path, r) as f: for line in f: parts line.strip().split() if len(parts) ! 5: invalid_lines 1 continue cls_id int(parts[0]) x, y, w, h map(float, parts[1:]) if not (0 x 1 and 0 y 1 and 0 w 1 and 0 h 1): print(f坐标越界: {txt_path} - {line.strip()}) class_count[cls_id] class_count.get(cls_id, 0) 1 total_boxes 1 print(各类别框数量:, class_count) print(总框数:, total_boxes) print(非法行数:, invalid_lines)这段代码的作用是给整个数据集做“体检”。class_count能直接反映五个类别是否均衡这决定了后面要不要做类别加权invalid_lines检查的是有没有空行、缺列或者坐标越界。YOLO 对越界坐标的容忍度很低越界的框在mosaic增强时会被直接丢弃或裁坏训练出来的模型对边缘病灶的召回率会明显变差。如果发现原包没有按train/val划分你需要自己切。234 张图属于典型小样本我一般按 8:2 或 7:3 切并且保证每个类别在训练集和验证集里的比例和全集一致也就是分层划分。不要用随机shuffle一把梭皮肤病数据集里同一个病人的多张照片往往相邻排列随机划分会让验证集里出现和训练集几乎一样的图mAP 虚高。2.2 五类疾病标签的视觉边界与易混点标注质量决定了 yolo 算法能学到什么。这五类病在临床上有明确病理差异但在图像特征上有不少重叠。如果你不是皮肤科医生建议先按下面的表建立视觉判别基准再去看数据集的标签是否可信。这里只谈图像特征不构成临床诊断。类别典型视觉特征易混对象白糠疹边界不清的浅色斑表面有细微糠状脱屑常见于面部白癜风早期、炎症后色素减退炎症后色素沉着过度边界相对清晰的深褐色斑多在外伤或炎症愈后出现特发性黑色素沉着症、黄褐斑特发性黑色素沉着症小米粒到黄豆大的点状色素沉着或减退斑散在分布白糠疹、日光性黑子汇流和网状融合成片的褐色斑中央融合、边缘呈网状纹理好发于躯干炎症后色素沉着过度白癜风边界清楚的瓷白色斑完全脱色无脱屑白糠疹、特发性色素减退从图像算法角度看最大的坑是白糠疹和白癜风。两者都表现为“浅色斑”但白癜风的边界要锐利得多常常是完整的圆弧或地图状边缘颜色接近纯白白糠疹的边界是渐进过渡的而且表面有纹理。YOLO 这类回归框的模型对边界锐利度的敏感度远低于像素级分割模型所以训练时经常把白糠疹的框和白癜风框混在一起。你在做标签 check 时重点看这两类的框是否严格框住了病灶边界如果标注框比病灶大了一圈等于把背景纹理也学进去了。“汇流和网状”这个类别名容易看懵英文对应confluent and reticulated papillomatosis它的图像特点是中央融合成片、边缘像网状或鱼鳞状延伸。这类病灶往往面积大、形状不规则标注时常常需要多个框一个框框不全。如果你发现某个txt里同一张图有七八个框大概率是这个类别不要觉得是标注错了。另外要注意“特发性黑色素沉着症”这个名字在不同翻译版本里可能对应不同疾病。以这个 zip 里的标签为准如果标签文件中该类的图像表现出的是点状白斑那你训练时就把该类当成“点状色素异常”处理别去纠结中文病名。算法项目里类别的视觉一致性比名称准确性更重要——只要标注的图像内部一致yolo 就能学会。3. 用 YOLOv8 把这 234 张图跑成可用的检测模型3.1 先把目录理顺从 zip 解压到 data.yaml拿到 zip 后第一步不是急着训练而是把目录结构统一。因为不同来源的数据集目录命名五花八门有的叫JPEGImages有的叫Annotations还有的标注是 XML 不是 txt。这里假设包内已经是 YOLO 格式我们只需要解压并确认划分。命令如下unzip yolo_pigmented_dataset.zip -d ./skin_dataset cd ./skin_dataset # 如果没有 val 目录按 8:2 划分 train/val mkdir -p images/val labels/val如果原包把标注和图像混在一个目录里你需要写个小脚本分拣。我之前处理过一个数据集它的 txt 和 jpg 全部平铺在一个文件夹是用下面这个 Python 脚本整理的import os import shutil src_dir raw dst_images_train dataset/images/train dst_labels_train dataset/labels/train os.makedirs(dst_images_train, exist_okTrue) os.makedirs(dst_labels_train, exist_okTrue) for fname in os.listdir(src_dir): if fname.endswith(.jpg): base os.path.splitext(fname)[0] txt_name base .txt txt_path os.path.join(src_dir, txt_name) if os.path.exists(txt_path): shutil.copy(os.path.join(src_dir, fname), dst_images_train) shutil.copy(txt_path, dst_labels_train)这个脚本的逻辑很简单先把图像文件复制到images/train再把同名 txt 复制到labels/train没有同名 txt 的图直接跳过。这样能保证图像和标签严格一一对应。注意这里用的是copy而不是move因为原始文件可能还有别的用途等确认没问题再删。然后创建data.yaml这是 ultralytics 训练时的数据集描述文件path: ./skin_dataset train: images/train val: images/val nc: 5 names: 0: pityriasis_alba 1: post_inflammatory_hyperpigmentation 2: idiopathic_guttate 3: confluent_reticulated 4: vitiligonc是类别总数必须和 labels 里的 class_id 最大值 1 一致。names的顺序要和标注时一致这个顺序错了训练不会报错但推理出来的类别会张冠李戴。如果你不确定原数据集的类别顺序去翻 labels 目录里出现过的 class_id不要凭猜。3.2 最小训练命令yolo detect train 的参数怎么设目录理顺后装 ultralytics 包并跑最小训练命令。这里以 YOLOv8 为例因为它对自定义数据集的支持最省事一行命令就能训pip install ultralytics yolo detect train \ dataskin_dataset/data.yaml \ modelyolov8n.pt \ epochs100 \ imgsz640 \ batch8 \ patience20 \ device0这条命令用的是 YOLOv8n 预训练权重输入尺寸 640训练 100 轮batch 8。device0表示用第一张 GPU没有 GPU 就改成devicecpu但 234 张图在 CPU 上跑 100 轮会比较久建议先用小模型缩小验证时间。modelyolov8n.pt会自动从官方地址下载预训练权重这就是常说的“yolo预训练模型下载”不用手动去官网翻。如果网络不稳定也可以先手动下载 yolov8n.pt 放到当前目录再把它当成model参数传入。几个参数的直觉imgsz640是训练时送入网络的图像尺寸。深色皮肤数据集的病灶往往不大如果原图很多是 3000x4000 的手机照片640 会丢失大量细节。等第一轮训完可以试着提到 960 或 1280 观察 mAP 变化。batch8取决于显存。YOLOv8n 在 640 下 batch 8 大约需要 6-8 GB 显存小显卡可以用 batch4 加accumulate4达到等效效果。patience20表示如果验证集损失连续 20 轮不下降就早停。小数据集跑不满 100 轮很正常可能 50 轮就停了。epochs100是上限不是必须跑满。后面我会专门说早停和过拟合的取舍。3.3 预训练权重选型与迁移学习小样本医学图像训练最关键的决定是用哪个规模的预训练模型。YOLOv8 系列从 n、s、m、l、x 依次变大参数越来越多。很多人一上来就选yolov8x.pt觉得模型越大越准。但在 234 张图的规模下大模型必然过拟合训练集 loss 能降到 0.01验证集 mAP 却一动不动。我的建议是优先yolov8n.pt或yolov8s.pt。YOLOv8n 参数量约 320 万特征提取能力对单类小目标检测够用如果验证集 mAP 稳定在 0.7 以上再尝试用yolov8s.pt微调看有没有提升。每次换模型都在同一套划分下训练否则对比没有意义。另外一个常用技巧是冻结 backbone 的前几层。迁移学习时浅层学到的是边缘、颜色、纹理等通用特征对医学图像同样有效深层的语义特征才和具体任务强相关。ultralytics 支持freeze参数yolo detect train \ dataskin_dataset/data.yaml \ modelyolov8n.pt \ epochs100 \ imgsz640 \ batch8 \ freeze5freeze5表示冻结前 5 层按 ultralytics 的层编号backbone 大约对应前 10 层。这个值不用精确冻结 5 到 10 层是为了让模型在训练初期不破坏已经学好的底层特征。小数据集上冻结层数多一点训练更稳但如果你发现 loss 迟迟不降说明底层特征可能不适合你的域可以调小freeze或直接设为 0。训练结束后runs/detect/train/目录下会生成weights/best.pt和last.pt。best.pt是验证集指标最好的权重last.pt是最后一轮的权重。小数据集上二者差别可能很大因为验证集本身就有波动我一般只信best.pt并且会拿它去跑独立的测试图验证而不是只看训练日志。4. 小样本医学图像的调参类别不平衡、损失函数与混淆矩阵4.1 五类样本数不均等时先看 counts 再谈训练234 张图带标签不代表五类各平均 47 张。皮肤病数据集里炎症后色素沉着过度往往样本最多汇流和网状最少。如果你直接拿原始分布训练yolo 的损失会被大头类别主导稀有类别即使 recall 是 0mAP 也可能很难看。所以训练之前先跑一遍 2.1 那个统计脚本拿到每类的框数。然后看比例如果最大类和最小类相差超过 5 倍就需要做类别平衡。常见做法有两种一是过采样稀有类别的图片二是修改损失权重。YOLOv8 没有像分类任务那样直接暴露class_weights配置项但 ultralytics 的损失计算会根据每个 batch 内类别的出现频率做一定的内置平衡。你可以先不做任何操作训一轮拿到混淆矩阵后再决定。yolo 的损失函数由三部分构成边界框回归损失box loss、分类损失cls loss和分布焦点损失dfl loss。在小样本场景下cls_loss最容易受类别不平衡影响。如果你发现稀有类别的 recall 明显低可以手动给稀有类别复制几份增强后的图像比如把原图左右翻转、上下翻转、旋转 90 度后重新生成标签加入训练集。注意翻转后的标签坐标要重新算不能直接复制 txt。import cv2 import numpy as np def flip_label(w, h, x, y, bw, bh, directionhorizontal): if direction horizontal: x_new 1.0 - x return x_new, y, bw, bh else: y_new 1.0 - y return x, y_new, bw, bh这个函数演示了水平翻转时归一化中心坐标x变成1-xy不变。为什么不是w变因为 YOLO 坐标是中心点加宽高水平翻转后宽高本身不变变的只有中心点的水平位置。如果你用 OpenCV 的cv2.flip翻图像就一定得同步改标注否则训练时模型会学到“病灶永远在右侧”这种错误先验。上下翻转同理只动y。还有一种更省事的方式在 ultralytics 训练参数里开增强。hsv_h、hsv_s、hsv_v控制颜色扰动degrees10控制小角度旋转flipud0.0默认关闭垂直翻转可以按需打开。但医学图像对颜色语义敏感hsv 扰动幅度要小默认值 0.015 还行不要往上加太多。4.2 混淆矩阵解读白糠疹与白癜风为什么总被混在一起训练完第一轮你最该看的不是 mAP而是runs/detect/train/confusion_matrix.png。这张图能直接告诉你模型把哪两类搞混了。我之前训过一个类似的数据集最大的混叠出现在白糠疹和白癜风之间其次是特发性黑色素沉着症和白糠疹。原因是这三类在图像上都表现为“浅色区域”而且白糠疹和白癜风早期在 640x640 的缩放下边界差异很小。看混淆矩阵时有个细节经常误导人矩阵每一行是独立归一化的也就是说每一行的和是 100%但整张矩阵所有格子的和不等于 1。这就是有人发现“yolo混淆矩阵总合不唯一”的原因。你读的时候应该按行看——第 i 行第 j 列表示“真实类别 i 被预测成 j 的比例”。如果某行对角线不到 50%说明这个类别基本没学会。例假设白糠疹那行有 40% 被分到白癜风那你要做的不是加训练轮数而是回头检查标注边界是否太随意。模型学到的边界是标注边界的投影标注把白糠疹框得太大背景的深色皮肤被包进框里模型会认为“浅色斑 部分深色背景”就是白糠疹特征反而把整个浅色区域当成了正样本。另外要看results.png里的val_cls_loss曲线。如果val_cls_loss在训练后半程反弹说明模型开始把训练集的噪声细节死记硬背下来这是过拟合的典型信号。这个时候加weight_decayultralytics 里叫wd默认 0.0005到 0.001或者降低lr可能比增加数据更管用。4.3 数据增强的边界mosaic、HSV 与软标签YOLOv8 默认开了mosaic1.0也就是训练时把 4 张图拼成一张。对于自然场景目标检测mosaic 能显著提升小目标性能但在医学图像上要谨慎。皮肤病病灶的大小、颜色、纹理都有临床意义拼接出来的图在病灶边缘会出现不自然的断层模型可能学到“跨图边界”这个伪特征。我倾向把mosaic调低到 0.3 或直接关闭尤其是最后一轮微调时可以设成 0。# data.yaml 同级目录可建一个 train_override.yaml 覆盖默认增强 mosaic: 0.3 hsv_h: 0.01 hsv_s: 0.2 hsv_v: 0.2 degrees: 5 translate: 0.1 scale: 0.3 flipud: 0.0 fliplr: 0.5 close_mosaic: 10close_mosaic: 10表示最后 10 轮关闭 mosaic让模型在正常构图下做微调这是 ultralytics 内置的行为但默认值有时候不够小数据集我会手动调大。translate和scale保持小幅度因为病灶在皮肤上的位置和尺寸有一定诊断意义平移太多等于告诉模型病灶可以长在任何地方其实不对。“软标签”这个概念在分类里常用YOLOv8 的分类损失默认用硬标签。小数据集上硬标签容易让模型对边界极度自信导致过拟合。你可以通过设置label_smoothing0.1来缓解yolo detect train \ dataskin_dataset/data.yaml \ modelyolov8n.pt \ epochs100 \ imgsz640 \ batch8 \ label_smoothing0.1label_smoothing0.1会把 one-hot 标签里的 1 变成 0.9其余 0 变成 0.1/nc 的均匀小值。这样模型不会过度依赖某个类别的绝对信号。缺点是在类别本身容易混淆时软标签可能进一步让白糠疹和白癜风更难分。如果你发现混淆矩阵里这两类的交叉比训练前还高就把它关掉。5. 避坑小数据集训练 yolo 最常见的 5 个翻车现场5.1 类别 id 错位loss 曲线正常但预测全错现象训练 loss 下降很漂亮mAP 也不低但拿一张只含白癜风的图去预测输出的却是“白糠疹”。原因data.yaml里names的顺序和标注 txt 里的 class_id 对应错了。标注时 0 代表白糠疹你的 yaml 里 0 却写成了白癜风模型学到的特征和名字对不上。解决训练前做一次最简单的 sanity check——挑一个类别的几张图和对应 txt打印出类别 id然后对照 yaml 里的 names 确认。如果你已经训完了可以用下面脚本快速检查 best.pt 在单张图上的输出类别from ultralytics import YOLO model YOLO(runs/detect/train/weights/best.pt) results model.predict(val_images/001.jpg, conf0.25, verboseFalse) for r in results: for box in r.boxes: cls_id int(box.cls[0]) print(fclass_id{cls_id}, name{model.names[cls_id]}, conf{box.conf[0]:.2f})这段代码会打印每个检测框的类别 id 和对应的 names。如果 id0 输出了 “vitiligo”那你 yaml 里的 names 顺序肯定错了。别急着重训直接改 yaml 并重新用 best.pt 预测即可。5.2 imgsz 太大导致显存溢出太小导致病灶变成几个像素现象训练时直接报CUDA out of memory或者训练正常但验证集上小病灶全漏检。原因234 张图的原始分辨率可能很高比如 3000x4000。imgsz640会把整张图缩到 640一个直径 100 像素的病灶缩完后只有约 21 像素难以检测imgsz1280又会让 batch8 显存爆炸。解决先用imgsz640训一个 baseline然后用imgsz1280做第二轮微调。YOLOv8 支持训练和推理尺寸不一致你甚至可以训 640、推理 1280。如果显存不足用batch4加accumulate8模拟大 batch再把imgsz提到 960通常是一个折中方案。不要直接用 1280 开局小数据集在大分辨率下很容易把背景纹理也学进去。5.3 EXIF 旋转导致标签错位训练 loss 正常但验证集 mAP 剧跳现象训练集 mAP 稳定在 0.8验证集 mAP 在 0.2 到 0.7 之间疯狂跳动不同轮次差异巨大。原因手机拍摄的深色皮肤照片常带 EXIF orientation 信息。OpenCV 的imread默认不处理 EXIF 旋转而 YOLO 训练时读取图片经过exif_transpose处理。如果原包里的jpg已经有旋转标记但你的标签坐标是基于旋转前的像素算的验证时图像被自动转正标注框就全偏了。解决训练前做一个预处理把带 EXIF 旋转的图先转正并保存。用 PIL 批量处理然后重新生成标注。注意转正的宽高可能互换所有归一化坐标要基于转正后的宽高重新计算不能直接沿用旧 txt。这个坑很隐蔽因为训练集 loss 仍然能降——模型在拟合“旋转后的错误标签”验证集因为同样做了随机增强偶尔能碰上对的情况mAP 就抖得厉害。5.4 验证集 mAP 虚高同一病灶的重复图泄漏现象best.pt 在验证集上 mAP 0.75但拿手机新拍的照片一测召回率低得离谱。原因小数据集常包含同一病灶的不同角度、不同光照照片这些“相似但不同”的图被随机分到了 train 和 val。模型在训练时见过几乎一样的验证图等于开卷考试。解决按“病人”或“病灶”划分数据集而不是按“张”。如果你不知道原始分组可以用图像感知哈希或 embedding 相似度做去重把余弦相似度超过 0.9 的图片归到同一组整组放入 train 或 val。这样评估才真实。训练前检查一下 val 里有没有和 train 高度相似的图这比调参更重要。5.5 早停 patience 设错模型还没收敛就被掐死现象训练日志显示EarlyStopping触发在第 18 轮但 val_loss 曲线在 25 轮之后还有明显下降空间。原因小数据集 大 imgsz 时一个 epoch 看到的样本太少val_loss 波动大。patience20可能在第 8 轮出现一次低点后震荡然后第 18 轮就触发早停错过了第 30 轮的真正收敛点。解决把patience设成 50 或 60并且同时盯results.png里的val_box_loss。如果 val_loss 在 50 轮后仍然每 10 轮下降一点点说明还没收敛可以手动把 epoch 拉长。更靠谱的做法是保留每个 epoch 的权重而不是只靠 best.pt。ultralytics 默认每轮都存 last.pt你可以用last.pt回退到某个中间轮次的模型做验证相当于后悔药。小数据集上我通常训练完先不删runs目录等手工抽检后再清理。6. 把模型用到真实照片上置信度阈值、导出 ONNX 与人工抽检训练完 best.pt 只是第一步真正考验在真实照片上的表现。小数据集模型对置信度的估计通常不准确不能直接沿用默认的conf0.25。我会先拿 10 张没参与训练的真实照片用不同阈值跑一遍观察漏检和误检的平衡yolo detect predict \ modelruns/detect/train/weights/best.pt \ source./real_photos \ conf0.15 \ iou0.5 \ saveTrueconf0.15是为了先把所有候选框都打印出来宁可多检不可漏检。如果这个阈值下误检太多再提到 0.3。我见过有些皮肤病检测项目最后部署时用conf0.4因为误检的代价比漏检高但做筛查场景我倾向于 0.2 左右让医生在结果里二次筛选。推理速度敏感的场景导出 ONNX 是标准操作yolo export modelruns/detect/train/weights/best.pt formatonnx imgsz640导出本身很简单但要记得imgsz必须和你推理时的输入尺寸一致否则某些算子会被优化掉精度会变。导出后用onnxruntime跑一遍同一张图对比 PyTorch 的结果置信度差异超过 0.05 就要查预处理代码是不是不一致。我自己的习惯是无论 mAP 多好看最后都要手动翻一遍检测结果图。把预测框画在原始分辨率上重点看两类图一是白糠疹和白癜风邻近出现的图二是病灶在图像边缘、被截断的图。这两个场景最能暴露小样本模型的短板。皮肤病检测的难点不在模型结构而在数据边界——标注是否一致、划分是否真实、阈值是否匹配。这个数据集的价值正在于它逼你把这些问题提前暴露出来。如果你能跑完这一整套流程后面再来 2000 张图也只是把同样的流程放大而已。希望帮到你。本文还有配套的精品资源点击获取
网站建设高端定制企业官网