简介:本资源是一份面向计算机视觉初学者与深度学习实践者的脸部皮肤病目标检测数据集,聚焦粉刺、丘疹、结节、脓疱四类常见痤疮病变识别任务,适用于YOLO系列模型训练及PASCAL VOC格式算法验证。压缩包共2000个文件,含1590张清晰原始JPEG图像、1590份VOC标准XML标注及410份YOLO格式TXT标签(对应全部1590张图),总大小35.93MB,结构规整为JPEGImages/Annotations/labels三级目录,便于直接接入主流检测框架。已有100人下载学习,适合作为课程设计、毕设项目或Kaggle式入门实战的数据基础。资源附带说明文档,标注严格遵循矩形框规范,总标注框达8827个,覆盖各类病灶形态分布,可直接用于数据加载、类别统计、可视化分析及模型baseline构建,显著降低皮肤病检测方向的数据准备门槛。
1. 项目概述:从零构建一个专业级皮肤病检测数据集
最近在做一个关于皮肤健康管理的项目,核心需求是开发一个能自动识别常见脸部皮肤问题的工具。市面上虽然有不少公开数据集,但要么类别不全,要么标注格式不统一,直接拿来用总是差点意思。特别是想用YOLO这类主流目标检测框架时,找到一个标注规范、质量上乘的VOC格式数据集更是难上加难。所以,我决定自己动手,从零开始构建一个专门针对脸部皮肤病的检测数据集。
这个数据集的核心目标,是服务于基于YOLO算法的目标检测模型训练。为什么选择YOLO?因为它速度快、精度高,非常适合部署在移动端或边缘设备上,实现实时的皮肤问题初步筛查。而选择VOC格式,则是考虑到其广泛的兼容性。PASCAL VOC数据集格式是计算机视觉领域一个非常经典的标准,几乎所有的深度学习框架(PyTorch, TensorFlow, PaddlePaddle)和目标检测代码库(如MMDetection, Detectron2, 以及YOLO官方版本)都提供了对VOC格式的直接支持或便捷的转换工具。用VOC格式标注,意味着你的数据在未来具有极强的可移植性和复用性。
这个数据集将聚焦于几种最常见、也最影响“面子工程”的脸部皮肤病,例如痤疮(痘痘)、色斑、玫瑰痤疮、脂溢性皮炎等。它不仅适合计算机视觉和机器学习的研究者、开发者用来训练和验证模型,对于医疗AI的入门者、希望将AI技术应用于健康领域的创业者,也是一个非常不错的练手项目。接下来,我将详细拆解整个数据集构建的全过程,包括设计思路、数据采集与处理、精细标注的实操细节、格式转换的技巧,以及我踩过的那些坑和总结出的宝贵经验。
2. 数据集整体设计与核心思路拆解
构建一个高质量的数据集,远不止是收集图片和画框那么简单。它是一项系统工程,需要在源头就做好顶层设计,这直接决定了未来训练出的模型上限。
2.1 需求定义与类别体系确立
第一步是明确我们要检测什么。脸部皮肤病种类繁多,但并非所有都适合或有必要通过视觉检测。我的设计原则是:常见、可视、有临床区分度。
常见性:优先选择高发病率的病症,确保数据源充足且模型有实际应用价值。我最终确定了四个核心类别:
acne:痤疮。包括黑头、白头、丘疹、脓疱等多种形态,是标注的重点和难点。nevus:色素痣。通常是边界清晰的深色斑点。melasma:黄褐斑。成片的、边界模糊的褐色斑片。rosacea:玫瑰痤疮。主要表现为面中部红斑,有时伴有丘疹、脓疱,需要与痤疮区分。
可视性:确保病症在常规可见光照片下有明显的视觉特征。一些需要触诊或特殊检查(如皮肤镜)的病症暂不纳入。
区分度:类别之间在图像特征上应有可区分的差异,避免给模型带来混淆。例如,将“炎性痤疮”和“非炎性痤疮”合并为
acne,但将特征迥异的melasma单独列出。
注意:类别名称全部使用英文小写,避免空格,这是为了后续文件处理和模型标签读取的便利性,一个微小的细节能避免很多不必要的麻烦。
2.2 数据来源策略与伦理考量
数据来源的多样性和质量是生命线。我采用了多源采集策略,并严格遵守伦理规范:
公开数据集筛选与再标注:从DermNet、ISIC Archive等皮肤病公开图像库中,筛选出符合我们类别定义的脸部特写图片。关键点:这些数据集通常提供诊断标签,但没有我们需要的目标检测边界框。因此,它们是我们重要的图片来源,但标注工作需要从头做起。
模拟拍摄与可控环境数据:为了增加数据多样性并控制质量,我在征得同意后,邀请志愿者在光线均匀、背景简单的环境下拍摄脸部照片。使用高分辨率手机或相机,确保图像清晰。这部分数据价值极高,因为背景干净,标注干扰少。
网络爬取的审慎使用:通过搜索引擎,使用特定关键词(如“脸部痤疮特写”、“黄褐斑图片”)爬取相关图片。这是风险最高的一环,必须极度谨慎:
- 版权:仅用于个人研究和学习,不涉及商用。最终数据集不会公开分发原始图像,仅分享标注文件。
- 隐私:所有爬取图片均需人工审核,确保不包含可识别个人身份的信息(如独特的痣、纹身、背景中的门牌号等),必要时进行模糊处理。
- 质量:过滤掉低分辨率、水印过大、角度过于怪异或经过重度美颜处理的图片。
2.3 标注格式选型:为什么是VOC?
如前所述,VOC格式是经典之选。一个VOC格式数据集的目录结构通常如下:
VOCdevkit/ └── VOC2024/ # 年份或自定义数据集名 ├── Annotations/ # 存放XML标注文件 ├── ImageSets/ │ └── Main/ # 存放训练集、验证集、测试集的列表文件 └── JPEGImages/ # 存放所有原始图像每个XML文件(如000001.xml)对应一张图片(000001.jpg),其中详细记录了图片大小、物体类别和边界框坐标。这种结构清晰,且与绝大多数训练代码的默认数据读取接口匹配。
虽然YOLO官方更常使用其自定义的.txt格式(每行class_id x_center y_center width height,坐标已归一化),但先标注成VOC再转换,有两大好处:一是可以利用更多成熟的标注工具(如LabelImg),它们对VOC支持最好;二是保留了一份坐标信息更丰富(像素绝对值)、可读性更强的“源文件”,方便后续的检查和调整。
3. 数据采集、清洗与预处理实操要点
有了设计图,接下来就是“施工”。数据准备阶段的工作量占整个项目的70%,其质量直接决定模型性能的天花板。
3.1 图像采集与初步清洗
采集到的原始图像是“毛坯房”,需要经过仔细的清理才能使用。
统一命名规则:将所有图像文件按顺序重命名,如
000001.jpg,000002.jpg... 这能避免因文件名含空格、中文或特殊字符导致的程序读取错误。我写了一个简单的Python脚本批量处理:import os from pathlib import Path image_dir = Path("./raw_images") save_dir = Path("./JPEGImages") save_dir.mkdir(exist_ok=True) for idx, img_path in enumerate(sorted(image_dir.glob("*.jpg"))): new_name = save_dir / f"{idx+1:06d}.jpg" # 6位数字,不足补零 os.rename(img_path, new_name)基础质量过滤:
- 分辨率:剔除分辨率低于640x480的图片。目标检测需要足够的像素来捕捉特征。
- 模糊度:使用OpenCV的拉普拉斯方差法快速检测模糊图片。方差低于某个阈值(例如100)的图片予以剔除。
import cv2 def is_blurred(image_path, threshold=100): image = cv2.imread(image_path) gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) fm = cv2.Laplacian(gray, cv2.CV_64F).var() return fm < threshold- 重复图片:使用感知哈希(pHash)或特征匹配来去除高度相似或完全重复的图片,防止数据泄露。
人脸区域裁剪与对齐(可选但推荐):我们的目标是脸部皮肤病,因此无关的背景信息是噪声。使用MTCNN或OpenCV的DNN人脸检测器自动检测并裁剪出人脸区域。这能显著减少模型需要学习的无关特征,提升精度和效率。裁剪后,统一将图像缩放至固定大小(如640x640),为后续标注和训练做准备。
3.2 标注工具选择与标注规范制定
我选择LabelImg作为标注工具。它开源、免费、支持VOC和YOLO格式,图形界面友好。
在开始标注前,必须制定并严格遵守一份《标注规范手册》,这是保证标注一致性的关键。手册核心内容包括:
边界框(Bounding Box)绘制准则:
- 紧密度:框体应紧紧包裹住目标病变区域,边缘间隙尽可能小。
- 完整性:对于不规则病灶(如一片黄褐斑),框体应覆盖所有明显病变部分。
- 对于密集小目标(如痤疮)的处理:这是最大的挑战。如果痘痘之间间隔清晰,必须逐个标注,即使它们非常小(但长宽建议大于10像素)。如果痘痘密集到连成一片,无法清晰区分单个边界,则标注为一个大的框体,并在类别上可考虑增加
acne_group标签,或在标注备注中说明。
困难样本与歧义处理:
- 疑似目标:对于无法确定是否为皮肤病的斑点(可能是阴影、污点),一律不标注。宁可漏标,不可错标。
- 部分遮挡:目标被头发、眼镜等遮挡,但可见部分超过50%且能判断类别,则标注可见部分。
- 类别模糊:例如一个红斑,介于玫瑰痤疮和炎症性痤疮之间。标注员需根据主要特征判断,如果无法判断,则记录为“困难样本”,由更专业的人员复核。
标注员培训与质检:至少进行两轮标注和一轮质检。第一轮由初级标注员完成;第二轮由资深标注员或医生复核,修正错误;最后随机抽取10%-20%的样本进行质检,计算标注一致率(IoU>0.7且类别正确),要求达到95%以上。
4. VOC格式标注文件详解与YOLO格式转换
标注完成后,我们得到了Annotations文件夹里的一堆XML文件。理解其结构,才能更好地利用和转换它。
4.1 VOC XML文件结构解析
以一个包含两个acne目标的000001.xml为例:
<annotation> <folder>VOC2024</folder> <filename>000001.jpg</filename> <path>/full/path/to/000001.jpg</path> <source> <database>My Dermatology Database</database> </source> <size> <width>800</width> <height>600</height> <depth>3</depth> </size> <segmented>0</segmented> <object> <name>acne</name> <pose>Unspecified</pose> <truncated>0</truncated> <difficult>0</difficult> <bndbox> <xmin>256</xmin> <ymin>128</ymin> <xmax>280</xmax> <ymax>152</ymax> </bndbox> </object> <object> <name>acne</name> <pose>Unspecified</pose> <truncated>0</truncated> <difficult>0</difficult> <bndbox> <xmin>400</xmin> <ymin>300</ymin> <xmax>430</xmax> <ymax>330</ymax> </bndbox> </object> </annotation>size: 图像的宽、高、通道数。至关重要,所有坐标转换都依赖于此。object: 每个检测目标。name: 类别标签,与我们定义的acne,nevus等对应。bndbox: 边界框的左上角(xmin,ymin)和右下角(xmax,ymax)的绝对像素坐标。difficult: 标记为1表示难以检测,训练时可能被忽略。truncated: 标记为1表示目标被截断(在图像边界外)。
4.2 从VOC到YOLO格式的精准转换
YOLO需要的.txt格式是归一化的中心坐标和宽高。转换公式是核心:
x_center = (xmin + xmax) / (2.0 * image_width) y_center = (ymin + ymax) / (2.0 * image_height) width = (xmax - xmin) / image_width height = (ymax - ymin) / image_height转换时,必须为每个类别分配一个唯一的整数ID。例如:acne:0,nevus:1,melasma:2,rosacea:3。这个映射关系需要保存为一个class.names或data.yaml文件,供训练时使用。
下面是一个健壮的转换脚本,它处理了difficult标签,并自动生成YOLO所需的目录结构:
import xml.etree.ElementTree as ET import os from pathlib import Path # 类别到ID的映射 class_mapping = {'acne': 0, 'nevus': 1, 'melasma': 2, 'rosacea': 3} def convert_voc_to_yolo(voc_annotations_dir, voc_images_dir, output_dir, ignore_difficult=True): """ 将VOC格式标注转换为YOLO格式。 Args: ignore_difficult: 是否忽略标记为difficult的目标。 """ output_dir = Path(output_dir) (output_dir / 'labels').mkdir(parents=True, exist_ok=True) (output_dir / 'images').mkdir(parents=True, exist_ok=True) for xml_file in Path(voc_annotations_dir).glob('*.xml'): tree = ET.parse(xml_file) root = tree.getroot() # 获取图像尺寸 size = root.find('size') img_w = int(size.find('width').text) img_h = int(size.find('height').text) # 对应的图片名和输出路径 img_name = root.find('filename').text txt_name = xml_file.stem + '.txt' txt_path = output_dir / 'labels' / txt_name yolo_lines = [] for obj in root.iter('object'): # 检查是否忽略困难样本 difficult = int(obj.find('difficult').text) if ignore_difficult and difficult == 1: continue cls_name = obj.find('name').text if cls_name not in class_mapping: continue # 跳过未定义类别 cls_id = class_mapping[cls_name] xmlbox = obj.find('bndbox') xmin = float(xmlbox.find('xmin').text) ymin = float(xmlbox.find('ymin').text) xmax = float(xmlbox.find('xmax').text) ymax = float(xmlbox.find('ymax').text) # 坐标归一化 x_center = (xmin + xmax) / 2.0 / img_w y_center = (ymin + ymax) / 2.0 / img_h width = (xmax - xmin) / img_w height = (ymax - ymin) / img_h # 确保坐标在0-1之间(处理可能的标注误差) x_center = max(0, min(1, x_center)) y_center = max(0, min(1, y_center)) width = max(0, min(1, width)) height = max(0, min(1, height)) yolo_lines.append(f"{cls_id} {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}") # 只保存有目标的标注文件 if yolo_lines: with open(txt_path, 'w') as f: f.write('\n'.join(yolo_lines)) # 可选:将对应图片复制到images文件夹 src_img = Path(voc_images_dir) / img_name dst_img = output_dir / 'images' / img_name if src_img.exists(): import shutil shutil.copy2(src_img, dst_img) print(f"Processed: {xml_file.name}") # 使用示例 convert_voc_to_yolo( voc_annotations_dir='./VOCdevkit/VOC2024/Annotations', voc_images_dir='./VOCdevkit/VOC2024/JPEGImages', output_dir='./yolo_dataset' )运行后,你会得到./yolo_dataset文件夹,里面包含images/(所有图片)和labels/(所有YOLO格式的.txt标注)。
4.3 数据集划分与配置文件生成
不能把所有数据都扔进去训练。需要按比例划分训练集、验证集和测试集。通常比例为70%:20%:10%。划分时要确保每个类别在三个集合中都有分布(分层抽样)。
import random from pathlib import Path image_dir = Path('./yolo_dataset/images') all_images = sorted([p.stem for p in image_dir.glob('*.jpg')]) # 获取所有图片名(不含后缀) random.seed(42) # 固定随机种子,确保结果可复现 random.shuffle(all_images) total = len(all_images) train_ratio, val_ratio = 0.7, 0.2 train_num = int(total * train_ratio) val_num = int(total * val_ratio) train_set = all_images[:train_num] val_set = all_images[train_num:train_num+val_num] test_set = all_images[train_num+val_num:] # 将划分结果写入文件 def write_list(file_path, img_list): with open(file_path, 'w') as f: for img in img_list: f.write(f"./images/{img}.jpg\n") # YOLO通常需要相对路径 write_list('./yolo_dataset/train.txt', train_set) write_list('./yolo_dataset/val.txt', val_set) write_list('./yolo_dataset/test.txt', test_set)最后,创建一个YOLO模型训练所需的data.yaml配置文件:
# data.yaml path: /absolute/path/to/yolo_dataset # 数据集的根目录 train: train.txt # 训练集列表文件,相对于path val: val.txt # 验证集列表文件 test: test.txt # 测试集列表文件(可选) # 类别数量和名称 nc: 4 # number of classes names: ['acne', 'nevus', 'melasma', 'rosacea'] # 可选:预定义的锚框(anchor),对于自定义数据集,YOLOv5/v8通常可以自动聚类生成 # anchors: ...这个data.yaml文件就是连接你的数据集和YOLO训练代码的桥梁。
5. 数据增强策略与模型训练初步验证
数据集构建好后,在投入正式训练前,还有一步至关重要:通过数据增强来“扩充”数据集,并做一个快速的训练验证,确保数据管道是通的。
5.1 针对皮肤病图像的数据增强
皮肤病变的检测,需要特定的增强策略来模拟真实世界的多样性,同时避免引入不合理的畸变。
几何变换:
- 水平翻转:非常安全且有效,人脸基本对称。
- 小角度旋转(±15°):模拟头部轻微倾斜。
- 随机缩放与裁剪:模拟不同拍摄距离。注意裁剪不能丢失关键目标。
- 避免使用垂直翻转和大角度旋转:这会产生不自然的人脸朝向。
颜色与亮度变换:
- HSV空间扰动:在色相(H)、饱和度(S)、明度(V)上做微小随机调整。可以模拟不同肤色、光照条件(如偏黄的白炽灯、偏蓝的日光)对病灶外观的影响。
- 对比度、亮度调整:模拟过曝或欠曝的环境。
- 高斯噪声:添加轻微噪声,模拟低质量摄像头的拍摄效果,提升模型鲁棒性。
模拟成像缺陷:
- 轻微模糊:模拟对焦不准或手抖。
- JPEG压缩伪影:模拟网络传输后图像质量下降。
实操心得:在YOLOv5/v8的训练中,这些增强大多可以通过配置文件(如
data.yaml同目录下的hyp.yaml或训练命令参数)直接启用和调整强度。建议初期使用默认或较弱的增强,先让模型学会“看”到目标,后续再根据模型在验证集上的表现(如过拟合时)逐步加强增强。
5.2 使用YOLOv8进行快速训练验证
现在,我们可以用一小部分数据,快速跑一个训练流程,验证数据集格式是否正确,以及模型能否正常学习。
安装与环境准备:
pip install ultralytics # 安装YOLOv8准备数据集结构:确保你的
yolo_dataset文件夹结构如下:yolo_dataset/ ├── images/ │ ├── 000001.jpg │ └── ... ├── labels/ │ ├── 000001.txt │ └── ... ├── train.txt ├── val.txt └── data.yaml启动一个快速训练(例如训练10个epoch,只为了验证):
from ultralytics import YOLO # 加载一个预训练模型(如最小的YOLOv8n) model = YOLO('yolov8n.pt') # 开始训练 results = model.train( data='/path/to/your/yolo_dataset/data.yaml', epochs=10, imgsz=640, batch=8, # 根据你的GPU内存调整 device='0', # 使用GPU 0,如果是CPU则设为'cpu' workers=4, # 数据加载线程数 name='skin_detect_test_run' # 本次实验的名称 )验证结果:
- 训练结束后,查看
runs/detect/skin_detect_test_run目录下的结果。 - 关键指标:关注
results.png中的损失曲线(train/val loss是否在下降),以及confusion_matrix.png。即使精度不高,只要训练损失在下降,且验证集上能看到一些正确的预测框,就证明你的数据集格式是正确的,模型能够学习。这是从“数据构建”迈向“模型训练”的关键一步。
- 训练结束后,查看
6. 构建过程中的常见问题、陷阱与解决方案
在整个数据集构建过程中,我遇到了无数大大小小的问题。这里把最具代表性的几个“坑”和解决方案记录下来,希望能帮你节省大量时间。
6.1 标注质量相关的问题
| 问题现象 | 可能原因 | 解决方案与排查技巧 |
|---|---|---|
| 训练时Loss震荡大,不收敛 | 标注噪声大,存在大量错误框(框错目标)或错误类别。 | 1.可视化检查:写脚本将标注框画在图片上,随机抽查几百张,肉眼观察。 2.统计异常:检查每个类别的边界框宽高比分布。如果某个类别的框普遍极扁或极长,可能标注不规范。 3.使用预训练模型进行初步推理:用COCO预训练的YOLO模型在你的图片上跑一遍,将模型的预测结果与你的标注对比,差异巨大的地方很可能标注有误。 |
| 模型对某一类别的AP(平均精度)极低 | 1. 该类样本数量严重不足。 2. 该类标注质量特别差(如框体不准确、类别混淆)。 3. 该类目标本身太难(如 melasma边界模糊)。 | 1.数据平衡:对该类别进行过采样,或使用数据增强专门针对该类。 2.针对性质检:重点复核该类别的所有标注样本。 3.调整标注规范:对于边界模糊的类别,召集标注员统一标注标准(例如,以肉眼可见的明显色素沉着边缘为准)。 |
| 模型预测的框总是比真实框大一圈或小一圈 | 标注规范不统一,有的标注员喜欢框得“松”一些,有的喜欢“紧”一些。 | 1.统一标准:在《标注规范手册》中明确加入示例图,展示“优秀”、“合格”、“不合格”的框体。 2.校准会议:定期召开标注员会议,对有争议的样本进行讨论并定标。 3.后处理:在训练时,可以稍微提高IoU阈值(如从0.5提高到0.6),鼓励模型学习更紧的框。 |
6.2 数据与格式转换问题
| 问题现象 | 可能原因 | 解决方案与排查技巧 |
|---|---|---|
| 训练时出现“Label file is empty”或“No labels found”警告 | 1. 转换后的YOLO.txt文件为空。2. 图片路径在 train.txt中写错了。3. 图片和标签文件名称不匹配。 | 1.脚本调试:在转换脚本中打印每个文件的处理结果,检查哪些XML文件转换后没有生成目标。可能是difficult标签被过滤,或类别不在映射表中。2.路径检查:确保 data.yaml中的path是绝对路径,且train.txt中的路径相对于path是正确的。可以用os.path.join手动拼接几个路径测试是否能打开文件。3.一致性检查:写脚本对比 images/和labels/文件夹下的文件名(不含后缀)是否完全一致。 |
训练时出现坐标越界错误(如IndexError) | 转换时坐标归一化计算错误,导致x_center,width等值大于1或小于0。 | 1.检查原始XML:确认xmin, ymin, xmax, ymax的值是否在图片尺寸范围内。有时标注工具会出bug,产生负坐标或超过宽高的坐标。2.在转换脚本中加入边界裁剪:如上文脚本所示,使用 max(0, min(1, value))将坐标强制限制在[0,1]区间。3.可视化验证:转换后,用脚本将YOLO格式的框画回原图,检查是否对齐。 |
| 类别ID混乱,预测结果张冠李戴 | class_mapping字典在转换和训练时不一致。 | 单一事实来源:只在一个地方定义类别映射。最佳实践是: 1. 创建一个独立的 classes.txt文件,按行写入类别名。2. 转换脚本读取该文件生成 class_mapping。3. data.yaml中的names也从这个文件读取。确保训练代码(如YOLO)使用的data.yaml就是这个文件。 |
6.3 训练过程中的数据问题反馈
即使训练开始了,数据的问题仍会暴露出来。模型是最好的质检员。
- Loss曲线初期飙升然后骤降:可能是学习率太高,但也可能是数据中存在极端错误的标签(如坐标全为0),导致梯度爆炸。检查验证集中Loss最高的那几个样本的标注。
- 验证集精度远低于训练集:典型的过拟合。首先考虑数据量是否足够。对于皮肤病数据集,如果只有几千张图,过拟合风险很高。此时应加强数据增强(如Mosaic, MixUp),或者考虑使用更小的模型(如YOLOv8n而非YOLOv8x)。
- 模型对某个尺度(大/小目标)检测效果差:检查数据集中目标尺度的分布。皮肤病目标(尤其是痤疮)通常是小目标。你需要确保训练图片的分辨率足够高(如640x640),并且在数据增强中减少或谨慎使用随机缩放裁剪,以免小目标在裁剪中被丢弃。可以在YOLO训练配置中调整
scale参数,或者专门为小目标设计增强策略。
构建一个可用的数据集是第一步,构建一个精良的数据集是一个持续迭代的过程。我的经验是,第一轮训练的结果,最重要的价值不是得到一个多好的模型,而是暴露数据本身的问题。根据模型反馈,回头去修正标注、补充数据、调整类别,再进行第二轮、第三轮迭代。这个过程往往需要重复3-5次,才能得到一个真正能支撑高性能模型的数据集。记住,在机器学习项目中,数据和算法是双引擎,而数据往往是那个更关键、更需要耐心去打磨的引擎。
本文还有配套的精品资源,点击获取