☰
YOLOv8车流量检测系统实战:从环境配置到跟踪计数
2026/10/8 1:06:38 网站建设 项目流程

简介:这套道路车流量检测系统基于YOLOv8深度学习算法与Python语言实现,面向高校毕业设计、交通数据分析和计算机视觉入门实践等场景,解决车辆实时检测、计数与交通流量统计问题,包含完整源码、训练好的模型和数据集,下载后即可直接运行。压缩包共307个文件、155.1MB,以126个py源码文件、125个pyc编译文件为主体,另有37个yaml配置、可部署的pt/onnx权重以及mp4演示视频,覆盖从代码到模型的完整链路。系统实现了端到端的检测流程,支持多种交通场景下的车辆识别,代码结构清晰、注释详尽,适合学生对照学习YOLOv8的目标检测原理与Python工程化实现。已有88人学习下载,对于需要快速完成课题或入门车辆检测的开发者,是能显著节省环境搭建与训练时间的实用资源。

1. 道路车流量检测系统:这套 YOLOv8 项目能直接跑,但难点在别处

拿到这套道路车流量检测系统(Python + YOLOv8)时,多数人第一件事就是急着跑 demo,结果往往卡在环境、路径和模型加载上。这套资源把最重的活做完了:自带 yolov8m.pt 权重、yolov8n 和 yolov8m 的 ONNX 模型、bus.jpg 与 zidane.jpg 测试图,还有 car.mp4、car2.mp4、traffic.MP4 三段真实交通视频。但我想先把一个反直觉的结论放在前面:YOLOv8 在这个项目里只负责"检测",也就是把每一帧里的车框出来;而"车流量统计"是另一件事,需要你自己补上计数逻辑。搞清楚这个边界,你才能把这套资源的价值榨干。它最适合两类人:一是正在做毕业设计、需要一套能演示的检测加统计系统的学生;二是想快速验证 YOLOv8 实际推理效果、不想从标注和训练开始折腾的从业者。

2. 先把环境搭起来并跑通视频:Python 环境配置与首次推理

2.1 yolov8 环境配置:依赖版本与 python 安装的注意点

这套系统基于 ultralytics 封装库,依赖远没有自己写训练框架那么复杂,但版本匹配仍然是第一步的坑。先说 Python 本体:建议装 Python 3.8 或 3.9,3.10 也能用,但部分旧版 torch 对 3.10 支持不完整。安装时务必勾选 Add Python to PATH,这一步对应很多人在命令行里敲 python 没反应的尴尬——本质上是 windows 的环境变量配置没做,pip 和 python 全在别处。我一般会顺手验证一下:

python --version pip --version

两条命令都有输出,环境变量才算靠谱。接下来创建虚拟环境和装依赖,这是 yolov8 环境配置里最值得固化下来的习惯:

python -m venv venv venv\Scripts\activate.bat # Windows # source venv/bin/activate # Linux / macOS pip install -r requirements.txt

requirements.txt 建议按下面这份走。注意 torch 要先装 CPU 版跑通,再考虑 CUDA,否则 CUDA 和 torch 版本不匹配的报错会消耗你大量时间:

# requirements.txt ultralytics==8.0.0 # YOLOv8 训练与推理主库,自带模型结构 opencv-python==4.8.1.78 # 视频读取与图像处理,必须单独装 numpy>=1.21 # 数组运算与后处理依赖 torch>=1.8 # 深度学习后端,CPU 版即可

装完之后别急着跑训练,先做一次 import 验证,很多人在这一步就翻车,报错大概率是 torch 被装成了 CPU 版而代码里指定了 cuda,或者 ultralytics 版本和 torch 版本冲突。这里建议直接跑一行:

python -c "from ultralytics import YOLO; print(YOLO.__name__)"

能输出就说明 yolov8 环境配置成功,可以进入推理环节了。

2.2 第一次推理:加载 yolov8m.pt 检测 bus.jpg

资源里自带的 PyTorch 权重是 yolov8m.pt,这也是我推荐你优先加载的版本。很多新手习惯照抄教程写 YOLO("yolov8n.pt"),而本地明明没有这个文件,ultralytics 就会自动联网下载,如果网络不畅就会卡在 Downloading 上。正确做法是明确指定资源里已有的模型文件:

from ultralytics import YOLO import cv2 # 使用资源内自带的 yolov8m.pt,断网也能跑 model = YOLO('yolov8m.pt') # 读取测试图 img = cv2.imread('bus.jpg') results = model(img, conf=0.4, iou=0.5, imgsz=640) for r in results: boxes = r.boxes.xyxy.cpu().numpy() # 四角坐标 [x1, y1, x2, y2] classes = r.boxes.cls.cpu().numpy().astype(int) # 类别索引 scores = r.boxes.conf.cpu().numpy() # 置信度 for box, cls, score in zip(boxes, classes, scores): print(f'class={model.names[cls]}, score={score:.2f}, box={box}')

这段代码的推理参数值得展开说明。conf 是置信度阈值,默认 0.25,但在车流量场景里我强烈建议提到 0.4 以上,因为路牌、树影、行人都会产生低置信度框,提上去能省掉大量误检。iou 是 NMS 的非极大值抑制阈值,0.5 是通用值,目标重叠多可以降到 0.4,但正常交通场景不用动。imgsz 是输入分辨率,640 是速度与精度的平衡点,想要更准可以改 1280,代价是推理耗时明显上升。

这里顺便解释一下类别输出。YOLOv8 默认在 COCO 80 类上训练,和道路车流量相关的类别索引是:2 表示 car,3 表示 motorcycle,5 表示 bus,7 表示 truck。后面做统计时,只筛选这四个类别就可以把非车辆目标过滤掉。bus.jpg 这张测试图恰好包含了 bus 类别,第一次跑通看到 class=bus 的输出,说明整个链路已经通了。

2.3 视频推理:car.mp4 逐帧检测并输出 mp4

图片检测只是热身,这套资源真正的演示效果在视频上。car.mp4 是典型的城市道路场景,直接逐帧推理并保存结果,就能做出一个直观的检测演示。视频推理逻辑和图片差不多,主要多了 VideoCapture 的读写和编码器设置:

import cv2 from ultralytics import YOLO model = YOLO('yolov8m.pt') cap = cv2.VideoCapture('car.mp4') fps = cap.get(cv2.CAP_PROP_FPS) # 取原视频帧率 w = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) # 取原视频宽度 h = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) # 取原视频高度 out = cv2.VideoWriter('output.mp4', cv2.VideoWriter_fourcc(*'mp4v'), fps, (w, h)) while cap.isOpened(): ret, frame = cap.read() if not ret: break results = model(frame, conf=0.4, imgsz=640) annotated = results[0].plot() # 自带画框函数 out.write(annotated) cap.release() out.release()

代码里值得注意的有两点。第一,cap.get(cv2.CAP_PROP_FPS) 拿到的帧率要直接传给 VideoWriter,否则输出的视频会快进或慢放,看起来像"倍速播放"。第二,VideoWriter 的编码器用 mp4v 兼容性最好,如果保存出来的文件打不开,换成 avc1 或 XVID 再试。我在 Windows 上遇到过 mp4v 写出来只有几 KB 的情况,多半是编码器没装全,这是 OpenCV 的老问题。

性能方面要先有预期:yolov8m 在纯 CPU 上跑 640 分辨率,1080p 视频大约只有 2~5 帧每秒。如果机器没有独立显卡,建议先换用 ONNX 版本的 yolov8n 跑推理,速度能快 3 倍左右,代价是 mAP 掉几个点。后面第 5 章我会讲怎么在 ONNX 和 PyTorch 权重之间切换。

3. 把检测变成流量:虚拟检测线与跟踪计数的实现

3.1 车流量统计的难点:检测和计数之间的鸿沟

很多人第一次做完视频检测,发现"每一帧都框出车了,但不知道这条路到底过了多少辆车"。原因很简单:检测解决的是"这一帧里哪里有车",而流量统计解决的是"一段时间里有多少辆车通过"。这两件事之间隔着目标跟踪。

举个例子,一段 600 帧的视频每秒 30 帧,一辆车从画面上方开到下方要经过约 20 帧,如果直接统计所有帧里的检测框数量,这一辆车会被算成 20 次。最常见的解决方案是虚拟检测线法:在画面里画一条横线,每辆车底边或中心点穿过这条线时计一次数。这个方案实现简单、实时性好,但只适合单向车道,而且同一辆车来回穿越会被重复计数。

更严谨的做法是给每个检测目标分配 ID 并持续跟踪,比如 ByteTrack 或 DeepSORT。目标一旦跨线,就把这个 ID 记入已计数集合,每个 ID 只计一次。这种做法能同时统计双向流量,还能顺带算出车速。ultralytics 库已经把 ByteTrack 内置在 model.track 里,不需要额外安装依赖,这对做毕设的同学来说非常友好。

3.2 基于 ByteTrack 的计数脚本:从检测框到过车数字

理解了思路,我给你一份可以直接改用的计数脚本。它把检测、跟踪、跨线计数和画面可视化串在一起,也是这套资源从"检测系统"升级成"车流量检测系统"的关键一步:

from ultralytics import YOLO import cv2 model = YOLO('yolov8m.pt') count_line_y = 480 # 虚拟检测线的 y 坐标,按视频高度调整 crossed_ids = set() # 已跨线的车辆 ID 集合,去重计数 car_classes = {2, 3, 5, 7} # car, motorcycle, bus, truck cap = cv2.VideoCapture('car2.mp4') total_count = 0 while cap.isOpened(): ret, frame = cap.read() if not ret: break # persist=True 让跟踪 ID 跨帧保持 results = model.track(frame, conf=0.4, persist=True, imgsz=640) if results[0].boxes is None: continue boxes = results[0].boxes.xyxy.cpu().numpy() # 四角坐标 ids = results[0].boxes.id.cpu().numpy() if results[0].boxes.id is not None else [] clses = results[0].boxes.cls.cpu().numpy().astype(int) for box, tid, cls in zip(boxes, ids, clses): if cls not in car_classes: continue cy = (box[1] + box[3]) / 2 # 目标中心点 y 坐标 if cy > count_line_y and tid not in crossed_ids: crossed_ids.add(tid) total_count += 1 print(f'vehicle {tid} crossed, total={total_count}') # 绘制检测结果和计数线 annotated = results[0].plot() cv2.line(annotated, (0, count_line_y), (annotated.shape[1], count_line_y), (0, 0, 255), 2) cv2.putText(annotated, f'count: {total_count}', (20, 60), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2) cv2.imshow('traffic count', annotated) if cv2.waitKey(1) & 0xFF == ord('q'): break cap.release() cv2.destroyAllWindows()

这段代码的关键参数有三个。persist=True 是必须的,它告诉 tracking 模块把当前帧的检测结果和历史帧的目标关联起来,ID 才能稳定存在;如果漏掉这个参数,每个目标每帧都会被分配一个新 ID,计数会变成帧数而不是车数。count_line_y 检测线的位置,需要根据你用的视频分辨率手动调,一般放在画面下方 1/3 到 1/2 处,避开画面边缘的目标进入和离开区域。car_classes 集合对应 COCO 类别,只统计车辆子集可以避免行人、自行车等检测框干扰计数。

用中心点而不是检测框底边做跨线判断,是为了减少抖动。YOLOv8 在目标被部分遮挡时,检测框的高度会跳动,底边位置起伏很大,而中心点相对稳定。实际使用中我还建议加一个方向判断:记录目标上一次的 cy 值,如果 cy 从上往下增加说明是正向通过,减少说明是反向,这样可以分别统计进出两个方向的车流。

3.3 输出统计结果:生成时段流量表

计数跑通之后,光在控制台打印还不够,毕设答辩或项目汇报时,需要把结果落成表格。最简单的做法是按固定时间窗口聚合,每分钟刷一次流量:

import time from collections import deque window_counts = deque() # 按秒记录 last_flush = time.time() start_ts = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime()) # 这段逻辑加在上一节计数循环内部 now = time.time() elapsed = int(now - last_flush) if elapsed >= 10: # 每 10 秒统计一次 window_counts.append(total_count) print(f'{time.strftime("%H:%M:%S")} 累计车辆: {total_count}') last_flush = now

跑完一段视频后,可以把每个时间窗口的累计值导出成 CSV,用 pandas 或直接写文件都行。这么做的好处是车流量数据变成了可分析的结构化数据,后续可以用 matplotlib 画时段曲线,也可以给交通管理场景做进一步的通行能力分析。统计时要注意:这个思路是基于"目标跨线"的事件计数,而不是画面内实时车辆数,两者含义不同,在说明文档里要区分清楚。

4. 避坑指南:YOLOv8 视频项目最常见的五个翻车点

4.1 中文路径和空格:模型加载失败的第一嫌疑人

现象:代码明明写着 YOLO('yolov8m.pt'),报错却是 FileNotFoundError,文件就在眼皮子底下,但程序就是找不到。

原因:ultralytics 内部拼接路径时对中文目录和空格处理很脆弱。最常见的场景是毕设文件放在"桌面/新建文件夹"这类路径下,Windows 用户名的中文目录也会让模型加载直接失败。

解决:把整个工程放到纯英文路径下,比如 D:\traffic_count\。模型文件的读取路径用绝对路径或者把 .pt 文件放在当前工作目录,然后用相对路径引用。我自己处理这类项目时,第一件事就是检查当前目录,用 os.getcwd() 确认工作目录到底在哪,这能省掉一半的路径疑难杂症。

4.2 断网时卡在 Downloading:模型文件没被找到

现象:明明资源里有 yolov8m.pt,一运行却出现 Downloading https://github.com/... 的提示,然后卡住不动,或者直接下载失败。

原因:ultralytics 在加载模型时,如果代码里写的是 YOLO('yolov8n.pt') 而本地没有对应文件,会自动尝试从官方仓库下载权重。很多教程默认写 yolov8n.pt,你照着抄,但资源里只有 yolov8m.pt 和 ONNX 版本,于是触发联网下载。

解决:第一,先 os.listdir() 看看当前目录有哪些模型文件,加载时明确写完整文件名。第二,把模型放到工程目录下,用绝对路径加载,杜绝 ultralytics 去官方源同步版本。如果确实需要 yolov8n.pt,可以先用资源里自带的 yolov8n.onnx 做推理,或者提前在有网络的环境下把它下好再拷贝过来。

4.3 视频打不开:OpenCV 的解码器跟 .MP4 不对付

现象:cap = cv2.VideoCapture('traffic.MP4') 后,cap.isOpened() 返回 False,或者循环里 ret 一直是 False,画面黑屏。

原因:OpenCV 是通过 FFmpeg 做视频解码的,但 Windows 上官方 pip 包里的 FFmpeg 支持并不完整。traffic.MP4 这种大写扩展名的文件,如果内部编码是 H.264 的高 profile 版本,OpenCV 有时就是解不出来。

解决:最省事的方案是用 FFmpeg 先转码成 OpenCV 稳定支持的格式:

ffmpeg -i traffic.MP4 -c:v libx264 -pix_fmt yuv420p traffic_ok.mp4

转码参数里 -c:v libx264 指定 H.264 编码,-pix_fmt yuv420p 是为了兼容性,转完后的文件 OpenCV 一般都能直接读。如果不想装 FFmpeg,也可以用 imageio 库读帧,它对编码器的支持和 OpenCV 不同,经常能互补。

4.4 显存不足和推理卡顿:分辨率与批次要一起调

现象:跑视频推理,显卡显存直接爆掉,报 CUDA out of memory;或者虽然没爆,但视频从流畅变成一帧一帧卡成 PPT。

原因:视频原始分辨率往往比训练时的 640 大得多,YOLOv8 默认会直接把整帧原始分辨率放进模型推理,1080p 的分辨率是 640 的两倍多,显存和算力需求呈指数上涨。如果是用 batch 方式一次丢多帧,问题更明显。

解决:在推理时明确设置 imgsz=640,让 ultralytics 内部做 letterbox 缩放,而不是直接吃原始分辨率。再加上 device='0' 指定显卡,避免 CPU 和 GPU 之间反复切换。具体调整顺序我一般是这样:先降 imgsz 到 480,再把显卡空闲的机器 batch 降到 1,最后才考虑换更小的模型。GTX 1660 Ti 这种 6GB 显存的卡,跑 yolov8m 在 640 分辨率下勉强能实时,想要更流畅就换 yolov8n。

4.5 误检太多:conf 阈值和 ROI 裁剪

现象:检测结果里出现大量把路牌、路灯、广告牌误判成车辆的框,圈出来的目标明显不对。

原因:COCO 预训练模型覆盖场景太广,对路边竖立的矩形物体很敏感,尤其是在 conf 阈值很低时,置信度 0.25 到 0.3 之间的误检会成群出现。另外,画面里人行道、绿化带等区域本身就不应该有车,但模型不管这些,照样输出框。

解决:第一,把 conf 从默认的 0.25 提到 0.4 甚至 0.5,这一招能过滤掉至少一半误检。第二,给检测区域做 ROI 掩码,只保留路面区域的检测结果,比如画面下半部分才是车道,就只统计 y 坐标在某个范围内的框。第三,如果车道场景很固定,可以去掉类别集合里概率较低的类别,比如摩托车在长距离下容易漏检,而漏检比误检更影响流量统计的准确性。

5. 模型调参:yolov8n 与 yolov8m 对比、ONNX 导出与部署

5.1 看网络结构图之前,先搞清楚 n 和 m 差在哪

很多人一上来就搜 yolov8 网络结构图,对着 C2f、SPPF 模块分析半天,其实选模型更该先看参数规模和推理速度。这套资源同时给了 yolov8m.pt、yolov8n.onnx 和 yolov8m.onnx,本身就适合做对比实验。粗略参数如下:

模型参数量COCO mAP50-95推理速度参考适用场景
yolov8n约 3.2M约 37.3最快实时监控、低算力设备
yolov8m约 25.9M约 50.2中等精度优先、有显卡

从表格能直接看出,n 和 m 之间差了大约 8 倍参数量,mAP 提升约 13 个点。在车流量场景里,如果只需要统计大致数量、对检测精度不敏感,n 是更理性的选择;如果要画框给领导或答辩老师看细节,m 的框会更贴合车身。我的建议是:先用 m 跑一遍视频,把结果存下来,再拿 n 跑同样的视频,对比误检数、漏检数和每秒帧数,用数据决定最终用哪个。

5.2 把 pt 导出成 onnx:摆脱 PyTorch 依赖

资源里的 ONNX 模型是已经导好的,但如果以后自己训练了新权重,还是要会导出。导出 ONNX 的主要目的是脱离 PyTorch 运行时,尤其部署到边缘设备时,ONNX 是通用格式。方法非常简单:

from ultralytics import YOLO model = YOLO('yolov8m.pt') model.export(format='onnx', imgsz=640, opset=12, simplify=True)

参数里 opset 是 ONNX 算子集版本,opset 12 对大部分推理引擎兼容性最好,如果部署目标是边缘平台的 NPU,需要先查该平台支持的 opset 版本,有的老平台只支持 11。simplify=True 会调用 onnx-simplifier 对计算图做精简,能减小文件体积并加快运行速度,但个别自定义算子有可能在简化后出错,导出后务必跑一次推理验证。

导出后在工程目录下会生成一个 yolov8m.onnx 文件。这个文件不依赖 torch,任何支持 ONNX 的推理库都能加载,这也是资源同时给 onnx 版本的原因。

5.3 onnxruntime 推理代码骨架

用 ONNX Runtime 跑 YOLOv8 和用 ultralytics 完全不同:你不再是调用模型对象,而是要自己做预处理、推理、后处理三步。这也是 ONNX 部署中最容易让人放弃的坎。我给出一个最小可运行的推理骨架:

import cv2 import numpy as np import onnxruntime as ort sess = ort.InferenceSession('yolov8n.onnx') input_name = sess.get_inputs()[0].name def letterbox(img, size=640): h, w = img.shape[:2] scale = min(size / h, size / w) nh, nw = int(h * scale), int(w * scale) resized = cv2.resize(img, (nw, nh)) canvas = np.full((size, size, 3), 114, dtype=np.uint8) canvas[:nh, :nw] = resized return canvas, scale frame = cv2.imread('zidane.jpg') inp, scale = letterbox(frame) # YOLOv8 输入是 NCHW、RGB、0-1 归一化 inp = inp[:, :, ::-1].transpose(2, 0, 1).astype(np.float32) / 255.0 inp = np.expand_dims(inp, axis=0) # 输出 shape 是 [1, 84, 8400],84 = 4 坐标 + 80 类 out = sess.run(None, {input_name: inp})[0] print(out.shape) # (1, 84, 8400)

这里的坑点集中在三点。预处理必须做 RGB 转换,因为 OpenCV 读出来是 BGR,直接送入模型会颜色错乱。letterbox 的缩放方式要和训练时保持一致,否则检测精度会下降。输出后处理则是 YOLOv8 特有的:8400 是 640 分辨率下三个尺度特征图的锚点总数,你真的需要解析这 8400 个候选框并做 NMS,才能得到最终检测框。好在有 onnxruntime 自带的非极大值抑制算子可以做这一步,但代码量不短。

如果你只是想在本地跑,ultralytics 直接加载 onnx 其实更省事:

model = YOLO('yolov8n.onnx') results = model('car.mp4', conf=0.4)

ultralytics 会自动帮你完成后处理,但你付出的代价是推理速度不如裸 onnxruntime 快,因为中间多了一层封装。

6. 再进一步:训练自己的数据集前,先跑一轮验证

6.1 val 先行,别急着花钱标注

很多同学拿到这套资源就想立刻训练自己的数据集。我的建议是先别急着收集图片、打标注,而是用现成模型对你自己的视频做一次验证。把手机架在路边拍 3 分钟视频,然后用资源里的 yolov8m.pt 直接推理,看检测效果再决定下一步。如果画面里的车都能被稳定框出来,说明 COCO 预训练权重足够覆盖你的场景;如果频繁漏检三轮车、货车等特殊车型,才需要考虑自己训练。这一步能帮你判断时间和钱该不该花。

验证命令很简单:

yolo detect val model=yolov8m.pt data=coco.yaml imgsz=640

6.2 一个能落地的训练起点

如果确认需要训练,推荐用迁移学习方式,基于现成的 pt 权重继续训练,而不是从零随机初始化。自己收集的数据集做到几百张图就能有不错效果,标注格式用 YOLO 的 txt 格式,目录结构固定为:

datasets/ mydata/ images/ train/ val/ labels/ train/ val/

data.yaml 指向路径,训练命令只需要两行:

yolo detect train data=mydata.yaml model=yolov8m.pt epochs=50 imgsz=640 yolo detect val data=mydata.yaml model=runs/detect/train/weights/best.pt

训练时重点看两个指标:mAP50 反映检测框与真实框的贴合程度,一般车流量场景达到 0.8 以上就够用;mAP50-95 是更严苛的指标,常见模型在 0.5 左右已经算合格。训练完别焦虑,先用 best.pt 跑视频看实际效果,再决定要不要继续加数据。我从那以后每次做这类项目,都强制自己先跑一轮 val 再决定训练策略,这个习惯帮我省掉过至少两次从头标注的返工。这套资源模型、视频和数据集都齐了,直接下载放本地就能开始验证,希望帮到你。

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

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

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

立即咨询