☰
YOLOv11安全帽检测与高空作业预警:训练调参到Jetson部署实战
2026/9/29 2:44:17 网站建设 项目流程

简介:面向建筑工程安全管理与计算机视觉应用人群,这份《建筑工程中的YOLOv11-安全帽检测与高空作业风险预警实战》PDF文档系统讲解了如何利用YOLOv11单阶段检测算法完成施工现场安全帽佩戴识别与高空作业风险预警。文档从YOLO系列发展历程、网络结构和工作原理切入,完整覆盖安全帽数据集收集、标注、预处理,到模型训练、优化与评估,再到风险预警系统的需求分析、架构设计、实现部署与实战案例,内容贴近工程落地场景,适合有初步深度学习基础、希望将目标检测技术应用到工地安全监控的开发者或研究人员参考。资源包共1个PDF文件,大小2.24MB,文档共37页,支持目录章节跳转及阅读器左侧大纲快速定位,排版清晰、图文表完整。目前已有46人学习下载。读者可借此掌握从数据标注(如LabelImg、RectLabel、LabelBox)到模型调优(剪枝、量化)的完整实施链路,并理解预警阈值设定、界面设计等系统集成细节,是一份可快速上手的实战型参考资料。

1. 安全帽检测与高空作业预警:为什么这个项目大家都要用YOLOv11

我最早接触 YOLOv11-安全帽检测与高空作业风险预警这个方向,是在一个工地智能化改造的需求单上。摄像头已经架到塔吊和爬架上,拍到的人小、帽子更小,旧模型要么把黄色油漆桶当帽子,要么戴着安全帽的人也漏掉。换到 YOLOv11 之后,小目标检测能力和推理速度才真正压得住工地现场的场景。这篇笔记就是把我从准备数据、训练调参到 Jetson 上部署、联动高空作业预警这一整条链路拆开讲,给准备上这个项目的团队省掉我踩过的那些坑。适合的对象很明确:有摄像头和标注数据,想在本地跑通验证,再决定要不要买算力卡的人。你不需要先懂论文公式,但你要会跑命令行、会看损失曲线。

2. 准备YOLOv11环境与训练数据:从Anaconda到数据集划分

2.1 YOLOv11的网络结构:三个模块决定了你训练上限

做工程的人常问“我要不要改网络结构”,回答这个问题前得先知道 YOLOv11 由哪几块组成。它仍然是 backbone + neck + head 的框架,backbone 负责提特征,neck 负责把不同尺度的特征融合,head 负责输出分类和回归框。真正和“安全帽检测”相关的改动集中在 head 部分的解耦结构:分类分支和回归分支分开,这让小尺寸的帽子不会因为分类压力太大而被回归分支带偏。另一个值得关注的是 C3k2 模块,它替代了早期的 C3,在保持特征复用的同时计算更省,这让 YOLOv11 在 Jetson 这类低算力设备上还有机会跑实时。

实际调优时不需要重写这些模块,你要做的是理解两个选择。第一,模型尺寸选 n、s、m 还是 l,工地边缘盒子一般选 n 或 s;第二,是否引入注意力机制,比如热词里的 HCANet,它的思路是对通道和空间两个维度做并行注意力,放到 neck 输出端能让“远处的小安全帽”在特征图上有更强响应。不要一上来就魔改 backbone,先在基线模型上把数据质量做对,再决定动哪里。

2.2 环境配置:一个能跑起来的minimal命令

YOLOv11 环境配置最怕的是把 PyTorch、CUDA、cuDNN 版本弄得各管各,最后训练时 OSError 崩掉。我一般用 Anaconda 建独立环境,Python 版本锁在 3.10。CUDA 先看显卡驱动支持的最高版本,再决定装哪一版 PyTorch,而不是反过来装最新版。下面这套是在 Linux 服务器上的最小命令,Windows 上把activate换成conda activate,CUDA 换成对应的 cu121 轮子:

conda create -n yolo11 python=3.10 -y conda activate yolo11 pip install torch==2.1.0 torchvision==0.16.0 --index-url https://download.pytorch.org/whl/cu121 pip install ultralytics

第一行创建独立环境,隔离系统自带的 Python;第二行激活;第三行装 PyTorch 时要特别注意--index-url指定 CUDA 12.1 的轮子,--index-url参数会把 torch 和 torchvision 同时固定到 cu121;最后一行装 Ultralytics 官方库,它内部会带上训练、验证、导出的 CLI 工具。装完后别急着训练,先跑一个yolo predict model=yolo11n.pt source=bus.jpg验证推理链路通不通。这一步花五分钟,能排除八成后续报错。

2.3 标注与数据划分:不要被安全帽数据集的坑带着走

安全帽检测的数据集公开的不少,但工地上用会有两个问题。一是“帽子戴在头上”的样本远多于“帽子拿在手里”的样本,模型会学成“头上有帽”,而不是“帽在头上”;二是远景安全帽只有几个像素,标注框稍微画大一点,框中心偏移就够模型学乱。因此我强烈建议你如果手头有现场摄像头,至少自己补标 30% 的镜头画面,不要全部依赖公开数据集。

标注格式我选择 YOLO 的 txt,一行代表一个目标:class x_center y_center width height,坐标都是归一化到 0~1 的小数。LabelMe 导出的 JSON 或 VOC 的 XML 都要先转成这个格式再喂给 YOLOv11。数据划分上,我的习惯是训练集 70%、验证集 20%、测试集 10%,而且划分时按“同一个摄像头视角”分组,避免同一个场景的视频帧同时出现在训练和验证里,导致验证分数虚高。

import os, random, shutil dataset = 'datasets/helmet_project' images = [f for f in os.listdir(f'{dataset}/images') if f.endswith('.jpg')] random.seed(42) random.shuffle(images) train, val, test = images[:int(len(images)*0.7)], images[int(len(images)*0.7):int(len(images)*0.9)], images[int(len(images)*0.9):] for name, split in [('train', train), ('val', val), ('test', test)]: os.makedirs(f'{dataset}/{split}/images', exist_ok=True) os.makedirs(f'{dataset}/{split}/labels', exist_ok=True) for img in split: shutil.copy(f'{dataset}/images/{img}', f'{dataset}/{split}/images/{img}') label = img.replace('.jpg', '.txt') if os.path.exists(f'{dataset}/labels/{label}'): shutil.copy(f'{dataset}/labels/{label}', f'{dataset}/{split}/labels/{label}')

这段脚本先把所有图片列表打乱,再按 7:2:1 切出三个文件夹,同时把同名 label 拷过去。random.seed(42)很关键,否则每次运行分组不同,复现实验就变猜大小。实际项目中你还要检查切分后每个类别的样本数比例,安全帽类别样本太少时先做简单复制增强,否则训练时类别损失会把少数类压没。

3. 训练安全帽检测模型:参数设置、小目标优化与改进

3.1 基线训练:一条完整的命令行

数据准备完成之后,先用 YOLOv11 官方配置跑一个基线,不做任何魔改。这一步的意义是建立“分母”,后面所有改进都要和它对比。我的建议是从yolo11s开始,因为n虽然快但小目标检测上限低,m在边缘设备又跑不动。训练命令如下:

yolo detect train \ model=yolo11s.pt \ data=helmet.yaml \ imgsz=640 \ epochs=120 \ batch=16 \ device=0 \ patience=20 \ project=runs/train \ name=helmet_baseline

参数逐一说清楚:model=yolo11s.pt是从官方预训练权重继续训练,不要从随机权重开始;data=helmet.yaml指向你数据集配置,里面写训练/验证路径和类别名;imgsz=640是输入分辨率,安全帽小目标场景我后面会调大;batch=16取决于显存,12G 卡这个数比较稳;patience=20是连续 20 个 epoch 验证损失不下降就停止,避免无效烧卡。

跑起来之后你要盯两个东西:train/box_loss和train/cls_loss曲线。如果你看到 box_loss 快速下降后停滞,而 cls_loss 还在波动,说明模型在“找得到位置但认不清类别”,这往往是小目标类别语义不足,不是训练轮数不够。这时候不要盲目加 epoch,先看下一节的小目标优化。

3.2 必调参数:imgsz、epochs、batch、patience 的含义与建议值

这四个参数每个都直接影响结果,但它们的作用点完全不同。imgsz影响输入图像被缩放到多大,安全帽在一张 1080p 画面里可能只有 20×20 像素,缩到 640 就只剩 12×12 左右,特征图上的响应会被池化抹掉,所以小目标场景我至少开到 960,代价是训练和推理变慢;epochs不是越大越好,训练后期模型会过拟合现场光影,我见过有人跑到 300 个 epoch,验证集 mAP 反而掉了;batch影响 BN 统计量的稳定性,batch 太小比如 4,安全帽这种样本数不平衡的数据集很容易在最后几十个 epoch 震荡。

patience很多新手直接不设,结果模型在验证集上已经过拟合两周还在跑。我的建议是设成epochs的 15%~20%,比如 120 个 epoch 就设 20。有时你重启训练会发现损失曲线比之前掉得更快,那是因为 warmup 又跑了一遍,不是模型变好了,不用慌。模型在验证集上的 mAP50-95 连续 20 个 epoch 不涨时就该停,再去分析数据而不是加轮次。

3.3 小目标优化:为什么安全帽总是漏检,我做了三件事

安全帽检测最大的痛是漏检,不是误检。漏检集中在两个位置:远处出入口的小帽檐和俯视视角下只露出头顶的小圆点。针对这两个情况,我按顺序做了三件事,每一步都重新测验证集。

第一步调高输入分辨率到 960,同时把mosaic增强概率从默认 1.0 降到 0.5。Mosaic 把四张图拼在一起训练,能丰富背景,但安全帽框本身太小,拼图后帽子被裁掉的概率变大,反而教坏模型。第二步修改数据增强参数,让hsv_h降低到 0.01,工地安全帽的黄色和红色是一类强语义特征,颜色抖动太狠会让模型靠纹理而不是颜色来认帽子。第三步是给 loss 里的小目标分配更高权重,这一步在配置文件里改:

# helmet.yaml 中的关键增强与损失配置 task: detect imgsz: 960 augment: mosaic: 0.5 hsv_h: 0.01 hsv_s: 0.5 hsv_v: 0.5 loss: box: 7.5 cls: 0.5 dfl: 1.5

box: 7.5表示框回归损失权重高于默认,小目标位置稍有偏差 IoU 就掉很多,需要拉高让模型更在意框准;cls: 0.5相对低,是因为安全帽类别数少且差异明显,不需要给分类太大压力。跑通后对比基线,你会发现 mAP50 可能只涨 1~2 个点,但漏检帧数减少三分之一,这个指标比 mAP 更贴近工地验收。

读者要注意,小目标优化没有银弹。你还需要检查 label 的质量:很多“漏检”其实是标注框比目标大一圈,帽檐的语义完全落在框内灰边里,模型学不到帽檐边界。把这类坏标注清洗一轮,效果比改任何参数都明显。

3.4 从YOLOv11到改进模型:HCANet 注意力的嵌入思路

当数据质量和分辨率都到头了,再往下挖就需要改结构。热词里的 HCANet 是一种混合注意力网络,核心思想是通道注意力与空间注意力并行,再把两路特征相加后与原始主干特征相乘。嵌入到 YOLOv11 时,我选择的插入点是 neck 的上采样之后,也就是 P3、P4 特征即将进入检测头之前。

下面这段示意代码展示了在 YOLOv11 的 head 前插入一个简化的 HCANet 模块,工程上你可以把这段写成自定义 nn.Module 再挂到模型上:

import torch import torch.nn as nn class HCANet_Block(nn.Module): def __init__(self, channels): super().__init__() self.ch_att = nn.Sequential( nn.AdaptiveAvgPool2d(1), nn.Conv2d(channels, channels // 4, kernel_size=1), nn.ReLU(inplace=True), nn.Conv2d(channels // 4, channels, kernel_size=1), nn.Sigmoid() ) self.sp_att = nn.Sequential( nn.Conv2d(channels, 1, kernel_size=7, padding=3), nn.Sigmoid() ) def forward(self, x): ch = self.ch_att(x) sp = self.sp_att(x) return x * (ch + sp)

通道注意力用全局平均池化计算每个通道的重要性,空间注意力用 7×7 卷积计算每个像素位置的重要性,二者相加后与原始特征相乘。这样做的好处是远处安全帽的微弱响应会乘上一个大于 1 的空间权重,但要注意channels // 4后的通道数不能太小,我试过在 Jetson 上通道压缩到 8,推理时间反而变长,因为低算力设备对逐元素乘法更敏感。嵌入方式不唯一,常见做法是加在Detect层的前面,然后用ultralytics的model.load接口加载改动后的权重。

插入模块之后必须重新小学习率训练,我从lr0=0.0005开始,比基线小一半,否则预训练权重会被注意力模块的随机初始化破坏掉。如果你发现加入注意力后 mAP 反而降了,先检查是不是训练轮数不够,这个模块收敛慢,我通常比基线多训练 40 个 epoch 才能看到收益。

4. 推理结果保存与高空作业风险预警:从单张到视频流

4.1 保存检测结果:两种常用方式

很多人在 Ultralytics 里跑完检测不知道结果存哪。YOLOv11 默认会保存在runs/detect/predict下面,带标注框的原图和标注后的 label 文本都有。如果你要在自己的业务系统里用,我一般用下面两种方式之一。

第一种是直接调用model.predict并指定save=True,适合批量离线分析历史监控片段:

from ultralytics import YOLO model = YOLO("runs/train/helmet_baseline/weights/best.pt") results = model.predict( source="demo_clip.mp4", imgsz=960, conf=0.35, save=True, save_txt=True, project="runs/infer_helmet" )

这里的conf=0.35是置信度阈值,工地现场光线杂我一般放 0.35,室内场景可以放在 0.45;save_txt=True会把每个目标的类别和坐标写成labels下的 txt 文件,这个文件就是后面风险预警的输入。结果保存在project指定的目录里,不会覆盖原视频。

第二种是实时逐帧处理时,不保存图片而是把结果对象直接传给业务函数。这样省掉磁盘 IO,还能在每帧里做判断。

4.2 预测后保存:如何把坐标和置信度落到结构体里

预测之后真正有用的是boxes对象。它包含xywhn(归一化坐标)、conf和cls。我会把一帧的检测结果整理成列表,再交给后续的风险预警模块,这样代码边界清晰,调试也方便:

def parse_results(result, shape): parsed = [] boxes = result.boxes if boxes is None: return parsed xywhn = boxes.xywhn.cpu().numpy() confs = boxes.conf.cpu().numpy() clss = boxes.cls.cpu().numpy().astype(int) for i in range(len(clss)): x_c, y_c, w, h = xywhn[i] if confs[i] < 0.35: continue parsed.append({ "class": int(clss[i]), "conf": float(confs[i]), "x": float(x_c), "y": float(y_c), "w": float(w), "h": float(h) }) return parsed

xywhn是归一化中心点坐标,不管原图是 1080p 还是 4K,都换算成 0~1 的小数。这样后面做“画面区域划分”时不会受分辨率影响。shape参数是你自己传入的原图尺寸,如果想转成像素坐标就在这步乘回去。常见错误是直接拿归一化坐标去画像素框,结果框漂移。另外注意每个循环别忘置信度过滤,否则后面会把一堆 0.1 置信度的噪点框当成真实目标。

4.3 高空作业风险预警逻辑:安全帽状态与区域规则的组合

很多团队做到检测就停了,但标题里的“风险预警”才是验收重点。高空作业预警不是“有人没戴安全帽就报警”这么简单,否则工地上会全天误报。我实现的规则分两层。

第一层是“人-帽子”配对。检测模型输出的类别我设为person和helmet两类,然后用同一个人的检测框来找对应安全帽框。判据是安全帽框的中心点是否落在人框的头部区域(人框上 20% 高度范围内),并且帽框面积与人框面积之比要在 8% 到 30% 之间。这个比值区间很关键:太大说明帽子框包含整个头,太小说明是误检远处的杂物。第二层是区域规则。通过画多边形区域判定作业区,只有人框中心点进入危险区域,才触发预警。下面这段是核心判断逻辑:

def judge_risk(person, helmet_list, danger_polygon): # person 和 helmet_list 都是 parse_results 输出的对象 person_x, person_y, person_h = person["x"], person["y"], person["h"] head_top_y = person_y - person_h / 2 safe_zone_y = head_top_y + 0.1 * person_h has_helmet = False for h in helmet_list: # 帽框中心是否在头部区域 if abs(h["x"] - person_x) < person_h * 0.35: if head_top_y < h["y"] < safe_zone_y: has_helmet = True break # 点是否落入多边形危险区 inside = cv2.pointPolygonTest(danger_polygon, (person_x * frame_w, person_y * frame_h), False) >= 0 return inside and not has_helmet

危险区域多边形的顶点用标注工具画一次,固化到配置文件里。注意pointPolygonTest接收的是像素坐标,所以前面parse_results拿到的中心点要乘回画面宽高。实际工地上,爬架区域往往被防护网遮挡,人的检测框会时断时续,这时我会加一个“连续 3 帧内超过 2 帧触发”的缓冲,避免单帧丢框导致误报。cv2的光照变化会让预测不稳定,必要时在进检测模型前先做 CLAHE 增强。

4.4 摄像头场景下的帧率控制

YOLOv11s 在 GPU 上能跑到 60fps 以上,但到 IPC 摄像头流里就未必,因为拉流解码和推流上报都可能卡住。我在部署中固定一个方案:用 OpenCVVideoCapture读,检测线程和显示线程分开。检测线程只对抽帧后的图像处理,每 3 帧取 1 帧作为检测帧;中间帧沿用上一帧的目标位置,利用视频连续性减少压力。

重点关注队列积压问题:如果检测线程处理帧的速度赶不上抽帧,queue会堆积,延迟越来越高。我常用的做法是检测线程开始前grab一帧直接丢掉,相当于主动丢帧,保证处理的是最新画面。对于 Jetson 这类设备,宁愿降低到 8~10fps 保持稳定延迟,也不要卡顿 5 秒后才看到上一秒的图像。部署监控画面上看到延迟超过 500ms 时,优先检查拉流缓冲,而不是去看模型耗时。

5. 部署到Jetson Nano与常见问题排查:血泪经验

5.1 Jetson Nano部署YOLOv11的完整步骤

工地边缘盒子里 Jetson Nano 很常见,但它只有 4G 内存,部署 YOLOv11 需要一套完全不同于服务器的流程。我的第一步是刷 JetPack 4.6,它自带 CUDA 10.2,与最新版 PyTorch 冲突很大。Ultralytics 官方没有直接给 Nano 的安装包,所以用下面的步骤从源码装 torch:

apt-get update && apt-get install -y python3-pip libopenblas-dev pip3 install numpy==1.19.5 wget https://github.com/ultralytics/yolov5/releases/download/v1.0/pytorch-v1.9.0.whl pip3 install pytorch-v1.9.0.whl pip3 install ultralytics

第一行装 OpenBLAS 是 torch 依赖的线性代数库;第二行锁 numpy 版本,太高版本的 numpy 在 JetPack 的 aarch64 Python 上会编译失败;第三行这里是示意,实际要找到对应 JetPack 版本的 torch 轮子名。装完 Ultralytics 后,先导出成 TensorRT 引擎再推理。不要直接在 Nano 上用.pt文件做预测,PyTorch 推理吃内存,跑几帧就 OOM。

导出命令一般是这样:

yolo export model=runs/train/helmet_baseline/weights/best.pt format=engine device=0

format=engine要求你已装好 TensorRT 版本,JetPack 自带trtexec。导出前建议先把imgsz=640固定,因为 engine 文件是绑定输入尺寸的,导出后就不能随便改。我在 Nano 上实际场景只跑 640 分辨率,因为 960 会让 engine 占用内存超过 1.4G,和系统共用 4G 时就容易崩。

5.2 编译问题与内存限制

最典型的问题是在 Nano 上pip install ultralytics时,编译torchvision报No matching distribution found。原因是 PyPI 默认仓库没有 aarch64 的 torch 轮子。解决方法是换用 NVIDIA 的官方索引,或者直接下载 JetPack 对应的轮子文件离线安装。另一个问题是 4G 内存无法同时跑推理和 GUI,我在部署时用systemd把检测进程设为开机自启,只保留 SSH,不启动桌面。

5.3 模型加载失败:现象、原因、解决记录

我在项目现场遇到过三次“加载引擎文件失败”。第一次,现象是Engine deserialization failed。原因是我在服务器上导出 engine,然后直接拷到 Nano 上,两边的 TensorRT 版本不一致。解决方法是必须在 Nano 本机重新导出,或者保证两端 TensorRT 大版本完全一致。

第二次,现象是加载成功但推理几秒后CUDA out of memory。原因是我没有检查其他进程占显存,nvtop一看还有个残留的 Python 进程。解决方法是先kill掉旧进程,再设置torch.cuda.set_per_process_memory_fraction(0.5),把显存占用限制在系统可接受范围。

第三次,现象是import torch直接报Illegal instruction (core dumped)。原因是 Nano 的 ARM CPU 不支持编译时的某项指令集。解决方法是换用 JetPack 官方预编译的 torch 版本,不要在自己交叉编译时开启过高的-march优化。

5.4 检测不稳定:现象、原因、解决记录

模型在 Nano 上跑得很稳,但现场框的位置会一跳一跳。排查后发现两个原因:一是画面里有压缩噪声,老旧的模拟摄像头转网络流后噪声直接变成高频特征,导致同一帧检测框左右漂移。解决方法是进模型前加一层轻量滤波:

import cv2 cap = cv2.VideoCapture("rtsp://camera/stream") while True: ret, frame = cap.read() frame = cv2.medianBlur(frame, 3) results = model.predict(frame, conf=0.4, verbose=False)

medianBlur比GaussianBlur更适合保留边缘同时去除椒盐噪声,让框的抖动明显下降。二是季节光线的变化,冬天下午 3 点的低角度阳光会让帽子反光,安全帽框被削弱。我不是去改模型,而是在系统里增加一个“时段阈值切换”:中午用高阈值 0.45,早晚低光照用 0.3。阈值切换不需要重新训练,但需要你把一天不同时段的数据各抽 200 帧做一次小验证,确认误报率可接受。

6. 最后的落地技巧:验证指标、蒸馏与离我最近的一次教训

模型训练到最后,验证指标不要只看 mAP50,安全帽检测的真实验收我推荐三个数字:漏报率、误报率和平均延迟。mAP50 只衡量框定位能力,而漏报率才是工地上真正会闹出伤情的事故指标。我通常会从一整天监控里均匀抽 5000 帧标出来算漏报率,这个动作虽然累,但能发现白天和夜间不同光线下的真实差异。夜间画面如果黑乎乎,YOLOv11 会大量漏报,这是正常现象,不要指望模型万能,给摄像头加补光比换模型更有效。

另一个能明显提高部署效能的技巧是蒸馏。把前面训练出的 YOLOv11m 作为教师模型,蒸馏到 Nano 上能跑的 yolo11n。做法很简单:用教师模型推理输出的 logits 作为软标签,让学生模型同时拟合硬标签和软标签。我试过在安全帽数据集上,蒸馏后的 n 模型 mAP 比直接训练高 2 个百分点左右,而且推理速度完全没变。这部分实现不要自己手写 loss,Ultralytics 官方就支持蒸馏训练的结构,你只需提供教师权重和蒸馏温度参数。

最后说个真实教训:有次我把训练集增广时不小心开了rotate90 度,工地画面是固定的俯视角度,歪着的安全帽根本不会出现在真实场景,结果模型训练 mAP 很高,到了现场把侧躺的帽子当漏检。自那以后我给自己立了个规矩,每个增强参数都要问一句“现场会出现这种情况吗”。增强不能为了刷分牺牲真实分布,否则再贵的模型都是纸面指标。希望这些记录能帮你在 YOLOv11 安全帽检测这个方向上少走几趟弯路。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询