news 2026/9/14 1:34:56

基于YOLOv5的人群密度检测全流程实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于YOLOv5的人群密度检测全流程实践

简介:这套基于YOLOv5的人群密度检测系统源码项目,面向目标检测、深度学习的开发者与研究人员,适合用来解决真实场景下的人群计数与密度评估问题。项目对标准YOLOv5做了多处改进:采用FasterNet主干网络替换原Backbone,结合Soft-NMS减小重叠目标漏检,并引入OTA最优运输分配优化损失函数,使模型在密集人群中拥有更稳定的检测精度。压缩包共267个文件,大小19.77MB,主要包含Python代码、yaml配置文件、图片样例、ipynb示例、预训练pt权重及配套前端展示页面,可供训练、推理、结果可视化等环节直接参考。已有205人学习浏览,说明该资源具备一定实操参考价值。下载后可获得完整系统代码与改进思路说明,便于复现实验、二次开发,也可在此基础上替换数据集或调整网络结构,快速搭建自己的密集场景检测方案。

1. 人群密度检测为什么落到 YOLOv5 上

人群密度检测不是先教模型数人头。传统密度图回归方法擅长估计稠密人群的总数,但输出是一个热图,拿不到每个人的坐标,出入口流向、区域限流这些后续业务全都要重新开发。而基于 YOLOv5 的人群密度检测系统,本质是先用目标检测找到画面里的每个人,再把坐标按区域或全图聚合,生成人数、热力图和密度等级。这个方案在密集场景下的推理速度足以接入实时视频流,同时训练链路里从标注、训练、自动标注到部署的每一步都有成熟工具,对做安防、零售客流、城市管理项目的工程师来说,是快速落地的首选路径。下面会从数据集准备、模型训练、自动标注到服务化部署,把每一步的参数和坑都拆开讲。

2. 选型原理与数据集准备:从检测头到标注格式

2.1 YOLOv5 的检测头为什么适合人群密度估计

YOLOv5 在 Backbone 和 Neck 之后保留三个检测头,分别在 80×80、40×40、20×20 的特征图上预测小、中、大目标。对人群密度检测来说,最关心的是 80×80 这一层,因为隔着通道或站在画面远端的人,在 640×640 输入下往往只有十几个像素宽,主要靠高分辨率特征层召回。默认 anchor 是 COCO 八十类目标聚类出来的,“人”只占其中一类,密度高且互相遮挡时,我一般会先对自有人群数据重新聚类 anchor,而不是直接拿默认值硬训。

从工程选型看,YOLOv5s 的计算量约为 16.5 GFLOPs,在 1080Ti 级别显卡上跑 640 分辨率能做到实时。对比 YOLOX 或 YOLOv8,YOLOv5 的部署生态更成熟,TensorRT、OpenVINO、ncnn 的示例多,改造起来省事。小团队做密度项目,最贵的不是训练而是数据标注和后期调试,YOLOv5 的工程结构把这一步的门槛压得比较低。对比 CSRNet 这类纯密度图回归模型,基于检测的方案多付出的计算量不大,但能同时输出每个行人的坐标,为后续的轨迹分析、区域拥挤预警提供基础。

方案输出粒度适合场景模型规模
密度图回归全图密度只有总数要求,人群极密集
YOLOv5 检测每人的 bbox需要计数+位置+轨迹
检测+跟踪每个人 ID出入口流向、限流

2.2 把 COCO 标注转成 YOLO 格式的脚本

数据集准备的第一步是统一标注格式。COCO 的 JSON 转 YOLO 的 txt 是很常见的操作,下面的脚本可以直接改路径使用:它按images的 id 找到图片宽高,再把bbox转成归一化的中心点坐标。这里只保留“人”这一类,所以类别 id 写 0。

import json import os def coco_to_yolo(json_file, save_dir): with open(json_file, 'r', encoding='utf-8') as f: data = json.load(f) img_info = {img['id']: img for img in data['images']} os.makedirs(save_dir, exist_ok=True) for ann in data['annotations']: img = img_info[ann['image_id']] h, w = img['height'], img['width'] x, y, w_box, h_box = ann['bbox'] # coco 为左上角+宽高 cx = (x + w_box / 2) / w cy = (y + h_box / 2) / h bw = w_box / w bh = h_box / h txt_path = os.path.join(save_dir, img['file_name'].replace('.jpg', '.txt')) with open(txt_path, 'a') as f: # 类别 id 按实际需求映射,这里写“人”这一类 f.write(f"0 {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}\n")

代码逻辑说明:ann['bbox']给的是左上角横纵坐标和宽高,先换算成中心点,再分别除以图片宽高做归一化。类别 id 的映射需要和后续data.yaml中的names对应。如果只检测人,全部写 0;如果同时区分坐姿、站姿,需要在构建 JSON 时就区分类别 id。常见错误是忘记过滤宽高小于 3 像素的标注,这些“超小人头”会干扰 anchor 学习,导致模型对细节敏感却分不清背景。

2.3 人群数据集的划分与增强策略

数据集的划分方式直接影响训练曲线是否可信。我会按场景划分:同一个监控点的白天、夜晚、雨天放到同一个集合,而不是随机打散。随机划分容易让模型记住背景,验证集 mAP 虚高,一换场景就现原形。参考比例是 7:2:1,如果负样本多,训练集可以再提两个百分点。

参数推荐值说明
训练集70%人群图片 + 少量空场景负样本
验证集20%用于调整 conf-thres 和训练早停
测试集10%最后跑一次 mAP 后不再改动

数据增强方面,YOLOv5 的hyp.scratch.yaml默认启用 mosaic、mixup、随机平移。对密集人群,mosaic 会拼出多个人头重叠的样本,有用;但hsv_hshear不需要调得太大,否则会改变衣物和肤色色调,模型学到错误的颜色关联。我一般把scale设成 0.5,让模型适应相机远近变化。别忘了负样本:纯马路、无人候车厅每类放 50 张以上,否则推理时很容易把座椅阴影判成人,这一点比任何超参数调整都更划算。

3. yolov5 环境配置与训练自己的数据集

3.1 yolov5 环境配置的常见做法

训练自己的数据集,先把环境跑通。常见做法是用 conda 建隔离环境,Python 选 3.9,PyTorch 选与 GPU 驱动匹配的预编译版本。下面是我惯用的安装序列:

conda create -n crowd python=3.9 -y conda activate crowd pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 git clone https://github.com/ultralytics/yolov5 cd yolov5 pip install -r requirements.txt

参数说明:cu118表示 CUDA 11.8 的预编译包,驱动支持 CUDA 12 就改成cu121。没有 GPU 时不要加--index-url后缀,直接pip install torch torchvision会装 CPU 版。requirements.txt会补齐 opencv-python、numpy、matplotlib 等依赖。装完跑一次python detect.py --weights yolov5s.pt --source data/images/bus.jpg,看到框出来的人和车,说明环境正常。这一步卡住最常见的是 OpenCV 的libGL.so.1缺失,在服务器上用apt-get install libgl1解决。

3.2 用 YOLOv5 训练人群密度模型的启动命令解析

训练命令我一般写成下面这样,方便把每次实验的输出目录分开:

python train.py --data crowd.yaml --weights yolov5s.pt \ --img 640 --batch-size 16 --epochs 100 --device 0 \ --cache ram --workers 8 --project runs/crowd --name v5s

对应的数据集配置文件crowd.yaml

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

命令说明:--img 640是训练输入分辨率;如果人群普遍很小,可以先试 640,后期再微调 960。--batch-size 16在 11G 显存上接近上限,显存不够就降一半并同步调低--workers--cache ram把图片预读入内存,减少磁盘随机读,但 16G 内存以下不建议开。--project--name只是整理输出目录,我习惯把每次实验单独命名,避免覆盖 best.pt。

提示:如果训练时显存不足,优先把--batch-size降到 8,而不是先调小模型。批次过小会让 BN 统计不稳定,人群密度这种目标分布不均的任务会特别敏感。

3.3 超参数与训练结果的关键指标

YOLOv5 默认读data/hyps/hyp.scratch-low.yaml,我的经验是先改四个参数,再跑一轮短实验看趋势。表格里的默认值来自官方 scratch 配置,实际以你下载的工程为准:

超参数默认值人群场景建议为什么这么调
lr00.010.01 或 0.005学习率过大会在人群密集时更早震荡
hsv_h0.0150.005减少衣物与肤色的色相扰动
fl_gamma0.00.5让模型多关注被遮挡的难例
scale0.50.5保持尺度增强,不改
box0.050.05保持默认,提升定位精度

修改后用--hyp hyp.yaml指定新的超参文件。训练时看runs/crowd/v5s/results.csv,主要关注val/obj_lossmAP_0.5。如果 mAP 不升但 train loss 一直降,大概率是增强过度,或验证集和训练集场景分布不一致。用tensorboard --logdir runs/crowd可以实时看曲线,比一堆 print 更直观。

4. 通过界面操作 YOLOv5 完成自动标注与推理部署

4.1 用界面化自动标注减少人工成本

面对几千张未标注的监控截图,人工标注太慢。常见做法是用已经练好的模型先跑一遍自动标注,再用标注工具人工修正。命令是:

python detect.py --weights runs/crowd/v5s/weights/best.pt \ --source /path/to/new_images \ --save-txt --save-conf --project auto_label --name pass1

参数说明:--save-txt为每张图生成同名 txt 标签文件,--save-conf会把置信度写入标签末尾,方便在标注工具里按分数过滤。生成的标签直接就是 YOLO 格式。但 detect.py 输出的框会包含 conf 值,而 YOLO 标签只取前五个数,所以在自动标注后要手动去掉最后一个分数字段,或者交给支持 conf 的标注工具处理。我一般用 labelImg 打开图片目录和自动生成的 labels 目录,只修正漏检和误检,大约能省六成时间。

阈值设置自动标注场景最终计数场景
conf-thres0.250.35
iou-thres0.450.45
img-size1280640

这个表说明:自动标注阶段宁多勿漏,因为后面有人工修正;最终计数时则要调高置信度,过滤阴影和广告牌上的人脸。具体取舍在第五章展开。

4.2 从检测结果生成密度热力与计数

推理时除了看检测框,还要把密度分布可视化。下面这段脚本用torch.hub.load加载训练好的模型,对单张图统计人数并生成热力图:

import torch import numpy as np import cv2 model = torch.hub.load('ultralytics/yolov5', 'custom', path='runs/crowd/v5s/weights/best.pt', force_reload=True) model.conf = 0.35 model.iou = 0.45 img = cv2.imread('street.jpg') results = model(img) boxes = results.xyxy[0].cpu().numpy() heatmap = np.zeros((img.shape[0], img.shape[1]), np.float32) for x1, y1, x2, y2, conf, cls in boxes: heatmap[int(y1):int(y2), int(x1):int(x2)] += 1.0 heatmap = np.tanh(heatmap / 3.0) * 255 heatmap_vis = cv2.applyColorMap(heatmap.astype(np.uint8), cv2.COLORMAP_JET) overlay = cv2.addWeighted(img, 0.6, heatmap_vis, 0.4, 0)

逻辑说明:每个检测框覆盖的区域加 1,得到密度累加热图;np.tanh(heatmap / 3.0)把高密度峰值压到 255,避免少数密集区域把热力图的颜色整体拉爆。applyColorMap把单通道映射成伪彩色图,再与原图按 0.6/0.4 叠加。要注意results.xyxy[0]是 numpy 数组,在 GPU 上要先.cpu().numpy()再操作。部署时建议把模型先导出成 ONNX,不要在线上反复torch.hub.load

4.3 服务化部署:用 Flask 暴露人群计数接口

有了检测结果,下一步就是把模型包装成服务。我用 Flask 搭最小接口时,会直接处理二进制图片流,避免临时文件带来的磁盘IO和权限问题:

from flask import Flask, request, jsonify import cv2 import numpy as np import torch app = Flask(__name__) def load_model(): model = torch.hub.load('ultralytics/yolov5', 'custom', path='best.pt', force_reload=False) model.conf = 0.35 model.iou = 0.45 return model model = load_model() @app.route('/count', methods=['POST']) def count(): f = request.files['image'] data = np.frombuffer(f.read(), np.uint8) img = cv2.imdecode(data, cv2.IMREAD_COLOR) results = model(img) persons = results.pandas().xyxy[0] return jsonify({'count': len(persons), 'density': 'high' if len(persons) > 20 else 'low'})

代码说明:np.frombuffer接住上传的二进制流,再用imdecode解码,输入输出都是内存数据。model在模块加载时初始化一次,后续请求复用同一个推理会话。多进程部署时每个 worker 会各自加载一次模型,显存不大就只开 2 个 worker。如果延迟压不进 30ms,把模型用 ONNX 导出后用 onnxruntime 推理,检测逻辑不变,只换推理后端。

5. 让密度检测更准确的阈值设置与视频帧验证技巧

5.1 conf-thres 与 iou-thres 在计数场景如何取舍

前面 4.1 的表格结论是:自动标注时用低 conf,计数时用高 conf。自动标注阶段宁多勿漏,最后有人工修正;而最终服务端计数没有人工兜底,conf=0.35能过滤掉大量阴影和广告牌上的人脸。iou-thres控制 NMS 合并力度,密集人群中两个真实行人重叠超过 0.45,取默认值会保留其中一个,等于漏了一个。我把 iou 提到 0.5,减少互相遮挡时的丢失,但代价是某些误检框也会被保留。调整之后必须用同一段视频跑一遍,记录每分钟人数曲线,看是否有随机尖峰。

5.2 用相邻帧计数突变定位漏检时段

一段 10 分钟的监控视频,模型在某个角度突然从 40 人掉到 5 人,原因大多是人群被立柱完全遮挡,或者摄像头发生抖动。定位这类问题不需要看完整段视频,记录帧号和计数,计算一阶差分即可。假设计数结果存在counts列表里,脚本可以这样写:

import numpy as np counts = np.array([42, 41, 40, 8, 6, 38, 39]) diffs = np.abs(np.diff(counts)) abnormal_frames = np.where(diffs > 15)[0] for frame_idx in abnormal_frames: print(f"frame {frame_idx} -> {counts[frame_idx]} to {counts[frame_idx+1]}")

这个技巧很直接:np.diff计算相邻两帧的人数差,阈值设成 15,超过就认为该帧附近发生了漏检或误检。把这些可疑帧单独导出,再和同一时刻的原始画面并排比对,就能快速确定是固定遮挡还是相机角度问题。

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

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

线下给监狱寄信效率太低怎么解决?白马信件小程序足不出户寄家书

频繁跑邮局寄信效率低怎么办?白马信件发信快,信件每日发出。很多家属每次寄信都要专程抽空前往邮局排队,来回耗费不少时间,想要简化寄信流程,可以选择白马信件小程序,在家线上完成家书代寄。线下到邮局寄信…

作者头像 李华
网站建设 2026/9/14 1:32:02

.NET构建与发布系统演进及优化实践

1. .NET构建与发布方式的演进背景2002年微软首次推出.NET Framework时,开发者需要手动编译项目后通过FTP上传到服务器完成部署。随着持续交付理念的普及,2014年推出的.NET Core引入了更现代化的构建系统,支持Docker容器化部署。而今天&#x…

作者头像 李华
网站建设 2026/9/14 1:31:58

BroPHP博客实战:从MVC路由到用户认证与SQL注入防御

简介:面向PHP初学者的简易博客系统项目源码,基于Brophp开发,首页通过动态参数区分不同用户,页面采用布局模板复用公共结构,覆盖文章展示、用户注册、登录会话、后台管理等典型模块。整包共636个文件,以240个…

作者头像 李华
网站建设 2026/9/14 1:31:38

OpenClaw Windows 11 AI助手一键部署与优化指南

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

作者头像 李华