ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

YOLOv8与PyQt5实现工地安全帽检测系统实战指南

YOLOv8与PyQt5实现工地安全帽检测系统实战指南 YOLOv8PyQt5做工地安全帽检测系统解决的是施工现场安全监管里一个非常具体的问题用图像和视频自动判断人员有没有佩戴安全帽。这类系统放到工地摄像头、进场闸机或巡检无人机画面里可以把“人工盯监控”变成“算法自动报警”。它适合正在学深度学习的开发者也适合想做视觉检测毕业设计或小型安防项目的人。最值得关注的不只是模型能不能跑通而是从数据、训练到桌面界面整个链路能不能稳定落地。我更愿意把这类项目拆成几个层面来看模型层面是YOLOv8目标检测界面层面是PyQt5显示和交互工程层面则是线程、队列、日志和异常处理。很多新手容易卡在模型训练完却不知道怎么接进界面或者界面一跑视频就卡死。下面按实际落地顺序拆一遍从选型、环境、训练、界面到排错每一步都说清楚为什么这么做。1. 先搞清楚这套系统解决什么问题再做技术选型1.1 安全帽检测的业务场景是什么工地安全帽检测本质上是一个二分类目标检测问题在图像中找到“人”这个目标再判断这个人头部是否佩戴了安全帽。实现方式有很多种可以只检测安全帽也可以检测“人安全帽”的组合关系。工程上更常见的做法是训练两个类别helmet戴了安全帽和no_helmet没戴安全帽这样模型直接输出违规目标。实际部署时通常不是只对单张图片做判断。施工现场会有固定机位摄像头、移动布控球机、人员出入口抓拍机还可能巡检人员手持设备拍照。系统的价值不在“能检测一张图”而在能不能接住持续的视频流稳定地输出违规告警。1.2 为什么选择YOLOv8YOLOv8是Ultralytics推出的目标检测框架相比YOLOv5它的代码结构更清晰训练和导出接口更统一。对于工地安全帽这种类别少、目标尺度不算极端的场景YOLOv8能在精度和速度之间取得比较合适的平衡。更重要的是YOLOv8的生态比较完整。训练完模型可以直接导出为ONNX、TensorRT等格式方便后续做推理加速。对于PyQt5桌面应用来说我们可以直接用Ultralytics的Python包在内存中完成推理也可以导出成ONNX用OpenCV或ONNX Runtime加载后一种方式在脱离训练环境时更轻量。如果你纠结要不要用YOLOv8我的建议是如果你希望快速出效果默认用YOLOv8足够如果你已经有YOLOv5的成熟权重要迁移也不要盲目重训先评估现有模型在安全帽场景下的漏检率。技术选型不是越新越好而是越贴合你的数据越好。1.3 PyQt5在系统里承担什么角色PyQt5负责的是“人机交互层”。模型本身不关心界面PyQt5的作用是把摄像头画面、图片上传、检测结果、违规次数、告警弹窗这些信息组织成一个桌面程序。这里有一个容易忽略的重点PyQt5界面线程不能执行耗时操作。YOLOv8推理虽然单张图片可能只要几十毫秒但在连续视频流中如果直接在界面线程里循环推理窗口会无响应。因此必须把视频读取和推理放到后台线程通过信号把结果传回界面线程刷新。这个设计不是可选项而是必须项。# 示例用QThread组织视频推理线程 class DetectThread(QThread): frame_signal pyqtSignal(QImage, list) def run(self): while self.running: ret, frame self.cap.read() if not ret: continue results self.model(frame) self.frame_signal.emit(convert_to_qimage(frame), results)上面只是示意实际代码要处理线程退出、摄像头异常、模型加载失败等问题。把这些想清楚比堆功能更重要。2. 环境配置是新手最容易翻车的地方2.1 硬件条件怎么看训练YOLOv8首选NVIDIA显卡。显存大小直接决定能训练的模型尺寸和批次大小。我的建议是如果显存只有4G到6G优先用YOLOv8n或YOLOv8s输入分辨率设640批次大小控制在4到8之间。如果显存8G以上可以尝试YOLOv8m。没有NVIDIA显卡就不要在本机硬跑训练可以用云GPU平台否则一个V100训练一小时的任务CPU可能要跑一天。推理阶段对硬件要求低一些。CPU也能跑但如果是视频流建议至少使用集显或低端独显。实际项目里很多现场机器根本没有独立显卡这时需要把模型量化或导出为ONNX用OpenVINO加速或者调低输入分辨率。2.2 Python环境和依赖版本YOLOv8的官方包是ultralytics依赖PyTorch。我推荐用Python 3.8到3.10之间的版本PyTorch 2.x都可以。安装时不要一次性把requirements里的包全部手动装极容易冲突。建议用虚拟环境不要用全局环境conda create -n helmet python3.9 conda activate helmet pip install ultralytics pip install pyqt5第一次安装时要注意ultralytics会带进很多依赖包括opencv-python、numpy、matplotlib等。如果之前装过老版本OpenCV或numpy容易因为版本不匹配导致莫名其妙的导入失败。出现这类问题先创建全新虚拟环境再按顺序装能省很多时间。2.3 数据集准备与标注格式安全帽数据集可以用公开数据集比如SHWD也可以自己采集工地图片标注。自己标注时类别名要保持一致建议类别文件写清楚helmet佩戴安全帽no_helmet未佩戴安全帽如果你还希望识别“整个人”而不是只看头部可以增加head或者person类别。但类别越多数据量要求越高训练时间也越长。对入门项目来说先跑两个类别足够了。标注工具可以用LabelImg或Labelme导出为YOLO格式的txt文件。每张图片对应一个同名txt内容为class_id x_center y_center width height坐标是归一化后的值取值范围0到1。这里最容易错的是标注完忘记检查类别索引把helmet和no_helmet写反导致训练出的模型判断完全反了。我自己踩过这个坑所以每次训练前都要先抽几张标注文件看一眼坐标和类别。3. 从零训练自己的安全帽检测模型3.1 数据集目录结构YOLOv8训练时推荐使用下面的目录结构datasets/ helmet/ images/ train/ val/ labels/ train/ val/ data.yamldata.yaml里面指定路径和类别train: datasets/helmet/images/train val: datasets/helmet/images/val nc: 2 names: [helmet, no_helmet]路径既可以使用相对路径也可以使用绝对路径。建议放在项目根目录下用相对路径避免换电脑后路径失效。3.2 训练参数怎么设置训练命令很简单yolo detect train datadatasets/helmet/data.yaml modelyolov8s.pt epochs100 imgsz640 batch8但参数背后有几个判断标准epochs不是越多越好。观察验证集mAP和损失曲线当loss趋向平稳、mAP不再上升时就可以早停。imgsz训练分辨率。640是通用值。如果工地摄像头拍摄的距离较远人头小可以尝试768或960但会显著增加显存占用和推理耗时。batch显存不够时减小不要硬顶。正常优先保证能跑起来。model预训练权重选yolov8n.pt、yolov8s.pt还是更大要结合显存和精度需求。不确定时先用n或s跑一轮完整流程再决定是否升级。注意这里不要一开始就用大批次。如果batch太大前几个epoch就会爆显存或导致训练中断。我一般会先用batch4跑通确认数据读取、标签加载没问题再调大。3.3 训练结果怎么看训练结束后在runs/detect/train目录下会生成results.png、confusion_matrix.png、val_batch*.jpg等文件。重点看三个指标mAP50IoU阈值0.5下的平均精度一般0.8以上算比较可用。F1-Confidence曲线看置信度阈值在哪个区间F1最高这个值在部署时很有用可以在界面里设置默认置信度。混淆矩阵看helmet和no_helmet之间是否有大量误判。如果no_helmet经常被识别成helmet说明缺少未佩戴安全帽的正样本需要补充数据。训练集和验证集要分开不能为了提升指标把验证集混进训练。很多项目先训练时效果很好一上现场就崩往往就是数据分布太单一。3.4 导出模型给PyQt5用训练完成后把best.pt导出成其他格式可以提升推理效率或方便部署yolo export modelruns/detect/train/weights/best.pt formatonnx imgsz640导出ONNX后可以用ONNX Runtime加载也可以继续用ultralytics加载best.pt。对于PyQt5项目我建议先直接用best.pt做推理等整个项目跑通了再考虑导出优化。这样可以减少变量出了问题也容易排查。如果是正式项目还可以导出TensorRT engine格式但TensorRT和显卡驱动、CUDA版本强相关换机器就要重新导出。入门阶段不建议一上来就碰。4. 把YOLOv8模型嵌入PyQt5界面的核心流程4.1 界面布局怎么设计PyQt5界面可以很简单也可以很复杂。对安全帽检测系统而言至少需要这些区域视频/图片显示区选择图片、视频、摄像头按钮开始检测/停止检测按钮结果统计区检测总人数、未戴帽人数日志区显示最近一次告警时间我建议用QMainWindow作为主窗口左侧放视频显示控件右侧放控制按钮和统计信息。不要一上来就做花哨皮肤先把功能跑通再优化交互。4.2 模型加载和推理封装模型加载只需要一行from ultralytics import YOLO model YOLO(best.pt)推理也简单results model.predict(frame, conf0.5, imgsz640)不过实际项目中不同输入源需要封装。我一般会写一个Detector类负责加载模型、接收图片、返回检测框和类别。class SafetyHatDetector: def __init__(self, weights): self.model YOLO(weights) def detect(self, img_bgr): results self.model.predict(img_bgr, conf0.5, verboseFalse) boxes results[0].boxes if boxes is None: return [] data boxes.data.cpu().numpy() return [list(map(float, box)) for box in data]这个封装看起来简单但能避免在界面代码里到处写YOLO相关的调用。后面如果要换成ONNX Runtime只需要改这个类。4.3 图片、视频、摄像头三种输入怎么统一处理PyQt5应用要支持的输入可能有三种单张图片、视频文件、摄像头实时画面。它们读取方式不同但推理逻辑一样。图片用QFileDialog选择文件读成OpenCV的BGR格式送入检测再把结果转回QImage显示。视频文件用cv2.VideoCapture读取每一帧循环推理。摄像头同样是cv2.VideoCapture(0)但要注意摄像头索引可能不是0。有些工地现场有多个USB摄像头需要在界面里提供下拉选择。针对视频和摄像头最怕的是UI卡死。解决方案是把读取和推理放在QThread中每秒最多刷新N帧比如15帧既能保证实时性又不会让CPU和内存一直飙升。4.4 结果显示和告警逻辑检测结果画框可以直接用OpenCVfor box in detections: x1, y1, x2, y2, conf, cls box label f{names[int(cls)]} {conf:.2f} color (0, 0, 255) if int(cls) no_helmet_idx else (0, 255, 0) cv2.rectangle(img, (int(x1), int(y1)), (int(x2), int(y2)), color, 2) cv2.putText(img, label, (int(x1), int(y1)-5), cv2.FONT_HERSHEY_SIMPLEX, 0.6, color, 2)告警逻辑不能只弹窗。连续视频中如果每一帧都弹窗界面会被消息淹没。更好的做法是只有当同一目标连续N帧都被检测为未戴帽且未戴帽持续超过一定时间才触发一次告警。这需要做简单的目标跟踪或状态计数。入门阶段可以简化如果当前帧检测到未戴帽目标就在状态栏显示红色警告文字而不是弹窗。5. 从单张图片到批量任务稳定性比功能更重要5.1 批量图片和视频文件的处理思路很多人的需求不只是看一张图而是处理一个文件夹或一段长视频。这时不能简单在界面线程里for循环处理原因有二一是处理时间不可控界面会一直无响应二是如果某个文件损坏或路径有问题整个任务会中断。我给的建议是用QThreadPool或QThread执行批量任务。每次读取一个文件处理完写结果再读下一个。输出目录提前建好文件名要避免重名。失败文件单独记录到日志跳过继续执行。比如处理一个文件夹里的所有图片for img_path in image_list: try: frame cv2.imread(img_path) results detector.detect(frame) draw_and_save(frame, results, output_dir, img_path) except Exception as e: log_error(img_path, e) continue这里的关键是“失败继续跑”。批量任务能不能跑通是一回事能不能保证中间失败不中断、输出结果可对应到原文件才是生产环境更关心的问题。5.2 摄像头长时间运行的稳定性工地安全帽监测是长时间连续运行的场景可能一开就是一天。这个过程中最常遇到的几个问题摄像头连接断掉。内存缓慢增长最后卡死。视频帧堆积推理速度跟不上输入速度。对第一个问题处理方式是在读取线程里判断cap.isOpened()如果失败重试几次仍失败就停止并提示。对第二个问题注意不要每帧都创建新的QImage和cv2对象而不释放定时把临时列表清掉。对第三个问题可以在读取帧后先判断处理队列长度超过阈值就丢帧。总的原则是实时性优先不追求每一帧都检测。5.3 并发和资源占用不要一开始就开很大并发。PyQt5单线程模型如果频繁调用YOLO推理CPU占用会很高。如果所有线程都同时调用同一个模型还可能产生锁竞争推理时间反而变长。在桌面应用里一个后台线程就够用了。如果模型加载在CPU上建议设置devicecpu如果有GPU可以设置device0。如果是在服务器端做批量处理可以同时开多个进程每个进程单独加载模型然后按队列分发任务。但这属于另一个量级的问题桌面端不需要。6. 常见报错和排查顺序6.1 安装和环境类报错“ModuleNotFoundError: No module named ultralytics”先确认当前Python环境是不是你安装包时用的环境。如果用了conda很容易因为命令行环境不对导致找不到模块。运行pip list | grep ultralytics确认存在后再检查代码运行时是否用了同一个解释器。“PyQt5导入报错could not load platform plugin xcb”常见原因是缺少系统图形库。在Linux下需要安装libxcb-*系列依赖Windows下一般不会出现。如果出现优先重装PyQt5pip uninstall pyqt5 pyqt5-tools pip install pyqt56.2 推理运行时报错“AttributeError: NoneType object has no attribute boxes”原因通常是模型没有在输入图片中检测到任何目标或者传入的图片为空。先打印图片shape确认读取成功再调用模型。还可以在predict里加conf阈值不要用默认0.25因为安全帽场景里远处目标置信度低容易漏检。“RuntimeError: CUDA out of memory”这个最好判断显存不够。把batch调小或切换到YOLOv8n或降低分辨率。如果训练时显存不够选模型时不要选大的有些云平台有免费额度可以用。“模型权重文件损坏或版本不匹配”下载预训练权重时有可能下载不完整。重新下载时检查文件大小。如果是自己训练的模型确认best.pt和代码使用的YOLO版本兼容老版本权重不一定能被新版YOLO直接加载。6.3 检测效果差怎么排查如果模型把戴了安全帽的人检测成未戴或者漏掉很多人按这个顺序排查先看测试图片的检测结果图判断是漏检还是框点偏差。如果是漏检降低置信度阈值看能否检测出来。如果能则说明模型置信度不够需要加数据或换大模型。如果检测框位置不准查看标注文件是否和图片对应类别是否混乱。如果只是某个摄像头角度下效果差收集该角度的数据补充训练。6.4 PyQt5界面卡死怎么排查界面卡死大部分原因是耗时操作阻塞了界面线程。检查顺序是否在paintEvent里调用了推理。是否在clicked信号处理里直接循环读视频。是否用time.sleep模拟延时。正确的做法是把所有耗时逻辑放到QThread只用信号更新界面。还有一个常见问题线程已经启动但关闭窗口时没有通知线程退出导致进程无法结束。需要在closeEvent里设置self.thread.running False并等待线程退出。def closeEvent(self, event): self.thread.stop() self.thread.wait() event.accept()这段代码很重要否则程序关闭后会出现“进程仍然在后台运行”的诡异现象。写在最后的个人经验这套系统从零开始到一个能演示的版本其实不需要太难的核心技术。最难的地方在于当环境变了、数据变了、摄像头角度变了系统还能不能稳定工作。我一般会先把单张图片检测跑通然后处理一段短视频再接入摄像头最后再考虑批量处理。每前进一步都先确认前一步输出是正常的。这样出问题时很快就能定位到是模型问题、代码问题还是环境问题。训练上不要追求一次到位。用公开数据集先训练一个baseline模型部署到界面里看看哪些场景漏检再针对性地补充数据。这个过程比盲目调参有意义得多。真正落地时最该盯住的不是功能列表而是输入格式、资源占用和失败重试。摄像头断线、图片编码异常、路径中有中文、显存不足这些看起来和算法无关的小问题反而决定了你的系统能不能被现场人员接受。先把这些坑填平再谈精度优化。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进