ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

基于YOLOv5的车辆潮汐监测系统:从训练到部署全流程

基于YOLOv5的车辆潮汐监测系统:从训练到部署全流程 简介这份毕业设计文档面向计算机视觉与智能交通方向的本科生及研究生围绕基于YOLOv5的车辆潮汐监测系统展开完整的设计与实现论述可用于毕业设计选题参考、深度学习目标检测项目复现及交通监控类课题学习。资源包内含1个docx文件约1.13MB即论文正文全文涵盖绪论、国内外研究现状、相关理论与技术、系统设计与架构、结论与展望及参考文献等完整章节。论文系统梳理了卷积神经网络、线程池、YOLOv5目标检测算法、MongoDB、PyTorch、cuDNN、Transformer与Flask等关键技术并详细阐述前后端分离架构、登录权限分配、数据上传与存储结构、神经网络模型处理接口以及车辆潮汐状态分析算法的设计思路。读者可从中获取一套从数据集构建、模型训练到可视化平台落地的完整方案理解如何通过实时车辆检测与分类为交通规划提供决策支持。目前已有87人学习适合需要撰写同类论文或搭建检测系统的读者参考借鉴。1. 车辆潮汐监测到底在测什么从一条堵死的左转道说起早高峰的左转道排到下一个路口对向车道却空得能跑步这种场景每个在城市里开车的人都见过。车辆潮汐监测系统要解决的就是这件事实时统计各车道的车流量与排队长度判断哪条车道该借给对向用什么时段切换切换后效果如何。它的核心不是检测到车而是按车道、按方向、按时间窗口把车数清楚。基于 YOLOv5 做这套系统是目前性价比最高的路线——YOLOv5 权重小、推理快、部署链路成熟配合 Python 和 OpenCV 就能在普通工控机甚至树莓派 5 上跑起来。这篇内容面向做目标检测课程设计、毕业论文选题或者真要在路口架一套原型验证的工程师把从数据集准备、模型训练、车道区域划分到潮汐判定的完整路径拆开讲清楚参数怎么设、坑在哪都会落到具体命令和代码上。2. 为什么选 YOLOv5 而不是 SSD 或 YOLOv3车辆检测的选型账2.1 车辆潮汐场景对检测器的四个硬要求潮汐监测和普通车辆计数最大的区别在于它要求检测结果能稳定映射到车道这个空间单元上并且要在连续视频流里保持帧间一致。这带来四个硬要求。第一是推理速度。潮汐判定需要按分钟级窗口统计但底层检测通常要跑到 10 FPS 以上才能避免漏计快速通过的车辆。YOLOv5s 在单张 1080Ti 上能到 140 FPS 左右在 CPU 上用 ONNX Runtime 也能到 8 到 15 FPS这个量级刚好够用。第二是小目标召回。路口摄像头架在 6 到 8 米高度远处车辆在画面里可能只有 30 到 50 像素宽。YOLOv5 的 PANet 结构对中小目标比 SSD 的 VGG 主干更友好SSD 在 300x300 输入下对 40 像素以下的目标召回明显掉。第三是类别简单。潮汐监测通常只需要 car、bus、truck、motorcycle 四类甚至只分大车/小车两类。类别少意味着可以牺牲分类头容量把资源集中在定位精度上YOLOv5n 或 YOLOv5s 足够。第四是部署生态。YOLOv5 官方仓库直接支持导出 TorchScript、ONNX、TensorRT、CoreML还有现成的 Flask 推理接口。相比之下 YOLOv3 的 Darknet 权重转 ONNX 要绕一圈SSD 的 TensorFlow 部署链路在边缘设备上更重。提示如果只是做毕业论文的算法对比章节YOLOv3、SSD、Faster R-CNN 都可以跑一遍做 mAP 对比但落地系统的主干建议锁死 YOLOv5s不要为了看起来更高级上 YOLOv8 或 YOLOv26后者在老旧 CUDA 环境下的兼容性会拖慢整个进度。2.2 环境配置从零到能跑通 detect.py 的最小步骤环境配置是第一个翻车高发区。血泪经验是不要用最新版 PyTorch不要用 Python 3.12不要用 CUDA 12 配老驱动。下面这套组合在 2024 年的主流工控机和云服务器上都验证过。# 创建独立环境Python 版本锁 3.8 或 3.9 conda create -n tide_yolo python3.9 -y conda activate tide_yolo # PyTorch 选 1.13.1 CUDA 11.7这个组合对 YOLOv5 v7.0 最稳 pip install torch1.13.1cu117 torchvision0.14.1cu117 \ --extra-index-url https://download.pytorch.org/whl/cu117 # 克隆 YOLOv5 仓库锁 v7.0 分支不要用 master git clone -b v7.0 https://github.com/ultralytics/yolov5.git cd yolov5 pip install -r requirements.txt # 验证环境 python -c import torch; print(torch.__version__, torch.cuda.is_available())这段命令的逻辑是先隔离环境避免和系统 Python 冲突再装指定版本的 PyTorch 和 torchvision然后拉取 YOLOv5 v7.0 分支。参数说明上torch1.13.1cu117里的cu117表示编译时链接的 CUDA 版本必须和nvidia-smi显示的驱动支持版本匹配。如果torch.cuda.is_available()返回 False先查驱动版本再查 CUDA Toolkit 是否装了 11.7不要急着重装系统。验证通过后跑一次官方示例python detect.py --weights yolov5s.pt --source data/images/bus.jpg --device 0如果能在runs/detect/exp/下看到带框的公交车图片环境就算通了。这一步跑不通后面所有训练都是白费。2.3 数据集准备车辆标注的类别设计与格式转换车辆检测数据集常见来源有三个UA-DETRAC、BDD100K、以及自己用路口摄像头截帧标注。UA-DETRAC 有 8 万多帧、标注了 82 万辆车适合做预训练BDD100K 场景更丰富但标注粒度粗自采数据最贴合实际路口但标注成本高。类别设计上我一般建议先只分四类car、bus、truck、motorcycle。不要一上来分十几个车型潮汐监测不关心是比亚迪还是特斯拉只关心占道长度和通过速度。标注工具用 LabelImg 或 CVAT导出 YOLO 格式。YOLO 格式每行是class_id x_center y_center width height全部归一化到 0 到 1。转换脚本如下import os import xml.etree.ElementTree as ET # VOC 的类别名顺序决定 class_id CLASSES [car, bus, truck, motorcycle] def voc_to_yolo(xml_path, img_w, img_h): tree ET.parse(xml_path) root tree.getroot() lines [] for obj in root.iter(object): cls_name obj.find(name).text if cls_name not in CLASSES: continue cls_id CLASSES.index(cls_name) bbox obj.find(bndbox) xmin float(bbox.find(xmin).text) ymin float(bbox.find(ymin).text) xmax float(bbox.find(xmax).text) ymax float(bbox.find(ymax).text) # 归一化并转为中心点宽高 x_c (xmin xmax) / 2.0 / img_w y_c (ymin ymax) / 2.0 / img_h w (xmax - xmin) / img_w h (ymax - ymin) / img_h lines.append(f{cls_id} {x_c:.6f} {y_c:.6f} {w:.6f} {h:.6f}) return lines这段代码的关键点有三个CLASSES的顺序必须和训练时data.yaml里的names完全一致否则类别会错位归一化用的img_w和img_h必须和实际图片尺寸一致不能想当然用 1920x1080:.6f保留六位小数是 YOLOv5 官方推荐精度太少会导致小目标框偏移。转换完成后目录结构应该是dataset/ images/ train/ val/ labels/ train/ val/ data.yamldata.yaml内容path: ./dataset train: images/train val: images/val nc: 4 names: [car, bus, truck, motorcycle]注意nc必须等于names的长度多一个空格少一个引号都会在训练启动时报错。这个错误看起来低级但我在三个项目里都见过有人栽在这。3. 训练自己的车辆数据集超参数怎么调、指标怎么看3.1 从预训练权重出发的训练命令与关键参数YOLOv5 训练不要从零开始一定要加载yolov5s.pt做迁移学习。车辆检测和 COCO 的类别有重叠预训练权重能省掉至少一半的收敛时间。python train.py \ --weights yolov5s.pt \ --data dataset/data.yaml \ --epochs 100 \ --batch-size 16 \ --imgsz 640 \ --device 0 \ --workers 8 \ --hyp data/hyps/hyp.scratch-low.yaml \ --project runs/train \ --name tide_v1参数逐个说--weights yolov5s.pt指定预训练权重--epochs 100对车辆这种简单类别通常 80 到 120 轮收敛--batch-size 16是 8GB 显存下的安全值显存够可以上 32--imgsz 640是 YOLOv5 的标准输入如果远处车辆特别小可以试 1280但显存翻四倍--workers 8是数据加载线程数设成 CPU 核数的 0.8 倍左右--hyp用hyp.scratch-low.yaml是因为车辆数据集通常不大低学习率增强配置更稳。训练启动后重点看三个指标box_loss、obj_loss、mAP0.5。box_loss应该在 20 轮内降到 0.05 以下obj_loss降到 0.03 以下mAP0.5在 60 轮后应该稳定在 0.85 以上。如果mAP卡在 0.5 不动八成是标注有问题不是模型问题。3.2 潮汐场景下的数据增强策略通用增强里mosaic和mixup对车辆检测帮助最大但潮汐场景有两个特殊点要注意。第一不要用上下翻转。车辆在画面里永远是轮子朝下上下翻转会造出物理上不存在的样本反而干扰学习。YOLOv5 默认不开上下翻转保持默认即可。第二mosaic概率不要设太高。默认 1.0 意味着每张图都做四图拼接在车辆密集的路口场景下会导致小目标被过度压缩。我一般把mosaic降到 0.5mixup保持 0.0因为 mixup 在车辆场景下容易造出半透明叠影对定位不利。修改hyp.scratch-low.yamlmosaic: 0.5 mixup: 0.0 flipud: 0.0 fliplr: 0.5 hsv_h: 0.015 hsv_s: 0.7 hsv_v: 0.4hsv_h/s/v是色调、饱和度、亮度扰动路口摄像头在不同时段光照差异大这三个值保持默认即可不要为了增强调到 0.5 以上否则颜色失真会让模型把白色车和灰色路面混淆。3.3 训练结果验证混淆矩阵和 PR 曲线的读法训练完在runs/train/tide_v1/下会生成confusion_matrix.png和PR_curve.png。混淆矩阵看对角线如果 car 和 truck 之间有明显误判说明这两类在远处小目标时特征太像解决办法是在标注阶段把皮卡统一归到 car不要单独设类。PR 曲线看曲线下的面积也就是各类的 AP。如果 motorcycle 的 AP 明显低于其他类通常是样本量太少。UA-DETRAC 里摩托车占比不到 5%自采数据要刻意补一些摩托车帧。验证命令python val.py \ --weights runs/train/tide_v1/weights/best.pt \ --data dataset/data.yaml \ --img 640 \ --task val输出里重点看mAP0.5和mAP0.5:0.95。前者到 0.9 以上、后者到 0.6 以上这套权重就可以拿去部署了。如果mAP0.5:0.95只有 0.3 左右说明框的定位精度不够检查标注框是否贴紧车辆边缘不要留太多空隙。4. 从检测框到潮汐判定车道区域划分与流量统计4.1 用多边形 ROI 把检测框映射到车道YOLOv5 输出的是画面坐标下的框潮汐监测需要的是第 2 车道当前有 7 辆车。中间这一步靠 ROI 多边形完成。思路是在画面上为每条车道画一个四边形检测框中心点落在哪个四边形内就归到哪条车道。代码用 OpenCV 的pointPolygonTestimport cv2 import numpy as np # 每条车道的四边形顶点按实际画面标定 LANE_POLYGONS { lane_1: np.array([[100, 400], [400, 400], [350, 700], [50, 700]]), lane_2: np.array([[420, 400], [720, 400], [670, 700], [370, 700]]), lane_3: np.array([[740, 400], [1040, 400], [990, 700], [690, 700]]), } def assign_lane(cx, cy): for lane_name, poly in LANE_POLYGONS.items(): # 返回正数表示点在多边形内 if cv2.pointPolygonTest(poly, (float(cx), float(cy)), False) 0: return lane_name return NoneLANE_POLYGONS的顶点必须按实际摄像头画面标定不能照抄。标定方法是在画面上叠加网格手动读出每条车道在画面底部和中部的大致边界。pointPolygonTest的第三个参数False表示只判断内外不返回距离速度快。4.2 跨帧去重为什么同一辆车会被数两次直接对每帧检测结果计数同一辆车在连续 30 帧里会被数 30 次。去重有两个主流方案跟踪算法和虚拟线圈。跟踪算法用 ByteTrack 或 DeepSORT给每辆车分配 ID按 ID 去重。优点是准确缺点是要多跑一个模型边缘设备上帧率会掉一半。虚拟线圈更轻量在每条车道上画一条水平线检测框中心点从上往下穿过这条线时计一次数。代码class LineCounter: def __init__(self, line_y): self.line_y line_y self.prev_centers {} # track_id - 上一帧 y 坐标 self.count 0 def update(self, track_id, cy): if track_id in self.prev_centers: prev_y self.prev_centers[track_id] # 从上往下穿过 if prev_y self.line_y cy: self.count 1 self.prev_centers[track_id] cyline_y是虚拟线圈的纵坐标一般设在画面高度的 60% 到 70% 处太靠上会受远处小目标抖动影响太靠下车辆已经驶出画面。prev_centers字典要定期清理否则长时间运行会内存泄漏建议每 5 分钟清一次超过 10 秒没更新的 ID。4.3 潮汐判定逻辑阈值、时间窗口和切换策略有了每条车道的分钟级流量潮汐判定就是一个规则引擎。核心参数有三个统计窗口、流量差阈值、最小切换间隔。统计窗口建议 5 分钟。太短会被红绿灯周期干扰太长反应迟钝。流量差阈值建议设为对向车道平均流量的 30%比如对向 5 分钟过了 100 辆本方向超过 130 辆才触发借道。最小切换间隔建议 15 分钟避免频繁切换导致驾驶员困惑。def judge_tide(flow_a, flow_b, window300, ratio0.3, min_interval900): flow_a: 方向 A 当前窗口流量 flow_b: 方向 B 当前窗口流量 返回: 1 表示 A 借 B 的道-1 表示 B 借 A 的道0 表示不变 if flow_a flow_b * (1 ratio): return 1 if flow_b flow_a * (1 ratio): return -1 return 0这个函数只做单次判定实际系统里要加状态机记录上次切换时间距离上次切换不足min_interval时即使满足条件也不切。状态机用 Redis 存last_switch_ts就行不要用内存变量否则服务重启就丢。提示潮汐判定结果不要直接控制信号灯先做成建议推给值班人员跑两周看准确率再考虑闭环。直接闭环一旦误判路口会堵得更死这个后悔药没地方买。5. 部署与性能优化从工控机到树莓派 5 的落地路径5.1 模型导出ONNX 和 TensorRT 的取舍训练完的best.pt不能直接上生产要先导出。导出命令# 导出 ONNX通用性最好 python export.py --weights runs/train/tide_v1/weights/best.pt \ --include onnx --img 640 --batch 1 --opset 12 # 导出 TensorRTNVIDIA 设备上最快 python export.py --weights runs/train/tide_v1/weights/best.pt \ --include engine --img 640 --batch 1 --device 0--opset 12是 ONNX 算子集版本低于 11 会缺一些算子高于 13 部分推理引擎不支持。--batch 1是推理时的批大小潮汐监测是实时流批大小设 1 延迟最低。TensorRT 导出必须在有 NVIDIA GPU 的机器上做导出的.engine文件绑定具体 GPU 架构换卡要重新导出。性能对比大致是PyTorch 原生推理 30 FPSONNX Runtime 50 FPSTensorRT FP16 能到 120 FPS 以上。树莓派 5 上没有 NVIDIA GPU只能用 ONNX Runtime 或 NCNNYOLOv5s 在树莓派 5 上大概 5 到 8 FPS做潮汐监测够用因为统计窗口是分钟级。5.2 推理服务封装Flask 接口与视频流处理生产环境建议把推理封装成 HTTP 服务视频解码和推理分离。Flask 最小示例from flask import Flask, request, jsonify import cv2 import numpy as np import onnxruntime as ort app Flask(__name__) session ort.InferenceSession(best.onnx, providers[CPUExecutionProvider]) def preprocess(img, size640): img cv2.resize(img, (size, size)) img img[:, :, ::-1].transpose(2, 0, 1) # BGR-RGB, HWC-CHW img np.ascontiguousarray(img, dtypenp.float32) / 255.0 return img[None, ...] app.route(/detect, methods[POST]) def detect(): file request.files[frame].read() img cv2.imdecode(np.frombuffer(file, np.uint8), cv2.IMREAD_COLOR) inp preprocess(img) outputs session.run(None, {session.get_inputs()[0].name: inp}) # outputs[0] 形状 [1, 25200, 5nc]后处理略 return jsonify({boxes: outputs[0].tolist()})preprocess里的[:, :, ::-1]是 BGR 转 RGBYOLOv5 训练时用的是 RGB推理时忘了转会直接导致检测全错这是最常见的部署翻车点。/255.0是归一化必须和训练时一致。视频流处理建议用cv2.VideoCapture拉 RTSP 流每 5 帧取 1 帧送推理中间帧用跟踪算法补这样能把有效帧率提到 25 FPS 以上。5.3 边缘设备上的帧率与精度平衡树莓派 5 或 Jetson Nano 这类设备上640 输入跑不动就降到 416 或 320。YOLOv5 支持动态输入尺寸导出时指定--img 416即可。精度损失大概 3 到 5 个 mAP 点但帧率能翻倍。另一个技巧是只对 ROI 区域推理。路口画面里天空和建筑占一半以上把画面裁剪到车道区域再送模型输入尺寸不变但有效分辨率提高远处车辆检测更准。裁剪坐标和LANE_POLYGONS共用一套标定数据不要维护两份。6. 避坑与排查车辆潮汐监测系统最常见的五个翻车点6.1 训练 loss 不降反升现象训练前 10 轮box_loss从 0.1 涨到 0.3mAP一直是 0。原因九成是学习率太大或者标注格式错。YOLOv5 默认初始学习率 0.01如果数据集只有几百张图这个值偏大。另一个可能是data.yaml里的nc和实际类别数不一致模型输出维度对不上。解决先用--hyp data/hyps/hyp.scratch-low.yaml把学习率降到 0.001 试 20 轮。如果还不行用python utils/check_dataset.py检查标注重点看有没有宽高为 0 的框。6.2 验证集 mAP 很高但实际画面检测不到车现象val.py输出mAP0.50.92但拿实际路口视频跑detect.py画面里明明有车却不出框。原因训练集和实际场景的域差异。训练集如果是 UA-DETRAC 的白天高速场景实际是夜间路口光照和视角完全不同。另一个可能是推理时的预处理和训练不一致比如训练用了 letterbox 填充推理直接 resize 导致长宽比失真。解决从实际摄像头截 200 帧标注后加入训练集做微调学习率设 0.0001 跑 30 轮。推理预处理必须用 YOLOv5 自带的letterbox函数不要自己写 resize。6.3 同一辆车被反复计数现象流量统计结果比实际车流高 3 到 5 倍。原因没有做跨帧去重或者虚拟线圈的line_y设在了画面抖动区域车辆在线上来回穿越导致重复触发。解决引入 ByteTrack 做 ID 分配按 ID 去重。如果不用跟踪把line_y往下移 50 像素并在计数逻辑里加同一 ID 在 2 秒内只计一次的冷却。6.4 ONNX 推理结果和 PyTorch 不一致现象PyTorch 下检测正常导出 ONNX 后框全偏或者置信度全乱。原因预处理不一致。PyTorch 推理时 YOLOv5 内部做了 letterbox 和归一化ONNX 导出后这些操作要自己实现。常见错误是忘了 BGR 转 RGB或者归一化用了 255 而不是 255.0 导致整数除法。解决对照detect.py里的LoadImages和letterbox函数把预处理一步步复刻到 ONNX 推理代码里。用同一张图分别跑 PyTorch 和 ONNX逐元素对比输入张量必须完全一致。6.5 长时间运行后服务卡死现象推理服务跑 2 到 3 天后响应越来越慢最后无响应。原因内存泄漏。常见来源是cv2.VideoCapture没有释放、跟踪算法的 ID 字典无限增长、Flask 的请求上下文没清理。解决给LineCounter.prev_centers加定期清理每 5 分钟删掉超过 10 秒没更新的 ID。VideoCapture在异常时用try/finally确保release()。Flask 用 gunicorn 起多 worker单个 worker 内存超过 2GB 自动重启。7. 一个能直接用的技巧用热力图反查漏检车道系统跑起来之后最怕的不是检测不到而是你不知道哪条车道在漏检。我一般会在部署阶段加一个热力图统计把每帧检测框的中心点累加到一张和画面同尺寸的浮点矩阵上跑 24 小时后归一化可视化。import cv2 import numpy as np class HeatmapRecorder: def __init__(self, h, w): self.heat np.zeros((h, w), dtypenp.float32) def add(self, cx, cy): # 在中心点周围 5x5 区域累加避免单像素太稀疏 x1, x2 max(0, cx - 2), min(self.heat.shape[1], cx 3) y1, y2 max(0, cy - 2), min(self.heat.shape[0], cy 3) self.heat[y1:y2, x1:x2] 1.0 def save(self, path): norm cv2.normalize(self.heat, None, 0, 255, cv2.NORM_MINMAX) norm norm.astype(np.uint8) colored cv2.applyColorMap(norm, cv2.COLORMAP_JET) cv2.imwrite(path, colored)add方法里用 5x5 区域而不是单像素是因为检测框中心点本身有抖动单像素累加会得到一张噪点图。cv2.normalize把累加值映射到 0 到 255COLORMAP_JET是常用的蓝到红配色红色区域就是车流最密集的地方。拿到热力图后把它和LANE_POLYGONS叠加。如果某条车道的多边形区域在热力图上几乎是蓝色说明这条车道要么没车要么模型在这条车道上漏检。结合原始视频回放能快速定位是 ROI 画错了还是模型对某个视角不敏感。这个技巧我在三个路口项目里都用过最典型的一次是发现最右侧车道热力图异常稀疏回放后发现那条车道的摄像头角度导致车辆只露出车顶模型把车顶当成了背景。解决办法是在训练集里补了 300 张俯视角度样本微调 20 轮后召回恢复正常。做这类系统我的习惯是每上线一个新路口先跑 48 小时热力图和流量日志确认没有系统性漏检再开潮汐判定。宁可多等两天也不要让一个漏检的车道去触发借道那个后果比不借道严重得多。希望帮到你。本文还有配套的精品资源点击获取
返回列表