news 2026/9/24 18:06:06

基于YOLOV8的道路车流量检测系统:从环境搭建到车辆计数部署

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于YOLOV8的道路车流量检测系统:从环境搭建到车辆计数部署

简介:面向交通管理、城市规划及毕业设计等场景的道路车流量检测系统,基于Python与YOLOv8算法实现,提供从车辆识别、跟踪计数到结果可视化的完整解决方案。系统附带训练好的YOLOv8m与YOLOv8n模型权重(PT/ONNX格式)及测试视频,能够快速适配不同精度与速度需求,在复杂交通环境中保持稳定检测效果。资源共307个文件,包括126个Python源码、125个Pyc编译文件、37个YAML配置,另有MP4测试视频、XML标注和JPG示例图等,压缩包整体约155.1MB,目录结构清晰,便于按模块查阅。目前已有87人学习下载,适合具备入门Python基础、希望快速落地YOLOv8目标检测项目的开发者使用。整套代码运行门槛低,能直接用于道路车辆计数、交通流量统计等实验,大幅缩短毕业设计或项目开发周期。

1. 道路车流量检测系统,先搞明白这套 YOLOV8 代码解决什么问题

道路车流量检测系统,说白了就是调用 Python 里的 YOLOV8 模型,从路口、高架或停车场的监控视频里把汽车检测出来,再通过跟踪和跨线判断统计出一段时间内的过车数量。标题里白纸黑字写了“完整代码以及训练好的模型,数据都有,可以直接使用”,这意味着你不需要从几十万张图片的训练起步,解压项目后用现成权重推一段视频,马上就能看到按时间统计的车流曲线。

这类系统的实际使用场景很具体:交管部门要评估路口拥堵,园区要统计出入车辆,高校毕业设计要做一个能演示的目标检测项目。对你来说,最划算的路径不是自己从零写一个 YOLO 训练流程,而是先基于这套代码把“检测—跟踪—计数”链路跑通,再按自己的摄像头画面校准参数。认清这一点,后面做事才有方向。

不过我要先说一句:拿到项目第一件事不是双击 main.py,而是把它当成一台没调好的机器,先安装环境,再验证模型,最后才改逻辑。接下来我就按这个顺序带你把整个项目拆开。

2. 跑通最小系统:环境搭建、代码结构与模型加载

2.1 环境怎么选:Ubuntu 20.04 和 Windows 都行,CPU 也能先跑

YOLOV8 是 Ultralytics 开源库做的那套目标检测框架,它把训练、推理、导出封装得很干净。你的系统里只要有 Python 3.9 或 3.10,安装一个 ultralytics 包,就能把训练好的模型加载起来。很多人上来就纠结 GPU,其实验证阶段完全可以用 CPU 顶着跑。在 Ubuntu 20.04 搭建 YOLOV8 环境 cpu 版本,我试下来最稳的办法是建虚拟环境后直接 pip 安装。

# 创建虚拟环境,避免污染系统 Python python3 -m venv traffic_env source traffic_env/bin/activate # 安装 YOLOV8 及其依赖,torch 会默认拉 CPU 版 pip install ultralytics opencv-python numpy

如果你用的是 Windows 且装了 Anaconda,那就在 Anaconda Prompt 里执行conda create -n traffic python=3.9 -y再激活,后面的 pip 命令完全一样。这里值得说清楚为什么必须用虚拟环境:YOLOV8 对 numpy、torch、opencv 的版本有搭配要求,直接装到系统环境里很容易和现有的 TensorFlow 或旧版 opencv 打架,到时候报错全是内部依赖冲突,排查起来非常痛苦。如果你在 Ubuntu 20.04 上装的是系统自带 Python 3.8,还是优先升级到 3.9 以上,部分依赖在 3.8 下虽然能跑但容易出奇怪问题。

如果你有 N 卡,想用 GPU 加速,装完上述依赖后可以手动重装一个 CUDA 版 PyTorch,命令是pip install torch --index-url https://download.pytorch.org/whl/cu121,具体 CUDA 版本要看你的驱动,但验证阶段不装也完全能跑。网络慢的时候,可以在 pip 后面加国内镜像源,比如清华源的-i https://pypi.tuna.tsinghua.edu.cn/simple,能省不少时间。装完用python -c "import ultralytics; print(ultralytics.__version__)"检查一下,能输出版本号说明环境基本通了。

2.2 项目代码结构与权重文件放哪里

这种道路车流量检测项目,目录结构一般不会太乱,解压后常见的布局如下。虽然不同作者命名习惯有差异,但核心元素就三块:代码、权重、数据。

road-traffic/ ├── main.py # 主程序,一般负责读视频、计数、输出结果 ├── detect.py # 模型推理脚本,可以做单张图和视频测试 ├── requirements.txt # 依赖清单 ├── weights/ │ └── best.pt # 训练好的 YOLOV8 模型 ├── datasets/ │ ├── images/ # 图片样本,可能会分 train/val │ └── labels/ # 标注 txt 或 json ├── runs/ # 训练或推理输出目录 └── README.md # 作者写的说明

拿到项目后先打开 README,看作者有没有说明入口文件是哪个、视频路径在哪、结果输出到哪。不要迷信单个 best.pt,我在实际项目里收到过一些权重文件名看着正常,实际上类别顺序和代码里写的不一致,跑出来全是莫名其妙的标签。你需要确认一件事:权重文件是用 COCO 预训练模型直接做的迁移学习,还是另外定义的自定义数据集。这个可以从 README 或者检测输出标签看出来,如果标签只有 car、bus、truck,说明是专门为车辆检测训练的。

如果项目里没有给你测试图片,只是给了训练好的模型,你可以用系统自带的摄像头当数据源,cap = cv2.VideoCapture(0)就可以读一帧来做验证。但更建议先找一张清晰的路口照片放进 datasets 目录,这样每次跑的结果可复现,排查也方便。

2.3 用一段最小代码验证模型能出框

环境装好了,文件也找到了,别急着跑整个系统。先用最小代码加载权重,对一张测试图做推理,确认模型本身没坏。这个习惯能帮你把“模型问题”和“业务代码问题”隔离开。

import cv2 from ultralytics import YOLO # 1. 加载模型权重 model = YOLO("weights/best.pt") # 2. 读取一张测试图 frame = cv2.imread("datasets/road/images/val/0001.jpg") # 3. 推理,conf 是置信度阈值,imgsz 是输入尺寸 results = model.predict(frame, conf=0.25, imgsz=640, device="cpu") # 4. 把检测框画到原图上 for r in results: for box, conf, cls in zip(r.boxes.xyxy, r.boxes.conf, r.boxes.cls): x1, y1, x2, y2 = [int(v.item()) for v in box] label = r.names[int(cls.item())] cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(frame, f"{label} {float(conf):.2f}", (x1, y1 - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2) cv2.imwrite("output.jpg", frame) print("saved output.jpg")

这段代码的逻辑是:model.predict 返回一个 Results 列表,每个元素对应一帧输入;r.boxes.xyxy 是一组左上角和右下角坐标,conf 是置信度,cls 是类别编号。把坐标转成 int 是因为 cv2.rectangle 不支持浮点数坐标。最后用 cv2.imwrite 保存结果,这一步很关键,因为只有保存下来,你才能肉眼确认模型到底有没有把车框对。

参数上,conf=0.25 是通常比较合理的下限,像夜间或小目标场景,这个值可以降到 0.15;imgsz=640 表示把图像缩放成 640x640 送进网络,常见取值范围是 480 到 1280,尺寸越大越吃显存,但小目标召回率会好一些。如果模型是 COCO 预训练权重,你只想统计车辆,可以加上classes=[2, 5, 7],分别对应 car、bus、truck,这样能减少行人和自行车误检对计数的影响。

除了写脚本,Ultralytics 也提供一行命令做同样的事:

yolo predict model=weights/best.pt source=datasets/road/images/val device=cpu

这个命令行适合快速看效果,但它不会输出结构化坐标,所以正式做车流量统计还是要回到 Python 脚本里操作数据结构。

3. 车流量统计的核心:车辆检测、跟踪与计数逻辑

3.1 检测之后为什么要跟踪:单帧画框不等于流量统计

把模型跑通后,很多人会自然想到:每帧把所有车框出来,然后从第 1 帧数到最后一帧,框的数量累加不就是车流量吗?这是典型的翻车思路。视频是 25 帧每秒,一辆车从出现到消失至少经过上百帧,如果你按帧累加,同一辆车会被重复记一百次。

正确的做法是先做目标跟踪,让每一辆车在连续帧里拥有固定编号,然后再对这个编号去重计数。YOLOV8 不像传统 YOLO 只做检测,Ultralytics 库里直接封装了跟踪器,支持 ByteTrack 和 BoT-SORT。你在代码里不需要手动引入额外的包,只需把 predict 换成 track,并且加上 persist=True,它就会在连续视频帧里维持跟踪 ID。我一般会选 ByteTrack,它在车辆相互遮挡、短暂离开画面再回来的场景下,ID 切换比纯卡尔曼滤波方案少一些。

from ultralytics import YOLO import cv2 model = YOLO("weights/best.pt") cap = cv2.VideoCapture("datasets/road/test/road.mp4") while True: ret, frame = cap.read() if not ret: break # persist=True 保证上一帧的 ID 延续到当前帧 results = model.track(frame, persist=True, conf=0.3, iou=0.5, tracker="bytetrack.yaml", device="cpu") if results[0].boxes.id is not None: for box, track_id in zip(results[0].boxes.xyxy, results[0].boxes.id): x1, y1, x2, y2 = [int(v.item()) for v in box] cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(frame, str(int(track_id.item())), (x1, y1 - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2) cv2.imshow("track", frame) if cv2.waitKey(1) & 0xFF == ord("q"): break cap.release() cv2.destroyAllWindows()

这段代码里最关键的是 model.track 返回的 boxes.id,这个 id 就是跟踪算法给同一辆车分配的终生编号。persist=True 告诉算法不要把视频帧当作独立图片处理,而是使用帧间关联信息。tracker 参数我通常写 bytetrack.yaml,因为它在车辆遮挡场景下比较稳。iou=0.5 是 NMS 的阈值,太高会导致同一个物体重复出框,太低又会漏掉重叠目标。

一个需要留神的地方:跟踪 ID 并不是 100% 稳定的。当两辆车并排行驶互相遮挡时,检测框短暂消失,重新出现后算法可能分配新 ID。这时候计数逻辑如果只看 ID 首次出现,就会把同一辆车算两次。这也是为什么很多成熟方案会在画面里设置检测线,结合“过线状态翻转”来计数。

3.2 虚拟检测线:中心点跨线计数怎么算

有了跟踪 ID,接下来要回答的问题是:一辆车经过哪一刻算“通过了一次”?最简单可靠的方法是画一条虚拟检测线。比如在画面第 300 像素高度画一条横线,车辆框的中心点从线上方走到下方,就算完成一次过线。这个办法实现成本低,而且比“统计框首次出现”更能抵抗跟踪 ID 短暂丢失。

line_y = 300 # 检测线位置,根据摄像头画面调节 prev_y = {} # 记录每个 id 上一帧的中心点 y total_count = 0 # 这是在视频循环内部要做的处理,突出核心逻辑 for box, track_id in zip(results[0].boxes.xyxy, results[0].boxes.id): x1, y1, x2, y2 = [float(v) for v in box] cx = (x1 + x2) / 2 cy = (y1 + y2) / 2 tid = int(track_id.item()) if tid not in prev_y: prev_y[tid] = cy continue # 上一帧在线上方,当前帧在线下方:向下过线 if prev_y[tid] < line_y and cy >= line_y: total_count += 1 print(f"车 {tid} 通过,当前总数 {total_count}") prev_y[tid] = cy

这里的核心是前后两帧比较,而不是当前帧单点判断。如果只判断cy >= line_y,同一辆车在跨线后可能连续几帧都满足条件,会导致重复计数。用prev_y[tid] < line_y and cy >= line_y相当于捕捉“从线上方到线下方”的那一次状态翻转,正好对应车轮压线的瞬间。需要反向计数就把两个比较符号互换,改成prev_y[tid] > line_y and cy <= line_y

检测线位置也很有讲究。我不建议把线放得太靠近画面边缘,因为跟踪算法在目标刚出现和快离开时 ID 容易抖动,放在中间偏下一点,等跟踪 ID 稳定了再计数最省心。如果你的场景是多车道,就画多条线或者一条线配合左右车道分区判断。另外,检测线最好在程序初始化时由人工用鼠标点击选定,放到配置文件里,这样换场景不用改代码。

3.3 按时间聚合车流量,写入 CSV 或 SQLite

实时计数出来的是一个不断累加的数字,但业务上真正需要的是“每分钟多少辆车”或“每小时多少辆车”。这一步不能靠一个计数器一直累加,而是要把计数结果按时间窗口切分。

import time import csv from collections import defaultdict # 用分钟作为 key,统计每个分钟内发生的过线次数 minute_buckets = defaultdict(int) current_minute = int(time.time()) // 60 # 在视频主循环里,每次判定过线时: # total_count += 1 minute_buckets[int(time.time()) // 60] += 1 # 每检测到新的一分钟,就把上一分钟数据落盘 if int(time.time()) // 60 != current_minute: with open("flow.csv", "a", newline="") as f: writer = csv.writer(f) writer.writerow([current_minute, minute_buckets[current_minute]]) current_minute = int(time.time()) // 60

把“过线事件”和“统计窗口”分开是关键。minute_buckets 用默认字典存分钟级计数,时间戳int(time.time()) // 60得到的是当前时间所属的分钟编号;当这个编号变化时,说明上一分钟已经结束,可以将它写入 CSV。这样即使程序中途退出,之前每个分钟的数据也已经持久化,不会丢。

在流量不大的场景,分钟级数据已经能看出车流早晚高峰规律。如果要输出小时级报表,可以在读取 CSV 后按小时做一次聚合,这里不展开。需要提醒的是,计数事件里的时间戳最好用视频时间戳或系统时间,但如果你是对历史视频做二次分析,就不能用 time.time(),而应该用视频帧号除以帧率得到相对于视频开始的时间,这一点经常有人踩坑。

4. 避坑指南:环境、路径、数据格式的 5 个常用排查点

先说明一个前提:下面这些坑不是我这个项目里独有的,而是所有 YOLOV8 道路目标检测项目里翻车频率最高的几类。每一类我都按“现象 → 原因 → 解决”的顺序写,你可以当成排查手册来用。

4.1 没建虚拟环境,报错全是依赖冲突

现象:你按教程把 ultralytics 装上了,但运行from ultralytics import YOLO时报一堆ModuleNotFoundError,甚至提示 numpy 版本不对或者 torch 编译错误。

原因:系统 Python 里早就有一套旧版本 opencv、numpy,ultralytics 需要更高版本,pip 又没强制升级,导致导入混乱。这种环境问题最玄学,同样的代码换台机器就正常。

解决:删掉现在的环境,从头建虚拟环境。我一般用python -m venv traffic_env,再激活后重新安装依赖。装好后用pip list确认 ultralytics、torch、numpy 都出现在同一环境下,再跑脚本。以后每次开新项目都建独立环境,这是最系统的规避方式,比你在报错里找补要快得多。

4.2 Windows 中文路径导致 cv2 读不到图片

现象:项目放在D:\道路流量\数据目录下,cv2.imread()返回 None,但文件确实存在。改成英文路径就正常。

原因:OpenCV 的 imread 在 Windows 上对中文路径支持不好,这是老问题,跟图像本身无关。

解决:要么把项目整体挪到全英文路径下,要么用cv2.imdecode读取文件字节。第二种方法更通用:

import cv2 import numpy as np def imread_unicode(path): data = np.fromfile(path, dtype=np.uint8) return cv2.imdecode(data, cv2.IMREAD_COLOR) frame = imread_unicode("D:/道路流量/数据/0001.jpg")

注意路径里使用正斜杠/,反斜杠在字符串里需要用双斜杠转义。写这段代码时尽量用pathlib.Path,能少踩很多转义坑。这个函数可以直接替换项目里的所有 imread 调用,不影响其他逻辑。

4.3 模型加载成功但检测结果全为空

现象:代码不报错,输出图像已经保存,但上面一个框都没有,或者只有零星几个框。

原因:最常见的是置信度阈值太高,尤其在夜间、光照差、车辆较小的情况下,模型输出的 confidence 普遍在 0.2 以下;另一种可能是你的权重是自定义类别,推理代码却在按 COCO 类别过滤 classes。

解决:先把 conf 降到 0.1 测试一轮,如果还是没框再查类别过滤。我习惯在排查时把classes参数先注释掉,只看原始模型输出。如果降阈值后还是空,就换一张目标明显、光照正常的测试图,排除图片本身问题。这类问题用排除法几分钟就能定位,别一直调阈值,那样只会越调越糊涂。

4.4 视频计数跳帧,车流量被重复计算

现象:跑出来的车流量数字每分钟几百辆,明显不合理;或者画面播放速度忽快忽慢,计数逻辑时而漏掉。

原因:视频循环没有按帧率控制速度,cap.read()读多快就处理多快,导致同一辆车在跟踪上一帧还没稳定时就被计数。另一个原因是循环里既做推理又做画图又做保存,处理耗时超过帧间隔,后面的帧被跳过,跟踪 ID 就乱了。

解决:读视频时先拿到帧率fps = cap.get(cv2.CAP_PROP_FPS),每处理完一帧,用time.sleep(1 / fps)控制节奏,或者用更准确的时间戳差做等待。如果处理速度跟不上,先别保存视频,只画框和计数,再把视频流输出打开,会好很多。还有一种情况是视频源本身是 30 帧每秒但代码里按 25 帧写死了时间基,单位时间统计对不上,排查时可以打印帧号和真实时间戳做对比。

4.5 labelme 转 YOLO 格式后框全部错位

现象:用一个开源脚本把 labelme 的 json 转成 yolo txt,训练时 loss 不降,把训练样本可视化一看,框的位置完全不对。

原因:labelme 的坐标是左上和右下的绝对像素,YOLO 需要的是中心点 x、中心点 y、宽、高,四个值都必须除以图像宽高做归一化。很多人转格式时忘记归一化,或者类别编号从 1 开始写,而 YOLO 约定从 0 开始。

解决:转换脚本先检查三个点:框中心公式是否是((x1+x2)/2)/width,宽高是否取绝对值,类别编号是否从 0 开始。转换完成后写一个小脚本,把 txt 画回原图比对,这一步是后悔药,花五分钟检查能省一整天训练时间。后面我会在训练章节把转换脚本完整贴出来,你可以直接照着核对。

5. 训练自己的车流检测模型:数据准备、标注与参数调优

5.1 数据准备:从路口视频抽帧,2000 张是合理起点

系统自带的模型未必适配你的摄像头角度和光线。常见做法是拍一段 1 小时左右的道路视频,均匀抽帧,生成 2000 到 3000 张图片,再按 8:2 或 9:1 划分训练和验证集。抽帧不要太密集,连续帧之间内容高度相似,会造成训练集严重冗余,模型容易过拟合到特定位置。

import cv2 import os os.makedirs("datasets/road/images/train", exist_ok=True) cap = cv2.VideoCapture("road.mp4") fps = cap.get(cv2.CAP_PROP_FPS) interval = int(fps) # 每秒抽 1 帧,一小时约出 3600 张,可再抽稀 frame_id = 0 save_idx = 0 while True: ret, frame = cap.read() if not ret: break if frame_id % interval == 0: save_path = f"datasets/road/images/train/{save_idx:06d}.jpg" cv2.imwrite(save_path, frame) save_idx += 1 frame_id += 1 cap.release()

这段代码用frame_id % interval控制抽帧间隔,fps作为间隔基准,保证每秒至少拿到一张,避免时间分布不均匀。抽帧后还要人工删掉大量重复的静止画面,比如红灯时车队停着不动,连续几十帧画面几乎一样,这些都会让数据集单调。最后按 train/val 分目录存放,YOLO 训练时直接指向这两个目录,不再需要额外做划分逻辑。

如果你不想从零开始标,也可以用公开的车辆检测数据集作为预训练补充,但要注意摄像头视角和你的场景是否接近。俯拍视角和水平视角训练出来的模型,在换场景后效果差别很大。最稳的做法还是抽自己的视频,哪怕先标 1000 张,也比直接用公开数据集更贴合现场。

5.2 labelme 标注车辆,再转成 YOLO 需要的 txt

标注工具用 labelme 比较常见,因为它输出 json,方便二次处理。标注车辆时建议矩形框选“car”“bus”“truck”分开,虽然外观有差异,但不同车型的尺寸比例对训练有影响,类别分清楚对统计也有好处。

labelme 的 json 是相对复杂的嵌套结构,训练前必须转成 YOLO 的纯文本格式。转换脚本要处理的核心字段就是 imageWidth、imageHeight 和 shapes:

import json import os CLASSES = ["car", "bus", "truck"] # 顺序就是 YOLO 类别编号 def convert_labelme(json_path, out_txt): with open(json_path, "r", encoding="utf-8") as f: data = json.load(f) h = data["imageHeight"] w = data["imageWidth"] lines = [] for shape in data["shapes"]: if shape["shape_type"] != "rectangle": continue label = shape["label"] if label not in CLASSES: continue cls_id = CLASSES.index(label) (x1, y1), (x2, y2) = shape["points"] cx = ((x1 + x2) / 2) / w cy = ((y1 + y2) / 2) / h bw = abs(x2 - x1) / w bh = abs(y2 - y1) / h lines.append(f"{cls_id} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}") txt_name = os.path.splitext(os.path.basename(json_path))[0] + ".txt" with open(os.path.join(out_txt, txt_name), "w") as f: f.write("\n".join(lines))

这段脚本有几个关键点。第一,shape_type必须是 rectangle,labelme 里也可以画多边形,很多车辆标着标着就画成了不规则多边形,转换时容易漏掉。第二,类别顺序 CLASSES 一旦确定就不能随便改,除非你打算重新训练并保持顺序。第三,输出坐标必须是浮点小数,宽高用绝对值,防止 x2 比 x1 小导致负值。

转完后强烈建议用“回画脚本”把 txt 内容画到原图上,框位置正确再进入下一步,别等训练完再回头查数据。回画脚本的写法其实就是读取 txt 后,把中心点坐标和宽高换算回左上角、右下角,再画矩形,这个流程跑一遍基本能发现所有格式问题。

5.3 训练命令与三个必调参数

数据准备好了,就该 yolov8 训练自己的数据集了。YOLOV8 训练不需要写一堆训练循环,Ultralytics 库用一句命令就能启动:

yolo detect train \ data=datasets/road/data.yaml \ model=yolov8s.pt \ epochs=100 \ imgsz=640 \ batch=16 \ device=0

data.yaml 的内容通常长这样:

train: datasets/road/images/train val: datasets/road/images/val nc: 3 names: ["car", "bus", "truck"]

这里我认为最值得调的是三个参数:epochs、batch、imgsz。epochs 新手别贪多,100 左右足够看出趋势,如果验证集 loss 还在明显下降就继续训练。batch 根据显存来,8GB 以下显存建议 batch=8,imgsz 保持 640;显存 16GB 以上可以 batch=16,imgsz=640 甚至涨到 800。这两个参数决定训练速度和显存占用,不直接决定精度。

另一个容易忽略的是 model 选择,yolov8n 最快但精度低,yolov8m 精度高但显存大,道路车流量场景通常 yolov8s 就是性价比甜点。更深的网络结构改进可以先放一放,能复现一个高精度的基线比什么天花乱坠的改进都重要。训练过程中退出控制台会自动保存模型,下次恢复训练可以用resume=True。如果显存不够,reduce batch 而不是降低 imgsz,因为图像尺寸明显影响检测小目标的性能。

5.4 用损失函数曲线判断训练状态,别惦记着动不动就改结构

训练跑完后,Ultralytics 会在 runs/detect/train 目录下生成 results.csv,里面记录了每一轮的训练损失、验证损失、mAP 等指标。很多人觉得深度学习是黑匣子,其实这张表能直接告诉你模型到底有没有在学。

import pandas as pd import matplotlib.pyplot as plt df = pd.read_csv("runs/detect/train/results.csv") plt.plot(df["epoch"], df["train/box_loss"], label="train_box_loss") plt.plot(df["epoch"], df["val/box_loss"], label="val_box_loss") plt.xlabel("epoch") plt.ylabel("loss") plt.legend() plt.savefig("loss_curve.png")

画出来的曲线,如果训练 loss 和验证 loss 同步下降,而且最终都趋于平缓,说明训练状态健康;如果训练 loss 一路下降,验证 loss 开始回升,就是过拟合了,解决办法是增加数据、增大数据增强强度或增加 dropout,而不是继续增加 epoch。如果两者都保持在高位不降,先检查标注文件是否错位、类别比例是否失衡,比如只有小汽车没有公交车,模型很难把公交车学好。

这张 loss 曲线图是判断训练状态最直接的依据,也是后续训练版本迭代的体检报告。我会把它和 best.pt 放在一起,下次调参或者换数据时先看曲线,方便对比。

6. 进阶:导出 ONNX 模型,在 RK3588 部署并保留可视结果

6.1 从 PyTorch 导出 ONNX,为边缘设备部署做准备

训练好的 best.pt 可以直接在 PC 上跑,但如果要部署到 RK3588 这类带 NPU 的开发板上,通常需要先把权重导出成 ONNX,再用 RKNN 工具链转换成 rknn 格式。导出命令很简单:

yolo export model=weights/best.pt format=onnx imgsz=640

导出后你会得到一个 best.onnx,可以用 onnxruntime 在 CPU 上做推理测试,确认精度和原模型基本一致后再做后端转换。需要注意导出时指定和推理一致的 imgsz,如果训练时用 640,导出也保持 640,尺寸不匹配会导致部署后 mAP 下降。ONNX 里的输出节点很多,部署脚本要根据模型结构取框信息,这一步建议直接参考 ultralytics 的导出范本,比自己从零解析省事得多。

6.2 一个值得养成的习惯:把检测结果可视化保存

最后我想说一个我自己的血泪教训。做车流量统计时,如果只在控制台打印计数数字,出现问题根本没法复盘,比如某监控时段少算了一百辆车,没有带框的视频,你连哪条规则有问题都查不到。所以我建议所有跑过的视频都出一份带时间戳、带跟踪编号、带计数结果的输出视频,哪怕帧率只有原来的一半,也比纯数字强。

一种简单的做法是在原来画框的基础上,把当前 total_count 和时间戳用 putText 写到画面右上角,再通过 cv2.VideoWriter 写文件。这样统计结果和过程是绑定的,复查时直接定位到对应时间点。当你需要向别人展示系统效果时,这份视频本身就是最好的交付物。希望这个习惯,能帮你在车流量检测这条路上少走点弯路。

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/24 18:05:26

YOLOv8车流量检测与计数系统实践:从模型选择到目标跟踪全流程

简介&#xff1a;面向毕业设计、课设与计算机视觉入门的道路车流量检测系统完整资源包&#xff0c;基于YOLOv8算法和Python实现&#xff0c;可完成车辆实时检测与流量统计&#xff0c;适用交通监控、城市车流分析等场景。包内共307个文件&#xff0c;含126个Python源码、125个p…

作者头像 李华
网站建设 2026/9/24 18:05:11

OpenCV手势识别实战:从HSV分割到指尖计数的端到端流水线

简介&#xff1a;本资源是一份面向人工智能与计算机视觉初学者的OpenCV手势识别实践项目&#xff0c;聚焦Python图像处理与基础手势识别逻辑实现&#xff0c;适用于课程大作业、技术入门实训及AI项目快速原型开发。压缩包共2个文件&#xff1a;核心为带逐行超详细注释的Python源…

作者头像 李华
网站建设 2026/9/24 18:04:44

基于YOLOv5与CNN的车牌检测识别实战:CCPD数据集全流程解析

简介&#xff1a;本资源面向计算机视觉方向的毕业设计、课程设计及学科竞赛参与者&#xff0c;提供一套基于CNN与YOLOv5的车牌检测与识别完整工程&#xff0c;数据集采用CCPD官方数据集&#xff0c;可帮助读者快速搭建车牌识别实验环境并完成项目复现。压缩包共10个文件&#x…

作者头像 李华
网站建设 2026/9/24 18:03:45

基于CNN的智能垃圾分类系统:从数据到部署的毕业设计实战

简介&#xff1a;这份资源是面向计算机、通信、人工智能、自动化等专业学生与从业者的深度学习实战项目&#xff0c;以卷积神经网络为核心实现智能垃圾分类&#xff0c;可作为毕业设计、课程大作业或期末课程设计的完整参考方案。项目代码经过调试测试&#xff0c;答辩评审分达…

作者头像 李华
网站建设 2026/9/24 18:03:43

招投标系统源码2021.zip部署与信创适配实战指南

简介&#xff1a;这是一套基于C#开发的招投标系统Web服务器源码&#xff0c;面向.NET初学者与Web应用开发者&#xff0c;用于学习企业级招标投标业务系统的架构设计与全栈实现。资源包含1976个文件&#xff0c;主体为199个C#类文件&#xff08;.class及.java后缀实为JSP编译产物…

作者头像 李华
网站建设 2026/9/24 18:03:35

PTP设备同步互通测试解决方案

引言高精度时间同步网络是现代通信网络稳定运行的核心基础支撑&#xff0c;整套系统主要由提供标准时间源的核心时间同步设备、承担时间信号传输的传送与接入网络、各类末端业务应用设备三大核心部分组成。时间同步互通覆盖全链路设备适配测试&#xff0c;涵盖时间同步设备、分…

作者头像 李华