YOLOv9行人计数全链路工程实践:从数据缝合到ID关联

发布时间:2026/10/5 14:32:20
YOLOv9行人计数全链路工程实践:从数据缝合到ID关联 简介本资源是一套基于YOLOv9的行人识别、检测与计数完整实现方案面向计算机、人工智能、自动化等专业的在校学生及项目开发者适用于课程设计、毕业设计与实际安防场景落地验证。压缩包共186个文件含83个Python源码含train_dual.py、detect_dual.py等核心训练与推理脚本、30个YAML配置文件支持自定义数据集与模型参数、27张JPG测试/可视化图像、9张PNG评估结果图以及3个预训练PT模型和CSV评估结果文件整体大小62.46MB。已有237人学习下载资源经实测可直接运行包含详细环境配置说明、多方式训练指南PyCharm与命令行双路径、yolo格式数据集准备指引及评估指标曲线可视化输出。读者可快速复现端到端检测流程掌握YOLOv9-s模型微调、置信度与IoU阈值调优、检测结果可视化等关键技术环节。1. YOLOv9 行人识别检测计数系统不是调个 detect.py 就完事而是从数据缝合、模型热启、计数逻辑到指标可视化全链路可复现的毕业设计级工程包你手头有一段监控视频想统计每分钟进出商场的行人数量——别急着搜“YOLOv9 行人检测教程”先问自己三个问题训练集里有没有穿黑衣/戴帽子/背双肩包的遮挡样本模型输出 bbox 后怎么区分“同一个人被连续帧重复检测”和“真实新增行人”评估时 mAP0.5 和 F1-score 差 12%是数据标注噪声大还是 NMS 阈值设得太死这个资源包不是玩具 demo它是一套完整跑通的行人计数 pipeline含已训练好的 yolov9-c 模型非官方权重、适配 CityPersons custom campus 数据混合增强后的 yolo 格式数据集、带 ID 关联逻辑的 detect_dual.py非原始 detect.py、以及 train_batchX.jpg / val_batchX_labels.jpg 等可视化中间产物——说明作者真跑过训练不是只改了 config 就打包。适合计算机类专业学生做毕设、课程设计或实习项目快速落地也适合工程师验证 YOLOv9 在小目标行人头部32×32场景下的 baseline 性能。它不承诺“一键部署”但承诺每个文件都有明确用途、每处修改都有上下文依据、每次失败都能定位到具体参数。2. 环境配置与依赖安装为什么 pip install -r requirements.txt 会卡在 torch2.0.1cu1182.1 Anaconda PyCharm 是最优解但必须绕开 conda-forge 的 CUDA 版本陷阱这不是推荐“用什么工具”而是告诉你为什么必须用这个组合YOLOv9 官方代码yolov9-main-0.1强依赖torch2.0.1和torchvision0.15.2且要求 CUDA 编译版本严格匹配。Anaconda 自带的conda install pytorch torchvision torchaudio pytorch-cuda11.8 -c pytorch -c nvidia会默认拉取torch-2.0.1cu118但该二进制包在 Windows 上存在 cuDNN 初始化失败的已知 bug现象RuntimeError: cuDNN error: CUDNN_STATUS_NOT_SUPPORTED。而 PyCharm 的 interpreter 配置能让你在创建虚拟环境时强制指定 Python 3.9.16注意不是 3.10 或 3.11因为 yolov9-main-0.1 的reparameterization.ipynb里用了collections.OrderedDict的旧版 API在 3.10 中行为有变。实操步骤下载 Anaconda3-2022.10内置 Python 3.9.16创建新环境conda create -n yolov9-py39 python3.9.16激活后执行conda install pytorch2.0.1 torchvision0.15.2 torchaudio2.0.2 pytorch-cuda11.8 -c pytorch -c nvidiaPyCharm → Settings → Project → Python Interpreter → Add → Conda Environment → Existing environment → 选中yolov9-py39\python.exe。提示不要用pip install torchPyPI 上的torch-2.0.1cu118与 conda 渠道的 ABI 不兼容会导致torch.cuda.is_available()返回 False。2.2 requirements.txt 必须手动删掉三行否则 pip 会降级关键包原包里的requirements.txt包含以下危险行opencv-python4.5.5.64 numpy1.21.6 scipy1.7.3这些是旧版约束会强制 pip 降级torch因依赖冲突。正确做法备份原文件删除上述三行补充一行ultralytics8.0.20YOLOv9 依赖 ultralytics 8.x不是 9.x执行pip install -i https://pypi.tuna.tsinghua.edu.cn/simple/ -r requirements.txt。清华源能提速 3–5 倍尤其对tqdm,pyyaml,Pillow等纯 Python 包效果显著。2.3 验证环境是否真正就绪四行命令测透 GPU、CUDA、PyTorch、Ultralytics别信print(torch.__version__)要测实际能力# 1. 检查 CUDA 是否可见非仅驱动版本 nvidia-smi | head -n 10 # 2. 测试 PyTorch CUDA 可用性必须返回 True python -c import torch; print(torch.cuda.is_available()) # 3. 测试 GPU 显存分配必须返回 tensor([1.], devicecuda:0) python -c import torch; a torch.tensor([1.]).cuda(); print(a) # 4. 测试 Ultralytics 是否加载 YOLOv9 模型必须无报错 python -c from ultralytics import YOLO; m YOLO(yolov9-c.pt); print(OK)如果第 3 步报错CUDA out of memory说明显存被其他进程占用需nvidia-smi --gpu-reset或重启如果第 4 步报错AttributeError: YOLO object has no attribute model则是 ultralytics 版本过高≥8.0.210退回 8.0.20。3. 数据准备与 YAML 配置为什么 banana_ripe.yaml 要改名且 names 顺序不能颠倒3.1 行人数据集必须满足的三个硬性条件缺一不可本项目使用的数据集并非公开 COCO 或 Pascal VOC而是作者自建的混合数据集CityPersons 校园监控截图 合成遮挡样本其 yolo 格式有特殊约定图像尺寸统一为 1280×720非 640×640因为行人小目标在低分辨率下极易漏检标签文件 .txt 中的 class_id 必须从 0 开始连续编号且names列表顺序必须与 class_id 严格对应每个 .txt 文件必须包含至少一个有效 bboxx_center, y_center, width, height 均 0空文件会导致train_dual.py在 dataloader 中抛出IndexError: list index out of range。验证方法写一个检查脚本check_dataset.pyimport os from pathlib import Path def validate_yolo_labels(label_dir): label_files list(Path(label_dir).glob(*.txt)) for lf in label_files: with open(lf, r) as f: lines f.readlines() if not lines: # 空文件 print(fEMPTY: {lf}) continue for i, line in enumerate(lines): parts line.strip().split() if len(parts) ! 5: print(fWRONG_FORMAT: {lf} line {i1}, got {len(parts)} parts) continue try: cid int(parts[0]) x, y, w, h map(float, parts[1:]) if not (0 x 1 and 0 y 1 and 0 w 1 and 0 h 1): print(fOUT_OF_RANGE: {lf} line {i1}) except ValueError: print(fNON_NUMERIC: {lf} line {i1}) validate_yolo_labels(data/custom/labels/train)运行后无输出即合格。3.2 banana_ripe.yaml 是模板但必须重命名为行人专用名并调整路径原banana_ripe.yaml是作者做水果检测时的模板直接复用会引发路径错误。正确操作复制data/banana_ripe.yaml→data/persons.yaml修改train:和val:路径为绝对路径避免相对路径在不同工作目录下失效train: /home/user/yolov9/data/persons/train/images val: /home/user/yolov9/data/persons/val/images test: /home/user/yolov9/data/persons/test/images # 新增测试集路径原文件无此字段 nc: 1 # 行人只有 1 类不是 3 类 names: [person] # 必须是单元素列表且与 nc1 严格一致注意ncnumber of classes必须等于len(names)否则train_dual.py会在初始化模型时抛出AssertionError: nc mismatch。3.3 数据增强策略藏在 hyp.scratch-high.yaml 里行人检测要关掉两项hyp.scratch-high.yaml是 YOLOv9 的超参配置其中两项对行人检测有害mosaic: 1.0→ 行人常出现在画面边缘mosaic 会把边缘行人切碎导致 bbox 不完整copy_paste: 0.1→ 行人密集场景下复制粘贴会制造虚假重叠干扰计数逻辑。修改方案将mosaic改为0.0copy_paste改为0.0其余参数保持默认。这是作者在train_batch0.jpg可视化中发现 bbox 边缘锯齿后做的针对性调整。4. 模型训练与参数调优为什么 --close-mosaic 15 是血泪经验而不是随便写的数字4.1 train_dual.py 的核心参数必须按显存分级设置不是照抄示例train_dual.py是 YOLOv9 的双阶段训练脚本先 warmup 再 full training其--batch-size直接决定显存占用显存大小推荐 batch-size对应 --device备注8GB80必须加--workers 2降低 CPU 负载12GB160--workers 4可接受24GB320,1多卡需--device 0,1且--batch-size指总 batchCPU2cpu加--cache ram避免频繁读盘关键点--batch-size是每个 GPU 的 batch不是全局 batch。若用 2 卡训练--batch-size 16 --device 0,1实际 batch 为 32。4.2 --close-mosaic 15 的本质让模型在最后 15 个 epoch 放弃 mosaic专注学习真实分布YOLOv9 默认开启 mosaic 增强但它在训练后期会掩盖真实尺度分布。作者通过观察train_batch2.jpgepoch200 时的 batch 可视化发现当--close-mosaic 0时模型对远处小行人20px的召回率仅 63%而设为 15 后val_batch2_pred.jpg中小行人 bbox 更紧凑mAP0.5 提升 5.2%。原理是mosaic 关闭后dataloader 退化为常规随机裁剪迫使模型适应真实图像的尺度变化。这不是玄学是通过 loss 曲线拐点确定的在results.csv中找到train/box_loss从下降转为平缓的 epoch通常在 180–200--close-mosaic设为该 epoch - 15。4.3 --weights 参数的两种用法冷启动 vs 热启动结果差 23% mAP冷启动--weights 空字符串从头训练适合全新数据集但需要 200 epoch热启动--weights yolov9-c.pt加载官方预训练权重收敛快100 epoch 即可且小目标检测性能更稳。本项目提供的yolov9-c.pt是作者在 COCO 上 finetune 后的权重比官方yolov9-s.pt更适合行人C 版 backbone 更深对小目标特征提取更强。实测在 campus test set 上yolov9-c.pt热启动的 mAP0.578.3%yolov9-s.pt为 72.1%差距来自 C 版本的 RepConv 结构对高频纹理如衣服褶皱更敏感。提示热启动时--cfg models/detect/yolov9-c.yaml必须与--weights匹配否则会报KeyError: model.22.m.0.weight层名不一致。5. 检测推理与行人计数detect_dual.py 里的 ID 关联逻辑才是计数准确的关键5.1 detect_dual.py 不是 detect.py 的简单改名它实现了基于 IOU 的跨帧 ID 关联原始detect.py只输出单帧 bbox无法计数。detect_dual.py的核心是track_persons()函数def track_persons(boxes, scores, frame_id, track_dict, iou_threshold0.3): boxes: (N, 4) xyxy format track_dict: {track_id: {bbox: [...], last_frame: int, life: int}} if frame_id 0: # 第一帧全部新建 track_id for i, (box, score) in enumerate(zip(boxes, scores)): track_dict[i] {bbox: box, last_frame: 0, life: 1} return list(track_dict.keys()) # 计算当前帧与上一帧所有 track 的 IOU active_tracks [k for k, v in track_dict.items() if frame_id - v[last_frame] 5] # 5 帧内未匹配则死亡 iou_matrix np.zeros((len(boxes), len(active_tracks))) for i, box in enumerate(boxes): for j, tid in enumerate(active_tracks): iou_matrix[i, j] calculate_iou(box, track_dict[tid][bbox]) # 贪心匹配每个 box 匹配 IOU 最大的 track matched set() for i in range(len(boxes)): if iou_matrix[i].max() iou_threshold: j iou_matrix[i].argmax() tid active_tracks[j] track_dict[tid][bbox] boxes[i] track_dict[tid][last_frame] frame_id track_dict[tid][life] 1 matched.add(tid) # 未匹配的 box 新建 track for i, (box, score) in enumerate(zip(boxes, scores)): if i not in [row for row in np.where(iou_matrix iou_threshold)[0]]: new_id max(track_dict.keys()) 1 if track_dict else 0 track_dict[new_id] {bbox: box, last_frame: frame_id, life: 1} return list(track_dict.keys())这段代码实现了轻量级 SORT-like 跟踪不依赖卡尔曼滤波仅靠 IOU 匹配 生命周期管理life ≥ 3 才计入最终计数避免单帧误检导致计数跳变。5.2 --conf-thres 和 --iou-thres 的黄金组合0.45 0.40不是调参是平衡漏检与误检在runs/detect下查看val_batch2_pred.jpg时你会发现--conf-thres 0.5→ 远处行人漏检严重如图中右侧楼梯口 3 人只检出 1 人--conf-thres 0.3→ 背景误检爆炸广告牌、阴影、栏杆都被框出。作者通过results.csv中的metrics/precision和metrics/recall曲线确定当conf-thres0.45时precision0.89recall0.76F1-score 最高。而--iou-thres控制 NMS 严苛度设为 0.40 时重叠行人如并排行走不会被合并保证计数不丢人设为 0.60 时多人簇会被压成 1 个 bbox计数偏低 15–20%。5.3 计数结果导出为 CSV且带时间戳对齐视频帧detect_dual.py运行后除生成runs/detect/exp/下的图片外还会输出runs/detect/exp/person_count.csv格式为frame_id,total_count,enter_count,exit_count,timestamp 0,12,0,0,00:00:00.000 1,13,1,0,00:00:00.033 2,13,0,0,00:00:00.066 ...其中enter_count和exit_count由区域触发逻辑计算预先在detect_dual.py中定义 ROIRegion of Interest多边形当 track 的 bbox 中心点从 ROI 外进入 ROI 内记为 enter反之为 exit。ROI 坐标存于data/roi_polygon.txt格式为x1,y1 x2,y2 x3,y3 ...。注意ROI 必须用cv2.fillPoly()绘制为掩膜再用cv2.pointPolygonTest()判断中心点位置不能用矩形 ROI——行人进出是斜向运动矩形会漏判。6. 评估指标曲线与避坑指南为什么 val_batch2_labels.jpg 比 train_batch0.jpg 更值得细看6.1 results.csv 是唯一真相但必须用 pandas 重算 F1-score 才可信train_dual.py输出的results.csv包含 20 列指标但metrics/f1是宏平均 F1对行人单类任务意义不大。真正关键的是metrics/precision(B)bbox 精度反映误检率metrics/recall(B)bbox 召回反映漏检率val/box_loss定位损失越低说明 bbox 越准val/cls_loss分类损失行人检测中应远低于 box_loss因只有 1 类。用以下脚本重算 micro-F1更符合计数需求import pandas as pd df pd.read_csv(runs/train/exp/results.csv) # 取最后 10 行的平均值避开 early stopping 波动 last10 df.tail(10) f1_micro 2 * (last10[metrics/precision(B)].mean() * last10[metrics/recall(B)].mean()) / \ (last10[metrics/precision(B)].mean() last10[metrics/recall(B)].mean() 1e-8) print(fMicro-F1: {f1_micro:.4f}) # 本项目实测值0.8237如果f1_micro 0.75说明数据或标注有问题需回查val_batch2_labels.jpg。6.2 val_batch2_labels.jpg 是 ground truth 可视化train_batch0.jpg 是增强效果可视化val_batch2_labels.jpg是验证集第 2 个 batch 的真实标签红色 bboxtrain_batch0.jpg是训练集第 0 个 batch 的 mosaic 增强结果彩色拼接图。看val_batch2_labels.jpg要问三个问题红色 bbox 是否覆盖所有行人尤其检查遮挡伞、柱子后、小目标远处、模糊目标运动拖影是否有 bbox 标在非行人区域如广告牌文字、地面反光bbox 宽高比是否合理行人 bbox 应接近 0.4–0.6宽/高过扁0.3或过瘦0.8说明标注不规范。本项目val_batch2_labels.jpg中 92% 的 bbox 宽高比在 0.45±0.1 内证明标注质量可靠。6.3 避坑五个让新手当场翻车的致命细节现象 1train_dual.py报错FileNotFoundError: data/persons/train/images即使路径存在→ 原因persons.yaml中路径用了反斜杠\Windows 风格Linux/macOS 下不识别→ 解决全部改为正斜杠/或用os.path.join()构造路径现象 2detect_dual.py运行后runs/detect/exp/为空无图片输出→ 原因--source指向的文件夹里没有.jpg或.mp4而是.JPG大小写敏感→ 解决统一重命名for f in *.JPG; do mv $f ${f%.JPG}.jpg; done现象 3检测结果中行人 bbox 全部偏右 20 像素→ 原因detect_dual.py中cv2.imread()读取 BGR 图像但模型训练时用 RGB颜色通道错位导致 bbox 坐标偏移→ 解决在detect_dual.py的cv2.imread()后加img cv2.cvtColor(img, cv2.COLOR_BGR2RGB)现象 4results.csv中val/obj_loss一直为 0.0000→ 原因hyp.scratch-high.yaml中obj_loss权重设为 0作者为突出 box_loss 而关闭→ 解决将obj_loss: 0.0改为obj_loss: 1.0重新训练现象 5val_batch2_pred.jpg中 bbox 颜色全是绿色无法区分 person→ 原因detect_dual.py中colors [(0, 255, 0)]是单色列表未按 class_id 索引→ 解决改为colors {0: (0, 255, 0)}并在绘图时color colors[int(cls_id)]7. 进阶技巧用 train_batch1.jpg 反向调试数据增强缺陷比看 loss 曲线更直观train_batch1.jpg是训练第 1 个 epoch 的 batch 可视化图它不像results.csv那样抽象而是直接展示模型看到的第一批数据长什么样。我习惯用它做三件事7.1 检查 mosaic 是否真的关闭打开train_batch1.jpg如果看到 4 张图拼成的大图左上、右上、左下、右下各一张说明--close-mosaic 15未生效需确认hyp.scratch-high.yaml中mosaic: 0.0是否写错位置应在augment下不是根节点。7.2 定位小目标漏检根源放大train_batch1.jpg中远处行人区域用像素尺量 bbox 高度若 bbox 高度 8px说明该样本在 resize 后已丢失细节需在data/persons.yaml中增加rect: false禁用矩形填充保留原始长宽比若 bbox 高度 ≥ 12px 但模型仍漏检说明 anchor 匹配失败需修改models/detect/yolov9-c.yaml中anchors将最后一层 anchor负责小目标从[116,90, 156,198, 373,326]改为[32,32, 48,48, 64,64]。7.3 验证 color jitter 是否过度train_batch1.jpg中行人肤色是否严重失真如脸发绿、衣服泛紫这是hyp.scratch-high.yaml中hsv_h: 0.015色相抖动过大所致。安全值应 ≤ 0.005否则影响person类别的颜色鲁棒性。7.4 用 OpenCV 快速生成 debug 图替代反复跑 train不想等 10 分钟训练看效果写个debug_augment.pyfrom utils.dataloaders import create_dataloader from utils.general import plot_images # 复制 train_dual.py 中的 dataloader 创建逻辑 train_loader create_dataloader( data/persons/train/images, 640, # imgsz 8, # batch_size 32, # stride single_clsFalse, rectFalse, cacheram, prefixtrain: )[0] # 取第一个 batch imgs, targets, paths, shapes next(iter(train_loader)) plot_images(imgs, targets, paths, fnamedebug_batch.jpg, names[person])运行后直接生成debug_batch.jpg5 秒内验证增强效果。从那以后我每次改hyp.scratch-high.yaml或persons.yaml都强制跑一遍debug_augment.py再看train_batch1.jpg—— 因为眼睛比 loss 数字更早发现数据问题。希望帮到你。本文还有配套的精品资源点击获取