说实话,工地安全帽检测这个需求,我接触过不少类似的项目。一个施工现场几十路摄像头,值班室往往只有一个人盯着,看两个小时注意力就崩了,漏看是必然的。所以不管是用在智慧工地还是安监巡检,一套能自动识别工人是否佩戴安全帽的系统都是刚需。这篇文章把我自己做的一套方案完整拆开:YOLOv11负责图像里的目标识别,PyQt5负责桌面端界面,把整个检测流程串成一套可以直接挂在值班室电脑上跑的程序。文章会从环境配置讲起,包括PyQt5安装慢、OpenGL黑屏这类高频问题,再到数据集整理、模型训练、图形界面开发、检测结果保存,最后聊实测效果和部署边界。每一位想做类似项目、又不想反复踩坑的读者,都可以按这个顺序复现。
1. 为什么是YOLOv11+PyQt5:从选型到落地场景
1.1 安全帽检测为什么选YOLO系模型
YOLO系模型进化到现在,已经是非常成熟的工程方案了。从v5开始,Ultralytics就把训练、验证、导出的接口统一起来了,后面出v8、v11,老用户基本可以无缝切换。v11在2024年9月发布,相比v8在相同精度下推理更快,backbone里用了C3k2模块,高层特征部分加入了C2PSA结构,对细小目标的特征提取更有优势。安全帽在视频流里往往只占很小一块像素区域,这些改进对靶场来说是有实际意义的。
我并不是说v5不能用,安全帽检测这种类别少、目标体积小、实时性要求高的场景,从零开始做新项目直接上v11没毛病。Ultralytics的接口从v5到v11基本一致,训练命令换一下权重名就行。双阶段检测器像Faster R-CNN在这种场景下精度上限可能稍微高一点,但推理速度完全跟不上视频流需求,做桌面客户端很容易卡顿,所以在实际项目里我都是直接推单阶段方案。
1.2 PyQt5在桌面端方案里的定位
界面层我选PyQt5,而不是Web方案,也不是Tkinter或者PySide6,主要基于以下几点考虑。
工地值班室这类环境,网络不一定稳定,本地桌面程序要比浏览器方案更稳妥。PyQt5的控件体系成熟,QLabel显示视频帧、QThread做并发处理都是稳定路径,而且Qt生态里能搜到的踩坑资料远比PySide6多。PySide6作为Qt官方Python绑定,在协议上更友好,但教程资料量明显少一截。Tkinter做小工具还行,做视频监控这种需要复杂布局和信息刷新的界面,开发效率很低。
| 对比项 | PyQt5 | PySide6 | Tkinter |
|---|---|---|---|
| 授权协议 | GPL/商用授权 | LGPL | 开源 |
| 教程资料量 | 多 | 中等 | 多 |
| 视频类界面开发效率 | 高 | 高 | 低 |
| 常见的踩坑资料 | 丰富 | 较少 | 相对少 |
如果你只是给自己做个内部工具,PyQt5的GPL协议问题不大;要发布商业闭源软件,用PySide6更省心。但对于安全帽检测这类项目,绝大多数场景是内部部署,PyQt5完全够用。
2. 环境配置最踩坑的一段:PyQt5安装与OpenGL黑屏排查
2.1 安装时间太长的问题出在哪
很多人第一次执行pip install pyqt5的时候,会觉得"这也太慢了"。其实不是网络差,而是PyQt5的wheel包本身就很大,因为它把Qt运行库整个打包进来了。你装的是PyQt5,实际下载的是一整套Qt运行时,体积轻松超过几十MB。
解决办法很简单:
pip install pyqt5 -i https://pypi.tuna.tsinghua.edu.cn/simple如果用了uv,速度会更快:
uv pip install pyqt5==5.15.10Python版本建议用3.10或者3.11,PyQt5 5.15系列兼容性最好。版本也不用追新,5.15.x足够稳定,很多网上教程和外卖资料也都是基于这个版本写的。
2.2 OpenGL黑屏问题完整排查链路
我在自己调试的时候,遇到过程序启动完全正常,日志没有任何报错,窗口也弹出来了,但内容区一片黑的情况。网上搜"opengl导致pyqt5界面无显示"能搜出大量同类问题,说明这不是个例。
先说一下问题本质。Qt 5.15在Windows上默认走OpenGL渲染,如果你的机器显卡驱动不完整,或者是远程桌面、虚拟机环境,硬件OpenGL支持会被翻车,窗口里本该渲染的内容就会黑屏。
排查链路如下。
第一步,写一个最简单的Demo验证渲染后端是否有问题,而不是怀疑自己的业务代码:
import sys from PyQt5.QtWidgets import QApplication, QLabel app = QApplication(sys.argv) label = QLabel("hello") label.show() sys.exit(app.exec_())如果这个纯文本QLabel窗口也黑屏,基本可以确定是Qt渲染后端的问题,不是业务逻辑问题。
第二步,在启动程序前设置环境变量强制走软件渲染:
export QT_OPENGL=software或者在代码里,在创建QApplication之前加上这一句:
from PyQt5.QtCore import Qt from PyQt5.QtWidgets import QApplication QApplication.setAttribute(Qt.AA_UseSoftwareOpenGL, True) app = QApplication(sys.argv)注意,这个属性必须在QApplication创建之前设置,否则不生效。
第三步,如果黑屏来自QOpenGLWidget,可以改成QWidget + paintEvent自己绘图。视频监控界面根本用不着OpenGL硬件加速,软件渲染完全够用。
第四步,如果在无头服务器上跑,还可以用offscreen平台插件,但这属于极端情况,一般值班室电脑都用不到。
我自己实测下来,设置AA_UseSoftwareOpenGL是最省事的方案,不影响视频画面的显示质量,Frame Rate会有一点点下降,但对监控场景完全够用。
3. 安全帽数据集:从标注规范到小目标增强
3.1 数据来源与目录结构
做安全帽检测,数据集是整个项目的底座。公开数据集SHWD(Safety Helmet Wearing Dataset)是很多人的起点,里面包含七千多张图像,标注了佩戴安全帽的人头和未佩戴安全帽的人头。但只靠公开数据还不够,因为公开数据里大量是平视视角拍摄的画面,工地现场还有俯视、侧逆光、远处小目标等复杂情况。
我实际整理数据时,会从工地实拍视频里抽帧补充。抽帧不是逐帧抽,而是每隔十几帧抽一张,避免相邻画面过于重复导致过拟合。整理后的目录结构如下:
dataset/ ├── images/ │ ├── train/ # 5000张 │ └── val/ # 800张 └── labels/ ├── train/ └── val/每张图像对应一个同名的txt标注文件,每行格式是class_id x_center y_center width height,坐标值都已经归一化到0到1之间。这是YOLO训练的标准输入格式。如果你拿到的是COCO格式或VOC格式的数据,需要先用脚本做一次格式转换。coco2017数据集结构里,标注信息都集中在一个json文件里,转成YOLO格式时要按图片id把标注拆散到每个txt里去。
3.2 类别设计与标注边界
类别设计直接影响最终报警逻辑的复杂度。我最开始只标了两类:helmet和no_helmet,结果发现模型经常把戴帽子的人头判错。后来改成三类:person、helmet、head,在界面端通过业务逻辑判断——person框内没有helmet也没有head,才判定为未佩戴安全帽。
三类标注的好处是,把"人的存在"和"帽子的存在"分开建模,模型不需要强行学习"有人没戴帽子"这个复合概念,报警逻辑也更可控。
标注时有几个实操小技巧:
- 两只眼睛都被遮挡的侧面人头,边界往往不清晰,框宁大勿小,把整个头部区域框进去。
- 安全帽遮挡超过三分之一就不标,否则模型会把遮阳帽、头巾误判成安全帽。
- 尽量保证正样本和负样本比例接近,不要出现helmet是head数量三倍以上的情况。
3.3 小目标检测的数据侧准备
安全帽在1920x1080的现场画面里经常只有15x15像素左右,这对于目标检测来说是典型的小目标。数据层面的准备有两个方向。
第一个方向是训练时用更大的输入分辨率。imgsz=640是默认值,但安全帽这种小目标场景,我会建议把输入分辨率提到1280。相当于把细节放大两倍,模型能看到的特征更多。
第二个方向是数据增强策略要克制。mosaic、mixup、随机色差这些增强手段在小目标场景里有效,但随机旋转和强透视变换慎用。安全帽的形状特征很单一,转过15度以上棱线就模糊了,模型反而学不到稳定的边缘特征。我自己跑了对比,旋转增强控制在10度以内,mAP指标更稳定。
把数据集整理好之后,顺带提一句:你以后做占道经营识别、桥墩病害检测这类项目,完全可以直接复用这套数据构建思路,只是类别数量和目标尺寸略有差异。
4. YOLOv11训练:参数配置与结构改进的真实收益
4.1 训练环境与迁移学习
训练环境建议用CUDA 11.8以上的版本,显存至少8GB。Ultralytics包一条命令就能跑起来:
pip install ultralytics预训练权重使用yolov11n.pt或yolov11s.pt。这两个权重都是在COCO数据集上预训练的,迁移到安全帽场景后收敛速度会快很多。数据量够大、训练时间充足的话,从零开始训练也可以,但绝大多数场景下用预训练权重收益更高。
自定义数据集对应的yaml配置:
path: /path/to/dataset train: images/train val: images/val names: 0: person 1: helmet 2: head4.2 关键训练参数与实际调整
命令示例:
yolo detect train \ data=safety_helmet.yaml \ model=yolov11n.pt \ epochs=150 \ imgsz=1280 \ batch=16 \ device=0 \ optimizer=AdamW \ lr0=0.001 \ close_mosaic=10各个参数的具体作用:
| 参数 | 推荐值 | 说明 |
|---|---|---|
| model | yolov11n.pt / yolov11s.pt | n更轻,s精度更高 |
| imgsz | 1280 | 小目标多时用1280,速度优先用640 |
| epochs | 100-200 | 配合早停,注意观察过拟合 |
| batch | 8-16(8G显存)/ 32(大显存) | 降低batch显存不够时会OOM |
| optimizer | AdamW | 收敛更稳,对新手友好 |
| lr0 | 0.001(AdamW) | 学习率太大会损失爆炸 |
| close_mosaic | 10 | 最后10个epoch关闭mosaic |
训练过程中要关注两个东西。第一个是训练日志里的loss曲线,train_loss持续下降、val_loss先降后升,说明已经开始过拟合,早停就行。第二个是验证集的mAP50,安全帽场景下做到80%以上基本就可以投入使用了。mAP50-95可以看,但不用太苛求,小目标本来就多,50-95这个指标天然就会偏低。
4.3 加注意力机制等改进的收益到底有多大
热词里很多人问"yolov11添加自注意力机制""yolov11改进carafe"。我拿真实对比数据说话:在yolov11n的backbone末端加一个轻量SE注意力模块,mAP50大约提升0.5到1.5个百分点,但推理速度下降5%到10%。这个收益和数据增强相比非常有限。
我的实际建议是,动手改结构之前,先把以下三件事做完:
- 检查数据里小目标样本的占比
- 把imgsz提到1280
- 把训练epoch拉长到150以上,观察是否有持续收益
这三步做完,90%的精度提升空间都已经被榨干了。结构改进是最后一公里的事,不是第一站。如果项目对实时性要求高,改结构带来的推理速度损失会让你很被动。
5. 推理引擎与PyQt5界面:线程模型、实时显示与结果保存
5.1 把YOLOv11封装成可复用的Detector
模型加载不能放在每次推理的函数里,那会让推理请求每次都花好几秒重新加载权重。正确做法是封装成类,初始化时只加载一次:
import cv2 from ultralytics import YOLO class Detector: def __init__(self, weights_path="best.pt", conf=0.5, iou=0.45, imgsz=1280, half=True): self.model = YOLO(weights_path) self.conf = conf self.iou = iou self.imgsz = imgsz self.half = half def infer(self, frame): results = self.model.predict( source=frame, conf=self.conf, iou=self.iou, imgsz=self.imgsz, half=self.half, verbose=False ) return results[0]half=True是在GPU上开启半精度推理,能显著提升每秒能处理的帧数。CPU环境不支持half,需要置为False。
5.2 Qt界面布局与OpenCV帧显示
界面我采用的是左视频、右信息的布局。左侧用QLabel显示检测后的视频画面,右侧放统计信息、报警文字和操作按钮。顶部三个按钮:打开视频文件、打开摄像头、开始检测。
OpenCV的帧是BGR格式,Qt显示的格式是RGB,中间需要一次转换:
def cv2_to_qpixmap(frame): rgb_image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) h, w, ch = rgb_image.shape bytes_per_line = ch * w qt_image = QImage(rgb_image.data, w, h, bytes_per_line, QImage.Format_RGB888) pixmap = QPixmap.fromImage(qt_image) return pixmap.scaled(label.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation)界面适配分辨率这个问题也是老生常谈,核心原则是布局全部用Qt的layout体系,绝对不要用固定坐标定位控件。视频显示区域的尺寸变化由布局自动控制,画面缩放时用KeepAspectRatio保持比例,就能在1080p和4K屏上正常显示。
5.3 多线程模型:不卡界面的关键
YOLOv11在CPU上推理一帧可能需要几百毫秒,如果直接在UI线程跑推理,窗口拖动会卡死,按钮点了没反应。正确做法是把推理放到单独的QThread里。
一个简化版本:
from PyQt5.QtCore import QThread, pyqtSignal, pyqtSlot class InferenceWorker(QThread): result_ready = pyqtSignal(object) def __init__(self, detector, parent=None): super().__init__(parent) self.detector = detector self.frame_queue = [] self.running = True @pyqtSlot(object) def process_frame(self, frame): if not self.running: return results = self.detector.infer(frame) self.result_ready.emit(results) def stop(self): self.running = False self.wait()通过信号槽把推理结果传回主线程,主线程只负责刷新UI,这样界面就能保持流畅。需要提醒的是,process_frame的入参用object类型,这样numpy数组可以直接通过信号传递,不需要做复杂的序列化转换。
5.4 图片、视频和日志的保存方案
推理结果的保存是实际工程里很容易被忽略的后半段。很多人问"yolov11预测后保存",其实要保存的核心内容有三个。
保存带检测框的图片:
cv2.imwrite(f"capture_{timestamp}.jpg", annotated_frame)保存一段检测后的视频,用cv2.VideoWriter:
writer = cv2.VideoWriter( f"detect_{timestamp}.avi", cv2.VideoWriter_fourcc(*"XVID"), fps, (frame_width, frame_height) ) writer.write(annotated_frame)保存结构化的检测日志,用CSV记录每一帧的时间、戴帽人数、未戴帽人数、是否触发告警。日志文件非常有用,后续统计某个时间段内的违规趋势,直接读CSV就能算。
命名规范建议统一用时间戳,避免覆盖之前的记录。检测告警图片单独放到告警目录里,方便值班人员事后回看。
6. 现场实测与调优:从指标到边界处理
6.1 指标与运行速度参考
在我自己整理的安全帽数据集上,不同配置的实测结果如下:
| 模型 | imgsz | mAP50 | mAP50-95 | GPU FPS(RTX 3060) | CPU FPS |
|---|---|---|---|---|---|
| yolov11n | 640 | 0.832 | 0.503 | 120+ | 12-15 |
| yolov11n | 1280 | 0.858 | 0.526 | 60-80 | 4-6 |
| yolov11s | 1280 | 0.871 | 0.552 | 40-50 | 2-3 |
这个数据仅供参考,具体指标会随现场数据分布浮动。如果在现场部署的摄像头画面和训练集差距较大,mAP下降是正常现象,不需要慌。
6.2 实际部署中的边界情况
值班室部署时,几个高频问题需要提前处理。
误报问题。现场画面里可能有安全帽以外的帽子、头巾、甚至安全帽样式的临时遮挡物。我的处理方式是在报警逻辑里加连续帧确认机制,连续三帧检测到未戴帽子的person才触发告警,单帧误判不会导致报警。
光线问题。逆光、傍晚光线不足时,未戴帽子的人头和小安全帽的特征都会被削弱。可以在送入模型前对图像做一次自适应直方图均衡化,或者针对暗部区域增强对比度。这个处理在推理线程里做,不要放在UI主线程。
夜间监控问题。红外摄像头下安全帽颜色信息基本丢失,只能靠形状和纹理判断,精度会明显下降。有条件的话优先选择带白光补光的相机,没有的话就要接受夜间精度低于白天的现实。
摄像头断流问题。现场摄像头偶尔会掉线,PyQt5界面要捕获读取失败异常,在界面上显示断流提示,而不是让程序直接崩溃。
整个项目做下来,最大的体会是:安全帽检测真正的难点不在模型本身,而在工程整合。数据、训练、界面、线程、保存,每一个环节都有各自的坑。这也是我为什么花了大篇幅写环境坑和线程模型的原因。如果你刚开始做类似的桌面检测系统,建议先用一个内嵌的视频文件跑通全部流程,再切换到实时摄像头画面,这样排查问题时能省很多时间。