简介:这份智慧工地检测数据集来自真实工地监控摄像头,共3065张图像,覆盖多视角多场景抓拍,面向反光衣穿戴检测、安全帽佩戴检测与人员入侵告警等任务。压缩包共2000个文件,以XML(VOC格式)标注、TXT(YOLO格式)标签和JPG图片为主,整体约585.74MB,LabelImg手工标注,可直接用于YOLO系列、SSD、CenterNet等主流算法。数据集充分采样复杂工地背景与不同机位视角,有助于提升模型真实场景泛化能力;压缩包内附完整图片百度云链接,弥补预览图不足。目前已有1433人学习,适合算法工程师、科研人员与智慧工地开发者作为训练集或评测集。
1. 智慧工地数据集的坑:为什么反光衣安全帽检测需要 VOC 和 YOLO 双格式
工地监控里真正跑过安全帽检测模型的人都知道,COCO 预训练权重直接搬到施工现场,误报和漏检会多到没法看。反光衣在阴天、逆光、强曝光下颜色会飘,安全帽在远处只有几十个像素,而且工人常常互相遮挡,这些都不是通用目标检测模型能直接 cover 的场景。这个智慧工地数据集一共 3065 张真实工地监控抓拍,来自多个摄像头视角和不同作业时段,标注了行人、安全帽、反光衣,同时打包了 VOC(XML)和 YOLO(TXT)两种标签,属于典型的“施工安全检测”训练数据。它的价值在于,不用再拿通用模型硬扛,也不需要自己爬图、清洗、转标注,直接可以进入 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> <filename>IMG_001.jpg</filename> <size> <width>1920</width> <height>1080</height> <depth>3</depth> </size> <object> <name>safety_vest</name> <difficult>0</difficult> <bndbox> <xmin>420</xmin> <ymin>310</ymin> <xmax>520</xmax> <ymax>480</ymax> </bndbox> </object> </annotation>bndbox里的四个值都是绝对像素坐标,原点在图片左上角。xmin/ymin表示框的左上角,xmax/ymax表示右下角。这种表示很适合人读,也好用 LabelImg 打开继续修正,但每个文件体积较大,且训练框架解析时需要额外一步。这也是为什么很多实际项目里,标注产出是 XML,训练输入却是 TXT,两者之间需要一次转换。
2.3 YOLO txt 标注的归一化坐标
每张图片对应的.txt文件里,每一行代表一个目标,格式如下:
0 0.2448 0.3657 0.0521 0.1574五个值依次是:类别 ID、归一化中心 x、归一化中心 y、归一化宽度、归一化高度。所有值都是相对图片宽高的比例,因此范围在 0~1 之间。用上面 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 XML | YOLO TXT |
|---|---|---|
| 坐标类型 | 绝对像素值 | 归一化浮点值 |
| 记录内容 | xmin, ymin, xmax, ymax | class, x_center, y_center, w, h |
| 是否依赖图片尺寸 | 自身带 size 节点,可直接读取 | 需结合图片 width/height 理解 |
| 常用编辑工具 | LabelImg、LabelStudio | LabelImg(保存为 YOLO)、Ultralytics |
| 典型训练框架 | Darknet、MMDetection | Ultralytics 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_ok=True) 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坐标都除以宽高,换算成 0~1 之间的归一化坐标。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_ok=True) os.makedirs('images/val', exist_ok=True) os.makedirs('labels/train', exist_ok=True) os.makedirs('labels/val', exist_ok=True) for img in image_files[:split]: base = os.path.splitext(img)[0] shutil.move(f'images/{img}', 'images/train/') shutil.move(f'labels/{base}.txt', 'labels/train/') for img in image_files[split:]: base = os.path.splitext(img)[0] shutil.move(f'images/{img}', 'images/val/') shutil.move(f'labels/{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 \ model=yolov8s.pt \ data=data.yaml \ epochs=150 \ imgsz=640 \ batch=16 \ device=0 \ patience=30 \ project=runs/smart_construction参数含义和调参建议整理成下表:
| 参数 | 本数据集建议 | 说明 |
|---|---|---|
| model | yolov8s.pt | 数据集量不大,s 级性价比最高 |
| epochs | 150 | 实际会因早停提前结束 |
| imgsz | 640 | 监控画面常用,提升到 1280 会显著变慢 |
| batch | 16(8G 显存改 8) | OOM 时优先降这里 |
| patience | 30 | 验证集指标连续 30 epoch 不升则停止 |
| device | 0 | 指定第一张 GPU,CPU 训练改 cpu |
训练结束后,runs/smart_construction/exp/weights/best.pt是验证集上表现最好的权重。训练日志里重点看mAP50-95和Recall两列。落地到工地告警场景时,漏检一个未戴安全帽的工人比误报一次严重得多,所以我更在意Recall的数值。如果发现Precision高但Recall明显偏低,说明模型太保守,推理时可以把置信度阈值降低到 0.2 左右,或者增加训练轮次。
4.3 训练时常见的三个坑
第一个坑是类别不平衡。反光衣因为穿着面积大、视觉明显,框的数量往往比安全帽多很多。两类的框数量差距超过 3 倍时,模型会偏向学数量多的类别。解决办法是先用统计脚本算一下每类框数,如果确实差异明显,可以用cls=0.8之类的损失系数调一下,或者对样本少的类别做马赛克增强增强。
第二个坑是显存不足。如果日志出现CUDA out of memory,先把batch减半,再把imgsz从 640 降到 512。不要一上来就换模型,大多数情况下是 batch 设太高。
第三个坑是空标签文件。部分监控画面里没有任何施工人员,或标注者漏标了,导致labels/train下存在 0 字节 TXT。虽然 YOLO 训练不会直接报错,但会让模型学到“当前画面没有目标”的背景模式,推理时更容易漏检。清洗阶段要确保空标签删除,且对应图片也不出现在训练列表里。
5. 监控场景下的验证与误报排查技巧
训练完权重不等于能用,先拿一段没参与训练的监控片段做批量推理,观察框的贴合程度和误报来源。
5.1 批量推理并保存可视化结果
一条命令就能完成测试集推理:
yolo predict \ model=runs/smart_construction/exp/weights/best.pt \ source=test_imgs/ \ conf=0.35 \ save=True \ project=runs/predictconf=0.35比默认的 0.25 更严格,过滤掉背景误报,适合智慧工地的固定视角监控。save=True会把预测框直接画在图片上并保存到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', conf=0.35, iou=0.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)iou=0.6是 NMS 的 IoU 阈值,值越小抑制越强。在这个场景下,我从默认的 0.7 降到 0.6,能有效减少安全帽和反光衣之间的重复框。
最后一个只适合工地监控的联动技巧:告警逻辑里先检测行人,再在行人框内部做安全帽和反光衣的二次判断。因为固定摄像头斜装时,远处的人形目标小,直接全图检测容易漏掉局部细节。两级检测把范围缩小后,误报率通常能降低三成以上。把 IoU 后处理和两级检测结合,可以直接接入实时告警流水线,这也是这个数据集从训练到工程落地的完整路径。
本文还有配套的精品资源,点击获取