车辆行人检测这类需求,在项目里出现的频率真的很高。要交付一套完整的基于深度学习YOLOv8的车辆行人检测系统,通常包含四件事:用YOLOv8做检测核心,用YOLO格式数据集完成训练,用一个可交互的UI界面把模型包装成能直接操作演示的工具,再把源码和训练好的模型一起闭环交付。这篇文章就是这个项目的完整复盘,从算法选型、数据准备、训练参数、界面设计到踩坑记录都会覆盖到,适合正在准备课程设计、毕业设计,或者想用目标检测快速做一个能落地演示应用的开发者参考。
1. 项目概述与整体设计思路
1.1 标题解剖:这套系统的五块拼图
“基于深度学习YOLOv8的车辆行人检测系统(YOLOv8+YOLO数据集+UI界面+Python项目源码+模型)”这个标题看起来很长,其实每一块都对应着系统中不可缺失的一环。
YOLOv8是检测算法本身,决定了系统“用什么技术识别目标”。YOLO数据集不是指某一个固定的开放数据集,而是指符合YOLO标注规范的数据集合。YOLO格式的核心约定是:每一张图片对应一个同名txt文件,txt中的每一行代表一个目标,内容为类别id和归一化后的中心点坐标、宽高。UI界面则解决“东西做出来了,怎么让人用得起来”的问题,如果只丢一个命令行脚本,验收方或者非技术使用者很难直观感受到检测效果。Python项目源码和模型则是交付物,模型权重直接决定检测能力,源码决定了别人能不能看懂、能不能二次扩展。
因此这套系统的完整链路是:准备符合YOLO格式的数据 → 用YOLOv8训练出自己的权重 → 封装成Python推理接口 → 通过UI界面可视化呈现 → 最终交付源码和模型。
1.2 为什么是YOLOv8,而不是Faster R-CNN或SSD
目标检测领域有几条路线,我在设计这套系统时并不是随手选YOLOv8,而是对比了实际应用场景后才确定的。
Faster R-CNN这类两阶段检测器精度确实高,但它先产生候选区域,再逐区域分类回归,推理速度很难做到视频级实时。如果系统要接摄像头实时画面,帧率会非常难看。SSD虽然是单阶段,速度上来了,但对小目标的检测能力一直不太理想,密集场景下的漏检也比较明显。车辆和行人正好经常出现在监控画面里,目标大小差异大、遮挡多,对密集场景的鲁棒性要求很高。
YOLOv8在这个需求下是比较均衡的选择。它沿用并改进了CSPDarknet骨干网络,引入了C2f模块增强特征提取,检测头改成了解耦结构,分类和回归分开输出;回归分支还用到了DFL机制,把边界框回归问题转化为概率分布问题,对小目标定位更精准。锚框部分也做了调整,改为anchor-free设计,少了一堆手工锚框参数的调优负担。更关键的是,YOLOv8本身自带了完整的训练、验证、导出命令行支持,从训练到导出ONNX再到部署,工具链非常顺。
简单说,对车辆行人检测这个具体任务,YOLOv8在“检测精度、推理速度、开发效率”三个维度上达成了比较好的平衡,这也是它成为这套系统核心算法的原因。
1.3 适用场景与目标读者
这套系统做出来之后,最典型的用途有三类。
第一类是课程设计和毕业设计。车辆行人检测是计算机视觉方向很常见也很合适的选题,它足够体现深度学习、目标检测、数据集处理、界面开发这些知识点,又不至于难到一个人做不出来。第二类是交通场景原型验证,比如园区车辆进出统计、路口行人提醒等,先跑通检测模型,再挂业务逻辑。第三类是学习型项目,很多开发者想看看完整的检测系统工程是怎么组织的,从模型训练到UI展示中间到底经历了哪些步骤。
当然,如果你打算部署到嵌入式设备,还需要额外做模型轻量化、TensorRT加速或剪枝量化这些工作,这篇文章也会在后面的扩展部分给出思路。
2. 环境准备与工程初始化
2.1 开发环境版本搭配建议
环境版本这一项,看起来简单,但很多人一开始就卡在版本不匹配上。我整理一套相对稳妥的组合,直接照着装就行。
- Python版本建议3.8到3.11之间,尽量别用3.7以下的版本,YOLOv8很多依赖已经放弃老版本Python。
- PyTorch推荐2.x版本,如果你有NVIDIA显卡,安装对应CUDA版本的PyTorch,比如CUDA 11.8或12.1对应的构建版本。
- 视觉库用OpenCV,负责图像读取和帧处理。
- UI框架我用的是PySide6,它是Qt官方的Python绑定,比PyQt5的授权和安装都更省心,API也更现代。
硬件方面,训练如果用YOLOv8s模型,显存6GB以上会舒服很多;如果用YOLOv8m,建议8GB以上。没有独立显卡也能跑,CPU训练只是慢得多,推理做演示是可以接受。关于GPU环境验证,装完PyTorch后建议先跑一句检测代码确认CUDA可用,不要等到训练时才报错再回头排查。
2.2 依赖安装与常见坑
项目的依赖文件一般长这样:
ultralytics>=8.0 opencv-python>=4.5 PySide6>=6.4 numpy>=1.22 pillow tqdm安装用一条命令搞定:
pip install -r requirements.txt国内网络环境下pip下载慢的话,换阿里云或清华的镜像源会快很多,这里不展开。
踩过的坑有两个值得提前说。第一个是opencv-python和某些模块版本冲突,表现为读图时报错无法加载,解决思路是重装opencv-python-headless版本或者指定一个稳定的OpenCV版本。第二个是PySide6安装没问题,但运行时提示找不到Qt平台插件,通常是因为环境变量被干扰,或者安装了两个不同版本的Qt库。解决办法是确认项目只用一套Qt绑定,不要同时装PyQt5和PySide6,两个库的插件目录会打架。
python -c "import torch; print(torch.cuda.is_available())"输出True说明GPU版本没问题,输出False就得回头检查PyTorch是否装了CUDA对应版本,或者显卡驱动是否正常。
2.3 数据集准备与格式转换
这套项目里数据准备是决定模型能不能训练好的基础。YOLO格式数据集的目录结构,推荐按下面这样组织:
datasets/ ├── vehicle/ │ ├── images/ │ │ ├── train/ │ │ └── val/ │ └── labels/ │ ├── train/ │ └── val/images里面放图片,labels里面放与图片同名的txt标注文件。每个txt的每一行格式如下:
# class_id center_x center_y width height,前两个是中心点坐标,后两个是宽高,全部归一化到0~1之间 0 0.512 0.418 0.234 0.125 1 0.318 0.652 0.088 0.210如果拿到的数据是COCO格式的json标注,就需要写一个小脚本做格式转换。大致逻辑是:读取json里每张图片的尺寸和标注框,将bbox的xywh从像素坐标除以图片宽高归一化,并写入同名txt。转换时注意COCO的bbox是[x, y, width, height]且x、y是左上角坐标,YOLO需要的是中心点坐标,计算时要转换。
如果完全使用自采数据,那就需要标注工具。labelImg这类工具支持直接输出YOLO格式,框完保存即可,标注速度虽然慢一点,但胜在流程简单。我个人建议数据总量至少准备2000到5000张图,车辆行人目标集中的场景可以适当少一点,类别分布不均衡时还需要考虑增加少样本类别的数据。
数据划分一般用8比1比1或者9比1,训练集、验证集、测试集分开目录。注意测试集不是必须的,但验证集一定不能和训练集有重叠,否则训练出来的mAP会虚高。
3. YOLOv8模型训练全流程
3.1 数据配置文件编写
开始训练前,需要准备一个data.yaml文件,把数据路径和类别信息告诉YOLOv8:
# data.yaml path: D:/datasets/vehicle train: images/train val: images/val nc: 2 names: ["person", "vehicle"]path指向数据集根目录,train和val填相对path的图片目录,框架会自动去对应目录下找同名labels。nc是类别数,names是类别名称列表。这里特别提醒一下,names的顺序必须和标注txt里的class_id保持一致,比如标注文件中0是person,1是vehicle,那么names列表第一个就要写person,第二个写vehicle,一旦错位,训练不会报错,但检测出来的人会被标成车,车被标成人。
3.2 训练命令与关键参数解释
训练命令简洁,但每个参数背后都有讲究:
yolo detect train \ data=vehicle.yaml \ model=yolov8s.pt \ epochs=150 \ imgsz=640 \ batch=16 \ device=0 \ patience=20 \ lr0=0.01 \ optimizer=SGDmodel=yolov8s.pt表示从一个在公开数据集上预训练过的权重开始微调。不要从零训练,预训练权重相当于已经学会了一些通用特征,微调之后收敛快、效果也好。如果数据量不大,从预训练开始几乎是必须的选择。
model前面的s表示small,还有n、m、l、x几个档位。n最轻量适合CPU或边缘设备,m和l精度更高但更吃显存。车辆行人检测用s起步就够,数据质量高的话m还能再涨一点。
epochs设150,实际训练中如果连续patience=20个epoch验证集指标没有提升,训练会自动早停,不用我们盯着。imgsz选640是速度精度的平衡点,显存紧张可以降到512甚至480,对应的小目标检测能力会略降。
batch根据显存来定,原则是尽量大,但别爆显存。如果batch调到16报CUDA Out of Memory,优先降到8,或者同时开AMP混合精度训练,节省显存明显。loss曲线如果抖动得很厉害,lr0可以降低到0.005再试。优化器方面SGD加动量是比较经典的训练配置,稳定、泛化好;AdamW收敛快但某些数据集上最终表现略逊,我实测下来车辆行人这类任务SGD更省心。
3.3 训练日志与指标怎么看
训练过程中控制台会不断输出Box_loss、Cls_loss、DFL_loss和验证集指标。随着epoch增加,这三个loss理论上应该逐步下降并趋于平缓。如果某个loss先降后升,大概率是过拟合了,可以加大数据增强、降低epoch或增加正则。
最需要盯的两个指标是mAP50和mAP50-95。mAP50表示IoU阈值取0.5时的平均精度,mAP50-95是IoU阈值从0.5到0.95取平均,后者对定位精度要求更高。车辆行人检测这种任务,合理的数据下YOLOv8s通常mAP50能做到80以上,mAP50-95在60左右已经很能说明模型学到了特征。两个指标如果都很低,先别急着调参,回去检查数据标注是不是有错框、漏框或者类别错标。
训练结束后,best.pt和last.pt会保存在runs/detect/train目录下。best.pt是验证集上表现最好的权重,部署和加载模型默认都用它。
3.4 模型测试与导出
训练完先用验证集测一下直观效果:
yolo detect predict model=best.pt source=D:/datasets/vehicle/images/val device=0它会在每个检测结果图上画出边界框、类别和置信度,直接肉眼确认有没有明显漏检或误检。导出ONNX用:
yolo export model=best.pt format=onnx dynamic=True导出ONNX主要是为了部署到非Python环境,或者给后续TensorRT加速做准备。dynamic=True让输入尺寸可变,部署灵活一些。这一步只要模型文件正常,基本不会出错。
4. UI界面设计与实现
4.1 功能清单与交互流程
UI界面不只是一个面子工程,它决定了这套检测系统能不能被别人顺畅使用。我设计的界面包含三块核心功能:图片检测、视频文件检测、摄像头实时检测。
- 图片检测:选择一张本地图片,显示原始图和标注检测框后的结果图,可以保存。
- 视频检测:选择mp4或avi文件,逐帧推理并实时显示,支持暂停和关闭。
- 摄像头检测:调用电脑摄像头或外接USB摄像头,实时显示画面和检测框,同时显示FPS帧率。
界面上还放了两个滑块,分别控制置信度阈值和IoU阈值,边调边看效果。置信度阈值默认0.45,调低会放过更多低分框但也会增加误检;IoU阈值默认0.5,影响重复框的抑制力度。这两个控件非常实用,演示时观众喜欢看这个交互过程。
4.2 关键技术点:图像格式转换与结果显示
PySide6的QLabel只能显示QImage或QPixmap,而OpenCV读取的帧是BGR顺序的NumPy数组,两者必须转换。转换函数封装如下:
def cv2_to_qimage(frame): 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).copy()注意最后一定要加.copy(),因为原rgb是临时数组,不拷贝的话QImage可能显示黑屏或者花屏。显示时通过QPixmap.fromImage设置到QLabel,再用scaled控制缩放比例。
模型推理的调用是核心:
results = self.model.predict(frame, conf=self.conf_threshold, iou=self.iou_threshold, device="0") annotated = results[0].plot()conf和iou从UI滑块读取,这样用户调整阈值时推理效果实时变化。plot方法会直接在原图上画出所有检测结果,省去手动绘制边界框的代码量。
4.3 用QThread避免界面卡死
这是UI开发里最容易踩的坑。如果直接把循环推理放在主线程里执行,摄像头画面每来一帧推理一次,界面会不停等待,拖动窗口、点击按钮都会卡顿,严重时看起来像程序崩溃。
正确做法是把检测任务放到独立线程,通过信号把结果帧传回主界面刷新。我用的是PySide6的QThread,伪代码如下:
class DetectThread(QThread): frame_ready = Signal(object) def __init__(self, model, source=0): super().__init__() self.model = model self.source = source self.running = True def run(self): cap = cv2.VideoCapture(self.source) while self.running: ret, frame = cap.read() if not ret: break results = self.model.predict(frame, conf=0.45, iou=0.5, device="0") annotated = results[0].plot() self.frame_ready.emit(annotated) cap.release()主界面里创建一个DetectThread实例,把frame_ready信号连接到更新QLabel的槽函数。摄像头开启后,主线程只负责接收信号刷新画面,推理在子线程完成,界面保持流畅。实际测下来,GPU推理时摄像头识别FPS能稳定在40到60,CPU上会降到10以下,还是建议用GPU跑。
4.4 阈值滑块、FPS显示与交互体验
滑块和检测参数的绑定逻辑很简单,滑块值改变后触发回调,更新DetectThread里的conf或iou属性。因为推理循环每帧都会读取这些属性,所以效果是实时生效的,不需要重启线程。
FPS计算可以用滑动窗口平均法:每处理一帧记录时间差,取最近30帧的平均帧率。工程上不建议用单帧瞬时帧率,数值跳动太剧烈,用户看起来反而觉得卡。界面上用一个QLabel专门显示实时FPS,演示效果直观。
另外一个小细节是保存结果时,不要直接保存显示用的QImage,最好把原始标注帧的BGR数组保存为jpg或png,这样颜色更准确。保存路径最好用时间戳命名,避免重名覆盖之前的结果。
5. 实测效果与问题排查实录
5.1 常见问题速查表
做这个项目过程中,以及帮其他人看代码时,最常遇到的问题集中在下面表格里。
| 现象 | 可能原因 | 解决思路 |
|---|---|---|
| 训练时报错找不到标签 | images目录改名后labels目录没同步,或者txt文件名与图片名不一致 | 检查图片和txt是否同名,注意jpg和jpeg后缀差异 |
| CUDA out of memory | batch太大或显存不足 | 调小batch到8或4,开启AMP混合精度,降低imgsz |
| 模型检测不到目标 | 置信度阈值设置过高,或者类别名称与训练不一致 | 调低conf阈值到0.25以下测试,检查names顺序 |
| 摄像头打开失败 | 摄像头索引号不对,或者被其他程序占用 | 换用cv2.VideoCapture(0)和cv2.VideoCapture(1)逐个测试 |
| 中文路径读取失败 | OpenCV和Qt对中文路径处理不一致 | 项目路径和图片路径尽量全英文 |
| 界面卡顿 | 推理在主线程执行 | 把推理封装到QThread子线程,避免阻塞UI |
| 视频检测速度慢 | 每帧都送入大尺寸模型推理 | 降低imgsz,或者隔帧检测后保留上一帧结果 |
| 数据训练时loss不下降 | 学习率太大或数据标签错误 | 检查labels文件内容,调低lr0,确认数据干净 |
像标签文件名不一致这种问题,往往肉眼很难发现,我习惯写个对照脚本,把images目录和labels目录的文件名列出来做差集,一眼就能看出哪个目录缺少对应标注。
5.2 推理性能优化思路
训练好的YOLOv8模型在性能调优时,有几个方向比较有效。
输入尺寸直接影响推理时间。imgsz从640降到512,推理速度能提升不少,代价是远处小目标更容易漏检。对车辆行人监控这种近中景为主的场景,512完全够用。
模型导出TensorRT后推理速度会明显提升。TensorRT需要NVIDIA显卡支持,导出的流程是先转ONNX,再用TensorRT的trtexec工具生成engine文件。生成engine时需要指定fp16精度,显存占用更低且推理精度损失很小。我实测在同样的显卡上,TensorRT fp16大约能带来1.5到2倍的性能提升。
如果不需要每一帧都检测,视频分析场景可以采取抽帧策略,比如每3帧检测一次,中间两帧直接沿用上一帧检测结果。这个技巧对视频监控类应用特别有效,可以把FPS指标“撑”得很高。
5.3 后续怎么扩展这套系统
这套系统的骨架搭好之后,往上加功能并不难。
加目标跟踪是很多人首选的方向。在检测基础上接入ByteTrack或DeepSORT,每个目标就能获得稳定的ID,进一步可以做车辆计数、行人轨迹绘制。再往上做车流量统计、指定区域闯入提醒,这套检测系统就能从“能识别”变成“能统计、能告警”。
另一个扩展方向是多任务检测。当前是两个类别,如果要加入红绿灯、交通标志等目标,只需要重新准备数据集,把data.yaml里的nc和names改掉,重新训练即可。YOLOv8对类别数量的扩展非常友好,不需要改网络结构。
更加硬核一点的玩法是单目测距,利用标定好的相机内参和车辆检测框底边位置估算目标距离。这个方向有一定数学门槛,但做出来后项目复杂度会高一个档次,写在简历上是很加分的。
我个人在实际操作中最大的感受是,这类项目最花时间的环节不是训练模型,而是数据清洗和UI联调。数据决定了模型的上限,UI决定了别人对你的成果的第一印象。如果你正在做类似项目,把这两块做扎实,比反复调参得到的收益大得多。
最后再分享一个实用小技巧:训练之前先把验证集上的一些样例图片单独抽出来做一次推理,把标注框和检测框重叠画在同一张图上,对比一下框的贴合度差异。这个方法能快速判断模型定位能力是否到位,比盯着mAP数字更直观。项目跑通了之后,别忘了把训练命令、数据集划分脚本、UI运行方式写进README,交付的时候别人拿起来就能用。