☰
基于YOLOv8的交通人群监测:从训练到边缘部署全流程
2026/10/1 2:35:13 网站建设 项目流程

简介:这份资源面向深度学习入门者、图像识别方向的毕业设计或课程设计学生,提供一套基于YOLOv8的交通人群监测完整实现方案,可用于行人流量统计、公共安全监控与智能交通管理等场景。压缩包共24个文件,约25.56MB,包含3个Python脚本、1个训练好的pt权重、1个ipynb开发笔记,以及png、jpg、gif等图像素材和txt、md、log等说明与日志文件,覆盖从模型训练到检测运行的完整链路。其中摄像头实时检测与静态图片检测脚本可直接运行,权重文件省去重新训练成本,笔记与文档帮助理解项目结构与使用方法。目前已有40人学习,适合希望快速上手目标检测实战、完成期末大作业或积累项目经验的读者参考借鉴。

1. 交通人群监测为什么值得用 YOLOv8 重做一遍

路口摄像头每天都在产生海量视频,但真正能拿来用的结构化数据少得可怜。传统做法要么靠人工盯屏,要么用背景建模加 HOG 加 SVM 那套老流程,一到早晚高峰、行人互相遮挡、电动车混行就集体翻车。基于 YOLOv8 的交通人群监测设计,核心就是把「人」和「车」这两类目标从视频流里稳定抠出来,再叠加计数、密度估计和越界告警,让一段监控视频变成可查询、可统计的表格。它适合两类人:一类是正在做基于 YOLOv8 的毕业设计、需要一套能跑通全流程方案的学生;另一类是想在边缘盒子上落地交通人群监测的工程师。这篇笔记不讲空泛概念,从环境搭建、数据集处理、训练参数、模型导出到板端部署,把每一步的命令和踩坑点都摊开讲,新手能照着复现,熟手能直接对参数和边界。

2. 环境搭建与数据准备:从零把 YOLOv8 跑起来

2.1 选 CPU 版还是 GPU 版,先看手头硬件

很多人一上来就问 yolov8 环境配置怎么做,其实答案取决于你手上有什么卡。如果只有一台办公笔记本,没有独显,那就老老实实装 CPU 版本,用 ubuntu20.04 搭建 yolov8 环境 cpu 版本完全可行,只是训练慢,适合先跑通推理和小规模验证。如果手上有 gtx1660ti 这类 6GB 显存的卡,可以跑训练,但 batch 要压到 8 甚至 4,否则显存直接爆。如果是 rk3588 或 orin 这类边缘板,训练不在板上做,板上只负责推理,训练在带独显的机器上完成后再导出模型。

我一般会先用 conda 建一个干净环境,避免和系统里的 python 包打架。ultralytics 这个包把 YOLOv8 的训练、验证、导出都封装好了,不需要自己去 clone 一堆仓库。

# 创建并激活虚拟环境,python 版本建议 3.8 到 3.10 conda create -n yolov8 python=3.9 -y conda activate yolov8 # 安装 pytorch,CPU 版本用下面这条 pip install torch torchvision --index-url https://download.pytorch.org/whl/cpu # 如果有 NVIDIA 显卡,换成对应 CUDA 版本的命令,例如 CUDA 11.8 # pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 # 安装 ultralytics pip install ultralytics # 验证安装,能打印出版本号就说明环境通了 yolo version

这段命令的逻辑是:先隔离环境,再装深度学习框架,最后装 YOLOv8 的官方封装。参数上唯一要注意的是 python 版本,3.11 以上有些依赖轮子还没跟上,容易在安装阶段就报错。装完之后用yolo version做一次自检,比直接跑训练再排错省时间。

提示:如果 pip 安装 torch 时卡在下载,先换国内镜像源,或者手动下载对应版本的 whl 文件本地安装,不要反复重试同一个命令。

2.2 交通人群数据集怎么标、怎么转

yolov8训练自己的数据集,第一步不是写训练脚本,而是把数据整理成 YOLO 能吃的格式。交通人群监测场景里,我一般只保留两个大类:person 和 vehicle,如果要做细一点,把 car、bus、truck、motorcycle 拆开,但类别越多,小目标越难学。标注工具用 labelme 标注用于 yolov8 是可以的,但 labelme 默认输出的是 JSON,需要转成 YOLO 的 txt 格式。

转换脚本的核心是把 labelme 的矩形框坐标归一化到 0 到 1 之间,并生成对应的类别索引。下面这个脚本我用了很多次,直接改路径就能跑。

import json import os from pathlib import Path # 类别映射,顺序要和训练时 data.yaml 里的 names 一致 class_map = {"person": 0, "vehicle": 1} def convert_labelme_json(json_dir, output_dir): json_dir = Path(json_dir) output_dir = Path(output_dir) output_dir.mkdir(parents=True, exist_ok=True) for json_file in json_dir.glob("*.json"): with open(json_file, "r", encoding="utf-8") as f: data = json.load(f) img_w = data["imageWidth"] img_h = data["imageHeight"] lines = [] for shape in data["shapes"]: label = shape["label"] if label not in class_map: continue # labelme 给的是两个对角点,需要算出左上和右下 points = shape["points"] x1 = min(points[0][0], points[1][0]) y1 = min(points[0][1], points[1][1]) x2 = max(points[0][0], points[1][0]) y2 = max(points[0][1], points[1][1]) # YOLO 格式:类别 中心x 中心y 宽 高,全部归一化 cx = (x1 + x2) / 2.0 / img_w cy = (y1 + y2) / 2.0 / img_h w = (x2 - x1) / img_w h = (y2 - y1) / img_h lines.append(f"{class_map[label]} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}") # 输出同名 txt txt_path = output_dir / (json_file.stem + ".txt") with open(txt_path, "w") as f: f.write("\n".join(lines)) if __name__ == "__main__": convert_labelme_json("./labels_json", "./labels_txt")

逻辑说明:遍历每个 JSON,读取图像宽高,把每个框的坐标归一化。参数上,class_map必须和后面 data.yaml 里的 names 顺序严格一致,否则训练出来的模型会把人和车认反。归一化用六位小数足够,YOLO 读取时不会因为精度丢框。

数据整理完之后,目录结构要长这样:images 下放 train 和 val 两个子目录,labels 下也放 train 和 val,文件名一一对应。然后写一个 data.yaml:

path: ./traffic_dataset train: images/train val: images/val nc: 2 names: ["person", "vehicle"]

nc是类别数,names的顺序就是类别索引。这个文件路径写错是新手最常见的翻车点,训练一开始报找不到图片,九成是这里的问题。

2.3 预训练权重从哪来,要不要用

yolov8预训练权重下载这件事,官方在 ultralytics 的发布页提供了 yolov8n.pt、yolov8s.pt 等不同尺度的权重。交通人群监测我一般从 yolov8s 起步,n 太小精度不够,m 以上在边缘板上跑不动。下载之后放在项目根目录,训练时用model=yolov8s.pt指定即可,框架会自动加载。如果网络不通,可以先在有网的机器上下好,再拷贝过去,不要用来源不明的权重文件,避免结构不匹配导致加载失败。

3. 训练参数怎么设:把损失曲线压下去

3.1 一份能直接抄的训练命令

yolov8训练参数含义看起来很多,但交通人群监测真正需要调的就那么几个。下面这条命令是我在 6GB 显存卡上跑通的配置:

yolo detect train \ data=./traffic_dataset/data.yaml \ model=yolov8s.pt \ epochs=100 \ imgsz=640 \ batch=8 \ lr0=0.01 \ lrf=0.01 \ patience=20 \ device=0 \ project=./runs/traffic \ name=exp1

逐项说:imgsz=640是输入分辨率,交通场景里远处行人很小,降到 416 会丢目标,升到 1280 显存吃不消,640 是精度和速度的平衡点。batch=8是 6GB 显存的安全值,显存更大的卡可以往上加。lr0是初始学习率,0.01 是官方默认,数据集小的时候可以降到 0.001 防止震荡。patience=20表示 20 轮验证指标不提升就早停,省时间。device=0指定第一块 GPU,CPU 训练改成device=cpu。

训练开始后,终端会打印每一轮的 box_loss、cls_loss 和 mAP。如果 box_loss 一直不降,先检查标注框有没有越界或者宽高为负;如果 cls_loss 降但 mAP 不涨,多半是类别不平衡,交通场景里车多行人少,可以适当增加行人样本。

3.2 用损失曲线判断该不该继续训

yolov8画损失函数曲线图,训练结束后在runs/traffic/exp1目录下会有 results.csv,里面记录了每轮的损失和指标。用 pandas 加 matplotlib 几行就能画出来:

import pandas as pd import matplotlib.pyplot as plt df = pd.read_csv("runs/traffic/exp1/results.csv") df.columns = df.columns.str.strip() # 列名可能带空格,先清理 plt.figure(figsize=(10, 5)) plt.plot(df["epoch"], df["train/box_loss"], label="box_loss") plt.plot(df["epoch"], df["train/cls_loss"], label="cls_loss") plt.xlabel("epoch") plt.ylabel("loss") plt.legend() plt.savefig("loss_curve.png", dpi=150)

看曲线有个经验:正常情况是前 10 轮快速下降,之后缓慢收敛。如果验证损失在某个点开始往上翘,说明过拟合了,要么加数据增强,要么提前停。交通人群监测的数据集如果只来自一个路口,模型换到另一个路口就会掉点,这是数据分布问题,不是调参能救的,必须补不同场景的样本。

3.3 数据增强里哪几个参数对人群监测最有用

YOLOv8 默认开了 mosaic、HSV 抖动和随机翻转。交通人群监测里,mosaic 能提升小目标召回,但会把四张图拼在一起,如果标注框在拼接边缘被截断,反而引入噪声。我的做法是训练前期开 mosaic,最后 10 轮关掉,让模型在真实分布上收尾。HSV 抖动对光照变化大的路口有用,hsv_v 可以调到 0.5。随机翻转要慎用,水平翻转对行人没问题,但交通标志和车牌方向敏感,如果类别里包含这些,翻转会制造错误标签。

4. 模型导出与边缘部署:从权重到板端推理

4.1 导出 ONNX 和 RKNN 的差别

训练完的 .pt 权重不能直接扔到 rk3588 或 hi3516cv610 上跑,需要先转成中间格式。rk3588部署yolov8 的常见路径是 pt 转 onnx,再用 rknn-toolkit2 转 rknn。hi3516cv610 yolov8模型转换与部署实战里,通常还要经过量化,把浮点权重压成 int8,否则算力跟不上。

导出 ONNX 的命令很简单:

yolo export model=runs/traffic/exp1/weights/best.pt format=onnx opset=12 simplify=True

opset=12是兼容性比较好的版本,simplify=True会做一次图优化,去掉冗余算子。导出后可以用 onnxruntime 先验证一遍推理结果和 pt 是否一致,确认无误再往板端转。

注意:导出时 imgsz 要和训练时一致,训练用 640 导出也用 640,否则板端预处理和后处理对不上,框会整体偏移。

4.2 板端推理的预处理对齐

边缘板上跑 YOLOv8,最容易翻车的不是模型本身,而是预处理。训练时框架做了 letterbox 填充,把图像等比缩放到 640 并补灰边。板端如果直接 resize 到 640x640,长宽比变了,检测框会变形。所以板端代码里必须复现 letterbox,记录缩放比例和填充偏移,后处理时再映射回原图坐标。

import cv2 import numpy as np def letterbox(img, new_shape=640, color=(114, 114, 114)): h, w = img.shape[:2] scale = min(new_shape / h, new_shape / w) nh, nw = int(round(h * scale)), int(round(w * scale)) img_resized = cv2.resize(img, (nw, nh), interpolation=cv2.INTER_LINEAR) canvas = np.full((new_shape, new_shape, 3), color, dtype=np.uint8) top = (new_shape - nh) // 2 left = (new_shape - nw) // 2 canvas[top:top + nh, left:left + nw] = img_resized return canvas, scale, left, top

逻辑是等比缩放后居中填充,返回的 scale、left、top 用于后处理还原坐标。参数上 color 用 114 是因为训练时默认填充值就是 114,板端必须一致,否则边缘区域的激活值会有偏差。

4.3 量化掉点的排查顺序

int8 量化后 mAP 掉 3 到 5 个点是正常的,掉 10 个点以上就要查。排查顺序是:先看校准集是否覆盖了真实场景,校准集不能用训练集随便抽几张,要包含不同光照和密度;再看量化配置里哪些层被跳过了,检测头部分通常对量化敏感,可以保留浮点;最后对比量化前后同一张图的输出,看是分类错还是框回归错。交通人群监测里,行人密集时框重叠严重,量化后 NMS 阈值要适当调低,否则会吞掉正确框。

5. 避坑与排查:那些让我返工的细节

5.1 训练 loss 为 nan

现象:训练一开始 box_loss 就是 nan,几轮后进程退出。原因:学习率过大或者标注文件里有宽高为 0 的框。解决:先把 lr0 降到 0.001 试一轮,如果还是 nan,写脚本扫一遍 labels 目录,把宽或高小于 1e-6 的行删掉。交通人群数据集里,标注时手抖点出重复点就会产生这种废框。

5.2 验证集 mAP 很高但实际推理全错

现象:训练日志里 mAP50 到 0.9,但拿视频一跑,框全在背景上。原因:data.yaml 里 train 和 val 路径写反了,或者验证集图片和标签没对齐,框架拿训练集当验证集评估。解决:打开 data.yaml 逐行核对路径,再随机抽一张 val 图片,用 labelimg 打开对应 txt 看框是否贴合。这个坑我踩过两次,血泪经验是训练前先可视化十张带框图。

5.3 边缘板上推理速度远低于预期

现象:rk3588 上单帧推理要 200ms,达不到实时。原因:模型用了 yolov8m 甚至 l,或者没有开 NPU 加速,跑在 CPU 上。解决:换 yolov8n 或 yolov8s,确认 rknn 模型确实加载到了 NPU,用板子自带的性能监控看 NPU 占用率。如果 NPU 占用为 0,说明推理走的是 CPU,检查 rknn 运行时版本和模型是否匹配。

5.4 类别索引错位导致人和车互换

现象:推理结果里行人被标成 vehicle,车被标成 person。原因:data.yaml 里 names 的顺序和标注转换时的 class_map 不一致。解决:统一以 data.yaml 的 names 为准,回头改转换脚本的 class_map,重新生成全部标签。这个错误在训练指标上看不出来,因为 mAP 照样高,只有可视化才能发现。

5.5 视频推理结果抖动严重

现象:同一辆车在连续帧里框忽大忽小,类别偶尔跳变。原因:单帧检测没有时序约束,加上置信度阈值设得太低。解决:把 conf 阈值从 0.25 提到 0.4,再在跟踪层加一个简单的 IOU 匹配,用上一帧的框约束当前帧。交通人群监测如果只做计数,可以每 5 帧检测一次,中间帧用跟踪补,速度还能提上去。

6. 把监测做成能用的系统:计数逻辑与阈值调优

单帧检测只是半成品,交通人群监测真正要的是统计数字。我一般会在检测之上加一个简单的越线计数:在画面里画一条虚拟线,当目标框中心从线的一侧移动到另一侧时,计数加一。这里有个细节,目标在线上来回抖动会导致重复计数,解决办法是给每个目标分配一个跟踪 ID,用 ID 记录是否已经计过,同一个 ID 只计一次。

# 简化版越线计数逻辑,track_id 由跟踪器提供 counted_ids = set() line_y = 360 # 虚拟线在画面中的 y 坐标 def update_count(track_id, center_y): if track_id in counted_ids: return 0 # 中心点越过线且方向向下 if center_y > line_y: counted_ids.add(track_id) return 1 return 0

这段逻辑的关键是counted_ids集合,它保证每个跟踪 ID 只贡献一次计数。参数line_y要根据实际摄像头安装高度和视角来定,一般放在画面下半部分,避免远处小目标刚进画面就触发。如果要做双向计数,就维护两个集合,分别记录从上往下和从下往上的 ID。

阈值调优上,conf 和 iou 这两个参数决定了系统的性格。conf 调高,漏检多但误报少;conf 调低,误报多但漏检少。交通人群监测里,如果用于告警,宁可误报也别漏报,conf 可以设 0.3;如果用于统计报表,要求数字准,conf 设 0.5 再配合跟踪去重。iou 是 NMS 的阈值,人群密集时调低到 0.5 能保留更多重叠框,稀疏场景调到 0.7 减少重复框。

最后说一个我自己的习惯:每次换摄像头或者换场景,不要直接信之前的参数,先抽 100 帧跑一遍,把结果导成 CSV,人工核对 20 帧,算一下漏检和误检的比例,再决定要不要微调。这个动作花不了半小时,但能省掉后面反复返工的后悔药。模型不是一次训练就完事的,交通场景会变,数据要持续补,参数要跟着调。希望帮到你。

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

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

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

立即咨询