简介:面向计算机专业毕业设计的基于深度学习的交通流量检测系统项目,覆盖从数据采集、预处理到模型构建、训练评估及系统集成的完整技术链路。压缩包内文件总数达两千个,其中一千四百余个JavaScript脚本承载前端交互与核心逻辑,四百余篇Markdown文档提供开发笔记与算法讲解,一百七十余个JSON配置存储模型参数与运行设置,另有HTML和CSS文件搭建可视化监控界面,便于实时查看检测结果,整体大小约一百三十MB。项目融合CNN、RNN、LSTM等网络结构,可实现车辆检测、流量计数与交通状况实时预测,并涉及数据增广、优化器选择、超参数调优及GPU加速等工程环节。已有六百六十六人学习使用,适合需要完整毕业设计参考的学生,可直接在已有代码基础上扩展模型或替换数据集进行复现实验。
1. 拿到这份“交通流量检测系统.zip”:先想清楚里面该有什么
拿到这个毕业设计交通流量检测系统.zip 时,第一件事不是急着解压,而是想清楚里面应该有什么。我带过不少做深度学习毕设的学生,最容易答辩翻车的不是模型精度不够,而是整个系统只有一堆训练代码和权重文件,没有演示界面、没有说明文档、没有可复现的数据集,评委一打开就冷场。这个方向解决的是一个问题:用深度学习算法把视频里的车辆和行人识别出来,实时统计车道流量,最后落成一个能演示、能被追问的完整系统。适合正在做毕设或课设、需要一个能跑通全链路参考方案的从业者。解压前,先给自己定三个验收标准:模型能跑、界面能开、答辩能答。
2. 把毕设需求拆成技术选型:检测任务、算法与算力
2.1 交通流量检测是检测任务,不是分类任务
很多第一次做深度学习项目的同学,会把交通流量检测理解成“给一张图判断有没有车”,这是分类任务的思路。但实际场景里,一张路口视频帧里可能同时有十几辆车和行人,你需要知道每一辆车在哪、属于什么类型、从哪个车道经过,才能统计流量。这本质上是目标检测任务:输出是每个目标的边界框坐标、类别和置信度,而不是一个简单的标签。
整个系统的工作流可以拆成四步:读取视频帧、检测车辆和行人、按车道区域判断目标归属、按时间窗口累加流量。检测模型承担的是第二步里最核心的“找目标”工作,后面三步都是围绕检测结果做的工程化处理。也就是说,检测模型的精度和速度直接决定了整个系统能不能用,而流量统计逻辑反而是相对简单的部分。
这个链路是深度学习入门项目里比较标准的实战项目案例,难点不在理论,而在把模型、数据、接口串起来。做毕设的时候,论文的“系统设计”部分也基本是照着这条链路写,先总体框架,再分模块讲检测、计数、可视化。
2.2 检测算法选型:为什么默认选 YOLO
检测模型的选型决定了后面所有工作的难度,这里务必想清楚。常见的候选有三类。
| 方案 | 精度 | 推理速度 | 端到端工程复杂度 | 毕设友好度 |
|---|---|---|---|---|
| Faster R-CNN 两阶段 | 高 | 慢(不适合视频实时) | 高,依赖组件多 | 中 |
| SSD 单阶段 | 中 | 快 | 中 | 中 |
| YOLO(v5/v8 系列) | 高 | 快 | 低,生态完整 | 高 |
我一般会直接推荐 YOLO 系列,原因有三。第一,它在速度和精度上平衡得最好,视频流推理是刚需,两阶段检测器在普通的笔记本上很难跑到实时。第二,YOLO 的开源生态太完整了,从训练到部署都有现成的命令行工具,这对毕设周期来说非常关键。第三,论文好写,因为 YOLO 的改进版本多,你可以基于 baseline 做消融实验或者小改进,这是毕设评审最喜欢的素材。
具体选哪个版本,以 2025 年这个时间点来看,YOLOv8 的集成度高,训练、验证、推理、导出一条命令走完,适合时间紧的同学。如果配置老一些,YOLOv5 也完全够用,网上能查到的资料最全。不建议从零手写网络结构或者用太冷门的模型,毕设的核心是“做出一个可用的系统”,不是重新发明检测器。
2.3 算力评估与深度学习环境配置:先看机器再定方案
深度学习环境配置是劝退率最高的一步,但也是最能提前规避的一步。动手前先看清楚手头机器的显卡和显存,这决定了你能跑训练还是只能做推理。
- 6GB 以下显存:选 YOLOv8n 或 YOLOv5s,图片尺寸降到 640,batch size 设 8 以内。
- 8GB 到 12GB 显存:选 YOLOv8s,batch size 设 16,基本够用。
- 没有独立显卡:不建议在本机训练,用云 GPU 平台跑训练,本机只做推理演示。
训练和推理可以分离。很多人以为必须有一张好显卡才能做这个毕设,其实不然。训练是离线过程,慢一点没关系;答辩现场是推理过程,用 CPU 跑 YOLOv8n 也能达到每秒几帧的检测速度,配合跳帧策略足够演示。
环境配置有一个清单值得抄下来对照检查:Python 3.9 以上、PyTorch 2.x、CUDA 与 cuDNN(无 GPU 可跳过)、ultralytics 包、opencv-python。安装顺序不要乱,先装 PyTorch 再装 ultralytics,否则可能出现 torch 版本不匹配导致模型加载失败。装完后跑一句yolo detect predict source=https://ultralytics.com/images/bus.jpg验证环境,能出结果说明环境没问题,后面就不用再折腾。
3. 跑通最小训练闭环:从 VOC 标注到 YOLO 格式的转换脚本
3.1 数据集从哪来:公开数据集与自建标注的取舍
训练一个可用的检测模型,数据质量比模型结构更重要。公开数据集里,交通场景优先考虑这三个:UA-DETRAC 是专门的车辆检测追踪数据集,BDD100K 有大量真实道路视频帧,Cityscapes 覆盖复杂的城市场景。选数据集时注意两点:一是类别命名要和你的需求对齐,二是照片分辨率要接近你的实际场景。
如果说公开数据集拿来做毕设显得太“公用”,你也可以自建数据集。常见做法是用 FFmpeg 按一定间隔从视频里抽帧,然后逐帧标注。间隔帧数没有固定标准,一般来说场景变化快的路口每 5 帧抽一帧,场景单调的高速路每 15 到 20 帧抽一帧。抽完帧后用 LabelImg 或 Roboflow 标注,标注的类别建议控制在 6 类以内:car、bus、truck、motorbike、bicycle、person。类别越多,标注工作量越大,模型收敛越慢,答辩时被问住的概率也越高。
关于数据量,每类至少 300 到 500 个实例是底线,整个数据集 2000 到 3000 张图比较合适。图像太少,模型的泛化能力会很差;图像太多,标注时间会拖垮整个毕设进度。一个折中的做法是先用 80% 公开数据加 20% 自采数据混着训练,这样既有真实感,又不至于累死。
3.2 把 VOC 标注转成 YOLO 格式:转换脚本与坐标归一化
标注工具导出的常见格式是 VOC XML,但 YOLO 训练需要的是每个图片对应一个同名 txt 文件,每行表示一个目标,格式是“类别id x_center y_center width height”,全部归一化到 0 到 1 之间。这个转换写起来不难,但坑实在不少,我直接给一段可以按需改的脚本。
import xml.etree.ElementTree as ET import os classes = ["car", "bus", "truck", "motorbike", "bicycle", "person"] def xml_to_yolo(xml_path, out_dir, img_w, img_h): tree = ET.parse(xml_path) root = tree.getroot() file_name = os.path.splitext(os.path.basename(xml_path))[0] with open(os.path.join(out_dir, file_name + ".txt"), "w") as f: for obj in root.findall("object"): cls = obj.find("name").text if cls not in classes: continue cls_id = classes.index(cls) box = obj.find("bndbox") xmin = float(box.find("xmin").text) ymin = float(box.find("ymin").text) xmax = float(box.find("xmax").text) ymax = float(box.find("ymax").text) x_center = ((xmin + xmax) / 2) / img_w y_center = ((ymin + ymax) / 2) / img_h w = (xmax - xmin) / img_w h = (ymax - ymin) / img_h # 防止标注越界导致训练时崩溃 x_center = max(0, min(1, x_center)) y_center = max(0, min(1, y_center)) w = max(0, min(1, w)) h = max(0, min(1, h)) f.write(f"{cls_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}\n")这段脚本的逻辑就是解析 XML 里的<bndbox>四个坐标值,算出中心点坐标和宽高,再分别除以图片宽高完成归一化。有三个参数需要特别注意:img_w和img_h必须和实际图片尺寸一致,否则所有框的位置都会偏移;classes列表的顺序一旦确定就不能再改,因为训练时是按索引读的,中间插一个类别会导致所有后续类别全部错位;归一化后做了 clip 到 0 到 1 的处理,避免标注框超出图片边界导致训练报错。
转换完记得随机抽查几个 txt 文件,打开看每一行的前两个数是否都在 0 到 1 之间。如果出现负数或者大于 1 的值,说明原 XML 里标注框本身就超出了图片范围,需要回标注工具里修正,不要指望脚本帮你兜底。
3.3 标注质量检查与数据集划分
转换完格式不等于数据就能用了,还差两道检查工序。第一道是可视化检查:随便挑几十张图,把 txt 里的坐标还原成矩形画在原图上,肉眼确认框是否贴边、是否漏标。这一步能用一段简单的 OpenCV 脚本批量完成,也可以直接看图。第二道是类别统计:数一数每个类别在训练集里出现多少次,如果 car 有 5000 个而 bus 只有 100 个,模型几乎必然会把 bus 漏掉。
针对类别不均衡,可以做的处理有两层。数据层面加一些 bus 样本的复制粘贴增强,或者干脆多找点公交车图片补标;训练层面可以把少样本类别的权重调高一点。不过对毕设来说,最省力的是标数据时就有意识多收集少样本类别的图片,而不是事后补救。
数据集划分上,按 7:2:1 切成三份:训练集、验证集、测试集。注意测试集一定要和训练集完全隔离,不能有同一段视频里抽出的相邻帧同时出现在两个集合里。这个踩坑很常见,因为视频相邻帧极其相似,会让验证分数虚高,答辩时一换真实视频立刻露馅。
4. 训练与验证:参数怎么定、曲线怎么看、指标怎么算
4.1 用一个最小训练命令跑通全流程
数据和环境都准备好后,先别急着调参,用最小的命令把流程跑通。下面是一个标准的 YOLOv8 训练命令。
yolo detect train data=traffic.yaml model=yolov8n.pt \ epochs=100 imgsz=640 batch=16 device=0data=traffic.yaml指向你的数据集配置文件,里面写明图片路径和类别名称;model=yolov8n.pt表示用预训练权重作为起点,比从零训练收敛快得多,毕设场景不要从零开始训;epochs=100是训练轮数,交通场景一般 80 到 120 轮足够;imgsz=640是输入图片尺寸,越大精度越高但显存占用也越大;batch=16是每轮批大小;device=0指定用第一张 GPU,没有 GPU 就改成device=cpu,但训练会非常慢。
对应的traffic.yaml内容如下。
path: ./traffic_dataset train: images/train val: images/val names: 0: car 1: bus 2: truck 3: motorbike 4: bicycle 5: person这里path是数据集根目录,train和val是相对于根目录的图片文件夹路径,names用列表形式给出类别名,索引顺序必须和第 3 章转换脚本里的classes顺序一致。训练启动后,ultralytics 会在runs/detect/train目录下保存权重、日志和验证结果图。
训练过程中需要盯住的只有一件事:耐心。前 10 轮 loss 可能波动很大,不用理会;50 轮以后如果还在持续下降,就接着训;如果验证集的 loss 开始回升而训练集 loss 还在降,就是过拟合的信号,应该提前停止。
4.2 训练曲线怎么看:别只看总 loss
训练结束后打开runs/detect/train下的results.png,一行一行看曲线,这里最容易暴露问题。
| 曲线 | 正常趋势 | 异常信号 |
|---|---|---|
| train_loss 总损失 | 整体下降后趋平 | 不降反升或剧烈震荡 |
| val_loss 验证损失 | 先降后平 | 中途反弹说明过拟合 |
| box_loss 框位置损失 | 下降后平缓 | 不平说明定位不稳 |
| cls_loss 分类损失 | 下降后平缓 | 为 0 说明模型在偷懒 |
| mAP@0.5 | 上升后趋平 | 徘徊不动检查数据 |
| mAP@0.5:0.95 | 略低于 mAP@0.5 | 差距大说明定位精度不足 |
可以参考的指标主要有三个。mAP@0.5是 IoU 阈值 0.5 下的平均精度,这个值在交通场景里做到 0.8 以上就算不错。mAP@0.5:0.95是更严格的指标,对定位精度更敏感,毕设论文里两个都写,显得专业。Precision 和 Recall 是一对矛盾指标,P 高 R 低说明模型保守,宁可漏检也不误检;R 高 P 低说明模型激进,经常把背景当目标。流量检测场景漏检比误检更影响计数准确性,所以一般倾向把置信度阈值调低一点,提高 Recall。
除了results.png,还可以用 TensorBoard 看更细的日志,ultralytics 训练时会自动记录。启动方式是在项目目录下执行tensorboard --logdir runs,然后浏览器打开显示的地址。TensorBoard 可以按轮次查看每一类的 P 和 R 曲线,排查具体哪个类别拖后腿。
4.3 用测试视频跑一遍推理验证效果
训练完成后拿出测试集视频跑一次推理,这一步既是验证也是答辩素材。
yolo detect predict model=runs/detect/train/weights/best.pt \ source=test_video.mp4 conf=0.25 save=Truemodel指向训练得到的权重文件,注意用best.pt不用last.pt;source是测试视频路径,也可以换成图片文件夹;conf=0.25是置信度阈值,低于这个值的结果会被过滤掉;save=True表示把标注后的结果保存下来。跑完后在runs/detect/predict目录下会生成带框的视频,这个视频就是答辩现场的核心演示材料。
如果发现视频里漏检多,先降conf到 0.15 再看看;如果发现误检多(路牌、树影被框出来),就升到 0.4 左右。置信度阈值是最直接的调节旋钮,不用重新训练。另外,save_txt=True可以额外保存每个目标的坐标和置信度到 txt 文件,做流量统计时需要用到这个结果。这个功能在做后期车辆计数时非常有价值,可以提前把检测结果缓存下来,方便复盘。
5. 毕设最常翻车的五个地方:一套踩坑记录
5.1 解压后文件乱码或伪加密:先解决“打不开”再谈训练
现象:zip 文件解压后,文件夹结构散了,文件名出现乱码,或者解压时弹出要输入密码,但工程本身并没有加密。
原因:这是 zip 压缩包的编码和标志位问题。Windows 下用旧版压缩工具制作 zip 时,中文文件名用的是系统本地编码,而非 UTF-8,解压工具默认按 UTF-8 解析就乱码了。所谓伪加密,是 zip 文件头里加密标志位被置位,但文件数据实际并未加密,常见于压缩工具兼容性问题。
解决:换用 7-Zip 或 Bandizip 这类对编码兼容更好的工具,解压时在选项里选择“强制使用 UTF-8 文件名”。如果确认是伪加密,7-Zip 能识别并正常解出文件;如果是真加密且不知道密码,系统做的是做不到“移除密码”的,先联系文件作者确认密码,或者在原始课程设计环境里重新导出工程。做完毕设的压缩包建议用英文文件名重新打包,能省掉答辩换电脑时解压乱码的麻烦。
5.2 训练一启动就报 CUDA out of memory
现象:训练命令刚跑几秒,终端直接报CUDA out of memory,GPU 显存被耗尽。
原因:最常见的情况是 batch size 设置过大,或者是 YOLOv8 的 cache 参数默认把数据缓存到显存导致占用飙升。另外,如果开了多个程序占用显存没释放,也会直接触发。
解决:把batch从 16 降到 8 或 4;imgsz从 640 降到 512;加上cache=False参数强制不对数据做显存缓存;关掉其他占显存的进程。如果这些都做了还是不够,说明显卡实在带不动当前模型规格,换yolov8n是最小权重模型,再不行就用云 GPU 平台。
5.3 车辆密集时远距离小目标漏检严重
现象:mAP 数值看着不错,但放到真实路口视频里,远处的车辆和行人几乎全部漏检,而且密集车流中框会乱跳。
原因:小目标在图片里占的像素太少,模型下采样后特征丢失严重。如果训练集里远距离样本占比不多,模型会直接放弃学习这类目标。还有一个因素是 NMS 的非极大值抑制,密集车辆重叠时 IoU 过高,后出现的车辆被抑制掉了。
解决:采样时多加入远距离和俯拍视角的视频帧;数据增强里把 mosaic、旋转、缩放的比例调高;推理时把nms_iou从默认的 0.7 调整到 0.4 到 0.5 之间,减少重叠框互相抑制。如果对极远距离的目标有强需求,可以对输入图片做切片推理,把大图切成四块分别检测再合并结果,代价是推理时间翻倍。
5.4 训练正常但推理结果全是空框或类别错乱
现象:训练时 loss 下降得很漂亮,验证集 mAP 也高,但实际推理时输出的框全空,或者把行人识别成自行车、把卡车识别成汽车。
原因:最隐蔽的原因是类别索引错位。如果你训练前修改过classes列表的顺序,而模型权重是在旧顺序下训练的,那么新推理时调用的模型还是会按旧索引输出,导致类别标签对不上。其次,训练时图片尺寸是 640,推理时输入尺寸如果设置成 1280,目标框的坐标会被拉伸错位。
解决:重新训练前,确认data.yaml的names顺序和训练脚本、推理脚本完全一致,改任何类别顺序都必须重训。推理时强制固定imgsz,与训练设置保持一致。调试时先把conf降到 0.1,把所有低置信度的检测结果都输出,看看是不是单纯的阈值问题,而不是模型本身坏了。
5.5 答辩现场视频推流卡顿怎么办
现象:现场演示视频时,画面一卡一卡地跳,检测框跟不上画面里车的移动。
原因:视频读取线程和检测线程速度不匹配,OpenCV 读取视频帧的速度远快于检测速度,积压的帧越来越多,最后画面播放像幻灯片。真实的网络视频流场景下,推流和拉流的缓冲机制也会放大卡顿问题。
解决:三个手段按顺序用。第一是跳帧检测,每隔 2 到 3 帧检测一次,中间帧沿用上一次检测结果,视觉观感影响很小。第二是把预读队列长度限制住,检测完一帧再读下一帧,宁肯帧率低也不积压。第三是调低输入分辨率,把 1080p 的视频缩到 640 宽再推理,检测速度能翻倍。答辩前的演示视频建议提前用固定跳帧策略录好一份,现场直接用录好的视频播放,再把实时推理作为加分项展示,不要赌现场网络的稳定性。
6. 交付前的最后一段路:Flask 接口与验收自检表
6.1 用 Flask 把检测模型包成一个能演示的接口
训练和推理跑通只完成了一半,毕设交付需要有一个“系统”的样子。最省事的做法是写一个 Flask 接口,把模型封装成 HTTP 服务,前端页面或者手机发一张图片过来,后端返回检测结果和数量。这个接口在答辩时可以直接用浏览器演示,比当场敲命令行观感好得多。
from flask import Flask, request, jsonify import cv2 import numpy as np from ultralytics import YOLO app = Flask(__name__) model = YOLO("best.pt") @app.route("/detect", methods=["POST"]) def detect(): file = request.files["image"] img = cv2.imdecode(np.frombuffer(file.read(), np.uint8), cv2.IMREAD_COLOR) results = model(img, conf=0.3) count = len(results[0].boxes) boxes = [] for box in results[0].boxes: x1, y1, x2, y2 = map(int, box.xyxy[0].tolist()) cls_id = int(box.cls[0]) boxes.append({"bbox": [x1, y1, x2, y2], "class": model.names[cls_id]}) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.imwrite("result.jpg", img) return jsonify({"count": count, "boxes": boxes}) if __name__ == "__main__": app.run(host="0.0.0.0", port=5000)这段代码把模型加载做在服务启动阶段,避免每次请求都重新加载权重;results[0].boxes取第一张图的所有检测框;model.names[cls_id]把类别 id 转成可读名称,这个映射直接来自训练时的 yaml 配置。接口返回 JSON,里面有总数量和每个框的坐标、类别,前端拿到数据后可以自己绘制,也可以直接用后端画好框的 result.jpg。
视频流场景可以在这个接口基础上扩展,把输入从单张图片改成视频帧序列,每处理一帧返回一次计数结果。对毕设来说,先保证单帧接口是通的,再谈视频流。
6.2 验收自检表:答辩前按这个清单过一遍
| 自检项 | 检查方法 | 通过标准 |
|---|---|---|
| 模型单帧推理 | 随便找一张测试图跑 predict | 框位置正确、类别无错乱 |
| 视频流处理 | 录一段 30 秒路口视频测试 | 检测不崩、计数误差可控 |
| Flask 接口 | 用 curl 或浏览器 POST 图片 | 返回 JSON、result.jpg 有标注框 |
| 数据集预案 | 检查测试集是否与训练集隔离 | 类别索引一致、无相邻帧混入 |
| 失败问答预案 | 准备 3 个模型失效案例 | 能解释原因并给出改进方向 |
这五条里,前三条是功能底线,后两条是答辩防线。我自己的习惯是,答辩前两周把测试视频换成一组完全没见过的真实路口录像,跑一遍看效果;如果效果崩了,降conf或者重训,而不是纠结论文里写的 mAP 数字。这套流程做完,模型、接口、视频、问答都有了,系统才算真正闭环。希望这个方向和这些具体做法能帮到你,少走点我当年走过的弯路。
本文还有配套的精品资源,点击获取