☰
YOLOv5车辆行人检测实战:5000张数据集训练与PyQt界面部署
2026/10/1 6:11:40 网站建设 项目流程

简介:本资源面向计算机视觉入门与进阶开发者,提供一套可直接运行的YOLOv5车辆行人检测完整方案,解决交通场景下person与car两类目标的检测需求。包内含yolov5s与yolov5m两种训练好的权重,mAP达90%以上,并附PR曲线、loss曲线等训练过程记录,方便复盘调参效果。资源共约2000个文件,以jpg图像、xml与txt双格式标注、py源码为主,另有pt权重、yaml配置、ui界面文件及少量mp4演示视频,压缩包约640MB,目录按数据集与代码分文件夹存放,结构清晰。配套PyQt界面支持图片、视频与摄像头三种检测模式,可自由切换输入源。同时提供5000余张交通场景行人车辆数据集,标注同时保留txt与xml两种格式,便于直接用于训练或格式转换。目前已有5802人学习下载,适合课程设计、毕业设计及检测项目快速落地参考。

1. 从一份 5000 张的车辆行人数据集说起:YOLOv5 检测 + PyQt 界面到底能落地成什么

手上有一批 5000 张标注好的车辆行人检测数据集,想把它变成一个能双击运行、点按钮就能出检测框的桌面工具,这是很多做毕设、做安防小工具、做园区车辆统计的工程师真实的需求场景。YOLOv5 车辆行人检测这条路线之所以被反复提起,不是因为它最新,而是因为它足够稳:模型结构成熟、训练脚本开箱即用、导出 ONNX 或 TorchScript 后接 PyQt 界面几乎没有额外依赖负担。整套方案要解决的核心问题就三件事——用 5000 张车辆行人检测数据集把 YOLOv5 练到一个可用的 mAP,把练好的权重封装成推理接口,再用 PyQt 做一个能选图、能选视频、能实时显示检测结果的界面。适合谁?适合已经会一点 Python、装过 conda 环境、但没完整跑通过「数据集 → 训练 → 推理 → 界面」全链路的人。下面按我实际做过的顺序,把每一步的参数、坑和验证方法讲清楚。

2. 数据集与标签格式:5000 张车辆行人数据怎么整理才不返工

2.1 先确认标注格式,再决定要不要转换

拿到一份车辆行人检测数据集,第一件事不是急着写 data.yaml,而是打开标注文件看格式。常见的有三种:Pascal VOC 的 XML、COCO 的 JSON、YOLO 的 txt。YOLOv5 只认最后一种,每行是class_id x_center y_center width height,且全部归一化到 0~1。如果数据集给的是 VOC XML,就得转;如果是 COCO JSON,也得转。我一般会先跑一段统计脚本,把类别分布和框的宽高分布打出来,因为车辆行人检测里「人」的框通常又高又窄,「车」的框又宽又扁,这个分布直接决定后面 anchor 和输入尺寸怎么选。

import os, glob from collections import Counter # 统计 YOLO 格式标签的类别分布与框宽高 label_dir = "datasets/vehicle_person/labels/train" cls_counter = Counter() w_list, h_list = [], [] for txt in glob.glob(os.path.join(label_dir, "*.txt")): with open(txt) as f: for line in f: parts = line.strip().split() if len(parts) != 5: continue # 跳过空行或异常行 c, x, y, w, h = int(parts[0]), *map(float, parts[1:]) cls_counter[c] += 1 w_list.append(w) h_list.append(h) print("类别分布:", cls_counter) print("平均框宽:", sum(w_list)/len(w_list)) print("平均框高:", sum(h_list)/len(h_list))

这段脚本的作用是给你一个量化依据:如果某一类样本数不到总数的 5%,训练时就要考虑过采样或者调类别权重;如果平均框宽高都小于 0.05,说明目标偏小,输入尺寸就不能压到 416,得用 640 甚至更大。参数上,label_dir指向你的标签目录,类别 id 从 0 开始,车辆行人两类一般是 0=person、1=vehicle,具体以你数据集为准,不要照抄。

2.2 目录结构与 data.yaml 的写法

YOLOv5 对目录结构有约定,我一般整理成下面这样,训练、验证、测试三份互不重叠:

datasets/vehicle_person/ ├── images/ │ ├── train/ # 约 3500 张 │ ├── val/ # 约 1000 张 │ └── test/ # 约 500 张 ├── labels/ │ ├── train/ │ ├── val/ │ └── test/ └── data.yaml

5000 张按 7:2:1 切是比较稳的比例,验证集少于 500 张时 mAP 波动会很大,看不出真实水平。data.yaml 只需要四行核心内容:

path: ../datasets/vehicle_person train: images/train val: images/val test: images/test nc: 2 names: ['person', 'vehicle']

nc是类别数,names的顺序必须和标签里的 class_id 严格对应,顺序错了模型会把车认成人,而且 loss 还能降,这种玄学问题排查起来最费时间。切分脚本我一般用随机种子固定,保证每次复现一致:

import random, shutil, os random.seed(42) # 固定种子,保证切分可复现 imgs = sorted(os.listdir("raw/images")) random.shuffle(imgs) n = len(imgs) splits = {"train": imgs[:int(n*0.7)], "val": imgs[int(n*0.7):int(n*0.9)], "test": imgs[int(n*0.9):]} for split, files in splits.items(): for f in files: shutil.copy(f"raw/images/{f}", f"datasets/vehicle_person/images/{split}/{f}") shutil.copy(f"raw/labels/{f[:-4]}.txt", f"datasets/vehicle_person/labels/{split}/{f[:-4]}.txt")

提示:切分前先检查有没有同一段视频抽帧出来的近似重复图,重复图跨 train/val 会导致验证指标虚高,实际部署时掉点。

2.3 数据增强的取舍:车辆行人场景别乱开

YOLOv5 默认开了 mosaic、HSV、翻转、缩放。车辆行人检测里,水平翻转基本安全,但垂直翻转要慎用——人倒过来在真实监控里几乎不出现,开了反而引入噪声。mosaic 对小目标友好,但如果你的数据集里车和人普遍偏大,mosaic 把四张图拼一起会让目标变小,训练前期 loss 震荡明显。我的习惯是前 50 个 epoch 开 mosaic,后面关掉做微调,用--close-mosaic 50控制。HSV 增强里hsv_h别超过 0.015,色相变化太大会让车身颜色失真,对以颜色区分车型的任务不利。

3. YOLOv5 训练:从环境配置到练好的权重

3.1 环境配置与依赖版本

YOLOv5 对 PyTorch 和 CUDA 版本比较敏感,我踩过的坑是 torch 版本和 torchvision 不匹配导致torch.load报错。稳妥做法是用 conda 建独立环境,先装匹配显卡驱动的 CUDA 版 torch,再装 requirements。

conda create -n yolov5_veh python=3.9 -y conda activate yolov5_veh # 按你的 CUDA 版本选对应命令,这里以 cu118 为例 pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 git clone https://github.com/ultralytics/yolov5 cd yolov5 pip install -r requirements.txt

装完先跑python -c "import torch; print(torch.cuda.is_available())",返回 True 才算环境通了。这一步不通,后面训练会默默退回 CPU,速度差几十倍,很多人以为是模型慢,其实是环境没配好。

3.2 训练命令与关键超参数

车辆行人检测我一般从 yolov5s 起步,5000 张数据量用 s 或 m 足够,n 容易欠拟合,l/x 容易过拟合且推理慢。训练命令:

python train.py \ --data datasets/vehicle_person/data.yaml \ --weights yolov5s.pt \ --img 640 \ --batch 16 \ --epochs 150 \ --hyp data/hyps/hyp.scratch-low.yaml \ --close-mosaic 50 \ --device 0 \ --project runs/train --name veh_person_s

参数逐个说:--img 640是输入尺寸,前面统计过框偏小就别降;--batch 16按显存调,8G 显存跑 640 大概能到 16,爆显存就减半并配合--accumulate;--epochs 150对 5000 张是够的,配合--patience 30早停;--hyp用 low 增强配置,车辆行人场景不需要太激进的增强;--close-mosaic 50表示最后 100 个 epoch 关 mosaic,让模型在真实分布上收敛。--weights yolov5s.pt是官方预训练权重,从零训在 5000 张上很难收敛好。

3.3 训练过程看什么指标

训练日志里重点盯三个:box_loss、obj_loss、mAP@0.5。box_loss 持续下降说明框回归在学;obj_loss 不降反升通常是标签有问题,比如有框越界或宽高为 0;mAP@0.5 在验证集上到 0.85 以上对车辆行人这种两类任务算可用,0.9 以上算好。如果训练集 mAP 高但验证集低,就是过拟合,减 epoch、加增强、或者换更小的模型。我一般会在runs/train/veh_person_s/下看results.png和confusion_matrix.png,混淆矩阵能直接告诉你车有没有被认成人。

# 用验证集跑一次评估,拿到每类 AP import subprocess subprocess.run([ "python", "val.py", "--data", "datasets/vehicle_person/data.yaml", "--weights", "runs/train/veh_person_s/weights/best.pt", "--img", "640", "--batch", "16", "--task", "val" ])

val.py输出的metrics/mAP_0.5和metrics/mAP_0.5:0.95是判断权重好坏的硬指标,best.pt是按验证集 mAP 存的,last.pt是最后一轮,部署一定用 best.pt。

3.4 练好的权重怎么验证再交付

拿到 best.pt 别急着接界面,先用测试集跑一批图,肉眼过一遍。我一般抽 50 张覆盖白天、夜间、遮挡、密集场景,看漏检和误检集中在哪。如果夜间漏检多,说明数据集夜间样本少,要么补数据,要么在推理时降 conf 阈值。验证脚本:

import torch model = torch.hub.load('ultralytics/yolov5', 'custom', path='runs/train/veh_person_s/weights/best.pt') model.conf = 0.25 # 置信度阈值 model.iou = 0.45 # NMS 的 IoU 阈值 results = model('datasets/vehicle_person/images/test', size=640) results.save('runs/detect/test_out')

conf调低召回高但误检多,调高反之,车辆行人场景我一般从 0.25 起调;iou控制 NMS 合并程度,密集人群里调低到 0.4 能减少框被吞。这一步的输出图就是你和界面之间的验收标准,图不过关,界面做得再漂亮也没用。

4. PyQt 界面:把练好的模型包成能点的工具

4.1 界面功能拆解与线程模型

PyQt 做检测界面,核心矛盾是「推理耗时」和「界面不能卡」。主线程只负责画界面和收信号,推理必须丢到 QThread 里,否则点一下按钮整个窗口就假死。功能上我一般做四块:选图片、选视频、开始/停止、结果显示区。图片走单帧推理,视频走逐帧推理,两者共用同一个推理函数。下面是最小可用的推理线程:

from PyQt5.QtCore import QThread, pyqtSignal import cv2, torch class DetectThread(QThread): frame_signal = pyqtSignal(object) # 把带框的帧发回主线程 def __init__(self, model, source, conf=0.25, iou=0.45): super().__init__() self.model = model self.source = source self.conf = conf self.iou = iou self.running = True def run(self): cap = cv2.VideoCapture(self.source) while self.running and cap.isOpened(): ret, frame = cap.read() if not ret: break # YOLOv5 接受 BGR numpy,内部自己转 results = self.model(frame, size=640) results.conf, results.iou = self.conf, self.iou annotated = results.render()[0] # 画框后的帧 self.frame_signal.emit(annotated) cap.release() def stop(self): self.running = False self.wait()

frame_signal用object类型传 numpy 数组,主线程收到后转 QImage 显示。running标志位是停止按钮的关键,直接 kill 线程会崩。results.render()返回的是画好框的帧,省得自己写画框逻辑。

4.2 主窗口与信号槽连接

主窗口负责布局和把线程信号接到显示控件上。用 QLabel 显示帧,QPushButton 触发,QSlider 调 conf 阈值。

from PyQt5.QtWidgets import QApplication, QMainWindow, QLabel, QPushButton, QVBoxLayout, QWidget, QFileDialog from PyQt5.QtGui import QImage, QPixmap import sys, torch class MainWindow(QMainWindow): def __init__(self): super().__init__() self.model = torch.hub.load('ultralytics/yolov5', 'custom', path='runs/train/veh_person_s/weights/best.pt') self.model.eval() self.label = QLabel("等待选择文件") self.btn_img = QPushButton("选择图片") self.btn_vid = QPushButton("选择视频") self.btn_stop = QPushButton("停止") layout = QVBoxLayout() for w in (self.label, self.btn_img, self.btn_vid, self.btn_stop): layout.addWidget(w) container = QWidget(); container.setLayout(layout) self.setCentralWidget(container) self.thread = None self.btn_img.clicked.connect(lambda: self.start("image")) self.btn_vid.clicked.connect(lambda: self.start("video")) self.btn_stop.clicked.connect(self.stop) def start(self, mode): if mode == "image": path, _ = QFileDialog.getOpenFileName(self, "选图片", "", "Images (*.jpg *.png)") else: path, _ = QFileDialog.getOpenFileName(self, "选视频", "", "Videos (*.mp4 *.avi)") if not path: return self.thread = DetectThread(self.model, path) self.thread.frame_signal.connect(self.show_frame) self.thread.start() def show_frame(self, frame): h, w, c = frame.shape img = QImage(frame.data, w, h, w*c, QImage.Format_BGR888) self.label.setPixmap(QPixmap.fromImage(img).scaled(self.label.size())) def stop(self): if self.thread: self.thread.stop() if __name__ == "__main__": app = QApplication(sys.argv) win = MainWindow(); win.show() sys.exit(app.exec_())

QImage.Format_BGR888对应 OpenCV 的 BGR 顺序,用错格式颜色会反。scaled保持显示自适应窗口。信号槽连接必须在thread.start()之前,否则第一帧可能丢。

4.3 打包成 exe 与依赖处理

界面跑通后要交付,用 PyInstaller 打包。YOLOv5 的坑在于torch.hub.load会去拉仓库,打包后没网就崩。解决办法是提前把模型加载改成直接加载本地权重,不走 hub:

# 打包前把模型加载改成显式路径,避免运行时联网 import torch ckpt = torch.load('weights/best.pt', map_location='cpu') model = ckpt['model'].float().eval()

打包命令pyinstaller -F -w main.py --add-data "weights;weights",-w去掉控制台,--add-data把权重打进去。打包后体积会到 1G 以上,这是 torch 的代价,接受不了就换 ONNX Runtime 推理,能压到几百兆,但精度要重新对齐。

5. 避坑与排查:这套方案最容易翻车的五个地方

5.1 验证集 mAP 很高,实际用起来全是漏检

现象:训练日志里 mAP@0.5 到 0.92,接上界面跑真实视频,人和车漏一大片。原因:验证集和训练集同分布,但真实场景的光照、角度、分辨率差异大,模型没泛化过去。解决:从真实场景抽 100 张重新标一小份,混进训练集做微调,或者至少用这批图做一次独立评估,别只看验证集数字。

5.2 界面点开始后卡死,任务管理器显示 CPU 占满

现象:点按钮后窗口无响应,风扇狂转。原因:推理写在了主线程,或者 QThread 里又调了阻塞的 GUI 操作。解决:确认推理在 QThread.run 里,主线程只做显示;另外torch.hub.load首次加载会联网,也会卡,提前加载好模型再启动线程。

5.3 检测框颜色和类别对不上

现象:车被画成人的颜色,或者标签文字错位。原因:names顺序和训练时不一致,或者 render 用的颜色表按索引取,索引和类别没对齐。解决:核对 data.yaml 的 names 顺序,界面里显示类别时用model.names[int(cls)]取,别硬编码。

5.4 视频推理越来越慢,内存持续上涨

现象:跑几分钟后帧率掉一半,内存占用只增不减。原因:每帧都新建 tensor 没释放,或者 results 对象累积。解决:推理循环里用with torch.no_grad():,每帧处理完手动del results,视频用cap.set(cv2.CAP_PROP_FRAME_WIDTH)降分辨率也能显著提速。

5.5 打包后报找不到 best.pt

现象:源码跑得好好的,打包成 exe 双击就报文件不存在。原因:PyInstaller 打包后路径变了,相对路径失效。解决:用sys._MEIPASS拼资源路径,或者把权重放 exe 同级目录用绝对路径读,--add-data的路径分隔符在 Windows 是分号,Linux 是冒号,别写错。

6. 进阶:把 conf 阈值做成自适应,让夜间和白天都不翻车

固定 conf 阈值是这套方案最大的短板。白天光照好,0.25 够用;夜间噪点多,同一个阈值会冒出一堆误检。我后来改成按帧亮度动态调阈值:先算帧的平均亮度,低于某个值就把 conf 提到 0.4,高于则降回 0.25。这个技巧不复杂,但实测能把夜间误检压掉三成左右。

import cv2, numpy as np def adaptive_conf(frame, base=0.25): gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) brightness = np.mean(gray) # 0~255 if brightness < 60: # 夜间/暗光 return min(base + 0.15, 0.6) elif brightness > 180: # 过曝 return base + 0.05 return base

亮度阈值 60 和 180 是我在几个监控场景里试出来的经验值,你的数据如果偏室内,得重新标定。另一个进阶方向是把 PyQt 界面里的 conf 和 iou 做成滑块实时调,用户自己找平衡点,比写死更实用。滑块拖动时只改model.conf,不用重载模型,响应很快。

验证这套自适应有没有用,别只看感觉,拿同一段夜间视频分别跑固定阈值和自适应,统计误检框数量,数字会告诉你答案。我自己踩过的最大教训是:一开始迷信验证集 mAP,觉得 0.9 以上就万事大吉,结果真实场景一跑全是问题。后来养成习惯,任何权重交付前必须过一遍真实场景抽帧,指标只是参考,眼睛看到的才算数。希望帮到你。

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

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

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

立即咨询