1. 先搞清楚“客流分析”到底在输出什么
1.1 一个摄像头画面不能直接当报表用
做过线下数据项目的人都听过一句话:摄像头到处都是,数据却很难拿。原始摄像头画面是一串不断变化的 RGB 像素,它本身不会告诉你今天几点进店了多少人、顾客在哪个货架前停留最久、周末和平时的人流峰值差多少。真正有价值的是把这些像素转成经营指标,这个转译过程就是 AI 客流分析系统存在的意义。
很多客户一开始会问“你们是不是做人脸识别的”,这个问题背后其实有个常见误解。传统安防人脸识别关注的是“这个人是谁”,而客流分析关注的是“有多少人、从哪来、往哪去、停留多久”。两者虽然都会用到视觉算法,但技术链路完全不同:一个要提取人脸特征去比对数据库,一个要检测整个人体并持续跟踪,生成的是轨迹和事件,而不是身份。
所以这篇文章要聊的,就是从摄像头原始画面到报表上一个个数字的完整链路:人体检测、多目标跟踪、轨迹生成、事件规则触发、最后落库成指标。每一层都有坑,而且每一层出错都会像接力赛一样传导到下一层。搞懂这条链路,你才能判断一个客流系统的数据到底可不可信,也才知道调试时应该从哪里下手。
1.2 业务指标拆解:进店数、停留时长、回头客与游逛路径
我习惯先跟客户把指标定义敲死,再谈算法。因为同一个词在不同人嘴里意思可能完全不一样。比如“客流”到底指的是门口路过的人、进门的人、还是收银台结账的人?这三个人数完全不同。
线下零售常用指标大概可以分成四类:
| 指标分类 | 典型例子 | 算法层面的对应关系 |
|---|---|---|
| 流量类 | 进店人数、过店人数、客流峰值 | 检测+跨线判定 |
| 时长类 | 平均停留时长、区域驻留时间 | 轨迹起止时间戳 |
| 深度类 | 游逛路径、楼层/区域热点、回头率 | 轨迹区域匹配与重识别 |
| 转化类 | 试穿率、提袋率(配合POS系统) | 轨迹事件与交易数据关联 |
以最常见的“进店人数”为例,完整定义其实是:人体检测框的中心点从店门口外部区域进入内部区域,并且至少停留超过几秒,才计为一个有效进店。如果只是人站在门口探头看一眼立刻离开,很多系统会把它算成过店而非进店。这个定义直接影响门口磁头线的画法和事件规则的触发条件。
一旦指标定义清晰,系统模块也就清晰了。前端摄像头负责采集视频流,中间算法服务负责检测、跟踪、事件判断,后端业务系统负责存储和报表展示。没有指标定义就谈技术选型,很容易出现“算法做得很炫但客户要的数字出不来”的尴尬局面。
1.3 从指标反推系统模块
从指标反推下来,你会得到一个比较固定的技术骨架:
- 视频接入层:兼容 RTSP、ONVIF 等协议,负责拉流、解码、抽帧。
- 检测模块:对每一帧画面做人形目标检测,输出目标框和置信度。
- 跟踪模块:跨帧关联同一个人的检测框,分配并维护 ID。
- 轨迹模块:把同一 ID 的目标中心点按时间排序,生成轨迹序列。
- 事件模块:基于轨迹和虚拟区域做空间逻辑判断,产生进出、停留、聚集等事件。
- 数据服务层:对事件做聚合计算,输出业务报表或实时看板。
有意思的是,很多项目做到一半才发现瓶颈不在算法而在视频接入。摄像头时间戳不同步、画面掉帧、码率波动,都会让跟踪模块拿到不连续的帧,ID 频繁切换,最终报表数字乱跳。所以我会把“视频接入质量”也当作算法链路的一部分来对待,而不是交给运维去处理就完事。
2. 人体检测:所有链条的第一层未必是你以为的那个人脸识别
2.1 检测器在网络层到底要输出什么
人体检测的核心任务是回答两个问题:画面里有没有人?人在哪?用一个矩形框把它框出来。现在主流方案基本都是基于深度学习的单阶段检测器,例如 YOLO 系列及其变体。网络输出的不是一张图,而是若干个张量:每个候选框的坐标、宽高、置信度,以及类别概率。
实际项目里我会把检测环节理解成一个“上游过滤器”。它不需要做得特别完美,但不能出现系统性偏差。什么叫系统性偏差?比如摄像头装在 3 米高的地方俯拍,检测器在训练集里见过的大部分是人正面或侧面的全身/半身图,俯视角度下人的头肩比例完全不同,检测器就会频繁漏检。这个问题不是调参能解决的,必须换训练数据或者做专门的适配。
很多人混淆“检测”和“识别”,其实检测只是“有没有、在哪里”,识别才是“是谁”。客流分析的第一层只需要检测,不需要识别身份。这意味着我们在技术选型上可以选择更轻量的模型,牺牲少量精度换取更高吞吐,因为后续跟踪模块本来就会对多帧结果做融合,单帧偶尔漏一次不一定致命。
2.2 模型选型:是在工控机上跑还是在摄像头里跑
落地架构不同,模型选型策略完全不同。我把项目分成两类:
一类是摄像头端智能方案,前端摄像头直接跑检测模型,只上传结构化结果(目标框、置信度),不上传原始视频。优势是带宽压力小、隐私成本低,劣势是前端算力有限,模型参数量被卡得很死,而且后续算法升级要逐台设备刷固件。适合点位多、带宽小、对实时性要求未必极致的场景。
另一类是中心端智能方案,摄像头只负责采集,视频流汇聚到服务器或者边缘盒子统一处理。优势是可以用更大模型,算法升级只动服务器,也方便做跨摄像头的轨迹拼接。劣势是带宽和存储成本高,一个 1080p 摄像头按 4Mbps 码率算,一天大约 40GB 左右,几十个点位就得上 NAS 或云存储。
在这两个方案之间,我通常会建议折中:摄像头端做轻量检测做人形框选,中心端用跟踪和事件模块做语义分析。这样第一层过滤分散了压力,第二层集中处理保障精度。如果项目预算有限,直接在中心端放一块带 GPU 或 NPU 的边缘盒子也完全可行。
2.3 检测环节的误检与漏检对后续的伤害
检测错误不会只停留在检测层,它会污染后面的所有层。误检(把柱子、海报人影、拖把当成人的框)如果被跟踪模块接受,就会形成一条几十秒甚至几分钟的“幽灵轨迹”,进而触发虚假的进店事件或驻留事件。漏检的伤害同样不小,一个顾客在画面边缘没被检测到,系统就不会有他的轨迹,他会直接从统计数据里消失。
所以上线前我会做一件很土但很有效的事:准备一段 30 分钟到 1 小时的真实营业录像,用系统跑一遍,导出所有检测框,人工快速扫一遍,统计误检率和漏检率。正常俯拍角度下,一个好的检测器误检率应该低于 1%,漏检率低于 2%——注意,这里说的“好”不只是模型好,还包括安装角度、焦距调整和光线环境都配合得好。
如果误检率偏高,优先检查两件事:一是置信度阈值是否调得太低,二是画面里是否有大量与人体颜色纹理相近的静态背景。如果是漏检偏高,则优先检查检测目标尺度——画面里人的目标框如果小于模型训练时的最小目标尺寸,怎么调阈值都白搭。
3. 多目标跟踪:从一串框变成一条稳定轨迹才是真正的分水岭
3.1 卡尔曼预测与匈牙利匹配怎样协作
检测器只能告诉你“这一帧有哪些人”,它不知道上一帧的张三还是这一帧的张三。多目标跟踪(MOT)要做的事,就是给每个检测框分配一个固定的 ID,并且持续维持这个 ID。目前工业界最常用的框架仍然是 DeepSORT 的改进版本,核心是卡尔曼滤波加匈牙利匹配。
卡尔曼滤波做的是“预测”和“更新”的循环。跟踪器根据目标上一帧的位置、速度,预测这一帧它大概在哪,然后再用检测器这一帧的结果去修正预测值。匈牙利匹配解决的是“分配问题”:我有 5 个预测位置,检测器给了 6 个框,到底哪个框对应哪个预测?算法通过计算 IoU 重叠度和特征相似度,找出一组总代价最小的匹配方案。
这里有一个很关键的点:匹配分为不同优先级。第一优先是用外观特征匹配,也就是把目标框裁剪出来,用一个 ReID(行人重识别)网络提取特征向量,和该 ID 的历史特征做余弦相似度比对。第二优先才是用 IoU 位置匹配。为什么这样设计?因为视频帧率如果不够,目标位移可能很大,前后两帧的框根本不重叠,单靠位置就丢了。当然,这也会带来一个隐患——如果这个 ReID 特征提取器区分能力不够,两个穿同样颜色衣服的人挤在一起时,ID 就会互相交换。
3.2 ID Switch 为什么是客流系统的隐形杀手
做客流分析最怕的一个问题不是漏检,而是 ID Switch,也就是一个原来稳定为 A 的 ID 突然变成了 B。ID Switch 最大的危害在于它会把轨迹切碎。客户想看“一个顾客从入口到收银台再到出口”的完整路径,结果系统显示的是两条不完整的轨迹,顾客在中间凭空消失又出现,报表里的平均游逛深度就会失真。
高密度场景里 ID Switch 尤其严重。想象一下三个人并肩走进门口,检测器给出整齐的三个框,跟踪模块可能把左边的人和中间的人在新一帧搞混。这种情况在货架区域很常见,因为货架会遮挡人体局部,人从货架后走出来时,检测框的中心点突然跳了很远,跟踪模块匹配不上,只好发一个新 ID。
要压制 ID Switch,有三个思路,按推荐程度排序:提高帧率、装高位俯拍、加轨迹后处理。帧率提到 15fps 以上能明显减少位移跳变;高位俯拍能大幅降低人与人之间的互相遮挡概率;轨迹后处理则在事件模块做修正,比如一条轨迹中断后,如果周边同时出现另一条轨迹且时间衔接极短,就做合并。这里提醒各位:单纯靠调大 IoU 阈值往往适得其反,会把本该独立的两个人强行并成一条轨迹,尤其是两个人在门口并排行走时。
3.3 让追踪更稳的三个实用补强手段
除了标准 DeepSORT,我习惯在工程里加三个补强手段。
第一个是图像坐标到地面坐标的映射。摄像头是透视成像的,画面下方的人看起来大,上方的人看起来小,直接用像素距离判断“人有没有移动”会产生误差。提前做一次单应性变换标定,把图像坐标映射到地面平面坐标,再用地面坐标做跟踪的位置预测和速度统计,轨迹会稳定很多。标定只需要在安装时放四个角点参照物,离线算一次矩阵,之后固定机位就不用再动。
第二个是低速目标的逗留抑制。一个人站在货架前看商品时,检测框会在小范围内抖动,可能导致卡尔曼滤波误判他在“移动”。我会在轨迹平滑层对短时间内的位移量做低通滤波,位移低于一个阈值就认为是驻留状态,防止出现“人明明站着不动轨迹却在原地画圈”的问题,否则区域停留时间会被拉长。
第三个是 ROI 屏蔽。画面里有些区域根本不可能出现正常顾客,比如收银台内侧、仓库门口、员工通道。我会在跟踪之前先把这些区域的检测结果过滤掉,既减少误检来源,也避免员工被计入客流量。这些区域往往不是静止不变的,收银台的人会走来走去,所以不能用简单屏蔽线,最好用多边形 ROI 结合业务时段来动态启用。
4. 轨迹事件化:把坐标流翻译成业务语言
4.1 事件规则引擎:跨线、驻留、方向与热点区域
检测和跟踪做完,你手里其实只是一堆“带时间戳的坐标序列”。要让业务系统能用,必须把它们转成事件。事件的定义就是空间与时间的组合逻辑,下面是我在项目里经常用到的一组规则模板:
- 跨线事件:轨迹点从虚拟线的 A 侧穿越到 B 侧,用于进出店统计。
- 区域驻留事件:轨迹中心点持续落在某个多边形区域超过设定时长,触发驻留事件。
- 方向事件:轨迹的起点和终点对应区域编号,推出“从大门到收银台”这类路径语义。
- 热点事件:某个区域内同时存在的轨迹数量超过阈值,触发拥挤预警。
- 逆向导流事件:在单向通道中轨迹方向与设定方向相反,用于商场消防疏散分析等。
规则引擎的写法不能写死在代码里,因为客户对“入口”“出口”的定义会改,货架位置会调整。我会做成一个 JSON 配置,把虚拟区域坐标、事件类型、触发阈值全部抽出去,让运营人员也能查看和修改。下面是一段简化的示例:
{ "zones": [ { "zone_id": "store_entrance", "type": "polygon", "points": [[120, 300], [220, 280], [230, 420], [130, 440]], "events": [ { "type": "cross_in", "from_zone": "outside", "to_zone": "inside" }, { "type": "cross_out", "from_zone": "inside", "to_zone": "outside" } ] } ], "dwell_threshold_seconds": 5, "enter_mimimum_frames": 6 }4.2 轨迹后处理:平滑、插值、状态机
事件规则跑在原始轨迹上很容易误触发,所以必须先做轨迹后处理,我归纳为三步。
第一步是平滑。卡尔曼滤波输出的轨迹依然会有小毛刺,直接用中心点去做跨线判断,可能因为一帧的抖动产生一次虚假跨线。我会用一个简单的滑动窗口均值,窗口大小取 5 帧,像素级摆动就会被抹平。
第二步是补帧。检测偶尔会漏一两帧,如果在漏帧期间轨迹正好穿越了门口线,跨线事件就丢了。我常用的做法是:当某个 ID 的轨迹缺失不超过 3 帧时,用前后两个有效点的位置做线性插值,把中间帧补上,然后再送进事件判断。注意这里的插值时间不能太长,超过 3 帧以后目标可能已经拐弯,补出来的位置会不真实。
第三步是状态机。这是最关键的一步。一个目标不应该既能触发“进入”又能触发“离开”,所以每个 ID 需要维护一个业务状态,比如“outside -> entering -> inside -> leaving -> outside”。状态机只允许合法的状态迁移,这样既能消除重复事件,也方便统计在店人数。我的经验是,状态机里至少要有“进店等待确认”和“离店等待确认”两个中间态,因为目标在门口犹豫时,轨迹会在线上来回穿,如果没有中间态延迟确认,一个正常顾客会触发三四次进出事件。
4.3 姿态类“异常事件”我为什么不建议一上来就做
很多客户拿到客流系统之后会问:能不能顺便识别跌倒、打架、偷窃、儿童攀爬这些“高级事件”?我的回答通常是:可以作为二期扩展,但不要在第一期做。
原因是姿态类事件与客流轨迹类事件的技术链路差异很大。客流追踪只需要目标框级别的信息,即使检测器没有把人的四肢位置提取出来,也不影响轨迹统计。而“跌倒”至少需要骨骼点检测,要准确区分一个大人是蹲下、弯腰还是跌倒,姿态估计器必须输出高于 90 的置信度,但这个数值在真实弱光、遮挡环境下很难达到。打架和聚集更是如此,“聚集”尚可以用轨迹密度近似,“打架”则涉及人与人、人与物之间的交互判断,误报率往往高到运营团队彻底忽略这个功能,最终沦为一个没人看的告警源。
我的建议是:第一期的异常能力只做两件低成本高可靠的事——区域闯入和超时逗留。这两个本质上还是轨迹事件,不需要姿态模型。等轨迹系统稳定运行一两个月,积累了足够的场景数据,再考虑用专门的小模型去识别特定动作,效果和可维护性都会好得多。
5. 实地调优:决定系统好坏的往往不是算法而是安装与现场
5.1 摄像头角度与高度对检测和跟踪的影响
做算法的人最容易忽略安装,但项目成败往往就挂在安装上。我见过太多算法在实验室跑得不错、一上现场就废的案例,原因几乎都在摄像头。
高度和角度是最先要确认的。理想安装高度我建议在 2.8 米到 3.5 米之间,向下倾斜约 30 到 45 度。这个角度下人的头肩特征仍然清晰,检测器不容易把两个人重叠成一个框,同时人也还在画面里保留足够大的目标尺寸。如果装到 6 米以上俯拍,虽然遮挡问题减少,但人的目标会变得很小,检测器精度下降,跟踪也受影响。如果只装在 2.2 米那么高(很多店铺门头只有这么高),画面大量是人的头顶,检测器可能把每个进店的人只识别出半个框,甚至漏检。
还有焦距和画幅。门口客流分析,镜头视场角宁小勿大。画幅太宽,人的目标太小;画幅太窄,门口两侧来的人还没进入画面就已经离开。我通常会先把画面调好,确保人员行走的通道区域占画面的 60% 以上,再锁死焦距和位置,并且用胶带或螺丝固定住,防止保洁阿姨不小心把摄像头推歪。安装角度一变,后面整条链路的标定和事件区域全部作废,这是最常见的事故。
5.2 玻璃门、反光、逆光这些经典翻车现场
有三个现场问题几乎无法靠调参完全解决,必须在安装时一并考虑。
第一个是玻璃门和玻璃幕墙。透明玻璃会让检测器同时看到室内的人和室外的倒影,跟踪模块可能会把倒影和真人当成同一个目标或两个目标。我的做法是尽量让摄像头斜对玻璃面而不是正对,同时在检测阶段屏蔽掉玻璃区域下半部分的倒影区域。另一个办法是用偏振镜片,但会增加成本,而且对低照度画面不友好。
第二个是反光地面。地砖或浅色瓷砖会把人的倒影照出来,倒影的检测框和真实人脸的检测框位置高度相似,跟踪时会出现上下两个框互相纠缠的情况。这个问题的处理思路是通过 ROI 把地平线以下区域整体屏蔽。注意,屏蔽不能把脚部轨迹也删掉,否则跨线判断时会丢位置,最好是只在“检测”环节屏蔽倒影,保留“轨迹”环节的地面映射坐标。
第三个是逆光。门店门口正对西晒时,人脸和人体是严重欠曝的,检测器几乎全灭。可以打开摄像头的宽动态(WDR)功能,或者把曝光时间调短,宁可画面整体偏暗,也要保证人物轮廓与背景能区分开。WDR 也不是万能的,强逆光到一定程度,唯一的办法是在门口内侧增加补光,让人的主体受光影。
5.3 排查链路:从报表数字异常倒推算法故障
系统上线后一定会出现“报表数字看着不对劲”的时候。别急着怀疑算法,先建立一个倒推排查顺序,我一般按四步走。
第一步看视频接入层。先确认画面帧率稳定、码率正常、关键帧没有间隔丢失。最简单的办法是拿工具直接拉流看几秒钟画面,用肉眼判断实时画面是否卡顿和跳帧。如果画面本身就一卡一卡的,下面所有步骤都不用做了,先解决推流和拉流的问题。
第二步看检测层。找到一段业务平稳的录屏视频,用可视化工具把检测框画出来,人工看一眼检测框是不是准确框住了人。如果大量框在墙和柱子上,阈值要上调;如果人移动速度稍快框就追不上,需要确认是不是检测模型输入分辨率太低。
第三步看跟踪层。在可视化界面打开轨迹回放,看每个 ID 的轨迹是否平滑、有没有突然跳跃和 ID 互换。轨迹跳跃常见的原因是检测漏帧导致跟踪模块断链,需要去查模型在特定视角下的漏检率。
第四步看事件规则。用门口区域状态机日志,看一个目标从 outside 到 inside 的状态转换是否符合业务预期。很多“少算了”的问题都出在状态机缺中间态,或者跨线判断的帧数阈值设得太宽,目标在门口停两三秒才判定进入,导致那两三秒内的其他事件混乱。
整个排查过程最好做成一个脚本化的工具链,一键导出某时段所有 ID 的轨迹回放,叠加区域框和事件标记,这样出了问题能快速定位到具体环节,不用靠猜。
6. 技术链路之外:匿名化与数据闭环
6.1 为什么不能把“识别谁”带进客流分析系统
客流分析系统在整个落地过程中,有一个设计原则值得反复强调:系统应该主动丢弃身份信息,构建匿名化处理机制。我们讨论的是“客流分析”,不是“身份识别”。有人脸图像的数据,与没有人脸图像的数据,在整个链路里的技术复杂度和安全等级完全不是一个量级。
人脸检测模型本身会输出人脸框,这时就要在系统架构里加一道“即用即弃”的处理:检测到人脸框后,不送人脸识别模型,直接把人脸区域做模糊化或者丢弃原始像素,只保留人体框和轨迹坐标。如果客户提出“想识别回头客”,我会建议用顾客追踪编号(Visit ID)来做:同一个人多次进入门店,系统通过 ReID 特征对比计算出“疑似同一个 Visit”,然后把这个匹配关系映射到一个随机编号上,而不是真实身份。
这样做的好处,不只在安全层面。丢掉身份信息后,数据结构变成纯时空序列,处理逻辑更简单,内存占用更低,而且业务流程里不涉及任何身份关联,运营人员用起来也更没有心理负担。
6.2 数据处理架构:人在环回放、离线与实时并行
客流系统一旦铺到几十上百个点位,数据的生命周期管理就成了工程质量分水岭。我推荐一个三通道架构。
实时通道负责在线统计当前人数和实时热区。它读的是最新几秒的轨迹事件,只做聚合不做整理,服务的是大屏和现场预警。
离线通道负责每天定时重算一次历史报表。它读取当天存下来的轨迹明细,用更完整的逻辑后处理(包括跨摄像头轨迹拼接、异常轨迹修正、补帧)重新生成指标,服务的是日报和周报。为什么要重算?因为实时链路为了低延迟会允许一些临时性错误,到离线复用完整上下文时,很多 ID Switch 和丢帧问题是可以被修正的。
回放通道则是给人看的。运营人员如果想知道“刚才 14:00 到 14:30 门口到底发生了啥”,可以调出这段时间的轨迹回放视频,叠加业务事件标记,逐帧查看。这个通道最好做成独立的可视化页面,不要把原始视频直接暴露在通用后台,避免普通账号看到不需要看的信息。
我自己的习惯是设置三套不同保存策略:原始视频留存 72 小时,轨迹明细留存 30 天,聚合指标留存 180 天以上。这个策略不是一成不变的,但大部分线下场景的运营需求都覆盖得到。痕迹越短,后续纠纷越少。
6.3 部署形态与算力成本
最后聊聊算力。一套 30 个点位的客流系统,中心端如果全部用 CPU 推流做检测,每路 1080p 25fps 大概要占 4 到 6 个 CPU 核,30 路下来是一个不小的压力。所以我通常推荐用边缘盒子分流:每台边缘盒子接 4 到 8 路视频,盒子只跑检测和跟踪的最轻量版本,中心服务器只做事件汇总和报表。
GPU 不是万能的,客流分析场景有个被低估的瓶颈不是算力而是解码能力。视频流 H.264/H.265 解码极其消耗资源,纯 GPU 检测虽然快,但解码还是得靠 CPU,所以预算有限时不要盲目追求大 GPU 服务器,买一台解码能力强、内存带宽高的机器,效果往往更好。
成本还有一个隐藏项:算法服务的运维。模型灰度更新、视频流故障检测、点位移位后的配置重下发,这些都是日常开销。一个看起来简单的客流系统,长期稳定的关键不在第一个月跑出了多漂亮的数字,而在半年后还能不能按照当初的指标口径持续输出。我见过太多项目上线当天惊艳,三个月后因为摄像头被风吹歪、模型没有定期验证而变成废数据的案例,所以调试文档和点位台账一定要从一开始就建立起来。
我在实际项目里最深的一个体会是,客流分析系统本质上是“约束下的检测跟踪系统”,它的技术上限取决于模型,但技术下限取决于现场。决定项目成败的,往往是那些写在算法论文里不会提的细节:摄像头装多高、门口那根线画在哪、夏天强光直射时 WDR 开多大、员工通道的 ROI 有没有及时更新。把这些细节做扎实了,链路里的每一环都能稳定输出,报表上的数字才有意义。