简介痤疮检测数据集面向皮肤检测、医学图像识别方向的算法学习者和模型训练者提供一套可直接用于目标检测任务的标注数据帮助解决痤疮区域定位与识别中样本获取难、标注成本高的问题。资源包共约2000个文件包含915张jpg图片、915个VOC格式xml标注文件、915个YOLO格式txt标注文件及少量说明文件压缩包大小约31.74MB同时兼容Pascal VOC与YOLO两种主流训练流程无需额外格式转换即可投入实验。标注类别为cuochuang单类采用labelImg工具绘制矩形框累计标注框数达11807个平均每张图约12.9个目标标注密度较高适合小目标密集场景下的检测模型训练与验证。目前已有206人学习下载可作为课程设计、毕业设计或医学图像检测课题的基础数据便于快速搭建训练管线、对比不同检测算法效果并在此基础上进行数据增强与模型调优。1. 915张痤疮检测数据集VOC与YOLO双格式到底怎么选、怎么用手上拿到一个标注好的痤疮检测数据集915张图、1个类别压缩包名字里写着「VOCYOLO格式」。很多人第一反应是解压、找train、直接喂给YOLOv8跑训练结果要么路径报错要么标签读不进去要么训练几轮loss不降。问题往往不在模型而在没搞清楚VOC和YOLO这两套标注体系到底差在哪、什么时候该用哪套、转换时哪些字段会丢。这个数据集规模不大属于典型的小样本单类别检测任务适合做皮肤病灶区域的定位验证、算法原型快速迭代也适合刚接触目标检测的从业者拿来跑通全流程。它解决的核心诉求很明确让你不用自己从零标注就能把一份现成的痤疮图像数据接进YOLO训练管线。适合人群包括做医疗辅助诊断原型的算法工程师、需要快速验证检测方案的学生以及想拿真实小数据集练手YOLO训练与评估的开发者。下面按「先认清格式、再动手转换、最后训练排错」的顺序拆开讲。2. VOC与YOLO标注格式的差异与选型判断2.1 两套格式的文件结构对比VOC格式的核心是每张图对应一个XML文件文件名与图片同名放在Annotations目录下。XML里记录图片尺寸、目标类别名、边界框的xmin、ymin、xmax、ymax四个坐标值。YOLO格式则是每张图对应一个txt文件放在labels目录下每行表示一个目标格式为「类别索引 中心x 中心y 宽 高」且这四个值都是相对于图片宽高的归一化数值范围在0到1之间。这个数据集标注为1个类别意味着YOLO的txt里每行开头都是0。VOC的XML里则会出现具体的类别名称比如acne或lesion具体名称取决于标注者定义。两套格式最本质的区别在于坐标表达方式VOC用绝对像素值YOLO用归一化相对值。这个差异直接决定了转换时不能简单复制数值必须做归一化计算。对比项VOC格式YOLO格式标注文件XMLTXT坐标类型绝对像素归一化0-1类别表达字符串名称整数索引目录结构Annotations/JPEGImagesimages/labels多目标多个object节点多行文本2.2 什么场景选VOC、什么场景选YOLO选VOC的典型场景是你需要用LabelImg继续修改标注、要把数据接入MMDetection或Detectron2这类框架、或者需要保留类别名称的可读性做人工复核。VOC的XML结构自描述性强打开就能看懂每个框对应什么类别、图片多大适合标注阶段和跨框架迁移。选YOLO的场景更直接你要用Ultralytics系的YOLOv5、YOLOv8、YOLOv11训练这些框架的数据加载器默认读YOLO格式的txt。用VOC格式虽然也能通过配置转换但多一层中间环节就多一个出错点。对于915张这种小规模数据集我一般建议直接转成YOLO格式把目录结构一次性摆对后面训练脚本不用改。注意这个数据集同时提供VOC和YOLO两套格式不代表两套内容一定完全一致。常见情况是YOLO格式由VOC转换而来转换脚本如果有bug可能出现框偏移或漏标。拿到后先用下面第4章的校验脚本比对两套格式的框数量是否一致。2.3 单类别数据集的标签索引陷阱1个类别听起来简单但恰恰容易翻车。YOLO的类别索引从0开始所以唯一类别就是0。问题出在有些转换脚本会写成从1开始或者VOC的类别名映射表里多写了一个背景类导致训练时类别数配置成2模型输出维度对不上训练直接报错或loss异常。判断方法很直接打开任意一个YOLO的txt文件看每行第一个数字。如果全是0说明索引正确如果出现1说明转换时类别映射偏了。同时检查data.yaml里的nc字段单类别必须写nc: 1names列表里只放一个类别名。这两处必须一致否则训练时会出现「标签类别超出范围」的报错。3. 从VOC到YOLO的转换脚本与目录组织3.1 转换脚本的完整实现下面这个脚本读取VOC的XML输出YOLO格式的txt同时完成坐标归一化。脚本假设图片是jpg格式XML在Annotations目录输出到labels目录。import os import xml.etree.ElementTree as ET # 类别映射根据实际XML里的类别名修改 class_map {acne: 0} # 单类别索引为0 def convert_bbox(size, box): 将VOC的xmin,ymin,xmax,ymax转为YOLO的center_x,center_y,w,h归一化 dw 1.0 / size[0] dh 1.0 / size[1] x_center (box[0] box[1]) / 2.0 y_center (box[2] box[3]) / 2.0 w box[1] - box[0] h box[3] - box[2] return (x_center * dw, y_center * dh, w * dw, h * dh) def convert_annotation(xml_path, out_dir): tree ET.parse(xml_path) root tree.getroot() size root.find(size) w int(size.find(width).text) h int(size.find(height).text) img_name os.path.splitext(os.path.basename(xml_path))[0] out_path os.path.join(out_dir, img_name .txt) with open(out_path, w) as f: for obj in root.iter(object): cls_name obj.find(name).text if cls_name not in class_map: continue cls_id class_map[cls_name] bndbox obj.find(bndbox) xmin float(bndbox.find(xmin).text) ymin float(bndbox.find(ymin).text) xmax float(bndbox.find(xmax).text) ymax float(bndbox.find(ymax).text) bb convert_bbox((w, h), (xmin, xmax, ymin, ymax)) f.write(f{cls_id} { .join([f{v:.6f} for v in bb])}\n) if __name__ __main__: xml_dir Annotations out_dir labels os.makedirs(out_dir, exist_okTrue) for xml_file in os.listdir(xml_dir): if xml_file.endswith(.xml): convert_annotation(os.path.join(xml_dir, xml_file), out_dir)逻辑说明convert_bbox函数接收图片宽高和VOC的四个坐标先算中心点再除以宽高做归一化。注意传入顺序是(xmin, xmax, ymin, ymax)因为函数内部按这个顺序取box[0]到box[3]。convert_annotation遍历XML里所有object节点按class_map过滤并取索引最后格式化成6位小数写入txt。参数方面class_map必须和XML里的name字段完全匹配大小写敏感如果XML里类别名是Acne而映射表写acne会全部跳过导致空标签。3.2 训练目录的摆放规范YOLOv8训练时对目录结构有约定推荐按下面这样组织dataset/ ├── images/ │ ├── train/ # 训练图片约732张 │ └── val/ # 验证图片约183张 ├── labels/ │ ├── train/ # 对应训练标签txt │ └── val/ # 对应验证标签txt └── data.yaml划分比例按8:2比较常见915张大约是732训练、183验证。图片和标签必须同名只是扩展名不同。如果图片是jpg标签是txt放在images和labels两个平行目录下YOLO会自动按路径替换找到标签。不要把所有图片堆在一个目录再在yaml里写同一个路径那样验证集和训练集会重叠评估指标虚高。data.yaml的内容如下path: ./dataset train: images/train val: images/val nc: 1 names: [acne]path是数据集根目录train和val是相对path的路径。nc必须等于names列表长度单类别就是1。names里的名称只用于显示不影响训练但建议和VOC里的类别名保持一致方便对照。3.3 转换后的快速自检转换完不要直接开训先跑一遍数量核对。VOC的XML里object节点总数应该等于YOLO所有txt的行数总和。下面这段脚本做这个比对import os import xml.etree.ElementTree as ET def count_voc_objects(xml_dir): total 0 for f in os.listdir(xml_dir): if f.endswith(.xml): tree ET.parse(os.path.join(xml_dir, f)) total len(tree.getroot().findall(object)) return total def count_yolo_lines(label_dir): total 0 for f in os.listdir(label_dir): if f.endswith(.txt): with open(os.path.join(label_dir, f)) as fh: total len([l for l in fh if l.strip()]) return total voc_total count_voc_objects(Annotations) yolo_total count_yolo_lines(labels) print(fVOC目标数: {voc_total}, YOLO目标数: {yolo_total}) assert voc_total yolo_total, 数量不一致检查转换脚本或类别映射如果两个数字不等优先查class_map是否漏了类别名其次查XML里是否有坐标为0或超出图片范围的异常框。坐标异常会导致归一化后数值不在0到1之间虽然脚本仍会写入但训练时这些框会被忽略或产生梯度异常。4. 用YOLOv8训练痤疮检测模型的完整命令与参数4.1 环境准备与预训练权重选择训练前先确认环境。Ultralytics的安装命令是pip install ultralytics它会自动装好torch和torchvision。如果机器有CUDA装完后用python -c import torch; print(torch.cuda.is_available())确认返回True。没有GPU也能跑但915张图在CPU上训练会非常慢建议至少用一张显存8G以上的卡。预训练权重方面YOLOv8提供n、s、m、l、x五个尺度。单类别小数据集用yolov8n.pt或yolov8s.pt就够了模型太大反而容易过拟合。权重文件在首次训练时会自动下载也可以提前下好放到当前目录。注意不要用分类任务的权重检测任务必须用带检测头的预训练模型。4.2 训练命令与关键参数解释yolo detect train \ data./dataset/data.yaml \ modelyolov8n.pt \ epochs100 \ imgsz640 \ batch16 \ lr00.01 \ patience20 \ projectruns/acne \ nameexp1逐项说明data指向yaml配置文件model指定预训练权重epochs是训练轮数小数据集100轮通常够配合patience20表示20轮验证指标不提升就早停imgsz640是输入分辨率痤疮病灶如果比较小可以提到768或896但显存占用会增加batch16根据显存调整显存不够就降到8或4lr0是初始学习率0.01是YOLOv8的默认值小数据集可以降到0.005减少震荡project和name决定输出目录训练日志、权重、混淆矩阵都会存在runs/acne/exp1下。训练过程中重点看三个指标box_loss持续下降说明框回归在收敛mAP50在验证集上逐步上升说明检测能力在提升如果box_loss下降但mAP50不动大概率是过拟合或验证集标签有问题。单类别任务的混淆矩阵只有两类背景和acne如果矩阵里acne被大量预测为背景说明模型没学到病灶特征需要检查标注框是否准确。4.3 训练结果解读与推理验证训练结束后runs/acne/exp1/weights目录下会有best.pt和last.pt。best.pt是验证指标最好的权重推理时优先用它。推理命令yolo detect predict \ modelruns/acne/exp1/weights/best.pt \ source./dataset/images/val \ conf0.25 \ saveTrueconf0.25是置信度阈值低于这个值的框不显示。痤疮检测如果漏检多可以降到0.1看更多候选框如果误检多提到0.4以上。推理结果默认存在runs/detect/predict下图片上会画出框和置信度。拿几张结果图对照原图看重点看小病灶有没有被漏掉、密集区域有没有框重叠。提示915张图训练出来的模型泛化能力有限换一批不同来源的痤疮图像指标大概率会下降。这个数据集更适合做流程验证和原型演示不要直接当成临床级模型使用。5. 训练痤疮检测模型时最容易踩的五个坑5.1 标签文件与图片不对应导致空标签现象训练启动后loss一直是nan或0或者日志里提示「no labels found」。原因通常是labels目录下的txt文件名和images下的图片名不一致比如图片是IMG_001.jpg而标签是img_001.txt大小写差异导致匹配失败。另一种情况是转换脚本输出的txt全部为空因为class_map里的类别名和XML里的name不匹配所有object都被continue跳过了。解决办法用第3.3节的脚本核对数量如果YOLO总行数为0检查XML里的实际类别名把class_map的键改成完全一致的值。5.2 坐标归一化时宽高传反现象训练能跑但框的位置明显偏移比如框集中在图片左上角或尺寸异常。原因是convert_bbox函数里把宽高传反了或者VOC的xmin、ymin顺序和函数参数顺序不一致。VOC的bndbox子节点顺序是xmin、ymin、xmax、ymax但有些标注工具输出的顺序可能不同。解决办法打开一个XML确认子节点顺序再对照转换脚本的参数顺序。归一化后的中心点x应该在0到1之间如果出现大于1的值说明除以的宽高不对。5.3 训练集和验证集图片重叠现象验证集mAP50异常高接近0.95以上但换一批新图推理效果很差。原因是划分时没有去重同一张图既在train又在val里模型相当于在验证集上「见过答案」。解决办法划分前先对所有图片名做集合运算确保train和val的交集为空。如果数据集本身有同一病灶的连拍图建议按病灶ID分组划分而不是按图片随机划分否则同一病灶的不同角度图会分到两边造成信息泄漏。5.4 类别数配置错误导致输出维度不匹配现象训练时报错「IndexError: Target X is out of bounds」或「AssertionError: nc mismatch」。原因是data.yaml里nc写成2或更多而标签里只有类别0。YOLO的检测头输出维度由nc决定nc2时模型预测两个类别但标签只有0计算loss时索引越界。解决办法确认data.yaml里nc: 1names列表只有一个元素。同时检查是否有转换脚本在类别索引上加了1导致标签里出现1。5.5 小目标病灶在640分辨率下丢失现象训练指标尚可但推理时小颗痤疮漏检严重。原因是痤疮病灶在原图中可能只占几十个像素缩放到640后信息进一步丢失。解决办法把imgsz提高到896或1024同时batch相应减小。另一种做法是在数据加载时不做过度缩放保持原图比例做letterbox填充。如果显存不够可以改用yolov8s并开启多尺度训练让模型适应不同尺度的小目标。6. 小样本单类别检测的进阶技巧从915张里榨出更多信息915张图、1个类别数据量确实不大。想让模型表现更稳有几个我实际用过的手段。第一个是离线数据增强在训练前用albumentations对训练集做翻转、旋转、亮度调整、高斯噪声把训练集扩到3000张左右。注意增强只对训练集做验证集保持原样否则评估指标不可信。第二个是 mosaic 和 mixup 的开关控制YOLOv8默认开启mosaic对小数据集有帮助但如果病灶本身形态固定mosaic可能引入不自然的拼接可以在最后20轮关掉mosaic让模型在真实分布上微调。第三个技巧是冻结 backbone 做 warmup。前10轮把backbone的学习率设得很低或直接冻结只训练检测头让模型先适应单类别的输出分布再解冻全网络微调。命令上可以通过freeze参数控制yolo detect train \ data./dataset/data.yaml \ modelyolov8n.pt \ epochs100 \ imgsz896 \ batch8 \ freeze10 \ lr00.005 \ patience20freeze10表示冻结前10层具体层数可以用model.model查看。这个做法在数据量小于1000时通常能提升2到5个点的mAP50。验证阶段除了看mAP建议手动抽20张验证图做混淆矩阵的逐图核对。YOLO自带的混淆矩阵是汇总的单类别任务里如果背景类被大量误判为acne矩阵上表现为背景行acne列数值高这时候要回去看标注是不是把非病灶区域也框进去了。我自己的习惯是每次训练完先看混淆矩阵再看PR曲线最后才看mAP数字。PR曲线能看出在不同置信度下的查准查全平衡比单一mAP更能反映模型在业务阈值下的表现。最后说一个习惯每次改完数据或参数把data.yaml、训练命令、关键指标记在一个文本文件里和权重放在一起。小数据集实验迭代快不记录的话两周后就忘了哪组参数对应哪个结果。这个数据集本身不大但把流程跑通、把每个坑踩明白换成其他单类别检测任务也能直接复用。希望帮到你。本文还有配套的精品资源点击获取