基于YOLOv5与ByteTrack的车辆潮汐监测系统:从检测跟踪到越线计数全流程
发布时间:2026/9/30 5:19:41来源:尧图网络
简介这份毕业设计文档面向计算机视觉与智能交通方向的本科生及研究生围绕基于YOLOv5的车辆潮汐监测系统展开完整的设计与实现论述可帮助读者理解如何将目标检测算法落地到城市交通监控场景。资源包内仅含1个docx文件约1.13MB即论文正文结构涵盖绪论、国内外研究现状、相关理论与技术、系统设计与架构、结论与展望及参考文献等章节。文中系统梳理了卷积神经网络、线程池、YOLOv5检测机制、MongoDB、PyTorch、cuDNN、Transformer与Flask等关键技术并给出前后端分离架构、登录权限分配、数据上传与存储结构、神经网络模型处理接口以及车辆潮汐状态分析算法的实现思路。目前已有87人学习适合需要参考选题框架、技术选型与论文写作逻辑的读者也可为后续优化复杂交通场景适应性与检测速度提供思路。1. 从一段晚高峰视频说起车辆潮汐监测到底在解决什么问题早高峰进城方向堵成停车场出城方向空得能跑步晚高峰反过来。这个现象每个在城市里开车的人都骂过但真要把它变成一个能写进毕业论文、能跑起来、能出数据的系统很多人第一步就卡住了——数据从哪来怎么定义潮汐检测出来的车流数字怎么变成结论车辆潮汐监测系统本质上就是一套用摄像头视频流自动统计双向车流量、判断车道潮汐特性的方案。核心链路是视频抽帧 → 目标检测识别车辆 → 多目标跟踪维持 ID → 越线计数 → 按方向和时间窗口聚合 → 输出潮汐指数。基于 YOLOv5 来做是因为它在精度和推理速度之间平衡得最好源码结构清晰改起来不费劲对毕业论文这种既要讲原理又要出实验结果的场景非常合适。这套东西适合谁做计算机视觉方向毕业设计的学生、想入门目标检测落地的工程师、需要做交通流量统计原型的产品团队。读完你应该能自己搭出一套能跑通、能出图、能写进论文的系统而不是停留在调了个预训练模型跑了几张图的阶段。2. 系统拆解从视频流到潮汐指数的完整链路2.1 为什么选 YOLOv5 而不是 SSD 或 Faster R-CNN目标检测算法选型这件事在毕业论文里是要写一段方案对比的。我一般会从三个维度去比精度、速度、工程友好度。Faster R-CNN 精度不错但两阶段检测器推理速度慢想在一段 1080p 视频上做实时或准实时统计帧率会很难看。SSD 速度快但小目标检测能力偏弱远处车辆容易漏检而交通监控场景里远处车辆恰恰占比不小。YOLOv5 是单阶段检测器在 COCO 上 mAP 和推理速度的平衡做得好而且 Ultralytics 的工程封装非常成熟——数据加载、增强、训练、导出 ONNX 一条龙改配置文件就能跑自己的数据集。另一个现实原因是YOLOv5 的社区资料足够多。你在训练自己数据集时遇到的坑大概率已经有人踩过并写了博客。对毕业论文来说这意味着你能把更多时间花在系统设计和实验分析上而不是跟环境配置死磕。具体到版本选择YOLOv5 有 n/s/m/l/x 五个尺度。车辆检测场景我一般推荐 yolov5s 起步——模型小、推理快在车辆这种纹理特征明显的目标上精度够用。如果显存充裕且追求更高 mAP可以上 yolov5m。n 版本虽然最快但小目标召回率会明显下降不建议在交通场景用。2.2 车辆检测之外为什么还需要跟踪和计数逻辑很多人做毕业论文时容易犯一个错把检测到车辆当成终点。但潮汐监测要的是一段时间内从 A 方向过了多少辆车从 B 方向过了多少辆车。检测只给你每一帧的框框和框之间没有身份关联你根本不知道这一帧的某辆车和上一帧的某辆车是不是同一辆。所以必须加多目标跟踪。常见做法是 YOLOv5 DeepSORT 或 YOLOv5 ByteTrack。ByteTrack 更轻量不需要额外的 ReID 模型在车辆场景下效果稳定我一般优先选它。跟踪之后每辆车有了稳定的 track ID再配合虚拟线圈就是在画面里画一条线或一个区域当某个 ID 的轨迹从线的一侧穿越到另一侧时计数加一同时记录方向。潮汐指数怎么算最简单的定义在统计窗口内方向 A 的流量除以方向 B 的流量得到一个比值。比值大于 1 说明 A 方向流量更大小于 1 说明 B 方向更大。再结合时间维度就能画出一天内潮汐指数的变化曲线。论文里可以进一步做平滑、设阈值、判断是否需要潮汐车道切换。2.3 一套可复现的目录结构和环境配置在动手写代码之前先把项目结构定下来。毕业论文的项目不需要多复杂但目录清晰能让你的论文系统设计章节好写很多。# 项目根目录结构 tidal_traffic/ ├── configs/ # 配置文件 │ ├── yolov5s.yaml # 模型结构配置 │ └── data_vehicle.yaml # 数据集配置 ├── data/ # 数据集 │ ├── images/ │ │ ├── train/ │ │ └── val/ │ └── labels/ │ ├── train/ │ └── val/ ├── weights/ # 模型权重 │ └── yolov5s.pt ├── src/ │ ├── detect.py # 检测脚本 │ ├── track.py # 跟踪计数 │ ├── tidal_index.py # 潮汐指数计算 │ └── utils/ │ ├── line_counter.py # 越线计数逻辑 │ └── visualize.py # 可视化 ├── outputs/ # 输出结果 │ ├── videos/ │ └── charts/ ├── requirements.txt └── README.md环境配置是第一个大坑。YOLOv5 对 PyTorch 和 CUDA 版本有要求版本不匹配会报各种莫名其妙的错。我一般用 conda 建虚拟环境锁定版本# 创建虚拟环境 conda create -n tidal python3.9 -y conda activate tidal # 安装 PyTorch根据你的 CUDA 版本选这里以 CUDA 11.8 为例 pip install torch2.0.1 torchvision0.15.2 --index-url https://download.pytorch.org/whl/cu118 # 安装 YOLOv5 依赖 pip install -r requirements.txt # 验证 GPU 是否可用 python -c import torch; print(torch.cuda.is_available())这里的关键参数是 CUDA 版本和 PyTorch 版本的对应关系。如果你机器上是 CUDA 11.8就装 cu118 对应的 torch如果是 12.1就换 cu121。装错了不会立刻报错但训练时会提示 CUDA 不可用然后默默用 CPU 跑速度差几十倍。验证那行命令输出 True 才算配置成功。注意不要混用 pip 和 conda 安装 PyTorch容易出现动态库冲突。要么全用 pip要么全用 conda。3. 训练自己的车辆数据集从标注到模型收敛3.1 车辆数据集怎么来、怎么标、怎么转格式毕业论文的数据集一般有三个来源公开数据集、自己采集、两者混合。公开的车辆检测数据集有不少但类别定义和你的需求未必一致。我建议的做法是用公开数据集做预训练或补充自己采集目标场景的视频抽帧做精标。采集时注意几点覆盖不同光照白天、黄昏、夜间、不同天气晴、阴、雨、不同拥堵程度。每个场景至少抽 200 到 500 帧保证样本多样性。抽帧间隔不要太密相邻帧几乎一样标了也是冗余。标注工具用 LabelImg 或 CVAT 都行标成 YOLO 格式。YOLO 格式的标签文件是 txt每行一个目标格式是类别编号 中心x 中心y 宽度 高度全部归一化到 0 到 1 之间。如果你拿到的是 VOC 格式的 XML需要转成 YOLO 格式。转换脚本很多人写过但边界情况容易翻车import xml.etree.ElementTree as ET import os def voc_to_yolo(xml_path, img_w, img_h, class_map): 将 VOC 格式 XML 转为 YOLO 格式 txt xml_path: XML 文件路径 img_w, img_h: 图片宽高 class_map: 类别名到编号的映射如 {car: 0, bus: 1, truck: 2} tree ET.parse(xml_path) root tree.getroot() lines [] for obj in root.findall(object): cls_name obj.find(name).text if cls_name not in class_map: continue # 跳过不关心的类别 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) # 边界裁剪防止标注超出图像范围 xmin max(0, min(xmin, img_w)) xmax max(0, min(xmax, img_w)) ymin max(0, min(ymin, img_h)) ymax max(0, min(ymax, img_h)) # 计算归一化中心点和宽高 cx (xmin xmax) / 2.0 / img_w cy (ymin ymax) / 2.0 / img_h w (xmax - xmin) / img_w h (ymax - ymin) / img_h lines.append(f{class_map[cls_name]} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}) return lines这段代码里最容易出问题的是边界裁剪。有些标注文件里 xmax 会等于图片宽度甚至超出 1 个像素不裁剪的话归一化后坐标会大于 1YOLOv5 训练时虽然不一定报错但会影响回归精度。另外类别映射要和你 data.yaml 里的 names 顺序严格一致顺序错了模型学出来的类别就是乱的。3.2 data.yaml 和超参数怎么配YOLOv5 的数据配置文件长这样# data_vehicle.yaml path: ../data # 数据集根目录 train: images/train # 训练集图片相对路径 val: images/val # 验证集图片相对路径 nc: 3 # 类别数 names: [car, bus, truck] # 类别名称顺序必须和标注一致超参数方面YOLOv5 默认的 hyp.scratch-low.yaml 对车辆检测基本够用但有几个参数我通常会调参数默认值建议值说明lr00.010.01初始学习率车辆检测不需要大改lrf0.010.01最终学习率系数余弦退火用momentum0.9370.937SGD 动量保持默认weight_decay0.00050.0005权重衰减防过拟合warmup_epochs3.03.0预热轮数小数据集可降到 1box0.050.05框回归损失权重cls0.50.5分类损失权重iou_t0.200.20IoU 训练阈值如果你的数据集比较小比如只有两三千张把 warmup_epochs 降到 1同时把 epochs 设到 100 到 150配合早停。数据集大上万张就可以用默认的 300 epochs。3.3 训练命令与关键日志解读训练命令本身不复杂# 单卡训练 python train.py \ --img 640 \ --batch 16 \ --epochs 100 \ --data configs/data_vehicle.yaml \ --weights weights/yolov5s.pt \ --cfg configs/yolov5s.yaml \ --name vehicle_exp1 \ --cache参数逐个说--img 640是输入分辨率车辆检测 640 够用想提小目标可以上 1280 但显存翻倍--batch 16根据显存调8G 显存用 1612G 可以上 32--weights指定预训练权重从 COCO 预训练模型开始收敛快很多--cache把图片缓存到内存加速数据加载但数据集大时吃内存。训练开始后重点看几个指标box_loss和cls_loss应该稳步下降如果震荡剧烈说明学习率偏大mAP0.5是主要精度指标车辆检测一般能到 0.85 以上precision和recall要平衡看recall 太低说明漏检多precision 太低说明误检多。如果训练到一半 mAP 不涨了先别急着加 epoch。检查一下验证集里有没有标注错误的样本或者类别不平衡——比如 truck 样本特别少模型就会偏向 car。这种情况可以在 data.yaml 里加类别权重或者对少样本类别做过采样。4. 检测跟踪计数把模型变成潮汐监测系统4.1 用 ByteTrack 给检测框赋予稳定 ID检测模型训练好之后导出权重接下来就是推理和跟踪。ByteTrack 的核心思路是不光用高分检测框做匹配低分检测框也拿来跟已有轨迹做二次匹配这样能减少 ID 切换。import cv2 import torch from yolov5.models.common import DetectMultiBackend from yolov5.utils.general import non_max_suppression, scale_boxes from yolov5.utils.augmentations import letterbox from byte_tracker import BYTETracker # 假设你用的是 ByteTrack 的 Python 实现 # 加载模型 device torch.device(cuda if torch.cuda.is_available() else cpu) model DetectMultiBackend(weights/best.pt, devicedevice) model.eval() # 初始化跟踪器 tracker BYTETracker(frame_rate30) cap cv2.VideoCapture(data/test_video.mp4) while cap.isOpened(): ret, frame cap.read() if not ret: break # 预处理letterbox 保持宽高比 img letterbox(frame, 640, stride32, autoTrue)[0] img img.transpose((2, 0, 1))[::-1] # HWC - CHW, BGR - RGB img torch.from_numpy(img).to(device).float() / 255.0 img img.unsqueeze(0) # 推理 pred model(img) pred non_max_suppression(pred, conf_thres0.4, iou_thres0.5) # 提取检测结果用于跟踪 dets [] for det in pred: if det is not None and len(det): det[:, :4] scale_boxes(img.shape[2:], det[:, :4], frame.shape).round() for *xyxy, conf, cls in det: dets.append([xyxy[0].item(), xyxy[1].item(), xyxy[2].item(), xyxy[3].item(), conf.item()]) # 更新跟踪器 online_targets tracker.update(dets, frame.shape[:2], frame.shape[:2]) for t in online_targets: tlwh t.tlwh tid t.track_id cv2.rectangle(frame, (int(tlwh[0]), int(tlwh[1])), (int(tlwh[0]tlwh[2]), int(tlwh[1]tlwh[3])), (0, 255, 0), 2) cv2.putText(frame, fID:{tid}, (int(tlwh[0]), int(tlwh[1])-5), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2) cv2.imshow(track, frame) if cv2.waitKey(1) 27: break关键参数说明conf_thres0.4是检测置信度阈值交通场景建议 0.3 到 0.5 之间太低误检多太高漏检多iou_thres0.5是 NMS 的 IoU 阈值车辆之间重叠不多0.5 够用。ByteTrack 的frame_rate要和你视频实际帧率一致设错了跟踪匹配的时间窗口会偏。4.2 虚拟线圈越线计数的实现细节有了稳定的 track ID计数就好做了。核心逻辑是记录每个 ID 上一帧的中心点位置和当前帧比较判断是否穿越了预设的线。class LineCounter: def __init__(self, line_start, line_end): line_start, line_end: 虚拟线圈的两个端点坐标 (x, y) self.line_start line_start self.line_end line_end self.tracks {} # track_id - 上一帧中心点 self.count_a 0 # 方向 A 计数 self.count_b 0 # 方向 B 计数 def _side(self, point): 判断点在线的哪一侧用叉积符号 x, y point x1, y1 self.line_start x2, y2 self.line_end cross (x2 - x1) * (y - y1) - (y2 - y1) * (x - x1) return 1 if cross 0 else -1 def update(self, track_id, center): 每帧调用传入 track_id 和当前中心点 if track_id in self.tracks: prev_side self._side(self.tracks[track_id]) curr_side self._side(center) if prev_side ! curr_side: # 发生越线 if prev_side 0 and curr_side 0: self.count_a 1 else: self.count_b 1 self.tracks[track_id] center这段代码里_side方法用的是叉积符号判断点在直线哪一侧这是计算几何里的标准做法。越线判断的条件是前后两帧中心点分居线两侧。方向判断根据叉积符号变化来确定。实际部署时有两个细节要注意一是线的位置要选在车辆轨迹比较垂直穿过的地方斜穿容易漏计二是要加一个冷却时间同一个 ID 在短时间内反复穿越同一条线只计一次防止跟踪抖动导致重复计数。4.3 潮汐指数计算与可视化输出计数数据拿到之后按时间窗口聚合算潮汐指数import pandas as pd import matplotlib.pyplot as plt # 假设 counts 是一个列表每个元素是 (timestamp, direction, track_id) df pd.DataFrame(counts, columns[timestamp, direction, track_id]) df[timestamp] pd.to_datetime(df[timestamp]) df.set_index(timestamp, inplaceTrue) # 按 5 分钟窗口聚合 window 5T count_a df[df[direction] A].resample(window).count()[track_id] count_b df[df[direction] B].resample(window).count()[track_id] # 计算潮汐指数加 1 防止除零 tidal_index count_a / (count_b 1) # 可视化 fig, ax1 plt.subplots(figsize(12, 5)) ax1.plot(count_a.index, count_a.values, b-, labelDirection A) ax1.plot(count_b.index, count_b.values, r-, labelDirection B) ax1.set_xlabel(Time) ax1.set_ylabel(Vehicle Count) ax1.legend(locupper left) ax2 ax1.twinx() ax2.plot(tidal_index.index, tidal_index.values, g--, labelTidal Index) ax2.set_ylabel(Tidal Index) ax2.legend(locupper right) plt.title(Tidal Traffic Monitoring) plt.tight_layout() plt.savefig(outputs/charts/tidal_index.png, dpi150)窗口大小选 5 分钟还是 15 分钟取决于你的视频时长和分析粒度。论文里可以两种都跑对比曲线平滑度和灵敏度。潮汐指数大于 1.5 或小于 0.67 通常可以认为存在明显潮汐现象这个阈值可以根据实际数据调整。5. 避坑指南训练和部署中最容易翻车的五个地方5.1 现象训练 loss 正常下降但 mAP 一直是 0原因最常见的是标签格式不对。YOLO 要求标签是归一化的中心点宽高如果你直接填了像素坐标或者类别编号从 1 开始而不是 0模型学出来的东西完全是乱的。另一个可能是 data.yaml 里的路径写错了模型实际加载的是空数据集。解决用官方提供的utils/general.py里的检查脚本验证标签或者自己写个脚本随机抽几张图把框画出来看。路径问题就在 data.yaml 里用绝对路径先跑通再改相对路径。5.2 现象推理时检测框位置整体偏移原因预处理用了 letterbox 但后处理没有做对应的坐标还原。letterbox 会在图片周围补灰边检测框是在补边后的图上算的直接映射回原图就会偏。解决用 YOLOv5 自带的scale_boxes函数做坐标还原它内部会处理 padding 的偏移量。不要自己手写缩放逻辑容易漏掉 padding 那一项。5.3 现象跟踪 ID 频繁切换计数严重偏多原因检测框抖动太大或者 ByteTrack 的匹配阈值设得不合适。车辆被遮挡再出现时如果 ReID 特征不够强跟踪器会当成新目标。解决提高检测置信度阈值减少低质量框适当增大 ByteTrack 的track_buffer参数让丢失的轨迹保留更久。如果还是不行考虑换 DeepSORT 加 ReID 模型但速度会降。5.4 现象夜间视频检测效果断崖式下降原因训练集里夜间样本太少模型没见过低照度下的车辆纹理。这是数据集层面的问题不是调参能解决的。解决补采夜间视频抽帧标注或者在训练时加亮度、对比度增强。推理阶段可以先做直方图均衡化或 CLAHE 预处理但效果有限根本还是靠数据。5.5 现象显存溢出batch 调到 1 还是 OOM原因图片分辨率设太高或者模型用了 yolov5l/x 这种大模型。另外--cache在数据集大时会吃大量内存不是显存但会导致进程被杀。解决先把--img降到 640 甚至 416换 yolov5s去掉--cache。如果还不行检查是不是有其他进程占着显存nvidia-smi看一下。6. 让论文实验部分更扎实消融对比与部署验证写到论文的实验章节光有一个 mAP 数字是不够的。答辩老师大概率会问你为什么选 YOLOv5换了别的模型效果差多少跟踪算法对计数精度的影响有多大这些问题需要你用消融实验来回答。我一般会设计三组对比。第一组是检测模型对比YOLOv5s vs YOLOv5m vs SSD vs Faster R-CNN在同一个验证集上比 mAP0.5、mAP0.5:0.95、推理耗时。第二组是跟踪器对比不加跟踪直接按帧计数 vs ByteTrack vs DeepSORT比计数准确率和 ID 切换次数。第三组是预处理对比原图 vs CLAHE 增强 vs 夜间增强模型比夜间场景的召回率。跑对比实验时有个血泪经验所有实验必须用同一份验证集、同一个置信度阈值、同一个 NMS 阈值。我见过有人对比时忘了统一阈值结果结论完全反了。另外推理耗时要跑多次取平均第一次推理有 CUDA 初始化开销不能算进去。部署验证这块如果你的论文要求系统实现建议至少做一个简单的 Web 界面或者命令行工具能上传视频、输出计数结果和潮汐曲线图。不需要多华丽但要能演示完整流程。用 Gradio 或 Streamlit 搭一个原型很快十几行代码就能出一个上传界面。import gradio as gr def process_video(video_path): # 这里调用你的检测跟踪计数流程 # 返回计数结果和潮汐曲线图路径 count_a, count_b, chart_path run_pipeline(video_path) return f方向A: {count_a} 辆, 方向B: {count_b} 辆, chart_path demo gr.Interface( fnprocess_video, inputsgr.Video(label上传交通视频), outputs[gr.Textbox(label计数结果), gr.Image(label潮汐曲线)], title车辆潮汐监测系统 ) demo.launch()这段代码的价值在于答辩时你可以现场跑一段视频让老师看到从视频输入到潮汐曲线输出的完整链路。比只放几张检测截图有说服力得多。最后说一个我自己的习惯每次跑完实验不管结果好坏先把配置、命令、输出路径记到一个实验日志里。毕业论文要跑几十组对比不记日志的话一周后你根本想不起来某个 mAP 数字是哪组参数跑出来的。这个习惯帮我省了太多返工时间。希望帮到你。本文还有配套的精品资源点击获取
网站建设高端定制企业官网