news 2026/10/22 13:32:54

YOLO人群计数实战:从环境搭建到误差优化

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
YOLO人群计数实战:从环境搭建到误差优化

简介:这份资源面向深度学习与计算机视觉方向的学习者和开发者,提供一套基于YOLO的实时人群计数完整实现方案,可用于火车站、商场、体育场等密集场景的人数统计与监控分析。压缩包共35个文件,约50KB,以18个Python脚本为核心,涵盖模型训练、推理、指标评估与数据转换等环节,另含9个Markdown文档、3个YAML配置、1个Jupyter Notebook及若干辅助文件,分别承担训练指南、参数配置与实验记录等职责。目前已有79人学习下载。资源围绕ShanghaiTech等数据集展开,包含训练计划、损失函数集成、课程排序、伪标签初始化与在线真值更新等模块,并配有本地与Colab云端训练的操作说明及环境配置脚本,便于读者理解从数据准备、模型微调到结果可视化的完整流程,适合希望快速上手人群计数项目或借鉴工程组织方式的开发者参考。

1. 从一张地铁站台照片说起:YOLO 人群计数到底在数什么

早高峰的地铁站台,一张监控截图里挤了上百号人,肉眼数一遍至少五分钟,还容易数漏。基于 YOLO 的人群计数要解决的就是这件事——让模型在图像里把每个人框出来,框的数量就是人数。它和传统密度图回归的思路不一样:密度图输出一张热力图,你只能得到一个总数,不知道每个人在哪;YOLO 走的是检测路线,每个人一个边界框,位置和数量一起给你。这份资源包就是一套能跑起来的 YOLO 人群计数实现,适合做安防监控统计、商场客流分析、活动人数估算的从业者,也适合刚接触深度学习图像识别、想拿一个完整项目练手的新手。它不挑场景,只要你能拿到俯拍或斜拍的密集人群图,就能用同一套流程训练和推理。

2. 环境搭起来:从零把训练管线跑通

2.1 为什么选 YOLO 而不是密度图回归

人群计数这个任务,学术界主流是密度图回归,代表方法有 CSRNet、CAN 这类。它的优势是能处理极端密集场景,几百人叠在一起也能给出一个相对准的总数。但它的短板也很明显:输出是一张和原图同尺寸的热力图,你拿不到每个人的坐标,做不了轨迹跟踪,也没法把某个框单独拎出来看。YOLO 是目标检测框架,输出的是一个个边界框加置信度,天然带位置信息。

那为什么不用 Faster R-CNN?精度上 Faster R-CNN 在中小规模人群里确实稳,但推理速度慢,一张 1080P 图在普通显卡上要几百毫秒,做实时视频流统计就吃力。YOLO 系列在速度和精度之间平衡得更好,YOLOv5s 在 1080P 图上能做到几十毫秒一帧,足够应付大多数监控场景。这份资源包选 YOLO 做底座,思路是对的——人群计数在中等密度(每张图 20 到 200 人)下,检测路线比密度图更实用,因为你能拿到每个人的位置,后续做区域人数统计、越界报警都方便。

提示:如果你的场景是演唱会、春运这种极端密集(单图 500 人以上),YOLO 的框会大量重叠,漏检率上升,这时候密度图回归更合适。选型前先看你的实际图像密度。

2.2 环境依赖与安装步骤

资源包是 Python 项目,依赖 PyTorch 和 YOLO 的实现库。我一般用 conda 建独立环境,避免和系统里的包打架。下面这套命令在 Linux 和 Windows 的 WSL 下都验证过:

# 创建独立环境,Python 版本选 3.8 或 3.9,太新的版本有些库还没适配 conda create -n crowd_yolo python=3.9 -y conda activate crowd_yolo # 安装 PyTorch,根据你的 CUDA 版本选对应命令 # CUDA 11.3 的机器用这条 pip install torch==1.12.1+cu113 torchvision==0.13.1+cu113 -f https://download.pytorch.org/whl/torch_stable.html # 如果没有 GPU,装 CPU 版 # pip install torch==1.12.1 torchvision==0.13.1 # 安装 YOLO 实现库和常用工具 pip install ultralytics opencv-python numpy pandas matplotlib tqdm pyyaml

装完之后验证一下:

import torch print(torch.__version__) print(torch.cuda.is_available()) # 有 GPU 应该输出 True

如果torch.cuda.is_available()返回 False,先别急着往下走。常见原因是 CUDA 版本和 PyTorch 版本对不上,或者显卡驱动太旧。用nvidia-smi看驱动支持的 CUDA 版本,再回来看你装的 PyTorch 是哪个 CUDA 版本编译的。这一步不解决,后面训练会直接掉到 CPU 上,速度差几十倍。

2.3 数据准备:标注格式和目录结构

YOLO 训练需要标注文件,格式是每行一个目标:类别 x_center y_center width height,坐标都是归一化到 0 到 1 之间的值。人群计数通常只有一个类别,就是 person,所以类别 id 固定为 0。

标注工具我一般用 labelImg 或者 CVAT。labelImg 装起来简单,pip install labelImg然后命令行敲labelImg就打开了。画框的时候注意,人群密集处人和人挨着,框要尽量贴紧每个人的可见部分,不要为了省事画一个大框把好几个人圈进去,那样模型学出来会把多人当一个。

目录结构按 YOLO 的标准来:

crowd_dataset/ ├── images/ │ ├── train/ │ │ ├── 001.jpg │ │ └── 002.jpg │ └── val/ │ ├── 101.jpg │ └── 102.jpg ├── labels/ │ ├── train/ │ │ ├── 001.txt │ │ └── 002.txt │ └── val/ │ ├── 101.txt │ └── 102.txt └── data.yaml

data.yaml内容:

path: ./crowd_dataset train: images/train val: images/val nc: 1 names: ['person']

这里有个容易翻车的点:path写相对路径时,是相对于你运行训练命令的目录,不是相对于 yaml 文件所在目录。我见过有人把 yaml 放在数据集根目录,然后在别的目录跑训练,结果路径找不到,报错信息还不明显。稳妥做法是写绝对路径,或者确保你在数据集根目录下执行训练命令。

3. 训练与推理:参数怎么设、结果怎么看

3.1 训练命令与关键参数

环境好了、数据齐了,训练命令其实就一行:

yolo task=detect mode=train model=yolov5s.pt data=crowd_dataset/data.yaml epochs=100 imgsz=640 batch=16

这行命令里每个参数都值得说清楚:

  • model=yolov5s.pt:用 YOLOv5s 预训练权重起步。人群计数数据量通常不大,从预训练权重微调比从头训收敛快得多。s 是 small 版本,参数量小,适合快速验证。如果精度不够再换 m 或 l。
  • epochs=100:训练轮数。人群计数数据集一般几千到几万张图,100 轮是个起点。看验证集 loss 曲线,如果 50 轮就平了,可以早停;如果还在降,加到 200。
  • imgsz=640:输入图像尺寸。人群密集时小尺寸会丢细节,640 是平衡点。如果显存够,可以上 1280,但 batch 要相应调小。
  • batch=16:批大小。显存 8G 以下建议 8 或 16,12G 以上可以 32。batch 太小训练不稳定,太大显存爆。

训练过程中会输出每轮的 loss 和 mAP。人群计数场景下,mAP 不是唯一指标,你更关心的是计数误差。我一般会在验证集上额外算一个 MAE(平均绝对误差),就是预测人数和真实人数的平均差值。MAE 在 5 以内算不错,10 以上说明模型还没学好。

3.2 推理脚本与计数逻辑

训练完得到best.pt,推理脚本这样写:

import cv2 from ultralytics import YOLO # 加载训练好的模型 model = YOLO('runs/detect/train/weights/best.pt') # 读图 img = cv2.imread('test_crowd.jpg') h, w = img.shape[:2] # 推理,conf 是置信度阈值,iou 是 NMS 的 IoU 阈值 results = model(img, conf=0.3, iou=0.5, imgsz=640) # 统计人数 boxes = results[0].boxes count = len(boxes) print(f'检测到人数: {count}') # 画框保存 annotated = results[0].plot() cv2.imwrite('result.jpg', annotated)

逻辑说明:conf=0.3是置信度阈值,低于这个值的框会被丢掉。人群计数里这个值不能设太高,因为密集处的人互相遮挡,置信度天然偏低,设 0.5 会漏掉很多人。我一般从 0.25 到 0.35 之间试,看漏检和误检的平衡。iou=0.5是 NMS 的阈值,两个人挨得近时框会重叠,IoU 设太低会把相邻的人误删,设太高会留下重复框。0.5 是常用起点。

results[0].boxes里每个 box 有xyxy坐标、conf置信度和cls类别。如果你要做区域计数,比如只统计画面下半部分的人,可以遍历 boxes 判断中心点 y 坐标是否在范围内:

count_region = 0 for box in boxes: x1, y1, x2, y2 = box.xyxy[0].tolist() cy = (y1 + y2) / 2 if cy > h * 0.5: # 只统计下半部分 count_region += 1 print(f'下半部分人数: {count_region}')

这个逻辑在商场客流统计里很常用,比如只统计进入某个区域的人数,不关心路过的人。

3.3 评估指标:MAE 和 RMSE 怎么算

光看 mAP 不够,人群计数要看计数误差。写个评估脚本:

import os import numpy as np from ultralytics import YOLO model = YOLO('best.pt') img_dir = 'crowd_dataset/images/val' label_dir = 'crowd_dataset/labels/val' mae_list = [] rmse_list = [] for img_name in os.listdir(img_dir): img_path = os.path.join(img_dir, img_name) label_path = os.path.join(label_dir, img_name.replace('.jpg', '.txt')) # 真实人数 = 标注文件行数 with open(label_path) as f: gt_count = len(f.readlines()) # 预测人数 results = model(img_path, conf=0.3, iou=0.5, verbose=False) pred_count = len(results[0].boxes) mae_list.append(abs(pred_count - gt_count)) rmse_list.append((pred_count - gt_count) ** 2) mae = np.mean(mae_list) rmse = np.sqrt(np.mean(rmse_list)) print(f'MAE: {mae:.2f}, RMSE: {rmse:.2f}')

MAE 反映平均误差,RMSE 对大误差更敏感。如果 MAE 是 5 但 RMSE 是 20,说明大部分图误差小,但有几张图误差特别大,要去看看那几张图是不是密度过高或者遮挡严重。

4. 避坑与排查:那些让我重训三次的问题

4.1 现象:训练 loss 不降,mAP 一直是 0

原因:最常见的是标注格式不对。YOLO 要求坐标归一化到 0 到 1,如果你用 labelImg 存成了 VOC 的 XML 格式,或者坐标没归一化,模型读进去全是乱的。另一个原因是data.yaml里的nc和names对不上,比如nc: 1但names: ['person', 'car']。

解决:打开一个标注 txt 文件,确认每行是 5 个值,且都在 0 到 1 之间。用下面这段脚本批量检查:

import os label_dir = 'crowd_dataset/labels/train' for fname in os.listdir(label_dir): with open(os.path.join(label_dir, fname)) as f: for i, line in enumerate(f): parts = line.strip().split() if len(parts) != 5: print(f'{fname} 第{i}行不是5个值') continue vals = [float(x) for x in parts[1:]] if any(v < 0 or v > 1 for v in vals): print(f'{fname} 第{i}行坐标越界: {vals}')

4.2 现象:推理时框大量重叠,一个人被框了好几次

原因:NMS 的 IoU 阈值设太高了。默认 0.7 在普通检测里没问题,但人群密集时相邻的人框重叠度本来就高,0.7 的阈值会把同一个人的多个候选框都留下。

解决:把iou降到 0.4 到 0.5 之间。如果还重叠,检查是不是训练时标注就有问题——同一个人被标了多个框,模型学出来就会重复检测。

4.3 现象:远处的小目标全部漏检

原因:输入尺寸太小。640 的输入下,远处一个人可能只有十几个像素,YOLO 的下采样到 32 倍后特征图上一个点对应原图 32 像素,小目标直接消失了。

解决:把imgsz提到 1280,同时 batch 减半避免显存爆。如果显存不够,用切片推理——把大图切成小块分别检测再合并,但要注意切边处的人会被切断,需要重叠切分。

4.4 现象:白天准,晚上或逆光就崩

原因:训练集里缺少对应光照条件的样本。模型没见过晚上的人群,自然认不出来。

解决:在训练集里补充夜间和逆光样本,至少占 20%。如果拿不到夜间数据,做数据增强时加亮度扰动和对比度扰动,能缓解但不能根治。

注意:数据增强里的 mosaic 和 mixup 在人群计数里要慎用。mosaic 把四张图拼一起,人群密度会突然变大,模型可能学到错误的密度先验。我一般把 mosaic 关掉或把概率降到 0.1。

5. 进阶技巧:把计数误差再压一压

训练跑通只是第一步,真正上线前还有几个技巧能把误差压下来。第一个是多尺度测试,推理时把同一张图缩放到 640、960、1280 三个尺寸分别检测,再把结果做 NMS 合并。小尺寸抓大目标,大尺寸抓小目标,合并后漏检会少一些。代价是推理时间翻三倍,适合离线统计不适合实时流。

第二个是置信度阈值自适应。固定 0.3 在不同场景下表现不一样,我的做法是先在一小批验证图上跑一遍,画出不同 conf 下的计数误差曲线,选误差最低的那个值。下面这段代码可以帮你快速找:

import numpy as np from ultralytics import YOLO model = YOLO('best.pt') img_path = 'val_sample.jpg' gt_count = 87 # 这张图的真实人数 best_conf, best_err = 0, 999 for conf in np.arange(0.1, 0.6, 0.05): results = model(img_path, conf=conf, iou=0.5, verbose=False) pred = len(results[0].boxes) err = abs(pred - gt_count) if err < best_err: best_err = err best_conf = conf print(f'conf={conf:.2f}, pred={pred}, err={err}') print(f'最佳 conf: {best_conf:.2f}, 误差: {best_err}')

第三个是后处理去重。YOLO 输出的框在密集处偶尔会有两个框套在同一个人身上,NMS 没滤掉是因为 IoU 没到阈值。可以加一个基于中心点距离的去重:如果两个框中心点距离小于较小框宽度的 0.5 倍,就认为是同一个人,保留置信度高的那个。

def dedup_boxes(boxes, dist_ratio=0.5): keep = [] boxes = sorted(boxes, key=lambda b: b.conf, reverse=True) for b in boxes: x1, y1, x2, y2 = b.xyxy[0].tolist() cx, cy = (x1+x2)/2, (y1+y2)/2 w = x2 - x1 dup = False for k in keep: kx1, ky1, kx2, ky2 = k.xyxy[0].tolist() kcx, kcy = (kx1+kx2)/2, (ky1+ky2)/2 kw = kx2 - kx1 if ((cx-kcx)**2 + (cy-kcy)**2) ** 0.5 < min(w, kw) * dist_ratio: dup = True break if not dup: keep.append(b) return keep

这个去重逻辑在商场入口这种人流密集但个体间距小的场景里特别管用,我实测能把重复计数压掉三到五个百分点。

最后说一个习惯:每次换场景部署前,我一定先拿 20 张该场景的图跑一遍,人工数一遍真实人数,和模型输出对比。如果 MAE 超过 10,不调参不上线。这个步骤花不了半小时,但能避免上线后数据离谱的尴尬。从那以后我每次接新场景都强制走一遍这个小验证,希望帮到你。

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

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

SQL Server误删数据恢复实战:解析事务日志生成UNDO脚本

简介&#xff1a;面向SQL Server 2016的数据恢复工具&#xff0c;专为需要处理数据丢失、误删记录等问题的数据库管理员和技术人员准备。工具核心是读取事务日志并生成对应恢复脚本&#xff0c;可在紧急情况下帮助用户找回关键数据&#xff1b;同时支持对删除操作进行解析&…

作者头像 李华
网站建设 2026/10/22 10:53:16

Codex 股票分析与研究:用 AGENTS.md 规范 akshare 量化回测流程

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/20 12:42:36

ADO封装库实战:从Connection到Recordset的核心用法与避坑指南

简介&#xff1a;面向商业开发场景的 ADO 源代码示例包 Ado_Aok_demo.zip&#xff0c;聚焦微软 ActiveX Data Objects 在 C 工程中的实际应用&#xff0c;适合需要在财务、库存、客户关系管理等业务系统中完成数据库连接的开发者参考学习。压缩包共收录 26 个文件&#xff0c;以…

作者头像 李华
网站建设 2026/10/15 18:52:18

Flask+LayUI+MySQL全栈模板实战:从跑通到避坑的完整指南

简介&#xff1a;这是一套基于 Python、Flask、LayUI 与 MySQL 搭建的网站模板&#xff0c;面向具备一定 Python 基础、希望快速构建后台管理或中小型 Web 项目的开发者&#xff0c;尤其适合课程设计、毕业设计与练手项目。资源包共 233 个文件&#xff0c;约 1.29MB&#xff0…

作者头像 李华
网站建设 2026/10/17 13:33:50

pstack-claude:用AI实时解析Linux进程调用栈的调试范式

1. 项目概述&#xff1a;pstack-claude 是什么&#xff0c;它解决的是哪类真实开发痛点&#xff1f;pstack-claude 这个名字乍看像一个工具组合词&#xff0c;但拆开来看&#xff0c;“pstack”是 Linux 系统中一个真实存在的诊断命令&#xff0c;用于打印指定进程的调用栈&…

作者头像 李华