Python人流量计数:YOLO+ByteTrack上下行统计实战

发布时间:2026/10/18 7:51:52
Python人流量计数:YOLO+ByteTrack上下行统计实战 简介这份资源面向Python计算机视觉初学者与需要落地客流统计的开发者围绕OpenCV实现人流量计数与上下行方向统计可应用于零售门店、交通站点、公共场所等场景的人员流动分析。包内共20个文件以py脚本、mp4与avi演示视频、pyc缓存、caffemodel与cfg/prototxt模型配置、names标签及md说明为主压缩包约138.21MB涵盖主程序、跟踪器与工具模块、MobileNetSSD与YOLO两类检测模型及示例视频目录结构清晰便于按模块查阅。已有511人学习下载。读者可据此掌握背景减除、目标检测、连通组件分析、卡尔曼滤波或质心跟踪、越界方向判定与独立计数器管理等完整链路并参考现成脚本与模型快速搭建可运行的人流统计原型理解参数调优与复杂场景下改用深度学习检测的排错思路。1. 从一段监控视频说起python人流量计数到底在数什么很多人第一次做人流量统计脑子里想的是“找个人、画个框、加一”真跑起来才发现完全不是这回事。摄像头架在门口画面里有人进有人出有人走到一半折返有人并排走被检成一个有人拎着大箱子被当成两个人。python人流量计数人数统计上下行计数统计这个方向核心要解决的不是“检测到人”而是“判断这个人往哪个方向走、有没有完整穿过一条虚拟线、同一帧里出现多个目标时怎么不重复计数”。它适合门店客流分析、通道通行统计、园区出入口管理等场景输入通常是一路 RTSP 流或本地视频文件输出是带时间戳的上下行人数。新手能靠 OpenCV 加检测模型跑通最小闭环熟手则要在跟踪 ID 稳定性、越线判定逻辑、遮挡处理上反复打磨。这一篇就按“能复现”的标准把整条链路拆开讲。2. 检测加跟踪加越线判定三段式链路怎么搭2.1 为什么不能只靠检测框做上下行计数只做目标检测每帧输出一堆人框帧与帧之间没有身份关联你根本不知道这一帧的张三和下一帧的张三是不是同一个人。计数就变成了“每帧数框”一个人站三秒能被数九十次。所以上下行计数的前提是跟踪给每个目标分配一个稳定 ID再观察这个 ID 的运动轨迹是否穿过了预设的计数线。常见做法是检测器出框跟踪器做 ID 匹配越线模块只认 ID 的轨迹点。检测器可以选 YOLO 系列跟踪器可以选 ByteTrack 或 DeepSORT越线逻辑自己写。选型上ByteTrack 对遮挡和低分框的容忍度更好速度快适合实时流DeepSORT 依赖外观特征ID 切换少但吃算力。我一般先用 ByteTrack 跑通ID 跳变严重再考虑换。2.2 最小可运行环境与依赖安装先确认 Python 版本在 3.8 以上显卡驱动和 CUDA 按检测器要求配好。下面这套依赖是我在多个项目里验证过的组合不绑定具体小版本装最新稳定版即可。# 创建独立环境避免和系统包冲突 python -m venv venv source venv/bin/activate # Windows 用 venv\Scripts\activate # 核心依赖推理、视觉、跟踪、数值计算 pip install ultralytics opencv-python numpy scipy pip install lap # ByteTrack 的匈牙利匹配依赖逻辑说明ultralytics 提供 YOLO 检测和内置的 ByteTrack 接口opencv-python 负责读流、画线、写视频scipy 用来做轨迹平滑。lap 是线性分配库ByteTrack 匹配阶段会调用。参数上如果机器没有独显装 CPU 版 torch 也能跑但帧率会掉到个位数实时流场景建议至少有一块入门级显卡。2.3 用 YOLO 加 ByteTrack 跑出带 ID 的轨迹这一步的目标是拿到每一帧里每个人的 ID 和中心点坐标。不要一上来就写越线先把轨迹打出来看。import cv2 from ultralytics import YOLO # 加载检测模型n 版最轻适合实时 model YOLO(yolov8n.pt) cap cv2.VideoCapture(input.mp4) # 换成你的 RTSP 地址也可以 track_history {} # 存每个 ID 的历史中心点 while cap.isOpened(): ret, frame cap.read() if not ret: break # persistTrue 让跟踪器在帧间保持状态 results model.track(frame, persistTrue, classes[0], verboseFalse) if results[0].boxes.id is not None: boxes results[0].boxes.xywh.cpu().numpy() ids results[0].boxes.id.int().cpu().numpy() for box, tid in zip(boxes, ids): cx, cy int(box[0]), int(box[1]) # 每个 ID 保留最近 30 个点够判断方向 track_history.setdefault(tid, []).append((cx, cy)) if len(track_history[tid]) 30: track_history[tid].pop(0) cv2.imshow(frame, frame) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()逻辑说明model.track 内部已经串好了检测和 ByteTrackclasses[0] 只保留人这一类避免车、动物干扰。persistTrue 是关键不加的话每帧跟踪器重置ID 全乱。track_history 用字典按 ID 存点限制长度防止内存涨。参数上xywh 是中心点加宽高取前两位就是中心坐标。如果画面里人很小把模型换成 yolov8s 或 yolov8m召回会好一些代价是帧率下降。2.4 越线判定的三种写法与选择拿到轨迹后判断上下行的常见写法有三种。第一种是线段相交法定义一条计数线判断连续两个轨迹点组成的线段是否与计数线相交相交时根据方向累加。第二种是区域法在线上方和下方各划一个区域ID 从上方区域进入下方区域记下行反之记上行。第三种是投影法把轨迹点投影到计数线的法向量上看投影值的符号变化。线段相交法最直接适合直线通道区域法抗抖动好但区域边界要调投影法对斜线场景更稳。我一般用线段相交法起步抖动大再叠加区域确认。def cross_line(p1, p2, line_start, line_end): 判断线段 p1-p2 是否与计数线相交并返回方向 def ccw(a, b, c): return (c[1] - a[1]) * (b[0] - a[0]) (b[1] - a[1]) * (c[0] - a[0]) a, b line_start, line_end # 标准线段相交判定 if ccw(p1, a, b) ! ccw(p2, a, b) and ccw(p1, p2, a) ! ccw(p1, p2, b): # 用 y 方向变化判断上下行画面坐标系 y 向下 return down if p2[1] p1[1] else up return None逻辑说明ccw 判断三个点的时针方向两个条件同时成立说明两条线段真正交叉。方向用 y 坐标变化判断因为图像坐标系原点在左上角y 增大是向下。参数上line_start 和 line_end 是你画的计数线两端建议横跨整个通道不要留缺口。注意这个函数只判断相交实际使用时要配合“该 ID 之前没计过数”的状态位否则一个人在线上来回走会反复触发。3. 把计数结果做稳状态机、去重与参数调优3.1 用状态位防止同一个 ID 重复计数越线判定最容易被忽略的是重复触发。同一个人走过线连续几帧的轨迹点可能多次满足相交条件计数直接翻倍。解决办法是给每个 ID 维护一个状态未越线、已越线。一旦判定越线并累加立刻把该 ID 标记为已计数后续帧不再处理。如果人折返再次穿线是否算第二次取决于业务。门店客流通常折返不算新客通道统计则可能要求双向都记。我一般加一个冷却时间同一个 ID 在 2 秒内不重复计数。counted_ids {} # 记录 ID 上次计数的时间戳 import time def should_count(tid, direction, cooldown2.0): now time.time() last counted_ids.get(tid) if last is None or now - last cooldown: counted_ids[tid] now return True return False逻辑说明counted_ids 存 ID 和上次计数时间冷却期内直接返回 False。参数 cooldown 按场景调人流快可以设 1 秒慢可以设 3 秒。注意这个字典要定期清理否则长时间运行会积累大量过期 ID内存缓慢上涨。可以每十分钟扫一遍删掉超过冷却时间三倍的条目。3.2 计数线位置与角度的四个必调参数计数线不是随便画的。位置太靠画面边缘人还没完整出现就被计数角度太斜并排走的人可能被线切到不同侧。下面这张表是我调参时固定会看的四个量。参数含义建议值调不好会怎样线位置计数线在画面中的 y 坐标画面高度 40% 到 60%太靠上漏计太靠下误计线角度计数线与水平方向夹角0 到 15 度角度大时并排人方向判断乱轨迹长度判断方向用的历史点数15 到 30 点太短方向抖动太长反应迟钝冷却时间同 ID 重复计数间隔1 到 3 秒太短重复计太长漏计折返线位置的选择逻辑是人从画面边缘走到线需要有一段完整轨迹才能判断方向所以线不能贴边。角度尽量与人的运动方向垂直这样相交判定最干净。轨迹长度和帧率有关25 帧的视频里 20 个点大约 0.8 秒足够看出方向。冷却时间要大于人走过线所需时间小于正常折返间隔。3.3 轨迹平滑去掉检测框抖动带来的假越线检测框中心点会抖尤其是人停下或遮挡时中心点可能来回跳导致轨迹在计数线附近反复穿越。常见做法是对中心点做滑动平均或卡尔曼滤波。滑动平均简单几行代码卡尔曼更稳但要调过程噪声和观测噪声。我一般先用滑动平均窗口取 5效果不够再上卡尔曼。from collections import deque class SmoothTrack: def __init__(self, window5): self.points deque(maxlenwindow) def update(self, point): self.points.append(point) # 对 x 和 y 分别取平均 xs [p[0] for p in self.points] ys [p[1] for p in self.points] return sum(xs) / len(xs), sum(ys) / len(ys)逻辑说明deque 固定长度自动丢弃旧点。每次 update 返回平滑后的坐标用它替代原始中心点做越线判断。参数 window 越大越平滑但延迟越高5 到 7 是常用范围。注意平滑只用于越线判定画框显示还是用原始坐标否则框会看起来滞后。4. 避坑与排查上下行计数翻车的五个现场4.1 现象人数只增不减上行下行都是正数原因方向判断只用了 y 坐标变化但画面里人可能横向走y 几乎不变或者计数线是竖线y 变化不能代表方向。解决方向判断要和计数线方向绑定。横线用 y 变化竖线用 x 变化斜线用投影。把 cross_line 里的方向逻辑改成基于计数线法向量的投影符号通用性最好。4.2 现象同一个人被分配了多个 ID计数偏多原因跟踪器 ID 切换常见于遮挡、快速移动、低帧率。解决先确认检测框质量把置信度阈值从默认 0.25 提到 0.4 到 0.5减少低分框干扰。再检查跟踪器参数ByteTrack 的 track_buffer 可以适当加大给遮挡后重匹配留时间。如果还不行换 DeepSORT 并调外观特征权重。另外画面分辨率太低也会导致 ID 跳尽量保证人的高度在 80 像素以上。4.3 现象并排走的两个人被计成一个原因检测框重叠NMS 把其中一个框抑制掉了。解决调低 NMS 的 IoU 阈值让重叠框保留更多。YOLO 里可以设 iou0.5 甚至 0.4。同时确认模型输入尺寸尺寸太小两个人挤在一起特征分不开把 imgsz 从 640 提到 960 或 1280代价是速度下降。如果并排非常紧考虑用姿态估计或分割但那是另一个量级的成本。4.4 现象计数线附近有人停留数字乱跳原因人在线上来回走轨迹反复穿越。解决加停留判定如果 ID 在计数线附近一定范围内停留超过阈值时间暂时冻结该 ID 的越线判断等人离开再恢复。或者用区域法替代线段法要求 ID 必须从一侧区域完整进入另一侧区域才计数单纯在线上晃不算。4.5 现象跑几分钟后帧率骤降内存上涨原因track_history 和 counted_ids 只增不减轨迹点越存越多。解决track_history 每个 ID 限制长度已经做了counted_ids 定期清理超过冷却时间三倍的条目删掉。另外如果用的是 imshow 调试记得在循环里处理窗口事件否则 GUI 队列会堆积。长时间运行建议关掉 imshow把结果写视频或推流。5. 从能跑到好用验证计数准确率的一个笨办法计数准不准不能靠眼睛看画面估。我一般用一段已知人数的视频做回归测试找一段通道视频人工数出上行和下行各多少人作为真值。然后跑脚本把输出和真值对比算误差。误差超过 10% 就回去查是漏检还是重复计。漏检看检测召回重复计看 ID 切换和冷却逻辑。这个笨办法比任何感觉都可靠。验证时还有一个技巧把计数线画在视频上同时把每个 ID 的轨迹用不同颜色画出来越线瞬间用红点标记。导出这段可视化视频逐帧看红点是不是落在人真正过线的位置。如果红点提前或滞后说明轨迹平滑窗口或冷却时间需要调。如果红点出现在人还没走到线的地方说明计数线位置太靠上。# 验证脚本核心把越线事件写进日志方便和人工计数对账 import csv with open(count_log.csv, w, newline) as f: writer csv.writer(f) writer.writerow([timestamp, track_id, direction]) # 在越线判定成功处调用 # writer.writerow([time.time(), tid, direction])逻辑说明日志记录每次计数的时刻、ID 和方向跑完后按方向分组计数和人工真值对比。参数上时间戳用秒级就够ID 用来排查重复。这个日志在调试阶段比 print 好用因为可以排序、去重、统计。我自己的习惯是任何计数项目上线前先用三段不同光照、不同人流密度的视频各跑一遍误差都在 5% 以内才敢放到实时流上。实时流还要额外看延迟从人过线到计数更新超过 1 秒在门店场景就有点迟钝了。最后提醒一句计数线不要画在门框正下方那里人进出遮挡最严重往通道里退半米轨迹完整了数字才稳。希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询