智慧工地安全帽反光衣检测:VOC与YOLO标注转换及YOLOv8训练实战

智慧工地安全帽反光衣检测:VOC与YOLO标注转换及YOLOv8训练实战 简介这份智慧工地检测数据集来自真实工地监控摄像头共3065张图像覆盖多视角多场景抓拍面向反光衣穿戴检测、安全帽佩戴检测与人员入侵告警等任务。压缩包共2000个文件以XMLVOC格式标注、TXTYOLO格式标签和JPG图片为主整体约585.74MBLabelImg手工标注可直接用于YOLO系列、SSD、CenterNet等主流算法。数据集充分采样复杂工地背景与不同机位视角有助于提升模型真实场景泛化能力压缩包内附完整图片百度云链接弥补预览图不足。目前已有1433人学习适合算法工程师、科研人员与智慧工地开发者作为训练集或评测集。1. 智慧工地数据集的坑为什么反光衣安全帽检测需要 VOC 和 YOLO 双格式工地监控里真正跑过安全帽检测模型的人都知道COCO 预训练权重直接搬到施工现场误报和漏检会多到没法看。反光衣在阴天、逆光、强曝光下颜色会飘安全帽在远处只有几十个像素而且工人常常互相遮挡这些都不是通用目标检测模型能直接 cover 的场景。这个智慧工地数据集一共 3065 张真实工地监控抓拍来自多个摄像头视角和不同作业时段标注了行人、安全帽、反光衣同时打包了 VOCXML和 YOLOTXT两种标签属于典型的“施工安全检测”训练数据。它的价值在于不用再拿通用模型硬扛也不需要自己爬图、清洗、转标注直接可以进入 YOLO 系列训练流程。适合三类人做施工安全告警系统的一线工程师、想提升监控场景行人检测准确率的算法同学以及需要可靠数据集完成课程设计或课题验证的学生。2. 数据集结构与标注格式从 XML 到 TXT坐标到底怎么换算标签同时给 VOC 和 YOLO不是冗余而是对应两套工具链。VOC 格式适合 LabelImg 手工标注和二次检查YOLO 格式则可以直接喂给 Ultralytics YOLO 训练。理解二者换算关系才是这套数据集真正值钱的地方。2.1 拿到压缩包后先看目录布局解压后通常能看到images/、labels/、xmls/或Annotations/和classes.txt。labels/下是与图片同名的.txt文件xmls/下是同名.xml文件图片则在images/下。先用命令快速核对三类文件数量是否一致find . -name *.jpg | wc -l find . -name *.xml | wc -l find . -name *.txt | wc -l如果图片数不等于 XML 数或者不等于 TXT 数说明存在漏标或者多余标签。常见的情况是缓存文件.DS_Store混入或者某张图被重复标注过。我一般会先用ls看一眼文件名把带有空格、中文、特殊符号的文件批量改名成IMG_0001.jpg这种纯数字再继续下一步。改名的目的是防止后面 Python 脚本读路径时因为转义问题中断尤其是 Windows 环境下反斜杠和中文目录容易踩坑。2.2 VOC XML 标注里的 bndbox 与坐标原点VOC 格式把每个目标的类别和真实框坐标写在 XML 里。以数据集里一张常见的高空作业抓拍为例标注结构如下annotation filenameIMG_001.jpg/filename size width1920/width height1080/height depth3/depth /size object namesafety_vest/name difficult0/difficult bndbox xmin420/xmin ymin310/ymin xmax520/xmax ymax480/ymax /bndbox /object /annotationbndbox里的四个值都是绝对像素坐标原点在图片左上角。xmin/ymin表示框的左上角xmax/ymax表示右下角。这种表示很适合人读也好用 LabelImg 打开继续修正但每个文件体积较大且训练框架解析时需要额外一步。这也是为什么很多实际项目里标注产出是 XML训练输入却是 TXT两者之间需要一次转换。2.3 YOLO txt 标注的归一化坐标每张图片对应的.txt文件里每一行代表一个目标格式如下0 0.2448 0.3657 0.0521 0.1574五个值依次是类别 ID、归一化中心 x、归一化中心 y、归一化宽度、归一化高度。所有值都是相对图片宽高的比例因此范围在 01 之间。用上面 XML 中safety_vest的框来算换算关系非常直观x_center (420 520) / 2 / 1920 0.2448 y_center (310 480) / 2 / 1080 0.3657 w (520 - 420) / 1920 0.0521 h (480 - 310) / 1080 0.1574在这里再放一张两种格式的对比表方便快速查阅也用于后面校验标签对比项VOC XMLYOLO TXT坐标类型绝对像素值归一化浮点值记录内容xmin, ymin, xmax, ymaxclass, x_center, y_center, w, h是否依赖图片尺寸自身带 size 节点可直接读取需结合图片 width/height 理解常用编辑工具LabelImg、LabelStudioLabelImg保存为 YOLO、Ultralytics典型训练框架Darknet、MMDetectionUltralytics YOLOv5/v8、YOLOv11看这个表格就能理解为什么网上下载的数据集经常出现“标签错乱”如果 TXT 里出现大于 1 的坐标说明有人把绝对像素值直接当作归一化值写了进去这种数据必须清洗。提示拿到数据集后先打开任意一个.txt和同名.xml对比一次确认类别 ID 顺序后再开始训练能省掉后面一整天的排查时间。3. 动手转换与清洗把 VOC 标签同步成 YOLO 并切分数据集虽然这个数据集两种格式都已经提供但实际工程里经常只拿到一种标注尤其是从标注平台导出的笼统格式。掌握 VOC 转 YOLO 的脚本能让你在处理其他项目数据时不再受工具链限制。3.1 写一个 VOC 转 YOLO 的脚本下面这段脚本基于 Python 标准库和 OpenCV解析 XML 并输出 YOLO 格式的 TXT 文件。我通常在拿到新数据集的第一时间跑一遍用来验证标签文件是否完整。import os import xml.etree.ElementTree as ET classes [person, safety_helmet, safety_vest] # 以数据集 classes.txt 实际顺序为准 def convert_voc_to_yolo(xml_path, out_dir): tree ET.parse(xml_path) root tree.getroot() img_w int(root.find(size/width).text) img_h int(root.find(size/height).text) lines [] for obj in root.findall(object): name obj.find(name).text if name not in classes: continue cls_id classes.index(name) box obj.find(bndbox) xmin float(box.find(xmin).text) ymin float(box.find(ymin).text) xmax float(box.find(xmax).text) ymax float(box.find(ymax).text) # 坐标换算中心点坐标和宽高都除以图片宽高 x_center (xmin xmax) / 2 / img_w y_center (ymin ymax) / 2 / img_h w (xmax - xmin) / img_w h (ymax - ymin) / img_h if w 0 or h 0: continue lines.append(f{cls_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}) if lines: basename os.path.splitext(os.path.basename(xml_path))[0] with open(os.path.join(out_dir, basename .txt), w) as f: f.write(\n.join(lines)) xml_dir xmls out_dir labels os.makedirs(out_dir, exist_okTrue) for xml_file in os.listdir(xml_dir): if xml_file.endswith(.xml): convert_voc_to_yolo(os.path.join(xml_dir, xml_file), out_dir)逻辑说明脚本先用ET.parse读取 XML然后从size/width和size/height拿到图片实际宽高。每个object的bndbox坐标都除以宽高换算成 01 之间的归一化坐标。classes列表的顺序决定了 TXT 里的类别 ID因此必须和最终训练时data.yaml的names保持一致。脚本中被过滤掉的w 0 or h 0是脏框一般来自标注时误操作真实项目里出现过不少次。3.2 清洗标注过滤无标注图片和过小目标工地监控画面常见 1080p 或 4K 分辨率工人距离摄像头远时头部可能只占画幅的很小一部分。这种小目标在训练时会拉低整体 mAP所以清洗阶段我会先统计一遍框的尺寸分布。import xml.etree.ElementTree as ET from pathlib import Path import collections area_counter collections.Counter() for xml_file in Path(xmls).glob(*.xml): root ET.parse(xml_file).getroot() for obj in root.findall(object): box obj.find(bndbox) w float(box.find(xmax).text) - float(box.find(xmin).text) h float(box.find(ymax).text) - float(box.find(ymin).text) if w 0 or h 0: continue area w * h if area 40 * 40: area_counter[40px] 1 elif area 80 * 80: area_counter[40-80px] 1 else: area_counter[80px] 1 print(area_counter)监控场景下的经验值是小于 40px 的目标基本不具备可判别特征模型要么根本学不到要么引入大量噪声。处理方式有两种一是把这类框直接过滤掉二是在评估阶段单独统计小目标指标。如果过滤后某张图变成空标签需要连图片一起剔除否则训练时会报“no labels found”的警告。3.3 按场景切分训练集和验证集3065 张图如果来自多个摄像头且连续抓拍直接随机划分容易把同一场景的相似画面同时放进训练集和验证集导致模型看起来效果很好换到新摄像头却掉点。稳妥做法是固定随机种子切分保证可复现import os import random import shutil random.seed(42) image_files [f for f in os.listdir(images) if f.endswith(.jpg)] random.shuffle(image_files) split int(len(image_files) * 0.9) os.makedirs(images/train, exist_okTrue) os.makedirs(images/val, exist_okTrue) os.makedirs(labels/train, exist_okTrue) os.makedirs(labels/val, exist_okTrue) for img in image_files[:split]: base os.path.splitext(img)[0] shutil.move(fimages/{img}, images/train/) shutil.move(flabels/{base}.txt, labels/train/) for img in image_files[split:]: base os.path.splitext(img)[0] shutil.move(fimages/{img}, images/val/) shutil.move(flabels/{base}.txt, labels/val/)random.seed(42)固定随机种子保证每次执行划分结果一致。split计算训练集数量这里取 90%剩余 10% 作为验证集。移动文件时图片和标签同名所以用base拼接出对应 TXT 文件名。如果希望保留原始副本shutil.move换成shutil.copy2即可。注意 YOLO 训练的目录要求是images/train、images/val、labels/train、labels/val四个目录同时存在图片和标签路径只能差一个images与labels的目录名。提示划分完成后随便挑 5 张训练集图片打开同名 TXT对照图上目标确认类别 ID 没有错位。这一步 5 分钟能完成却常常是训练结果“跑飞”的真正原因。4. 用 YOLOv8 训练反光衣安全帽检测模型目录结构齐整之后进入训练环节。Ultralytics YOLOv8 是目前接入自定义数据集最快的方式但要让模型在工地监控上真正好用还得把data.yaml、模型大小和训练参数调对。4.1 data.yaml 与类别顺序对齐先打开数据集自带的classes.txt确认类别顺序。这个数据集包含行人、安全帽、反光衣三类但不同版本的标注顺序可能不一样比如有的把safety_helmet放前面有的把person放前面。在项目根目录创建data.yaml时必须严格对照它path: D:/smart_construction_dataset # 改成你的绝对路径 train: images/train val: images/val nc: 3 names: 0: person 1: safety_helmet 2: safety_vestpath指向数据集根目录train和val是相对路径。names索引顺序必须和前面转换脚本里的classes顺序一致否则训练时所有标签都会偏移模型学出来的类别名称和张贴出来的结果对不上。另外建议names全部用英文中文类别名虽然能训但会污染后续生成的混淆矩阵图片字体排查时非常难受。4.2 训练命令与关键参数3065 张图属于中小规模数据集模型选择上yolov8s最稳yolov8m也可以。如果显卡显存只有 8G直接使用yolov8s否则容易卡在 batch size 上。启动训练的命令如下yolo train \ modelyolov8s.pt \ datadata.yaml \ epochs150 \ imgsz640 \ batch16 \ device0 \ patience30 \ projectruns/smart_construction参数含义和调参建议整理成下表参数本数据集建议说明modelyolov8s.pt数据集量不大s 级性价比最高epochs150实际会因早停提前结束imgsz640监控画面常用提升到 1280 会显著变慢batch168G 显存改 8OOM 时优先降这里patience30验证集指标连续 30 epoch 不升则停止device0指定第一张 GPUCPU 训练改 cpu训练结束后runs/smart_construction/exp/weights/best.pt是验证集上表现最好的权重。训练日志里重点看mAP50-95和Recall两列。落地到工地告警场景时漏检一个未戴安全帽的工人比误报一次严重得多所以我更在意Recall的数值。如果发现Precision高但Recall明显偏低说明模型太保守推理时可以把置信度阈值降低到 0.2 左右或者增加训练轮次。4.3 训练时常见的三个坑第一个坑是类别不平衡。反光衣因为穿着面积大、视觉明显框的数量往往比安全帽多很多。两类的框数量差距超过 3 倍时模型会偏向学数量多的类别。解决办法是先用统计脚本算一下每类框数如果确实差异明显可以用cls0.8之类的损失系数调一下或者对样本少的类别做马赛克增强增强。第二个坑是显存不足。如果日志出现CUDA out of memory先把batch减半再把imgsz从 640 降到 512。不要一上来就换模型大多数情况下是 batch 设太高。第三个坑是空标签文件。部分监控画面里没有任何施工人员或标注者漏标了导致labels/train下存在 0 字节 TXT。虽然 YOLO 训练不会直接报错但会让模型学到“当前画面没有目标”的背景模式推理时更容易漏检。清洗阶段要确保空标签删除且对应图片也不出现在训练列表里。5. 监控场景下的验证与误报排查技巧训练完权重不等于能用先拿一段没参与训练的监控片段做批量推理观察框的贴合程度和误报来源。5.1 批量推理并保存可视化结果一条命令就能完成测试集推理yolo predict \ modelruns/smart_construction/exp/weights/best.pt \ sourcetest_imgs/ \ conf0.35 \ saveTrue \ projectruns/predictconf0.35比默认的 0.25 更严格过滤掉背景误报适合智慧工地的固定视角监控。saveTrue会把预测框直接画在图片上并保存到runs/predict/exp/。推理结果里最容易出现三类错误框覆盖不完整、同一目标被重复检测、以及把蓝色围挡或阴影识别成行人。其中重复检测可以通过调低iou值来缓解。5.2 用混淆矩阵反查易混淆类别训练目录下有一张confusion_matrix.png重点看safety_helmet和person之间的混淆项。戴白色安全帽的工人头部区域经常会被同时框成“行人”导致安全帽和行人两个框高度重叠。出现这种情况时可以在后处理里把行人框和头盔框做 IoU 匹配重叠超过 0.5 时只保留置信度更高的类别。这里用 Python 接口快速打印单张结果验证置信度和坐标from ultralytics import YOLO model YOLO(runs/smart_construction/exp/weights/best.pt) results model(test_imgs/IMG_0102.jpg, conf0.35, iou0.6) for r in results: for box in r.boxes: cls int(box.cls.item()) conf float(box.conf.item()) xyxy [round(v, 1) for v in box.xyxy.tolist()[0]] print(model.names[cls], round(conf, 2), xyxy)iou0.6是 NMS 的 IoU 阈值值越小抑制越强。在这个场景下我从默认的 0.7 降到 0.6能有效减少安全帽和反光衣之间的重复框。最后一个只适合工地监控的联动技巧告警逻辑里先检测行人再在行人框内部做安全帽和反光衣的二次判断。因为固定摄像头斜装时远处的人形目标小直接全图检测容易漏掉局部细节。两级检测把范围缩小后误报率通常能降低三成以上。把 IoU 后处理和两级检测结合可以直接接入实时告警流水线这也是这个数据集从训练到工程落地的完整路径。本文还有配套的精品资源点击获取