简介:基于YOLOv8的行人检测系统毕业设计项目包,面向计算机相关专业正在准备毕设的学生,以及需要完整项目实战练习的开发者。资源内含可运行源码、训练好的.pt模型权重及全部训练与测试数据,覆盖从模型配置、训练脚本到结果输出的完整流程,经导师指导并获得评审99分的高分认可,代码结构清晰,零基础用户也能按文档快速上手复现。压缩包共15个文件,以jpg图片数据、pt模型权重、yaml配置文件、py训练脚本和txt说明文档为主,整体大小约155.74MB,适合作为课程设计、期末大作业或项目实战参考。目前已有116人学习使用。除核心检测逻辑外,还提供多种模型规格与融合配置,方便读者对比精度与速度,深入理解YOLOv8在行人检测场景中的调优方法。
1. 毕业设计拿到“基于YOLOv8的行人检测系统,这个资源包能帮你省掉一半以上的前期时间
打开某开源平台搜“基于yolov8的行人检测系统 源码+训练好的模型+全部数据”,结果一页一页。很多同学毕设题目就落在这个方向上,但真正动手时才发现:光有源码没有权重,推理是一张全黑的图;光有模型没有数据,答辩老师一问“你的训练集怎么来的”就卡壳。这个标题把源码、模型、数据三样东西打包在一起,意味着你可以跳过从零训练行人检测器最耗时的数据标注和模型调试阶段,直接拿到一个能跑的基线系统,再按毕设要求做界面、加功能、优化指标。它适合本科毕业设计、课程设计,也适合第一次接触目标检测、想快速体验“训练到部署”闭环的读者。但别指望解压就能答辩——你要搞清楚模型是哪个数据集训的、类别索引是不是0、数据格式和你本机环境是否匹配。这些下面一一拆开讲。
2. 为什么是YOLOv8做行人检测:选型逻辑、环境搭建和代码包结构
2.1 YOLOv8的网络结构对行人检测友好在哪里
行人检测是目标检测的子问题,YOLOv8能做到高帧率和接近两阶段检测器的精度,是工程落地的首选。YOLOv8主要有四处改动:主干里的C2f模块替代了YOLOv5的C3,等价于用更多的跨层连接让梯度流动更充分;检测头改成了Anchor-Free,直接预测目标中心与宽高,少了一组锚框超参数;分类与回归分支解耦,训练收敛更快;Neck部分保留FPN+PAN,多尺度特征对大小行人都能覆盖。从行人检测的角度看,Anchor-Free + FPN/PAN的组合在常规场景下漏检率比旧版更低,尤其对密集站立的人群。更关键的是,Ultralytics把训练、验证、导出、部署封装成一条命令,这对毕业设计需要反复改参数调实验非常省时间。
2.2 在Ubuntu 20.04上搭建YOLOv8环境(CPU版也能跑)
环境搭建是拿到源码包后的第一道门槛。搜“ubuntu20.04搭建yolov8环境cpu版本”的同学很多,其实步骤非常少。通过conda隔离Python版本能避免很多依赖冲突。
conda create -n yolov8 python=3.10 -y conda activate yolov8 pip install ultralytics pip install torch torchvision --index-url https://download.pytorch.org/whl/cpu先装CPU版PyTorch再装ultralytics,是为了避免pip自动拉取CUDA版torch导致磁盘占用过高。如果你的笔记本有NVIDIA显卡(比如GTX 1660Ti以上),想把训练放在GPU上,就不要指定cpu的index-url,直接把torch和ultralytics一起装:
pip install ultralytics python -c "import torch; print(torch.cuda.is_available())"最后一行输出True说明GPU可用。注意ultralytics当前版本要求Python 3.8以上,建议3.10;Python 3.12某些旧版本依赖会报编译错误。
2.3 验证安装:用官方COCO权重跑一张图
环境配好后,先在源码包外验证ultralytics本身没问题。使用官方YOLOv8n权重对官方示例图推理:
python - <<'PY' from ultralytics import YOLO model = YOLO("yolov8n.pt") # 首次运行会自动下载权重 results = model("https://ultralytics.com/images/bus.jpg", save=True) print(results[0].boxes) PY看到输出里包含person类别说明环境正常。此时你用的模型是COCO 80类预训练权重,其中person是第0类。但注意,COCO的person和第3章要用的行人检测数据集不能直接画等号:COCO里person包含各种姿态和尺度,背景简单;而行人检测数据集往往有密集遮挡、夜间/雨天场景。所以毕设项目如果提供了专门在行人数据上训练好的权重,推理脚本里的类别索引必须改过来,否则会用80类的头去预测1类数据集的标签,几乎什么都检测不到。
2.4 先摸清源码包的目录结构再动手
每个毕设打包的目录都不一样,但通常会有这几个东西:源码(train.py/detect.py)、训练好的权重(best.pt/last.pt)、数据集(images/labels)和说明(README)。我一般会先执行find . -maxdepth 2 -type d看一眼大目录,再重点看三个文件:data.yaml(数据集配置)、best.pt(验证集上最优权重)、predict.py(推理脚本)。用Python快速比较best.pt和last.pt的差异:
import torch a = torch.load("weights/best.pt", map_location="cpu")["model"] b = torch.load("weights/last.pt", map_location="cpu")["model"] print(a.__dict__.get("names"), b.__dict__.get("names"))如果两个权重的names不一致,说明训练过程中类别映射出了问题,这种情况在网上下载的模型里并不少见。拿到源码包后第一件事不是跑训练,而是确认模型输出类别和你将要用的数据类别对得上,这一步能避免后面所有推理结果都是空的。
3. 行人检测数据集怎么准备:从标注格式到训练集划分
3.1 标题里的“全部数据”到底是什么格式
标题写的“全部数据”通常指已经整理成YOLO格式的行人检测数据集,目录大概是images/train、labels/train、images/val、labels/val。但也有很常见的例外:数据是COCO JSON标注,或者LabelMe导出的*.json。打开一个标注文件看看:
ls datasets/person/labels/train/ | head -5 sed -n '1p' datasets/person/labels/train/000001.txt第二行输出如果是0 0.52 0.31 0.18 0.42这类五个数,就是YOLO格式,可以直接用。如果看到的是[{"image_path":...}]这种JSON结构,说明还需要转换。很多网上打包的数据集不会告诉你细节,因此我的习惯是先花两分钟统计标签分布:
awk '{count[$1]++} END {for (k in count) print k, count[k]}' datasets/person/labels/train/*.txt输出里如果只出现0,说明是单类行人检测;出现0 1 2则要注意是不是包含“行人”、“骑行者”、“背景”等多类。毕设答辩时会问“你的模型能检测哪几类”,所以这个分布必须提前搞清楚。
3.2 把COCO JSON转换成YOLO格式:bbox转换脚本
如果你的“全部数据”是COCO格式(常见于CrowdHuman、COCO-Person等大型行人数据集),需要把JSON里的bbox从[left, top, width, height]转成YOLO需要的[x_center, y_center, width, height]并归一化。下面这段脚本是常见做法,可以处理单类或多类,并生成一个类别映射文件。
import json import os from pathlib import Path def coco_to_yolo(json_path, out_dir): with open(json_path, "r", encoding="utf-8") as f: coco = json.load(f) cat_id_map = {c["id"]: i for i, c in enumerate(coco["categories"])} Path(out_dir).mkdir(parents=True, exist_ok=True) for img in coco["images"]: w, h = img["width"], img["height"] stem = Path(img["file_name"]).stem lines = [] for ann in coco["annotations"]: if ann["image_id"] != img["id"]: continue x, y, bw, bh = ann["bbox"] cx = (x + bw / 2) / w cy = (y + bh / 2) / h nw, nh = bw / w, bh / h lines.append(f"{cat_id_map[ann['category_id']]} {cx:.6f} {cy:.6f} {nw:.6f} {nh:.6f}") if lines: Path(out_dir, f"{stem}.txt").write_text("\n".join(lines), encoding="utf-8") coco_to_yolo("annotations.json", "labels/train")这段脚本把COCO bbox左上角坐标转成归一化中心坐标。注意三个坑:一是COCO的类别ID不连续,比如person可能是1,但YOLO要求类别从0开始连续编号,所以必须重建cat_id_map;二是有些数据集的文件名带子目录,Path(file_name).stem只取文件名,可能重名,建议先os.path.relpath保留子目录;三是ann里可能存在无效框(宽高为0或坐标越界),转换前最好过滤。
3.3 LabelMe标注如何转成YOLO格式
如果“全部数据”是LabelMe标注的JSON,格式是shapes里的多边形点集,没有直接的bbox。都要转成外接矩形,然后做同样的归一化。下面是常用的转换片段:
import json import glob def labelme_to_yolo(json_file, out_dir): with open(json_file, "r", encoding="utf-8") as f: data = json.load(f) w, h = data["imageWidth"], data["imageHeight"] stem = Path(json_file).stem lines = [] for shape in data["shapes"]: if shape["label"] != "person": continue xs = [p[0] for p in shape["points"]] ys = [p[1] for p in shape["points"]] x1, y1, x2, y2 = min(xs), min(ys), max(xs), max(ys) bw, bh = x2 - x1, y2 - y1 cx, cy = (x1 + x2) / 2, (y1 + y2) / 2 lines.append(f"0 {cx/w:.6f} {cy/h:.6f} {bw/w:.6f} {bh/h:.6f}") Path(out_dir, f"{stem}.txt").write_text("\n".join(lines), encoding="utf-8")这段代码把LabelMe多边形的最小外接矩形当作目标框。如果标注时用矩形框,shape["points"]恰好只有两个顶点,逻辑同样成立。注意类别名要和后续data.yaml里的names保持一致,比如这里用的label是person,names: ['person'],序号0对应person。如果数据里有多个类别,比如person、rider、background,得先确认毕设到底要做单类还是多类检测,不相关的类先去掉,否则会影响mAP计算。
3.4 划分训练集与验证集:别把同一个场景的照片分到两个集合
数据转换完成后,需要把图片和标签划分成train和val。用random.shuffle直接分会有个坑:连续抓拍的背景高度相似,如果同一场景的帧被拆分到训练和验证,验证指标会虚高。建议按图片所在目录分组切分,至少保证视频帧序列不分家。一个简单粗暴的方式是按文件名前缀分组:
import os import random from pathlib import Path source_images = "datasets/person/all_images" source_labels = "datasets/person/all_labels" random.seed(42) images = sorted(Path(source_images).glob("*.jpg")) random.shuffle(images) split_point = int(len(images) * 0.8) train_imgs = images[:split_point] val_imgs = images[split_point:] for subset, img_list in [("train", train_imgs), ("val", val_imgs)]: Path(f"datasets/person/images/{subset}").mkdir(parents=True, exist_ok=True) Path(f"datasets/person/labels/{subset}").mkdir(parents=True, exist_ok=True) for img in img_list: os.rename(img, f"datasets/person/images/{subset}/{img.name}") lbl = Path(source_labels) / (img.stem + ".txt") if lbl.exists(): os.rename(lbl, f"datasets/person/labels/{subset}/{lbl.name}")这个脚本使用随机分割但设置了随机种子。它能跑通的前提是所有图片都在同一个目录,如果源码包的数据本身已经按train/val组织好,就跳过这一步。切完以后检查一下:
ls datasets/person/images/train | wc -l ls datasets/person/labels/train | wc -l数目不一致说明有图片没有对应标签,训练时YOLO会忽略这些图片,但如果缺口太大,训练集会缩水,最好找回来。
3.5 写data.yaml:路径、类别数和names
YOLOv8用data.yaml描述训练数据。下面是一个单类行人检测的完整示例:
path: /home/yourname/datasets/person # 绝对路径,不要写相对路径 train: images/train val: images/val nc: 1 names: 0: person建议path写成绝对路径,因为训练命令的工作目录不一定是项目根目录,相对路径经常会报“dataset not found”。如果你把数据放在项目目录内,也可以写成path: datasets/person,但必须确认路径从当前目录是可达的。nc: 1对应单类,names的索引从0开始。验证配置是否正常:
from ultralytics.data import YOLODataset ds = YOLODataset("data.yaml", imgsz=640) print(len(ds))如果能打印出数据集长度,说明路径和标签格式都能被YOLO解析。不能解析时会直接报错,这时候按第5章里的排查方法检查标签内容即可。
4. 用YOLOv8训练行人检测模型:命令、参数和损失曲线
4.1 从预训练权重微调,省一半时间
既然毕设项目给了训练好的模型,你可以选择直接做二次微调,用yolov8n.pt这个在COCO上预训练过的权重作为起点。常见做法是:
yolo train data=data.yaml model=yolov8n.pt epochs=50 imgsz=640 batch=16 device=0 name=pedestrian_finetune这里model=yolov8n.pt会自动下载COCO预训练权重建模,并将最后的输出类别数自动调整为data.yaml里的nc=1。如果不想下载,也可以指向本地已有的权重:
yolo train data=data.yaml model=runs/detect/person_baseline/weights/best.pt epochs=30 imgsz=640 batch=8 device=0把之前训出来的best.pt当初始权重继续训练,适合你已经有一版模型、想让它在新增数据上再提升的情况。只用单类训练,50个epoch在GTX 1660Ti上大约需要40分钟到1小时,CPU上则可能要跑一夜。毕设时间紧的话,一次训练用epochs=30先看收敛趋势即可。
4.2 训练命令的必调参数:imgsz、batch、device、patience
上述命令里几个参数直接影响训练成败。imgsz是输入分辨率,行人检测用小目标多,建议设为640或更高,设成320会加速但漏检明显增加;batch受显存限制,可以先设16,OOM就降到8或4;device=0指定用第一块GPU,CPU训练则写device=cpu。patience是早停耐心值,默认50个epoch内验证指标没有提升就停,数据量小、指标波动大时可以调大到20。还有一个容易被忽略的cache参数,建议设cache=True,会把图片缓存进内存或磁盘,减少磁盘IO等待:
yolo train data=data.yaml model=yolov8n.pt epochs=100 imgsz=640 batch=16 device=0 cache=True name=person_v1缓存会占约两倍图片集大小的内存,不够则写cache=disk。这在普通机械硬盘上训练时提速非常明显。
4.3 训练过程中的监控:从震荡的loss到稳定的mAP50
训练开始后终端会每个epoch打印一行:train/box_loss、cls_loss、dfl_loss、val/box_loss、metrics/precision(B)、recall(B)、mAP50(B)等。毕设答辩时需要展示“模型如何收敛”,最直观的是看训练结束后自动生成的曲线图。训练完的目录里有个runs/detect/person_v1/results.png,里面包含所有loss曲线和mAP曲线。如果你想自己画某一次实验的损失函数曲线图,也可以读CSV再画:
import pandas as pd import matplotlib.pyplot as plt df = pd.read_csv("runs/detect/person_v1/results.csv") plt.figure(figsize=(10, 4)) 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")看曲线的时候注意一个现象:train loss持续下降但val mAP50不涨,大概率是过拟合已经发生,此时可以回退到val mAP最高的那个epoch,也就是weights/best.pt。ultralytics每次训练完成都会在runs/detect/person_v1/weights/下写出best.pt和last.pt,毕设最终要用的永远是best.pt。
4.4 训练中断了怎么办:断点续训
长训练的时候断电或内存崩溃很常见。last.pt就是为断点续训准备的。恢复训练命令:
yolo train resume model=runs/detect/person_v1/weights/last.pt系统会自动读取之前训练到第几个epoch、优化器状态和数据配置,继续往下跑。注意恢复训练时必须使用和之前相同的代码版本和data.yaml路径,否则可能出现类别不匹配或数据重新分割的问题。另外,如果你想修改学习率或增强参数再继续,别用resume,建议用model=last.pt但不加resume,从头以新的配置继续。
5. 避坑指南:行人检测项目最常见的5个翻车点
5.1 现象:训练刚开始就OOM,显存爆掉
很多人在GTX 1660Ti 6G显存上训练,默认batch=16直接OOM。原因是yolov8l或yolov8x在640分辨率下显存占用很高。先检查你用的模型是n/s/m/l/x哪一档。解决方法是换小模型或降batch:
yolo train data=data.yaml model=yolov8s.pt imgsz=640 batch=8 device=0如果还是OOM,把batch=4、imgsz=480,或者开启梯度累积。ultralytics的yolo train没有单独的梯度累积参数,但通过增大batch并配合device=0可以调;如果条件有限,直接用yolov8n并设imgsz=640 batch=4,在6G显存上能跑。
5.2 现象:训练到一半,val mAP50一直是0
最常见的原因是data.yaml的类别和标签文件里的类别ID对不上。比如标签里是0 0.5 0.5 0.1 0.1,但data.yaml里names: ['person', 'rider'],模型以为有两个类,实际上没有类别1的样本,训练时criterion报错或者强行忽略。解决办法是用前面统计标签分布的方法查看类别索引,然后修改data.yaml,保证nc等于实际类别数。还有一种原因是标签坐标是绝对像素值而不是归一化值,导致IoU计算失败。可以抽样一帧图片画框验证:
import cv2 img = cv2.imread("datasets/person/images/train/000001.jpg") h, w = img.shape[:2] with open("datasets/person/labels/train/000001.txt") as f: for line in f: cls, cx, cy, bw, bh = map(float, line.split()) x1 = int((cx - bw/2) * w) y1 = int((cy - bh/2) * h) x2 = int((cx + bw/2) * w) y2 = int((cy + bh/2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), (0,255,0), 2) cv2.imwrite("check.jpg", img)如果框位置明显偏移或超出图像范围,就是标签归一化出了问题。
5.3 现象:小目标和遮挡行人漏检严重
行人检测数据集里,远处的小目标和互相遮挡的行人是最难的部分。YOLOv8默认输入640,小目标只有几个像素,特征提取阶段就容易丢。解决方向有三个:把imgsz从640提升到960或1280,虽然训练变慢,但对小目标提升明显;在data.yaml里关闭过强的mosaic增强,因为mosaic会进一步缩小目标占比;使用官方提供的--augment默认配置也可以在推理时用TTA,但毕设系统一般不用TTA,因为太慢。更实际的做法是调整推理时的conf和iou:
yolo predict model=best.pt source=crowd.jpg conf=0.2 iou=0.4conf=0.2能召回更多候选框,但误检也变多;iou=0.4比默认0.7的NMS更激进,能保留被压制的重叠框。在密集行人场景下,IOU调低比调conf更有用。
5.4 现象:模型推理结果全是0,一个框都没有
如果你是直接用官方yolov8n.pt对行人照片推理,通常能检出;但换成自己训练好的best.pt后没人,一般在模型类索引和数据类别对不上。自己训练的模型类别索引从0开始,但如果data.yaml里names是['person'],而推理脚本里names引用了COCO的80类表,就会出错。解决方法是直接用模型自带names:
model = YOLO("best.pt") names = model.names # {0: 'person'} 这才是模型真实的类别映射不要在推理脚本里硬编码COCO类别名。另一个原因是输入图片通道数不对,有些灰度图被读成3通道后模型虽然不报错,但预测效果极差。用cv2.imread读图后确认img.shape是(H, W, 3)。
5.5 现象:CPU推理速度极慢,误以为是代码死循环
用CPU跑YOLOv8n单张640图片,耗时大约在0.5秒到2秒之间,这在笔记本上算正常。慢的往往不是推理本身,而是视频帧读取、图像缩放和画框后存储阻塞。优化顺序是:先确保输入帧尺寸不超过640,用letterbox而不是直接resize;再用yolo predict带half=True试图半精度推理(CPU不支持的话会提示错误);最后把视频写入从cv2.VideoWriter改成不带质量压缩的avi编码,能显著降低写盘时间。如果你是在Ubuntu 20.04上用纯CPU跑,还建议编译OpenVINO版本,ultralytics里直接指定device=openvino就能用Intel核显加速,速度能提升一倍多:
yolo predict model=best.pt source=video.mp4 device=openvino上面这个操作不用改代码,适合演示现场快速出效果。注意openvino只支持Intel芯片,AMD或Apple Silicon无效。
6. 让毕设更好交差:推理脚本、模型导出和版本管理的一个小习惯
6.1 写一个能对图片、视频、摄像头同时生效的推理入口
源码包里通常会有detect.py,但很多时候只支持一种输入。我习惯封装成三选一,用socket或命令行参数控制:
import argparse from ultralytics import YOLO parser = argparse.ArgumentParser() parser.add_argument("--source", type=str, default="0", help="image_path or video_path or '0' for camera") args = parser.parse_args() model = YOLO("runs/detect/person_v1/weights/best.pt") results = model(args.source, conf=0.3, iou=0.5, imgsz=640, save=True, vid_stride=2) for i, r in enumerate(results): print(f"{i}: {len(r.boxes)} persons detected")vid_stride=2表示视频每隔一帧处理一次,省掉一半计算量。摄像头输入源写"0"会自动打开默认摄像头,毕设演示时非常方便。
6.2 导出ONNX或TensorRT,防止答辩现场没有PyTorch环境
下载的源码包里最常缺的是部署环境。最好提前把best.pt导出成ONNX,就算答辩电脑没有GPU和ultralytics,也能用OpenCV的DNN模块跑。导出命令:
yolo export model=runs/detect/person_v1/weights/best.pt format=onnx dynamic=True opset=12导出后得到best.onnx。验证一下它和PyTorch模型输出是否一致:
import onnxruntime as ort import numpy as np session = ort.InferenceSession("best.onnx") input_name = session.get_inputs()[0].name outputs = session.run(None, {input_name: np.random.randn(1, 3, 640, 640).astype(np.float32)}) print([o.shape for o in outputs])形状通常是(1, 84, 8400),如果没有输出C2f分支的dlf模块,也是正常的。如果你后面要上RK3588等嵌入式板子,优先导出成format=rknn的中间格式,这个标题的热搜里也提到过RK3588部署YOLOv8,假设你和作者都打算往边缘端做,那ONNX这步必须验证无误,否则转移到瑞芯微的NPU上会踩一堆算子不支持的坑。
6.3 把毕设交付变成可复现的工程:一个README和一个requirements.txt
最后想分享一个我自己的交付习惯:不管源码包里原本有什么,在提交毕设前花10分钟写一个最小化requirements.txt和README.md。requirements.txt只写ultralytics==8.x.x、opencv-python、torch这几行,不锁定过多版本;README里写清楚“训练好的模型在weights/best.pt,测试图片放在demo/images,运行python detect.py --source demo/1.jpg即可看到结果”。这个东西能让老师或其他同学在一台新机器上5分钟跑通,也能让两周后的你快速捡起代码。我见过太多毕设项目因为没有README,作者自己答辩前都忘了模型是从哪个数据集训的。把这个习惯带上,你的“基于YOLOv8的行人检测系统”就不只是一个压缩包,而是一个可以被复现、被评分的完整工程。希望帮到你。
本文还有配套的精品资源,点击获取