ARTICLE DETAIL

资讯详情

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

YOLOv8行人检测实战:环境搭建、数据转换、训练调参与ONNX部署

YOLOv8行人检测实战:环境搭建、数据转换、训练调参与ONNX部署 简介基于YOLOv8的行人检测项目专为计算机科学、人工智能、通信工程、自动化等专业的课程设计、毕业设计及项目初期演示而准备也适合有一定基础的学习者进阶。项目包含训练模式与视频检测两个Python脚本配套yolov8n.pt、yolo11n.pt及best.pt等预训练权重另附数据集说明文档可帮助读者快速理解YOLOv8目标检测的训练、验证与推理流程。压缩包共6个文件以pt权重文件、py脚本和txt说明为主整体大小约15.89MB结构紧凑、开箱即用。除了源码和权重还整理了核心指标曲线图、混淆矩阵、F1分数曲线、精确率-召回率曲线、验证集预测结果及标签分布图答辩评审时可直接展示实验结果无需额外整理。目前已有36人学习下载适合作为毕设项目、课程设计或大作业的可靠起点也可在代码基础上修改扩展更多检测功能。1. 拿到项目包别急着解压先搞清楚 YOLOv8 行人检测项目里的资源到底有什么用如果你是从学长学姐、GitHub Release 或课程设计平台拿到一个叫《基于YOLOv8的行人检测项目.rar》的压缩包第一件事不是双击解压而是先想清楚一件事这个包大概率不是双击就能跑出检测框的傻瓜软件而是一套可移植的工程资产——里面会有训练好的权重、数据集切片、训练日志和推理脚本也可能夹杂着作者本人都忘了清理的中间产物。行人检测这个任务本身并不难难的是把数据集、训练参数和部署边界一次对齐。这个项目包真正能解决的问题是给你一条从标注好的行人图片到本地摄像头/视频里实时框出人的完整链路。它不像工业级的商汤、旷视方案那样强调多目标跟踪和跨镜重识别而是聚焦在 YOLOv8 单模型的目标检测能力上。适合谁适合正在做毕业设计的学生、刚入门目标检测的算法工程师、以及需要在边缘设备比如 RK3588、Jetson Orin上快速跑通行人检测原型的嵌入式开发者。反直觉的结论是这个项目包的核心价值不在模型本身而在数据集与训练配置的整合方式——你真正复现它时大概率要在环境搭建和数据处理上花掉 70% 的时间。如果你拿到包后第一反应是我要看懂每一行代码那方向就错了。正确姿势是先把包里的资源分好类哪些是训练资产、哪些是推理资产、哪些可以直接扔进回收站。下面我会按一个完整工程的生命周期从环境、数据、训练、避坑到部署把这个项目包拆开来讲。2. 环境搭建与项目包结构在 Ubuntu 20.04 上把 CPU 版 YOLOv8 先跑起来2.1 先给压缩包做资源体检分清训练资产与推理资产解压后我会习惯性地先执行一次tree -L 2看目录结构。一个标准的 YOLOv8 行人检测项目包通常包含以下五类目录project_root/ ├── dataset/ # 数据集本体及标签 │ ├── images/ │ │ ├── train/ │ │ └── val/ │ └── labels/ │ ├── train/ │ └── val/ ├── runs/ # 训练输出权重/曲线/验证图 │ └── detect/ │ └── train/ ├── scripts/ # 训练与推理脚本 ├── models/ # 自定义模型结构或 yaml 配置 └── requirements.txt注意runs/detect/train这个目录它里面通常有weights/best.pt和last.pt前者是验证集上指标最好的权重后者是最后一次 epoch 的断点权重。如果项目包连runs目录都没有说明作者只给了代码和数据集没有给训练好的权重你需要自己从零训练。数据集目录的images和labels必须严格一一对应这是 YOLO 格式的铁律。如果发现labels里txt文件数量少于images里jpg的数量说明有部分图片没有被标注这一批图片在训练前必须剔除否则会在数据加载时报AssertionError: Label not found。2.2 用 conda 搭一个不污染系统 Python 的 YOLOv8 环境常见做法是在 Ubuntu 20.04 上直接用 conda 创建虚拟环境。需要注意的是如果你手头没有 NVIDIA 显卡就装 CPU 版本的 PyTorch如果有 GTX 1660 Ti 这类显卡装 CUDA 11.8 对应的 PyTorch 即可。GPU 和 CPU 版本在 YOLOv8 的使用层面没有 API 差异只是推理速度差一个数量级。# 创建 Python 3.10 虚拟环境 conda create -n yolov8_ped python3.10 conda activate yolov8_ped # CPU 版本 PyTorch无 NVIDIA 显卡时 pip install torch torchvision --index-url https://download.pytorch.org/whl/cpu # 安装 ultralytics 库YOLOv8 的官方实现 pip install ultralytics # 验证安装 python -c from ultralytics import YOLO; print(YOLO.__name__)这段命令的思路是先建一个干净的 Python 环境再把 PyTorch 和 ultralytics 装进去。--index-url指定 CPU 版本的 PyTorch 下载源能避免默认安装到 CUDA 版本导致 import 报错或运行缓慢。ultralytics是 YOLOv8 的官方 Python 包它把训练、验证、导出、推理都封装成了统一接口。如果你的项目包里带了requirements.txt直接执行pip install -r requirements.txt也行但建议先看一遍内容确认没有多余的包——有些项目作者会把jupyter、matplotlib这类开发依赖混进来。2.3 单张图片推理验证项目包权重可用的最小命令环境就绪后不要急着训练。先用项目包里的best.pt跑一次单张图片推理确认权重文件和模型代码是匹配的。这是成本最低的冒烟测试能提前暴露权重损坏、类别名不匹配、图像尺寸异常三类问题。# 进入项目根目录 cd ~/project_root # 用训练好的权重对测试图片做推理 yolo detect predict modelruns/detect/train/weights/best.pt \ sourcedataset/images/val/000001.jpg \ conf0.4 \ saveTrue \ project./runs/predict这条命令里conf0.4是置信度阈值低于 0.4 的检测框会被过滤掉saveTrue会把标注了检测框的结果图保存到./runs/predict。如果推理结果图里行人框位置合理框住躯干而不是只框住头或腿说明权重和数据集是配套的。如果大量误检先检查权重是在什么数据集上训练的——很多项目包作者会用 COCO 预训练权重直接发布那个权重检测的是 COCO 的 80 类其中 person 类别的索引是 0但自定义行人数据集通常只有一个类别。3. 数据集加工把 Labelme 标注的 JSON 批量转成 YOLOv8 行人检测要的 txt 标签3.1 YOLO 标签格式的本质归一化坐标加上类别编号YOLOv8 的数据标注格式是每张图片对应一个同名的.txt文件文件里每行表示一个目标格式为class_id center_x center_y width height。这里的center_x、center_y、width、height都是相对图片宽高的归一化数值取值范围在 0 到 1 之间。而 Labelme 生成的标注是 JSON 格式里面记录的是多边形顶点坐标或矩形框左上角、右下角的绝对像素坐标。# dataset/labels/train/000001.txt # 类别id 中心点x 中心点y 框宽 框高 0 0.523437 0.389063 0.153125 0.581250 0 0.512891 0.674414 0.101562 0.264844上面的示例里有两个行人框。第一行的0.523437表示行人中心位于图片 52.34% 宽度处0.581250表示框高占图片高度的约 58%。这种归一化表示的优点是不同分辨率的图片共享同一套标签文件训练时无论输入尺寸是 640 还是 1280标签都不需要重新换算。踩过坑的人都知道Labelme 的 JSON 里矩形框是用points字段存储两个角点的而且坐标坐标系是以图片左上角为原点的。转换脚本的核心逻辑是从shapes里取rectangle类型的标注把两个角点坐标换算成 YOLO 格式。3.2 批量转换脚本从 Labelme JSON 到 YOLOv8 txtimport json import os from glob import glob def labelme_to_yolo(json_path, save_dir, class_dict): 将Labelme标注的JSON文件转换为YOLO格式的txt文件 参数: json_path: Labelme JSON文件路径 save_dir: 保存txt标签的目录 class_dict: 类别名称到id的映射字典 with open(json_path, r, encodingutf-8) as f: data json.load(f) img_w data[imageWidth] img_h data[imageHeight] txt_name os.path.basename(json_path).replace(.json, .txt) txt_path os.path.join(save_dir, txt_name) with open(txt_path, w, encodingutf-8) as out: for shape in data[shapes]: if shape[shape_type] ! rectangle: continue # 只处理矩形框忽略多边形标注 label shape[label] if label not in class_dict: print(f警告: 标签 {label} 不在类别字典中跳过) continue x1, y1 shape[points][0] x2, y2 shape[points][1] # 防止标注越界 x1 max(0, min(x1, img_w)) x2 max(0, min(x2, img_w)) y1 max(0, min(y1, img_h)) y2 max(0, min(y2, img_h)) # 计算YOLO格式的归一化坐标 cx (x1 x2) / 2 / img_w cy (y1 y2) / 2 / img_h w abs(x2 - x1) / img_w h abs(y2 - y1) / img_h out.write(f{class_dict[label]} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}\n) # 使用示例 class_dict {pedestrian: 0} json_files glob(labelme_annotations/*.json) os.makedirs(labels, exist_okTrue) for jf in json_files: labelme_to_yolo(jf, labels, class_dict) print(f转换完成共处理 {len(json_files)} 个JSON文件)这里的关键是shape[points]的取值和归一化计算。Labelme 的点在 JSON 里是列表形式points[0]是矩形左上角points[1]是右下角坐标。脚本里做了越界截断这是必要的因为手工标注时经常会出现框超出图片边缘的情况。转换后一定要随机抽查几对图片和 txt 文件用可视化脚本把框画回去。不要相信转换脚本一次跑通就万事大吉——我见过有人的 Labelme 版本是把points存成[[x1,y1],[x2,y2]]但也有人用shape_type: polygon画矩形导致points里有多个顶点。这类问题只有可视化能暴露出来。3.3 数据清洗负样本、难例与类别均衡行人检测和其他目标检测的显著差异在于场景复杂度。在街景视频里行人存在严重的遮挡、截断和小尺寸问题远距离行人可能只有 20×40 像素。直接用公开行人数据集比如 CityPersons、CrowdHuman训练出来的模型在校园或小区场景里往往会因为域差异而误检严重——最常见的是把路灯杆、消防栓、树影误判为行人。常规做法是从项目包的原始视频里抽帧补充 20005000 张目标场景的图片并手动标注。这是整个项目里最耗时但最值得投入的部分。补充数据时注意三个原则一是保留一部分没有行人的负样本图片对应空的 txt 文件让模型学会没有就是没有二是难例挖掘把那些遮挡严重、姿势异常的行人单独挑出来标注而不是只标容易的正面全身照三是控制类别均衡行人检测往往只有单类如果项目包是多类数据集行人和车辆混标建议把车辆类别剔除或单独训练否则模型容量会被分散。4. 训练配置与参数调优用 GTX 1660 Ti 训练 YOLOv8 行人检测模型的完整命令4.1 准备数据集 YAML路径与类别描述训练第一步是写一个pedestrian.yaml文件告诉 YOLOv8 数据在哪里、有多少类、类名是什么。这个文件是整个训练流程的路由表一旦路径写错训练会在第一个 epoch 直接报FileNotFoundError。# pedestrian.yaml # 训练集和验证集的图片路径绝对路径或相对项目根目录的路径 path: ./dataset train: images/train val: images/val # 类别数只有行人一类 nc: 1 # 类别名称顺序必须与标签txt里的class_id对应 names: 0: pedestrianpath字段是数据集根目录train和val是相对于path的子目录。注意这里配置的是图片目录YOLOv8 会自动在同级目录下寻找对应的labels文件夹。如果你把 labels 文件夹放错了位置比如放成了dataset/labels/train而不是dataset/labels训练会因为找不到标签而报错——这个错误的迷惑性极强因为报错信息是在训练几千张图之后才出现。4.2 训练命令与关键参数说明# 激活虚拟环境后执行训练 conda activate yolov8_ped # 用640分辨率训练100个epoch yolo detect train \ modelyolov8s.pt \ datapedestrian.yaml \ imgsz640 \ epochs100 \ batch16 \ lr00.01 \ patience10 \ project./runs \ namepedestrian_train \ device0modelyolov8s.pt表示加载 YOLOv8s 的 COCO 预训练权重。在 GTX 1660 Ti 6GB 显存上s 版本是性价比最高的选择——n 版本精度不够m 版本容易显存溢出。batch16需要根据显存调整6GB 显存跑 s 模型 640 分辨率时 16 是安全值如果报CUDA out of memory就降到 8。lr00.01是初始学习率YOLOv8 默认值就是这个。patience10表示验证集指标连续 10 个 epoch 不提升就提前停止训练这是防止过拟合和节省时间的关键参数。整个训练过程会在每个 epoch 结束后输出 mAP50、mAP50-95、precision、recall 四个指标你需要关注的是 mAP50行人检测这类单类任务中 mAP50 达到 0.9 以上才算合格。4.3 freeze 参数的用法冻结主干提升训练稳定性的边界在哪里热搜词里有一批人在搜yolov8 训练参数 freeze这里单独说透。freeze参数允许你冻结模型前 N 层通常是主干网络的 conv 层的权重只训练检测头。在迁移学习场景下这能避免小数据集上主干特征提取器被破坏。# 冻结前10层只训练检测头 yolo detect train \ modelyolov8s.pt \ datapedestrian.yaml \ imgsz640 \ epochs100 \ batch16 \ freeze10 \ lr00.005 \ project./runs \ namepedestrian_train_freeze冻结主干后有个直觉陷阱训练速度并不会明显变快因为梯度还是要回传的只是被冻结层的参数不更新。真正的收益是训练稳定性——当你的数据集比较小少于 10000 张图时冻结前 10 层能有效防止过拟合验证集 mAP 曲线会更平滑地上升而不是剧烈震荡。如果你用的是项目包自带的best.pt做微调而不是 COCO 预训练权重建议freeze0因为权重已经在行人数据上收敛过不需要再冻结特征提取器。4.4 从训练日志定位模型状态损失函数曲线图解读训练结束后项目包的runs/.../results.png会画出一组曲线图包含 Box Loss、Cls Loss、DFL Loss 和 mAP 曲线。不要被满屏的曲线吓到只看三个关键信号第一训练损失和验证损失是否同步下降。如果训练损失持续下降而验证损失在第 40 个 epoch 开始反弹说明模型开始过拟合此时应该停止训练而不是继续跑完剩余 epoch。第二mAP50 曲线是否还在上升。如果最后十几个 epoch mAP50 涨幅小于 0.5%说明模型基本收敛可以提前终止。第三Box Loss和DFL Loss的绝对值——这两个值没有统一的最优标准但如果在训练初期就出现剧烈抖动通常说明学习率过高或数据集有脏标签。用项目包里的runs目录对照看你能直观感受到一个正常训练应该长什么样这比从零看公式有用得多。5. 避坑与常见问题排查YOLOv8 行人检测训练与推理的 5 个高发踩坑点5.1 现象训练卡在 Loading dataset 阶段进度条长时间不动原因分析最常见的是数据加载器在扫描图片时遇到了损坏的图片文件或者图片格式不是 RGB 三通道。有些相机会生成带 alpha 通道的 PNGYOLOv8 在预处理时会因通道数不一致抛异常但这个异常被多线程数据加载器吞掉了表现为卡住。解决方案在训练前用脚本批量检查图片from PIL import Image import os from glob import glob bad_list [] for img_path in glob(dataset/images/train/*.jpg): try: img Image.open(img_path) img.load() if img.mode ! RGB: bad_list.append((img_path, img.mode)) except Exception as e: bad_list.append((img_path, str(e))) print(f发现 {len(bad_list)} 个异常图片) for item in bad_list[:10]: print(item)5.2 现象mAP50 很高但实际推理时漏检严重原因分析这是行人检测项目的经典翻车现场。训练集里行人都是站姿、完整可见的测试场景里却出现了坐姿、骑电动车、婴儿车等形态。YOLOv8 的卷积特征对姿态变化敏感单靠训练集分布无法泛化到这些边缘情形。解决方案不是调参能解决的必须补充对应姿态的训练数据。骑电动车的人可以拆成车人两个目标如果项目包没有这类标注需要自己从公开数据集如 CrowdHuman里筛选迁移学习素材或者用项目包自带的视频抽帧工具切出候选帧再半自动标注。这里值得投入时间因为行人检测的落地难点从来不在算法而在数据分布。5.3 现象CUDA out of memory但已经调到 batch8 了原因分析显存不足不一定只由 batch size 决定。imgsz640意味着输入分辨率为 640×640特征图在训练阶段会保留多个尺度的中间结果。如果同时开了增强默认开启 mosaic显存占用比推理时高出 46 倍。解决方案两个硬手段——batch4配合accumulate4梯度累积 4 步后再更新权重等效于 batch16 的效果或者把imgsz降到 512。后者会带来 5% 左右的 mAP 下降但在 1660 Ti 上这是可行的权衡方案。第三个选择是换yolov8n.pt权重n 版本的参数量是 s 的 1/3 左右。5.4 现象验证集标签 txt 路径对不上训练时跳过所有验证图片原因分析YOLOv8 要求验证集的 labels 目录和 images 目录严格同名同层。如果你把验证集图片放到了dataset/val/images但标签放在dataset/val/labels而自己又写了val: val/images的配置YOLOv8 会自动推导标签路径为dataset/val/images/labels自然找不到。解决方案数据集目录统一为images/train与labels/train的并列结构YAML 里只写train: images/train。这个目录映射逻辑是黑匣子不要试图用软链接或自定义路径去挑战它。5.5 现象推理速度只有 10 FPS远达不到实时要求原因分析行人检测落地时推理速度往往比精度更重要。YOLOv8s 在 1660 Ti 上理论能跑 60 FPS 以上但如果你用source视频文件还开着saveTrue且没设置vid_stride解码和写盘的 I/O 会拖慢整个链路。另外CPU 版本在 640 分辨率下本身就只有 25 FPS。解决方案对视频输入加上vid_stride2每两帧检测一帧或max_det10限制每帧最多检测 10 个目标。如果你不需要画框关掉save参数。这些参数在精度影响极小的情况下能让吞吐量翻倍。6. 部署与进阶从 PyTorch 权重到 ONNX 推理在 CPU 环境下榨干单帧性能很多人训练完就停了但行人检测项目的最终交付形态通常是能用的程序而不是漂亮的 mAP 曲线。项目包里如果带了部署相关的代码或说明大概率是围绕 ONNX 或 OpenCV 推理展开的。这里给出一条从 PyTorch 到 ONNX 再结合 OpenCV 的投产链路。6.1 导出 ONNX 并验证输出一致性ONNX 格式能把 PyTorch 模型导出为中间表示供 OpenCV DNN、ONNX Runtime、NCNN 等多个推理框架加载。导出行人检测模型时YOLOv8 会把输出头拼接成一个(1, 84, 8400)的张量——84 来自 4 个框坐标 80 个 COCO 类别或者 41 个行人类别8400 是三个尺度特征图拼接后的锚点数。# 导出ONNX格式动态输入尺寸 yolo export modelruns/detect/train/weights/best.pt \ formatonnx \ imgsz640 \ dynamicFalse \ simplifyTrue导出后建议做一个前后一致性验证分别用 PyTorch 和 ONNX Runtime 读取同一张图对比输出的检测框(x1, y1, x2, y2, conf, cls)差异。误差超过 1% 就要检查simplifyTrue是否改变了算子拓扑——少数情况下 ONNX 优化会折叠掉一些正则项导致输出漂移。import onnxruntime as ort import numpy as np import cv2 # 加载ONNX模型并推理 session ort.InferenceSession(best.onnx) input_name session.get_inputs()[0].name # 预处理缩放至640x640归一化到[0,1] img cv2.imread(test.jpg) img_resized cv2.resize(img, (640, 640)) img_input img_resized[:, :, ::-1].transpose(2, 0, 1) # BGR转RGB并调整通道顺序 img_input np.ascontiguousarray(img_input, dtypenp.float32) img_input / 255.0 # 推理 outputs session.run(None, {input_name: img_input[None, ...]})[0] print(f输出张量形状: {outputs.shape}) # 期望输出 (1, 84, 8400)这里img_input[None, ...]为输入增加 batch 维度ONNX 推理不需要计算梯度所以dtype用 float32 即可。输出张量的形状是(1, 84, 8400)需要后处理从置信度中解码出框坐标这部分在 YOLOv8 的源码里对应的是non_max_suppression函数。6.2 在 CPU 上用 OpenCV DNN 跑行人的实用技巧如果你最终要部署的机器没有 GPUOpenCV DNN 模型是比 ONNX Runtime 更轻量的选择因为它直接内嵌了 NMS 后处理不需要额外依赖。import cv2 # 加载ONNX模型 net cv2.dnn.readNetFromONNX(best.onnx) # 构造输入blob注意OpenCV的输入是BGR顺序 blob cv2.dnn.blobFromImage(img_resized, 1/255.0, (640, 640), (0, 0, 0), swapRBTrue) net.setInput(blob) outputs net.forward() # outputs形状为(1, 84, 8400)取出每个锚点的得分 classes outputs[0, 4:, :] # 84个通道中前4个是坐标第5个起是类别得分 confidences classes.max(axis0)这个后处理里有个常见的坑OpenCV DNN 的输出通道顺序是(cx, cy, w, h)加各类得分而swapRBTrue会把输入从 BGR 转成 RGB。如果训练时图像预处理用的是 RGB 顺序而 OpenCV 没有设置swapRB颜色通道错位会直接把精度打崩而且这种错误极其隐蔽——模型不会报错只是检测结果变差。6.3 最终自检清单与交付建议结束前用一张自检表验证整个项目包的完整体验训练权重能否在 CPU 上以不低于 2 FPS 的速度完成单帧推理数据集划分时是否把同一场景的连续帧同时放进了 train 和 val这是数据泄露会导致 mAP 虚高导出的 ONNX 能否被实际部署框架加载并输出非空检测框。这三项过关项目包才算真正在你手上跑通了。我自己的习惯是在每次训练前把数据集目录的完整树结构打印出来存成一个dataset_structure.txt放到 runs 目录下这样回溯时才不会因为改过目录结构而忘了当初用的配置。最后再补充一句如果项目包里同时出现了多个 pretrain 权重先用最新的那个因为作者的调参过程通常是递进的best.pt比last.pt更可靠。希望这个方向上的踩坑记录能帮你在行人检测项目里少走几趟弯路。本文还有配套的精品资源点击获取
返回列表