
商场做客流统计这件事听起来简单真做起来坑不少。早几年用红外对射或者WiFi探针的方案精度和抗干扰能力都一言难尽。现在基于深度学习的目标检测技术成熟了用YOLO系列去做视觉客流统计成了主流方案精度高、部署灵活还能顺带拿到行人轨迹这类额外数据。这篇文章就从一个实际落地项目的角度把基于YOLOv5/v8/v10构建商场客流智能统计系统的全流程拆开讲清楚包括数据集怎么搞、模型怎么选怎么练、追踪计数逻辑怎么做以及最后怎么套一个能看的UI界面上去。内容偏实操适合有Python基础、想拿视觉检测做完整项目的开发者参考。1. 方案选型与整体架构设计1.1 YOLOv5/v8/v10怎么选YOLO系列现在版本多得让人眼花缭乱刚接触的人很容易卡在第一步到底用哪个版本我给出的建议很直接——主力用YOLOv8特殊场景看需求切换到v5或v10。YOLOv5胜在生态成熟网上资料最多很多老项目都基于它如果你要维护旧代码或者参考历史方案v5是必须会的。但v5的CSPDarknet结构相对老了同等精度下推理速度不如v8优化得好。YOLOv8是目前工程落地的甜点版本。Ultralytics官方维护API统一训练、验证、导出一条龙而且Anchor-Free的检测头设计让它在小目标比如距离摄像头较远的行人上表现更好。商场客流场景里人通常是密集的、尺度和姿态变化都很大v8的泛化能力实测下来比v5稳。YOLOv10重在最极致的推理速度。它去掉了NMS后处理端到端部署很干净适合在Jetson这类边缘设备上跑极限帧率。但v10刚出来没多久周边工具链没有v8成熟我在实际项目里用它做过一次尝试速度确实快但训练调参的坑比v8多新手不建议直接上。我这套系统的选型逻辑训练阶段用YOLOv8m部署阶段按设备算力导出FP16或INT8量化模型。如果现场只有老旧GPU或者纯CPU再考虑用YOLOv5s或者YOLOv10n做降级方案。1.2 系统整体架构与模块划分一个能实际运行的客流统计系统最少包含四个模块视频接入层、检测追踪层、计数逻辑层、可视化展示层。视频接入层负责拉取摄像头RTSP流或者读取本地视频文件解码成帧交给检测模块。这里要注意商场摄像头一般是大华或海康的RTSP流如果没配置好会产生花屏和卡顿这种问题跟模型精度没关系但排查起来很头疼后面我会专门讲这块的坑。检测追踪层就是YOLO系列模型做行人检测再配合跟踪算法给同一个行人分配稳定的ID。客流计数不能只看单帧检测结果不然一个人走过去会被重复计数所以必须加跟踪。计数逻辑层是整个系统的灵魂。常见的做法是虚拟线圈——在画面上画一条线跟踪目标跨线时触发计数。这个方案逻辑简单、实时性高而且好调阈值商场出入口场景够用。更复杂的做法是计算轨迹方向比如从摄像头画面左侧走到右侧记为“进”反过来记为“出”这在双通道门禁场景很实用。可视化展示层有两种主流选择基于PyQt/PySide的桌面应用或者基于Flask/FastAPI的Web界面。桌面端适合单机部署直接接显示器看实时画面Web端适合把数据暴露给商场运营团队手机浏览器就能看。我项目里用的是Web方案因为需求方要同时给财务、运营、安保三个部门开放查看权限。2. 数据集构建与预处理2.1 数据采集的工程细节数据集质量直接决定模型精度天花板。很多人拿COCO的person类直接训效果却不好问题就出在场景分布差异上——COCO里的行人大多是户外自然场景跟商场室内灯光、俯视角度、密集人群差别很大。正确做法是去目标场景采集原始视频。我当时的做法是找了A同学的实战课模拟场地另外还用了几段公开的商场监控视频片段。采集时要注意覆盖时间段早中晚的光线不同商场的灯光角度、自然光比例都会变。尽量采集早上10点、下午3点、晚上8点三个时段的视频保证光照多样性。覆盖摄像机角度出入口的摄像头有高有低、有的正对着人走的方向、有的是侧向视角。角度不同行人的形态差异非常大。至少要采集两种典型角度的数据。覆盖人流量等级要刻意拍一组平时的稀疏客流再拍一组促销日或者节假日的密集客流。密集场景下人挤人贴合边界严重这是模型最容易翻车的地方。如果现场采集不方便我的后备方案是用公开的MOT挑战赛数据集比如MOT17里截取行人帧再通过风格迁移或者图像融合把户外行人“贴”到商场背景里。这种方法能快速扩充数据量但要注意不要让贴图痕迹太重否则模型会学到错误的纹理特征。2.2 标注规范与工具选择数据集格式推荐直接做YOLO格式的TXT标注一行一个目标内容是“类别的索引 x_center y_center width height”坐标都是归一化到0~1的。标注工具我用的是LabelImg虽然界面老旧了点但胜在稳定、上手零成本。XAnyLabeling这种新工具也支持自动预标注适合处理大规模数据但我自己的经验是全人工标注虽然慢但质量可控尤其是对边界模糊的目标人眼判断更可靠。标注时最容易翻车的点是类别的定义。有人会把person、customer、passenger这种类别全建一遍结果网络被搞糊涂了。商场客流统计只需要一个类别person或者如果你还要顺带统计员工可以加一个staff类但要保证标注样本数量足够多。类别越少模型越好训这是铁律。关于是否要标注“被遮挡只露出一半的人”这个问题我的建议是一半以上身体可见的都要标。如果完全不标模型看到遮挡目标就会漏检如果标得太多又会让模型对大面积遮挡目标产生误检。折中方案是在训练数据里保留约20%的遮挡样本标注时统一标准露出头部和肩部的算正样本。标注规模上我建议至少准备5000张有效标注帧。如果从30分钟的视频里抽帧大概能抽到3000~4000张加上增强扩样基本够用。不要追求数量上的虚高每张图平均标注人数在5~20人的数据比每张图只有1~2人的数据有价值得多因为密集场景的拥挤度分布wisdom直接决定模型上限。2.3 数据增强策略YOLOv8自带的增强已经很强了默认配置里有马赛克Mosaic、随机仿射变换、HSV色域变换等。对于商场场景我要额外注意两点第一不要开太大的水平翻转。商场摄像头如果是装在出入口正上方画面里人的左右朝向是有实际意义的——左边是门内右边是门外。盲目开水平翻转会让模型分不清左右方向影响后面方向判定逻辑。如果计数的核心是跨线而不是方向那翻转可以开但你要做进出分流计数我建议关掉或者只在部分数据上开。第二马赛克增强对密集人群有奇效。Mosaic把四张图拼在一起相当于变相增加了每张图的密度模型能提前适应“人多到爆炸”的情况。v8的训练默认会自动用这个策略想调节比例可以查看ultralytics配置文件的mosaic参数。还有一个我在预处理时踩过坑的细节不要用太高的输入分辨率。YOLOv8默认训练分辨率是640x640如果你觉得检测小目标不够准尝试提升到960或1280确实有效但训练时间会暴增而且部署时输入尺寸必须跟着改。商场的1080p画面缩小到640x640行人大约只有30~60像素高这个尺寸对YOLOv8来说完全够识别了没必要盲目上高分辨率。3. 模型训练全流程与参数调优3.1 环境配置与训练脚本训练环境建议直接上显卡最低6GB显存起步。数据标注是在某台老台式机上做的CPU是十年前的水平但训练和推理根本跑不动后来换了云GPU环境每天租用的成本比买卡划算得多。哪怕你手头有卡我也建议至少先本地把环境配好跑通一次训练流程再转云。依赖安装没什么好说的pip install ultralytics一条命令搞定。需要注意版本锁定ultralytics升级很频繁我用的是8.2.x版本训练和导出都稳定。如果新版本出问题直接回退到8.1.x大概率能救回来。训练脚本非常简洁用官方API就能启动yolo train dataconfig/data.yaml modelyolov8m.pt epochs150 imgsz640 batch16 device0关键全在config/data.yaml里结构如下train: /path/to/dataset/images/train val: /path/to/dataset/images/val nc: 1 names: [person]一定要检查路径写的是绝对路径还是相对路径我吃过亏——在本地写的是相对路径换到云服务器上训练直接报错找不到数据。3.2 训练参数设置逻辑训练时最核心的超参数是epochs和batch。epochs我建议至少120起步配合早停patience20机制模型训练到后期精度提升会越来越慢早停会自动截断避免白烧显卡。batch则看显存如果训练时报CUDA Out of Memory的错误把batch调小或者开启梯度累积accumulate参数。初始学习率使用默认值就行。v8的默认初始lr是0.01配合warmup和余弦退火调度整体曲线比较平滑。如果你想更快收敛到低loss可以调高到0.02我有次实验确实缩短了10%的训练时长但最终mAP差不多没必要为了节省一点点时间增加风险。图像大小imgsz前面说过这里再强调一遍部署一致性训练和推理的分辨率如果差太多精度会有明显下降。最好是训练用640部署也把输入resize到640。训练过程的监控主要看两个指标训练loss曲线和验证集mAP50。loss曲线稳步下降说明在正常收敛mAP50在0.8以上对于单类行人检测来说就算及格达到0.87以上在商场场景下基本不会出现大面积漏检了。我记得当时训练到第80个epoch左右收敛最终mAP50为0.89mAP50-95为0.71。这组数据供参考如果低于这个水平先怀疑数据集而不是模型结构。3.3 预训练权重与模型微调从零训练YOLOv8m大约要训到天荒地老而且效果还不如迁移学习。强烈建议加载COCO预训练权重yolov8m.pt然后用自己的商场数据集微调。COCO里各种类别的通用特征已经学得很好了你只需要让网络在“行人”这个类别上去适应你场景的风格。微调时要注意冻结部分层。YOLOv8m的身体层比如前10层冻结不更新只训练检测头和后面某些特征层好处是训练速度快、所需数据量少特别适合数据集只有几千张的情况。用Ultralytics的freeze参数就可以freeze10一般是指定冻结前10层。我实际测试下来冻结前10层确实让训练速度提升了30%左右而且不会掉点。因为在预训练模型上底层特征提取器学的已经是通用视觉特征了你场景里的光照角度变化还没到颠覆它的时候。4. 客流计数核心逻辑检测 跟踪 跨线判定4.1 跟踪器的选择ByteTrack优先检测是单帧的事但统计是连续帧的事。需要一个跟踪算法把不同帧里的同一个行人关联起来保持ID稳定。主流的跟踪方案有DeepSORT和ByteTrack两种。DeepSORT依赖外观特征提取模型一般是ReID网络流程复杂、模型又多一套推理速度慢而且外观特征在商场这种帽子口罩、衣服相似度很高的场景里经常失效——你让两个穿黑衣服的人从摄像头前走过DeepSORT非常容易把这个人的ID换到那个人身上。ByteTrack则是基于运动信息的优雅方案。核心思想是“谁说检测置信度低就一定不是人”——它会把低置信度的检测框也保留下来利用高置信度匹配后的剩余框和剩余轨迹再做一轮匹配。这样做的效果是即使模型这一帧有个目标检测置信度只有0.3一般会被阈值卡掉ByteTrack也能通过位置交并比把它匹配上保持ID稳定。我在商场场景里实测对比过DeepSORT的ID Switch次数是ByteTrack的3倍以上。对于客流统计这种系统ID Switch多就意味着计数错误多所以不用犹豫直接ByteTrack。ByteTrack的调用不需要自己写Ultralytics官方已经内置了tracker配置文件指向bytetrack.yaml就行当然后端也有官方写的参考实现。关键参数是track_buffer追踪缓冲帧数这个控制目标丢失后保留轨迹多久。商场里大量的人被瞬时遮挡如果buffer设得太短人从柱子后面出来就换新ID了设得太长又会增加误匹配。我调参的时候发现**track_buffer60**是个比较稳的值对应约2~3秒的遮挡容忍足够应对大部分情况。4.2 虚拟线圈跨线计数原理拿到稳定的跟踪ID后计数就简单了。虚拟线圈的本质是在画面里定义一条有方向的线段记录每个目标中心点的运动轨迹当中心点从线段一侧运动到另一侧时判断方向并累加计数。具体代码逻辑我用一个简单的示例来说明def is_crossing_line(prev_point, curr_point, line_start, line_end): # 线段向量 line_vec_x line_end[0] - line_start[0] line_vec_y line_end[1] - line_start[1] # 目标运动向量 mv_x curr_point[0] - prev_point[0] mv_y curr_point[1] - prev_point[1] # 计算叉积判断跨线方向 side_prev (prev_point[0] - line_start[0]) * line_vec_y - (prev_point[1] - line_start[1]) * line_vec_x side_curr (curr_point[0] - line_start[0]) * line_vec_y - (curr_point[1] - line_start[1]) * line_vec_x return side_prev * side_curr 0当检测到目标与虚拟线圈有交点时再结合前后帧中心点的位置变化就能判断是出还是进。这套逻辑很成熟但细节在工程化线圈不要画在画面最边缘那里人刚进入镜头只检测到半个身体中心点不稳容易发生横跳。我一般把线圈画在画面中间偏出入口的位置保证人至少有1秒的有效轨迹被跟踪到。线圈宽度建议跟一个成年人的肩宽差不多画太宽会把并行走进来的两个人算成一次穿越画太窄又容易漏。用中心点判断时连续帧之间轨迹要做平滑滤波否则中心点抖动会导致误判“跨了两次线”结果一个进出动作被重复计数。最简单的办法是当检测到跨线事件后对该ID设置200帧的冷却时间期间不再触发计数这是所有防重复计数的底线。4.3 进出方向判定与高峰时段数据清洗方向判定是商场客户比较在意的功能除了知道“有几个人来了”更想知道“净增加人数”是多少。这里要把进、出两个方向分开累计。我在代码里维护两个计数器entered_count和exited_count当目标从一个方向上跨线时递增对应方向。有个细节要注意如果人的轨迹是先往左移动再折返往右走这一来一回不应该被计为“进了一次又出一次”。为了应对这种情况需要对每个目标记录它的完整运动路径计算它在线圈附近的整体方向向量而不是只看局部几帧的位移。简单做法是把目标第一次出现在画面里的位置和最后离开的位置或最终位置连成一条向量用这个向量去跟线圈方向向量做点积。高峰期还有个数据清洗问题商场开门前保洁人员来回走动、铺货人员经过出入口这些都会被算成客流。我在实际项目里会给计数结果加一个滞留时间过滤——目标如果从出现到跨线中间停留超过5秒比如站在那里聊天的安保就认为是非纯经过类目标不计入客流数据。这个过滤不能直接做在检测层最好做在计数层让统计逻辑更灵活。5. UI界面与可视化系统实现5.1 技术栈选定FastAPI Web前端很多人以为做UI就是硬啃PyQt其实对于商场方这种同时多部门查看的需求Web界面的性价比要高太多了。我用FastAPI做后端接口前端用Vue ECharts做数据可视化整体开发量比PyQt小而且跨平台、随时远程访问。FastAPI这里有两个先天优势一是异步事件循环天然适合对接视频流的实时推送二是自带Swagger文档调试接口不需要额外装工具。后端处理视频帧的流程是这样的摄像头帧拿到后送进YOLOv8模型推理同步或异步都行得到检测框后交给ByteTrack更新ID然后在视频帧上画线和框最后把帧编码成JPEG推给前端前端用img标签每秒刷新10~15次实现实时画面。这个方案比WebRTC简单多了而且实测延迟在200毫秒以内对看实时画面来说完全可以接受。5.2 数据面板与图表模块数据面板才是客户每天盯着的页面。我用ECharts做了两个核心图表某时间段的进出量柱状图统计每半小时进、出人数方便商场运营判断早晚高峰。数据存在后端内存里或SQLite中前端每隔30秒拉一次接口刷新不搞实时推送因为半小时级别粒度的数据没必要用WebSocket。实时累计差额折线图把entered_count - exited_count随时间变化画出来这就是商场当前滞留人数的近似值在消防疏散和预案制定时很有参考价值。另外还要有一个人流量热力图模块。我把视频画面划分成网格比如6x8统计每个格子内检测框中心点的出现频次叠加在画面上。哪个区域最拥挤一目了然客户看了都说这比光看数字强得多。实现上用后端维护一个二维数组每隔1秒把所有活跃目标的坐标刷进对应格子再映射成热力颜色返回前端。5.3 系统配置与告警功能UI不光是展示还要能配置。我在界面上做了几个实用功能画线——操作人员可以鼠标在视频流画面上直接拖拽线条修改虚拟线圈位置不用重启服务阈值设定——当某块区域人员密度超过设定值界面弹告警提示回放检索——按时间段重放历史计数数据这个目前是简单实现真的要做大型回放系统牵扯到视频存储架构那是另一个工程了。前端画线的实现其实不复杂捕捉鼠标在img标签上的像素坐标换算成模型推理输入下的坐标注意缩放比例存到后端配置里下一次推理就用新线圈。前端再在画布层覆盖画一条线显示位置。6. 模型部署、性能优化与常见问题排查6.1 导出与推理加速模型训练完不能直接拿.pt文件用。部署环境千差万别最好把模型导出成更适合运行的格式。英伟达GPU部署用TensorRT封装的engine格式。推理速度对比我实测在RTX 3060上YOLOv8m的PyTorch原生推理大约25毫秒/帧转TensorRT FP16后直接压缩到8毫秒/帧快了3倍以上。这个优化是必须做的不然同时接4路摄像头单卡根本忙不过来。导出命令yolo export modelbest.pt formatengine device0 halfTrue导出时指定halfTrue开启FP16。如果显存紧张还可以尝试int8True做INT8量化但要做校准集否则精度可能崩掉。我的建议是FP16是性能与质量平衡点INT8除非极限需要否则不碰。如果部署机是CPU用ONNX Runtime跑INT8量化的onnx模型速度也还过得去但跟GPU不是一个量级。纯CPU跑YOLOv8m在1080p上大约150毫秒/帧也就是说每秒不到7帧勉强能动但做实时统计会很吃力。CPU部署优先考虑YOLOv8n或YOLOv10n。6.2 摄像头流处理的实战技巧商场摄像头RTSP流是最容易出幺蛾子的环节这里单独列几个排查重点断流重连RTSP流可能因为网络抖动或者摄像头重启而断掉。代码里必须实现自动重连机制不然摄像头掉线一次整个系统就挂到第二天早上特尴尬。我建议维护一个“没收到帧超时”的定时器超过10秒没新帧就重新尝试拉流失败则退避重试。解码延迟问题当发现画面延迟越来越大很大概率是OpenCV的VideoCapture缓冲队列累积了太多帧。解决办法是设置缓冲区大小或者在读取帧时先清空缓冲再取最新帧。多路流并发不要在主进程里每路摄像头都开一个VideoCapture循环。正确做法是每路流一个独立线程或进程经队列送推理。如果环境允许多进程隔离更好某个模型崩溃不会影响其他摄像头。6.3 计数精度的验证与误差评估系统上线前必须有精度验收环节否则客户说“你数得不对”时你没有任何反驳依据。我采用的方法是人工抽验法。取一段15分钟的监控视频人工数出每分钟的进出人数然后让系统跑同样视频对比结果。以人工计数的结果作为基准统计系统的绝对误差和相对误差。我们跑出来的实测数据是平峰时段人少准确率能达到95%以上高峰时段人潮密集准确率会掉到88%左右误差来源主要是遮挡严重时跟踪ID切换导致的重计或漏计。这个水平在商场客流场景基本够用。如果实测误差过大需要分模块定位。我的一套排查思路先看检测框有没有漏检单帧画面里框的数量跟人眼数量是否一致再看跟踪的ID是否稳定同一个人的框ID是不是一直在变最后看线圈位置和方向判定逻辑。定位到具体模块再针对性优化千万别一上来就重新训练模型。6.4 常见错误与避坑速查表现象可能原因解决方案训练时CUDA OOMbatch太大/分辨率太高调小batch使用梯度累积推理慢GPU利用率低未转TensorRT或输入尺寸过大用engine格式及时关闭torch.no_grad()有检测框但计数没反应线圈坐标与推理尺寸不匹配检查缩放比例前后端坐标保持一致一个人来回走动反复计数轨迹折返导致多次跨线引入冷却时间或轨迹方向向量判断跟踪ID频繁切换检测置信度阈值过高漏检低置信度目标检测置信度降到0.25并增大track_buffer实画面白屏RTSP流失败/解码异常检查网络连接与摄像头在线状态打印RTSP返回码前端热力图很稀疏网格精度设太高目标攒不进格子调小网格分辨率加大统计频率6.5 边缘设备部署与云端协同架构有些商场要求把计算放在本地边缘盒子视频不必上传云端避免隐私问题。这种场景下推荐Jetson Orin Nano或者带独立显卡的Mini PC。实测Jetson Orin Nano部署TensorRT FP16的YOLOv8s模型处理一路1080p流能做到25帧/秒左右功耗低、体积小放在弱电箱里很合适。云端和边缘的协同是这样做的边缘端负责实时推理和计数把聚合后的数据每10秒推一份到云端云端负责长期存储和展示报表。这样网络断了也不影响现场因为数据都留在本地恢复后能续传。架构上有一个细节消息传输用MQTT协议比裸HTTP轮询更稳妥带宽占用小且具备断线重连的天然机制省掉自己写心跳的麻烦。6.6 代码工程化与异常兜底最后想强调一个很多人忽略的问题所有外部依赖都要加异常兜底。YOLO推理很简单但生产环境中摄像头断流、模型推理出错、GPU内存泄漏这些情况都要预判。维护一个简单的日志模块记录模型推理时间、每路摄像头状态、计数异常告警这样凌晨一点设备报错时你翻日志能10分钟定位问题而不是先把系统跑一遍看现象。我的习惯是所有关键路径都加try-except外围再套一层看门狗看门狗负责进程崩溃后自动拉起这比排出完美的算法代码对项目存活的意义更大。7. 收尾前最后几句话这个系统做下来我的体会是YOLO模型只占整个项目工作量的小头真正花时间的是数据集的取舍、追踪参数的调优和计数逻辑的防重维护。模型精度再高追踪环节做得糙计数对不上账一样白搭。另外一个小建议如果有条件做项目前先跟商场运营聊半个小时。你会发现他们真正想要的并不是“实时人数”而是“哪个时间段该加开收银台、哪个区域容易堵人、周末和节假日的客流趋势有没有异常”。这些目标会直接影响你要画哪些页面、输出哪些指标。方向对了技术方案才站得住。如果对追踪计数逻辑或者界面实现细节有兴趣欢迎一起交流这套系统的代码整理一下还是很有参考价值的。