简介:面向yolo系列算法目标检测任务的妇科皮肤病数据集,覆盖黑色素瘤、猴痘、水痘、痤疮、疣、花斑癣、粉刺、银屑病、湿疹、癣共10类皮肤问题,适合医学图像检测研究、皮肤病辅助诊断模型训练以及yolo系算法的教学实战。数据集已按训练/验证/测试划分好,并附带data.yaml配置文件,可直接接入yolov5、yolov7、yolov8、yolov9、yolov10、yolo11等主流版本进行训练与验证,免去自行标注和分集的繁琐流程。压缩包总计2000个标注文件,以xml标注为主,同时提供yolo格式的txt标签和voc格式的xml标签,分别存放于不同文件夹,坐标信息均采用归一化的中心点与宽高表示,便于不同工具链读取和转换。资源整体约169.39MB,结构清晰,文件命名规范,适合快速上手实验。目前已有98人学习下载,对于需要标准医学皮肤病检测数据集的开发者或研究者而言,是一个完整且开箱可用的训练资源。
1. 用YOLO做妇科皮肤病检测,先看数据再谈模型
4418张图像、十类妇科皮肤病变标签,放到yolo目标检测流程里,第一件事不是调参,而是确认标签粒度。黑色素、猴痘、水痘、痤疮、疣、花斑癣、粉刺、银屑病、湿疹、癣这十类,很多在早期外观上只差一个色泽环和边界形态,全局分类网络容易把多病灶图像判成一类,而YOLO把每个病灶框出来再做分类,更贴近皮肤科医生的读图方式。不过妇科部位图像的光照、姿势、遮挡差异远大于公共数据集,训练时既要防模型记住背景,也要防类别不平衡导致少数类被吞掉。下面按数据准备、标签转换、训练参数、损失排查、推理迁移这条yolo目标检测流程展开,引用的命令和脚本用YOLOv8为例。
2. 妇科皮肤病数据集的构成与YOLO标签格式转换
拿到压缩包并解压后,先不要急着训练。打开标签文件确认它是COCO JSON、Pascal VOC XML还是Ultralytics的txt。妇科皮肤病数据集里的“黑色素”可能是色素痣也可能是黑色素瘤,“猴痘”和“水痘”的疱形态接近,标注规范直接决定后面几十轮训练是否白跑。
2.1 十类皮肤损伤的标注目标定义
下表按标题中的顺序给出一个通用映射,具体以解压后的names文件或类别编号为准:
| 类别ID | 标签名 | 常见外观特征 |
|---|---|---|
| 0 | melanoma | 不对称、边缘不规则、色泽不均 |
| 1 | monkeypox | 凸起水疱或脓疱,周围红晕 |
| 2 | chickenpox | 小而密集的水疱,疱壁薄 |
| 3 | acne | 红肿丘疹,常伴脓头 |
| 4 | wart | 表面粗糙的丘疹 |
| 5 | tinea_versicolor | 边界清楚、颜色不一斑片 |
| 6 | comedone | 黑头或白色闭合性粉刺 |
| 7 | psoriasis | 红色斑块,有银白色鳞屑 |
| 8 | eczema | 红斑、苔藓化、边界不清 |
| 9 | ringworm | 环形红斑,中央脱屑 |
注意,类别ID必须从0开始连续编号。如果原JSON里category_id从1开始,转换时要统一减一;如果原标签里顺序和标题顺序不一致,先写一个小字典映射到新ID,不要直接用原ID。
2.2 从原始标注到YOLO txt标签的转换脚本
常见做法是拿到COCO JSON标注,一个image_id对应一张图,一个annotation对应一个框。下面的脚本把它转成YOLO训练用的txt标签,转换思路和KITTI标注转YOLO时用的坐标归一化完全一致。
import json from pathlib import Path from collections import defaultdict def coco_to_yolo(coco_path: str, output_dir: str) -> None: with open(coco_path, encoding='utf-8') as f: coco = json.load(f) img_info = {img['id']: img for img in coco['images']} img_annos = defaultdict(list) for ann in coco['annotations']: img_annos[ann['image_id']].append(ann) out = Path(output_dir) out.mkdir(exist_ok=True) for img_id, anns in img_annos.items(): info = img_info[img_id] w, h = int(info['width']), int(info['height']) name = Path(info['file_name']).stem lines = [] for ann in anns: cat = ann['category_id'] - 1 # 原ID从1开始则减1 x, y, bw, bh = ann['bbox'] # 左上角坐标和宽高 # 裁剪到画面内部,避免越界 x2 = min(x + bw, w) y2 = min(y + bh, h) x = max(x, 0) y = max(y, 0) bw, bh = x2 - x, y2 - y if bw <= 2 or bh <= 2: # 过滤过小目标 continue cx = (x + bw / 2) / w cy = (y + bh / 2) / h nw = bw / w nh = bh / h lines.append(f"{cat} {cx:.6f} {cy:.6f} {nw:.6f} {nh:.6f}") (out / f"{name}.txt").write_text("\n".join(lines), encoding='utf-8')这段代码的要点是:category_id - 1用来把COCO从1开始的类别号转为YOLO从0开始;bbox的四个值是[x, y, width, height],其中x, y是框左上角;归一化使用图像宽高w, h做分母。bw <= 2的条件过滤掉只有几个像素的小框,这类框往往是人手标注的噪声,强行保留会让box_loss抖动。如果原标注是XML文件,把ann['bbox']换成解析bndbox后的xmin, ymin, xmax, ymax,公式不变,只需要先把x2-x算出宽高。
2.3 数据集目录结构与类别映射表
YOLO训练时不会自动识别图片和标签的关联,它要求图片放在images/train、images/val,同名txt放在labels/train、labels/val。目录结构如下:
gynecological_skin/ ├── images/ │ ├── train/ │ └── val/ ├── labels/ │ ├── train/ │ └── val/ └── gynecological_skin.yaml对应的data.yaml按下面的写法填写:
path: /absolute/path/to/gynecological_skin train: images/train val: images/val names: 0: melanoma 1: monkeypox 2: chickenpox 3: acne 4: wart 5: tinea_versicolor 6: comedone 7: psoriasis 8: eczema 9: ringworm如果原始压缩包没有划分train和val,可以先用train_test_split切分,固定随机种子保证可复现:
from sklearn.model_selection import train_test_split import glob, os, shutil imgs = sorted(glob.glob('raw/images/*.jpg')) train, val = train_test_split(imgs, test_size=0.2, random_state=42) for split, paths in [('train', train), ('val', val)]: os.makedirs(f'gynecological_skin/images/{split}', exist_ok=True) os.makedirs(f'gynecological_skin/labels/{split}', exist_ok=True) for p in paths: name = os.path.basename(p) stem = os.path.splitext(name)[0] shutil.copy(p, f'gynecological_skin/images/{split}/{name}') label = f'raw/labels/{stem}.txt' if os.path.exists(label): shutil.copy(label, f'gynecological_skin/labels/{split}/{stem}.txt')test_size=0.2在4418张图上大约留出884张验证图,比例合适;random_state=42保证重复执行时切分结果不变。复制标签文件时如果找不到同名txt,说明原始标注漏标,这种图片要么补标,要么移出训练集。
3. 训练YOLOv8妇科皮肤病模型的关键参数
把4418张图喂给YOLOv8之前,先决定用哪个模型体和优化策略。YOLOv8把anchor箱交给网络自适应学习,省掉了YOLOv5里手动聚类锚框的步骤,对病灶这种长宽比不固定的目标更省事。
3.1 为什么选YOLOv8而不是更早版本
从yolo算法发展角度看,YOLOv8使用C2f结构替换C3模块,特征融合阶段对小目标更友好;同时内置了mosaic、mixup、hsv扰动等数据增强,不用额外写增强代码。妇科皮肤病图像里病灶相对周围皮肤对比度不高,多尺度特征是否丰富直接决定小水疱能不能被找到。这里用yolov8n.pt作为起点,不会因为模型过大导致显存不足,训练一轮的时间较短,方便快速验证标签质量。如果显存足够,可以换成yolov8s.pt,精度通常会提升1到2个点。
3.2 yolov8训练自己的数据集的命令与yaml配置
安装ultralytics后,直接用命令行启动训练:
pip install ultralytics yolo train data=gynecological_skin.yaml \ model=yolov8n.pt \ epochs=120 \ imgsz=640 \ batch=16 \ lr0=0.001 \ optimizer=AdamW \ patience=20 \ cos_lr=True \ device=0data指向2.3节写的yaml;model填预训练权重路径,YOLOv8会下载对应参数;epochs=120对4418张图像来说足够,配合早停不会浪费太多时间。关键参数如下表:
| 参数 | 建议值 | 说明 |
|---|---|---|
| imgsz | 640 | 原图分辨率如果超过两倍,建议先归一化到640;小水疱多可上调到768 |
| batch | 16 | 显存小于8G时降到8,同时把lr0降到0.0005 |
| lr0 | 0.001 | 使用预训练权重时的常用初始值 |
| optimizer | AdamW | 小数据集上AdamW收敛比SGD稳 |
| patience | 20 | 验证集指标连续20轮不升就早停 |
| cos_lr | True | 余弦退火,减少后期震荡 |
训练过程中日志和断点会保存在runs/detect/train/。每次跑新实验前建议手动改一下project或name,比如project=runs_skin name=exp03,避免覆盖上一轮的权重。如果一轮训练后发现验证集mAP很低,先检查标签有没有错位,而不是盲目加epoch。
3.3 小样本类别不平衡的处理
4418张图不是平均分配给10个类别。常见分布是痤疮、湿疹、癣样本偏多,猴痘、黑色素偏少。先统计每个类别的框数量,再决定要不要做增强。
from pathlib import Path import collections def count_boxes(label_dir): counter = collections.Counter() for txt in Path(label_dir).glob('*.txt'): for line in txt.read_text().splitlines(): if line.strip(): counter[int(line.split()[0])] += 1 return counter counts = count_boxes('gynecological_skin/labels/train') print(counts)如果某个类的框数少于另一个类的一个零头,比如monkeypox总共不到50个框,模型很难学到稳定的特征。常见做法是仅对少类图像做离线增强,然后把这些增强副本追加到训练集中:
import cv2 from pathlib import Path from albumentations import Rotate, RandomBrightnessContrast few_images = ['raw/images/001.jpg'] # 从上一段统计中挑出少类图片 for img_path in few_images: img = cv2.imread(img_path) rotated = Rotate(limit=10, p=1.0)(image=img)['image'] bright = RandomBrightnessContrast(limit=0.2)(image=rotated)['image'] out = Path('augmented') / Path(img_path).name cv2.imwrite(str(out), bright)增强副本复制时,标签txt也要复制成同名文件。注意不要用垂直翻转,私密部位图像上下翻转后会改变解剖结构的方向语义,模型容易学到错误特征;水平翻转在皮肤科通常可接受。复制数量控制在原样本的1到2倍,过量复制会让训练每个epoch都反复看同一张图,局部过拟合反而拉低验证集AP。
4. 读懂yolo损失函数并排查训练异常
训练跑到几十轮后,不能只看mAP有没有涨。YOLOv8输出的损失曲线隐藏了很多标签质量和数据分布的信息,学会读它,排错会快很多。
4.1 YOLOv8损失曲线的几个分量
yolo损失函数由三部分组成:box_loss使用CIoU衡量预测框与真实框的位置误差,cls_loss使用BCEWithLogitsLoss做多标签分类,dfl_loss处理边界框分布的离散化误差。结果目录里的results.csv记录了每个epoch的训练和验证分量:
import pandas as pd import matplotlib.pyplot as plt df = pd.read_csv('runs/detect/train/results.csv') df.columns = [c.strip() for c in df.columns] plt.plot(df['epoch'], df['train/cls_loss'], label='train_cls') plt.plot(df['epoch'], df['val/cls_loss'], label='val_cls') plt.legend() plt.savefig('cls_curve.png')第一行读入的列名会带大量前导空格,必须strip一次再取列。重点看验证集分类损失,它比train/cls_loss更早暴露过拟合。如果val/cls_loss在前10轮快速下降后突然回升,说明模型开始记忆皮肤纹理和光影,这种时候首先要降低lr0,其次是减少mosaic概率或关闭mixup。
4.2 标注漏检与标签噪声的排查
妇科皮肤病数据集中有些图像只框了最大的病灶,旁边的小丘疹没框。这种漏标对YOLO的伤害比错标更大,因为漏检区域会变成假负样本,模型被迫学会“这里没有目标”。先用一张图一个框的统计把异常图片找出来:
from pathlib import Path box_counts = [] for txt in Path('gynecological_skin/labels/val').glob('*.txt'): c = sum(1 for line in txt.read_text().splitlines() if line.strip()) box_counts.append((txt.name, c)) for name, c in sorted(box_counts, key=lambda x: x[1])[:20]: if c == 0: print(name, c)这份代码会列出验证集里标签完全为空的图片。如果一张图里确实一个病灶都没有,它作为背景样本没问题;但更多情况是漏标,打开对应图片看一遍就知道。另一种常见错误是所有标签的框宽高都等于原图尺寸,这说明原始的bbox用的可能是中心点坐标[cx, cy, w, h],被误读成了[x, y, w, h],转换时要把前半段做x = cx - w/2的偏移。
4.3 用验证集PR曲线决定阈值和增强策略
训练结束后,跑一次标准验证:
yolo val model=runs/detect/train/weights/best.pt \ data=gynecological_skin.yaml \ conf=0.25 \ iou=0.5conf=0.25是判断正负样本的置信度阈值,iou=0.5是NMS和mAP计算时的IoU阈值。验证输出会生成PR_curve.png和confusion_matrix.png。看混淆矩阵时,注意黑色素类和疣类是否互相误检,这两类颜色接近,如果互相混淆很严重,考虑给它们补充边界更清晰的难例样本。
val/cls_loss很低但mAP不高,往往不是训练问题,而是数据集类别定义重叠。临床上湿疹和银屑病早期本就难以区分,让模型输出上保留这两个类,还是合并成一个“红斑鳞屑类”,需要和标注者确认。如果决定合并,第三个类别对应的txt里的class数字也会变,这是调整标签而不是调整网络结构,简单改映射文件比重新训练更快。
5. 用训练好的YOLO权重做妇科皮肤病推理与迁移
训练完成后,best.pt不只是用来做检测,也可以作为新数据集的预训练权重,减少从头训练的成本。
5.1 单张图像推理Python脚本
加载权重并输出每个框的类别、置信度和坐标:
from ultralytics import YOLO model = YOLO('runs/detect/train/weights/best.pt') results = model.predict(source='test.jpg', conf=0.3, imgsz=640, save=True) for r in results: for cls, conf, xyxy in zip(r.boxes.cls, r.boxes.conf, r.boxes.xyxy): print(model.names[int(cls)], round(float(conf), 2), xyxy.tolist())conf=0.3比默认0.25高,适合对假阳性敏感的筛查场景;imgsz=640必须和训练一致,否则会先resize再推理,影响小病灶定位。save=True会把画了框的结果图存到runs/detect/predict/,方便直接和原图做对比。
5.2 新增类别时保留旧病种的微调技巧
妇科皮肤病数据集经常会补一批新的标记图像进来,比如新增“脂溢性皮炎”。直接在best.pt上从头训练会覆盖旧类别。常见做法是冻结backbone的前10层,让底层特征保持不变,只更新检测头,最后再解冻整个网络做低学习率精调:
yolo train data=gynecological_skin_v2.yaml \ model=runs/detect/train/weights/best.pt \ freeze=10 \ epochs=50 \ lr0=0.0005freeze=10代表冻结模型前10层,这一步能大幅降低新类别对旧类别特征的破坏。迁移阶段建议把旧数据保留20%到30%参与训练,相当于场景回放,防止灾难性遗忘发生。如果新增类别和已有类别外观很相似,比如“脂溢性皮炎”和“湿疹”都表现为红斑,不要把它们分到两个太近的类,可以在标注阶段先合并成一个父类,等新样本积累到每类100个框以上再拆开。训练完成后用5.1的脚本跑一遍旧类别的验证集图片,逐一确认旧病种的检出没有明显退化。
本文还有配套的精品资源,点击获取