基于YOLOv5的人群密度检测全流程实践 📅 发布时间:2026/9/14 1:34:56 👁 浏览次数: 简介这套基于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 或 YOLOv8YOLOv5 的部署生态更成熟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, encodingutf-8) as f: data json.load(f) img_info {img[id]: img for img in data[images]} os.makedirs(save_dir, exist_okTrue) 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(f0 {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_h和shear不需要调得太大否则会改变衣物和肤色色调模型学到错误的颜色关联。我一般把scale设成 0.5让模型适应相机远近变化。别忘了负样本纯马路、无人候车厅每类放 50 张以上否则推理时很容易把座椅阴影判成人这一点比任何超参数调整都更划算。3. yolov5 环境配置与训练自己的数据集3.1 yolov5 环境配置的常见做法训练自己的数据集先把环境跑通。常见做法是用 conda 建隔离环境Python 选 3.9PyTorch 选与 GPU 驱动匹配的预编译版本。下面是我惯用的安装序列conda create -n crowd python3.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.yamlpath: /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_loss和mAP_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.35iou-thres0.450.45img-size1280640这个表说明自动标注阶段宁多勿漏因为后面有人工修正最终计数时则要调高置信度过滤阴影和广告牌上的人脸。具体取舍在第五章展开。4.2 从检测结果生成密度热力与计数推理时除了看检测框还要把密度分布可视化。下面这段脚本用torch.hub.load加载训练好的模型对单张图统计人数并生成热力图import torch import numpy as np import cv2 model torch.hub.load(ultralytics/yolov5, custom, pathruns/crowd/v5s/weights/best.pt, force_reloadTrue) 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, pathbest.pt, force_reloadFalse) 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。自动标注阶段宁多勿漏最后有人工修正而最终服务端计数没有人工兜底conf0.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(fframe {frame_idx} - {counts[frame_idx]} to {counts[frame_idx1]})这个技巧很直接np.diff计算相邻两帧的人数差阈值设成 15超过就认为该帧附近发生了漏检或误检。把这些可疑帧单独导出再和同一时刻的原始画面并排比对就能快速确定是固定遮挡还是相机角度问题。本文还有配套的精品资源点击获取