
做工地安全帽检测这个方向模型部分其实不难真正折磨人的是把模型包装成一个能让现场值班人员双击就用的工具。去年我接手一个项目在工地出入口的摄像头画面里实时检测工人有没有戴安全帽没戴的要弹报警、留截图、记日志。第一版我图省事用YOLOv8做了个Web端Demo模型效果倒是挺好结果到现场一部署就翻车了——工地值班室的电脑配置老旧浏览器一打开页面就卡内网连Node服务还得配半天环境最后这套方案直接被甲方毙掉。后来我换成YOLOv11PyQt5做桌面端系统把模型和界面一起打包成exe接上摄像头电源就能跑这才算真正落地。这篇文章就把这套安全帽检测系统从数据准备、模型训练、PyQt5界面开发到打包部署的完整过程复盘一遍核心就是三个词YOLOv11、PyQt5、安全帽检测数据集。适合正在做计算机视觉方向毕业设计、或者准备把目标检测模型落地成桌面工具的同学照着这篇文章走一遍基本能避开我踩过的大部分坑。1. 项目概述与技术选型思路1.1 需求边界与功能规划做项目之前先别急着写代码先把需求边界理清楚。安全帽检测系统最核心的功能是对视频画面中的工人进行安全帽佩戴状态判断发现未佩戴立即告警。但“未佩戴”这个状态怎么定义是个值得想清楚的问题。如果直接训练一个“unhelmet”类别样本很难收集得全面因为“未戴帽”的视觉形态太丰富了——有人光着头、有人戴草帽、有人戴鸭舌帽都会干扰模型。所以更稳妥的做法是检测两类目标person人和helmet安全帽然后在后处理逻辑里判断如果一个person框附近检测不到helmet框就判定为未佩戴。这样既降低训练难度又方便后续调整判定规则。功能边界上我最终确定了这几个模块本地图片检测、视频文件检测、RTSP/摄像头实时检测、检测结果截图保存、告警日志记录。图片检测用来快速验证模型效果视频和摄像头实时检测才是现场真正要用的功能。日志记录这个点最容易忽略但甲方验收时往往会重点看——出了安全事故追溯责任系统能不能提供“某时间点某画面未戴帽”的可回溯证据这比检测准确率还重要。1.2 模型选型为什么选YOLOv11YOLO系列现在已经有v5、v8、v11好几个主流版本外加RT-DETR、DETR这类端到端方案选型确实纠结。我把当时对比的几个点列出来YOLOv5社区成熟度高网上资料多但底子是2020年的结构新增优化少新出的改进模块用起来得自己魔改。YOLOv8Ultralytics官方统一生态文档齐全跟v11共用一套API属于稳妥的“不会出错”选项。YOLOv112024年9月底发布在v8基础上重新设计了C3k2模块增加了C2PSA注意力相关结构推理速度更快COCO精度更高。RT-DETR端到端检测器精度确实能打但工程依赖重在老旧工控机上部署难度大实时性也不占优。安全帽检测本身不是特别极端的视觉任务目标尺度中等、类别少所以核心矛盾不是“精度不够”而是“速度和部署成本”。YOLOv11在保证实时性的前提下精度处于第一梯队而且直接用ultralytics库就能训练和推理跟v8的代码兼容性极高出问题随便一搜都有解。实际测试下来yolo11n模型在1080Ti上跑640分辨率推理时间能压到10ms以内这个性能对值班室监控场景来说绰绰有余。1.3 界面方案PyQt5还是PySide6桌面端界面方案我比较过Tkinter、PyQt5、PySide6、Electron。Tkinter太简陋画视频画面、做告警动效都很吃力Electron本质是套壳浏览器打包出来300MB起步对工地那台老电脑是灾难。剩下就是PyQt5和PySide6两者API高度相似底层都是同一个C Qt库PySide6是Qt官方维护的绑定更新及时、授权友好但网上中文教程相对少踩坑时搜到的答案经常是PyQt5的。PyQt5是Riverbank Computing维护的老牌绑定生态成熟度高几乎所有问题都能搜到现成答案开发效率最高。最后选PyQt5主要就是冲着生态成熟去的。做项目最怕的不是问题多而是问题搜不到解法。如果你不介意用新一套PySide6也能无缝切换界面代码基本只需要改import语句。2. 环境配置与数据集准备2.1 开发环境搭建我用的环境是Python 3.10 PyTorch 2.1 CUDA 11.8YOLOv11的代码在Python 3.8以上都能跑不用追求最新版本。GPU选择上有N卡最好显存8G以上训练很轻松没有N卡的话可以用CPU跑但训练时间会非常长建议用云GPU或者Colab过渡。conda create -n helmet python3.10 -y conda activate helmet pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 pip install ultralytics pip install PyQt5 opencv-python -i https://pypi.tuna.tsinghua.edu.cn/simplePyQt5安装有个常见的坑默认从PyPI下载国内网络经常很慢甚至失败。我查过pyqt5安装时长这个问题有人能装十几分钟原因就是网络波动。解决办法很简单pip后面加清华源参数-i https://pypi.tuna.tsinghua.edu.cn/simple顺便把ultralytics、opencv-python也一起用国内源装几分钟搞定。现在也有用uv做包管理的uv pip install pyqt5速度更快不过项目要求不强制pip够用了。提示torch建议不要用pip install torch这种裸命令会默认装CPU版或CUDA版本不对的包。要么用官方指定源的命令要么先确认nvidia-smi能看到CUDA版本再装对应匹配的torch。2.2 OpenGL导致PyQt5界面无显示的排查这是PyQt5开发里最诡异、也最常见的坑网上搜“opengl导致pyqt5界面无显示”能找到一片哀嚎。症状是程序启动后窗口状态栏有但主界面黑屏、空白控制台偶尔报Could not create OpenGL context或者QOpenGLWidget requires a graphics context。根因是Qt 5.15默认会尝试初始化OpenGL上下文来渲染部分控件但工地值班室的电脑很多是核显、老显卡、远程桌面或者虚拟机环境OpenGL支持不完整初始化失败后整个窗口就白屏。解决办法按优先级操作在程序入口import PyQt5相关模块之前设置环境变量import os os.environ[QT_OPENGL] software这会强制Qt走软件渲染路径不依赖显卡驱动。如果设置后还是黑屏检查PyQt5自带的Qt5插件版本。可以尝试重新安装匹配版本的PyQt5-Qt5组件pip install PyQt5-Qt55.15.2 -i https://pypi.tuna.tsinghua.edu.cn/simple在物理机上更新显卡驱动虚拟机/远程桌面场景优先用环境变量方案。软件渲染模式下视频显示、按钮交互都没问题唯一影响是QOpenGLWidget这类控件的3D动画帧率会降低但监控界面用不到3D特效可以放心开。2.3 安全帽数据集整理与格式转换安全帽检测的公开数据集网上一搜很多常见的有SHWDSafety Helmet Wearing Dataset、Safety Helmet Detection Dataset等这些数据集主要标注person和helmet两个类别有些还会带head类表示头部区域。但公开数据集有一个隐患标注质量参差不齐有些框歪了、漏标了直接训练会学出一堆噪声。我的建议是下载后先抽100张图人工过一遍看看标注跟实际情况是否对得上必要时重新标注或剔除脏数据。数据集目录结构要按YOLO格式组织Ultralytics训练时认这个结构datasets/helmet/ ├── images/ │ ├── train/ │ └── val/ └── labels/ ├── train/ └── val/labels目录下每个txt文件对应一张图片的标注每行格式是class_id center_x center_y width height四个坐标值必须是0到1之间的归一化数值。比如一张1280x720的图一个安全帽框左上角坐标是(320,180)右下角是(400,252)那么center_x(320400)/2/12800.28125center_y(180252)/2/7200.3width(400-320)/12800.0625height(252-180)/7200.1。如果你下载的数据集是VOC格式xml标注或COCO格式json标注需要写脚本转成YOLO txt格式。下面给一个简单的VOC转YOLO脚本核心片段import os import xml.etree.ElementTree as ET def convert_voc_to_yolo(xml_path, out_path, class_map): 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): cls obj.find(name).text if cls not in class_map: continue 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 lines.append(f{class_map[cls]} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}) with open(out_path, w) as f: f.write(\n.join(lines))数据划分也别忘了按8:1:1左右分为训练集、验证集、测试集划分时尽量保证图片来源不重叠比如同一个视频抽出来的帧不要同时出现在训练和验证里否则指标会虚高。这一步用Python的random库加shutil移动文件即可代码就不全贴了。2.4 数据增强与小目标优化思路安全帽检测有个实际难题摄像头装在工地大门高处人离摄像机远时画面里安全帽可能只有30x30像素属于典型小目标。YOLOv11默认开启的数据增强基本够用Ultralytics在训练时的增强参数包括hsv_h/hsv_s/hsv_v颜色扰动、translate平移、scale缩放、fliplr水平翻转、mosaic拼图等。对于小目标可以重点调高scale增强让模型见过更多小尺度目标mosaic增强虽然复杂但能有效提升模型对遮挡、小目标的鲁棒性。如果调完增强还是漏检最简单的优化方案是把推理和训练的分辨率从640提高到1024。安全帽本身很小输入分辨率提高后特征图上的目标像素变多检测率提升很明显。代价是推理变慢、显存占用变大需要在帧率和准确率之间取平衡。再激进一点可以用SAHI这类切图推理方式把大图切成多个patch分别推理再合并但那是后话基础项目先用好“提高分辨率”这一招就够了。3. 模型训练与推理保存3.1 编写数据集配置与训练脚本在ultralytics框架下训练先要写一个数据集yaml配置文件告诉框架训练集、验证集路径以及类别信息# helmet.yaml path: datasets/helmet train: images/train val: images/val nc: 2 names: 0: person 1: helmet注意类别编号要和标签里的class_id对应。我的数据集标签里0是person、1是helmet如果你的数据集相反训练时就要改names顺序或者统一改标签。这里建议全部改成0person、1helmet因为后面后处理判断戴帽逻辑要经常操作这两个类别固定编号方便写代码。训练命令如下yolo detect train \ datahelmet.yaml \ modelyolo11n.pt \ imgsz640 \ epochs100 \ batch16 \ device0 \ projectruns/detect \ namehelmet_expmodelyolo11n.pt表示从官方预训练权重yolo11n开始微调这比随机初始化训练快得多、效果好得多。如果显存不够把batch降到8或4如果训练集比较大epochs可以设80~120配合早停机制防止过拟合。3.2 训练指标怎么看训练完成后结果保存在runs/detect/helmet_exp/目录下重点关注几个东西weights/best.pt验证集上表现最优的权重部署时就用它。weights/last.pt最后一轮权重有时best和last相差很大说明训练波动大。results.pngloss和mAP曲线能直观看到训练是否收敛。confusion_matrix.png混淆矩阵看类别间是否严重混淆。安全帽检测这个任务验收标准一般在mAP50达到0.9以上才比较稳mAP50-95达到0.7以上就算合格。如果mAP50-95很低但mAP50很高说明模型对大目标检测很准但定位精度一般或者小目标还存在漏检。此时优先检查数据标注是否精细其次再考虑提高imgsz或增加数据增强。训练过程中出现val loss持续不降、训练集loss却很低的情况说明过拟合了。解决办法增加数据量、增强数据多样性或者增加weight_decay。Ultralytics默认开了patience50的早停如果连续50轮验证集指标不提升会提前终止训练这本身就是在防过拟合。3.3 推理与保存检测结果训练完就要进入应用阶段。“yolov11预测后保存”是很多人问的问题其实Ultralytics框架已经内置了保存功能一行代码搞定from ultralytics import YOLO model YOLO(runs/detect/helmet_exp/weights/best.pt) results model.predict(test.jpg, conf0.45, saveTrue)运行后结果图会默认保存到runs/detect/predict/目录图片名跟原图一致。如果你想指定输出目录加project和name参数results model.predict(test.jpg, conf0.45, saveTrue, projectoutput, nametest_result)对视频文件做检测并保存需要自己加一点OpenCV写入逻辑。注意results[0].plot()返回的是绘制好检测框的BGR图像可以直接交给VideoWriter写入import cv2 from ultralytics import YOLO model YOLO(runs/detect/helmet_exp/weights/best.pt) cap cv2.VideoCapture(test_video.mp4) fps cap.get(cv2.CAP_PROP_FPS) width int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) height int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) writer cv2.VideoWriter(result_video.mp4, cv2.VideoWriter_fourcc(*mp4v), fps, (width, height)) while True: ret, frame cap.read() if not ret: break results model.predict(frame, conf0.45, verboseFalse) annotated_frame results[0].plot() writer.write(annotated_frame) writer.release() cap.release()这里有个经验model.predict每帧调用一次视频解码和模型推理是串行的实际帧率大约只有十几FPS。如果追求实时性后续要通过多线程把读帧和推理分开这个下面讲PyQt5时细说。除了保存图片很多时候我们还需要在程序里拿到目标框坐标去做逻辑判断。results[0].boxes对象里有三个常用属性boxes results[0].boxes xyxy boxes.xyxy.cpu().numpy() # 左上角右下角坐标 cls boxes.cls.cpu().numpy() # 类别编号 conf boxes.conf.cpu().numpy() # 置信度这三个数组一一对应遍历时就能得到每个目标的类别和位置信息这是后面实现“未戴帽判断”的基础。3.4 模型导出与加速如果要在配置较低的电脑上跑不想每次推理都加载PyTorch环境可以把模型导出成ONNX格式yolo export modelruns/detect/helmet_exp/weights/best.pt formatonnx dynamicTrue导出后在PyQt5里用onnxruntime加载推理启动速度快、内存占用小还能免装CUDA版torch。不过onnxruntime的NMS需要自己写代码量会多一截如果机器性能够直接加载.pt文件最省事。我这里主流程先用.pt后面打包部署时再考虑优化。4. PyQt5界面开发4.1 主界面布局与QSS美化界面设计直接影响甲方的第一印象我不建议随便放个裸窗口。PyQt5里用QMainWindow QWidget布局整体分成三块左侧视频显示区占大头、右侧控制面板固定宽度、底部状态栏。控制面板放这些控件打开摄像头按钮、打开视频文件按钮、开始检测按钮、停止按钮、截图按钮、置信度阈值滑块、当前检测状态标签。状态栏显示FPS、当前画面分辨率和告警状态。整体风格用QSS调成深色工业风黑底配亮色文字监控系统用深色系在暗光值班室里不刺眼也更耐看。class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle(安全帽检测系统) central_widget QWidget() self.setCentralWidget(central_widget) layout QHBoxLayout(central_widget) # 左侧视频显示区 self.video_label QLabel(视频区域) self.video_label.setMinimumSize(960, 540) self.video_label.setStyleSheet(background-color: #000; color: #aaa;) layout.addWidget(self.video_label, stretch1) # 右侧控制面板 right_panel QFrame() right_panel.setFixedWidth(260) right_panel.setStyleSheet(background-color: #2b2b2b;) right_layout QVBoxLayout(right_panel) self.btn_camera QPushButton(打开摄像头) self.btn_video QPushButton(打开视频文件) self.btn_detect QPushButton(开始检测) self.btn_stop QPushButton(停止) self.btn_shot QPushButton(截图) # ... 添加到布局 layout.addWidget(right_panel) self.statusBar().showMessage(就绪)关键点视频显示用QLabel可以但要记得setScaledContents的坑——它会拉伸画面比例。更好的做法是收到一帧后手动缩放到QLabel的尺寸并保持宽高比用Qt.KeepAspectRatio。4.2 视频流读取与检测线程分离这是整个PyQt5开发中最容易踩坑的地方一定要多线程。如果在主线程里直接cap.read()再model.predict()界面必定卡死鼠标拖动窗口都会残影。我的方案是拆成两个线程视频采集线程负责读摄像头/视频文件通过signal发送原始帧QImage格式。检测线程从队列拿原始帧跑YOLOv11推理绘制检测框把标注帧发回UI线程显示。采集线程的简化代码class VideoThread(QThread): frame_signal pyqtSignal(QImage) def __init__(self, source0): super().__init__() self.source source self.cap cv2.VideoCapture(source) self.running True def run(self): while self.running: ret, frame self.cap.read() if not ret: break rgb cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) h, w, ch rgb.shape bytes_per_line ch * w image QImage(rgb.data, w, h, bytes_per_line, QImage.Format_RGB888) self.frame_signal.emit(image.copy()) self.cap.release()注意两个细节一是OpenCV读出来的是BGR要先cvtColor转成RGB否则显示出来的视频颜色发蓝二是QImage构造时rgb.data指向的缓冲区一旦被OpenCV下一帧复用就会出错所以emit时要image.copy()拿到一份独立的内存。检测线程的核心逻辑类似上面视频推理那一段区别是模型只初始化一次不要在run循环里反复YOLO(...)加载否则速度慢到让你怀疑人生。4.3 未戴帽判断逻辑与告警联动拿到模型推理的boxes后判断安全帽佩戴状态我用的是“包含/相交”逻辑。遍历所有类别id为person的框再找所有类别id为helmet的框判断person框内是否有一个helmet框与之相交且位置合理——安全帽应该戴在头上所以更多是判断helmet框的中心点是否落在person框的上半部分。简化版判断代码def judge_helmet(person_box, helmet_boxes, iou_threshold0.3): x1, y1, x2, y2 person_box person_area (x2 - x1) * (y2 - y1) for hb in helmet_boxes: hx1, hy1, hx2, hy2 hb ix1 max(x1, hx1) iy1 max(y1, hy1) ix2 min(x2, hx2) iy2 min(y2, hy2) inter_w max(0, ix2 - ix1) inter_h max(0, iy2 - iy1) inter inter_w * inter_h if inter / person_area iou_threshold: return True return False这个逻辑比单纯判断“helmet的iou大于多少”更能反映真实场景安全帽通常只占person框上部的一小块用inter除以person面积比用标准IoU更宽容也不容易误判。判断结果为False的person框我在绘制时用红色粗框标出并在状态栏和右侧面板同时显示“检测到未佩戴安全帽”必要时可以加个声音提醒。注意阈值不能太小否则画面里远处两个人挨得近别人的安全帽可能被误判成当前人的实际场景可以先用0.3再根据现场画面微调。4.4 高分辨率适配与界面缩放现在很多值班室用的是高分屏PyQt5默认不做DPI适配字体和控件会显得很小这就是经常被提到的“pyqt5适配分辨率”问题。在程序入口处加两行设置QApplication.setAttribute(Qt.AA_EnableHighDpiScaling, True) QApplication.setAttribute(Qt.AA_UseHighDpiPixmaps, True)布局上尽量用layout的伸缩因子不要用setFixedSize写死窗口尺寸。视频帧显示时用QSize做等比缩放scaled_pixmap pixmap.scaled(self.video_label.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation) self.video_label.setPixmap(scaled_pixmap)这样无论摄像头是720p还是1080p画面都能自适应窗口大小不会拉伸变形。4.5 截图、日志与参数配置面板截图功能很简单把当前标注帧保存成jpg。注意文件名最好带时间戳避免被覆盖比如capture_20250412_143052.jpg。timestamp time.strftime(%Y%m%d_%H%M%S) cv2.imwrite(fcapture_{timestamp}.jpg, current_frame_bgr)日志我用QTableWidget显示每次检测到未戴帽就插入一行时间、是否告警、告警截图路径同时把同样的内容追加写入csv文件方便后面做统计报表。csv写入时用utf-8-sig编码不然Excel打开会乱码。参数配置面板可以做成QTreeWidget加下拉框的形式树形结构管理“模型路径”“置信度阈值”“报警开关”等配置项。在QTreeWidgetItem的某一列嵌入QComboBox控件是个很实用的技巧item QTreeWidgetItem([报警模式, ]) combo QComboBox() combo.addItems([关闭, 声音, 画面闪烁]) tree_widget.addTopLevelItem(item) tree_widget.setItemWidget(item, 1, combo)这样用户在界面里就能直接改配置不用去改配置文件对非技术值班人员友好得多。5. 常见问题与排查技巧实录5.1 问题速查表开发过程中遇到的典型问题整理成一张表方便对照排查现象可能原因解决办法PyQt5窗口启动后黑屏OpenGL上下文初始化失败设置环境变量QT_OPENGLsoftwarePyQt5安装超时网络问题换清华/阿里源安装摄像头打开失败索引/权限问题从0开始换摄像头索引检查系统权限检测画面卡顿推理放在UI线程拆多线程把模型推理放到独立QThreadGPU显存不足batch或imgsz过大降低batch或imgsz降到480中文字体显示乱码/路径错误编码问题全局用utf-8路径尽量用英文打包后运行提示缺少模型资源未打包把best.pt放到exe同目录或用外部资源路径其中“检测画面卡顿”是新手最容易犯的错我实测过把model.predict(frame)直接写在UI线程里1080p视频只剩3FPS界面拖都拖不动拆线程之后能到25FPS以上完全两个体验。5.2 打包发布经验PyQt5项目交付给甲方不能让人家装Python环境一定要打包成exe。我用的是PyInstaller命令pip install pyinstaller pyinstaller -w -F main.py-w表示不显示控制台窗口-F表示打成单文件。但单文件模式有两个问题一是启动时要把临时文件解压出来第一次打开会比较慢二是容易被杀毒软件误报工地那台电脑的管家软件直接给拦了。后来我改成-D目录模式启动快很多误报率也低一些交付时把整个文件夹一起发过去。PyInstaller不会自动打包yolo权重要么把best.pt放到exe同目录要么在spec文件里把权重作为data文件加进去。我个人建议放同目录因为甲方后面想换新模型直接替换文件就行不用重新编译。5.3 系统扩展方向整个YOLOv11PyQt5的框架其实是一个通用“视频目标检测桌面工具”换成数据集就能做很多事。比如把安全帽数据集换成占道经营数据集就能搭一个城管用的店外经营检测系统换成桥墩病害数据集配合工地现场图片可以做一个施工病害巡检工具。核心代码基本不用动换数据、换模型权重、调整类别名称界面跟推理逻辑完全复用。这个项目还有几个可以继续深挖的方向把模型从yolo11n换成yolo11s或yolo11m提升精度在YOLOv11中接入自注意力机制模块来提升小目标检测能力用TensorRT做推理加速在低配工控机上把帧率再提一档。每一项都能单独开一篇教程这里先把基础版的系统跑通后面再慢慢迭代。最后分享一个个人体会安全帽检测在CV里算相对简单的任务花大力气调模型收益其实有限真正决定项目落地体验的是数据质量和界面稳定性。我在这个项目里最大的教训就是第一版过度纠结模型精度结果现场一跑画面灰蒙蒙、黑屏、卡顿各种问题轮番上阵甲方直接给差评。后来把精力放到整理干净的数据、写稳的界面逻辑和打包流程上系统才真正被用起来。如果你也在做类似项目建议先把端到端流程跑通再回头优化细节稳步推进比一步到位实在得多。