news 2026/10/18 4:52:41

基于YOLOv5与PyQt5的行人车辆实时检测计数系统开发实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于YOLOv5与PyQt5的行人车辆实时检测计数系统开发实践

前阵子有个做交通调研的朋友找我帮忙,想做一个能自动统计路口行人和车辆数量的工具。一开始以为只是简单的目标检测,后来实际做下来才发现,从模型选型、数据训练到桌面界面开发、计数逻辑实现,每一步都有不少坑。最后我做出了一套基于YOLOv5检测模型、用PyQt5搭建桌面界面、在COCO数据集上训练过、支持行人车辆实时检测与计数的系统。这篇文章就想把完整的思路、操作步骤和踩过的坑都聊透,给正在做类似目标检测上位机应用、或者想入门YOLO系检测开发的人一个可以参考的落地范本。

1. 项目设计与技术选型解析

1.1 为什么选择YOLOv5而不是其他模型

这个项目的核心任务是行人车辆检测与计数,实时性是硬指标。我对比过Faster R-CNN、EfficientDet、YOLOv8等方案,最终选择了YOLOv5。YOLOv5是单阶段检测器,直接把目标框和类别概率一步回归出来,在GPU上能轻松跑到几十ms一帧,CPU下用nano版本也能勉强流程跑。Faster R-CNN精度确实不差,但对实时视频流来说还是偏重,光RPN那一步就带来不少额外延迟,部署起来也更麻烦。YOLOv8虽然更新,但v5的社区生态成熟度是没法比的优势,遇到问题搜一下基本能找到现成答案,模型结构也更透明,方便按需修改输出层。

YOLOv5还提供了s、m、l、x四种规格,可以根据算力灵活切换。我这个项目用的是YOLOv5s,它在精度和速度上最均衡,在3060显卡上640分辨率推理只需10ms左右,完全满足实时检测需求。对于行人车辆这类不算极端的目标,v5s的精度已经足够。如果你要检测特别小的远距离目标,可以换v5m或提高输入分辨率,但代价是帧率下降。我的建议是:先确定你的部署环境和延迟容限,再决定规格,不要一上来就套最大的模型。

单阶段回归的好处也体现在代码实现上。YOLO的输出是一个三维张量,shape为[batch, anchor_num, (class_num+5)*grid_num],解码逻辑直接清晰,不需要额外的RPN或ROI Pooling。这一点在后续封装成PyQt界面里的Detector类时,省了很多事。

1.2 PyQt界面框架选型理由

模型推理再快,最终还是要给用户一个好用的操作界面。最初我考虑过三个方案:直接OpenCV窗口显示、Flask网页前端、PyQt桌面程序。OpenCV命名窗口最轻量,但按钮、滑块、统计图表这些交互控件都很难做,只能靠键盘事件,体验太原始。Flask做一个局域网网页确实方便远程查看,但每次跑起来还得搞浏览器、配置端口,对非技术用户来说反而不友好。PyQt则正好踩在点上:原生窗口性能好,控件丰富,QThread做多线程又不卡界面,打包成exe也方便分发。

选择PyQt5而不是PyQt6,是因为PyQt5的教程、提问、样例代码存量最大,踩坑时能最快找到参考。PyQt6虽然更新,但一些API有变动,比如枚举类型必须用全限定名,对于写惯了PyQt5的人来说反而容易出错。PyQt5配合Qt Designer可以拖拽生成.ui文件,但我在实际项目中更喜欢纯代码写界面,因为要动态控制检测画面刷新,纯代码更直接,也避免.ui文件到处拷来拷去的麻烦。

另外,PyQt的信号槽机制非常适合做视频流的异步刷新。画面检测线程将QImage通过信号发到主线程,主线程槽函数里直接更新QLabel,天然就解决了跨线程更新UI的崩溃问题。这一点我会在第3节里详细展开。

1.3 系统整体架构与模块划分

整个系统我从结构上拆成了三块:检测模型模块、业务逻辑模块、界面展示模块。检测模型模块负责加载权重、预处理图像、推理、非极大值抑制,对外只暴露一个detect(frame)方法;业务逻辑模块负责视频源读取、帧调度、计数统计、ROI过滤;界面展示模块负责按钮事件、画面绘制、计数显示。模块之间通过接口或信号连接,互不干扰。

这种分层最大的好处是独立可测。我一开始先把检测模块单独写好,用命令行输入一张图验证推理正确,然后再写界面,避免界面bug和模型bug纠缠在一起。后面加计数功能时,也只是在业务逻辑层里加了一个Tracker类,没动界面代码。

架构上没有用复杂的UML,核心就是一条单向数据流:视频帧 -> 推理 -> 画框 -> 信号发送 -> 界面显示。如果你是照着自己做,建议也按这个思路拆,千万别把模型调用和界面刷新写在一个函数里,后面你一定会想骂人。

2. 模型核心原理与COCO数据集训练要点

2.1 YOLOv5网络结构解构

YOLOv5能兼顾速度和精度,关键在于它的结构设计。Backbone用的是CSPDarknet,CSP结构把特征图分成两部分,一部分走密集卷积,另一部分直接连接,既减少了计算量,又保持了梯度流丰富度。Neck部分用PANet做特征融合,它有多条自底向上和自顶向下的路径,让小目标也能从大特征图里拿到语义信息。Head是三个不同尺度的检测头,分别对应原图缩小8倍、16倍、32倍后的特征图,所以模型能同时检测大中小目标。

COCO数据集是目标检测领域最常用的基准,包含80个常见物体类别。行人对应person类,车辆对应car、bus、truck、bicycle、motorcycle等类。我们做行人车辆检测,最直接的方案是直接使用COCO预训练权重,因为它已经见过这些类别的无数变体,特征提取能力非常强。但为了让模型更贴合你自己的场景,建议还是在COCO子集上继续微调。

注意YOLOv5的锚框(anchor)是自动学习的,训练时会在你的数据集上重新聚类生成合适的初始anchor大小。如果是基于COCO预训练权重继续微调,训练命令里的--weights yolov5s.pt会自动保留之前的anchor,再通过训练微调,效果一般不错。

2.2 COCO数据集处理与训练配置

从COCO数据集训练要解决两个问题:数据格式转换和类别选择。COCO原生是JSON标注,而YOLOv5需要每个图片对应一个txt文件,每行格式是class cx cy w h,所有坐标都归一化到0~1。我写了一个脚本,从COCO的annotations中筛选出person、car、bus、truck、bicycle这几个类别的标注,过滤掉目标宽度或高度小于10像素的极端小目标,然后生成YOLOv5格式标签。筛选后大约得到了6万张训练图片,这个量级训练YOLOv5s完全足够。

训练配置文件我用了一个自定义data.yaml,内容大概是这样:

train: ./dataset/images/train val: ./dataset/images/val nc: 5 names: ['person', 'car', 'bus', 'truck', 'bicycle']

训练命令也很直接:

python train.py \ --data data/custom.yaml \ --cfg yolov5s.yaml \ --weights yolov5s.pt \ --epochs 100 \ --batch-size 16 \ --img 640

这里--batch-size要看你显卡显存,3060 12G跑16没问题,如果显存小就降到8。--img 640是标准输入分辨率,如果想提升小目标检测效果可以改为768,但训练时间会明显增加。优化器默认是SGD,我习惯手动加一个--cos-lr,让学习率余弦衰减,收敛更稳定。

2.3 训练参数与调优经验

训练过程中我踩过不少坑,最典型的是数据类别不平衡。COCO里person的样本非常丰富,而bus这类就相对少。训练出来的模型对行人的mAP明显高于公交车。解决方式是我给每个类别的损失权重做了加权,在--cls参数上调高稀缺类别的系数。你可以直接改模型配置文件里的cls_pw值,或者在损失函数处单独改,但最简单的是在训练命令后加--cls 0.6(默认0.5),增大分类损失占比,让模型更重视分类任务。

另一个经验是观察mAP曲线。训练日志里会输出mAP@0.5和mAP@0.5:0.95,前者是IoU阈值0.5下的平均精度,后者更严格,从0.5到0.95每隔0.05算一次平均。我训练到第60个epoch时,mAP@0.5已经稳定在0.88左右,但mAP@0.5:0.95只有0.62,说明模型框的位置还不够精确。这种情况我后来调整了--iou阈值(NMS的IoU阈值从0.45降到0.4),召回率稍降但精确率提升,最终计数场景下误报减少了不少。

过拟合也是常客。如果训练集loss下降但验证集loss上升,第一个动作不是调模型,而是减少epoch,或者增强数据增强。YOLOv5自带的mosaic增强在小目标上效果显著,我关掉mosaic跑了一次对比,同样epoch下mAP掉了3个百分点以上。所以不要轻易关掉mosaic。

3. 从零搭建人车检测计数系统实操

3.1 开发环境与依赖安装

我的开发环境是Ubuntu 20.04,Python 3.8,PyTorch 1.9,CUDA 11.1,显卡RTX 3060。Windows下也能跑,但Linux下编译OpenCV和一些底层库更方便。第一步是拉取YOLOv5代码库并安装依赖:

git clone https://github.com/ultralytics/yolov5.git cd yolov5 pip install -r requirements.txt

然后额外安装PyQt5和opencv-python:

pip install PyQt5 opencv-python

这里要注意PyTorch版本和CUDA的对应关系,PyTorch 1.9对应CUDA 11.1,版本不对会报CUDA driver error。如果电脑不支持CUDA,也可以先CPU跑,只是帧率会低很多。我建议无论如何先跑通CPU模式,确认代码流程没问题,再上GPU加速。

3.2 模型训练与权重转换

按照第2节给出的命令训练完成后,runs/train/exp目录下会生成best.pt和last.pt。best.pt是验证集上mAP最高的权重,我们用它来做推理。

在PyQt中加载YOLOv5权重,我推荐直接使用YOLOv5原生的模型加载方式,避免依赖hub下载外部代码。代码这样写:

import torch class Detector: def __init__(self, weights='best.pt', device='cuda'): # 加载YOLOv5模型 self.model = torch.load(weights, map_location=device)['model'].float() self.model.fuse().eval() self.device = device self.model.to(device) # 设置推理参数 self.conf = 0.4 self.iou = 0.45 # 自定义类别 self.classes = ['person', 'car', 'bus', 'truck', 'bicycle'] def detect(self, img): # 输入BGR图像,输出检测结果 results = self.model(img, size=640, conf_thres=self.conf, iou_thres=self.iou) return results

torch.load返回的dict里有model字段,取出来之后通过fuse()合并部分卷积层,加速推理。注意一定要加.eval(),否则batch normalization层的行为会不一致,导致检测结果不稳定。

如果你的部署环境没有PyTorch或不想带这么大依赖,可以转成ONNX:

python export.py --weights best.pt --include onnx --img 640

但ONNX在Python里还要用onnxruntime加载,而且PyQt项目里直接带着PyTorch也才几百MB,部署到有显卡的机器上完全能接受,所以我这里直接用.pt。

3.3 PyQt界面开发与核心逻辑实现

界面布局我用的是垂直布局方案:顶部放操作按钮(打开视频、打开摄像头、开始暂停、停止)、置信度滑块、计数显示标签;中间一个大的QLabel用于显示检测画面;底部放一个统计文本区,显示各类别数量。所有按钮都连接到对应的槽函数,滑块通过valueChanged信号实时更新Detector的conf属性。

视频流处理是整个界面开发里最容易卡死的地方。如果你直接在按钮的槽里读取视频帧并做推理,界面一定会失去响应,因为推理是阻塞的。正确方案是用QThread把视频读取和检测放到子线程,然后通过信号把图像帧发回主线程。我这里定义了一个VideoThread:

import cv2 import numpy as np from PyQt5.QtCore import QThread, pyqtSignal, QTimer from PyQt5.QtGui import QImage class VideoThread(QThread): change_pixmap_signal = pyqtSignal(QImage) count_signal = pyqtSignal(dict) def __init__(self, detector): super().__init__() self.detector = detector self._run_flag = True self.cap = None self.source = 0 # 默认摄像头 def set_source(self, source): self.source = source def run(self): self.cap = cv2.VideoCapture(self.source) while self._run_flag: ret, frame = self.cap.read() if not ret: break # 推理 + 画框 results = self.detector.detect(frame) annotated = results.render()[0] # numpy数组 # 统计 det_df = results.pandas().xyxy[0] counts = det_df['name'].value_counts().to_dict() # 转换QImage rgb_image = cv2.cvtColor(annotated, 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) self.change_pixmap_signal.emit(qt_image) self.count_signal.emit(counts) self.cap.release() def stop(self): self._run_flag = False self.wait()

主界面里连接信号:

self.video_thread = VideoThread(self.detector) self.video_thread.change_pixmap_signal.connect(self.update_frame) self.video_thread.count_signal.connect(self.update_counts)

槽函数里直接self.image_label.setPixmap(QPixmap.fromImage(qt_image)),这个操作在GUI线程,非常轻量。

我在实际开发中发现,不需要每帧都emit信号。摄像头30fps,界面最多显示25fps,人眼根本看不出差别。计算帧间隔,每3帧发一次信号,CPU占用能降不少。如果播放本地视频,可以用cv2.CAP_PROP_FPS控制帧率,避免渲染速度太快导致视频播放加倍速。

3.4 行人车辆计数算法实现

计数看起来简单,实际上暗藏很多坑。最原始的思路是每一帧统计检测框个数,但那只是“画面中当前有多少人/车”,不是“累计通过多少人/车”。如果要统计一个路口的车流量,必须做目标跟踪,给每个目标分配唯一ID,然后只对首次出现的目标计数。

我实现了一个轻量级的质心跟踪器。核心流程是:

  1. 对当前帧检测到的每个目标,计算其边界框中心点。
  2. 与上一帧的所有目标中心点进行距离匹配,使用简单的贪心算法,也可以调用scipy.optimize.linear_sum_assignment做匈牙利匹配。
  3. 如果距离小于某个阈值(比如50像素),认为是同一个目标,继承上一帧的ID。
  4. 如果没有匹配到,就分配一个新ID,并把这个ID计入总数。
  5. 如果一个ID连续多帧(比如30帧)没有出现,则认为目标已经离开画面,删除该ID。

这种跟踪逻辑虽然朴素,但对固定摄像头场景足够了。重要的是它比DeepSORT轻量得多,CPU占用小,完全够这个项目的需求。

计数统计时有几个细节值得注意:第一,建议设置一个计数区域(ROI),只统计ROI内的目标,避免画面边缘走动的路人被误计入。可以用OpenCV的pointPolygonTest判断中心点是否在区域内。第二,对于车辆和行人重叠严重的场景,NMS后容易漏掉一个目标,可以通过降低置信度阈值缓解。第三,计数结果要定期清零,比如每分钟输出一次“最近一分钟内有12辆车通过”,这样更有统计意义。

4. 常见问题排查与性能优化实录

4.1 训练阶段典型问题速查

训练阶段的问题最能劝退人。这里把我在训练过程中遇到的高频问题整理成一张速查表,方便各位对照排查:

问题现象可能原因解决方案
显存溢出OOMbatch过大或分辨率过高调小batch、降低img尺寸、开启--amp混合精度
训练loss显示NaN学习率过大或标签有异常降低初始学习率(比如0.01降到0.005),检查标签坐标是否越界
验证集mAP一直很低类别不平衡或数据集质量差检查标签是否正确、增加稀缺类别样本、调整类别权重
模型过拟合epoch过多减少epoch,或增加mosaic、mixup增强
训练速度极慢没开启GPU或数据读取瓶颈检查nvcc -V、PyTorch是否用CUDA,将数据放到SSD上

特别提醒一句:COCO数据集下载非常耗时间,而且文件很大。建议只下载需要的部分,或者用网盘分享,别去硬磕官方服务器。我自己是先用官方间接方式下载,速度不稳定,后来找了一个校园网镜像才顺利。

4.2 界面卡顿与视频流处理优化

界面卡顿的根源基本都在主线程里做了耗时操作。除了把视频处理放到子线程外,还有一个容易被忽略的坑:QImage转换时千万不要用QPixmap多次拷贝。正确的做法是用QImage(data, w, h, bytes_per_line, QImage.Format_BGR888)直接包住numpy数组的内存,再转为QPixmap。如果每帧都做一次cv2.cvtColor然后再QPixmap.loadFromData,性能会差很多。

我实测过一个优化前后的对比:优化前处理截图和显示总共耗时45ms(约22fps),优化后降到28ms(约36fps)。区别主要有三点:一是避免复制数据,直接引用numpy数组;二是减少信号频率,每3帧发一次;三是关闭不需要的日志输出,比如YOLOv5默认会打印推理耗时,量产时要关闭。

还有一点,滑动置信度阈值的时候,不要直接在检测线程里读滑块值,因为线程间变量共享需要加锁。我用的是pyqtSignal通知Detector更新conf值,或者在界面线程里设置self.detector.conf = value,属于Python对象属性读写,是安全的,实测没有任何问题。

4.3 计数准确性改进方案

计数不准确一般分成漏检、误检、重复计数三类。漏检的常见特征是某个目标时有时无,尤其是行人被车辆遮挡时。解决漏检有两个方向:一是降低置信度阈值到0.25,但会增加大量误检;二是使用更高的推理分辨率,超分辨目标更多。这两者需要平衡。我在项目里先试了低阈值,发现画面里空调外机都会被当成车,后来改成了ROI过滤来剔除无关区域。

误检的特征是画面里出现大量闪烁的框,尤其在逆光或夜间。对策是设置一个合理的置信度阈值,比如0.4,同时结合类别过滤,只保留person和car等我们关心的类别。还可以用max_det限制单帧最大检测目标数,避免纯噪声被当成目标。

重复计数是最头疼的。由于颠簸或抖动,同一辆车可能在两帧之间中心点偏移过大,导致被当成新目标。我的经验是把匹配距离阈值设得适中(30~60像素),同时记录每个ID的最后出现时间,如果某个ID消失后又在下一次出现,但间隔很短(小于0.5秒),就还是复用原来的ID。这个小技巧能大幅减少重复计数。

4.4 实际场景部署的几点经验

最后聊几个只有实际部署时才会发现的用户体验问题。摄像头安装角度很重要,我试过在2米高的位置俯拍,检测效果很好,行人车辆交错也能稳定跟踪;但如果是人眼平视角度,车辆遮挡行人的情况非常严重。建议摄像头高度至少3米,角度下压30度。

夜间环境是一个巨大的坑。普通RGB摄像头在夜间几乎检测不到深色衣服的行人,后来换成红外补光的摄像头后好很多。如果只是测试demo,建议白天固定光照场景,不要在晚上硬刚。

模型推理时间要保持余量。我用的YOLOv5s在3060上10ms,加上界面渲染和跟踪,整体接近25ms/帧,刚好满足实时。如果你要在更高分辨率(1080p)下运行,可以把输入分辨率设为640不变,让模型内部缩放,画面显示在原分辨率上,效果不错。

CPU上运行也不是不可能,但只能跑YOLOv5n或YOLOv5s,分辨率降到416。我试过在只用了CPU的笔记本上跑,速度约200ms/帧,也就是5fps,勉强能用但绝对谈不上实时。所以无论如何建议准备一块带CUDA的N卡,哪怕是老旧的1060都会好很多。

就我个人经验来说,做这种检测系统,最忌讳一上来就追求花哨功能。先把“视频进、框出”这条主线跑通,再逐步加计数、跟踪、ROI、统计图表。我这次也是先写了命令行检测脚本,确认模型在测试视频上表现稳定,才开始搭PyQt界面。界面部分遇到了几次闪退,基本都是线程访问UI资源导致的,后来严格用信号槽通信就彻底解决了。如果你正在做类似项目,建议你严格按照这个顺序走:模型训练 -> 命令行检测 -> 多线程封装 -> 界面搭建 -> 计数优化。每一步都有明确的验收标准,出了岔子也更容易定位问题。

最后再分享一个实用的小技巧:训练好后,先跑一段真实场景视频,把检测结果和实际画面录制下来,回头对着视频看漏检和误检的模式,比盯着mAP指标瞎猜有效得多。比如我就是在录播中发现公交车经常被识别成卡车,原因是公交车侧面有深色条纹,在YOLOv5s特征图上和卡车很像。后来我在数据集里额外加入了偏侧的公交车样本重新训练,这个误检才明显下降。这种针对性的数据补充,比调整模型结构带来的提升要直观得多,也便宜得多。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/18 4:50:04

从硬件评估到外设接通:在RB5开发板上部署OpenClaw本地智能体全记录

前些天我又把一套 OpenClaw 环境重新装到了那块 RB5 机器人开发套件上,这次参考资料比上次少,但踩的坑比上次多。整个过程折腾了差不多一个周末,最后跑通的时候反而没有太多兴奋感,因为大部分时间都耗在“环境依赖、模型格式、外设…

作者头像 李华
网站建设 2026/10/18 4:49:45

Halcon工业视觉系统进阶:模板匹配、标定与性能优化实战

在自动化产线守到凌晨,视觉抓取坐标始终偏了 0.3 毫米,压力大到头皮发麻。这种经历,做 Halcon 相关项目的人大概率都碰过。很多人以为到了进阶阶段,拼的是算子调用熟练度,但真正把工业级视觉系统跑稳之后才发现&#x…

作者头像 李华
网站建设 2026/10/18 4:48:58

Java Base64源码JAR包深度解析:查表法实现与跨平台兼容调试

简介:本资源是一个面向Java初学者与中级开发者的BASE64编解码工具库实践包,聚焦网络传输、HTTP头处理、证书密钥存储等典型场景中的ASCII安全编码需求。包内共8个文件,含6个可读性强的Java源码文件(涵盖Encoder/Decoder封装、异常…

作者头像 李华
网站建设 2026/10/18 4:48:42

基于Copula理论的风光场景生成与K-means削减实操

基于Copula理论的风光场景生成与削减:从相关性建模到K-means聚类的全流程实操做电力系统随机优化的人,迟早都会撞上同一个问题:风电场出力和光伏出力的数据摆在一起,单独看都挺正常,但合起来就成了一个折磨人的高维随机…

作者头像 李华
网站建设 2026/10/18 4:44:47

无人驾驶MPC轨迹跟踪:从二自由度模型到QP求解的代码实践

简介:这份资源面向无人驾驶与自动控制方向的学习者和开发者,提供模型预测控制(MPC)的完整程序代码实现,帮助理解车辆路径规划、速度控制与转向操作中的预测优化逻辑。压缩包共16个文件,约38KB,以…

作者头像 李华