☰
YOLOv8+PyQt5行人过马路危险行为检测告警系统实战解析
2026/10/12 0:43:06 网站建设 项目流程

简介:基于YOLOv8与PyQt5的行人过马路危险行为检测告警系统,面向计算机视觉、深度学习方向的在校生、研究者或企业开发者,主要解决过马路场景中行人低头玩手机、持机打电话等危险行为的实时识别,同时检测行人、斑马线、车辆等目标。压缩包共878个文件,大小约175.44MB,涵盖图像数据集与标注文件(jpg/txt)、Python源码与依赖配置(py/yaml)、训练好的模型权重(pt)、说明文档与部署脚本(md/sh)等;项目拆分为带GUI界面的推理模块和可用于独立训练调优的ultralytics源码,便于从环境配置到界面演示的完整衔接。目前已有1515人学习下载。压缩包附带部署教程,覆盖Anaconda虚拟环境创建、依赖安装、GUI加载模型与图片/视频/视频流测试、自定义数据集训练、评估指标查看及无GUI推理输出等关键流程,并提供数据集配置说明,整体适合作为毕业设计、课程项目或行人危险行为预警系统的快速落地参考。

1. 这个 zip 里到底装了什么:从帧到告警,一条完整的工程链路

城市路口的监控画面里,行人低头看手机、突然加速跑动、闯红灯横穿马路,这些行为靠人眼盯十几个屏幕根本盯不过来。基于 YOLOv8+PyQt5 的行人过马路危险行为检测告警系统,做的就是这件事:用 YOLOv8 在视频帧里同时检出行人、车辆和交通信号灯,再用 PyQt5 搭一个桌面告警台,把检测结果一帧一帧画出来,命中危险行为就触发报警。解压这个 zip,里面是源码、数据集、训练好的权重、评估指标图表和部署教程,目标就是让拿到它的人不用从零开始爬坑,直接跑通一条从训练到告警的完整链路。这套方案适合做智慧交通课设、城市路口监控原型验证,以及想把 YOLOv8 落成桌面程序的一线开发者。

2. 环境装配与源码结构:把 YOLOv8 和 PyQt5 先跑在同一条轨道上

2.1 源码包里那几块东西,各自是干什么的

解压 zip 之后,第一件事不是急着运行,而是把目录结构摸透。常见的组织方式是分五个目录加一个文档:dataset/放标注好的图片和标签,models/放训练好的权重文件,utils/放检测和告警逻辑,gui/放 PyQt5 界面代码,runs/放训练曲线和评估指标图片,部署教程.md是唯一入口文档。

一个容易犯的错误是直接双击main.py,结果报缺模块。正确做法是先看部署文档里的环境要求。这套系统依赖 CUDA 版的 PyTorch、ultralytics 库、PyQt5 和 OpenCV-Python,四者版本必须互相兼容。我一般会先建一个独立的 conda 环境,避免把开发机上的其他项目环境搅乱。

2.2 用 conda 重建环境的完整命令与参数说明

打开 Anaconda Prompt 或终端,按下面的命令逐条执行。这里以 Python 3.10 为例,兼容性和稳定性都经过多数路测验证。

conda create -n crossing_detect python=3.10 -y conda activate crossing_detect pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 pip install ultralytics pip install PyQt5 pyqt5-tools pip install opencv-python

torch 和 torchvision 的版本必须和你的显卡驱动匹配。--index-url指定 CUDA 11.8 的预编译包,如果你的显卡驱动较新,也可以换成 cu121 来装。装完之后在 Python 里执行import torch; print(torch.cuda.is_available()),输出True才代表 GPU 可用。注意 ultralytics 会自动依赖 numpy 和 pandas 的特定版本,如果后面 import 报错,多半是 numpy 版本冲突,把 numpy 降到 1.26.4 通常能解决。

2.3 检查权重文件和类别配置文件是否对得上

拿到源码包后,先看models/目录下有没有.pt权重文件,以及dataset/里的data.yaml。这个 yaml 文件里定义了类别列表和训练验证数据路径,格式大概是这样的:

path: dataset train: images/train val: images/val names: 0: pedestrian 1: phone_user 2: runner 3: red_light

names的顺序必须和训练时权重学习到的类别顺序完全一致。如果权重是在0: pedestrian, 1: phone_user, 2: runner, 3: red_light上训练的,而 yaml 里写反了,检测结果就会张冠李戴。拿到包以后,先用ultralytics自带的 check 功能跑一帧图验证一下,确认每个类别的置信度输出正常再进行下一步。

3. 用自己的数据训练危险行为检测模型:目录划分、标注与训练参数

3.1 类别定义决定了系统的上限:什么算「危险行为」

这套系统的核心不只是检出「有行人」,而是判断行人的行为是否需要告警。常见类别设计是行人、低头族、奔跑者和红灯状态四种,也可以把车辆加进去作为第五类来辅助判断冲突风险。

设计类别时最容易犯的错是把「行为」和「状态」混在一起。红灯是一个状态,低头看手机是一个行为,两者在图像上的特征完全不同。红灯适合用目标检测或颜色阈值来判断,低头族这种行为在单帧图像上也是可行的,因为头肩姿态和手部位置有明显特征。但「闯红灯」这种复合行为需要结合行人在斑马线上的位置和信号灯状态来推理,单靠一个检测框是做不到的,需要后处理逻辑判断。这个源码包的常见做法是检测出三类行为加一类信号灯,最后用一个 Python 逻辑模块做规则判断,而不是让模型直接输出「闯红灯」这种高层语义。

3.2 标注工具选择与标注格式转换

数据集目录里应该已经有一部分预标注好的图片,但你总得加自己的数据。标注工具用 LabelImg 或 X-AnyLabeling 都行,前者轻量,后者支持自动标注辅助。YOLOv8 需要的是 YOLO 格式的 txt 标注文件,每行内容是「类别ID 中心点x 中心点y 宽度 高度」,坐标值都是归一化到 0~1 的浮点数。

LabelImg 默认保存为 Pascal VOC 格式的 XML,需要转换。常见的目录划分是images/train、images/val、labels/train、labels/val四件套,再配合上面那个data.yaml。一张图对应一个同名 txt,文件名相同但扩展名不同,放在对应的 labels 目录下。转换脚本用下面的写法:

import os import xml.etree.ElementTree as ET from glob import glob def convert_voc_to_yolo(xml_dir, out_dir, class_names): for xml_path in glob(os.path.join(xml_dir, "*.xml")): tree = ET.parse(xml_path) root = tree.getroot() img_w = int(root.find("size/width").text) img_h = int(root.find("size/height").text) txt_name = os.path.basename(xml_path).replace(".xml", ".txt") with open(os.path.join(out_dir, txt_name), "w") as f: for obj in root.findall("object"): class_name = obj.find("name").text class_id = class_names[class_name] box = obj.find("bndbox") x1, y1, x2, y2 = [float(box.find(v).text) for v in ("xmin", "ymin", "xmax", "ymax")] x_center = (x1 + x2) / 2 / img_w y_center = (y1 + y2) / 2 / img_h bw = (x2 - x1) / img_w bh = (y2 - y1) / img_h f.write(f"{class_id} {x_center:.6f} {y_center:.6f} " f"{bw:.6f} {bh:.6f}\n") if __name__ == "__main__": class_names = {"pedestrian": 0, "phone_user": 1, "runner": 2, "red_light": 3} convert_voc_to_yolo("voc_xmls", "labels/train", class_names)

转换时最容易翻车的是坐标归一化除以了错误的尺寸。这张代码里img_w和img_h来自 XML 里原始图像的宽高,不是标注框的宽高,两者混用会让所有框位置偏移。另一个坑是 XML 里的bndbox值可能带小数点,直接转 int 会截断,所以上面代码用float()解析,最后再归一化。

3.3 训练命令与关键参数表

数据和 yaml 就位后,训练命令很简单:

yolo detect train data=dataset/data.yaml model=yolov8n.pt epochs=100 imgsz=640 batch=16 patience=20

几个参数是这套任务的重点。model=yolov8n.pt用的是轻量版骨干网络,路口监控场景对帧率敏感,n 版(nanoscale)在 GTX 1660 Ti 这种级别显卡上能跑到 60 FPS 以上,精度也能满足行为分类。epochs=100是起步值,实际看results.png里的验证损失曲线,如果 60 轮就收敛了就不用跑满。batch=16在 8G 显存上是安全值,显存小就降到 8。patience=20是早停参数,20 轮验证损失不下降就自动终止,这个能帮你省掉很多无效等待。

训练结束后的产物在runs/detect/train/目录,里面有best.pt和last.pt。部署时永远用best.pt,不是last.pt。loss 曲线图也在同一个目录里,看train/cls_loss和val/cls_loss两条曲线是否同步下降,如果训练损失降了验证损失反升,就是过拟合信号,回退到早停那一轮的权重即可。

3.4 数据集规模与类别不平衡的处理

行为检测任务里最容易出现类别不平衡:正常行走的行人样本可能占 70%,低头族和奔跑者各占 15%,红灯状态最少。训练初期模型会把频率高的类别学得特别好,低频类别几乎检不出来。常见做法是先用基础数据跑一版,看每个类别的 Precision 和 Recall 报告,哪类低了就专门补哪类的图。

补数据不是简单加图,而是要增加难例。同一个路口不同时段的光线变化、雨天反光、夜间低照度,这些都会影响行为识别。有效技巧是对图片做数据增强,但 YOLOv8 自带增强已经比较强,不需要重复叠加。如果某个类别样本量还是不够,可以靠复制粘贴增强,但要注意同一张图在 train 和 val 里不能同时出现。

4. 用 PyQt5 把检测结果变成告警台:GUI 与推理线程的三种接法

4.1 视频流处理的线程模型:别让检测把界面卡死

PyQt5 界面跑在主线程,YOLOv8 推理跑在 GPU 上,两者如果混在同一个线程里,界面会像幻灯片一样一卡一卡。根源是推理一次要几十毫秒,而 Qt 界面要求事件循环每 50 毫秒响应一次,推理期间界面事件队列全部积压。

常见做法是用QThread跑推理循环,通过信号把结果传回主线程。检测线程负责读帧、推理、画框,然后把带标注的 QImage 和告警标志发射出去,主线程只负责接收并刷新 QLabel。这样界面和推理互不阻塞。下面是一个最小可用的 Worker 框架:

import cv2 import numpy as np from PyQt5.QtCore import QThread, pyqtSignal from ultralytics import YOLO class InferenceThread(QThread): frame_ready = pyqtSignal(object) alert_triggered = pyqtSignal(str) def __init__(self, video_path, weight_path, conf_thres): super().__init__() self.model = YOLO(weight_path) self.cap = cv2.VideoCapture(video_path) self.conf_thres = conf_thres self.running = True def run(self): while self.running: ret, frame = self.cap.read() if not ret: break results = self.model.predict(frame, conf=self.conf_thres) annotated = results[0].plot() self.frame_ready.emit(annotated) for box in results[0].boxes: if int(box.cls) in DANGER_CLASSES: self.alert_triggered.emit(f"检测到危险行为: {DANGER_NAMES[int(box.cls)]}")

构造函数的三个参数里,video_path可以是摄像头索引(0表示第一个 USB 摄像头),conf_thres控制检测灵敏度,建议在界面里做成一个可拖动滑条,默认 0.35。self.model在初始化时加载,不在run()里加载,是因为模型加载本身要一两秒,放在初始化里避免重复加载。frame_ready信号传出的是 numpy 数组,主线程槽函数里要转换成 QImage 再显示。

4.2 视频帧到 QImage 的转换与绘制

OpenCV 读出来的帧是 BGR 格式的 numpy 数组,PyQt5 显示需要 QImage。转换时一个常见的花屏故障是忘了指定Format_RGB888或Format_BGR888。PyQt5 不提供 BGR 的直接格式,所以要么用cv2.cvtColor转成 RGB,要么利用Format_BGR888这种隐含通道顺序的格式。

from PyQt5.QtGui import QImage, QPixmap def numpy_to_qpixmap(arr): h, w, ch = arr.shape if ch == 3: rgb = cv2.cvtColor(arr, cv2.COLOR_BGR2RGB) qimg = QImage(rgb.data, w, h, 3 * w, QImage.Format_RGB888) else: qimg = QImage(arr.data, w, h, ch * w, QImage.Format_RGBA8888) return QPixmap.fromImage(qimg.copy())

qimg.copy()是必须的,因为 QImage 只持有传入数组的引用,numpy 数组在下一帧被覆盖后,界面上的画面会出现撕裂或花屏。复制之后就没这个问题了。3 * w是每行字节数,也就是步长,漏掉或写错会让图像看起来像被撕碎了再拼起来一样。

4.3 告警触发的后处理逻辑与界面联动

YOLOv8 输出的是行为类别框,告警逻辑在框之上再叠一层规则。常见规则是:检测到phone_user或runner类别时,若该框中心点位于斑马线兴趣区域内,且当前信号灯状态为红灯或绿灯转红灯期间,则判定为危险行为。

告警动作通常包括三件事:界面状态栏变红、播放提示音、把告警帧截图存到本地。截图用下面这段代码:

import datetime import os def save_alert_frame(frame, alert_dir="alerts"): os.makedirs(alert_dir, exist_ok=True) ts = datetime.datetime.now().strftime("%Y%m%d_%H%M%S_%f") path = os.path.join(alert_dir, f"alert_{ts}.jpg") cv2.imwrite(path, frame) return path

时间戳精确到微秒可以避免多路告警同时触发时文件名冲突。告警截图建议保存原帧而不是标注后的帧,原帧占空间更小,而且保留人工复核的完整性,标注帧反而会遮挡部分细节。告警声音用 QSound 播放一个短 WAV 文件即可,注意不要用循环播放,否则连续告警时声音会叠成一团噪声。

4.4 界面上还要暴露哪些控制项

除了显示画面,告警台还应该提供几个控制项:置信度阈值滑条、IOU 阈值滑条、开始/暂停按钮、告警日志列表。置信度阈值滑条直接影响误报率,调低了行人手臂摆动就会被识别成危险行为,调高了细小的低头动作会被漏掉。IOU 阈值影响相邻框合并,多人密集过马路时这个值很关键。

另外把 FPS 显示在界面上是调试利器。如果实际 FPS 只有个位数,先看是不是用了model.predict(frame, verbose=True)每次调用都在重新做前处理,改成model(frame)这种直接调用会快很多。或者把输入尺寸从 640 降到 416,帧率能提升 50% 以上,代价是远距离小目标检出率下降。

5. 避坑:跑这套源码最常见的 5 个翻车现场与排查方法

5.1 显存不足:训练时 OOM,推理时程序直接退出

现象:训练刚跑十几个 batch 就报CUDA out of memory,或者 GUI 推理运行几分钟后程序闪退。

原因:batch size 或 imgsz 超过显卡显存承受范围。模型本身不大,但输入图像越大,中间特征图占的显存呈平方级增长。

解决:把 batch 从 16 降到 8,imgsz 从 640 降到 512。如果还不行,用torch.cuda.set_per_process_memory_fraction(0.8)限制 PyTorch 显存占用率。推理端闪退的另一个常见原因是多线程里访问了同一个 CUDA 上下文,InferenceThread里self.model不能跨线程重新绑定,初始化后只读使用。

5.2 PyQt5 和 OpenCV 的版本冲突导致 import 报错

现象:pip install PyQt5 pyqt5-tools之后,执行from PyQt5.QtWidgets import QApplication报错DLL load failed。

原因:pyqt5-tools 会拉入一个旧版 PyQt5 依赖,和已装的 PyQt5 版本冲突,DLL 入口点被覆盖。

解决:不要装 pyqt5-tools,那个包里带的是 Qt Designer 的可执行程序,需要界面设计工具的话单独下载独立的 Qt Designer 安装包。已经踩坑的话先卸载再重新安装:

pip uninstall PyQt5 pyqt5-tools -y pip install PyQt5==5.15.10

5.3 中文路径导致模型加载失败

现象:源码包解压到D:\项目\行人检测\这种带中文的路径下,加载权重时报错找不到文件或 UnicodeDecodeError。

原因:Windows 下 OpenCV 和 PyTorch 的部分底层文件操作使用 ANSI 编码,中文路径会乱码。

解决:把整个项目放到纯英文路径下,比如D:\CrossingAlert\。数据集里也不要出现中文文件名。这个问题在 Linux 下不存在,Windows 下跑一定要规避。

5.4 训练时 loss 正常但验证时所有类别都检不出来

现象:训练日志里的 loss 曲线平滑下降,results.png看起来正常,但用训练好的权重跑单张图,一个框都画不出来。

原因:data.yaml里val目录路径写错,验证过程实际上是在空集上跑的,验证损失和 mAP 全是垃圾数据。训练过程是按train路径读取的,所以训练 loss 正常,给人的错觉是一切都好。

解决:训练前先手动数一数验证集图片数量。训练日志里val阶段会打印Scanning val/...,确认扫描到图片数量不是 0。另外在data.yaml里用绝对路径最省心,相对路径依赖运行命令时的当前目录,换个执行方式就断了。

5.5 CUDA 版本和 PyTorch 不匹配导致 CPU 推理

现象:程序能跑,但 FPS 只有个位数,GPU 利用率一直是 0%。

原因:torch.cuda.is_available()返回False,或安装了 CPU 版的 PyTorch。升级驱动后旧的 CUDA 运行时缓存也会导致这类问题。

解决:用nvidia-smi看驱动支持的 CUDA 版本,再按前文命令重装对应版本的 PyTorch。验证是否真正用上 GPU,在 Python 里执行:

import torch print(torch.cuda.is_available()) print(torch.cuda.get_device_name(0))

输出设备名代表 GPU 可用,然后用watch -n 1 nvidia-smi实时观察训练时显存和 GPU-Util 是否活跃。

6. 评估与交付:mAP、混淆矩阵之外的部署硬账

6.1 看懂runs/detect/val下的评估指标

训练多轮后,要对best.pt做一次独立验证,用yolo detect val命令生成评估报告。重点看三张表和一张图:精确率召回率曲线、混淆矩阵、F1 曲线和各类别的 Precision/Recall 数值表。

mAP50-95 比 mAP50 更能反映行为检测的定位精度。拿低头族类别来说,如果 mAP50 有 0.9 而 mAP50-95 只有 0.4,说明模型能大致框住人,但框的边界抖动很大。告警系统对边界框精度要求没那么高,面积相近就行,所以这类模型可用。而如果 mAP50 本身就低,说明类别特征学习不到位,需要回去补数据。

混淆矩阵里最容易出问题的组合是phone_user和pedestrian互混,低头族和正常行人在远距离低分辨率下确实难分。如果混淆率高于 30%,建议在 GUI 里加一个告警前置延迟——连续 3 帧都识别到同一行为才触发告警,能滤掉大量单帧误报。

6.2 导出到 ONNX 或 RK3588 平台:部署的另一条路

桌面 GUI 跑通了,下一个需求往往是把它推到边缘设备上。常见做法是把 PyTorch 权重导出成 ONNX 再转成推理引擎格式。导出命令如下:

yolo export model=models/best.pt format=onnx opset=12

opset=12是兼容性和算子支持度的平衡点。注意导出后的 ONNX 模型输入输出都是动态尺寸,在 RKNN 工具链里转成 RKNN 格式时,要固定输入尺寸为 640×640,否则部分算子无法映射到 NPU 上。热词里频繁出现的 RK3588 部署路线一般是:YOLOv8 权重 → ONNX → RKNN → RKNN-Toolkit2 加载模型推理。这条链路里最容易卡住的是某些 YOLOv8 算子如SiLU和DFL在 NPU 上不支持,先转 ONNX 跑rknn.analyzer看算子兼容性列表,再决定是改导出配置还是换 YOLOv8n 的简化版。

6.3 一段反复调整出来的经验

我自己做这类系统时,最深的教训是:功能跑通不难,难在「告警密度」的设定。用一段路口监控做了完整测试后,发现默认置信度 0.5 的时候每分钟误报十几次,盯一上午就会对告警声脱敏。把置信度提到 0.65,再把连续三帧确认的延迟逻辑打开,误报降到每小时两三次,这个密度才是值守人员能接受的水平。这个阈值不是算出来的,是找一个雨天和晴天的下午各看 20 分钟试出来的。

如果你拿到源码包,建议第一周先别想着改模型,把默认参数跑通,然后每天花半小时看告警截图,手动记下哪些是误报哪些是漏报,一周之后你对这个数据集的脾气就有底了。这比我给你任何参数表都管用。希望这篇笔记能帮你把这个系统跑顺,也祝你少踩几个我踩过的坑。

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

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

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

立即咨询