news 2026/10/18 3:40:03

YOLOv5+PyQt行人车辆检测计数系统:从COCO训练到桌面部署全解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
YOLOv5+PyQt行人车辆检测计数系统:从COCO训练到桌面部署全解析

做目标检测的人应该都有一个同感:把YOLOv5跑通不难,训练一个能识别行人和车辆的自定义模型也不难,难的是把这两件事和一套像样的桌面端界面打包成一个能给别人演示、能实际点按钮操作的产物。这个项目标题包含的要素很明确——YOLOv5检测系统、PyQt界面、COCO数据集训练、行人车辆检测计数,可以说是一个典型的“算法+界面+业务逻辑”的完整闭环。我当时做这个项目最大的体会是:真正让人卡住的往往不是模型精度,而是“模型产出检测框之后怎么接业务逻辑”“界面怎么不掉帧”“计数结果怎么让人看得明白”这一类工程问题。

这个内容适合三类人看:准备拿目标检测做课程设计或毕业设计的学生、已经把YOLOv5玩熟但想做成交互系统的开发者、以及需要把行人车辆统计能力交付给非技术用户的项目负责人。我会直接从项目选型逻辑开始,逐步拆解整个系统的搭建过程,包括训练细节、界面架构、计数实现和部署阶段的坑,尽量把能直接抄作业的部分都写清楚。

1. 先说清楚项目选型:为什么是YOLOv5、PyQt和COCO这个组合

1.1 这个项目本质上是“检测模型 + 业务计数”的组合

只做检测,YOLOv5装上官方权重就能跑,无非是inference出框画画线。但“行人车辆检测计数”这个需求多了一个关键动作——统计。统计背后意味着系统要在连续的视频帧里搞清楚一件事:画面里出现过的目标到底是同一个还是新的,过了线的行人应该记一次还是记十次。这已经不只是目标检测一个模型的事,还需要跟踪、轨迹关联、跨线判断这些配套逻辑。

所以理解这个项目,不能把它当成“续写YOLOv5 demo”,而应该当成“用检测结果驱动一个统计业务的小型系统”。PyQt界面在这里不是锦上添花,而是业务闭环里必须有的一层:用户需要一个入口来加载视频、看实时画面、设虚拟线、看统计数字,否则模型检测能力再强也没法直接交付给使用者用。

1.2 YOLOv5在2025年的环境下依然是务实选择

YOLOv5不算最新,但它在这个组合里反而很合适。原因有几个。

第一,生态成熟。从训练到导出再到推理,资料密度极高,遇到问题基本都能搜到解决方案。第二,模型体积和算力要求可控。yolov5s权重只有14MB左右,GTX 1060这种老卡也能跑到几十毫秒一帧;yolov5m精度更高,但RTX 2060也能流畅处理。第三,COCO预训练权重可以直接作为迁移学习的起点,省去了从零训练的成本。

要注意的是,YOLOv5的部署场景非常宽。既能用PyTorch原生推理,也能转成ONNX走onnxruntime,还能转成TensorRT在英伟达平台上做加速。就算将来项目要扩展到边缘设备,这套技术栈也不需要推倒重来。

模型体积(权重文件)GPU推理延迟参考(640输入)精度倾向适合场景
yolov5s约14MB约5~10ms中等CPU/低配GPU也能跑,推荐入门部署
yolov5m约40MB约10~20ms中高精度要求一般,算力尚可
yolov5l约90MB约20~35ms高服务器部署,追求准确率
yolov5x约170MB约30~60ms最高离线分析,吞吐量要求低

如果只是行人车辆检测计数,yolov5s和yolov5m是首选。我实际跑下来,yolov5m在白天街景视频上的行人识别准确率比yolov5s明显高一截,代价是帧率下降一半。实时性要求高的场景就老老实实用yolov5s,不是所有地方都需要最高精度。

1.3 PyQt在界面方案里的优势:桌面端集成度最高

有人会问:为什么不用Web前端或者直接用OpenCV的cv2.imshow?cv2.imshow只能做临时调试,按钮、文本框、表格、多页面一个都没有。Web方案要做前后端通信,可以做成一个独立的系统,但是部署起来要起服务、配端口、处理浏览器兼容,对于单机演示和本地交付来说太重了。

PyQt的好处是它的信号槽机制和Python的数据流完美契合。视频帧本质上是numpy数组,检测结果本质上是列表字典,这些都可以直接通过信号传递到界面线程,渲染成QLabel里的图片,或者在TableWidget里一行一行展示。整个过程不需要转化数据格式,也没有HTTP协议层的开销。

唯一需要谨慎对待的是线程模型。PyQt界面必须跑在主线程,但视频解码和模型推理如果也放在主线程,画面必然卡死。所以整体架构一定要把工作线程和UI线程分开,这个后面专门说。

2. 数据与训练:让COCO预训练权重真正为“行人车辆”服务

2.1 先看清COCO能给你什么,别一股脑训练全部80类

COCO数据集本身有80个类别,其中和这个项目强相关的其实只有几个:person、car、truck、bus、motorcycle、bicycle。COCO的类别ID里,person是0,car是2,motorcycle是3,bus是5,truck是7。如果你的业务只关心行人和车辆,最常规的做法是只取这几个类别的数据进行训练,其余类别要么不管,要么直接过滤。

这里有一个容易犯的错误:直接用官方yolov5s.pt跑推理,然后发现它把什么电视、书籍、餐桌都在画面里标出来了,显得很乱。正确的做法是准备一份自己的data.yaml,只定义需要的类别,然后把COCO预训练权重作为--weights输入,最后微调出一个专用模型。

# data.yaml示例:只保留行人车辆相关类别 train: ./datasets/train/images val: ./datasets/val/images nc: 6 names: 0: person 1: car 2: truck 3: bus 4: motorcycle 5: bicycle

2.2 两类数据准备路线:官方数据集裁剪 vs 自建数据集

做法一:使用官方COCO数据集,但只筛选需要的类别。COCO数据集的标注格式是JSON,需要写成YOLO格式的txt文件。YOLO格式的每一行是“类别ID,中心点x坐标,中心点y坐标,宽度,高度”,且所有坐标都归一化到0~1之间。自己写脚本转格式时,核心逻辑是:遍历JSON里的annotations,找出category_id属于目标类别的标注,把bbox的四元组从[x, y, width, height]转成归一化坐标,同时注意图片尺寸信息要正确读取。这个转换过程不建议手工做,写一次脚本,后续维护会很方便。

做法二:自建数据集。用手机或摄像头采集画面,用LabelImg或Labelme标注。自建数据对特定场景的效果提升最明显,比如你需要在校园门口做统计,那就拍校园门口的真实人流车流。标注的时候有几个原则:

  • 每张图的目标尽量多标全,不要漏;
  • 行人和车辆被遮挡比较严重时,能辨认轮廓的部分也要标;
  • 同一类目标在不同尺度下都要有样本,远距离的小目标如果漏标,训练出来的模型对远目标识别会特别差。

我建议两者结合起来做:底子用COCO裁剪出来的通用样本,让模型具备基础的识别能力;再叠加一两百张自建场景图,把“现场感”带进去。自建图片不用多,300张左右就能对特定场景有明显改善,因为迁移学习的底子已经在了。

2.3 训练参数怎么定:不是所有默认值都适合你

YOLOv5默认的训练配置是coco128.yaml,直接拿来跑只适用于官方demo。自定义训练时,有几个参数必须人工调整。

--img控制输入图片尺寸。默认640x640,如果画面里行人小目标多,可以升到800或960,但显存占用和推理耗时都会上升。--batch受显存限制,16G显存跑yolov5m大概能到16到32的batch。--epochs是个核心权衡点:迁移学习场景下100轮足够,300轮能榨出更多精度,但后面的收益曲线非常平缓。

另外一个好习惯是使用--cos-lr余弦退火学习率,并配合--warmup默认设置。相比固定学习率,余弦退火在中后期能让损失收敛更稳,尤其是你的数据集只有几百张时,学习率太大容易过拟合。

训练结束后,重点看val集上的mAP@0.5和mAP@0.5:0.95两个指标。mAP@0.5衡量的是“宽松IoU下的平均精度”,行人车辆检测场景里,如果能达到85%以上基本够用;mAP@0.5:0.95更严格,60%以上就算不错。如果发现精度不够,先排查是不是目标太小、数据集太少,再考虑换更大的模型,别一上来就升级yolov5x。

2.4 转成可用推理格式:PyTorch权重、ONNX还是TensorRT

训练完得到的best.pt可以直接在PyTorch里做推理。不过进入部署阶段,通常建议转成ONNX格式,原因有两个:一是onnxruntime的推理性能在CPU上通常优于PyTorch原生的CPU推理;二是格式更通用,后续要换推理框架不需要重新训练。

python export.py --weights runs/train/exp/weights/best.pt --include onnx --opset 12

导出之后可以用onnxruntime加载:

import onnxruntime as ort session = ort.InferenceSession("best.onnx") input_name = session.get_inputs()[0].name # 输入预处理、输出解析与PyTorch版本类似

如果部署目标有NVIDIA GPU,还可以进一步转TensorRT的engine格式,推理速度会比ONNX常规模式快一倍以上。TensorRT的优化比较吃环境版本,坑也多,属于“精度不行才考虑”的优先级,项目初期不用上来就碰。

3. PyQt界面与推理线程:怎么设计才不卡顿不掉帧

3.1 界面到底要放哪些功能区

我搭的界面布局经过好几轮迭代,最后稳定为四块区域。最上面是顶层工具栏,放“打开视频”“打开摄像头”“开始检测”“暂停”“退出”这类操作按钮。中间主区域是一块大画布,用于实时显示检测画面。画布的下方或右侧放统计面板,实时显示行人数量、车辆数量、当前帧时间戳、总计数。最右侧留一块区域放虚拟线配置和参数调整,比如线的两个端点坐标、检测置信度阈值、IOU阈值。

关于“打开摄像头”这个功能,需要注意区分本机USB摄像头和网络摄像头(RTSP流)。USB摄像头用cv2.VideoCapture(0)就能打开,RTSP流则要传完整地址,而且网络摄像头通常有延迟,解码端要做容错。两种输入源都要在逻辑上和视频文件统一走同一条帧处理管线,界面不关心图像来自哪里,只负责接收帧。

3.2 推理必须放子线程:QThread和信号槽的正确姿势

PyQt里所有UI操作都必须在主线程,否则界面会假死。视频解码和模型推理如果在主线程做,每处理一帧要几百毫秒,界面直接就转圈了。解决方式是把“抓帧+推理+绘制结果”全部放到一个QThread子线程里,处理完一帧后通过信号把结果发回主线程。

class DetectWorker(QThread): frame_ready = pyqtSignal(np.ndarray, list) finished_one = pyqtSignal(int) # 可用来更新计数 def __init__(self): super().__init__() self.running = True self.model = load_model("best.onnx") def run(self): while self.running: ret, frame = capture() if not ret: break detections = self.model.infer(frame) drawn = draw_boxes(frame, detections) self.frame_ready.emit(drawn, detections) self.msleep(30) # 控制帧率

在主线程里连接这个信号,用QLabel.setPixmap更新画面。这样即使推理速度慢,界面也不会卡死,最多是画面刷新频率降低。

3.3 帧处理管线的三段分工

整套处理流程可以拆成三段:解码段、推理段、渲染段。解码用OpenCV的VideoCapture读取视频或摄像头,输出BGR格式的numpy数组。推理段负责预处理(resize到模型输入尺寸、归一化)、模型前向计算、后处理(置信度过滤、NMS去重),最终输出检测框列表。渲染段把检测框、类别标签、置信度画到原始帧上,再转成QImage显示。

一个非常实用的小细节是颜色格式转换。OpenCV读进来的是BGR,而QImage默认按RGB解释,直接转会得到颜色错乱的画面。转换时要么用cv2.cvtColor(frame, cv2.COLOR_BGR2RGB),要么在构造QImage时明确指定格式为Format_RGB888。

def to_qimage(frame: np.ndarray) -> QImage: rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) h, w, ch = rgb.shape bytes_per_line = ch * w return QImage(rgb.data, w, h, bytes_per_line, QImage.Format_RGB888)

这个函数的性能远比cv2.imencode转成jpg再显示的方式高,后者会引入额外的编码解码开销,帧率瞬间掉一半。

3.4 模型加载和推理接口的封装

整个检测模块最好封装成一个类,对外只暴露两个方法:load_model()和infer()。内部细节(是否用ONNX Runtime、预处理、后处理)全部封装在里面。界面层不需要关心用的是PyTorch还是ONNX。这样做的直接好处是切换推理后端时,只需要改这个类的内部实现,界面代码一行不动。

还有一点,模型加载本身比较耗时,首次加载可能要一两秒,这个操作应该放在工作线程初始化阶段完成,不要塞进界面主线程的构造函数里。否则打开程序时会白屏很久。

4. 行人车辆计数的实现:从检测框到可交付的统计数据

4.1 两种计数模式:跨线计数和区域统计

行人车辆检测计数在业务上最常见的两种需求是跨线计数和区域统计,两种我都在这个项目里做过,建议按具体场景选择。

跨线计数针对的是“一条道路上通过了多少行人车辆”的统计。操作方式是在画面中画一条虚拟线,目标从线的一侧运动到另一侧时计数加一。区域统计针对的是“某个区域当前有多少人多少车”的实时密度统计,不需要画线,只要检测框中心点落在指定的多边形区域里就算在内。跨线计数适合出入口流量统计,区域统计适合广场、站台滞留量监控。

4.2 计数前为什么必须做目标跟踪

直接对每一帧独立检测后统计计数,会出现一个严重问题:同一个行人站在画面里不动,如果他的检测框中心点反复横跨线两侧,计数器就会反复累加;而一个车辆正常通过时,可能因为某一帧漏检导致计数少一。所以要做可靠计数,必须引入目标跟踪,给每个检测目标分配一个稳定的ID。

轻量方案是ByteTrack,它以检测框为输入,做帧与帧之间的匹配关联,不依赖于额外的外观特征模型,在CPU上也能跑得很快。传统DeepSORT需要额外训练一个ReID模型,行人车辆场景下不一定值得增加这个复杂度。

tracker = ByteTrack() tracks = tracker.update(detections) for track in tracks: track_id = track.track_id center = track.center # 用track_id和center去做跨线判断

每帧拿到目标跟踪结果后,行人的位置变化会非常平顺,计数逻辑只需要关心每个稳定ID的轨迹即可。

4.3 跨线判断的几何核心:用叉积判断方向

跨线判断的逻辑并不难,核心是计算检测框中心点到虚拟线的位置方向。设线的两个端点为A(x1, y1)和B(x2, y2),目标中心点为P(x, y),用向量叉积:

cross = (B.x - A.x) * (P.y - A.y) - (B.y - A.y) * (P.x - A.x)

如果cross > 0,P在线的左侧;如果cross < 0,P在线的右侧。记录每个目标上一帧的方向,当方向从正变负或从负变正时,说明目标越线了,计数加一。

实际使用中有两个注意点。第一,目标在线上来回抖动时容易误计,解决办法是设置“越线停留判断”:只有连续N帧方向都保持改变后才真正计数,或者判断目标中心点是否完全越过线而不仅仅是在线附近。第二,线不要画在画面边缘,否则目标半路消失再出现会被当成两个对象,影响计数。

4.4 统计面板的实时展示和数据落盘

检测计数结果不能只显示在画面角落的调试文字里,要有一块清晰的统计界面。我通常会在界面里放两个大数字控件,分别累计行人和车辆总数,下面放一个表格显示最近一分钟的时间序列数据,比如每分钟通过人数、每分钟通过车辆数。这比只给一个总数有用得多——运营人员可以看到流量高峰时段。

数据落盘也是一个容易被忽视的需求。项目打包给用户使用后,用户不会满足于“看一眼数字”,他会问“昨天的统计在哪看”。我在项目里把每条计数记录(时间、类型、方向、帧号)实时追加写入CSV文件,同时用pandas做每日汇总。考虑到CSV在高频写入时容易因程序异常中断而损坏,后来改成了按小时切换文件的分片写入方式,并把当天文件用日期命名,异常恢复时还能保留完整的数据文件。

5. 部署阶段才真正暴露的六个性能与稳定性坑

5.1 推理明明不慢,但界面还是掉帧严重

遇到过好几次这种情况:单独跑模型推理,一帧只要30ms,但在PyQt界面上显示出来就只有每秒10帧了。问题往往出在画面渲染链路。QLabel.setPixmap在每次更新时都会拷贝整张图片数据,分辨率越大开销越大。如果有检测框绘制、时间戳叠加等额外操作,开销叠加后帧率就崩了。

优化手段有几个。一是降低显示分辨率,检测推理用640x640输入,但显示时把画面缩到与控件等宽即可。二是用QGraphicsView加QGraphicsScene替代QLabel来实现高性能绘制,尤其在画面有大量动态图形元素时优势明显。三是限制帧率上限,计算上25帧就够实时预览了,没必要让模型全力跑,省下的算力留给后续的业务逻辑。

5.2 CPU机器上YOLOv5能不能跑,能到什么程度

不是所有用户都有NVIDIA GPU。我在一台只配了i5处理器和核显的笔记本上测试过yolov5s的ONNX版本,640输入下,单帧推理大约300到600ms。也就是说实时视频流根本跑不起来,只能做到“1秒处理2到3帧”。

如果目标机器确实是纯CPU,有两个可实操的方向。第一,换更小的模型,比如yolov5n,推理速度能提升一倍以上,精度下降没有想象中那么严重。第二,降低输入分辨率,把img从640降到480或416,CPU推理速度能显著上升,代价是小目标漏检。第三,开启onnxruntime的CPU线程优化,设置intra_op_num_threads为物理核心数,比默认配置快不少。但整体来说,CPU上做实时检测仍然很勉强,这类项目尽量还是准备一张入门级独立显卡。

5.3 长视频运行的稳定性:内存泄漏和崩溃恢复

跑十分钟没事,跑两个小时突然崩溃或画面严重卡顿,这个问题在长时间监控场景里非常致命。最典型的原因是帧对象没有及时释放。Python本身有垃圾回收机制,但如果图像帧在信号传递链路里被多处引用,就可能堆积在内存里迟迟不释放。解决方案是明确控制帧生命周期:帧处理完、信号发出去之后,不再保留任何对该帧的引用。

另一个问题是大批量视频文件处理时的异常恢复。一旦某个视频文件损坏或编码格式不支持,VideoCapture读到中途可能抛出异常。项目的健壮性做法是在外层捕获异常,记录当前处理到的文件名和进度,自动跳到下一个文件,而不是整个程序崩溃退出。

5.4 精度短板和实际效果的边界要提前告诉用户

模型在实际场景里不会像demo视频那么完美。行人背对镜头、夜间光线不好、车辆被树木遮挡、雨天反光、目标密集拥挤——这些情况都可能导致漏检或误检。我在交付项目时会给对方一个明确的产品边界说明:该系统的计数准确率在正常光照、目标间距适中的场景下可以达到90%以上,但极端场景需要额外的针对性数据去优化。

技术上的补救措施包括开启YOLOv5的--augment测试时增强,以及用多帧检测结果做平滑(比如连续3帧里出现2次才算确认目标出现),都能明显降低单帧漏检产生的计数误差。

5.5 置信度阈值和NMS阈值不是越大越好

很多初学者看到检测结果有误报,第一反应是把置信度阈值调到0.9以上。实际上高置信度阈值会过滤掉大量小而真实的目标,行人小目标的置信度本身就偏低,0.9的阈值会让计数结果严重偏低。我建议置信度0.4到0.5之间做均衡,NMS的IoU阈值保持默认0.45即可,这两个参数放在界面参数区让用户可调,而不是写死在代码里。

5.6 视频源切换时的工作流稳定性

工作线程在打开视频时如果遇到格式异常,会卡在read操作上长时间无响应。这个问题在切换摄像头或接入RTSP流时特别常见。一个有效的方案是给视频源的打开和读取操作设置超时机制,RTSP流用OpenCV读取时加上CAP_PROP_OPEN_TIMEOUT_MSEC和CAP_PROP_READ_TIMEOUT_MSEC参数。如果还是异常,就重启捕获线程,而不是尝试同一个线程内恢复。

6. 项目完成后的三个扩展方向,以及我个人踩过的坑总结

第一个扩展方向是多路视频同时处理。界面做成多标签页,每个标签页跑一路视频流,各自独立的检测推理线程和计数面板。这个扩展对项目架构的要求是所有的业务逻辑都要与具体视频流解耦,我当时把这些逻辑都封装在了一个StreamController类里,每个视频源实例化一个,整个扩展过程还算顺利。

第二个方向是数据可视化。把统计结果按小时汇总生成柱状图、热力图,或者在地图上标注各摄像机点位的人流量,让“计数”升级成“流量分析”。这些功能可以用PyQt自带的绘图组件或ECharts嵌入网页组件来实现,属于锦上添花的加分项。

第三个方向是模型升级到YOLOv8或YOLO11。检测头、训练策略都有提升空间,但迁移成本并不是零。我个人的建议是:如果当前系统已经稳定运行,不要为了追新而换版本;除非精度或速度指标确实达不到要求,否则YOLOv5这个方案已经能支撑一整套完整业务了。

最后说一点最实用的经验:开发顺序一定不要是“先训练模型、再做界面、最后加计数”,而应该是“先用最简陋的界面把视频流+推理+显示跑通,再加追踪,再加跨线计数,最后美化界面”。每加一个环节,都要保持程序能跑能退,不要贪多。我见过太多人一上来就想着把界面设计得很精美,结果卡在数据绑定和线程同步上,好久都出不了可运行的成品。先跑通一个灰头土脸的完整流程,再逐步打磨,才是这类系统最靠谱的推进方式。

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

Unity模型替换实战指南:材质绑定、骨骼映射与Prefab安全升级

1. 项目概述&#xff1a;为什么在Unity里“换模型”不是点一下就完事的事在Unity中替换模型&#xff0c;听起来就像Photoshop里换一张图层——拖进去、选中、替换、保存。但实际干过几个项目的人都知道&#xff0c;这事儿远比表面复杂。我带过三届某高校的Unity实训课&#xff…

作者头像 李华
网站建设 2026/10/18 3:37:36

C++相似度计算实战:算法选型、代码实现与工程落地

做后台开发和算法落地的同学&#xff0c;十有八九都遇到过这种需求&#xff1a;给一段文本找出最像的另一段&#xff0c;或者从一个库里筛出重复内容。这个能力背后的根基&#xff0c;就是相似度判断。今天聊聊用C实现相似度判断这件事&#xff0c;包括算法选择、代码实现和工程…

作者头像 李华
网站建设 2026/10/18 3:34:33

内存池从原理到实现:定长池、线程安全与性能优化实战

1. 为什么需要内存池&#xff1a;先搞懂三个绕不开的问题做后台服务或者游戏服务器开发的同仁&#xff0c;应该都有过这样的经历&#xff1a;某天线上接口的TP99突然涨了一截&#xff0c;排查下来发现不是业务逻辑变慢了&#xff0c;而是程序在频繁分配和释放小块内存时卡住了。…

作者头像 李华
网站建设 2026/10/18 3:34:01

Check Point认证体系详解:从CCSA到CCSM的进阶路线

老实说&#xff0c;很多做网络安全的朋友一聊到厂商认证&#xff0c;第一反应就是“背书、刷题、没意思”。但我要唱个反调&#xff1a;如果你是做企业防火墙、网关这类边界防护的&#xff0c;Check Point 的认证体系反而是最值得沉下心研究的一张证书。原因很简单——你工作里…

作者头像 李华
网站建设 2026/10/18 3:26:55

Delphi嵌入式数据库:Absolute Database实战与跨版本迁移

简介&#xff1a;Absolute Database v7.93 的 Delphi 源代码包&#xff0c;面向需要跨版本使用 Delphi 4&#xff5e;11 的数据库开发人员&#xff0c;以及桌面、嵌入式和企业级应用开发者。该版本兼容最新的 Delphi 11 Alexandria&#xff0c;核心解决传统数据库引擎在 IDE 集…

作者头像 李华