☰
基于深度学习的交通流量检测系统:从YOLO训练到车辆计数实战
2026/10/11 11:43:45 网站建设 项目流程

简介:基于深度学习的交通流量检测系统毕业设计压缩包,面向计算机科学与人工智能相关专业的学生,整合了从数据采集、清洗、标注、增强,到模型选择、训练、评估,再到系统集成与部署的完整流程,适合作为毕设参考或工程入门。压缩包内含两千个文件,主要文件类型包括脚本、文档和配置三类,对应JavaScript、Markdown与JSON格式,其中脚本用于前端交互与数据可视化,文档用于说明系统结构与算法原理,配置用于存储模型参数与接口信息,整体压缩后约130.5MB。已有666人学习下载,属于较受关注的毕设项目方案。资料涵盖CNN、RNN、LSTM等深度学习模型在车辆检测与流量预测中的应用思路,并针对交通监控场景给出光照、天气等环境干扰下的数据预处理方法,以及模型优化、实时性保障等关键问题的解决策略。整体目录结构清晰,可帮助读者快速理解系统架构与实现细节,为独立开发类似系统或完善论文提供有力支撑。

1. 拿到交通流量检测的毕设 zip:先看清这是检测、跟踪、计数三件事的组装

“基于深度学习的交通流量检测系统”这类毕业设计包,解压后核心不是那个训练好的模型权重,而是“检测 + 跟踪 + 计数”三段管线怎么串起来。交通流量检测和普通目标检测不一样:目标检测只回答“画面里有什么车”,交通流量检测要回答“单位时间内有多少辆车通过某个断面”。这是两个问题。很多同学把精力全放在调 YOLO 的 mAP 上,结果计数环节一测就翻车——车被重复计数、遮挡后 ID 跳变、虚拟线附近框来回抖。这篇笔记就按落地顺序拆:方案选型、数据集准备、训练调参、计数与部署、排坑记录,最后补上答辩时能用的可视化和消融实验做法。适合那些目标是把系统跑通、能演示、能写出规范论文的读者。

2. 方案选型与数据集准备:YOLO 系为主,先定检测粒度再谈精度

2.1 检测、跟踪、计数三层架构:为什么毕设首选 YOLO + ByteTrack

交通流量检测系统的常见架构分三层:检测层负责每帧找出车辆位置,跟踪层负责给同一辆车分配稳定 ID,计数层负责判断车辆是否越过虚拟检测线。检测层选型,我一般直接排除 Faster R-CNN 和 SSD。Faster R-CNN 精度是高,但在视频流场景下推理速度撑不住实时性;SSD 对小目标不友好,路口视频里远处车辆往往只有几十个像素。两阶段检测器做毕设演示总觉得卡顿,单阶段的 YOLO 系是性价比最高的选择,YOLOv5s 或 YOLOv8n 在 1080Ti 上能跑到 60 FPS 以上,做计数绰绰有余。

跟踪层是很多毕设容易忽略的部分。如果不做跟踪,每帧单独检测出来的框无法判断“画面里这台车上一帧是否出现过”,计数只能靠“检测到就加一”,结果一定是重复计数。常见的跟踪方案有 DeepSORT 和 ByteTrack。DeepSORT 需要额外训练一个 ReID 模型提取外观特征,毕设工作量直接翻倍;ByteTrack 只需要检测框的 IoU 关联,用低分检测框做二次匹配,对遮挡的容忍度也不错。我倾向于 ByteTrack,少一个模型依赖,论文里也好解释。

这里顺带说一句,图像配准技术在这个项目里用不上。图像配准是“多相机画面拼接”时的需求,单路口单摄像头场景只需要把检测结果映射到视频坐标即可,别被这个名词带偏。

2.2 数据集来源与标注格式转换:VOC、COCO、YOLO 三种格式的互转脚本

数据集选择上,城市交叉口场景优先考虑 UA-DETRAC,纯车辆检测,带标注框;如果需要检测行人和骑行者,就得混入 BDD100K 的数据,或者自己标注一部分。UA-DETRAC 的标注类别是 car、van、bus、others 四类,没有行人,这是一个常见认知偏差——很多同学以为交通流量只数汽车就够了,但导师一句话“行人算不算交通流量”就会让你回来补数据。稳妥做法是统一成四类:car、bus、truck、pedestrian,骑行者可以根据视频素材决定要不要加。

标注格式是个大坑。UA-DETRAC 官方给的是 XML 格式(类似 VOC),而 YOLO 训练需要的是 txt 格式,每行一个class_id cx cy w h,其中 cx、cy 是归一化后的中心点坐标,w、h 是归一化后的宽高。VOC 格式给的是左上角和右下角的像素坐标,不转换直接训练,检测框会完全错位,而且 loss 一开始就是 nan。下面这段脚本是我常用的 VOC 转 YOLO 工具:

import os import xml.etree.ElementTree as ET from glob import glob def voc_to_yolo(xml_path, out_dir, class_names): tree = ET.parse(xml_path) root = tree.getroot() img_w = int(root.find("size/width").text) img_h = int(root.find("size/height").text) lines = [] for obj in root.findall("object"): name = obj.find("name").text if name not in class_names: continue cls_id = class_names.index(name) bnd = obj.find("bndbox") x1 = float(bnd.find("xmin").text) y1 = float(bnd.find("ymin").text) x2 = float(bnd.find("xmax").text) y2 = float(bnd.find("ymax").text) # 像素坐标 -> 归一化中心点 + 宽高 cx = (x1 + x2) / 2.0 / img_w cy = (y1 + y2) / 2.0 / img_h bw = (x2 - x1) / img_w bh = (y2 - y1) / img_h lines.append(f"{cls_id} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}") out_name = os.path.splitext(os.path.basename(xml_path))[0] + ".txt" with open(os.path.join(out_dir, out_name), "w") as f: f.write("\n".join(lines)) class_names = ["car", "bus", "truck", "pedestrian"] os.makedirs("labels", exist_ok=True) for xml in glob("annotations/*.xml"): voc_to_yolo(xml, "labels", class_names)

这段脚本有两个关键细节。第一是class_names的顺序必须和训练时data.yaml里的类别顺序完全一致,否则类别编号错位;第二是边界框坐标先算中心点再归一化,直接拿 x1、x2 做归一化会得到错误的 cx。跑完后建议随机抽 10 个 txt 文件,把坐标还原成像素值画到原图上确认一遍,这一步能省下后面好几个小时的排错时间。

2.3 视频抽帧与自标注:现场素材怎么处理最省力

毕设通常要加入自己采集或网上找的现场视频来增强说服力。从视频抽帧时,常见做法是用 OpenCV 按固定间隔抽帧,而不是逐帧全抽。24 FPS 的视频逐帧抽,相邻帧之间车辆位移极小,重复样本太多,训练出来的模型对动态姿态泛化差。我一般抽 5 FPS,也就是每 5 帧取一帧,一个 5 分钟的视频能抽到 1500 张左右,足够补充训练。抽帧时保留原始分辨率,因为检测模型的输入是 640×640,但标注和训练时的缩放策略不同,提前压缩会让小目标标注困难。

自己标注建议用 LabelImg,导出格式选 PascalVOC,然后用上一小节的脚本转成 YOLO 格式。标注时注意两点:类别名称不要混用大小写,YOLO 的类别索引对大小写敏感;遮挡严重的车辆宁可框大一点也别框小,框大模型还能学到部分特征,框小了容易学成背景。补充数据时按“白天正常光照 60%、傍晚/阴影 20%、雨天或夜间 20%”的比例配,这样训练出来的模型在答辩现场演示时不容易掉链子。

3. YOLOv5/v8 训练与调参:把 train.py 的参数吃透再跑

3.1 环境配置与预训练权重:Ubuntu 20.04 下的 PyTorch 安装坑

训练环境我建议直接用 Ubuntu 20.04 + CUDA 11.x + PyTorch 1.12 左右的组合,这套组合的兼容性问题最少。Windows 下也能跑,但显存管理差一些,而且很多算子编译到一半报错,浪费时间。用 conda 建独立环境最稳:

conda create -n traffic python=3.8 -y conda activate traffic # 根据自己的 CUDA 版本选择对应的 PyTorch 安装命令 pip install torch==1.12.1 torchvision==0.13.1 --extra-index-url https://download.pytorch.org/whl/cu113 pip install -r requirements.txt # YOLOv5 仓库根目录下的依赖清单

这里最常翻车的是 PyTorch 和 CUDA 版本不匹配,装完 torch 后一import torch就报CUDA not available。先跑一句python -c "import torch; print(torch.cuda.is_available())"确认,返回 False 的话多半是 PyTorch 版本对应的 CUDA 和驱动不匹配,而不是显卡坏了。另一个坑是requirements.txt里部分包版本冲突,建议装完后单独验证 YOLOv5 的检测脚本能跑通一张图片再开始训练。

预训练权重选择上,无论如何都不要从零训练。用 YOLOv5s 的 COCO 预训练权重作为起点,收敛速度快,最终精度也更高。下载权重时如果网络不稳导致文件不完整,训练会直接报pickle.UnpicklingError,这种错误不用查代码,重新下载一次即可。

3.2 train.py 的八个关键参数:epochs、batch-size、img-size 怎么配合

YOLOv5 的训练入口是train.py,参数看似一堆,真正需要理解的不到十个。我把常用参数整理成表格:

参数推荐值说明
--epochs100~150用早停机制的话,100 轮足够收敛
--batch-size16~32根据显存调,8G 显存用 16,24G 用 32
--img640检测精度和速度的平衡点,别乱降到 320
--datadata.yaml 路径数据集配置,路径写绝对路径最省心
--weightsyolov5s.pt预训练权重,用 v8 的话换对应模型
--cacheram小数据集加速,数据集大就关掉
--workers4~8数据加载线程数,Windows 上设 0
--patience30验证指标 30 轮不提升就早停

实际训练命令大概是:

python train.py \ --data /home/user/traffic/data.yaml \ --weights yolov5s.pt \ --img 640 \ --batch-size 16 \ --epochs 120 \ --patience 30 \ --cache ram \ --workers 4 \ --device 0

data.yaml里的train和val路径写绝对路径是血泪经验。相对路径在 train.py 的当前工作目录下解析,一旦你用 nohup 或 systemd 方式在后台跑,工作目录一变就报文件找不到。--cache ram能显著加快训练,因为它把图片一次性加载进内存,但如果数据集超过 20G,内存不够会触发 swap,训练速度反而变慢,这时候改成--cache disk。--batch-size不是越大越好,显存占用是线性增长的,但梯度更新效果有边际效应,8G 显存强行开 32 会直接 OOM,然后掉进第 5 章的坑。

3.3 L2 正则化与数据增强:weight_decay、mosaic、mixup 怎么调

模型泛化能力靠两件事:正则化和数据增强。YOLOv5 的优化器默认使用 SGD,其中weight_decay参数就是 L2 正则化的实现方式。这个参数在源码里默认是 0.0005,含义是对权重矩阵的平方和施加惩罚,约束权重不要过大,从而抑制过拟合。如果你要在自己的 PyTorch 代码里显式控制,写法如下:

import torch.optim as optim optimizer = optim.SGD( model.parameters(), lr=0.01, momentum=0.937, weight_decay=5e-4, # L2 正则化系数,对应 YOLOv5 的 weight_decay nesterov=True, )

weight_decay调大(比如 1e-3)会让模型权重更小、泛化更好,但训练收敛会变慢;调小(比如 1e-5)则模型拟合能力变强,但过拟合风险上升。毕设数据集通常只有几千张图,建议保持默认 0.0005,不要乱动。

数据增强方面,mosaic 是 YOLOv5 最核心的增强策略,把四张图拼成一张训练,对小目标检测提升明显。但有一个细节:训练最后 10 个 epoch 会自动关闭 mosaic,因为拼接后的图片分布和真实场景不一致,末期关闭能让模型在真实分布上微调。如果你发现训练 loss 曲线在最后阶段异常上升,先检查是不是 mosaic 关闭导致的正常波动。mixup 增强是一种图像混合策略,毕设数据量不大时可以开启,数据量超过 1 万张再开,否则会引入太多噪声,模型学不到干净的边界特征。

3.4 训练过程监控与模型选择:loss 曲线、best.pt 和 last.pt 的区别

训练开始后不要干等,日志里每一轮都会打印box_loss、cls_loss、obj_loss和验证集的mAP@0.5。我看训练是否正常主要盯两个信号:box_loss 是否在前 20 轮内明显下降;mAP@0.5 是否持续上升。如果 box_loss 下降但 mAP 不动,大概率是数据标注有问题,比如类别错位或标注框偏移;如果 loss 在某个值附近震荡不降,可能学习率偏大,调低到 0.001 再试。

训练结束后runs/train/exp目录下会生成两个权重文件:best.pt和last.pt。best.pt是验证集指标最优的那一版权重,last.pt是最后一轮的权重。绝大多数情况下选best.pt,但有一种例外:验证集分布和实际部署场景差别大(比如验证集全是白天,实际演示在傍晚),这时候last.pt反而可能更适合现场。我自己习惯训练完用两套权重分别在实拍视频上跑一遍,肉眼对比检测框稳定性再定。

4. 从检测框到流量统计:车辆计数、跟踪与 Web 服务封装

4.1 虚拟线圈计数:用检测框中心点穿越虚拟线来判断

流量统计最常见的实现是虚拟线圈法。在画面中画一条横向虚拟线(比如 y=400),车辆检测框的中心点从线的上方穿越到下方,就记一次数。这个办法比“检测到车就计数”靠谱得多,因为车辆在画面中出现的位置有先后,中心点穿越虚拟线代表它真正通过了路口断面。

核心判断逻辑写成代码是:

def count_crossing(track_id, cx, cy, line_y, last_pos, counter): """ track_id: 车辆跟踪ID cx, cy: 当前帧检测框中心点 line_y: 虚拟线位置 last_pos: dict, 记录每个track_id上一帧的cy counter: dict, 记录每个track_id是否已计数 """ if track_id not in last_pos: last_pos[track_id] = cy return False prev_cy = last_pos[track_id] # 中心点从线上一侧穿越到另一侧 if prev_cy < line_y and cy >= line_y: if not counter.get(track_id, False): counter[track_id] = True last_pos[track_id] = cy return True # 中心点离开虚拟线一段距离后重置计数状态,防止原地抖动 if counter.get(track_id, False) and cy < line_y - 30: counter[track_id] = False last_pos[track_id] = cy return False

这段代码处理了两个实际问题。第一个是“一个 ID 只计一次数”,通过 counter 字典标记已计数状态;第二个是“防止在虚拟线附近来回抖动导致重复计数”,设定line_y - 30的滞回区间,车辆中心点必须完全越过线 30 像素以上才允许重置计数状态。这个滞后间隔是经验值,视频分辨率 1920×1080 时 30 像素合适;分辨率越低,这个值要相应调小。虚拟线的位置选画面中下部,太靠近画面边缘会出现车辆刚进入画面就被计数的误判。

4.2 ByteTrack 跨帧跟踪:track_thresh、match_thresh 与计数去重

虚拟线圈计数依赖稳定的跟踪 ID,否则上一小节代码里的counter字典就失去意义。ByteTrack 的推理逻辑比 DeepSORT 简洁:先用检测器输出所有框,高置信度框做一次 IoU 匹配,低置信度框在第一次匹配失败后做二次匹配。核心参数有两个:track_thresh控制检测框是否进入跟踪流程,默认 0.5;match_thresh控制 IoU 匹配阈值,默认 0.8。

参数调优的经验是:track_thresh从 0.5 降到 0.4,能把更多模糊的检测框纳入跟踪,减少漏计;但如果背景复杂,降得太低会把误检框也当成车辆,计数虚高。match_thresh从 0.8 升到 0.9,帧间匹配更严格,能减少遮挡导致的 ID 切换;但调太高会让车辆短暂遮挡后找不到匹配,ID 直接丢失。实际操作时,我的调参顺序是:先固定一个阈值,用一段 30 秒的实拍视频跑通流程,人工数一遍真实车流量,然后对比输出数字做微调。拿数据说话比理论推导可靠得多。

跟踪还有一个容易被忽视的问题:检测器对每帧单独推理,帧与帧之间的检测框会有抖动,导致同一辆车的中心点在虚拟线附近来回穿。ByteTrack 的 ID 稳定性能缓解这个问题,但无法完全消除,所以要配合上一小节的滞回区间逻辑一起使用。毕设论文里写清楚这套“检测-跟踪-计数”的三层配合关系,答辩时比单纯讲 mAP 更有说服力。

4.3 Flask 封装成 Web 服务:模型只加载一次,帧处理循环要放到独立函数里

系统演示环节,把模型封装成 Web 服务比命令行跑脚本更直观。常见做法是 Flask 提供两个接口:一个是 HTTP 上传视频文件,另一个是返回统计结果 JSON。工程上最容易犯的错是在每次请求里都重新加载模型。模型加载慢且耗显存,多请求并发时直接 OOM。正确做法是把模型加载放到全局,第一请求加载后常驻内存,后续请求直接推理:

from flask import Flask, request, jsonify import torch app = Flask(__name__) model = None def load_model(): global model if model is None: model = torch.hub.load('ultralytics/yolov5', 'custom', path='best.pt', force_reload=False) model.conf = 0.35 # 检测置信度阈值 model.iou = 0.45 # NMS IoU 阈值 return model def process_video(video_path, line_y): model = load_model() tracked = {} # track_id -> (last_cy, counted) counts = {"car": 0, "bus": 0, "truck": 0, "pedestrian": 0} cap = cv2.VideoCapture(video_path) fps = cap.get(cv2.CAP_PROP_FPS) frame_interval = max(1, int(fps // 10)) # 每秒处理约10帧,降低计算压力 frame_idx = 0 while True: ret, frame = cap.read() if not ret: break if frame_idx % frame_interval != 0: frame_idx += 1 continue results = model(frame) dets = results.pandas().xyxy[0] # x1,y1,x2,y2,confidence,class for _, det in dets.iterrows(): cx = (det['xmin'] + det['xmax']) / 2 cy = (det['ymin'] + det['ymax']) / 2 # 此处调用 ByteTrack 的 update 获取 track_id track_id = tracker.update(det) if count_crossing(track_id, cx, cy, line_y, last_pos, counter): counts[det['name']] += 1 frame_idx += 1 cap.release() return counts @app.route('/analyze', methods=['POST']) def analyze(): f = request.files['video'] video_path = os.path.join('uploads', f.filename) f.save(video_path) line_y = int(request.form.get('line_y', 400)) result = process_video(video_path, line_y) return jsonify(result) if __name__ == '__main__': app.run(host='0.0.0.0', port=5000)

这段代码第 17 行的frame_interval是性能调优的关键:流量计数不需要每帧都做检测,每秒处理 10 帧已经足够,能把视频处理速度提升两倍以上。line_y通过请求参数传入,前端页面可以加一个滑条让用户调整虚拟线位置,这比写死在代码里更实用。模型推理时建议包一层torch.no_grad(),显式关闭梯度计算,能省出不少显存。

5. 毕设排坑记录:数据、训练、部署三个阶段的常见问题排查

5.1 训练 loss 一开始就是 nan:标签坐标越界或学习率过高

现象:train.py 在第一个 epoch 就打印出nan,随后所有 loss 和指标全部失效。
原因:最常见的是标注坐标越界,比如归一化后的 cx 或 cy 大于 1,模型计算的损失直接爆炸;其次是学习率设得过高,SGD 在前几步就发散。
解决:先在数据集上跑一个检测脚本,把每张图的 txt 标签和原图画在一个窗口里,肉眼扫一遍,看有没有框超出画面边界。如果是坐标越界,问题大概率出在 VOC 转 YOLO 脚本没有做边界截断。学习率过高的话,把--lr从默认的 0.01 临时降到 0.001 验证,如果 loss 恢复正常,说明是发散,可以改回 0.01 配合 warm-up 再试。

5.2 mAP 很高但实拍视频检测掉点:训练分布和现场分布不一致

现象:验证集上 mAP@0.5 达到 0.85,但拿手机去路口拍的视频一测,车辆漏检严重,尤其在夜间。
原因:训练集来自公开数据集,和现场视频的光照、机位、场景风格差异太大,模型学到的是公开数据集的分布,不是现场分布。
解决:从现场视频里抽 200~500 帧,标注后混入训练集做微调。如果时间来不及标注,可以先用训练好的模型跑一遍现场视频,挑出置信度低于 0.3 的帧,人工手动修正标注,这种方式比从零标注快很多。夜间场景实在没有标注条件,就用数据增强里的亮度、对比度随机扰动模拟低光环境。

5.3 计数结果比人工统计多一倍:跟踪 ID 切换导致同一辆车被重复计数

现象:系统统计的车流量比人工数多了 80%~120%,集中在拥堵或车辆遮挡较多的时段。
原因:车辆互相遮挡时,检测框短暂丢失,跟踪 ID 断裂,车辆重新出现后拿到新 ID,被虚拟线圈当成另一辆车再次计数。
解决:三个方向同时处理。第一,把track_thresh降到 0.4,让低置信度检测框也能参与跟踪,减少 ID 断裂;第二,在计数逻辑里加入“最小连续帧数”条件,同一 ID 的视频帧连续出现 3 帧以上才允许计数;第三,调整虚拟线位置,放到车辆较少发生遮挡的画面区域。先做后两条,性价比最高。

5.4 CUDA out of memory:batch-size 过大或 cache 设置不当

现象:训练跑了 20 个 epoch 后报CUDA out of memory,或者刚启动就报错。
原因:PyTorch 的显存分配有缓存机制,训练后期某些中间变量的显存占用会波动,前期刚好能跑,后期峰值一上来就爆。
解决:把--batch-size减半是最快的解法,不用动其他参数。--cache ram改成--cache disk能省掉一部分显存用于存储数据缓存。如果问题依旧,在训练脚本里加一行torch.cuda.empty_cache()放到每个 epoch 结束后,强制释放没用的缓存。注意这不是在掩盖问题,是把 PyTorch 的显存碎片整理掉。

5.5 无显卡环境部署:CPU 推理太慢,视频处理一小时还没跑完

现象:答辩现场的演示机没有独立显卡,用 CPU 推理,1080P 视频跑完 5 分钟内容需要 40 多分钟。
原因:YOLOv5s 在 CPU 上的 FP32 推理速度只有 2~3 FPS,处理 5 分钟视频的几千帧画面自然慢得离谱。
解决:先用轻量模型替代,yolov5n比yolov5s的参数量少约一半,CPU 推理速度能提到 8~10 FPS;再把输入尺寸从 640 降到 480,速度进一步提升;如果还嫌慢,就把视频抽帧间隔放大到每秒 5 帧。这几招对计数准确率的影响在 10% 以内,但对演示效率的提升是数量级的。

6. 答辩加分与进阶验证:Grad-CAM 可视化与消融实验怎么补

6.1 Grad-CAM 可视化:让检测模型不再是黑匣子

答辩时导师最爱问的问题是“模型为什么认为这是车”。Grad-CAM 能回答这个问题:它把模型最后一层特征图的梯度加权求和,生成一张热力图,叠加在原图上,红色区域就是模型分类决策的主要依据。实现上,PyTorch 里用 hook 注册前向和反向钩子,捕获特征图与梯度,然后计算加权平均。代码如下:

from torchvision.transforms.functional import to_pil_image import torch def grad_cam(model, input_tensor, target_class): features = {} gradients = {} def forward_hook(module, input, output): features['value'] = output def backward_hook(module, grad_input, grad_output): gradients['value'] = grad_output[0] # 以 YOLOv5 的 backbone 最后一层为例 target_layer = model.model.model[-2] hook_f = target_layer.register_forward_hook(forward_hook) hook_b = target_layer.register_backward_hook(backward_hook) output = model(input_tensor) model.zero_grad() output[0, target_class].backward() hook_f.remove() hook_b.remove() weights = gradients['value'].mean(dim=(2, 3), keepdim=True) cam = (weights * features['value']).sum(dim=1, keepdim=True) cam = torch.relu(cam) cam = cam / (cam.max() + 1e-6) return to_pil_image(cam[0].cpu())

这段代码的预期效果是热力图主要集中在车辆目标的车身轮廓上,而不是背景。如果热力图散落在背景区域,说明模型学到了错误的上下文线索,这时候要回头检查数据集的标注质量和增强策略。生成几张有代表性的热力图放进论文附录,答辩时展示“可视化结果与语义一致”,比空口说“模型效果不错”有说服力得多。

6.2 消融实验表:mAP、FPS 与计数准确率的对比口径

答辩委员会最买账的是消融实验。至少跑三组实验:baseline(预训练权重直接微调)、加数据增强(mosaic、mixup、HSV 扰动)、加跟踪计数逻辑(ByteTrack + 虚拟线圈)。指标要覆盖检测和计数两个维度,mAP 只代表检测,不代表流量统计准。

一个可参考的表格格式:

实验设置mAP@0.5推理 FPS计数准确率
Baseline(YOLOv5s 微调 100 epoch)0.8162未接入计数
+ 数据增强(mosaic + mixup)0.8462未接入计数
+ ByteTrack + 虚拟线圈0.845591.4%

第三行的计数准确率要说明统计口径:人工数一遍演示视频的真实车流量,再用系统跑一遍,准确率 = 1 - abs(系统值 - 人工值) / 人工值。这个数字比 mAP 更能体现系统价值,也是毕设论文的核心数据。注意推理 FPS 在接入跟踪后会下降,因为跟踪模块本身就消耗计算资源,这是正常现象,别觉得是性能退化。

6.3 时序演示技巧:30 秒视频比千字描述更有说服力

答辩演示环节,我建议准备一段 30 秒的合成视频:左边是原始路口画面,右边是检测框 + 虚拟线 + 实时计数的渲染画面,右下角放一个统计面板,显示各类型车辆的累计数量。选视频时故意挑一段有车辆遮挡、有行人横穿的素材,让评委看到模型在复杂场景下的表现。即使模型偶尔漏检一两次,也比全程无挑战性的素材更有讨论空间。

我自己的经验是,做毕设这类系统最耗时间的往往不是模型训练,而是数据清洗和计数后处理。把虚拟线圈逻辑、跟踪去重逻辑、可视化界面这三块先做扎实,再回头优化模型精度,整个项目的完成度和答辩表现都不会差。希望帮到你。

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

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

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

立即咨询