简介:建筑工地安全目标检测数据集聚焦建筑施工现场的安全合规识别,面向目标检测开发者、工业安防算法工程师及计算机视觉研究人员,可用于安全帽、反光背心、防护靴等装备佩戴检测,以及未戴安全帽、未佩戴口罩、未穿安全背心等违规行为的自动识别。数据采集自真实工地监控画面,共600张图片,按训练集426张、验证集117张、测试集57张划分,标注YOLO格式边界框与类别,覆盖8个关键类别,整体适配YOLOv5、YOLOv8等主流框架。资源包内共1202个文件,含600张jpg图片、600个txt标注文件、1个yaml配置文件和1个docx说明文档,压缩后约37.43MB,目录规整便于直接训练与二次开发。目前已有264人学习,适合用来快速搭建工地安全监控模型、开展安全违规检测算法验证,也能作为企业安全培训与算法教学的示例数据。
1. 建筑工地安全目标检测数据集.zip:这到底是个“软件”还是“素材包”
打开这个压缩包之前,先给一个反直觉的结论:建筑工地安全目标检测数据集.zip基本不包含任何可运行的代码,它是一堆已经拍好的现场图片,外加对应的标注文件。你需要自己承担“把标注喂给检测框架”的后续工作。很多第一次接触数据集包的人,解压之后对着几百兆的图片一脸懵,以为缺了主程序,其实主程序本来就不在里面。
这个zip能解决的问题很直接:工地上的安全帽佩戴、反光衣穿戴、人员违规闯入等场景,靠人盯监控不现实,目标检测模型就可以在视频流里实时框出“没戴安全帽的人”。对算法工程师、安防方案集成商,以及所有准备在工地视觉方向做验证的人来说,这套数据是训练和评测的基础。它值不值得用,取决于三件事:标注格式你认不认、类别设计合不合理、数据划分你能不能复现。下面按实际落地的顺序,把每一层拆开讲。
2. 打开压缩包先做结构审查:目录层级、标注格式与类别规范
2.1 目录层级与文件命名:先看根目录再看子目录
拿到任何数据集包,我一般不会先解压,而是先压缩软件里看根目录。常见做法是根目录下只有images和annotations两个文件夹,名字也可能是JPEGImages和labels。如果有README或classes.txt,那这个包的制作者还算用心;如果只有一堆散落的图片和XML,你就得自己花时间建结构。
解压后第一步,用tree命令或者文件管理器看层级,重点关注三点:图片和标注是否一一对应、文件名是否有空格或中文字符、有没有划分好的train/val目录。命令行动作用bash:
unzip 建筑工地安全目标检测数据集.zip -d ./site_safety cd ./site_safety tree -L 2参数说明:-d指定解压目标目录,避免文件直接铺满当前目录;tree -L 2只看两层结构,防止输出刷屏。如果系统没有tree,可以用find . -maxdepth 2 -type d替代。这一看就能判断后续要不要做格式整理,花五分钟省半小时。
2.2 标注格式选型:VOC XML、COCO JSON、YOLO TXT的区别
目标检测数据集的核心是标注格式,格式决定你后面用哪个框架顺手。这个领域最常见的三种格式,我把选型理由和边界整理在下面:
| 格式 | 文件形态 | 适合场景 | 主要坑 |
|---|---|---|---|
| VOC XML | 每张图一个XML文件 | 小团队可视化审查、用LabelImg二次标注 | XML字段冗余,解析慢,坐标是绝对坐标 |
| COCO JSON | 整个数据集一个JSON | 大规模管理、跨框架复用、做统一统计 | 修改麻烦,标注错位排查困难 |
| YOLO TXT | 每张图一个TXT | 直接进YOLO系列训练 | 坐标是归一化的,肉眼没法直接看 |
判断你手上这套数据的格式,看annotations文件夹结尾是.xml还是.json,或者labels文件夹里是不是.txt。如果同一份数据同时出现多个格式,以classes.txt里写的类别顺序为准,因为YOLO TXT里的类别ID是按classes.txt的行号编码的,行号对不上,训练出来的模型就全乱了。
2.3 类别设计的合理性:安全帽、反光衣和“负类”
工地安全检测的类别设计,直接决定模型的可用性。一个合格的数据集,类别通常不会只有“person”和“helmet”两类。常见的类别设计包括:hard-hat(安全帽)、reflective-vest(反光衣)、person(人员)、no-hard-hat(没戴安全帽的人)。这里有个容易被新手忽略的设计点:很多数据集把“没戴安全帽的人”也单独列为类别,而不是只标“安全帽”。
这样做的好处是训练时模型既要学会识别“有帽子”的正样本,也要学会识别“没帽子”的反例,推理时直接输出违规目标。如果你的包里面没有no-hard-hat这个类别,训练时就要用“person区域减去被hard-hat覆盖的区域”做后处理,或者干脆把未戴帽人员标为负样本,单独做一次分类。这种方案也能跑,但在现场精度和召回率往往不如直接列负类的版本。
3. 校验与数据划分:把原始素材变成可复现的训练集
3.1 图片-标签一致性校验:漏标和错位是最隐蔽的雷
解压后最该做的不是急着训练,而是校验图片和标注是不是一一对应。工地数据集常见的问题包括:某张图有XML但没JPEG、图片文件名和XML文件名前缀不一致、XML里的filename字段写错了但实际文件名是对的。这些错位问题在训练时表现为“警告:未找到图像”,严重时导致训练中途崩溃。
我用一段Python做基础校验,逻辑是比对文件名集合,再抽查XML里的实际框坐标是否超出图片尺寸:
import os from xml.etree import ElementTree as ET from PIL import Image img_dir = "./site_safety/images" ann_dir = "./site_safety/annotations" imgs = {os.path.splitext(f)[0] for f in os.listdir(img_dir) if f.endswith((".jpg", ".jpeg", ".png"))} anns = {os.path.splitext(f)[0] for f in os.listdir(ann_dir) if f.endswith(".xml")} print("缺标注的图片:", len(imgs - anns)) print("无图片的标注:", len(anns - imgs)) # 抽查前20个XML,检查坐标是否越界 for name in sorted(anns)[:20]: tree = ET.parse(os.path.join(ann_dir, name + ".xml")) for obj in tree.findall("object"): 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) with Image.open(os.path.join(img_dir, name + ".jpg")) as im: w, h = im.size if xmax > w or ymax > h or xmin < 0 or ymin < 0: print(f"越界: {name} -> {xmin},{ymin},{xmax},{ymax}, 图尺寸 {w}x{h}")逻辑说明:第一段用集合差找出缺失文件;第二段抽查前20个XML,把标注框解析出来和图片实际尺寸比对。PIL的作用是读取图片宽高,ElementTree解析XML。这段代码在遇到标注问题时能迅速定位是哪个文件、越界了多少,不会让你在训练日志里大海捞针。
3.2 划分train/val/test:随机分割也要讲究
直接整包丢进训练脚本是个高频翻车行为。工地数据集的拍摄时间、天气、角度往往集中在同一时段,如果不打乱直接按目录顺序划分,训练集和验证集可能高度同源,val指标好看但现场一测就掉点。我一般做法是:按文件名哈希或随机数分割,但固定随机种子,保证可复现。
import os import random import shutil random.seed(42) # 固定种子,复现每次划分 img_dir = "./site_safety/images" train_dir, val_dir, test_dir = "./data/images/train", "./data/images/val", "./data/images/test" for d in [train_dir, val_dir, test_dir]: os.makedirs(d, exist_ok=True) files = [f for f in os.listdir(img_dir) if f.endswith(".jpg")] random.shuffle(files) n = len(files) train_files = files[:int(n * 0.7)] val_files = files[int(n * 0.7):int(n * 0.85)] test_files = files[int(n * 0.85):] for f in train_files: shutil.copy(os.path.join(img_dir, f), os.path.join(train_dir, f)) # val/test 同理逻辑说明:先打乱文件列表再切三份,这样同一个工地的连续帧不会全挤在训练集里。测试集单独留出来很重要,因为验证集在训练过程中会被用来调参,测试集是你最后评估模型用的,碰都不能碰。比例我习惯用7:1.5:1.5,如果你这套包本身已经带了划分好的目录,就不需要再做这步,但至少要把val里是否混入训练集做一次查重,用文件名做交集即可。
3.3 把VOC XML转成YOLO TXT:坐标换算与类别映射
如果你想用Ultralytics YOLO系列来训练(现在yolov8训练自己的数据集已经是主流路线),就得把VOC XML转成YOLO TXT。这个步骤的坑在于坐标需要换算成归一化值,而且类别ID必须和类别列表的行号一致。
import os from xml.etree import ElementTree as ET classes = ["hard-hat", "reflective-vest", "person", "no-hard-hat"] # 以classes.txt为准 def convert(xml_file, out_file): tree = ET.parse(xml_file) root = tree.getroot() w = int(root.find("size/width").text) 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) xmax = float(box.find("xmax").text) ymin = float(box.find("ymin").text) ymax = float(box.find("ymax").text) x_c = ((xmin + xmax) / 2) / w y_c = ((ymin + ymax) / 2) / h ww = (xmax - xmin) / w hh = (ymax - ymin) / h lines.append(f"{cls_id} {x_c:.6f} {y_c:.6f} {ww:.6f} {hh:.6f}") with open(out_file, "w") as f: f.write("\n".join(lines)) xml_dir = "./site_safety/annotations" out_dir = "./site_safety/labels" os.makedirs(out_dir, exist_ok=True) for xml in os.listdir(xml_dir): if xml.endswith(".xml"): convert(os.path.join(xml_dir, xml), os.path.join(out_dir, xml.replace(".xml", ".txt")))逻辑说明:每行记录是“类别ID 中心x 中心y 宽 高”,全部归一化到0~1区间。注意这段代码里if name not in classes的过滤逻辑,如果你的数据集里出现了classes.txt之外的类别名,比如写成了“hard hat”带空格,这里会直接跳过,训练时该图的标注就少了一个目标。实际处理时应该打印过滤掉的类别名,确认是不是拼写错误,而不是悄悄丢掉。
4. 训练前置与参数设计:样本尺寸、小目标增强与类别不均衡
4.1 样本尺寸归一化:先看分辨率分布再定训练尺寸
工地监控截图的分辨率通常不统一,有的来自1080P录像截帧,有的是抓拍机出图,尺寸参差。训练前我用Python统计一遍所有图片的分辨率分布,然后决定要不要做letterbox统一。
from PIL import Image import os img_dir = "./site_safety/images" sizes = {} for f in os.listdir(img_dir): if f.endswith(".jpg"): with Image.open(os.path.join(img_dir, f)) as im: w, h = im.size key = f"{w}x{h}" sizes[key] = sizes.get(key, 0) + 1 for size, count in sorted(sizes.items(), key=lambda x: -x[1]): print(size, count)逻辑说明:统计结果能直接告诉你数据集的“主流分辨率”。如果大多数是1920x1080,个别是1280x720,训练尺寸设640或者1280都合理;如果有大量低分辨率图片混在里面,建议用letterbox统一到固定尺寸而不是直接resize。letterbox会保持宽高比,防止目标变形导致检测框偏移。YOLO在训练时自带letterbox处理,但如果你用的是MMDetection的默认配置,训练前就要想清楚输入尺寸,因为尺寸不一致会导致batch训练时数据加载报错。
4.2 小目标训练策略:安全帽和反光衣在画面里可能只有30像素
工地场景里,中远距离的人头部占的面积非常小,安全帽目标经常只有20到40像素宽。这对目标检测是典型的“小目标问题”。小目标在多次下采样后特征几乎消失,模型学不到有效信息。常见做法有三种:多尺度训练、复制粘贴增强、切图训练。
Ultralytics YOLO系列里,多尺度训练可以直接通过配置项实现。用yolov8训练自己的数据集时,训练命令可以这样:
yolo train data=site.yaml model=yolov8s.pt imgsz=1280 epochs=150 batch=16 device=0,1参数说明:imgsz=1280是增大输入分辨率的直接方式,小目标像素占比会从原来的2%提升到接近5%,特征更明显;device=0,1指定双卡。但注意,输入尺寸提高一倍,显存占用涨四倍,如果你的显卡只有8GB显存,batch设8都够呛,需要配合梯度累积或者换yolov8n这类轻量骨干。
还有一种场景级的增强叫copy-paste,把标注好的小目标对象随机复制粘贴到其他图片上,增加其在训练中的出现频次。实操上,我用一个现成Python脚本生成增强样本,然后在训练配置里把增强后的图片一并纳入训练集,不做在线增强,因为离线增强能把边界情况控制得更明确。
4.3 类别不均衡:反光衣标注数量少,权重怎么配
工地数据集里,反光衣和人员往往数量不均衡。原因很现实:夏天工人可能不穿反光衣但戴安全帽,因而反光衣的正样本数远少于安全帽。训练时模型会偏向多数类,导致反光衣召回率极低。处理这个问题的两个常用手段:重采样和损失权重。
MMDetection系列可以在train_cfg里配置类别权重,或者直接在数据pipeline里做上下采样。如果用Ultralytics YOLO,没法直接给每个类别设损失权重,但可以在训练后处理阶段做阈值调整:对低召回类别降低置信度阈值,提高其检出数量。这个方法在验证集上很快能看到效果,但也容易误检,需要配合现场实测。
另一个容易被忽视的点是:检查标注里是否有人体重叠严重的情况。工地人多时,前景工人把后面工人的安全帽挡住,标注员容易漏标后面的目标。这部分漏标数据放进训练集,等于教模型“被遮挡的安全帽不算安全帽”,评测时反而会觉得模型表现不错,因为验证集里同样漏标了。遇到这种情况,我一般会把多名工人高度遮挡的图片单独抽出做人工二次检查。
5. 标注与训练的避坑实录:五个真实翻车现场
5.1 类别名带空格导致标签错位
现象:训练正常启动,但跑了100个epoch后,验证集mAP只有0.3,远低于预期。排查发现某些目标被正确框出,但类别全乱了。
原因:annotations里的类别名是“hard hat”,而classes.txt里是“hard-hat”,XML转YOLO时“hard hat”未匹配到classes,被跳过或分配了错误的ID。
解决:写一段脚本扫描所有XML里的name字段,和classes.txt做严格比对,把拼写不一致的项统一替换。别试图靠训练时的容忍机制兜底,它不会兜底,只会悄悄降低精度。
5.2 图片EXIF旋转信息导致标注坐标整体偏移
现象:训练时loss下降正常,但推理时发现部分图片的检测框位置整体向右下方偏了。所有出问题的图片在手机或相机预览里方向正常,在Python读入后旋转了90度。
原因:部分现场照片由手机拍摄,EXIF信息里带有Orientation标记,但标注软件读原始像素坐标时没有处理旋转,导致标注坐标和实际像素对不上。
解决:在数据整理阶段用Pillow统一处理,ImageOps.exif_transpose(im)后再保存,把EXIF旋转物理化。处理完重新做一次标注框越界检查,跑一遍3.1节的校验代码,越界问题会立刻暴露。
5.3 验证集与训练集同源,val mAP虚高
现象:训练完成后val mAP高达0.9,但在工地现场自拍的测试视频上,检测率明显偏低。回看数据划分,发现划分前没有打乱顺序,前70%的图片来自同一个机位,后30%也来自同一时段,两者高度相似。
原因:数据采集时往往是连续截图,相邻帧之间场景重叠率极高。按顺序切分后,训练集和验证集其实共享了大量几乎相同的画面,验证失去了意义。
解决:划分前统一打乱,并且对帧连续的场景做去重。一般做法是计算相邻帧的感知哈希,比如dHash,相似度超过阈值的只保留一张。这样划分出来的验证集才真正反映模型在陌生场景上的表现。
5.4 负样本缺失导致误检爆炸
现象:模型在工地现场频频把红色安全帽误检为反光衣,把蓝色堆料布框成人员。训练集里几乎没有包含这类干扰物的负样本图。
原因:数据集包只提供了有目标的“正样本”图片,没有采集“空场景”负样本。工地环境里很多物体颜色、纹理和目标高度相似,模型没有见过足够多的负样本就无法学到区分边界。
解决:在训练集中加入负样本图片(没有目标的纯背景),并标注为空文件,也就是每个负样本的TXT文件不写任何内容。常见做法是从原始视频里截取大量没有人员出现的画面加入数据集,让模型学会抑制背景误判。
5.5 遮挡严重时“未戴帽”和“被遮挡”无法区分
现象:后景人员头部被前景人员头盔挡住,模型把“被遮挡的未戴帽人员”标为no-hard-hat,又因为该人员实际没戴帽,标注员也标了no-hard-hat,看起来模型很准,但现场有人戴帽却被前景遮挡时,模型反而报违规。
原因:只有边界框标注,没有可见性标注,模型只能学“这个位置是否有目标”,无法区分“目标不可见”和“目标确实违规”。
解决:遇到这种场景,要么在数据集中加入遮挡程度标签,做两级分类;要么在推理后处理阶段加入逻辑:当no-hard-hat的检测框与hard-hat的检测框高度重叠时,合并为遮挡样本,不触发告警。逻辑简单但效果明显,是血泪经验换来的。
6. 上线前的验证技巧:先看热力图再谈指标
训练完不要急着部署。我经历的教训是,只看验证集mAP就上线的模型,第一次去工地现场就翻车了。对着监控画面跑了一个小时,把消防水管误检成了安全员,当时的感受是:指标再漂亮不如现场跑一遍。
这里给一个低成本但有效的验证流程:挑一段5分钟的工地监控视频,切成帧,用训练好的模型逐帧推理,把所有预测结果集中渲染成视频,肉眼过一遍。注意看误检是否集中在阴影、红色管道、蓝色铁皮这类“看起来很接近”的区域。如果误检密集,大概率模型学到的是颜色纹理,而不是目标结构。这时可以用类激活热力图去验证,看模型对目标的关键响应区域是在头部(帽子区域)还是在全身。如果响应区域分散在胸口或腿部,说明模型没有学到“帽子的结构特征”,而是学到“穿反光衣的人”。
这套验证做完,再谈参数调优。工地场景一般需要调整两个推理参数:置信度阈值和IoU阈值。置信度阈值默认0.25对工地偏敏感,误报多;我一般调到0.4到0.5,反光衣这类结构特征明显的类别可以保持在0.3。IoU阈值用来过滤重复框,工地人员密集,默认0.6会导致相邻人员的框被过滤掉一个,建议降0.45。
最后一个方向:如果你后续想做塔吊、挖掘机这类带角度的设备检测,可以往旋转目标检测那条线扩展,MMRotate训练DOTA格式的数据集就是成熟的路线。但前提是把当前数据整理好,至少保证每张图的标注框和类别完全干净,再谈模型结构升级。数据不干净,什么结构都救不回来。希望这份踩坑清单能帮你少走一段弯路。
本文还有配套的精品资源,点击获取