简介:面向深度学习目标检测与毕业设计场景,这套基于YOLOv9的行人识别检测计数系统提供了从源码、训练好的模型到评估指标曲线的完整实现。包内共收录186个文件,约62.46MB,核心包括83个Python源码文件用于训练与推理、30个YAML配置便于自定义数据集与模型结构、3个pt权重文件可直接加载使用,另有Jupyter Notebook、CSV评估结果与大量训练/验证批次图片,方便复现训练过程并核对检测效果。当前已有237人学习下载,适合计算机视觉、人工智能、自动化等相关专业学生及开发者快速搭建行人检测计数项目。配套的详细运行教程从环境配置、数据集准备、参数调整讲到测试流程,训练好的模型和评估曲线能帮读者省去从零训练的周折,直接查看精度指标并上手二次开发;同时可通过重参数化脚本对模型进一步优化,目录结构清晰,便于按模块理解数据流转与推理输出。
1. YOLOv9行人识别检测计数系统:这套源码包的拆解与落地
做目标检测项目最怕什么?不是模型选型,而是拿到一个号称能跑的源码包,结果环境配三天、数据集格式对不上、训练完精度上不去,最后连检测脚本都调不通。这套基于YOLOv9的行人识别检测计数系统源码包,我拆完以后的感觉是:它把从数据准备、模型训练、指标评估到推理检测的完整链路都收在一个项目里了,内置了训练好的模型权重和评估指标曲线,拿到手不需要再从零训练就能先跑通检测效果。适合三类人:做毕业设计需要完整技术栈的学生、刚接触YOLO系列想对比v5/v8/v9差异的开发者、以及有行人计数需求但不想从头标注数据的工程人员。后面我会按实际复现顺序,从环境配置讲到训练调参、推理检测,再单独把重参数化这个容易被忽略的提点技巧拆开讲。
2. 环境配置与数据集准备:从零到跑通的完整路径
2.1 环境搭建的两种方式与选型理由
项目使用的是YOLOv9官方仓库的分支代码,基于PyTorch框架实现。环境配置这块,教程里推荐的是Anaconda + PyCharm的组合,这也是目前做深度学习项目最主流的搭配方式。Anaconda负责创建独立的Python虚拟环境,避免不同项目之间的包版本冲突,PyCharm则提供可视化的运行调试界面,尤其适合学生和刚入门的人,因为终端命令不熟的时候,直接在PyCharm里点运行按钮比敲命令更容易上手。
我实际复现时的建议顺序是:先装Anaconda,创建虚拟环境并指定Python版本(YOLOv9官方要求Python 3.8以上,建议直接用3.9或3.10),然后激活环境安装依赖包,最后在PyCharm里把Anaconda的解释器导入进来。创建环境的命令如下:
conda create -n yolov9 python=3.9 -y conda activate yolov9创建好虚拟环境后,接下来安装依赖。项目根目录下有requirements.txt文件,里面列出了所有需要安装的包。安装时直接使用pip命令即可:
pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple这里用清华源是为了加速下载,因为PyTorch、torchvision这类包体积比较大,直接从官方源下载在国内网络环境下很容易超时。如果显卡驱动和CUDA已经装好,PyTorch会自动调用GPU进行加速运算。没有NVIDIA显卡的机器也不用慌,训练时指定device为cpu即可,只是速度会慢很多,后面会详细说。
2.2 YOLO格式数据集的结构与制作方法
这套行人检测系统使用的是YOLO格式的目标检测数据集。在开始训练之前,必须先搞清楚这个格式的组织方式,因为后续修改配置文件、训练脚本都和它直接相关。
YOLO格式的数据集结构如下:
dataset/ ├── images/ │ ├── train/ # 训练集图片 │ └── val/ # 验证集图片 ├── labels/ │ ├── train/ # 训练集标注文件(txt格式) │ └── val/ # 验证集标注文件 └── data.yaml # 数据集配置文件每张图片对应一个同名的txt标注文件,文件内容每一行代表一个目标,格式是:
class_id center_x center_y width height注意这里的坐标都是归一化后的值,范围在0到1之间,不是像素坐标。class_id从0开始编号,对应data.yaml里names列表的索引。
如果你手头只有VOC格式的xml标注或者COCO格式的json标注,需要先转换成YOLO格式。这里我给一个XML转YOLO的Python脚本供参考:
import xml.etree.ElementTree as ET import os def convert_voc_to_yolo(xml_file, class_names, output_dir): """ 将VOC格式的XML标注转换为YOLO格式的txt标注 参数: xml_file: VOC XML文件路径 class_names: 类别名称列表,索引即类别ID output_dir: 输出txt文件的目录 """ tree = ET.parse(xml_file) root = tree.getroot() # 获取图片尺寸 size = root.find('size') img_w = int(size.find('width').text) img_h = int(size.find('height').text) lines = [] for obj in root.iter('object'): name = obj.find('name').text if name not in class_names: continue # 类别ID在names列表中的索引 class_id = class_names.index(name) # 获取边界框坐标 bndbox = obj.find('bndbox') xmin = float(bndbox.find('xmin').text) ymin = float(bndbox.find('ymin').text) xmax = float(bndbox.find('xmax').text) ymax = float(bndbox.find('ymax').text) # 转换为YOLO格式的归一化坐标 center_x = (xmin + xmax) / 2 / img_w center_y = (ymin + ymax) / 2 / img_h width = (xmax - xmin) / img_w height = (ymax - ymin) / img_h lines.append(f"{class_id} {center_x:.6f} {center_y:.6f} {width:.6f} {height:.6f}") # 写入输出文件 base_name = os.path.splitext(os.path.basename(xml_file))[0] with open(os.path.join(output_dir, f"{base_name}.txt"), 'w') as f: f.write('\n'.join(lines)) # 使用示例 class_names = ['person'] # 只有行人一个类别 convert_voc_to_yolo('annotation.xml', class_names, './labels/train')这个脚本的核心逻辑是解析XML里的边界框坐标,然后按照YOLO格式的公式进行归一化转换。转换时要注意两点:一是类别名称必须和后续yaml文件里的names列表严格对应;二是如果图片里有被截断的目标,标注时仍然要标全,否则训练时模型会学到错误的边界。
2.3 数据配置文件data.yaml的修改要点
代码里自带的data目录下有一个参考用的yaml配置文件,格式如下:
# 数据集配置文件 train: data/images/train # 训练集图片所在路径 val: data/images/val # 验证集图片所在路径 # 类别定义 names: 0: person修改时重点是三个地方:train路径指向训练集图片文件夹、val路径指向验证集图片文件夹、names列表换成自己数据集的实际类别。路径建议使用相对路径,这样项目整体移动时不需要重新修改。如果你是使用绝对路径,要注意Windows系统下分隔符是反斜杠,容易和字符串转义冲突,建议统一用正斜杠。
3. 训练配置与调参:train_dual.py核心参数逐项拆解
3.1 训练脚本的整体逻辑与参数体系
train_dual.py是这套系统的主训练脚本,它基于YOLOv9官方训练代码二次封装而来,核心逻辑和Ultralytics YOLO系列保持一致。脚本通过argparse接收命令行参数,也支持在PyCharm里直接配置运行参数。为了方便理解,我把最关键的几个参数按功能分组说明。
| 参数类别 | 参数名 | 默认值 | 作用 |
|---|---|---|---|
| 模型配置 | weights | yolov9-s.pt | 预训练权重路径 |
| 模型配置 | cfg | models/detect/yolov9-c.yaml | 模型结构配置文件 |
| 数据配置 | data | data/banana_ripe.yaml | 数据集配置文件路径 |
| 超参数 | hyp | hyp.scratch-high.yaml | 学习率、数据增强等超参数 |
| 训练设置 | epochs | 100 | 训练轮数 |
| 训练设置 | batch-size | 16 | 批次大小,受显存限制 |
| 训练设置 | device | 0 | 使用的GPU编号或cpu |
| 训练设置 | close-mosaic | 15 | 最后N轮关闭马赛克增强 |
| 训练设置 | img | 640 | 输入图片尺寸 |
其中weights参数如果你填入yolov9-s.pt,脚本会自动下载对应的预训练权重到项目根目录。如果网络不好,也可以先从其他地方下载好权重文件放到根目录,脚本会优先加载本地文件。cfg参数是模型结构定义文件,yolov9-c.yaml对应的是带CSP模块的完整版本,yolov9-s则在精度和速度之间做了平衡,显存不足时优先用s版本。
3.2 直接运行与命令行运行的两种方式对比
方式一是在PyCharm里直接配置参数运行,适合新手和调试阶段。具体操作如下:打开train_dual.py,点击右上角的Edit Configurations,在Parameters一栏填入参数,例如:
--weights yolov9-s.pt --cfg models/detect/yolov9-c.yaml --data data/person.yaml --epochs 100 --batch-size 8 --device 0 --close-mosaic 15方式二是在PyCharm的Terminal终端窗口输入完整命令,适合需要频繁调整参数或使用远程服务器的场景。官方示例命令为:
python train_dual.py --workers 8 --device 0 --batch 16 --data data/coco.yaml --img 640 --cfg models/detect/yolov9-c.yaml --weights '' --name yolov9-c --hyp hyp.scratch-high.yaml --min-items 0 --epochs 500 --close-mosaic 15对比来看,方式一更适合本项目场景,因为参数都可视化地展示在配置面板里,修改起来直观。方式二适合批量跑实验时通过脚本循环发起多个训练任务。我一般建议学生先用方式一跑通,再尝试方式二理解命令行参数的真义。
3.3 关键训练参数的调整逻辑与失败排查
实际训练中,参数调整是最影响最终精度的环节。我这里把几个容易翻车的参数单独拎出来讲:
epochs:训练轮数。数据集小(几千张)的情况下,100轮基本可以收敛;数据集大(几万张)建议200轮以上。判断方法很简单,训练结束后看results.csv里的验证集mAP曲线是否已经走平,如果没有走平就加大epochs继续训练。
batch-size:这是和显存强绑定的参数。batch-size乘以图片尺寸决定了一次前向传播占用的显存。8GB显存跑yolov9-s建议batch-size设为8,如果报CUDA out of memory错误,先把batch-size减半再试。
device:单张显卡填0,多显卡填0,1,2,3,没显卡填cpu。CPU训练速度大概比GPU慢20倍以上,如果只是测试代码流程可以用CPU,正式训练一定要用GPU。
close-mosaic:这个参数是YOLOv9新增的,作用是在训练最后N轮关闭马赛克数据增强。马赛克增强会把多张图片拼在一起训练,能显著提升模型鲁棒性,但最后阶段不关闭会导致模型收敛不稳定。一般设置15比较合理。
训练开始后在PyCharm控制台会看到进度条、loss值和mAP指标。当loss值持续下降说明训练正常,当loss值震荡而mAP不动时,可能是学习率太大或者数据标注有问题。训练结束后,在runs/train/目录下会生成weights文件夹,里面的best.pt就是验证集上精度最高的模型,last.pt是最后一轮的模型。results.csv里记录了每一轮的精确率、召回率、mAP50等指标,后面做评估可以直接读取。
4. 检测推理与计数:detect_dual.py的使用与结果输出
4.1 推理脚本参数配置与执行
训练完成后,下一步就是用训练好的模型对图片或视频进行检测。项目中的detect_dual.py是推理脚本,和很多YOLO项目的detect.py不同,这个脚本额外集成了计数逻辑,直接输出目标数量。
修改参数时重点关注以下几项:
--weights runs/train/exp/weights/best.pt # 训练得到的最优权重 --source test_imgs/ # 待检测的图片或视频路径 --conf-thres 0.25 # 置信度阈值,低于此值视为背景 --iou-thres 0.45 # NMS的IoU阈值source参数可以是单张图片、一个文件夹(会遍历文件夹内所有图片)、一个视频文件(如test.mp4),甚至是摄像头设备号(如0表示笔记本自带摄像头)。conf-thres决定检测的严格程度,阈值越高漏检越多但误检越少;阈值越低检测越多但会把背景误判为行人。行人检测场景我建议设置在0.3到0.4之间,既保证召回率又不会太吵。
4.2 检测结果的保存路径与格式说明
运行检测脚本后,结果会保存在runs/detect/目录下,每次运行生成新的exp文件夹。文件夹里包含检测后的图片或视频,图片上画有检测框、类别标签和置信度。如果你需要把检测结果导出为结构化数据,可以在脚本里增加一个txt输出逻辑:
# 在detect_dual.py中找到结果写入的部分,添加以下代码 from pathlib import Path results_dir = Path('runs/detect/exp') output_file = results_dir / 'detection_results.txt' with open(output_file, 'w') as f: for img_path, detections in results.items(): f.write(f"{img_path}: {len(detections)} persons\n") for det in detections: # det格式: [x1, y1, x2, y2, conf, class_id] f.write(f" {det[0]:.1f},{det[1]:.1f},{det[2]:.1f},{det[3]:.1f},{det[4]:.2f}\n")这样输出的txt文件可以直接被后续的数据处理程序读取,比如行车记录仪视频中统计每个时间点的行人数量。注意这里的results变量名是我为了举例写的,实际脚本中对应的变量名是detect_output,需要先打印出来看一下结构再改。
4.3 视频流推流到摄像头的操作
除了检测本地文件,这套系统还支持接摄像头实时画面。只需要把source参数改为摄像头设备号,启动后就会弹出窗口实时显示检测画面。这个功能很适合做演示,但要注意摄像头分辨率过高会导致帧率下降,建议先用1280x720分辨率的视频流测试。
摄像头实时检测的完整命令为:
python detect_dual.py --weights runs/train/exp/weights/best.pt --source 0 --conf-thres 0.3这里source填0表示调用笔记本自带摄像头,外接USB摄像头一般填1。运行效果会实时显示在屏幕上,每帧左上角会显示当前帧的行人数量。我实际测试时发现,如果摄像头画面里行人密集且互相遮挡,计数会出现偏差,这是单目视觉的天然局限,不是因为代码有问题。
5. 避坑与排查:训练和检测中六个高频问题定位
5.1 CUDA out of memory:显存不足的三种处理方式
现象:训练刚启动或运行几个batch后,控制台直接报错CUDA out of memory,程序终止。
原因:batch-size过大,加上图片尺寸和模型参数量共同决定的显存占用超过了显卡可用显存。YOLOv9模型比YOLOv5更大,同样的batch-size占用显存更多。
解决:优先把batch-size减半甚至减到2;其次把img参数从640改为512或416;还不行就换yolov9-s的cfg配置。这三个操作按顺序尝试,一般都能解决。如果显存还是不够,最后一招是启用梯度累积,在训练脚本里设置accumulate参数为4,相当于每个batch内部累积梯度,效果等同于batch-size乘以4的显存需求,但速度会慢。
5.2 训练loss为NaN:学习率与数据问题排查
现象:训练过程中loss值突然变成NaN,进度条消失,模型参数全部变成无效值。
原因:最常见的是学习率初始值过大导致梯度爆炸,其次是数据集中存在像素值异常的图片(如全黑图、全白图)或标注框坐标超出图片边界。第三种情况是训练时用了自动混合精度,而部分GPU型号和PyTorch版本存在兼容性问题。
解决:先改hyp.scratch-high.yaml里的lr0参数,从默认的0.01改成0.001或更小。然后检查数据集的txt标注文件中是否存在大于1或小于0的坐标值,用以下命令扫描所有标注文件:
grep -rE "[1-9][0-9]*\.|-" labels/train/ | head -20如果输出结果中有坐标值异常的行,说明数据集有问题,需要用脚本过滤掉非法标注。最后如果是混合精度问题,在训练脚本中把amp参数改为False。
5.3 验证集mAP为0:类别编号从头验证
现象:训练过程正常,loss值下降,但每轮结束输出的mAP50始终是0。
原因:绝大多数情况下是data.yaml中的names列表顺序和标注文件中的类别ID不对应。比如你的标注文件里类别ID是0,但yaml里names列表第一个元素不是person,就会导致模型认为所有预测都不匹配。
解决:打开一个标注txt文件,看第一列数字是什么。如果标注文件里是0,names列表第一位要是person:
names: 0: person注意不能写成1: person,否则类别ID强制从1开始,和标注文件不匹配。
5.4 训练速度极慢:数据加载瓶颈定位
现象:GPU利用率长时间在20%以下,训练一个epoch要几个小时,明明GPU显存还有大量空闲。
原因:workers参数设置太小,导致数据从磁盘读取到内存的速度跟不上GPU计算速度。也可能是数据图片体积过大,每张图片几MB,读取花费过多时间。
解决:把workers参数从默认值调大到8或16,这个参数在train_dual.py的argparse里可以设置。图片过大则先做一次批量压缩,统一缩放到640x640以下再存回数据集。另外检查train路径下是否不小心把标注文件和图片放在同一个目录,YOLO读取时会卡在文件过滤上。
5.5 检测效果差但训练正常:置信度阈值与模型泛化
现象:训练时mAP60以上,但检测实际图片时漏检严重,甚至完全检测不到目标。
原因:conf-thres阈值设得过高,或者训练集图片和测试集场景差异大(比如训练的是白天场景,测试的是夜间场景)。还有一种可能是使用了best.pt而非last.pt,当训练后期过拟合时,best.pt反而更差。
解决:先把conf-thres降到0.1,看检测框是否出现。如果出现但置信度在0.1到0.2之间,说明模型已经学到了特征但测试场景差异大,建议收集一些测试场景的图片加入训练集做微调。如果置信度接近1还是漏检,说明模型没有学到有效特征,需要检查训练集标注质量。
5.6 计数重复计算:视频连续帧中同一人多计
现象:用视频检测时,同一个行人从画面左侧走到右侧,计数器输出的累计数量远超实际人数。
原因:detect_dual.py的计数逻辑在没有跟踪模块的情况下,每一帧都独立检测并累加,同一人在每帧中都被当作一个新目标计数。
解决:这个场景有两个改法。简单方式是只统计当前帧的行人数量并输出,不做跨帧累加。进阶方式是集成ByteTrack等跟踪算法,给每个检测框分配一个ID,跨帧时通过IoU和外观特征匹配同一个目标。代码层面改动量不大,在每帧检测结果上套一层跟踪逻辑,即可实现持续计数且不重复。
6. 用reparameterization.ipynb做模型重参数化:从yolov9-c到可部署的推理模型
YOLOv9和其他YOLO版本最大的区别在于引入了可编程梯度信息(PGI)和广义高效层聚合网络(GELAN),这两个结构都依赖重参数化技术。简单说,训练时的模型是一个带有额外辅助分支的复杂结构,推理时要把这些辅助分支合并到主干网络里,从而在减少计算量的同时保持精度。这就像是写完代码后做一次代码混淆压缩,功能不变但体积和运行效率都优化了。
项目根目录下的reparameterization.ipynb就是做这个转换的。有些博主说YOLOv9不需要重参数化就能直接部署,那是因为Ultralytics代码里已经在推理时自动处理了。但如果你用的是官方YOLOv9仓库代码,训练得到的pt文件如果没有做重参数化就用detect脚本推理,速度会慢20%左右,因为模型里还包含冗余结构。
打开notebook后,核心执行步骤是读取训练好的best.pt,加载模型结构,执行重参数化后保存为新权重文件。笔记本里的主要代码逻辑如下:
import torch from models.yolo import Model # 加载训练好的模型 model = torch.load('runs/train/exp/weights/best.pt', map_location='cpu') model_model = model['model'] # 融合BN层和卷积层,减少inference时的计算量 model_model.fuse() model_model.eval() # 保存重参数化后的模型 torch.save({ 'model': model_model, 'epoch': -1, 'best_fitness': model['best_fitness'], 'names': model['names'] }, 'runs/train/exp/weights/best_reparam.pt')这段代码最关键的一行是model_model.fuse(),它会把卷积层后面的BatchNorm层合并到卷积层内部,推理时少一次内存读取和计算。执行前需要切换到conda的环境yolov9,因为notebook的kernel默认可能是base环境,直接跑会报import错误,我第一天就被这个问题卡了十分钟。好在切换kernel的操作很简单,在PyCharm的notebook编辑界面右上角选择要求的解释器即可。
重参数化后的模型可以用来对视频做实时检测。你还可以把它部署到服务端,用Flask封装一个HTTP接口,这样前端页面可以上传图片来请求检测结果。这个思路我去年给一个室内监控项目做落地时用过,把小模型重参数化后部署到嵌入式设备上,检测帧率直接翻了一倍,效果立竿见影。
再补充一个经验,训练好的模型如果要在ONNX Runtime或者TensorRT上运行,重参数化是必须的前置步骤。直接拿原始pt转换ONNX,转出来的模型会包含一些推理解析时才支持的操作,导致转换失败。所以正确顺序永远是:训练 → 重参数化 → 导出ONNX → 部署。
从那以后我每次做YOLO系列项目,训练完后第一件事就是看有没有重参数化这个步骤,确认做完了再谈部署和性能优化。如果你用的模型是yolov9-s这种轻量版本,这个优化带来的速度提升没有yolov9-c明显,但精度保持是一样的。整个系统我从环境配置到最终推理走了一遍,最大的感受是YOLOv9的参数确实比v5要多,但每个参数都有明确用途,调参逻辑是通的。希望这篇拆解能帮你少走些弯路,早日把行人检测计数系统跑起来。
本文还有配套的精品资源,点击获取