news 2026/10/5 14:32:14

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
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 后,怎么区分“同一个人被连续帧重复检测”和“真实新增行人”?评估时 mAP@0.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 会卡在 torch==2.0.1+cu118?

2.1 Anaconda + PyCharm 是最优解,但必须绕开 conda-forge 的 CUDA 版本陷阱

这不是推荐“用什么工具”,而是告诉你为什么必须用这个组合:YOLOv9 官方代码(yolov9-main-0.1)强依赖torch>=2.0.1和torchvision>=0.15.2,且要求 CUDA 编译版本严格匹配。Anaconda 自带的conda install pytorch torchvision torchaudio pytorch-cuda=11.8 -c pytorch -c nvidia会默认拉取torch-2.0.1+cu118,但该二进制包在 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+ 中行为有变。实操步骤:

  1. 下载 Anaconda3-2022.10(内置 Python 3.9.16);
  2. 创建新环境:conda create -n yolov9-py39 python=3.9.16;
  3. 激活后执行:conda install pytorch==2.0.1 torchvision==0.15.2 torchaudio==2.0.2 pytorch-cuda=11.8 -c pytorch -c nvidia;
  4. PyCharm → Settings → Project → Python Interpreter → Add → Conda Environment → Existing environment → 选中yolov9-py39\python.exe。

提示:不要用pip install torch!PyPI 上的torch-2.0.1+cu118与 conda 渠道的 ABI 不兼容,会导致torch.cuda.is_available()返回 False。

2.2 requirements.txt 必须手动删掉三行,否则 pip 会降级关键包

原包里的requirements.txt包含以下危险行:

opencv-python==4.5.5.64 numpy==1.21.6 scipy==1.7.3

这些是旧版约束,会强制 pip 降级torch(因依赖冲突)。正确做法:

  1. 备份原文件;
  2. 删除上述三行;
  3. 补充一行:ultralytics==8.0.20(YOLOv9 依赖 ultralytics 8.x,不是 9.x);
  4. 执行: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.], device='cuda: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 文件必须包含至少一个有效 bbox(x_center, y_center, width, height 均 > 0),空文件会导致train_dual.py在 dataloader 中抛出IndexError: list index out of range。

验证方法:写一个检查脚本check_dataset.py:

import 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(f"EMPTY: {lf}") continue for i, line in enumerate(lines): parts = line.strip().split() if len(parts) != 5: print(f"WRONG_FORMAT: {lf} line {i+1}, 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(f"OUT_OF_RANGE: {lf} line {i+1}") except ValueError: print(f"NON_NUMERIC: {lf} line {i+1}") validate_yolo_labels("data/custom/labels/train")

运行后无输出即合格。

3.2 banana_ripe.yaml 是模板,但必须重命名为行人专用名并调整路径

原banana_ripe.yaml是作者做水果检测时的模板,直接复用会引发路径错误。正确操作:

  1. 复制data/banana_ripe.yaml→data/persons.yaml;
  2. 修改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"] # 必须是单元素列表,且与 nc=1 严格一致

注意:nc(number 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.0,copy_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指总 batch
CPU2cpu加--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.jpg(epoch=200 时的 batch 可视化)发现:当--close-mosaic 0时,模型对远处小行人(<20px)的召回率仅 63%;而设为 15 后,val_batch2_pred.jpg中小行人 bbox 更紧凑,mAP@0.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热启动的 mAP@0.5=78.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_threshold=0.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-thres=0.45时,precision=0.89,recall=0.76,F1-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中定义 ROI(Region 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(f"Micro-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 的真实标签(红色 bbox),train_batch0.jpg是训练集第 0 个 batch 的 mosaic 增强结果(彩色拼接图)。看val_batch2_labels.jpg要问三个问题:

  1. 红色 bbox 是否覆盖所有行人?尤其检查遮挡(伞、柱子后)、小目标(远处)、模糊目标(运动拖影);
  2. 是否有 bbox 标在非行人区域?(如广告牌文字、地面反光);
  3. bbox 宽高比是否合理?行人 bbox 应接近 0.4–0.6(宽/高),过扁(<0.3)或过瘦(>0.8)说明标注不规范。
    本项目val_batch2_labels.jpg中 92% 的 bbox 宽高比在 0.45±0.1 内,证明标注质量可靠。

6.3 避坑:五个让新手当场翻车的致命细节

现象 1:train_dual.py报错FileNotFoundError: data/persons/train/images,即使路径存在
→ 原因:persons.yaml中路径用了反斜杠\(Windows 风格),Linux/macOS 下不识别
→ 解决:全部改为正斜杠/,或用os.path.join()构造路径

现象 2:detect_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)

现象 4:results.csv中val/obj_loss一直为 0.0000
→ 原因:hyp.scratch-high.yaml中obj_loss权重设为 0,作者为突出 box_loss 而关闭
→ 解决:将obj_loss: 0.0改为obj_loss: 1.0,重新训练

现象 5:val_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.py:

from 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_cls=False, rect=False, cache="ram", prefix="train: " )[0] # 取第一个 batch imgs, targets, paths, shapes = next(iter(train_loader)) plot_images(imgs, targets, paths, fname="debug_batch.jpg", names=["person"])

运行后直接生成debug_batch.jpg,5 秒内验证增强效果。

从那以后我每次改hyp.scratch-high.yaml或persons.yaml,都强制跑一遍debug_augment.py,再看train_batch1.jpg—— 因为眼睛比 loss 数字更早发现数据问题。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/5 14:29:02

SoftServe:面向深度学习的轻量级拟牛顿优化器

1. 这不是又一个“优化器替代品”&#xff0c;而是一次对深度学习底层计算范式的重新校准SoftServe 这个名字乍看像某家IT外包公司的产品代号&#xff0c;但当你把它和 Quasi-Newton、deep learning 并列时&#xff0c;它立刻显露出锋利的数学棱角——这不是在 Adam 的参数表里…

作者头像 李华
网站建设 2026/10/5 14:27:56

Windows下dlib安装全指南:从C++编译环境到人脸检测实战

1. 先从原理说起&#xff1a;dlib为什么装起来这么费劲 先说结论&#xff1a;Windows 上装 dlib 本身不难&#xff0c;难的是你缺了一整套 C 编译环境。很多人卡在 pip install dlib 上&#xff0c;看着终端刷了大半屏的 Building wheel for dlib 然后报红&#xff0c;第一…

作者头像 李华
网站建设 2026/10/5 14:25:53

AI Native 团队开发落地手册:CLAUDE.md、Plan Mode 与 Agent 实战

1. 从“AI Native 团队”说起&#xff1a;为什么传统 SDLC 到了必须重写的时候“AI Native 团队完整开发落地手册”这个标题&#xff0c;第一次看到的时候我正带着一个六人小组做内部工具重构。当时我们刚把 CI 流水线跑通&#xff0c;结果发现一个尴尬的事实&#xff1a;代码是…

作者头像 李华
网站建设 2026/10/5 14:25:15

.NET 6 WebApi JWT鉴权实战:从401调试到Token续签

简介&#xff1a;本资源是一套基于.NET 6平台构建Web API并集成JWT身份鉴权的完整实战源码&#xff0c;面向C#后端开发初学者及Web API安全实践者&#xff0c;解决现代API服务中用户认证与授权的核心问题。压缩包含68个文件&#xff0c;总大小1.43MB&#xff0c;涵盖11个C#业务…

作者头像 李华
网站建设 2026/10/5 14:22:56

DeepSeek Harness 省 Token 实战:五个开关把账单压到三成

1. 账单失控的真相&#xff1a;Harness 到底在哪些环节烧 Token很多人第一次打开 DeepSeek Harness 的用量面板时都会愣一下——明明只是让它读几个文件、改两行代码&#xff0c;怎么一天下来 Token 消耗能顶得上手动对话几十轮的用量。我最初也踩过这个坑&#xff0c;一个下午…

作者头像 李华