简介:基于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 python=3.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 model=runs/detect/train/weights/best.pt \ source=dataset/images/val/000001.jpg \ conf=0.4 \ save=True \ project=./runs/predict这条命令里conf=0.4是置信度阈值,低于 0.4 的检测框会被过滤掉;save=True会把标注了检测框的结果图保存到./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 txt
import 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', encoding='utf-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', encoding='utf-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_ok=True) 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)训练出来的模型,在校园或小区场景里往往会因为域差异而误检严重——最常见的是把路灯杆、消防栓、树影误判为行人。
常规做法是从项目包的原始视频里抽帧,补充 2000~5000 张目标场景的图片,并手动标注。这是整个项目里最耗时但最值得投入的部分。补充数据时注意三个原则:一是保留一部分没有行人的负样本图片(对应空的 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 \ model=yolov8s.pt \ data=pedestrian.yaml \ imgsz=640 \ epochs=100 \ batch=16 \ lr0=0.01 \ patience=10 \ project=./runs \ name=pedestrian_train \ device=0model=yolov8s.pt表示加载 YOLOv8s 的 COCO 预训练权重。在 GTX 1660 Ti 6GB 显存上,s 版本是性价比最高的选择——n 版本精度不够,m 版本容易显存溢出。batch=16需要根据显存调整,6GB 显存跑 s 模型 640 分辨率时 16 是安全值,如果报CUDA out of memory就降到 8。
lr0=0.01是初始学习率,YOLOv8 默认值就是这个。patience=10表示验证集指标连续 10 个 epoch 不提升就提前停止训练,这是防止过拟合和节省时间的关键参数。整个训练过程会在每个 epoch 结束后输出 mAP50、mAP50-95、precision、recall 四个指标,你需要关注的是 mAP50,行人检测这类单类任务中 mAP50 达到 0.9 以上才算合格。
4.3 freeze 参数的用法:冻结主干提升训练稳定性的边界在哪里
热搜词里有一批人在搜yolov8 训练参数 freeze,这里单独说透。freeze参数允许你冻结模型前 N 层(通常是主干网络的 conv 层)的权重,只训练检测头。在迁移学习场景下,这能避免小数据集上主干特征提取器被破坏。
# 冻结前10层,只训练检测头 yolo detect train \ model=yolov8s.pt \ data=pedestrian.yaml \ imgsz=640 \ epochs=100 \ batch=16 \ freeze=10 \ lr0=0.005 \ project=./runs \ name=pedestrian_train_freeze冻结主干后有个直觉陷阱:训练速度并不会明显变快,因为梯度还是要回传的,只是被冻结层的参数不更新。真正的收益是训练稳定性——当你的数据集比较小(少于 10000 张图)时,冻结前 10 层能有效防止过拟合,验证集 mAP 曲线会更平滑地上升,而不是剧烈震荡。
如果你用的是项目包自带的best.pt做微调而不是 COCO 预训练权重,建议freeze=0,因为权重已经在行人数据上收敛过,不需要再冻结特征提取器。
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 通道的 PNG,YOLOv8 在预处理时会因通道数不一致抛异常,但这个异常被多线程数据加载器吞掉了,表现为"卡住"。
解决方案:在训练前用脚本批量检查图片:
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,但已经调到 batch=8 了
原因分析:显存不足不一定只由 batch size 决定。imgsz=640意味着输入分辨率为 640×640,特征图在训练阶段会保留多个尺度的中间结果。如果同时开了增强(默认开启 mosaic),显存占用比推理时高出 4~6 倍。
解决方案:两个硬手段——batch=4配合accumulate=4(梯度累积 4 步后再更新权重,等效于 batch=16 的效果),或者把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=视频文件还开着save=True且没设置vid_stride,解码和写盘的 I/O 会拖慢整个链路。另外,CPU 版本在 640 分辨率下本身就只有 2~5 FPS。
解决方案:对视频输入加上vid_stride=2(每两帧检测一帧)或max_det=10(限制每帧最多检测 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 类别(或者 4+1 个行人类别),8400 是三个尺度特征图拼接后的锚点数。
# 导出ONNX格式,动态输入尺寸 yolo export model=runs/detect/train/weights/best.pt \ format=onnx \ imgsz=640 \ dynamic=False \ simplify=True导出后建议做一个"前后一致性验证":分别用 PyTorch 和 ONNX Runtime 读取同一张图,对比输出的检测框(x1, y1, x2, y2, conf, cls)差异。误差超过 1% 就要检查simplify=True是否改变了算子拓扑——少数情况下 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, dtype=np.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 跑行人的实用技巧
如果你最终要部署的机器没有 GPU,OpenCV 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), swapRB=True) net.setInput(blob) outputs = net.forward() # outputs形状为(1, 84, 8400),取出每个锚点的得分 classes = outputs[0, 4:, :] # 84个通道中,前4个是坐标,第5个起是类别得分 confidences = classes.max(axis=0)这个后处理里有个常见的坑:OpenCV DNN 的输出通道顺序是(cx, cy, w, h)加各类得分,而swapRB=True会把输入从 BGR 转成 RGB。如果训练时图像预处理用的是 RGB 顺序而 OpenCV 没有设置swapRB,颜色通道错位会直接把精度打崩,而且这种错误极其隐蔽——模型不会报错,只是检测结果变差。
6.3 最终自检清单与交付建议
结束前,用一张自检表验证整个项目包的完整体验:训练权重能否在 CPU 上以不低于 2 FPS 的速度完成单帧推理;数据集划分时是否把同一场景的连续帧同时放进了 train 和 val(这是数据泄露,会导致 mAP 虚高);导出的 ONNX 能否被实际部署框架加载并输出非空检测框。这三项过关,项目包才算真正在你手上跑通了。
我自己的习惯是在每次训练前把数据集目录的完整树结构打印出来,存成一个dataset_structure.txt放到 runs 目录下,这样回溯时才不会因为改过目录结构而忘了当初用的配置。最后再补充一句,如果项目包里同时出现了多个 pretrain 权重,先用最新的那个,因为作者的调参过程通常是递进的,best.pt比last.pt更可靠。
希望这个方向上的踩坑记录能帮你在行人检测项目里少走几趟弯路。
本文还有配套的精品资源,点击获取