反光衣检测数据集:1028张工地图像XML标注与YOLO训练全流程
简介这份反光衣检测数据集面向计算机视觉与目标检测方向的开发者、算法工程师及高校学生尤其适合正在做安全帽/反光衣佩戴识别、工地安全监控等课题的研究者。数据以建筑工地场景为主将目标划分为反光衣和其他衣服两类可直接用于YOLO系列模型的训练、微调与验证帮助解决实际场景中反光衣识别样本不足的问题。压缩包共2058个文件包含1029张jpg图像与1029个同名xml标注文件图像与标注一一对应整体约81.87MB目录分为JPEGImages与Annotations两部分结构规整便于直接接入常见检测框架。目前已有1778人学习下载说明该数据集在同类任务中具备一定参考价值。读者可借助这批真实工地图像完成数据清洗、标注解析、类别平衡与模型评估快速搭建反光衣检测基线并在此基础上做数据增强或迁移学习实验节省自行采集与标注的时间成本。1. 反光衣检测数据集落地1028 张工地图 XML 标注YOLO 直接开训工地安全帽和反光衣的视觉检测是目标检测落地里最典型的场景之一。但真正动手时卡住大多数人的不是模型结构而是数据——网上能找到的反光衣数据集要么只有图片没有标注要么标注格式和 YOLO 对不上要么类别定义混乱。这份反光衣检测数据集给了一个相对干净的起点1028 张以建筑工地为主的图片全部配了 PASCAL VOC 格式的 XML 标注类别只有两类——反光衣和其他衣服。图片和标注一一对应命名从 reflective_000000 顺序排到 reflective_001028没有缺号断档。它适合两类人一是想快速跑通 YOLO 训练流程、验证自己环境是否正常的工程师二是需要一个小规模、类别清晰的数据集来做反光衣检测原型验证的从业者。下面从数据本身讲到转换、训练、排错把这条链路走完。2. 拆开这份数据集XML 结构、类别定义与选型理由2.1 目录结构与文件对应关系拿到压缩包解压后标准结构是 Annotations 和 JPEGImages 两个文件夹。Annotations 里放 XMLJPEGImages 里放同名 JPG。这种 VOC 风格的组织方式虽然老但兼容性最好LabelImg、labelme、大部分转换脚本都认这个布局。# 解压后先确认文件数量和命名是否对齐 ls Annotations | wc -l ls JPEGImages | wc -l # 两边都应该是 1029 个文件000000 到 001028 # 抽查几个文件名是否一一对应 ls Annotations | head -5 ls JPEGImages | head -5逻辑说明第一步先做数量核对这是最容易被跳过但最致命的一步。如果 Annotations 和 JPEGImages 数量不一致后面转换脚本会静默丢样本训练时精度上不去你还找不到原因。参数上wc -l统计的是文件行数对ls输出而言就是文件个数。命名规律是 reflective_ 加六位数字六位补零保证了排序稳定不会出现 000010 排在 000009 前面的问题。2.2 XML 标注字段解读每个 XML 文件遵循 VOC 规范核心字段是 filename、size、object。object 里包含 name类别名、pose、truncated、difficult 和 bndbox。bndbox 里的 xmin、ymin、xmax、ymax 是像素坐标左上角为原点。annotation filenamereflective_000000.jpg/filename size width640/width height480/height depth3/depth /size object namereflective_clothes/name bndbox xmin112/xmin ymin88/ymin xmax305/xmax ymax420/ymax /bndbox /object /annotation逻辑说明name 字段决定了类别索引的映射关系。这份数据集只有两类实际 XML 里可能出现 reflective_clothes 和 other_clothes 两种取值。转换前必须先把所有 XML 里的 name 取值统计一遍确认没有拼写变体比如 reflective 和 reflective_clothes 混用那会导致类别数从 2 变成 3。bndbox 坐标是绝对像素值YOLO 需要的是归一化后的中心点加宽高这是转换脚本要处理的核心。2.3 为什么选 VOC XML 而不是直接给 YOLO txt很多人会问既然要训 YOLO为什么不直接给 txt 格式。原因在于 XML 保留了图片尺寸和更多元信息转换过程可逆、可审计。txt 格式一旦生成坐标是归一化的你很难反推原始标注是否准确。XML 作为中间格式方便做数据清洗、可视化校验和格式互转。常见做法是保留 XML 作为原始标注用脚本按需生成 YOLO txt这样同一份数据可以喂给不同框架。提示转换前先备份 Annotations 文件夹任何批量脚本都可能因为编码或路径问题改坏原文件。3. XML 转 YOLO txt转换脚本、类别映射与校验3.1 类别映射与配置文件YOLO 训练需要两个配置文件数据集的 yaml 和类别名文件。类别顺序必须和转换时用的映射一致否则训练出来的模型会把反光衣识别成其他衣服。# classes.txt 内容顺序即类别索引 # 0: reflective_clothes # 1: other_clothes# reflective.yaml path: ./reflective_dataset train: images/train val: images/val nc: 2 names: 0: reflective_clothes 1: other_clothes逻辑说明nc 是类别数names 是索引到名称的映射。这个映射必须和转换脚本里的 class_mapping 字典完全一致。参数上path 是数据集根目录train 和 val 是相对路径。常见翻车点是 yaml 里写了绝对路径换台机器就找不到数据。建议用相对路径把 yaml 放在项目根目录。3.2 转换脚本与坐标归一化import os import xml.etree.ElementTree as ET from PIL import Image # 类别映射必须与 yaml 中的 names 顺序一致 class_mapping { reflective_clothes: 0, other_clothes: 1 } def convert_annotation(xml_path, img_path, output_dir): tree ET.parse(xml_path) root tree.getroot() # 用图片实际尺寸做归一化而不是 XML 里的 size # 因为部分标注的 size 可能与真实图片不符 with Image.open(img_path) as img: w, h img.size lines [] for obj in root.findall(object): name obj.find(name).text.strip() if name not in class_mapping: print(f未知类别 {name}跳过 {xml_path}) continue cls_id class_mapping[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) # 坐标裁剪防止越界 xmin max(0, min(xmin, w)) xmax max(0, min(xmax, w)) ymin max(0, min(ymin, h)) ymax max(0, min(ymax, h)) # 转为中心点加宽高再归一化 cx (xmin xmax) / 2.0 / w cy (ymin ymax) / 2.0 / h bw (xmax - xmin) / w bh (ymax - ymin) / h lines.append(f{cls_id} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}) # 写 txt文件名与图片同名 base os.path.splitext(os.path.basename(xml_path))[0] with open(os.path.join(output_dir, base .txt), w) as f: f.write(\n.join(lines)) # 批量处理 xml_dir Annotations img_dir JPEGImages out_dir labels os.makedirs(out_dir, exist_okTrue) for xml_file in os.listdir(xml_dir): if not xml_file.endswith(.xml): continue base os.path.splitext(xml_file)[0] img_file base .jpg xml_path os.path.join(xml_dir, xml_file) img_path os.path.join(img_dir, img_file) if not os.path.exists(img_path): print(f图片缺失 {img_file}跳过) continue convert_annotation(xml_path, img_path, out_dir)逻辑说明这段脚本做了四件事——读 XML、用真实图片尺寸归一化、裁剪越界坐标、写 YOLO txt。参数上cx:.6f保留六位小数是 YOLO 官方推荐的精度太少会丢定位精度太多没必要。坐标裁剪是血泪经验有些标注的 xmax 会超出图片宽度一两个像素不裁剪会导致训练时 loss 异常。类别映射用字典硬编码比从文件读更直观但改类别时记得同步改 yaml。3.3 转换后的校验方法转换完不能直接开训先做三件事数量核对、可视化抽查、空标签检查。# 1. 数量核对 ls labels | wc -l # 应该和图片数量一致 # 2. 空标签检查空 txt 说明该图没有有效标注 find labels -empty # 3. 可视化抽查用 PIL 画框确认坐标没跑偏from PIL import Image, ImageDraw import os def visualize(img_path, label_path, save_path): img Image.open(img_path).convert(RGB) w, h img.size draw ImageDraw.Draw(img) with open(label_path) as f: for line in f: parts line.strip().split() if len(parts) ! 5: continue cls_id, cx, cy, bw, bh map(float, parts) x1 (cx - bw / 2) * w y1 (cy - bh / 2) * h x2 (cx bw / 2) * w y2 (cy bh / 2) * h draw.rectangle([x1, y1, x2, y2], outlinered, width2) img.save(save_path) # 抽查前 10 张 for i in range(10): base freflective_{i:06d} visualize(fJPEGImages/{base}.jpg, flabels/{base}.txt, fcheck_{base}.jpg)逻辑说明可视化是最后一道防线。归一化坐标反算回像素坐标画框看是否贴合目标。如果框整体偏移多半是宽高用反了如果框大小不对检查归一化时除的是宽还是高。参数上outlinered只是标记不影响数据。这一步花十分钟能省后面几小时的排查。4. YOLO 训练配置数据划分、超参与显存权衡4.1 训练集验证集划分1028 张图片常见做法是按 8:2 划分训练集 822 张验证集 206 张。划分要保证两类样本分布均衡不能训练集全是反光衣、验证集全是其他衣服。import os import random import shutil random.seed(42) # 固定随机种子保证可复现 all_imgs [f for f in os.listdir(JPEGImages) if f.endswith(.jpg)] random.shuffle(all_imgs) split int(len(all_imgs) * 0.8) train_imgs all_imgs[:split] val_imgs all_imgs[split:] for phase, imgs in [(train, train_imgs), (val, val_imgs)]: os.makedirs(fdataset/images/{phase}, exist_okTrue) os.makedirs(fdataset/labels/{phase}, exist_okTrue) for img in imgs: base os.path.splitext(img)[0] shutil.copy(fJPEGImages/{img}, fdataset/images/{phase}/{img}) shutil.copy(flabels/{base}.txt, fdataset/labels/{phase}/{base}.txt)逻辑说明random.seed(42)是后悔药保证每次划分结果一致方便对比实验。参数上0.8 是经验值数据量小于 5000 时常用 8:2大于 10000 可以用 9:1。复制而不是移动保留原始数据不动。划分后要再统计一次两类的框数量确认没有严重偏斜。4.2 训练命令与关键超参yolo detect train \ datareflective.yaml \ modelyolov8n.pt \ epochs100 \ imgsz640 \ batch16 \ lr00.01 \ patience20 \ projectruns/train \ namereflective_exp逻辑说明model 选 yolov8n 是因为数据量小大模型容易过拟合。epochs 设 100配合 patience20 做早停验证集 loss 20 轮不降就停。imgsz640 是 YOLO 默认输入尺寸工地图片目标通常不小640 够用。batch16 是显存和稳定性的折中显存不够就降到 8 或 4。lr00.01 是初始学习率小数据集不建议太大。参数怎么改如果训练 loss 震荡把 lr0 降到 0.001如果欠拟合把 epochs 加到 200。4.3 显存与 batch 的权衡显存不够是新手最常见的翻车点。8G 显存跑 yolov8n 加 640 尺寸batch16 基本是上限。如果报 CUDA out of memory按这个顺序降先降 batch 到 8再降 imgsz 到 512最后换更小的模型。不要一上来就改模型先确认是不是 batch 太大。注意训练前用nvidia-smi看显存占用别在跑着其他任务时开训否则显存争抢会导致训练中断。5. 避坑与排查五条真实踩坑记录5.1 类别名不一致导致类别数翻倍现象训练日志里 nc 显示 3 或 4但 yaml 里写的是 2。原因XML 里 name 字段有拼写变体比如 reflective_clothes 和 reflective 混用转换脚本按字典映射时把未知类别跳过了但如果你用的是自动扫描类别的脚本就会生成多余类别。解决转换前先跑一遍统计。import os import xml.etree.ElementTree as ET from collections import Counter names Counter() for f in os.listdir(Annotations): if f.endswith(.xml): root ET.parse(os.path.join(Annotations, f)).getroot() for obj in root.findall(object): names[obj.find(name).text.strip()] 1 print(names)5.2 坐标越界导致 loss 变 NaN现象训练几个 epoch 后 loss 突然变 NaN。原因部分 bndbox 的 xmax 或 ymax 超出图片实际尺寸归一化后宽高大于 1YOLO 计算 loss 时出现异常值。解决转换脚本里加坐标裁剪如 3.2 节所示把坐标限制在图片范围内。5.3 图片和标注不同名导致静默丢样本现象训练集数量比预期少。原因JPEGImages 里有图片但 Annotations 里没有对应 XML或者反过来。转换脚本遇到缺失就跳过不报错。解决转换前做一次集合比对。import os imgs {os.path.splitext(f)[0] for f in os.listdir(JPEGImages) if f.endswith(.jpg)} xmls {os.path.splitext(f)[0] for f in os.listdir(Annotations) if f.endswith(.xml)} print(有图无标注:, imgs - xmls) print(有标注无图:, xmls - imgs)5.4 验证集泄露导致精度虚高现象验证集 mAP 很高但实际测试效果差。原因划分时没有固定随机种子或者同一张图的不同增强版本同时进了训练集和验证集。解决划分前固定 seed且划分在增强之前做。这份数据集每张图只有一份不存在增强泄露但固定 seed 仍然必要。5.5 中文路径导致读取失败现象训练报错找不到文件但路径明明存在。原因数据集放在中文命名的文件夹里某些库对中文路径支持不好。解决把数据集放在纯英文路径下比如/data/reflective别放在桌面或中文目录里。6. 进阶技巧用验证结果反查标注质量训练跑通只是开始真正拉开差距的是用模型结果反查数据质量。我一般会在训练完第一个模型后用验证集跑推理把预测框和真实框叠在一起看。如果某个类别 mAP 明显偏低先别急着调模型去看这个类别的标注是不是有问题。yolo detect val \ modelruns/train/reflective_exp/weights/best.pt \ datareflective.yaml \ conf0.25 \ iou0.5 \ save_jsonTrue逻辑说明conf0.25 是置信度门限低于这个值的预测不参与评估。iou0.5 是判定预测框和真实框匹配的阈值。save_jsonTrue 会输出每张图的预测结果方便逐张对比。参数怎么调如果漏检多把 conf 降到 0.1 看是不是门限太高如果误检多把 conf 提到 0.4。拿到预测结果后重点看两类图一是真实框有但预测框没有的说明漏检可能是标注框太小或太模糊二是预测框有但真实框没有的说明误检可能是背景被误标或漏标。把这两类图挑出来重新标注再训一轮mAP 通常能涨几个点。排查项看什么常见结论漏检图真实框是否过小小目标需要提高输入尺寸误检图背景是否有相似目标需要补负样本框偏移预测框和真实框偏差方向标注坐标可能写反类别混淆反光衣被识别成其他衣服两类样本不均衡还有一个习惯每次改完标注或转换脚本先跑 10 个 epoch 的小实验看 loss 曲线是否正常再开完整训练。别一上来就 100 epoch跑几小时发现数据有问题时间全浪费了。从那以后我每次拿到新数据集都强制走一遍「数量核对 → 类别统计 → 可视化抽查 → 小轮次试训」这四步没出过大的翻车。希望帮到你。本文还有配套的精品资源点击获取