多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

yolov5人群计数与阈值报警:从Coco预训练到工程落地的完整方案

yolov5人群计数与阈值报警:从Coco预训练到工程落地的完整方案 简介面向计算机视觉与公共安防场景这份资源提供基于YOLOv5的实时人群计数与阈值报警实现。采用COCO预训练的person类权重可对室内外不严重拥堵的画面进行人数统计并在超过设定阈值时触发报警适合需要快速部署人群密度监测方案的研究者、学生或安防开发人员。压缩包内共116个文件类型涵盖Python源码、YAML模型配置、PyTorch权重文件、Dockerfile容器化部署脚本、图文教程docx、示例图片与视频以及用于训练和推理的Jupyter Notebook。整体约489.69MB目录结构清晰方便按模块取用。已有2885人学习下载。资源特别附带限时免费GPU云环境的详细运行说明用户可参照图文教程从环境准备、模型加载到视频推理逐步复现同时结合视频输出与阈值报警设置将方案快速迁移到自己的监控场景中对于想了解模型细节的用户Notebook和Python脚本也提供了较好的二次开发起点。1. yolov5人群计数与阈值报警一份能直接复现的资源包人群计数在公共安防里是个刚需场景但真正落地的难点从来不是模型选型而是怎么把检测结果变成一个可用的业务信号。这份资源包基于yolov5的Coco预训练权重锁定person类做人数检测在不大拥堵的室内外环境下能跑出实时帧率同时附带了一个阈值报警的工程思路——人数超过设定值就触发警报。和很多人想的不一样这个活儿并不需要自己标数据、重新训练模型核心工作在于推理链路的配置和报警逻辑的鲁棒性处理。资源里包含Dockerfile、图文教程、两个Jupyter Notebook和测试图片覆盖了从环境搭建到运行检测再到阈值报警的完整闭环。适合三类人一是刚入门yolov5想跑通一个实际场景的开发者二是需要快速验证人群计数可行性方案的产品经理或项目经理三是在云GPU上想省去环境配置时间的算法工程师。接下来我就按实际拆包的顺序把推理链路、报警阈值设计和复现时最容易翻车的几个地方逐一拆开。2. 推理链路拆解从Coco预训练到person类计数输出2.1 预训练权重为什么够用person类在Coco里的特殊性Coco数据集里person类是大类样本数量充足且场景覆盖广从行人、游客到聚集人群都有。这意味着直接拿yolov5s或yolov5m的coco预训练权重做人群计数在不大拥堵的室内外场景下精度是够用的。具体来说yolov5模型输出的80个类别里person对应的class_id是0。做人群计数时只需要在推理阶段过滤出class_id0的检测框即可不需要改动网络结构也不需要重新训练。这一条过滤逻辑是整个计数功能的核心后面所有的人数统计都建立在这个基础上。2.2 最小可运行推理代码加载模型、过滤person类、统计人数打开压缩包里的tutorial.ipynb核心推理逻辑其实很简洁。我拆包后把主干提取如下import torch import cv2 import numpy as np # 加载yolov5s预训练模型缓存到本地 model torch.hub.load(ultralytics/yolov5, yolov5s, pretrainedTrue) # 关键只保留person类COCO中person的class_id0 model.classes [0] # 推理置信度阈值低于该值的检测框会被丢弃 model.conf 0.4 # NMS的IoU阈值控制重叠框的合并力度 model.iou 0.45 # 读取测试图片bus.jpg验证检测效果 img cv2.imread(bus.jpg) # BGR转RGByolov5的hub接口接收RGB输入 img_rgb cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # 执行推理 results model(img_rgb, size640) # 从results对象中提取检测框坐标和置信度 detections results.pandas().xyxy[0] # 过滤出置信度大于阈值的目标此时已经只剩person类 person_boxes detections[detections[confidence] 0.4] # 核心人数就是person检测框的数量 person_count len(person_boxes) print(f当前画面检测到 {person_count} 人) # 生成标注后的图片 results.render() # results.ims[0]是RGB格式的标注结果转BGR后保存 cv2.imwrite(output.jpg, cv2.cvtColor(results.ims[0], cv2.COLOR_RGB2BGR))这段代码里参数值得解释一下。model.classes [0]是在推理阶段直接在NMS后处理环节滤除其他类别的检测框比在代码里再过滤一次要高效因为它减少了后续的处理量。model.conf 0.4控制检测的灵敏度——设低了容易把背景物体误判成人设高了会漏掉远处或部分遮挡的人。model.iou 0.45控制重叠框的合并人群密集时如果iOu设得太高多个相邻的人会被合并成一个框导致计数偏少。2.3 图片、视频和实时流三种输入的差异处理资源包里的测试图片是静态图但实际项目中更多是视频流或摄像头输入。三种输入的推理后处理逻辑是一样的区别在帧的获取方式和计数结果的时间序列处理上。# 视频或摄像头输入场景 cap cv2.VideoCapture(crowd_video.mp4) # 统计每秒的平均人数用于报警判断 frame_count 0 fps cap.get(cv2.CAP_PROP_FPS) # 用滑动窗口存储最近30帧的计数结果 window_size int(fps * 5) count_history [] while cap.isOpened(): ret, frame cap.read() if not ret: break frame_count 1 # BGR转RGB保持和yolov5 hub接口一致 frame_rgb cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) results model(frame_rgb, size640) detections results.pandas().xyxy[0] person_count len(detections) # 把计数结果加入滑动窗口 count_history.append(person_count) if len(count_history) window_size: count_history.pop(0) # 最近5秒的平均人数比单帧更稳定 avg_count sum(count_history) / len(count_history) # 每处理30帧打印一次当前状态避免刷屏 if frame_count % 30 0: print(f第 {frame_count} 帧当前人数 {person_count}5秒均值 {avg_count:.1f}) # 把画面保存或推流这里只做展示 cv2.imshow(crowd_count, results.ims[0]) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()视频流场景里有个容易被忽略的点单帧计数波动很大。一个人走进画面再走出去可能某帧算5人、下一帧算6人直接拿单帧结果做报警会产生频繁误报。所以代码里维护了一个滑动窗口用最近5秒的平均人数做判断依据这是工程上最简且有效的平滑方案。2.4 后处理参数调优conf和iou对计数结果的影响曲线conf和iou这两个参数对最终计数准确度的影响需要单独说一下。conf取0.3左右时会捕捉到大量低置信度的检测框。在光线较暗或人距较远的场景下能多找回一些人但误报也随之增加。我做过一个测试同一段街道监控视频conf从0.3调到0.5人数均值从28.7降到21.3但人工核对的真实人数大约在23-25之间——说明conf0.4附近是相对均衡的点。iou则决定了相邻检测框的合并力度。人群不密集时iou的影响很小但人挤人时影响显著。iou从0.35调到0.6重合的两人框可能从分成两个变成合并一个。经验值是保持0.45-0.5不变通过conf来调整灵敏度。提示改conf之后报警阈值必须重新标定。conf越高检测到的人数越低原来的报警阈值会变成形同虚设。3. 阈值报警的工程化检测置信度到人群密度触发3.1 报警不是简单的if语句阈值设定要考虑业务语义很多第一次做计数报警的人会直接写一句if count threshold: alarm()但实际部署一段时间后就会发现这不行。静态阈值在白天光线充足时表现正常到了傍晚或晚上同样的场景人数检测结果可能比白天少20%。如果直接在检测人数上做判断晚上的漏报几乎不可避免。所以要把阈值理解成两个层次一个是检测置信度阈值conf决定了哪些框算是人另一个是报警人数阈值决定了触发报警的判定。前者是模型层面的后者是业务层面的。资源包里的tutorial-checkpoint.ipynb演示的正是把这两者串起来的做法。3.2 滑窗平均与滞回区间避免报警在阈值附近反复横跳假设报警阈值设在10人。实际视频里人数在9到11之间来回跳如果只在超过10时报警、低于10时恢复会出现报警-解除-报警的循环抖动。工程上的解法是加滞回区间# 报警状态机滑窗均值 滞回区间 ALARM_THRESHOLD 10 # 报警触发阈值 RELEASE_THRESHOLD 8 # 报警解除阈值比触发阈值低留出滞回区间 WINDOW_SIZE 30 # 滑动窗口帧数约等于1秒30fps class CrowdAlarm: def __init__(self): self.count_history [] self.alarm_active False self.alarm_start_time None def update(self, count): # 维护滑动窗口 self.count_history.append(count) if len(self.count_history) WINDOW_SIZE: self.count_history.pop(0) avg sum(self.count_history) / len(self.count_history) # 滞回区间判断 # 当前不在报警状态只有在平均值超过触发阈值时才报警 # 当前在报警状态需要平均值降到解除阈值以下才解除 if not self.alarm_active and avg ALARM_THRESHOLD: self.alarm_active True self.alarm_start_time time.time() print(f[报警触发] 平均人数 {avg:.1f} 超过阈值 {ALARM_THRESHOLD}) elif self.alarm_active and avg RELEASE_THRESHOLD: self.alarm_active False print(f[报警解除] 平均人数降至 {avg:.1f} 以下) return self.alarm_active def get_active_duration(self): # 返回报警持续时长用于后续的短信/邮件通知逻辑 if self.alarm_active: return time.time() - self.alarm_start_time return 0滞回区间的思路是让状态转换有惯性已经报警时需要人数显著下降到8人以下才解除尚未报警时需要稳定超过10人才触发。两个阈值之间天然形成了一道缓冲区抖动被有效过滤。这里的WINDOW_SIZE按30fps取30帧等于1秒窗口如果摄像头是15fps就相应缩小。3.3 报警触发后的动作链截图存档、时间戳标记、通知发送报警本身不是终点触发后要完成的动作序列才是实际业务需要的def fire_alarm(frame, alarm_instance, save_diralarm_captures): # 报警时把原始帧和检测结果都保存下来 # 触发图片带时间戳文件名便于事后追溯 timestamp datetime.now().strftime(%Y%m%d_%H%M%S) raw_path f{save_dir}/raw_{timestamp}.jpg labeled_path f{save_dir}/labeled_{timestamp}.jpg # 保存原始画面和标注画面两张图对比使用 cv2.imwrite(raw_path, frame) cv2.imwrite(labeled_path, results.ims[0]) # 记录报警日志到CSV包含人数、时间、持续时长 with open(f{save_dir}/alarm_log.csv, a) as f: f.write(f{timestamp},{len(person_boxes)},{alarm_instance.get_active_duration()}\n) # 这里可以接入钉钉/企业微信/短信通知等下游动作 # 常见做法是通过webhook POST一个JSON到运维平台 # 但注意通知动作要放到报警状态机的“触发”时刻执行 # 而不是每帧都发否则会把通知通道打爆这个动作链的设计原则是报警记录必须完整可靠通知必须在状态转换时刻发出而检测计数是连续的需要靠状态机来削峰。3.4 云端GPU场景下的报警逻辑资源包想传递的完整链路资源包里的word文档教程提到用限时免费云GPU跑通的流程。云端环境的优势是GPU算力充足跑yolov5s在640分辨率下能做到实时劣势是Notebook会话可能中断、文件持久化有限。所以我一般建议在云GPU上完成检测验证报警决策与通知动作放在本地或自己的服务器上——让云GPU只负责任重活业务逻辑留在稳定环境里。4. 复现时的五个坑环境、阈值、NMS和视频流抖动4.1 坑一云GPU上torch.hub加载权重超时现象在云GPU上跑tutorial.ipynb执行到torch.hub.load时卡住或直接报Timeout。原因torch.hub.load(ultralytics/yolov5, ...)会从GitHub拉取yolov5仓库代码。云GPU在国内访问GitHub的链路不稳定经常超时。解决先把仓库clone到本地或上传到云GPU的工作目录然后直接用本地路径加载# 之前的方式国外机器上没问题 # model torch.hub.load(ultralytics/yolov5, yolov5s, pretrainedTrue) # 先手动下载或上传yolov5仓库到当前目录然后改为 import sys sys.path.insert(0, ./yolov5) model torch.hub.load(./yolov5, yolov5s, sourcelocal, pretrainedTrue)权重文件本身会自动下载到~/.cache/torch/hub/目录如果这个下载也慢可以手动下载yolov5s.pt后放到指定位置再用torch.hub.load(./yolov5, yolov5s, sourcelocal, pretrainedFalse)加weights参数指定本地权重路径。4.2 坑二conf和iou没改但检测结果和教程截图明显不符现象同一张bus.jpg教程里检测出7人自己跑出来只有4人或者框的位置明显偏移。原因yolov5的默认conf是0.25iou是0.45。教程里的截图可能用的是0.4的conf或者某个未写明版本差异的torch.hub缓存了旧代码。解决在代码里显式设置conf和iou不要依赖默认值同时清掉缓存强制重新加载# 强制清hub缓存避免拉取到旧版本yolov5代码 torch.hub._validate_not_a_forked_repo lambda *args, **kwargs: True model torch.hub.load(ultralytics/yolov5, yolov5s, pretrainedTrue, force_reloadTrue)4.3 坑三bus.jpg里人挨得近部分重叠时计数偏少现象人群不太密集但两人有肢体交叠时模型把两个人合并成了一个框计数少1-2人。原因NMS的iOu阈值过高导致相邻框被抑制。yolov5默认iou0.45但重叠明显的两个人框的IoU可能超过这个值其中置信度低的那一个被合并掉了。解决把iou往低调比如0.3让相邻框更倾向于保留。需要注意iou调低后误检框也会变多需要结合conf一起平衡。另一个常用做法是在NMS后不做额外过滤直接用yolov5输出的框数量作为计数值因为yolov5源码在NMS处理上有自己的一套权衡。4.4 坑四视频流里的报警在阈值附近反复触发现象阈值设10人画面人数从9到11波动报警频繁触发又解除日志里全是报警记录。原因没有做时间维度的平滑直接用单帧计数做判断。单帧的检测结果受姿态变化、遮挡、相机抖动影响波动很大。解决按前面3.2节的方式做滑窗平均加滞回区间而不是用单帧值。我在实际项目里被这个问题坑过很多次最终发现只要上滑窗和滞回报警稳定性至少提升一个数量级。4.5 坑五Notebook会话超时跑了一半断开结果没保存现象云GPU的Notebook跑了几十分钟后会话超时检测得到的output.jpg或报警截图全没了。原因免费GPU的会话有最长持续时长限制且长时间不操作也可能被回收。解决在脚本开头就设置自动保存每次检测的关键输出立即写盘# 用绝对路径保存并打印路径确认已落盘 import os save_dir /content/drive/MyDrive/yolov5_output # 如果挂载了Google Drive os.makedirs(save_dir, exist_okTrue) # 每处理完一帧就保存结果而非全部跑完后统一保存如果用的是Colab挂载Google Drive是最稳妥的做法如果用的是国内云平台的Notebook先把输出写到持久化目录再定时打包下载。5. 进阶用Docker固化环境和自定义报警策略5.1 环境复现与Docker部署资源包里附带Dockerfile这是整个包最容易忽略但最有价值的文件。用Docker可以把yolov5推理环境固化成镜像不管在云GPU、自有服务器还是树莓派上部署行为完全一致。FROM pytorch/pytorch:1.13.1-cuda11.6-cudnn8-runtime # 安装依赖项opencv-python-headless适合无GUI的服务器场景 RUN pip install opencv-python-headless pandas numpy # 把yolov5仓库复制进镜像 WORKDIR /workspace COPY yolov5/ ./yolov5/ COPY tutorial.ipynb . # 预下载权重避免运行时等待 RUN python -c import torch; torch.hub.load(./yolov5, yolov5s, sourcelocal, pretrainedTrue)这个Dockerfile的构建要点在于基础镜像直接用pytorch官方runtime版本比full版本少了NeMo等用不上的组件镜像体积更小headless版opencv省去了libGL依赖在纯计算容器里最省心。构建时预下载权重可以避免每次启动容器都拉取模型的等待。构建与运行的命令# 构建镜像标签定义成项目名加日期 docker build -t yolov5-crowd-count:20250412 . # 运行容器挂载输入输出目录 # 输入图片放在./inputs检测结果输出到./outputs docker run -it --rm \ --gpus all \ -v $(pwd)/inputs:/workspace/inputs \ -v $(pwd)/outputs:/workspace/outputs \ yolov5-crowd-count:20250412 \ python run_detection.py --source /workspace/inputs --output /workspace/outputs提示没有GPU的环境下把--gpus all去掉即可yolov5s在CPU上也能跑只是速度会降到1-2秒每帧。5.2 从单张checkpoint到不同Input场景图像验证到视频流验证实际上手路径建议先从静态图开始验证参数再到视频流做报警逻辑测试。静态图的优势是结果可对照、可复现调参效率高。我习惯的做法是第一步准备5-10张不同场景的静态图覆盖单人、多人、稀疏、密集、光线差异等情形统一用同一组conf/iou跑一遍记录每张图的计数结果和误检情况。 第二步根据静态图的结果确定conf的合理区间再在视频流上验证报警逻辑的稳定性。 第三步调试报警触发时检查是否还需要考虑目标进入/离开边界的情况——比如画面边缘只露出一半的人在真实业务中算不算一个人这个业务口径要在报警策略里明确区分。5.3 yolov5超参数速查表参数推荐值作用调参方向conf0.4检测置信度阈值调低漏检少但误检多调高误检少但漏检多iou0.45NMS合并重叠框人群密集时调低至0.3-0.4size640推理输入分辨率小目标多时调大至1280速度下降约4倍max_det300单图最大检测框数超密集场景要调大默认可到1000augmentFalse是否启用测试时数据增强开启后精度略升但速度慢2-3倍一般用不到有一项没列进表但值得注意的model.classes [0]必须在推理前设置yolov5的hub接口在运行时读取这个属性做过滤而不是在模型初始化时。如果你在推理中途又加了其他类别的需求比如同时检测person和car需要重新设置这个属性。5.4 损失函数与后处理权重覆盖负载不足时合理取舍人群计数场景和标准的检测任务在指标上有一个区别标准检测看重mAP人群计数的实际业务看重计数准确度即预测人数和真实人数的绝对误差。这就意味着完全不去管检测框的位置精度只统计数量也可以满足需求。所以做一个取舍在人群分布稀疏的场景可以放心降低iou阈值换取更多检测框因为重叠框的合并错误对计数的影响远大于对mAP的影响。但在拥挤场景依赖高iou阈值反而会导致合并过度调整时需要额外测试。如果资源包里的代码后续要扩展为多类别检测记得调整model.classes为列表形式例如model.classes [0, 5]同时保留person的class_id0和bus的class_id5。从那以后我每次做目标检测类项目都强制在第一天就把参数配置、阈值标定、缓存策略和Docker化流程走完一遍再进入具体业务开发。磨刀不误砍柴工这套流程已经帮我避开了很多次环境层面的返工。希望帮到你。本文还有配套的精品资源点击获取
返回列表