ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

YOLOv11多目标跟踪与热力图:零售门店客流统计实战

YOLOv11多目标跟踪与热力图:零售门店客流统计实战 简介这份PDF文档面向零售行业技术人员、计算机视觉初学者及希望落地智能客流分析的开发者系统讲解如何用YOLOv11实现多目标跟踪并生成店内热力图。内容从零售客流统计的重要性与挑战切入剖析YOLOv11骨干网络、颈部网络与检测头架构以及基于检测的跟踪与目标关联算法再延伸至核密度估计、网格划分、颜色映射等热力图生成原理并给出小型便利店、中型超市、大型购物中心三类实战案例与效果对比。资源包共1个PDF文件大小约2.03MB支持目录章节跳转、阅读器左侧大纲显示与章节快速定位33页内容完整、图表清晰便于按模块查阅。已有114人学习。读者可借此掌握从系统架构设计、硬件选型、模块开发到性能优化与部署上线的完整链路并获得客流指标计算、动态热力图更新及算法优化等可复用思路。1. 零售门店客流统计为什么值得用 YOLOv11 重做一遍一家 200 平的社区超市店长每天最头疼的不是缺货而是说不清「人到底从哪来、在哪停、哪条动线是死的」。传统做法要么靠人工掐表要么靠红外对射计数器前者费人后者只能给一个进出总数连顾客在货架前站了多久都不知道。YOLOv11 多目标跟踪加热力图这套组合解决的正是这个问题用普通监控摄像头把每一帧里的人检测出来、跨帧绑定成轨迹、再把轨迹累积成空间热度分布最终输出一张能直接指导陈列调整的热力图。这套方案适合三类人一是想从零搭一套门店客流分析系统的算法工程师二是手里已有摄像头、想低成本验证效果的技术负责人三是做零售数字化但被商用客流盒子报价劝退的开发者。它不需要 GPU 集群一张消费级显卡就能跑通推理训练环节用 YOLOv11 的预训练权重做微调即可。下面按「原理选型 → 环境与数据 → 跟踪与热力图实现 → 避坑 → 进阶调优」的顺序把这条链路拆开讲透。2. YOLOv11 检测 多目标跟踪 热力图三段链路怎么选型2.1 为什么检测端选 YOLOv11 而不是 YOLOv8零售场景的检测目标很单一——人。但难点在于摄像头装在 2.8 米左右高度俯拍人形在画面里往往只有 40 到 90 像素高属于典型小目标同时门店灯光不均匀逆光门口和货架阴影区对比强烈。YOLOv11 相比 YOLOv8 在 neck 部分换了 C3k2 结构检测头改成解耦式小目标召回率在同类数据上通常有可见提升这也是热搜里「yolov11小目标优化」被反复提的原因。选型上我一般这样定如果只是验证可行性直接用yolo11n.pt或yolo11s.pt预训练权重跑推理人这个类别在 COCO 里本来就有零样本就能出结果如果要正式上线必须用自己门店的监控截图标注 800 到 1500 张做微调否则俯拍角度下的漏检会让你怀疑人生。跟踪端不要自己写直接用 ByteTrack 或 BoT-SORTUltralytics 已经内置一行配置就能挂上。热力图端用累积轨迹点做高斯核密度估计比单纯画点图更能反映「停留强度」。2.2 三段链路的输入输出契约把整条链路想成流水线每段的输出格式必须提前定死否则后面接不上环节输入输出关键字段检测单帧图像检测框列表bbox(xyxy)、conf、cls跟踪检测框 历史轨迹带 ID 的框track_id、bbox、帧号热力图轨迹点序列热度矩阵/图像坐标、停留权重、核宽这里最容易翻车的是坐标系。检测框是像素坐标热力图如果直接按像素累积换个分辨率就废了。常见做法是把像素坐标归一化到 0 到 1或者用单应性矩阵映射到门店平面图的物理坐标这样热力图才有跨摄像头、跨日期的可比性。2.3 跟踪器选 ByteTrack 还是 BoT-SORTByteTrack 的核心思路是「不丢弃低分框」先用高分框匹配轨迹再用低分框补匹配对遮挡场景友好速度快。BoT-SORT 在 ByteTrack 基础上加了相机运动补偿和 ReID 特征适合摄像头会轻微晃动的场景但多了一个 ReID 模型帧率会掉。零售门店摄像头一般固定安装我优先选 ByteTrack配置简单、延迟低。如果门店有多个摄像头需要跨镜追踪同一个人那才考虑上 ReID但那是另一个量级的工程。3. 环境配置与数据准备从零跑通第一帧3.1 用 conda 搭一个干净的 YOLOv11 环境热搜里「yolov11(ultralytics)环境配置」是高频问题坑基本都在 CUDA 版本和 torch 版本对不上。我习惯用 conda 隔离步骤如下# 创建独立环境python 版本别追新3.10 最稳 conda create -n yolo11 python3.10 -y conda activate yolo11 # 先装 torch注意 CUDA 版本要和驱动匹配这里以 cu121 为例 pip install torch torchvision --index-url https://download.pytorch.org/whl/cu121 # 再装 ultralytics它会自动拉 opencv、numpy 等依赖 pip install ultralytics # 验证 GPU 是否可用 python -c import torch; print(torch.cuda.is_available(), torch.__version__)逻辑说明先装 torch 再装 ultralytics是为了避免 pip 自动解析时给你装一个 CPU 版 torch。参数上--index-url指向 PyTorch 官方 wheel 源cu121 对应 CUDA 12.1如果你的驱动只支持到 11.8就换成 cu118。验证那行必须打印True否则后面推理会静默走 CPU帧率掉到个位数你还以为是模型慢。3.2 用预训练权重先跑一帧确认链路通在标注数据之前先用 COCO 预训练权重跑一段门店视频确认检测和跟踪能出结果from ultralytics import YOLO # 加载预训练权重n 版最轻适合先验证 model YOLO(yolo11n.pt) # 对视频做跟踪推理persistTrue 保证跨帧 ID 连续 results model.track( sourcestore_demo.mp4, trackerbytetrack.yaml, # 跟踪器配置 conf0.3, # 置信度阈值俯拍场景别设太高 iou0.5, # NMS 的 IoU 阈值 persistTrue, # 关键保持跟踪状态 saveTrue, # 保存带框视频方便肉眼核对 showFalse )逻辑说明model.track是 Ultralytics 封装好的检测加跟踪接口内部先检测再喂给跟踪器。persistTrue是跨帧保持 ID 的关键不加的话每帧都当新目标track_id 会乱跳。conf0.3比默认 0.25 略高是因为俯拍小目标误检多但也不能太高否则漏检。跑完看saveTrue输出的视频如果人框稳定、ID 不闪说明链路通了可以进入标注环节。3.3 标注数据的三个硬性要求微调数据不用多但要「对」只标 person 一类其他类别全部忽略减少干扰。标注框要贴紧人体可见部分俯拍时头和肩是主要特征别把地面阴影框进去。训练集和验证集按 8:2 分且要覆盖不同时段白天、傍晚、逆光否则模型在某个光照下会集体翻车。导出成 YOLO 格式后目录结构是images/train、labels/train、images/val、labels/val配一个data.yaml指向这些路径和类别名。这一步没有捷径标 1000 张大概需要一个人一天半但它是后面所有效果的地基。4. 多目标跟踪与热力图生成把轨迹变成可读的热度4.1 从 track_id 提取轨迹点并过滤噪声跟踪跑完每个 track_id 对应一条轨迹。但原始轨迹里有大量噪声有人只是路过镜头边缘、有人被货架挡住导致 ID 切换、还有静止不动的店员。直接拿全部点画热力图结果会糊成一团。我一般做三层过滤import numpy as np from collections import defaultdict # 假设 tracks 是 {track_id: [(frame, x_center, y_center), ...]} tracks defaultdict(list) def collect_tracks(results): for r in results: if r.boxes.id is None: continue ids r.boxes.id.cpu().numpy().astype(int) xywh r.boxes.xywh.cpu().numpy() for tid, box in zip(ids, xywh): x, y box[0], box[1] tracks[tid].append((r.frame_id, x, y)) def filter_tracks(tracks, min_len15, max_jump80): clean {} for tid, pts in tracks.items(): # 过滤一轨迹太短视为路过噪声 if len(pts) min_len: continue # 过滤二相邻帧位移过大视为 ID 跳变 valid [pts[0]] for p in pts[1:]: dx p[1] - valid[-1][1] dy p[2] - valid[-1][2] if (dx**2 dy**2) ** 0.5 max_jump: valid.append(p) if len(valid) min_len: clean[tid] valid return clean逻辑说明min_len15表示一条轨迹至少存在 15 帧才保留按 25fps 算约 0.6 秒能滤掉大部分路过。max_jump80是相邻帧中心点位移上限超过就认为 ID 被错误继承断开。这两个参数要按你的帧率和画面分辨率调分辨率越高max_jump 要相应放大。4.2 用高斯核密度生成热力图有了干净轨迹点热力图用核密度估计KDE生成比直接画散点更能体现停留强度import cv2 import numpy as np def build_heatmap(tracks, h, w, sigma25): # 初始化累积矩阵 heat np.zeros((h, w), dtypenp.float32) for tid, pts in tracks.items(): for _, x, y in pts: ix, iy int(x), int(y) if 0 ix w and 0 iy h: heat[iy, ix] 1.0 # 高斯模糊做核密度平滑sigma 控制热度扩散范围 heat cv2.GaussianBlur(heat, (0, 0), sigma) # 归一化到 0-255 便于着色 heat heat / (heat.max() 1e-6) * 255 heat heat.astype(np.uint8) # 伪彩色映射JET 对比最强 colored cv2.applyColorMap(heat, cv2.COLORMAP_JET) return colored逻辑说明先在每个轨迹点位置累加计数再用高斯模糊把离散点扩散成连续热度场。sigma25是扩散半径值越大热区越糊、越平滑值越小越锐利但可能碎。归一化那步不能省否则不同时段客流差异大时颜色不可比。COLORMAP_JET是热力图最常用的配色红高蓝低店长一眼能看懂。4.3 把热力图叠回门店平面图纯热力图没有空间参照店长看不懂。常见做法是把热力图和门店平面图做半透明叠加def overlay(floor_plan_path, heatmap): plan cv2.imread(floor_plan_path) plan cv2.resize(plan, (heatmap.shape[1], heatmap.shape[0])) # 半透明叠加alpha 控制热力图权重 alpha 0.55 blended cv2.addWeighted(plan, 1 - alpha, heatmap, alpha, 0) cv2.imwrite(store_heatmap_overlay.png, blended)逻辑说明alpha0.55是热力图权重太高会盖住平面图细节太低热区看不清0.5 到 0.6 之间比较合适。前提是热力图坐标系和平面图坐标系已经对齐如果摄像头是斜拍需要先用单应性矩阵把像素坐标映射到平面图坐标这一步不做叠加就是错位的。5. 客流统计落地避坑五条血泪经验5.1 现象白天准、傍晚漏检严重原因门店傍晚逆光人形变成剪影YOLOv11 在正常光照下训练的权重对高对比度剪影不敏感。解决在标注数据里专门补 200 张逆光截图训练时开mosaic和hsv_v增强让模型见过这种极端光照。别指望一个权重打天下。5.2 现象同一个人被分配了多个 track_id原因顾客被货架或柱子遮挡超过跟踪器的缓冲帧数轨迹断了重新出现时被当新目标。解决ByteTrack 的track_buffer默认 30 帧遮挡严重的门店调到 60同时降低match_thresh让匹配更宽松。如果还不行说明遮挡时间太长只能上 ReID。5.3 现象热力图上一片红看不出重点原因轨迹点没做过滤路过的人和停留的人权重一样加上 sigma 设太大整个画面糊成一块。解决按轨迹长度加权停留久的轨迹给更高权重sigma 从 25 降到 15 试试再不行就只保留停留超过 3 秒的轨迹点。5.4 现象推理帧率只有 5fps视频卡顿原因多半是 torch 装成了 CPU 版或者imgsz设太大。解决先跑torch.cuda.is_available()确认推理时imgsz640足够别用 1280如果还慢把模型换成yolo11n或者用 TensorRT 导出加速。5.5 现象换了个摄像头热力图完全对不上原因不同摄像头的安装高度、角度、焦距不同像素坐标没有可比性。解决每个摄像头单独做一次单应性标定把像素坐标映射到统一的平面图坐标系。标定用四对对应点就够OpenCV 的findHomography一行搞定。不做标定跨摄像头数据就是废的。6. 进阶用停留时长加权和分时段热力图挖出真动线基础版热力图只告诉你「哪里人多」但零售真正想知道的是「哪里人停得久」。同样是红色区域一个人快速穿过和一个人站了 30 秒商业含义完全不同。我的做法是给每个轨迹点加时间权重轨迹在某个网格内停留的帧数越多该点权重越高。实现上不用改检测和跟踪只在累积热力矩阵时把heat[iy, ix] 1.0改成 dwell_weight其中dwell_weight是该轨迹在当前网格的连续帧数。更进一步把一天切成早、中、晚三个时段分别生成热力图再对比。我做过一个社区超市的案例全天热力图显示收银台最热但分时段一看上午生鲜区热度高、下午日化区热度高而收银台的热度其实是排队造成的假象。店长据此把促销堆头从收银台旁边挪到了生鲜区入口两周后生鲜区连带购买率有明显变化。这个结论靠一个总数计数器永远得不出来。验证热力图准不准我有个笨办法但很管用随机抽三个时段人工在监控里数某个货架前停留超过 5 秒的人数和热力图对应区域的热度排名做对比。如果排名一致说明这套链路可信如果差得远先回去查单应性标定和轨迹过滤参数。参数没有万能值sigma、min_len、max_jump这三个我每次换门店都要重调一遍调参花的时间往往比写代码多。最后说个习惯我从来不直接把热力图当结论交给业务方而是附上原始轨迹视频和过滤前后的对比图。业务方看到「过滤掉了 40% 的噪声轨迹」这个数字才会相信剩下的热区是真的。技术方案能不能落地很多时候不取决于模型多强而取决于你能不能把中间过程讲清楚、让用的人敢信。希望帮到你。本文还有配套的精品资源点击获取
返回列表