简介:基于YOLOv8的行人检测项目,面向计算机视觉方向的高校学生、教师及企业开发者,尤其适合课程设计、毕业设计、大作业或项目初期立项演示。压缩包共6个文件,其中3个PyTorch权重文件(含轻量版模型、新版本模型及训练得到的最佳权重)可供模型加载与再训练,2个Python脚本分别实现训练流程与视频目标检测,1个数据说明文档帮助快速配置数据集,整体仅15.89MB,结构紧凑、上手门槛低。代码均已在作者本地运行通过,输出核心指标曲线、混淆矩阵、F1分数曲线、精确率-召回率曲线、验证集预测结果及标签分布图,可清晰呈现模型效果,为课程答辩或毕设展示提供有力凭证。目前已有36人学习下载,不仅是拿来即用的完整案例,也适合在源码基础上修改以适配其他检测场景,兼顾入门学习与二次开发。
1. 别再急着解压:YOLOv8 行人检测项目里真正值钱的是什么
从各种渠道拿到「基于YOLOv8的行人检测项目.rar」这类压缩包,第一步往往是解压后直接运行 train.py——三分钟后收获一个 ModuleNotFoundError 和满屏报错。这个项目讲的不是发明了更强的模型,而是把 YOLOv8 行人检测从数据标注、训练到推理组织成一条能复用的流水线。解压后能看到数据集、data.yaml、训练入口和一批已训练权重;换个场景,重新标一批数据就能再训。适合做智慧安防、园区巡检、辅助驾驶感知的从业者,也适合要用"行人检测 + YOLOv8"组合交作业的学生。先说一个反直觉的结论:这类项目最值钱的不是那几百兆权重,而是数据格式和训练参数已经替你踩过一遍坑。下面从环境到训练再到部署,把完整流程拆开讲。
2. 解压后的第一件事:项目目录解读与最小环境搭建
拿到手的 .rar 解压后,我不会先去读 README,而是先列一遍目录。原因很简单:这类项目大概率是从论文实验或者毕设工程里打包出来的,里面既有核心代码,也有一堆一次性产物(旧权重、没用的数据集切片、甚至别人机器的绝对路径配置)。先分清哪些是核心、哪些可以直接忽略,能省掉后面大半天的排查时间。
2.1 解压后先看目录:分清核心文件和一次性文件
常见目录结构大概是下面这样,我按"要不要动"分了三类:
| 路径 | 一般是什么 | 要不要改 |
|---|---|---|
| datasets/images、datasets/labels | 训练图片和 YOLO 格式标注 | 换成自己的数据 |
| data.yaml | 数据集路径、类别名 | 每次训练前必改 |
| train.py 或 train.sh | 训练入口 | 按需调参数 |
| weights/best.pt、weights/last.pt | 训练产物和断点 | 做二次开发用 |
| runs/ | 每次训练的日志、曲线、验证图 | 只看不改 |
| requirements.txt | 依赖清单 | 建议锁死版本 |
如果压缩包里还套了一层同名文件夹,先把路径里的中文和空格去掉。YOLOv8 的配置文件对空格能容忍,但配合第三方库时经常出幺蛾子。另外,runs/目录里往往有上一轮训练留下的 results.csv 和混淆矩阵,这些是判断"原项目训练得怎么样"的最好材料,别急着删。压缩包里的 README 我一般扫一眼但不全信——很多是作者给自己留的笔记,命令是旧版本的,真正可信的是 train.py 里的实际参数和 runs/ 里的日志。
2.2 用 conda 装一个最小环境:先 torch 后 ultralytics
无论压缩包里的 requirements.txt 写了什么,我习惯自己重建一个干净环境,避免把原作者的依赖垃圾也装进来。顺序上有一个关键点:先装 PyTorch,再装 ultralytics。如果先装 ultralytics,pip 会把它依赖的 torch 版本一起拉进来,有时候和你显卡驱动对应的 CUDA 版本不一致,后面跑起来才发现 GPU 根本没用上。这也是搜索"yolov8环境配置"时最容易踩的第一道坎。
conda create -n yolo python=3.10 -y conda activate yolo # 先装 GPU 版 torch,1660Ti 用 cu118 这一套 pip install torch==2.1.2 torchvision==0.16.2 --index-url https://download.pytorch.org/whl/cu118 # 再装 ultralytics,版本锁死,避免 API 漂移 pip install ultralytics==8.2.100说明几个参数:python 3.10 是 ultralytics 支持得很好的版本,3.12 也能跑但部分转 ONNX 的依赖容易踩坑;--index-url指定的是 PyTorch 官方 wheel 源,cu118 对应 CUDA 11.8,1660Ti 这类 Turing 架构显卡完全够用。最后锁死 8.2.100 而不是装 latest,原因后面第 5 章会专门讲,先记住"锁版本"这个习惯。装完先验证 GPU 是否真的参与计算,这一步能过滤掉八成环境问题:
import torch print(torch.cuda.is_available()) print(torch.cuda.get_device_name(0))如果输出True和显卡型号,说明 PyTorch 的 CUDA 环境没问题。如果输出False,多半是 torch 装成了 CPU 版,重新按上面的命令行装一次即可。另外在终端里跑一下nvidia-smi确认驱动版本不要太老,旧驱动对 CUDA 11.8 的兼容性有时候会莫名其妙出问题。
提示:切换显卡或换机器后,训练前先跑一遍
torch.cuda.is_available(),避免在 CPU 上白耗半天。
2.3 用官方权重验证环境:一张图跑通推理
环境就绪后,不要直接跑原项目的训练脚本,先用官方预训练权重跑一次推理。这样能把"环境问题"和"项目代码问题"隔离开:如果官方权重能出框,说明环境是好的,问题在项目代码;如果官方权重都报错,那就回去查环境。
from ultralytics import YOLO model = YOLO("yolov8n.pt") # n 是体积最小的版本,验证环境用正合适 results = model.predict("street.jpg", conf=0.25, device="0") for r in results: for b in r.boxes: cls_id = int(b.cls[0]) conf = float(b.conf[0]) x1, y1, x2, y2 = b.xyxy[0].tolist() print(cls_id, conf, (x1, y1, x2, y2))yolov8n.pt第一次使用会自动下载,大概 6MB 多一点;如果下载失败,手动把权重文件放到当前目录再跑。conf=0.25是置信度阈值,低于这个值的框直接过滤;device="0"指定第一张 GPU,不写的话默认 CPU,速度会慢到让你误以为程序卡死。打印出来的 cls_id 如果包含 0,就是 COCO 的 person 类别,说明这个模型本身就具备行人检测能力。推理脚本能出框,意味着从安装、CUDA、权重加载到后处理的全链路都通了,这时候再进项目代码的坑。
2.4 确认网络结构:从 yaml 到结构图
很多下载这个项目的人会搜"yolov8网络结构图",那是为了画论文插图,不是排查问题。日常开发里更需要确认的是:当前装的 ultralytics 版本认不认识项目里用到的网络结构。做法是用官方 yaml 构建一次模型,不加载权重:
from ultralytics import YOLO model = YOLO("yolov8s.yaml") # 只构建结构,不加载权重 print(model.model)如果项目里改过 head、加过注意力模块,原作者的 yaml 可能和你装的版本不兼容,这一步会直接抛异常。真正要画结构图时,我一般把模型导出成 ONNX,再用 netron 打开,比手画或者打印参数列表直观得多。这一步同时也是后面部署到 RK3588 之类边缘设备的前置步骤,提前把导出链路跑通能少踩很多板端工具链的坑。
3. 准备行人数据集:格式转换脚本与 data.yaml 的四个坑
行人检测和通用目标检测最大的区别在数据:行人是强变形目标,姿态、遮挡、尺度变化都比车辆严重。而 YOLOv8 训练入口不挑数据来源,它只认一种标注格式——每张图对应一个 txt,每行是"类别 中心x 中心y 宽 高",坐标归一化到 [0,1]。所以拿到 COCO、VOC 原始标注,第一步永远是转换。这一章把转换脚本和最容易踩的四个坑一起讲。
3.1 行人数据从哪来:COCO、VOC、CrowdHuman 与 SCB-Dataset3
常见开源数据里挑行人:COCO 的 person 类别(category_id=1)数量多、场景杂,适合做初始训练;Pascal VOC 的 person 类别更干净,适合小规模验证;CrowdHuman 专门为密集行人设计,单人框、遮挡多,适合做难点增强;SCB-Dataset3 是面向行人检测的国产数据集,特点是复杂背景和相机视角更多样,近年来在改进实验里被用得很多。
选数据的核心不是"哪个更好",而是"和你的落地场景像不像"。做园区出入口的,多找平视、近距离的数据;做十字路口远距离感知的,重点补充小目标行人。这个判断会直接影响后面要讲的 imgsz 参数。无论选哪个,最后都要统一成 YOLO 格式,并且最好只保留 person 一个类别——既然项目叫行人检测,就不要把 rider、traffic light 混进来,类别越多 mAP 越容易被稀释。
3.2 把 VOC XML 转成 YOLO 格式:转换脚本与边界处理
VOC 的标注是 XML,每个 object 里有个 bndbox 节点,存的是绝对坐标 xmin/ymin/xmax/ymax。转 YOLO 格式要做的就是把绝对坐标变成相对图片宽高的归一化中心点坐标。转换脚本本身不难,但边界处理才是容易翻车的地方:
import xml.etree.ElementTree as ET import os CLASSES = ["person"] def voc_to_yolo(xml_path, out_dir): root = ET.parse(xml_path).getroot() size = root.find("size") w = int(size.find("width").text) h = int(size.find("height").text) lines = [] for obj in root.iter("object"): if obj.find("name").text not in CLASSES: continue box = obj.find("bndbox") x1 = float(box.find("xmin").text) y1 = float(box.find("ymin").text) x2 = float(box.find("xmax").text) y2 = float(box.find("ymax").text) # 坐标越界裁剪:标注经常超图片边界 x1 = max(0, min(x1, w)); x2 = max(0, min(x2, w)) y1 = max(0, min(y1, h)); y2 = max(0, min(y2, h)) if x2 - x1 < 3 or y2 - y1 < 3: continue # 过滤退化成线的坏标注 cx = (x1 + x2) / 2 / w cy = (y1 + y2) / 2 / h bw = (x2 - x1) / w bh = (y2 - y1) / h lines.append(f"0 {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}") if lines: out_name = os.path.basename(xml_path).replace(".xml", ".txt") with open(os.path.join(out_dir, out_name), "w") as f: f.write("\n".join(lines))代码里两处值得注意:一是坐标裁剪,VOC 数据里存在 bndbox 超出图片宽高的脏标注,不裁剪会出现中心点、宽高大于 1 的情况,YOLOv8 训练时不一定会报错,但 loss 曲线会异常;二是过滤掉宽或高小于 3 像素的框,这种框在缩放后几乎就是单个像素,属于无效监督信号。转换完后用 wc -l 抽查几个 txt,行数应该和 XML 里的 person 标注数对得上。
3.3 把 COCO JSON 转成 YOLO 格式:按类别过滤标注
COCO 的标注是单个 JSON,要把 instances 按 image_id 分组,再用 bbox 字段转换。注意 COCO 的 bbox 格式是 [x, y, width, height],和 VOC 的 [x1, y1, x2, y2] 不一样,抄代码时最容易在这出错:
import json from pathlib import Path def coco_to_yolo(json_path, out_dir, target_cls="person"): coco = json.loads(open(json_path, encoding="utf-8").read()) cat_id = next(c["id"] for c in coco["categories"] if c["name"] == target_cls) img_info = {im["id"]: im for im in coco["images"]} # 只保留目标类别,按图片 id 分组 anns_by_img = {} for ann in coco["annotations"]: if ann["category_id"] != cat_id: continue anns_by_img.setdefault(ann["image_id"], []).append(ann) for img_id, anns in anns_by_img.items(): im = img_info[img_id] w, h = im["width"], im["height"] lines = [] for ann in anns: x, y, bw, bh = ann["bbox"] # COCO: 左上角 + 宽高 cx = (x + bw / 2) / w cy = (y + bh / 2) / h lines.append(f"0 {cx:.6f} {cy:.6f} {bw / w:.6f} {bh / h:.6f}") out = Path(out_dir) / (im["file_name"].rsplit(".", 1)[0] + ".txt") out.write_text("\n".join(lines))这里按target_cls="person"过滤后,很多图片根本没有行人标注,脚本就跳过不生成 txt。这正好契合 YOLOv8 的规则:图片可以没有标注文件,但不能有空的 txt——空 txt 在部分增强逻辑下会引发奇怪的 loss 波动。如果你希望这些图不参与训练,在划分 train/val 时不包含它们即可。
3.4 data.yaml 的写法与 train/val 划分
转换完标注,接下来写 data.yaml。这是整个项目里最容易被忽略、又最容易出问题的文件。一个能用的最小配置长这样:
path: /home/user/datasets/pedestrian train: images/train val: images/val names: 0: person三个坑需要说明。第一,path是基准路径,train和val会拼在它后面,所以train: images/train实际指向/home/user/datasets/pedestrian/images/train;如果把path写成相对路径,一定要确认训练时的当前工作目录在哪,我遇到过好几次在项目根目录能跑、换到子目录就报 dataset not found。第二,nc(类别数)可以不写,ultralytics 会从names推断;但如果你写了nc: 1而 names 里有两个键,它会按 names 为准,容易造成误会,建议直接不写。第三,标注 txt 里的类别 id 必须和 names 的键对齐——转换脚本写死0,names 里也必须是0: person,一旦错位,训练不会报错,但 mAP 永远上不去。做数据划分时常见做法是按 8:2 随机分,注意同一个视频序列的帧别拆散到两个集合,否则验证集会泄漏。
3.5 可视化检查标注:训练前必做的十分钟
转完格式、写完 yaml,我强烈建议抽 5 张图把标注画出来看一眼。这一步能发现脚本没考虑到的坐标系问题(比如某个数据集是 EXIF 旋转过的图,宽高和实际显示不一致)。可视化脚本很朴素:
import cv2 from pathlib import Path img_dir = Path("datasets/pedestrian/images/train") for img_path in list(img_dir.iterdir())[:5]: img = cv2.imread(str(img_path)) h, w = img.shape[:2] txt = img_path.with_suffix(".txt") if not txt.exists(): print("缺标注:", img_path.name) continue for line in txt.read_text().strip().splitlines(): _, 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, 0, 255), 2) cv2.imshow("check", img) cv2.waitKey(0) cv2.destroyAllWindows()如果画出来的框整体左移、右移或者明显偏移,基本可以断定是转换脚本坐标系错了;如果框能正确框住行人,再花两分钟把 train/val 的文件列表整理好,训练数据才算真正准备好。这十分钟能拦下后面几小时的无效训练,性价比非常高。
4. 训练自己的行人检测模型:命令、参数与损失曲线判断
数据准备好了,训练反而是整个项目里"最不需要创造性"的环节。YOLOv8 的命令行入口封装得很好,关键在三点:命令怎么写、参数怎么设、以及怎么判断训练有没有跑偏。很多人把"yolov8训练自己的数据集"理解成"把 epochs 调大然后干等",这是对算力的浪费。下面按我实际的调参顺序讲。
4.1 最小训练命令:从一条命令看懂训练产物
以 1660Ti(6GB 显存)为例,一条能直接跑的训练命令长这样:
yolo detect train \ model=yolov8s.pt \ data=data.yaml \ imgsz=640 \ batch=16 \ epochs=100 \ device=0 \ project=runs \ name=pedestrianmodel=yolov8s.pt用预训练权重做热启动,比从头训练收敛快得多;行人检测这种单类别任务,s 版在精度和速度之间比较均衡。如果显存吃紧,换成yolov8n.pt更保险。训练过程中终端会每 N 个 epoch 打一张表,列里有 box_loss、cls_loss、dfl_loss 和 mAP@0.5;前几个 epoch mAP 是 0 很正常,等类别 loss 降下来自然就上去了。结束后去runs/pedestrian/看产物:weights/best.pt是验证集 mAP 最高的权重,weights/last.pt是最后一个 epoch 的权重,results.csv是每个 epoch 的全部指标明细,还有验证集预测图和混淆矩阵。
4.2 三个必调参数:batch、imgsz、epochs
很多下载的项目里作者会留一个"我调好的参数",但那是在他的显卡和数据量下调的。落到你自己机器上,三个参数必须重新考虑:
| 参数 | 默认值 | 6GB 显存建议 | 说明 |
|---|---|---|---|
| batch | 16 | 8~16(配 amp) | 显存不够就减半,优先保 imgsz |
| imgsz | 640 | 640 | 远处行人小就上 1280,batch 要跟着减 |
| epochs | 100 | 100 + patience=30 | 提前停止开好,别死等到 100 |
imgsz是对行人检测影响最大的参数。行人尺寸分布极不均匀:近处行人占满半张图,远处行人只有十几个像素。640 下采样后小行人特征基本被抹掉,所以场景里有远距离行人需求时,我一般直接用 1280 训练,代价是显存占用约 4 倍,batch 相应减到 4~8。除了这三个,还有两个小而重要的:lr0控制初始学习率,数据量小于 500 张时我一般手动降到 0.005;patience是早停,设为 30 意味着连续 30 个 epoch 验证 mAP 不涨就停,避免干等。另外 ultralytics 默认开 mosaic 等数据增强,小数据集不要关掉,mosaic 相当于免费的合成样本;8.2 版本在最后 10 个 epoch 会自动关闭 mosaic 做精调,这是正常行为,别以为是报错。
4.3 画损失函数曲线:判断训练是否健康
训练跑起来后别只盯着进度条。我习惯每 20 个 epoch 就把results.csv拉出来画一次损失函数曲线图,这是判断训练有没有跑偏最直接的信号。"yolov8画损失函数曲线图"这件事没有官方命令,用 pandas + matplotlib 二十行搞定:
import pandas as pd import matplotlib.pyplot as plt df = pd.read_csv("runs/pedestrian/results.csv") df.columns = [c.strip() for c in df.columns] # 列名带前导空格,先清掉 fig, axes = plt.subplots(1, 3, figsize=(15, 4)) for ax, col in zip(axes, ["train/box_loss", "train/cls_loss", "train/dfl_loss"]): ax.plot(df["epoch"], df[col]) ax.set_title(col) plt.tight_layout() plt.savefig("loss_curves.png", dpi=150)健康的曲线是前 20 个 epoch 快速下降、之后缓慢趋平。如果把val/box_loss也画上去,两条线一起看:train 一直降、val 翘头,就是过拟合,这时候加大数据增强或减小模型规模,都比硬扛 epochs 有效;两条线同时走平,说明模型容量到头了,该加数据或换更大的模型。如果曲线抖动得很厉害,先检查是不是 batch 太小。深度学习训练里"玄学"成分不少,但损失曲线是少有的可靠信号——它如果异常,后面 mAP 一定异常。
4.4 中断恢复与迁移微调:给自己留后悔药
训练中断是常态:断电、显存被别的进程占了、或者你发现参数设错了。ultralytics 提供了后悔药——断点续训:
yolo detect train resume model=runs/pedestrian/weights/last.ptresume会读取last.pt里保存的 epoch、优化器状态和学习率计划,从断点继续而不是从头来。注意它恢复的是"训练状态"而不是单纯的模型权重,所以不要用best.pt续训。续训后会在原 project/name 下新建一个目录保存后续结果,找产出时别去错地方。另一个常见场景是小数据集迁移:行人数据只有几百张时,直接把 backbone 冻住只训练 head:
yolo detect train model=yolov8s.pt data=data.yaml freeze=10freeze=10表示冻结前 10 层(YOLOv8s 的 backbone 基本覆盖),显存占用更小、收敛更快,且不容易在小数据上把预训练特征冲掉。第一轮训完想全量微调,再用best.pt去掉 freeze 训一轮。
提示:resume 只认 last.pt,别用 best.pt 续训。
5. 行人检测常见问题排查:五条现象级记录
训练和部署里翻车是常态,下面这五条是行人检测项目里见过最多、也最容易被带偏的问题。每条按"现象→原因→解决"写,直接对照你自己的报错去查。
5.1 loss 变 nan:先查标签类别号,再调学习率
现象:训练日志里第一个 epoch 的 loss 就是 nan,或者跑几个 batch 后突然变 nan。 原因:最常见是标注 txt 里的类别 id 越界,比如 data.yaml 只定义了0: person,但某个 txt 里第一列写成了1;其次是学习率太大导致梯度爆炸,或者有空 txt 文件参与训练。 解决:先跑一遍 3.5 节的遍历代码,再统计所有 txt 的类别 id 最大值和空文件数量;确认标签没问题后,把lr0从默认 0.01 降到 0.005 或 0.001。顺序不能反——标签问题是根因,只调学习率是掩耳盗铃。
5.2 CUDA out of memory:6GB 显存的取舍
现象:torch.cuda.OutOfMemoryError,或者训练刚启动几十秒就"Killed"。 原因:1660Ti 只有 6GB 显存,yolov8s + imgsz=640 + batch=32的组合本来就不现实;另外 DataLoader 的多进程 worker 也会额外占显存,workers 开太大同样会爆。 解决:按 4.2 的参数表走——batch 降到 8 或 16,开amp=True,workers=2~4;还不行就换yolov8n,或者把 imgsz 降到 480。记住一个原则:对行人检测来说,保 imgsz 比保 batch 更重要,小 batch 配合小学习率一样能收敛。
5.3 mAP 很高但漏检远处行人:小目标问题
现象:验证集 mAP@0.5 在 0.9 以上,换成 1080p 视频后,远处行人漏掉一半。 原因:验证集里的标注框平均面积偏大,真实场景的远处行人只有二三十像素。640 输入下采样 32 倍后,这些行人的特征图区域只有一个点,模型根本学不到"这是行人"。 解决:训练和推理统一用imgsz=1280,batch 相应减半;1280 也压不住就对原图切块推理(SAHI 思路),切成 4 块分别检测再合并;数据层面补充小目标样本或对小目标做复制粘贴增强。别指望加个损失函数分支就能逆天改命,输入分辨率不够,什么 head 改进都白搭。
5.4 导出 ONNX 后结果对不上:预处理与 NMS 的锅
现象:.pt推理能框住行人,转 ONNX 后用 onnxruntime 推理,框少了、置信度也变了。 原因:YOLOv8 推理流程是"预处理(letterbox + 归一化)→ 模型 → NMS",导出 ONNX 时 NMS 默认被拆到模型外。onnxruntime 推理时如果 letterbox 填充方式、置信度阈值、iou 阈值和原来不一致,输出自然对不上。 解决:导出时固定格式,常见做法是用 ultralytics 自带导出:model.export(format="onnx", opset=12, imgsz=640);推理侧复用 ultralytics 的预处理逻辑,不要自己另写一套;最后拿同一张图跑.pt和 onnxruntime,用脚本对比两边框坐标,误差控制在几个像素内才算通过。
5.5 换台机器就报错:环境版本没锁死
现象:在自己机器上好好的,把项目拷给别人或换台服务器,import 直接报错,或者同样的代码结果不同。 原因:ultralytics 迭代快,8.0 和 8.2 之间的 Python API 和命令行参数都有变化;项目大概率只给了代码,没给环境清单。 解决:项目根目录放一份锁死的requirements.txt,明确写ultralytics==8.2.100、torch==2.1.2、torchvision==0.16.2;换机器时用conda env export > environment.yml导出完整环境。还有一个血泪经验:写代码时别依赖过新的 API,比如统一用model.predict()这种稳定接口,别用只在某个小版本里出现过的参数。
6. 把模型用起来:视频计数、热力图与 RK3588 部署路径
训练完的 best.pt 最终要落到场景里。下面讲三个最常用的落地动作:视频推理、密度可视化、边缘设备部署。三者共用同一个模型,但各有各的注意点。
6.1 视频推理与行人计数:最小脚本
from ultralytics import YOLO import cv2 model = YOLO("runs/pedestrian/weights/best.pt") cap = cv2.VideoCapture("street.mp4") while cap.isOpened(): ok, frame = cap.read() if not ok: break res = model(frame, conf=0.3, imgsz=640, device="0")[0] people = 0 for b in res.boxes: if int(b.cls[0]) == 0: people += 1 x1, y1, x2, y2 = map(int, b.xyxy[0]) cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 0, 255), 2) cv2.putText(frame, f"people: {people}", (20, 40), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2) cv2.imshow("pedestrian", frame) if cv2.waitKey(1) == 27: break cap.release() cv2.destroyAllWindows()关键是把conf从训练时常用的 0.25 提到 0.3——视频场景下误检比漏检更影响体验,略调高阈值能过滤掉大量抖动检测。要计数更准确就不要逐帧统计(同一个人会被重复统计),而是检测框中心点跨越画面设定直线时计数,那是另一个话题。
6.2 行人密度热力图:用检测结果画出来的可视化
搜"yolov8可视化热力图"时看到的大多是特征图可视化,但对安防场景,我更推荐用检测结果直接画密度:把每个行人框的中心点叠加成高斯分布,得到的热力图能直观展示拥挤区域。
import numpy as np heat = np.zeros((frame.shape[0], frame.shape[1]), dtype=np.float32) for b in res.boxes: x1, y1, x2, y2 = map(int, b.xyxy[0]) cx, cy = (x1 + x2) // 2, (y1 + y2) // 2 cv2.circle(heat, (cx, cy), 25, 1.0, -1) # 半径 25 像素 heat = cv2.GaussianBlur(heat, (0, 0), sigmaX=15) heatmap = cv2.normalize(heat, None, 0, 255, cv2.NORM_MINMAX).astype(np.uint8) heatmap = cv2.applyColorMap(heatmap, cv2.COLORMAP_JET)sigmaX=15和圆的半径决定热力图平滑程度:大屏展示时半径调大,做量化分析时调小。这个方法的好处在于不依赖模型内部特征,框准了热力图就准,排查问题也更直观。
6.3 RK3588 部署路径:从 .pt 到 .rknn 的常见做法
边缘端部署是目前把这类项目产品化最常见的路径。以 RK3588 为例,标准流程是:best.pt 导出 ONNX,再用 RKNN-Toolkit2 转成 .rknn,最后在板端用 rknn-toolkit-lite2 的 Python API 推理。期间有两点要特别注意:一是 RKNN 转模型默认做 INT8 量化,行人这类小目标对量化误差很敏感,精度可能掉 1~3 个点,建议先用几百张训练图做量化校准;二是 NMS 在导出时已经被拆掉,板端要么自己实现 NMS,要么走 RKNN 的后处理接口,直接拿原始输出会看到一堆重叠框。
我自己的教训是:不要在 mAP 上过度自信。曾经把验证集 mAP 0.93 的模型直接部署到 RK3588,现场视频里小目标全丢,最后定位到是 INT8 量化对低分辨率行人特征图损伤太大。后来养成一个习惯:任何模型上线前,先挑 20 张最难的照片做人工验收,肉眼过了再谈指标。这套流程从数据、训练到部署走完一遍后,你会发现这类 YOLOv8 行人检测项目真正能带走的,不是压缩包里那几个文件,而是你亲手调过的参数和踩过的坑。希望帮到你。
本文还有配套的精品资源,点击获取