ARTICLE DETAIL

资讯详情

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

YOLOv8道路车流量检测系统:Python实现与计数实战

YOLOv8道路车流量检测系统:Python实现与计数实战 简介一套基于YOLOv8算法的道路车流量检测系统以Python语言实现面向毕业设计、智能交通与城市车辆统计等场景可用于实时车辆识别、计数与流量分析。压缩包共307个文件约155.1MB其中126个Python源码和125个pyc编译文件构成主体37个YAML文件负责模型与训练配置另含pt/onnx模型文件、测试视频、示例图片及标注数据覆盖从数据准备到模型推理的完整流程。系统附带训练好的模型和数据集可直接复现检测效果免去从零搭建与训练的繁琐过程代码结构清晰、注释详尽便于理解YOLOv8的训练流程、参数调优与部署思路。已有88人学习下载适合需要快速落地车流量检测方案的研究者、开发者及毕业设计学生可有效缩短开发周期并提升项目成功率。1. 拿到这套 YOLOV8 道路车流量检测系统后第一晚该做什么把一段路口监控视频拖进命令行几秒后 car、bus、truck 的检测框直接显示在画面上每一帧的坐标和置信度同时落盘——这套基于 Python 和 YOLOV8 的道路车流量检测系统给人最直接的印象就是“不用白手起家”。很多人做这个项目卡得最死的往往不是算法而是数据要自己抽帧、自己标注、自己训练一轮训练可能等一个晚上。标题里最值钱的三块它都带了完整代码、训练好的模型、可以继续用的数据集。你拿到手就可以马上验证推理效果再按自己的需求改计数逻辑。适合交通工程从业者、智慧城市方向的开发者和相关专业研究生。我会按自己拿到这种项目后的习惯路径来讲从拆目录、跑通推理到实现计数、排查问题再到微调和验证照着做就能在自己的机器上复现。2. 拆目录与模型文件这套系统不是黑匣子2.1 一份典型的道路车流量检测工程目录拿到项目压缩包先别急着装环境用 tree 或文件管理器扫一遍目录结构。常见的工程布局大致是这样traffic-flow/ ├── data/ # 数据资产训练图片、标注、测试视频 │ ├── images/ │ │ ├── train/ │ │ └── val/ │ ├── labels/ # YOLO 格式的 txt 标注 │ ├── traffic.yaml # 数据集配置文件 │ └── demo.mp4 # 可直接推理的测试视频 ├── models/ │ ├── yolov8n.pt # 官方预训练基线用来做效果对比 │ └── best.pt # 在交通数据集上训练出来的权重 ├── scripts/ │ ├── detect.py # 图片/视频检测入口 │ ├── count.py # 车流量计数主程序 │ ├── train.py # 训练/微调入口 │ └── utils/ │ ├── tracker.py # 跟踪器常用 ByteTrack封装 │ └── zone.py # 虚拟线圈 / 区域定义 └── requirements.txt # Python 依赖清单这个结构的核心思想是“数据、模型、逻辑”三块分离。data 目录保存原料models 目录保存训练产物scripts 目录保存人写的控制逻辑。检测和计数分开是因为它们是两个层面的问题检测解决“画面里哪有车”计数解决“这辆车算没算过”。后面改计数规则时不需要动模型改模型时也不需要重写计数逻辑这对后续迭代很重要。实际拿到手的目录名可能不叫 traffic-flow但只要你看到类似 images、labels、best.pt、detect.py 这几个关键字就可以按这个对应关系快速定位文件。requirements.txt 也值得先看里面记录的依赖版本决定你是否需要额外补装某个 python 包。很多翻车事故都源于依赖版本不一致比如 numpy 2.0 和部分旧版 opencv 的兼容问题一开始就把版本锁好能省掉大量排查时间。2.2 数据集的标注格式认识 YOLO 的 txt 标注车流量项目的训练数据最常用的是 YOLO 格式也就是每个图片对应一个同名 .txt 文件。一行代表一个目标五个字段0 0.4832 0.5128 0.1048 0.1281 1 0.6100 0.7140 0.1830 0.2260第一个数字是类别编号后面四个依次是归一化后的中心点 x、中心点 y、宽度、高度取值都在 0 到 1 之间。比如 0.4832 表示目标中心位于图片水平方向的 48.32% 处。使用归一化坐标的好处是不管训练时分辨率是 640 还是 1280标注文件都不用改模型内部会做缩放对齐。类别编号不是随意的它对应数据集配置文件里的 names 列表。常见的交通数据集配置文件大概长这样train: data/images/train val: data/images/val nc: 4 names: [car, bus, truck, motorcycle]训练时模型会把“car”当成 0 类、“bus”当成 1 类依此类推。如果你自己补充数据label 文件里类别编号必须沿用这个顺序否则会出现“车被当成卡车”这类错乱。还有一个小坑这个 yaml 文件里的路径是相对项目根目录写的如果把数据集单独拷贝到别的地方训练会直接报 “dataset not found”。优先保证 images 和 labels 的子目录层级原样保留。2.3 权重文件格式best.pt 和官方预训练模型的区别目录里通常有两个 .pt 文件一个是官方下发的 yolov8n.pt另一个是数据训练后保存的 best.pt。这两个东西别混用。yolov8n.pt 是 Ultralytics 在 COCO 上预训练的通用权重识别 80 类但没专门针对交通视角优化best.pt 是在本项目交通数据上继续训练出来的类别通常只有 car、bus、truck、motorcycle 几种。推理时优先用 best.pt。从文件本身看.pt 是 PyTorch 的权重文件它不只存了参数还带网络结构、训练超参数、类别名等元信息因此可以用 ultralytics 直接加载也能继续做迁移学习。加载之前我习惯先用一段短代码确认模型暴露出来的类别顺序from ultralytics import YOLO model YOLO(models/best.pt) print(model.names) # 期望输出 {0: car, 1: bus, 2: truck, 3: motorcycle}这段代码的作用是在跑长视频之前快速确认两件事模型文件路径是否正确以及训练时定义的类别顺序是否符合预期。如果输出结果里混进了 person、bicycle 之类的不相关类别说明权重文件很可能不是交通场景专用版本需要回去核对。真正部署时很多人会先用 export 转成 .onnx或者进一步转成 TensorRT 的 .engine。几个常见格式的差别可以看这张表格式加载方式能否继续训练常用场景.ptYOLO(best.pt)可以实验验证、微调、二次开发.onnxonnxruntime 推理一般不用跨平台部署、边缘设备转换.engineTensorRT 加载不可以GPU 服务器高吞吐推理如果你打算把系统部署到 RK3588 这类带 NPU 的边缘设备上常规路线就是先把 .pt 导出成 .onnx再用对应工具链量化成 INT8。这个过程会损失少量精度但能换来明显更低的推理延迟。模型文件这部分不需要有太多“玄学”核心判断就一句话想在电脑上复现效果直接加载 best.pt想上线到边缘设备再考虑导出 onnx。3. Python 环境与最小推理今晚就把系统跑通3.1 准备一个干净的 Python 运行环境先说明这套系统的核心依赖是 Ultralytics YOLOv8它对 Python 版本的要求是 3.8 到 3.11。我习惯用 conda 创建一个独立环境避免和系统自带的 python 打架# 创建并激活独立环境traffic 是环境名可自行修改 conda create -n traffic python3.8 -y conda activate traffic # 如果你不确定显卡驱动状态先执行 nvidia-smi 看驱动和 CUDA 版本 nvidia-smi每行命令的作用第一行创建 Python 3.8 环境第三行激活环境之后的 pip 安装都只会装到这个环境里不会污染系统目录。nvidia-smi 输出的右上角会写驱动支持的 CUDA 版本比如 12.1。注意驱动版本和 PyTorch 的 CUDA 编译器版本不需要完全一致只要驱动版本不低于 PyTorch 要求的 CUDA 版本就能跑。如果机器上没有 Anaconda直接用 python -m venv traffic 也能达到同样目的只是创建和激活的命令略有不同。至于为什么用 3.8 而不是最新版主要是稳定性考虑部分老代码依赖的 opencv-python、numpy 组合在 3.11 上偶尔会遇到编译兼容问题而 3.8 是目前验证最充分的版本。GTX 1660Ti 这类老显卡用户更要注意别盲目装最新版 PyTorch显存只有 6G重负载训练容易 OOM。3.2 安装 ultralytics 和视频处理依赖激活环境后接下来装依赖。最小集合只要两个包pip install ultralytics8.1.0 opencv-python # 验证 GPU 是否对 PyTorch 可见返回 False 也能推理但只能走 CPU python -c import torch; print(torch.cuda.is_available())这里把 ultralytics 版本固定到 8.1.0是因为这是相对稳定、文档也最多的大版本范围。opencv-python 负责视频解码、抽帧和结果绘制。执行最后一行 python 命令会输出 True 或 False。输出 True 代表 PyTorch 能调用 GPU如果输出 False常见原因是安装的是 CPU 版 PyTorch需要按你机器的 CUDA 版本重新安装对应预编译包。注意离线内网环境部署时pip 和首次加载模型都可能因为无法访问外网而卡住。常见做法是提前把依赖包和模型权重下载好ultralytics 首次加载 yolov8n.pt 时如果本地找不到权重会自动联网下载因此我一般会先把 best.pt 这类文件放在 models 目录并用相对路径加载绕开自动下载环节。3.3 最小推理先跑 demo再换自己的视频装完环境第一步不是改代码而是用附带视频验证模型是否正常。在项目根目录下执行from ultralytics import YOLO # 加载训练好的交通场景模型注意路径不要写错 model YOLO(models/best.pt) # 对 demo.mp4 执行推理saveTrue 会将画好框的视频保存到 runs/detect 目录 results model.predict( sourcedata/demo.mp4, conf0.4, iou0.5, imgsz1280, device0, saveTrue, )这段代码做了什么第一行创建模型实例ultralytics 会从 .pt 文件里解析网络结构并加载权重第二行起的 predict 参数里source 指定输入视频conf 是置信度阈值iou 是 NMS 去重阈值imgsz 是推理输入边长device 指定用显卡还是 CPU。模型内部会把视频帧缩放成长边 1280 的尺寸输入网络检测到的目标再映射回原图坐标。主要参数的经验值如下表参数含义经验值conf置信度阈值高于它才保留0.3~0.5iouNMS 重叠抑制阈值0.45~0.6imgsz推理输入边长640 或 1280device0 表示第一张 GPUcpu 表示纯 CPU按机器定save是否保存检测结果Trueconf 设置成 0.4 算比较平衡调低到 0.25 可以捡回一部分被漏检的车但误检也会增加iou 调太高会导致同一辆车同时出两个框。如果你是 GTX 1660Ti 这种 6G 显存的老卡建议在命令行里额外加 --half 参数启用半精度推理并把 imgsz 降到 640速度能快一倍显存占用也更低。对应的命令行版本也很常见python scripts/detect.py --source data/demo.mp4 --weights models/best.pt --conf-thres 0.4 --iou-thres 0.5 --imgsz 1280跑完以后打开保存的视频确认车、公交、卡车基本都能框出来。如果视频中只有少量目标被框优先检查是不是路径写错或权重加载成了 yolov8n.pt。这一步通过就说明环境和模型都正常可以进入计数逻辑的改造了。4. 车流量统计逻辑从“看到车”到“数出车”4.1 为什么要引入跟踪器计数不是每帧检测的叠加如果你直接把每帧检测框数量累加得到的一定不是车流量而是“车流量乘以平均持续帧数”这种虚高数字。车流量统计的前提是搞清楚“哪几帧里的检测框属于同一辆车”。这就是跟踪器要做的事给每个目标分配一个唯一 ID在视频连续帧间维持这个 ID。常见做法是集成 ByteTrack 或 DeepSORT。我一般会优先用 ByteTrack原因是它不需要额外的特征提取模型对车辆这种刚性目标足够鲁棒在公交车遮挡小车、大车挡住小车的场景下不容易丢 ID。它的核心思想是上一帧的高置信度检测框和当前帧的高置信度框先做 IoU 匹配匹配不上的低置信度框再用低阈值去匹配一遍。这样短暂遮挡产生的低质量框也能接上轨迹ID 不会频繁切换。如果你拿到手的代码里已经封装好 tracker.py基本不用自己实现算法但要明白里面有几个可调参数丢失缓冲帧数、IoU 匹配阈值、新轨迹初始化最少连续检测帧数。这些参数在后面计数不准的排查中会频繁用到。4.2 虚拟线圈跨线计数一辆车只算一次道路车流量最主流的做法是“虚拟线圈”。在画面中画一条虚拟线相当于地磁线圈或车检器的位置当车辆中心从线的一侧移动到另一侧计数器加一。下面是一段可以直接改的跨线判断逻辑假设视频坐标左上角为原点LINE ((100, 300), (500, 300)) # 虚拟检测线起点 (x1,y1)终点 (x2,y2) def cross_side(pt): 用向量叉积判断一个点位于检测线的哪一侧。返回值的符号代表方向。 x1, y1 LINE[0] x2, y2 LINE[1] return (x2 - x1) * (pt[1] - y1) - (y2 - y1) * (pt[0] - x1) for track in active_tracks: # 遍历跟踪器输出的存活目标 cx, cy track.center now_side cross_side((cx, cy)) # 上一帧在另一侧说明车辆刚刚完成跨线这是最理想的计数时机 if track.last_side is not None and track.last_side ! now_side: if now_side 0: counter_up 1 else: counter_down 1 track.last_side now_side # 记录当前侧供下一帧比较逻辑说明cross_side 返回的是一个带符号的数值你不需要关心具体数值大小只看符号变化即可。如果一辆车一直在线的同一侧行驶符号不变不会触发计数只有符号翻转才说明它压过了检测线。用中心点而不是检测框的左上角或右下角是因为框角点在车辆转向时抖动明显中心点更稳定。参数说明LINE 的两个端点按实际画面像素坐标设置。摄像头固定后只要标定一次这条线所在画面位置就是物理断面位置。一个常见坑是车辆从左到右和从右到左都会触发计数如果只关心单向车流需要在方向判断上加一个过滤条件丢弃另一个方向的 increment。另外跟踪 ID 在目标消失后可能被销毁重新出现的车辆会拿到新 ID所以跨线计数逻辑本身不依赖 ID 的连续性只依赖 last_side 是否在上帧被正确记录。4.3 区域计数与排队长度统计除了跨线另一种常见需求是统计“某个区域里同时停着多少车”比如路口进口道的排队长度。实现上用一个多边形区域判断车辆中心点是否落在区域内from shapely.geometry import Point, Polygon # 区域示例停止线前的一段车道坐标单位是像素 zone Polygon([(200, 250), (600, 250), (600, 400), (200, 400)]) zone_ids set() # 记录已经进入区域的跟踪 ID for track in active_tracks: inside zone.contains(Point(track.center)) if inside: zone_ids.add(track.id) elif track.id in zone_ids: zone_ids.remove(track.id) # 车驶离区域释放 ID queue_len len(zone_ids) # 当前时刻区域内的车辆数这段代码的关键点在于用 zone_ids 这个集合保存“当前在区域内”的 ID而不是直接数这一帧有几个框。因为视频里一辆车可能被检测出两个重叠框或者两个 ID 同时在一辆车上直接数框会让排队长度虚高。用集合去重后同一辆车只会占用一个名额。至于为什么车离开后要 remove是为了保证排队长度是瞬时值如果不释放集合只会越来越大统计结果彻底失真。区域计数的坐标坑比跨线更多。如果你的检测结果是经过 imgsz 缩放后输出的而区域坐标是在原视频分辨率上画的多边形这两套坐标必须对齐。常见的处理方式是记录原始帧的宽高检测完成后把中心点坐标乘上“原始宽 / 推理宽”的比值再去做区域判断。否则区域看似画在车道上实际判断时偏移很大。提示如果 detect.py 里对 imgsz 做了缩放虚拟线圈和区域的坐标务必以原始视频分辨率为准否则计数区域会整体偏移。这个坑我至少见过三次每次都是计数结果异常偏低。5. 避坑指南道路车流量检测最常见的五个坑5.1 现象白天训练好的模型晚上一测漏检四成把日间训练的 best.pt 直接跑夜间视频置信度普遍偏低很多暗色车辆直接被漏检。原因不是模型坏而是训练集的“数据分布”里几乎没有夜间样本模型从没见过低照度下的车灯、反光路面和阴影组合。解决办法分三步第一从夜间视频里抽几百帧加入训练集做微调这是治本第二推理前对帧做 gamma 变换或自适应直方图均衡把暗部细节拉出来第三临时把 conf 降到 0.25同时把 iou 提到 0.6让模型多输出一些候选框再由跟踪器把连续帧的检测结果平滑掉降低单帧误检的影响。夜间场景没有一劳永逸的参数我一般会保存一套独立的 night.yaml 推理配置白天晚上分开用。5.2 现象远处的车太小还没到路口就频繁丢帧镜头视野中远端车辆可能只占 20×20 像素YOLOV8 虽然有多尺度特征但深层特征图对这么小的目标响应很弱结果就是车快到路口才被检测到车辆轨迹在远端断掉。解决思路有三个把推理分辨率 imgsz 从 640 提到 1280小目标特征会明显增强也可以把画面切成左右两半分别检测再合并相当于变相放大目标训练阶段开启多尺度训练让模型见过更多不同尺寸的车辆。如果 CPU 跑 1280 实在太慢优先考虑在 GPU 上用 TensorRT 加速而不是把分辨率降回去。这个坑的根源是下采样倍数和输入尺寸之间的平衡属于这类模型的结构性短板只能靠输入分辨率和训练策略去补。5.3 现象一辆车被数成三辆计数结果虚高 20%计数器比人工数多出不少最常见的场景是车辆被前方大车遮挡几帧跟踪器认为目标消失重新出现时分配了新 ID跨线逻辑把同一辆车又算了一次甚至几次。排查方向要看跟踪器的丢失缓冲阈值很多实现里默认只有几帧公交车长度足够把旁边车道的小车挡住四五帧。把丢失缓冲调到 30 到 60 帧目标在短暂遮挡后还能续上旧 ID。另一个更保险的做法是跨线触发不只看一帧的符号翻转连续两帧都保持新一侧才计数距离远的车辆即使 ID 切换也能被这层确认逻辑过滤掉。最后在计数状态结构里记录“已经计过数的 ID 集合”同一个 ID 终身只统计一次作为兜底。5.4 现象CPU 推理个位数 FPS实时变成了笑话很多人拿笔记本直接跑 1280 分辨率的视频CPU 推理一帧要几百毫秒结果视频慢放一样。这个坑不在代码而在于没搞清楚 CPU 和 GPU 各自的定位。解决方式如果目标是离线分析视频CPU 也能用但要降低分辨率到 640 且关闭可视化绘制如果目标是实时检测一定要走 GPU并把模型导出成 TensorRT 或 ONNX减少 Python 层后处理开销。导出后的 FP16 模型在 1660Ti 上通常能砍一半耗时。部署到 RK3588 这类边缘设备时直接用 NPU 走 INT8 量化推理CPU 到 NPU 的算子适配是主要工作量。另一个经常被忽略的瓶颈是视频帧读取用 opencv 的普通 read 循环取帧和解码串行GPU 会大量时间空转改成读取线程加队列能明显提升整体吞吐。# 快速基准测试固定 640 分辨率统计纯推理耗时 python scripts/detect.py --source data/demo.mp4 --weights models/best.pt --imgsz 640 --device 0 --benchmark通过这个命令先确认每一帧的平均耗时再评估是否需要上 TensorRT。如果 640 分辨率下单帧已经超过 50ms问题多半出在模型或后处理而不是视频解码。5.5 现象换个路口模型精度断崖式下降在 A 路口测出高精度换到 B 路口就频繁误报、漏报。这几乎是所有落地项目都会遇到的情况本质是训练数据和目标场景之间存在分布漂移相机架设高度不同、镜头焦距不同、当地车型构成不同、光照方向不同都会让同一个权重表现骤然变差。正确的处理方式是“少量数据快速微调”而不是推翻重来。从新路口采集 200 到 500 张有代表性的画面手动或半自动标注在 best.pt 基础上接着训练 30 到 50 个 epoch通常就能把精度拉回可用范围。微调时把学习率调低到 0.001并冻结主干网络前若干层避免破坏已经学到的通用特征。不要指望一个模型覆盖所有路口车流量检测系统的维护更新本来就是持续的过程。6. 让计数结果更可信热力图、训练曲线与快速微调6.1 用检测结果画一张车流热力图给非技术同事汇报时热力图比一行行数字直观得多。把一段时间内所有检测框的中心点叠到一张图上再做高斯模糊就能看到道路的“热度分布”。import cv2 import numpy as np heat np.zeros((H, W), dtypenp.float32) # H、W 取原视频分辨率 for cx, cy, conf in detections: heat[int(cy), int(cx)] 1 heat cv2.GaussianBlur(heat, (0, 0), sigmaX15) # sigma 控制热力扩散半径 heat cv2.normalize(heat, None, 0, 255, cv2.NORM_MINMAX) heat_color cv2.applyColorMap(heat.astype(np.uint8), cv2.COLORMAP_JET) overlay cv2.addWeighted(frame, 0.6, heat_color, 0.4, 0)这段代码的逻辑是先在空白矩阵上按中心点坐标累加频率再用高斯核把离散点扩散成连续热区。sigmaX 越大热力越平滑建议从 15 开始调。最后通过 addWeighted 把热力图叠加到某一帧原图上得到半透明效果。这个方法用来判断路口哪个方向流量最集中非常直观。6.2 从损失函数曲线判断模型是否真的收敛如果你自己重新跑了训练训练结束后 runs/detect/train 下会生成 results.png包含 box_loss、cls_loss、dfl_loss 以及 precision、recall 曲线。判断标准很简单训练 loss 和验证 loss 同步下降最后趋于平缓说明模型学到了东西如果训练 loss 还在降但验证 loss 反弹说明过拟合需要增加数据或降低 epoch。训练过程的 val/box_loss 是最值得盯的一条线下降速度变慢后就可以考虑提前停止。顺便说一句best.pt 保存的是验证集上指标最好的那一次权重last.pt 是最后一轮权重微调通常直接用 best.pt。6.3 用少量新数据快速微调适配自己的场景这是我把这套系统用到新路口的固定动作。数据准备好后训练入口非常简单from ultralytics import YOLO model YOLO(models/best.pt) # 在已有模型基础上继续训练 model.train( datadata/traffic.yaml, epochs50, imgsz640, batch8, lr00.001, freeze10, )参数说明data 指向新数据集的 yaml 配置文件epochs 设 50 对微调足够太多容易过拟合batch 根据显存调整1660Ti 上 8 很稳lr0 从 0.001 起步而不是默认的 0.01是为了不破坏已经收敛的权重freeze10 表示冻结主干前 10 层让浅层保留通用特征只更新高层特征。这套参数我几乎每个路口都用稳定省心。我保留的一个习惯是无论模型在验证集上表现多好都要留一段完全没有参与训练和测试的原始视频做最终验收。跑一遍完整计数流程把结果和人工数对比误差在 5% 以内才敢把这个数字交给别人。这个习惯帮我挡掉很多次翻车。希望这篇从目录拆解到避坑再到微调的路径能让你少走弯路第一夜就能把系统跑起来后面每一步都心里有数。本文还有配套的精品资源点击获取
返回列表