简介:这份资源是面向计算机视觉方向毕业设计与课程设计的一套YOLO行人检测项目,适合需要快速搭建目标检测实验环境的学生与开发者。包内含核心脚本与模型权重配置,共24个文件,以Python脚本、编译缓存pyc、类别/锚框配置txt、测试图片jpg、Shell执行脚本、说明文档md及字体文件等为主,压缩包整体约261KB。通过yolo_predict.py加载预训练模型完成图片或视频中的行人预测,detect.py提供简洁调用接口,gen_anchors.py用于生成YOLO算法的锚框,utils.py与config.py分别承担图像处理与参数配置,predict.sh支持Linux环境下快速执行,目录结构清晰,便于二次开发与课程演示。目前已有85人学习使用,既适合毕业设计、课程设计的完整方案参考,也可作为入门YOLO目标检测的实操资料,帮助理解锚框机制、多尺度预测思路与模型调参方法。
1. 行人检测的 zip 到底值不值得解压
拿到一个叫基于yolo的行人检测.zip的压缩包,多数人的第一反应是解压、找README、跑 demo。但我建议你先把它当成一份「技术方案」来审,而不是当成现成代码来跑——因为 YOLO 行人检测这个方向,真正卡人的从来不是检测框架本身,而是数据集质量、目标尺度分布和部署环境的匹配度。这个包如果组织得好,应该包含四样东西:可复现的环境配置、能直接推理的预训练权重、带标注的行人数据集(或生成脚本)、以及训练/评估的完整流程。这篇文章就按这条链路展开,从选型到训练再到部署避坑,讲清楚每一步怎么做、参数怎么调、翻车了怎么排查,让你拿到任何同类项目都能快速判断它值不值得投入。
2. 选型先行:YOLO 版本与项目结构怎么定
2.1 从 v5 到 v8,行人检测该选哪一代
行人检测属于目标检测里的中尺度目标问题,不像遥感或病理切片那样极端小目标,也不像工业质检那样需要像素级边界。YOLO 系列里,v5 和 v8 是当前落地最主流的两个选择,v5 胜在生态成熟、部署资料多,v8 胜在训练收敛更稳、内置了更灵活的训练策略。
如果你的 zip 里只有yolov5目录,别急着嫌弃老。v5 的detect.py和train.py几十年如一日地稳定,社区里改网络结构的变体几乎都以它为基础,遇到报错搜一下就有答案。v8 的ultralytics包把训练和推理统一成一个 Python API,代码更简洁,但对自定义数据集的组织方式有自己的一套约定,刚上手时会被它的数据集 yaml 配置绕一下。
我的建议很直接:如果是做课程设计或快速验证,选包内已有的版本就行;如果是自己做落地项目,优先看是不是 v8 或更新版本,因为 v8 对数据增强、损失函数做了不少工程化改进,同等数据量下训练出来模型对小尺度行人的召回率通常比 v5 好一点。版本选型本质上是赌生态,不是赌论文指标,所以别只看 mAP,要看你能搜到多少同版本的踩坑帖。
2.2 拿到 zip 后先看这四个文件再动手
不要一上来就pip install -r requirements.txt,先花五分钟检查项目结构。一个组织良好的 YOLO 行人检测项目,至少应该包含以下几类东西:
| 文件/目录 | 作用 | 缺失时的后果 |
|---|---|---|
weights/或runs/ | 预训练权重(.pt或.onnx) | 只能从头训练,耗时成倍增加 |
data/或datasets/ | 行人数据集或下载脚本 | 只能用自己的数据,标注成本高 |
train.py/detect.py | 训练与推理入口 | 需要自己补脚本,工作量大 |
requirements.txt或environment.yml | 依赖锁定 | 环境装完一堆版本冲突 |
我见过不少「基于 yolo 的行人检测」压缩包,解压后只有一堆.py和一个空目录。那种包不是不能用,而是把最脏最累的部分——数据准备和权重获取——留给你自己做。如果你已经有一定基础,这不算坏事,因为自己走一遍数据流程印象更深;但如果你想快速跑通看效果,请优先找带权重的版本。
另外留意包内是否包含标注好的行人数据。有些数据集是 VOC 格式的 XML 标注,有些是 YOLO 格式的 txt 标注,两者转换是大坑之一,后面专门讲。先确认你的包里的标注格式,再决定要不要自己写转换脚本。
3. 环境部署与最小推理:先把预训练权重跑起来
3.1 用 conda 快速搭一套可复现环境
无论包内是 v5 还是 v8,我都建议先用 conda 建独立环境,别往 base 环境里直接装。深度学习项目最怕的不是装不上,而是装上了但把系统 Python 环境搞乱了,后面排查问题全是环境原因。
conda create -n yolo_ped python=3.9 -y conda activate yolo_ped # 先装 PyTorch,版本根据你的 CUDA 版本选 # 如果显卡是 30 系及以上,建议 CUDA 11.8+ pip install torch==2.0.1 torchvision==0.15.2 --index-url https://download.pytorch.org/whl/cu118 # 再装项目依赖,注意先看 requirements.txt 里是否锁版本 cd 你的项目目录 pip install -r requirements.txt这里有一个关键点:PyTorch 一定要先于其他依赖安装。YOLO 训练脚本会在导入时检测 CUDA 是否可用,如果先把numpy、opencv装了再装 torch,某些组合下会出现 OpenCV 与 PyTorch 的 CUDA 运行时冲突,报错信息是libcudart.so: cannot open shared object file,极其玄学。先装 torch 再装其他依赖,这个顺序基本能避开。
装完之后别急着训练,先验证环境是否真的能用 GPU:
python -c "import torch; print(torch.cuda.is_available(), torch.cuda.get_device_name(0) if torch.cuda.is_available() else 'CPU')"如果这里输出False,大概率是 torch 版本和你本机 CUDA 驱动不匹配。注意区分「驱动 CUDA 版本」和「运行时 CUDA 版本」——nvidia-smi显示的 CUDA 版本是驱动支持的,不是 torch 实际使用的。解决办法是卸载 torch 重装对应 cu 后缀的版本,别去改系统环境变量,那是给自己挖坑。
3.2 用一张图片验证模型:最小推理脚本
环境通了的下一步,是拿一张含行人的图片跑通推理。这一步的目的是验证权重文件和模型结构是否匹配,而不是追求检测效果,所以用项目自带脚本或手写最小推理都可以。
如果你用的是 v8,最简洁的方式是这样:
from ultralytics import YOLO # 加载权重,包内如果没有预训练权重,第一次运行会自动下载 model = YOLO("yolov8n.pt") # 推理,source 可以是图片路径、目录、视频或摄像头序号 results = model.predict( source="test_images/pedestrian.jpg", conf=0.25, # 置信度阈值,行人检测建议 0.25 起调 iou=0.7, # NMS 的 IoU 阈值 save=True, # 保存标注后的图片 imgsz=640 # 输入分辨率,和训练时保持一致 )如果是 v5,对应命令是:
python detect.py --weights yolov5s.pt --source test_images/pedestrian.jpg --conf 0.25 --iou 0.7 --imgsz 640跑完看两件事:一是有没有检测出图中的行人,二是检测框是否基本贴合人体轮廓。如果完全没检测出来,先别怀疑权重坏了,先看图片——图中的人物是不是太小?是不是背面/遮挡/暗光?这些场景对行人检测模型是天然压力测试,后面避坑章节会细说。
conf和iou这两个参数是检测效果的第一组旋钮。conf太低会引入大量误检,太高会漏掉被遮挡的行人;iou控制重叠框的合并程度,行人密集场景下iou=0.7往往不够,要调到0.45左右才能避免多个框叠在同一个人身上。记住一个原则:先调conf控制误检率,再调iou控制重叠框,两者互相牵制。
3.3 检测结果怎么看:置信度、NMS 与类别筛选
跑通推理只是开始,你得理解输出里每一列是什么,否则后面调参就是盲人摸象。YOLO 的检测输出通常是一个(N, 6)的张量,每一行对应一个检测框,前四列是框坐标(中心点 x、y 和宽高,或 xyxy 格式),第五列是置信度,第六列是类别 ID。
置信度在行人检测里有特殊意义。COCO 预训练权重里person类别的置信度普遍偏高,因为训练数据里行人样本多、姿态变化大,模型对「像人」的物体有很强的响应。这带来一个典型问题:把雕塑、广告牌上的人形、远处模糊的人影都当成行人。所以行人检测部署时,conf阈值往往要比通用检测高一些,我一般从0.35起步,根据误检率上下浮动。
NMS 相关的坑更隐蔽。YOLO 输出的原始预测框大量重叠,NMS 的作用是把同一目标的多个框合并,iou阈值越低,合并越激进,却可能导致相邻行人被合并成一个框。反过来iou阈值越高,重叠框残留越多,密集行人场景下就会出现「一个人两个框」的翻车现场。调参时记住:行人密度越大,iou阈值应越小。
4. 训练自己的行人检测模型:数据集与参数是核心
4.1 行人数据集怎么选:公开集与自标注的取舍
如果你的 zip 内自带了数据集,省事很多;如果没有,就得自己准备。行人检测的公开数据集有不少,常见的是 COCO 的 person 类、Caltech Pedestrian、CityPersons 和 WiderPerson。它们的共同点是:场景以街景为主,行人姿态覆盖站立、行走、骑行,遮挡情况不一。
但公开集有一个问题:场景分布和你的实际应用大概率不一致。你在园区装监控,训练集全是城市街景,模型对俯视角度、夜间红外、雨天反光的行人会明显掉点。所以常见的做法是「公开集预训练 + 自采数据微调」。先下载公开集训练一个 base model,再标注几百张你自己的场景图做 fine-tune,效果远好于只用公开集或只用自采数据。
行人数据集的标注规范有几个硬性要求。一是标注框要紧贴人体,不要像画风景框一样留大量余白,否则模型学到的行人边界是模糊的;二是遮挡超过 50% 的行人建议不标,否则模型会学到「半个人也是完整的人」这种错误映射;三是图片里出现的小目标行人(高度小于 32 像素)要么放大裁剪后单独训练,要么直接忽略,否则它们会拉低整体训练效率。
有人会问能不能直接爬图自动标注,我的回答是:可以用于预筛选,但必须人工复核。自动标注的框经常出现「框住两条腿」「框住上半身」的问题,这些噪声会让训练好的模型在推理时输出半个身位的检测框,后处理很难修正。
4.2 VOC 标注转 YOLO 格式:转换脚本与四个边界坑
很多公开数据集提供的是 VOC 格式(XML 文件),而 YOLO 训练需要的是每张图对应一个 txt 文件、每行一个class x_center y_center width height、坐标归一化到 0~1。转换脚本是每个行人检测项目绕不开的坎,直接上代码:
import os import xml.etree.ElementTree as ET from glob import glob def voc_to_yolo(xml_path, out_dir, class_names, img_width, img_height): """ 将单个 VOC XML 转换为 YOLO txt 参数: xml_path: XML 文件路径 out_dir: 输出的 txt 目录 class_names: 类别名列表,例如 ['person'] img_width, img_height: 图片原始尺寸 """ tree = ET.parse(xml_path) root = tree.getroot() # 注意:有的 XML 里 size 节点包含宽度高度,有的不包含 # 这里先用外部传入的尺寸,避免读不到 size 时报错 lines = [] for obj in root.findall('object'): cls_name = obj.find('name').text if cls_name not in class_names: continue # 只保留目标类别 cls_id = class_names.index(cls_name) bndbox = obj.find('bndbox') x1 = float(bndbox.find('xmin').text) y1 = float(bndbox.find('ymin').text) x2 = float(bndbox.find('xmax').text) y2 = float(bndbox.find('ymax').text) # 边界处理:把超出图片范围的框裁剪回来 x1 = max(0, min(x1, img_width)) y1 = max(0, min(y1, img_height)) x2 = max(0, min(x2, img_width)) y2 = max(0, min(y2, img_height)) # 过滤掉标注错误的框(宽或高为 0) if x2 <= x1 or y2 <= y1: continue # 转归一化坐标 x_center = (x1 + x2) / 2 / img_width y_center = (y1 + y2) / 2 / img_height w = (x2 - x1) / img_width h = (y2 - y1) / img_height lines.append(f"{cls_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}") # 写入 txt xml_name = os.path.basename(xml_path).replace('.xml', '.txt') with open(os.path.join(out_dir, xml_name), 'w') as f: f.write('\n'.join(lines))这段代码做了三件关键的事:用外部传入的图片尺寸避免 XML 里 size 缺失导致归一化算错;裁剪超出边界的框处理标注软件留下的越界框;过滤掉宽高为 0 的坏标注防止训练时 loss 变 nan。
除了代码本身,还有四个实战中很容易踩的坑。第一,XML 里filename的图片名可能和实际文件名大小写不一致,Linux 下大小写敏感会导致图片找不到,建议转换前统一改名;第二,图片尺寸要以实际读取为准,不能只信 XML 里的<size>,有些数据集的 XML 尺寸和真实图片不一致,最好用 PIL 或 OpenCV 实际读一遍图shape;第三,类别过滤不能少,公开数据集里可能混入rider、bicycle等类别,你需要决定是忽略还是另建类别;第四,生成的 txt 和图片要放在同一目录且同名,YOLO 训练时按同名匹配查找,稍有不一致就会「找不到标签」直接跳过该图。
转换完后一定要抽查统计。写一段小脚本统计每一张图的行人数量,如果出现大量「标注框数量和 txt 行数不一致」的图,说明有标注被过滤了,需要回头检查是越界还是尺寸错误导致的。
4.3 训练命令与关键参数:batch、epoch、anchor 的调法
数据准备好了就可以开训。用 v5 训练自定义数据集的命令是:
# 假设数据集放在 datasets/person/ 下,images 和 labels 两个子目录 # person.yaml 里定义 train/val 路径和类别名 python train.py --data person.yaml --weights yolov5s.pt --img 640 --batch 16 --epochs 100 --device 0用 v8 则是:
yolo train data=person.yaml model=yolov8s.pt imgsz=640 batch=16 epochs=100 device=0第一步是配置person.yaml,它决定了 YOLO 去哪里找数据和类别定义:
path: datasets/person train: images/train # 训练集图片目录 val: images/val # 验证集图片目录 nc: 1 # 类别数,这里只有 person 一类 names: ['person'] # 类别名,顺序与 txt 标注里的 class id 对应三个必调参数是batch、epochs和imgsz。batch不是越大越好,它受显存约束,显存不够时优先减半而不是减小imgsz,因为降低输入分辨率会直接影响小目标行人的检测能力;epochs对于行人检测这个单类别任务,从预训练权重微调时 50~100 轮足够,因为行人检测不是从零学起,过长的训练会过拟合到训练集的特定场景;imgsz是行人检测里最值得加码的参数——同等算力下把 640 提到 768,小目标行人召回率能明显提升,但推理速度会下降约 20%,需要你根据部署设备的能力做取舍。
训练过程中还有两个容易被忽略的开关。一个是 mosaic 增强,v5/v8 默认开启,它把四张图拼成一张训练,对小目标丰富,但对行人这种中尺度目标容易导致目标被切分,如果发现训练集损失一直在降、验证集损失波动很大,可以尝试把mosaic关掉或在最后 10 轮关闭,通常能稳住验证集表现。另一个是 pretrained 权重,优先用 COCO 预训练权重而不是从头训练,COCO 里 person 类占比高,预训练模型已经具备行人特征提取能力,从头训练往往需要 3 倍以上数据量才能达到同样效果。
4.4 训练中的两个信号:损失曲线与混淆矩阵
训练不是把命令丢出去就完事,你得学会读训练日志。YOLO 训练时每个 epoch 会打印 box loss、obj loss、cls loss 和 mAP 指标,四个信号里有三个值得关注:box_loss如果一直不降,说明回归头学不到框的位置,通常是标注框质量太差(框不贴合人体)或学习率过大;obj_loss居高不下,说明目标置信度学不好,常见原因是数据里目标太小、正负样本不平衡;mAP50 能到 0.8 以上但 mAP50-95 很低,意味着检测框位置不够精准,需要提高imgsz或检查标注精度。
训练结束后生成的confusion_matrix.png是一个被大多数人忽略但极其有用的图。很多人看到混淆矩阵里数值总和不是 1 就以为代码出错,其实是 YOLO 的混淆矩阵按「预测类别」做了归一化,每一行代表该类别实际样本被预测到各类的比例,所以行和为 1。如果你是单类别行人检测,混淆矩阵会退化成 3x3(person、background、miss),重点关注 background 那一行:如果有很多「person 被预测为 background」,说明漏检严重,需要降低conf或提高输入分辨率;如果「background 被预测为 person」,说明误检多,需要提高conf或检查是否有太多与环境相似的负样本。
训练完保留什么文件也有讲究。runs/train/exp/weights/下会有best.pt和last.pt,两者都别删——best.pt是验证集上表现最好的权重,用于最终推理;last.pt是最后一轮的权重,当你想继续训练时,用--weights last.pt接着跑比从头跑省一半时间。很多人只留best.pt,后续想加数据扩充训练时只能重新开始,血泪教训。
5. 行人检测部署避坑:五条实测踩坑记录
5.1 小目标行人漏检:调 anchor 和输入分辨率
现象:监控画面里远处的行人完全检测不到,近处的正常;或检测框只有半截身体。原因:YOLO 的 anchor 尺寸覆盖不够,输入分辨率太低导致小目标在特征图上只剩几个像素。解决:优先把imgsz从 640 提到 768 或 896,观察召回率变化;如果仍然漏检,再考虑修改 anchor——在训练命令中加--anchor让 YOLO 根据数据集自动重新计算 anchor,通常 50 轮后 anchor 会重新聚类到更匹配行人比例的尺寸。注意:改 anchor 必须用--cache缓存数据,否则每次训练都重新聚类,结果不稳定。
5.2 密集场景误检翻车:NMS 阈值与置信度联动
现象:地铁站、商场入口这类人流密集场景,一张图里出现十几个框,其中好几个叠在同一个人身上,甚至行人之间互相串框。原因:默认 NMS 阈值iou=0.7太宽松,重叠 70% 的框不会被合并;同时conf=0.25太低放进了很多低质量预测框。解决:把iou降到0.4~0.45,conf提到0.35~0.5,两者配合才能压住密集场景的框爆炸。还有一个容易被忽略的变量:输入分辨率。密集场景下,行人之间的像素间隔本来就小,分辨率越高框越精细,NMS 合并越容易出错,所以密集场景反而建议不要过度提高imgsz,640 往往是更稳的选择。
5.3 训练时 loss 变成 nan:学习率与 BN 层
现象:训练到某一步突然打印的 loss 全是nan,日志里出现RuntimeError: NaN loss,训练立即崩溃。原因:最常见的是学习率过大导致梯度爆炸;其次是标注数据里出现「框坐标超出图片范围」导致的数值异常,虽然转换脚本里做了边界裁剪,但有些数据集原标注本身就包含负数坐标。解决:先检查数据,写脚本统计所有 txt 里是否有小于 0 或大于 1 的坐标,有就清洗掉;如果数据没问题,降低初始学习率,v5 命令行加--lr0 0.001,v8 在配置里设lr0=0.001。另外注意一个隐藏问题:如果batch太小(比如 8),BN 层的统计量不稳定,也可能诱发 loss 抖动,可以把batch提到 16 以上或用--nosave先跑 20 轮观察曲线。
5.4 边缘设备推理卡顿:量化与推理引擎
现象:模型在服务器上检测 30 FPS,部署到 Jetson 或 RK3588 上只剩 3 FPS,完全不可用。原因:PyTorch 的 GPU 推理在边缘设备上跑不出性能,必须经过模型转换和量化。解决:先把权重导出为 ONNX,再转成对应平台引擎格式。转换时注意两个参数:opset版本不能太新,边缘设备上opset=11或者12兼容性最好;量化推荐使用 INT8 量化,行人检测对精度损失容忍度较低,量化后 mAP 掉 2~3 个点可以接受,掉超过 5 个点要检查量化数据集是否覆盖足够多的行人场景。如果你的 zip 项目里没有提供转换脚本,按这个流程走:.pt -> .onnx -> .engine(TensorRT)/ .rknn(RK3588),每一步都要用一张实拍图验证输出框是否变化。
5.5 混淆矩阵总和不是 1:归一化方式搞错了
现象:训练生成的confusion_matrix.png里所有格子加起来不是 1,强迫症犯了以为训练出错。原因:YOLO 的混淆矩阵默认对每行归一化,显示的是「真实类别被预测成各类别的比例」,不是全矩阵归一,所以行和是 1、总和不一定是 1。解决:这个现象不是 bug。真正需要警惕的是对角线之外的值:如果「person 被预测成 background」比例高于 10%,说明漏检率偏高,优先调输入分辨率和conf;如果「background 被预测成 person」比例高于 5%,说明误检严重,优先调conf并检查训练集的负样本(不包含行人的背景图)是否足够。我一般会在项目中同时保留confusion_matrix.png和results.png,前者看类别混淆,后者看损失曲线,两个图对不上时以results.png的损失曲线为准排查训练过程。
6. 用一段视频把整个流程连起来:调试脚本与验证技巧
训练好的模型不能只看静态图效果,行人检测的实际挑战在连续帧里才暴露出来——闪烁的检测框、突然丢帧的人影、镜头抖动导致的误检。我习惯在项目里单独写一个视频验证脚本,把模型放到接近真实部署的环境里压测。
import cv2 from ultralytics import YOLO model = YOLO("runs/train/exp/weights/best.pt") video_path = "test_videos/street.mp4" cap = cv2.VideoCapture(video_path) fps = cap.get(cv2.CAP_PROP_FPS) width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) writer = cv2.VideoWriter("output_video.mp4", cv2.VideoWriter_fourcc(*"mp4v"), fps, (width, height)) frame_count = 0 while cap.isOpened(): ret, frame = cap.read() if not ret: break frame_count += 1 # 每 3 帧推理一次,模拟边缘设备的性能瓶颈 if frame_count % 3 != 0: continue results = model.predict( source=frame, conf=0.35, iou=0.45, imgsz=768, # 和训练时的分辨率保持一致,不要随意改 verbose=False ) for result in results: boxes = result.boxes for box in boxes: x1, y1, x2, y2 = map(int, box.xyxy[0]) cls_id = int(box.cls[0]) conf = float(box.conf[0]) # 只画 person 类别,避免误检类别干扰观察 if cls_id == 0 and conf >= 0.35: cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(frame, f"person {conf:.2f}", (x1, y1 - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2) writer.write(frame) cv2.imshow("Pedestrian Detection", frame) if cv2.waitKey(1) & 0xFF == ord("q"): break cap.release() writer.release() cv2.destroyAllWindows() print(f"视频处理完成,共写入了 {frame_count // 3} 帧")这个脚本模拟了部署态的三个关键变量:跳帧推理对应边缘设备算力瓶颈,推理时锁定imgsz对应训练/推理分辨率不一致导致的精度跳变,只输出置信度 0.35 以上的框对应实际场景里对误检率的硬性要求。逐帧播放视频时重点观察三个现象:检测框是否在相邻帧之间来回跳、一个行人是否被拆成两个框、以及远处的小目标是否存在「出现几帧后消失」的闪烁——前两个问题多半要调 NMS 和置信度,第三个问题直接指向输入分辨率。
验证脚本跑完后,我习惯再用一个只有一行的统计命令看一下整体效果:
python detect.py --weight best.pt --source test_videos/street.mp4 --save-txt --save-conf生成的labels里每帧的检测框数据都保存了,你可以快速统计单帧最多框数、平均置信度,这些数字比肉眼直观得多,也是后续写部署需求文档的依据。
回到开头那个问题:基于 YOLO 的行人检测这个方向,值不值得投入?我的答案是值得,但前提是你要接受一个事实:YOLO 只是骨架,数据集、参数、部署调优才是血肉。一个 zip 包解压跑通 demo 只需要半小时,但要把它变成稳定运行的业务系统,后面还有十倍的工作量在等着你。这也是我写这篇文章的原因——把链路走一遍,你才知道每个环节的分量。希望帮到你。
本文还有配套的精品资源,点击获取