简介:这份资源是面向深度学习入门者、毕业设计或课程设计学生的YOLOv8交通人群监测完整项目包,聚焦智能交通与公共安全场景下的行人检测与流量统计。压缩包共24个文件、约25.56MB,以png、jpg图像样本和结果曲线图为主,辅以py检测脚本、pt训练权重、ipynb实验笔记及md说明文档,覆盖数据、模型、代码与文档全链路。项目包含摄像头实时检测与静态图片检测两套实现,配套通用工具函数、依赖清单和训练输出记录,便于快速复现与二次开发。目前已有40人学习,适合希望掌握目标检测落地流程、积累工程经验的读者参考借鉴。
1. 交通人群监测为什么总在“人挤人”场景翻车
早高峰的地铁换乘通道、商圈路口、景区入口,这些地方的人流密度一上来,传统检测方案就开始集体失灵。基于YOLOv8的交通人群监测设计,要解决的核心问题就是:在遮挡严重、目标尺度剧烈变化、光照忽明忽暗的真实交通场景里,把行人和非机动车稳定地框出来、数清楚、跟得住。它适合两类人:一类是正在做智慧交通、安防监控、客流统计方向的一线开发和算法工程师;另一类是需要一个能跑通、能改、能部署的完整方案来支撑毕业设计或项目交付的同学。
很多人第一次跑YOLOv8官方权重,在COCO上看着mAP挺漂亮,一换到自己拍的交通监控视频就傻眼——远处的人只有十几个像素,近处的人半个身子被广告牌挡住,晚上逆光只剩一个轮廓。这不是模型不行,是通用权重和交通人群这个垂直场景之间隔着一条数据鸿沟。这篇笔记就顺着“这是什么、怎么搭、数据怎么处理、参数怎么调、坑在哪”这条线,把一套可复现的交通人群监测方案讲透,让你拿到标题里的思路后能自己动手落地,而不是停在“知道有这么个东西”。
2. 从零搭起YOLOv8交通人群监测环境:CPU和GPU两条路怎么选
2.1 环境搭建的选型逻辑与最小依赖
先说选型。YOLOv8基于Ultralytics框架,安装本身不复杂,难的是版本对齐。我一般会先确认三件事:Python版本、PyTorch版本、CUDA版本。如果你手头只有普通笔记本,没有独显,那就走CPU路线,用Ubuntu 20.04搭建YOLOv8环境CPU版本完全可行,只是训练慢,推理单帧大概几百毫秒,做演示和功能验证够用。如果有GTX1660Ti这类6G显存的卡,那就能跑训练,但batch size要压到8甚至4,否则显存直接爆。
最小依赖清单其实就几个:Python 3.8到3.10之间、PyTorch、torchvision、ultralytics、opencv-python、numpy。不要一上来就装一堆花哨的包,先把主干跑通。下面是我在Ubuntu 20.04上验证过的CPU环境搭建命令,GPU版本把torch的index-url换成对应CUDA版本即可。
# 创建独立虚拟环境,避免污染系统Python conda create -n yolov8_traffic python=3.10 -y conda activate yolov8_traffic # CPU版本PyTorch安装(GPU版本请替换为对应cuda的index-url) pip install torch torchvision --index-url https://download.pytorch.org/whl/cpu # 安装ultralytics主包,它会自动拉取opencv等依赖 pip install ultralytics # 验证安装是否成功 yolo checks这段命令的逻辑是:先隔离环境,再装框架底座PyTorch,最后装ultralytics。yolo checks会打印出当前环境检测到的设备、版本信息,如果这里报错,后面全白搭。参数上唯一要注意的是Python版本,3.11以上某些依赖轮子还没跟上,容易编译失败,稳妥起见用3.10。
2.2 预训练权重获取与首次推理验证
环境好了之后,下一步是拿预训练权重。YOLOv8的权重在ultralytics安装后可以通过命令行自动下载,也可以手动放到项目根目录。常见做法是直接用yolov8n.pt这种nano版本做快速验证,因为它小、推理快,适合先确认整条链路通不通。交通人群监测对精度要求高的话,后面再换yolov8s.pt或yolov8m.pt。
from ultralytics import YOLO # 加载nano预训练权重,首次运行会自动下载 model = YOLO("yolov8n.pt") # 对一张交通场景图做推理,save=True会把结果图存到runs目录 results = model.predict( source="test_traffic.jpg", # 换成你自己的交通监控截图 conf=0.25, # 置信度阈值,交通人群建议先设0.25观察 iou=0.45, # NMS的IoU阈值,人多时适当调低 imgsz=640, # 输入尺寸,和训练时保持一致 save=True ) # 打印检测到的类别和数量,方便快速统计 for r in results: print("检测框数量:", len(r.boxes)) print("类别索引:", r.boxes.cls.tolist())这段代码的关键在conf和iou两个参数。conf=0.25是观察阈值,先看模型能召回多少目标;iou=0.45控制重叠框合并的激进程度,人群密集时如果发现框被吞掉,可以降到0.4。imgsz=640必须和后续训练尺寸一致,否则精度会掉。跑完这一步,你会看到结果图里人被框出来,但大概率会漏掉远处小目标和被遮挡的人——这正是需要自己训练数据的原因。
3. 交通人群数据集处理:从Labelme标注到YOLOv8可训练格式
3.1 标注工具选择与类别定义
交通人群监测的类别定义直接决定模型上限。我一般只设两类:person和vehicle,如果场景里有非机动车,再加一个bicycle或motorbike。类别不是越多越好,每多一类,标注成本和误检风险都往上走。标注工具用Labelme还是LabelImg都行,Labelme适合多边形分割,LabelImg适合矩形框。纯检测任务用LabelImg更快,但如果你后面想扩展到分割,Labelme的json更灵活。
标注时有个血泪经验:遮挡超过70%的目标直接标ignore或者不标,否则模型学到的全是残缺特征,推理时反而把完整的人漏掉。远处小于20像素的人,如果业务上不需要计数,也可以不标,避免引入噪声。
3.2 格式转换脚本与四个边界坑
Labelme或LabelImg产出的标注格式和YOLOv8要求的txt格式不一样。YOLOv8需要的是每行class_id x_center y_center width height,且全部归一化到0到1之间。下面这个转换脚本处理Labelme的json,同时把边界情况一并处理掉。
import json import os from pathlib import Path def labelme_to_yolo(json_dir, output_dir, class_map): """ 将Labelme的json标注转换为YOLOv8需要的txt格式 class_map: 类别名到id的映射,如 {"person": 0, "vehicle": 1} """ 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 # 跳过未定义类别,避免训练时报错 points = shape["points"] xs = [p[0] for p in points] ys = [p[1] for p in points] # 边界坑1:坐标可能超出图像范围,必须裁剪 x_min = max(0, min(xs)) x_max = min(img_w, max(xs)) y_min = max(0, min(ys)) y_max = min(img_h, max(ys)) # 边界坑2:宽高为0的退化框直接丢弃 w = x_max - x_min h = y_max - y_min if w <= 1 or h <= 1: continue # 归一化并保留6位小数 x_center = (x_min + x_max) / 2.0 / img_w y_center = (y_min + y_max) / 2.0 / img_h norm_w = w / img_w norm_h = h / img_h # 边界坑3:归一化后仍可能略超1,做最终钳制 x_center = min(max(x_center, 0.0), 1.0) y_center = min(max(y_center, 0.0), 1.0) norm_w = min(max(norm_w, 0.0), 1.0) norm_h = min(max(norm_h, 0.0), 1.0) lines.append(f"{class_map[label]} {x_center:.6f} {y_center:.6f} {norm_w:.6f} {norm_h:.6f}") # 边界坑4:没有有效标注的图片不生成空txt,否则训练时读入空文件报错 if lines: txt_path = output_dir / (json_file.stem + ".txt") with open(txt_path, "w", encoding="utf-8") as f: f.write("\n".join(lines)) # 使用示例 labelme_to_yolo( json_dir="raw_annotations", output_dir="labels/train", class_map={"person": 0, "vehicle": 1} )这个脚本里四个边界坑都是实际训练时踩出来的。坐标裁剪防止越界框导致loss异常;退化框丢弃避免宽高为0时除零;归一化钳制防止浮点误差让值变成1.000001;空标注不生成文件是因为YOLOv8训练时读到空txt会直接抛异常中断。参数上class_map必须和后续data.yaml里的names顺序完全一致,否则类别全乱。
3.3 data.yaml配置与数据集划分
转换完标注,还要写data.yaml告诉YOLOv8去哪里找图和标签。交通人群数据集一般按7:2:1划分训练、验证、测试。目录结构建议统一成images和labels平行,方便路径拼接。
# data.yaml path: /home/user/traffic_dataset # 数据集根目录 train: images/train val: images/val test: images/test nc: 2 # 类别数 names: 0: person 1: vehicle这里nc和names的索引必须和转换脚本里的class_map对应。常见错误是yaml里写了3类但标注只有2类,训练启动时直接报维度不匹配。划分数据集时注意同一段视频的连续帧不要跨train和val,否则验证集精度虚高,实际部署就翻车。
4. YOLOv8训练参数怎么调:交通人群场景的关键配置
4.1 训练命令与核心参数含义
数据准备好之后,训练本身一条命令就能启动,但参数设不对,结果天差地别。下面是我在交通人群数据集上常用的训练配置。
yolo detect train \ data=data.yaml \ model=yolov8s.pt \ epochs=150 \ imgsz=640 \ batch=8 \ lr0=0.01 \ lrf=0.01 \ patience=30 \ workers=4 \ device=0 \ project=runs/traffic \ name=exp1逐个说参数。model=yolov8s.pt是small版本,比nano精度高,比m版本省显存,交通人群场景性价比最高。epochs=150配合patience=30,意思是30轮验证精度不提升就早停,避免过拟合。batch=8是6G显存下的安全值,显存够可以加到16。lr0=0.01是初始学习率,lrf=0.01是最终学习率系数,YOLOv8默认余弦退火,这两个控制下降曲线。workers=4是数据加载线程数,CPU核少就降到2,否则可能卡死。
4.2 损失函数曲线怎么看与过拟合判断
训练启动后,runs目录下会生成results.csv和损失曲线图。YOLOv8画损失函数曲线图可以直接用内置的绘图,也可以自己读csv画。重点看三条线:box_loss、cls_loss、dfl_loss。正常情况三条都下降然后趋于平稳。如果box_loss还在降但val的box_loss开始升,就是过拟合,该早停或加数据增强。
import pandas as pd import matplotlib.pyplot as plt # 读取训练日志 df = pd.read_csv("runs/traffic/exp1/results.csv") df.columns = df.columns.str.strip() # 列名可能带空格,先清理 fig, axes = plt.subplots(1, 2, figsize=(12, 4)) # 训练损失曲线 axes[0].plot(df["epoch"], df["train/box_loss"], label="train box") axes[0].plot(df["epoch"], df["train/cls_loss"], label="train cls") axes[0].set_xlabel("epoch") axes[0].set_ylabel("loss") axes[0].legend() axes[0].set_title("train loss") # 验证损失曲线 axes[1].plot(df["epoch"], df["val/box_loss"], label="val box") axes[1].plot(df["epoch"], df["val/cls_loss"], label="val cls") axes[1].set_xlabel("epoch") axes[1].set_ylabel("loss") axes[1].legend() axes[1].set_title("val loss") plt.tight_layout() plt.savefig("loss_curve.png", dpi=150)这段代码读results.csv并画训练和验证损失对比。列名里的train/box_loss这种格式是YOLOv8固定的,strip是防止csv里列名带空格导致KeyError。看曲线时如果train和val同步下降,说明模型还在学;如果train降val平,说明容量够但数据不够;如果train降val升,立刻停。
4.3 数据增强参数对人群密集场景的影响
YOLOv8默认开启mosaic、mixup、HSV增强等。交通人群场景里,mosaic增强能显著提升小目标检测,因为它把四张图拼一起,变相增加了小目标样本。但mosaic概率太高会让训练后期不稳定,我一般设mosaic=0.8,最后10轮关掉。HSV增强对光照变化大的路口很有用,hsv_h=0.015、hsv_s=0.7、hsv_v=0.4是常用值。翻转增强fliplr=0.5可以用,但flipud不要开,人不会倒着走,开了反而引入错误先验。
5. 交通人群监测部署避坑:从模型导出到板端推理的常见问题
5.1 模型导出ONNX的坑
训练完要部署,第一步通常是导出ONNX。命令很简单:yolo export model=best.pt format=onnx。但这里有几个坑。第一,导出时imgsz必须和训练一致,否则推理框全偏。第二,动态轴设置,如果部署端输入尺寸固定,用dynamic=False,否则某些推理引擎会报维度错误。第三,导出后一定要用onnxruntime跑一遍对比,确认输出和PyTorch一致。
import onnxruntime as ort import numpy as np # 加载导出的onnx模型 session = ort.InferenceSession("best.onnx", providers=["CPUExecutionProvider"]) # 构造和训练一致的输入 input_blob = np.random.randn(1, 3, 640, 640).astype(np.float32) outputs = session.run(None, {session.get_inputs()[0].name: input_blob}) # 打印输出形状,确认是[1, 4+nc, 8400]这种格式 for i, out in enumerate(outputs): print(f"输出{i}形状:", out.shape)这段验证代码的作用是确认onnx模型能正常推理且输出维度符合预期。YOLOv8检测输出一般是[1, 4+nc, 8400],如果形状不对,说明导出参数有问题。参数上providers指定CPU,部署到RK3588或Orin时换成对应provider。
5.2 板端部署的量化与精度损失
如果部署到RK3588这类边缘设备,通常要做INT8量化。量化能提速两三倍,但精度会掉,交通人群场景里小目标掉得最明显。我的经验是:量化校准集必须包含各种光照和密度的真实场景图,至少200张,否则量化后的模型在夜间几乎不可用。量化后mAP掉3到5个点是正常的,如果掉超过10个点,检查校准集是否和训练分布差太远。
5.3 推理后处理的NMS参数调优
部署端后处理里NMS的conf和iou阈值和训练时不一样。训练时conf=0.25是为了看召回,部署时为了降低误报,conf可以提到0.4到0.5。iou在人群密集时降到0.4,否则相邻的人会被合并成一个框。这个参数没有万能值,必须拿实际视频调,调完看计数准不准。
6. 把交通人群监测做扎实的一个笨办法
如果你问我这套方案里最值得投入时间的地方是什么,我会说是验证环节,而不是模型改进。很多人一上来就想改YOLOv8的head、加注意力机制,但交通人群监测的瓶颈往往不在网络结构,而在数据质量和后处理逻辑。我自己的习惯是:每训练一版模型,先拿三段不同时段的视频跑一遍,人工数出真实人数,再和模型计数对比,算一个绝对误差。误差超过15%就回去看漏检和误检分别在哪,而不是盲目调参。
具体做法是写一个简单的计数对比脚本,把模型输出和人工标注的计数放一起,按场景分类统计。
| 场景 | 真实人数 | 模型计数 | 绝对误差 | 主要问题 |
|---|---|---|---|---|
| 早高峰通道 | 42 | 38 | 9.5% | 远处小目标漏检 |
| 晚高峰路口 | 35 | 41 | 17.1% | 广告牌反光误检 |
| 夜间入口 | 28 | 22 | 21.4% | 逆光轮廓召回低 |
这张表是我调参时的真实记录格式。看到夜间误差大,就去补夜间数据;看到误检多,就提高conf阈值或加负样本。这个笨办法比盲目改网络有效得多。另外,模型导出后一定要在目标设备上跑一遍完整视频,不要只看单帧结果,因为视频里的时序抖动和帧间不一致只有连续跑才能暴露。
最后说个我自己的教训:早期做交通人群监测时,我花了大量时间在模型改进上,结果部署到板端发现推理速度只有3帧,根本没法用。后来回头做量化和输入尺寸裁剪,才把速度拉到15帧以上。所以从第一天起就要把部署约束纳入考虑,训练时用的输入尺寸、模型大小,都要和最终硬件匹配。希望帮到你。
本文还有配套的精品资源,点击获取