3D视觉客流统计全链路实战:从目标检测到越线计数的算法拆解 客流统计这件事看起来只是数人头但真要做到高准确率、低误判、能落地背后其实是一条完整的算法链路在支撑。我做过几个3D视觉客流统计项目从最早的纯2D检测方案一路踩坑到现在的多模态融合最大的感受是单点算法再强链路没串好最后的数据也没法看。这篇文章就把我实际项目里跑通的这套链路拆开讲清楚——从目标检测怎么选型到轨迹跟踪怎么稳住ID再到3D视觉怎么解决遮挡和高度误判最后怎么把原始轨迹变成可用的客流数据。适合正在做客流统计、智能安防、零售分析这类项目的朋友参考不管你是刚接触目标检测的新手还是已经在调跟踪算法的老手应该都能找到一些能直接抄作业的东西。1. 整体方案设计与链路拆解1.1 为什么客流统计不能只靠单帧检测很多人刚接触客流统计第一反应就是拿个YOLO跑检测每帧数人头帧间做个简单计数就完事了。我最早也是这么干的结果在实际场景里被教做人。商场入口人流密集的时候两个人并排走检测框重叠NMS一压就丢一个有人弯腰系鞋带检测框突然变小计数逻辑直接懵了更别说来回走动的人每帧都算一次数据直接翻倍。问题的根源在于客流统计的本质不是“检测到多少人”而是“有多少人经过了某条线”。这是一个时序问题不是单帧问题。单帧检测只能告诉你“此刻画面里有几个人”但没法告诉你“这个人是刚进来的还是已经在那站了十分钟”。所以必须引入轨迹跟踪把帧与帧之间的检测结果关联起来形成每个人的运动轨迹再基于轨迹做计数判断。那为什么还要上3D视觉2D检测加跟踪在简单场景下也能跑但一旦遇到遮挡、光照变化、人群密集2D的短板就暴露了。3D视觉能提供深度信息直接解决“这个人离摄像头多远”“两个人是不是前后重叠”这类问题。尤其是俯视场景下3D点云或者深度图能给出人的空间位置跟踪稳定性会好很多。整条链路的逻辑是这样的目标检测负责从每帧图像里找出人轨迹跟踪负责把不同帧里的人关联成同一个人3D视觉负责提供空间信息辅助关联和去重最后业务逻辑层负责把轨迹转化成进出计数。四层各司其职缺一不可。1.2 算法链路的分层架构与数据流我把整条链路分成四个层次每层的数据流和职责边界都很清晰层级核心职责输入输出关键指标检测层逐帧定位人体目标RGB图像/深度图检测框置信度深度值mAP、召回率、推理延迟跟踪层帧间关联形成轨迹检测结果序列轨迹ID轨迹历史MOTA、IDF1、ID Switch次数3D融合层空间去重与高度过滤轨迹深度信息3D轨迹真实世界坐标深度误差、去重准确率业务层越线判断与计数3D轨迹进出人数方向计数准确率、误报率数据流是这样的摄像头采集RGB帧和深度帧或者用双目/RGBD相机检测层对每帧做推理输出人体框和对应的深度值。跟踪层拿到检测序列后用卡尔曼滤波预测下一帧位置再用匈牙利算法做匹配匹配上的继承ID没匹配上的新建ID。3D融合层把2D框和深度值结合算出人在真实世界坐标系下的位置同时根据高度信息过滤掉非人体目标比如推车、货架。业务层拿到3D轨迹后判断轨迹是否穿过了预设的计数线穿过就计数同时根据穿越方向判断是进还是出。这个架构的好处是每层可以独立优化。检测层换模型不影响跟踪层跟踪层调参不影响业务逻辑。实际项目里我经常是检测层用YOLO系列快速迭代跟踪层用ByteTrack或者DeepSORT做对比实验3D融合层根据相机类型做适配业务层根据客户需求定制计数规则。1.3 方案选型的核心考量与取舍选型这件事没有最好的方案只有最合适的方案。我一般从四个维度来权衡精度、速度、成本、部署环境。精度方面检测层如果追求极致精度可以用Faster R-CNN或者DETR系列但推理速度慢边缘设备跑不动。实际项目里我更多用YOLOv5/v8或者YOLOXmAP够用速度也快。跟踪层ByteTrack在MOT榜单上表现很好而且不需要额外的ReID模型速度快DeepSORT精度更高但需要跑ReID特征提取算力消耗大。3D融合层如果相机自带深度输出比如RGBD相机直接用深度图如果是普通RGB相机就得用单目深度估计或者双目匹配精度会打折扣。速度方面整条链路的延迟要控制在可接受范围内。客流统计一般不需要实时到毫秒级但也不能太慢。我一般要求端到端延迟在200ms以内这样每秒能处理5帧以上对于正常步行速度的人来说足够了。检测层占大头跟踪层和3D融合层相对轻量。成本方面RGBD相机比普通RGB相机贵不少但省去了深度估计的计算开销。如果预算有限可以用普通RGB相机加单目深度估计但精度和稳定性会差一些。边缘设备的选择也很关键Jetson系列、瑞芯微、地平线这些平台各有优劣要根据模型大小和算力需求来选。部署环境方面室内和室外差别很大。室内光照稳定检测和跟踪都好做室外光照变化剧烈还得考虑雨雪天气检测模型需要更强的泛化能力。俯视和侧视也不一样俯视遮挡少但深度信息更重要侧视遮挡多但2D检测更成熟。注意选型时不要一味追求SOTA模型实际项目里稳定性和可维护性比榜单排名重要得多。我见过太多项目用了最新最炫的模型结果部署时各种兼容性问题最后还不如用成熟方案。2. 目标检测层的核心细节与实操要点2.1 检测模型选型从YOLO到轻量化方案目标检测是整条链路的入口检测不准后面全白搭。客流统计场景下的检测有几个特殊要求人体类别单一但形态多样、遮挡频繁、需要处理小目标远处的人、推理速度要快。YOLO系列是我用得最多的。YOLOv5在速度和精度之间平衡得很好YOLOv8进一步提升了小目标检测能力YOLOX的Anchor-Free设计对密集场景更友好。如果算力特别紧张可以考虑NanoDet或者YOLO-Fastest模型大小能压到几MBmAP虽然降一些但速度飞快。热词里提到的“macs仅5mb的目标检测模型”其实就是这类轻量化思路通过减少MACs乘加运算次数来压缩模型适合边缘部署。SSD系列我也用过SSD-MobileNet在移动端表现不错但小目标检测能力不如YOLO。Faster R-CNN精度高但速度慢除非对精度有极致要求否则不推荐在客流统计里用。实际选型时我会先跑一个基准测试用目标场景的测试集对比不同模型的mAP、召回率、推理延迟和模型大小。下面是我在某商场入口场景的实测数据模型mAP0.5召回率推理延迟Jetson Xavier模型大小YOLOv5s0.920.8928ms14MBYOLOv8n0.940.9132ms12MBYOLOX-Tiny0.900.8722ms10MBNanoDet-Plus0.850.8215ms5MBSSD-MobileNetV20.830.8018ms8MB从数据看YOLOv8n精度最高NanoDet-Plus速度最快但精度损失明显。最后我选了YOLOv5s因为它在精度和速度之间最平衡而且社区资源丰富遇到问题好查。2.2 数据标注与增强策略让模型适应真实场景检测模型的效果七分靠数据三分靠调参。客流统计场景的数据标注有几个坑标注框要统一标准。是标全身还是标可见部分遮挡情况下怎么标我的做法是只要能看到头部和肩膀就标全身框被遮挡的部分按估计位置补全。这样模型学到的框更完整跟踪时也更稳定。小目标要重点关注。远处的人可能只有几十个像素很容易漏检。标注时要把这些小目标都标上训练时用Mosaic增强、随机缩放来提升小目标检测能力。热词里提到的“小目标检测”就是这个痛点实际项目里我会专门统计不同距离下的人体像素大小确保训练集覆盖各种尺度。负样本要充足。商场里的广告牌、模特、推车、货架都容易被误检成人。我一般会收集大量负样本包括空场景、只有货架的场景、有模特但无真人的场景让模型学会区分。数据增强要贴合场景。光照变化、运动模糊、遮挡模拟这些增强手段能显著提升模型泛化能力。我常用的增强组合是Mosaic4图拼接、MixUp图像混合、随机亮度对比度调整、随机遮挡Cutout。注意不要用水平翻转因为人的左右不对称翻转后可能影响跟踪时的方向判断。实操心得标注数据时我会让标注员先标100张然后我抽查20张确认标准一致后再批量标。不一致的标注比不标还可怕模型会学歪。2.3 推理优化与部署从训练到落地的关键步骤训练好的模型要部署到边缘设备中间还有不少工作要做。我一般按这个流程走模型导出。PyTorch训练完的模型先导出成ONNX格式再用TensorRT或者OpenVINO做推理优化。ONNX是中间格式方便跨平台。导出时要注意opset版本不同版本对某些算子的支持不一样。量化压缩。FP32转FP16或者INT8能显著减少模型大小和推理延迟。INT8量化需要校准集我一般从训练集里抽500张图做校准。量化后精度可能会掉1-2个点如果掉太多可以用量化感知训练QAT来补偿。推理引擎选择。NVIDIA平台用TensorRTIntel平台用OpenVINOARM平台用NCNN或者MNN。TensorRT的优化效果最好但只支持NVIDIA显卡。Jetson系列上TensorRT是标配我实测YOLOv5s用TensorRT FP16推理延迟能从28ms降到15ms。后处理优化。NMS是检测后处理里最耗时的部分可以用GPU加速或者改成Soft-NMS。另外检测框的坐标要映射回原图注意letterbox的padding要减掉。# TensorRT推理后处理示例简化版 import numpy as np def postprocess(outputs, conf_thres0.5, iou_thres0.45): # outputs: [batch, num_anchors, 5num_classes] boxes [] scores [] for pred in outputs: # 过滤低置信度 mask pred[:, 4] conf_thres pred pred[mask] if len(pred) 0: continue # 解析框和类别 xywh pred[:, :4] conf pred[:, 4] cls pred[:, 5:].argmax(axis1) # 转xyxy xyxy xywh2xyxy(xywh) # NMS keep nms(xyxy, conf, iou_thres) boxes.append(xyxy[keep]) scores.append(conf[keep]) return boxes, scores部署测试。部署后要在真实场景跑至少一周观察检测稳定性。重点关注光照变化时会不会漏检、遮挡时会不会误检、远处小目标能不能检出。我一般会录一段测试视频离线跑一遍统计漏检率和误检率再针对性优化。2.4 检测层的常见问题与排查技巧检测层的问题我总结了几类高频的漏检。原因可能是目标太小、遮挡太严重、光照太暗、模型置信度阈值太高。排查方法先把置信度阈值降到0.1看能不能检出如果能检出说明是阈值问题如果还不行看训练集里类似场景的样本够不够不够就补数据。误检。广告牌、模特、反光地面都容易被误检。排查方法收集误检样本加入负样本重新训练。另外可以加一个基于深度的过滤如果检测框对应的深度值异常比如在地面以下就过滤掉。框不准。检测框偏移或者大小不对可能是Anchor设置不合理或者训练时回归损失权重太低。可以试试Anchor-Free的模型或者调整损失函数里box损失的权重。推理速度慢。先看是不是后处理耗时太长NMS可以优化再看模型本身换轻量化模型或者做量化最后看硬件是不是算力不够考虑换设备。问题类型可能原因排查方法解决方案漏检小目标/遮挡/光照降阈值测试补数据/调阈值/增强误检负样本不足收集误检样本加负样本/深度过滤框不准Anchor不合理可视化检测框换Anchor-Free/调损失速度慢后处理/模型/硬件分阶段计时优化NMS/量化/换设备3. 轨迹跟踪层的算法实现与调参经验3.1 跟踪算法选型ByteTrack vs DeepSORT vs OC-SORT跟踪层的任务是把检测框串成轨迹。选跟踪算法核心看三个指标MOTA多目标跟踪准确度、IDF1ID F1分数、ID Switch次数。MOTA衡量整体跟踪质量IDF1衡量ID保持能力ID Switch越少越好。ByteTrack是我最常用的。它的核心思想是不光用高置信度检测框做匹配低置信度检测框也拿来用只是匹配优先级低一些。这样能有效减少漏检导致的轨迹断裂。ByteTrack不需要ReID模型速度快在MOT17上MOTA能到80。DeepSORT精度更高因为它用了ReID特征做外观匹配但需要额外跑一个ReID网络算力消耗大。如果场景里人穿的衣服颜色差异大DeepSORT的ID保持能力会更好。但客流统计场景里冬天大家都穿深色衣服ReID特征区分度不高DeepSORT的优势就不明显了。OC-SORT是最近比较火的它在ByteTrack基础上加了观测中心补偿对非线性运动更鲁棒。如果场景里有人突然加速或者转弯OC-SORT会比ByteTrack稳一些。我的选型建议是算力充足且场景外观差异大用DeepSORT算力紧张或外观差异小用ByteTrack运动模式复杂试试OC-SORT。实际项目里我大部分时候用ByteTrack够用且省算力。3.2 卡尔曼滤波与匈牙利匹配跟踪的核心机制跟踪的核心就两件事预测和匹配。预测用卡尔曼滤波匹配用匈牙利算法。卡尔曼滤波的作用是根据上一帧的轨迹状态位置、速度预测这一帧的位置。人的运动可以近似为匀速模型状态向量是[x, y, w, h, vx, vy, vw, vh]分别是中心点坐标、宽高和对应的速度。预测公式是x x vx * dt y y vy * dt w w vw * dt h h vh * dtdt是帧间时间差。预测完之后用检测框和预测框做匹配。匹配的代价矩阵用IoU交并比或者马氏距离。IoU简单直观但遮挡时容易匹配错马氏距离考虑了不确定性更鲁棒但计算复杂。匈牙利算法负责在代价矩阵上找最优匹配。匹配阈值很关键阈值太高容易匹配错阈值太低容易丢轨迹。我一般设IoU阈值0.3马氏距离阈值9.4877卡方分布95%置信区间。# 卡尔曼滤波预测示例 import numpy as np from filterpy.kalman import KalmanFilter def create_kalman_filter(): kf KalmanFilter(dim_x8, dim_z4) # 状态转移矩阵 kf.F np.array([ [1,0,0,0,1,0,0,0], [0,1,0,0,0,1,0,0], [0,0,1,0,0,0,1,0], [0,0,0,1,0,0,0,1], [0,0,0,0,1,0,0,0], [0,0,0,0,0,1,0,0], [0,0,0,0,0,0,1,0], [0,0,0,0,0,0,0,1] ]) # 观测矩阵 kf.H np.array([ [1,0,0,0,0,0,0,0], [0,1,0,0,0,0,0,0], [0,0,1,0,0,0,0,0], [0,0,0,1,0,0,0,0] ]) # 过程噪声和观测噪声 kf.Q np.eye(8) * 0.01 kf.R np.eye(4) * 1.0 kf.P np.eye(8) * 10.0 return kf3.3 轨迹管理新建、更新与删除的时机把控轨迹管理是跟踪层里最容易被忽视但最影响效果的部分。每条轨迹都有生命周期新建、更新、删除。新建轨迹。当检测框没有匹配上任何现有轨迹时新建一条轨迹。但不要一检测到就新建因为误检也会产生新轨迹。我一般设一个min_hits参数连续匹配上3帧才确认这条轨迹否则先挂起。这样能过滤掉大部分误检。更新轨迹。匹配上的轨迹用检测框更新卡尔曼滤波状态。如果连续几帧没匹配上轨迹进入“丢失”状态继续用卡尔曼滤波预测位置但不再更新。丢失超过max_age帧我一般设30帧就删除轨迹。删除轨迹。除了丢失超时还有一种情况是轨迹离开画面。如果预测位置超出画面边界也可以提前删除。注意min_hits和max_age这两个参数对跟踪效果影响很大。min_hits太大新出现的人跟踪不上太小误检容易形成假轨迹。max_age太大丢失的人会一直占着ID太小遮挡后重新出现的人会分配新ID。我一般先在测试集上网格搜索找到最优组合。3.4 跟踪层的调参实战与避坑指南跟踪调参我踩过的坑比检测层还多。分享几个典型的ID Switch频繁。原因可能是匹配阈值太高、卡尔曼滤波参数不合理、检测框抖动大。排查方法可视化跟踪结果看ID在什么时候切换。如果是遮挡时切换降低匹配阈值如果是检测框抖动导致平滑检测框或者调卡尔曼滤波的观测噪声。轨迹断裂。人走着走着轨迹断了重新出现时分配了新ID。原因可能是漏检导致连续几帧没匹配上、max_age太小。解决方法用ByteTrack的低置信度匹配策略或者增大max_age。假轨迹多。误检形成的轨迹或者背景噪声形成的轨迹。解决方法增大min_hits或者加一个轨迹长度过滤太短的轨迹不输出。密集场景匹配错。人挤人的时候A的检测框匹配到了B的轨迹。解决方法用马氏距离代替IoU或者加入运动方向一致性约束。问题可能原因排查方法解决方案ID Switch阈值高/滤波差/框抖动可视化ID变化降阈值/调滤波/平滑框轨迹断裂漏检/max_age小看断裂时机低置信匹配/增大max_age假轨迹误检/min_hits小看轨迹来源增大min_hits/长度过滤匹配错密集场景看匹配对马氏距离/方向约束4. 3D视觉融合与业务层实现4.1 深度信息获取RGBD、双目与单目估计的取舍3D视觉的核心是深度信息。获取深度有三条路RGBD相机、双目立体匹配、单目深度估计。RGBD相机比如RealSense、Kinect直接输出深度图精度高、延迟低但价格贵而且室外强光下红外深度会失效。室内客流统计用RGBD是最省事的我一般推荐RealSense D435i深度范围0.3-3米正好覆盖客流统计场景。双目立体匹配用两个RGB相机通过视差算深度。成本比RGBD低但算法复杂纹理少的区域比如白墙匹配效果差。而且双目需要严格标定标定误差会直接影响深度精度。单目深度估计用深度学习模型从单张图预测深度成本最低但精度最差而且尺度不确定不知道绝对距离。如果只是做相对深度排序判断谁在前谁在后单目估计够用如果要算真实世界坐标单目估计不够。我的建议是室内场景优先RGBD室外场景考虑双目预算极低且精度要求不高用单目估计。实际项目里我用RGBD最多省去了深度计算的麻烦而且深度图和RGB图对齐好融合简单。4.2 2D到3D的坐标映射从像素到真实世界拿到深度图后要把2D检测框映射到3D空间。核心是相机内参和坐标系变换。相机内参矩阵KK [[fx, 0, cx], [0, fy, cy], [0, 0, 1]]fx, fy是焦距cx, cy是主点坐标。这些参数通过相机标定获得。对于检测框的中心点(u, v)对应的深度值d从深度图读取。那么相机坐标系下的3D坐标是X (u - cx) * d / fx Y (v - cy) * d / fy Z dZ就是距离相机的深度。如果要转到世界坐标系比如地面坐标系还需要一个外参矩阵R|t描述相机相对于地面的位姿。# 2D到3D坐标映射示例 def pixel_to_3d(u, v, d, K, R, t): # 相机坐标系 fx, fy, cx, cy K[0,0], K[1,1], K[0,2], K[1,2] X_c (u - cx) * d / fx Y_c (v - cy) * d / fy Z_c d # 世界坐标系 P_c np.array([X_c, Y_c, Z_c]) P_w R P_c t return P_w实际项目里我一般把世界坐标系设在地面上Z轴朝上。这样人的高度就是Z坐标判断是不是人就很方便——正常成年人高度在1.5-2米之间低于1米或者高于2.5米的大概率不是人。4.3 基于3D信息的去重与高度过滤3D信息最大的价值是解决2D解决不了的问题遮挡去重和高度过滤。遮挡去重2D场景下两个人前后重叠检测框IoU很高跟踪时容易搞混。但3D场景下两个人的深度值不同直接就能区分开。我一般用3D距离做匹配代价而不是2D IoU。3D距离小于阈值的才认为是同一个人。高度过滤商场里的推车、货架、广告牌2D检测容易误检成人。但3D场景下这些物体的高度要么太低推车要么太高货架直接根据Z坐标过滤掉。我一般设高度范围0.8-2.2米超出范围的检测框直接丢弃。还有一个技巧是用3D信息做轨迹平滑。2D轨迹在遮挡时容易跳变但3D轨迹因为深度信息稳定跳变会小很多。我一般用3D轨迹做卡尔曼滤波再把结果投影回2D做可视化。实操心得深度图的质量很关键。RGBD相机的深度图在边缘处噪声大我一般会对深度图做中值滤波或者用检测框内深度值的中位数而不是中心点深度这样更稳定。4.4 越线计数与方向判断的业务逻辑实现业务层是最后一步把轨迹转化成客流数据。核心逻辑是判断轨迹是否穿过计数线以及穿越方向。计数线一般是一条线段定义在3D世界坐标系的地面上。判断穿越的方法是看轨迹的连续两个点是否分别位于计数线的两侧。如果是再判断穿越方向——从A侧到B侧是进从B侧到A侧是出。# 越线计数示例 def check_crossing(traj, line_start, line_end): # traj: [(x1,y1), (x2,y2), ...] # line: (x1,y1) - (x2,y2) for i in range(1, len(traj)): p1 traj[i-1] p2 traj[i] # 判断p1和p2是否在线的两侧 side1 cross_product(line_start, line_end, p1) side2 cross_product(line_start, line_end, p2) if side1 * side2 0: # 穿越了 direction in if side1 0 else out return True, direction return False, None def cross_product(a, b, p): return (b[0]-a[0])*(p[1]-a[1]) - (b[1]-a[1])*(p[0]-a[0])实际项目里有几个细节要注意计数线位置。计数线要放在人流必经之路上但不能放在门口正中间因为门口有人徘徊。我一般放在门口往里1-2米的位置这样能过滤掉在门口犹豫的人。方向判断。进和出的方向要定义清楚。我一般以相机视角为准从画面下方到上方是进从上方到下方是出。但如果是俯视相机就要根据实际场景调整。去抖动。人在计数线附近来回走动容易反复计数。我一般加一个冷却时间同一个人穿越后3秒内不再计数。或者用轨迹的净位移来判断只有净位移超过阈值才计数。多人同时穿越。两个人并排走同时穿越要分别计数。这个靠轨迹ID区分每条轨迹独立判断。4.5 业务层的常见问题与优化技巧业务层的问题主要是计数不准。我总结了几种情况重复计数。同一个人被计了多次。原因可能是轨迹断裂后重新分配了ID或者人在计数线附近来回走。解决方法加冷却时间或者用ReID特征做轨迹拼接。漏计数。有人经过但没计数。原因可能是检测漏了、跟踪断了、计数线位置不对。解决方法检查检测和跟踪的召回率调整计数线位置。方向判断错。进被判断成出。原因可能是计数线方向定义反了或者轨迹点顺序乱了。解决方法检查计数线定义确保轨迹点按时间顺序排列。多人重叠计数错。两个人重叠时只计了一个。原因可能是跟踪时两个ID合并了。解决方法用3D距离做匹配确保重叠时也能区分。问题可能原因排查方法解决方案重复计数ID切换/徘徊看轨迹ID冷却时间/ReID拼接漏计数漏检/断轨/线位置看检测跟踪日志补检测/调线位置方向错线方向/点顺序可视化轨迹检查线定义/排序重叠计数错ID合并看重叠时ID3D距离匹配5. 整条链路的联调与性能优化5.1 端到端联调从视频输入到客流数据输出整条链路联调我一般分三步走离线联调。用录制的测试视频离线跑整条链路输出客流数据和人工标注的ground truth对比。这一步主要看计数准确率一般要求95%以上。如果达不到就分段排查先看检测召回率再看跟踪ID Switch次数最后看业务逻辑。在线联调。把链路部署到边缘设备接实时视频流观察运行状态。重点关注推理延迟、内存占用、CPU/GPU利用率。如果延迟太高就优化检测模型或者后处理如果内存泄漏就检查轨迹管理有没有及时删除。长期测试。在真实场景跑一周以上观察不同时段、不同光照、不同人流密度下的表现。我一般会记录每天的计数数据和实际客流对比看有没有系统性偏差。联调时我会加一些调试输出每帧的检测框数量、跟踪轨迹数量、当前活跃轨迹ID列表、计数事件日志。这样出问题时能快速定位是哪一层的问题。5.2 性能瓶颈定位与优化手段整条链路的性能瓶颈一般在检测层。检测推理占端到端延迟的60%以上。优化手段按优先级排模型量化。FP32转FP16延迟能降30%左右精度几乎不掉。INT8量化延迟能降50%但精度可能掉1-2个点。我一般先试FP16不够再上INT8。推理引擎优化。TensorRT对NVIDIA平台优化最好OpenVINO对Intel平台优化好。同一模型TensorRT比原生PyTorch快2-3倍。输入分辨率调整。检测输入从640x640降到416x416延迟能降40%但小目标检测能力会下降。如果场景里人比较大可以降分辨率如果远处人很多就别降。跳帧处理。客流统计不需要每帧都检测可以每2帧检测一次中间帧用跟踪预测。这样能降一半计算量但跟踪精度会受一点影响。多线程流水线。检测、跟踪、业务逻辑分到不同线程用队列传递数据。这样能充分利用多核CPU提升吞吐量。跟踪层和业务层相对轻量优化空间不大。但如果轨迹数量很多比如几百条卡尔曼滤波和匈牙利匹配也会耗时。这时候可以限制活跃轨迹数量或者用更高效的匹配算法。5.3 实际项目中的部署经验与教训部署这块我踩过的坑不少分享几个典型的相机标定不准。3D视觉依赖相机内参和外参标定不准深度和坐标全错。我一般用棋盘格标定标定后验证在地面上放几个已知位置的标记看映射后的坐标对不对。误差超过5厘米就要重新标。时间同步问题。RGB图和深度图如果不同步融合时会错位。RGBD相机一般硬件同步问题不大双目相机需要软件同步要注意时间戳对齐。边缘设备散热。Jetson系列跑久了会降频推理延迟飙升。我一般加散热片或者风扇或者限制推理频率避免过热。网络传输延迟。如果相机和边缘设备分离视频流走网络延迟会很大。我一般用RTSP推流延迟在200ms左右。如果要求低延迟就用USB直连或者MIPI接口。模型更新。场景变化后比如商场重新装修检测模型可能失效。我一般保留一个在线更新机制收集新场景的数据定期重新训练模型。注意部署前一定要做压力测试模拟最大人流密度看系统能不能扛住。我见过太多项目平时跑得好好的一到节假日人流暴增就崩了。6. 常见问题速查与避坑指南6.1 检测跟踪联合调试的典型问题检测和跟踪是耦合的很多问题需要联合排查。我整理了一个速查表现象可能层级排查步骤解决方案计数偏多跟踪/业务看ID Switch次数降匹配阈值/加冷却计数偏少检测/跟踪看检测召回率补数据/调阈值ID频繁切换跟踪可视化ID变化调卡尔曼/用ReID轨迹碎片化检测/跟踪看漏检率低置信匹配/增max_age深度跳变3D融合看深度图质量滤波/中位数深度高度过滤误杀3D融合看过滤日志调高度范围联合调试时我一般先固定检测层调跟踪层跟踪层稳定后再调3D融合层最后调业务层。不要同时调多层否则出了问题不知道是哪层的。6.2 不同场景下的参数调整建议不同场景参数差别很大。我按场景给几组推荐参数商场入口人流密集、侧视检测置信度0.4跟踪IoU阈值0.3min_hits3max_age30高度范围0.8-2.2米计数线位置门口往里1.5米办公楼通道人流稀疏、俯视检测置信度0.5跟踪IoU阈值0.4min_hits2max_age20高度范围1.0-2.0米计数线位置通道正中间地铁闸机快速通过、正视检测置信度0.45跟踪IoU阈值0.35min_hits2max_age15高度范围0.9-2.1米计数线位置闸机口这些参数不是绝对的要根据实际效果微调。我一般先在测试集上网格搜索找到最优组合再在真实场景验证。6.3 独家避坑技巧与经验总结最后分享几个我踩坑踩出来的经验不要迷信端到端模型。有些论文提出检测跟踪端到端的模型但实际项目里分离式架构更可控出了问题好排查。端到端模型一旦效果不好你都不知道是哪部分的问题。数据比算法重要。我见过太多项目在算法上折腾半天最后发现是训练数据不够。花时间收集和标注高质量数据比换模型带来的提升大得多。简单场景用简单方案。如果场景里人不多、遮挡少2D检测加跟踪就够了不用上3D。3D视觉虽然好但成本和复杂度都高杀鸡不用牛刀。留好调试接口。整条链路要留足够的日志和可视化接口出问题时能快速定位。我一般会保存每帧的检测框、跟踪轨迹、计数事件方便回放分析。定期更新模型。场景会变模型会过时。我一般每季度重新训练一次检测模型用最近收集的数据。这样能保持长期稳定。测试要覆盖边缘情况。正常场景跑得好不代表没问题要专门测试光照突变、人群暴增、遮挡严重、有人徘徊。这些边缘情况才是真正考验系统的时候。硬件选型留余量。算力不要卡着需求选留30%余量。人流高峰时算力需求会暴增没余量就崩了。和业务方对齐计数口径。什么叫“一个人”大人抱小孩算一个还是两个推婴儿车算不算这些业务规则要在项目初期就对齐否则最后数据对不上扯皮都扯不清。这些经验有些是踩坑踩出来的有些是同行交流学到的。客流统计这条链路技术只是一部分更多的是对场景的理解和对细节的把控。算法链路搭好了剩下的就是不断调优和迭代。