YOLOv11货架商品识别实战:从数据标注到动态库存管理
简介面向零售行业技术开发者与管理者这份PDF围绕YOLOv11实现货架商品识别与库存动态管理展开针对传统人工盘点效率低、易出错和库存数据滞后等痛点提供完整的算法选型、系统搭建与优化思路。文档共35页内容从零售业务需求分析切入系统讲解YOLO系列算法演进及YOLOv11的核心原理网络结构、损失函数、训练流程并覆盖货架商品识别系统的数据采集与标注、模型训练与部署库存动态管理系统的实时监控、预警机制与补货策略最后给出实验结果分析和未来展望。整个资源以1个PDF文件形式提供压缩包仅1.89MB支持目录章节跳转和左侧大纲快速定位文字、图表显示清晰便于按章查阅。目前已有77人学习/下载很适合正在探索目标检测在零售场景落地实践的中高级开发者。1. 零售货架识别的真实画像为什么库存管理卡在视觉最后一公里在一家连锁便利店的总部每天上午都会有几十个门店店长把货架照片发到群里然后由运营人员一张张打开、比对、录入缺货商品。这套流程的代价不仅是人力更在于数据是事后的——等发现某款饮料已经空了两天补货单才被创建。货架商品识别要解决的正是这个视觉最后一公里让摄像头或手机拍的普通照片直接变成结构化库存数据。标题里的 YOLOv11是目前把这件事落地最顺的检测模型之一它负责找出画面里的每个商品、每个空位再把位置和类别信息喂给后端的动态库存逻辑让缺了什么、还剩多少、该补多少从一个需要人看的问题变成一个可以自动计算的指标。这篇笔记适合两类人一类是想在自己门店或小仓库里搭一套库存视觉方案的技术负责人另一类是想把 YOLOv11 练出来并部署在零售场景的算法工程师。我会从数据、训练、小目标优化到库存去重把这条路完整走一遍并标出我踩过的坑。2. 货架商品识别的数据与选型先搞定标签再谈YOLOv112.1 货架场景为什么让检测模型集体翻车光照、遮挡和密集排列在普通目标检测数据集里一个物体通常占据画面的主要部分背景干净、目标独立。货架却完全是另一个世界商品紧密排列相邻包装几乎贴在一起瓶装饮料有反光袋装零食有皱褶层板遮挡下半部分价签、促销牌又制造大量干扰。我第一次拿通用 COCO 预训练模型直接去跑超市饮料货架照片结果非常震撼——它把一排同款可乐认成十几个不同物体还把价签当成了商品。原因在于通用模型的学习分布和货架场景差异太大。货架识别的几个硬约束一是密集同类商品挨得很近NMS非极大值抑制稍微激进一点就会把相邻的真实目标合并成一个二是弱纹理很多商品包装大面积纯色或高光模型不容易提取稳定特征三是类别不平衡可口可乐和百事可乐长得像但库存意义上它们是不同 SKU需要模型区分到包装细节。所以在上手 YOLOv11 之前第一优先级不是调参而是把数据问题和标签问题想清楚。常见做法是自采数据不要试图在网上找现成的商品数据集——不同国家、不同货架的陈列方式差异太大公开数据集只能用来做预训练或验证流程。2.2 数据采集与标注货架图片怎么拍、标签怎么打采集阶段要覆盖门店的多个角度、多个时间段、多种光环境。我会用一个可以伸缩的自拍杆模拟不同身高视角从正面和稍微俯视的角度各拍一遍每张照片里保证货架层板大致水平商品不出现严重透视变形。数量上每个 SKU 至少要有 200 个实例如果 SKU 总数是 50那么标注框总量最好在 1 万到 2 万之间。注意这里的实例数是指所有图片中该商品出现的次数之和不是图片张数。标注工具我一般用 LabelImg 或 X-AnyLabeling。标注时有一个容易被忽略的细节框要贴住商品可见部分而不是商品投射到地面的完整矩形。比如一个被前面商品挡住一半的盒子只标可见区域如果完全看不见就不标。这样训练出来的模型学的是当前视角下能看到什么而不是想象背后有什么能防止推理阶段产生幻觉。标注类别名建议直接使用 SKU 编码例如sku_coca_cola_500ml不要用可乐1这种显示名因为后续库存管理逻辑会直接匹配这个编码。标注完以后还需要做一遍质量抽检。我会写一个脚本统计每个类别的标注框宽度、高度分布如果发现某个类别的框宽度中位数只有十几个像素这个类别大概率属于小目标需要在后文的小目标优化阶段特别处理。以下是检查标注分布的一个简单脚本import os from collections import Counter # 遍历YOLO格式的txt标注文件 label_dir labels/train size_stats Counter() box_count Counter() for fname in os.listdir(label_dir): with open(os.path.join(label_dir, fname)) as f: for line in f: parts line.strip().split() if len(parts) ! 5: continue cls int(parts[0]) w float(parts[3]) # 归一化宽度 h float(parts[4]) # 归一化高度 box_count[cls] 1 size_stats[cls] w # 累计宽度粗略看小目标比例 for cls, cnt in box_count.items(): avg_w size_stats[cls] / cnt print(fClass {cls}: boxes{cnt}, avg_norm_width{avg_w:.4f})这段代码的作用是读取 YOLO 格式的标签统计每个类别的标注框数量和平均归一化宽度。如果某个类别的平均归一化宽度小于 0.05意味着在 1920x1080 的画面里宽度不到 96 像素基本可以归入小目标范畴后续要考虑切图或调整增强策略。参数说明labels/train是你的训练集标注目录YOLO 标签每行格式是class x_center y_center width height其中位置和宽高都是相对图片尺寸的归一化值。2.3 模型选型与权重文件为什么从YOLOv11开始以及怎么下载预训练权重目标检测模型现在选择非常多RT-DETR、YOLOv8、YOLOv11、YOLOv12 都在迭代。我在零售场景里优先选 YOLOv11 而不是更早的版本原因有两个一是 Ultralytics 对 YOLOv11 的工程化支持非常完善从训练到 ONNX 导出再到 TensorRT 部署命令行和 Python API 都有文档二是 YOLOv11 在同样精度下推理速度比 v8 更快对门店里几十路摄像头的场景来说单路节省几个毫秒累积起来都是电费和时间。但要注意YOLOv11 不是一个装上就懂货架的模型。它自带的是 COCO 预训练权重能认出 80 类日常物体但里面没有可口可乐细长罐或某品牌薯片这种细粒度类别。所以你需要下载 COCO 预训练权重作为起点在自己的货架数据上微调。下载方式有两种一种是在 Ultralytics 代码里直接指定模型名例如yolov11n.pt、yolov11s.pt、yolov11m.pt第一次运行时会自动从 GitHub 下载另一种是从官方发布页手动下载到本地。我建议用yolov11s.pt作为起点n 太小对包装细节的拟合能力不足m 和 l 在训练阶段吃显存先跑通流程再用更大的模型。权重文件下载后的存放位置也有讲究最好固定在一个目录比如weights/并在训练脚本里用绝对路径引用。因为 Ultralytics 在训练时会自动把权重缓存到当前目录如果你在不同目录下运行了多次会残留多个.pt文件后期排查版本时容易混乱。实际项目中我习惯先把yolov11s.pt复制到项目weights/下再开始写训练脚本。下面会展示完整的环境配置和训练流程。3. 从环境配置到首个训练Ultralytics版YOLOv11跑通货架模型3.1 适合0基础的YOLOv11环境配置Python、CUDA、ultralytics安装网上搜YOLOv11环境配置会看到大量适合0基础纯小白的超详细教程但很多是从安装 Anaconda 开始一步步截图信息密度太低。我这里给一个更简洁、也更容易复现的步骤。前提是你有一台 Windows 或 Linux 电脑建议显存 6GB 以上没有 GPU 也能训练但速度会慢到让你失去耐心。首先是创建 Python 环境。我习惯用 conda但 venv 也完全可以。YOLOv11 依赖 Python 3.8 以上建议 3.10 或 3.11因为很多第三方库对 3.12 的兼容性还在补。创建的 Python 环境要与显卡驱动匹配的 CUDA 版本匹配但 Ultralytics 的 pip 包会自动安装一个可用的 torch 版本。最稳妥的做法是先安装 CUDA 11.8 或 12.1 对应的 PyTorch再安装 ultralytics。以下命令在 Ubuntu 20.04 上验证过# 创建环境并激活 conda create -n yolov11 python3.10 -y conda activate yolov11 # 安装 PyTorch根据你的 CUDA 版本选择命令这里是 CUDA 11.8 pip install torch2.1.0 torchvision0.16.0 --index-url https://download.pytorch.org/whl/cu118 # 安装 ultralytics pip install ultralytics这段命令的核心是两步先把 PyTorch 装对再装 ultralytics。--index-url参数指定 PyTorch 官方预编译包避免 pip 默认装到 CPU 版本。安装完成后用python -c import torch; print(torch.cuda.is_available())验证 CUDA 是否可用输出True说明 GPU 版本装好了。如果输出False先检查显卡驱动nvidia-smi再看 PyTorch 的 CUDA 版本是否匹配。这里最常见的翻车是电脑装的是 CUDA 12.2但 pip 装了 cu118 的包其实这通常也能用因为 PyTorch 会捆绑自己的 CUDA 运行时只要驱动足够新就行。3.2 把你的货架数据转成YOLO格式并用命令行开始训练标注工具直接输出 YOLO 格式还好如果你从 LabelImg 导出了 VOC XML 或 COCO JSON需要先转换。Ultralytics 支持读取 COCO 格式的数据集目录但更简单的做法是转成 YOLO 的目录结构。标准结构如下dataset/ images/ train/ 20240101_shop001_01.jpg val/ 20240101_shop002_01.jpg labels/ train/ 20240101_shop001_01.txt val/ 20240101_shop002_01.txt data.yamldata.yaml是数据集描述文件内容很简单path: /path/to/dataset # 数据集根目录 train: images/train # 训练图片目录 val: images/val # 验证图片目录 nc: 20 # 类别数量SKU数 names: 0: coca_cola_500ml 1: pepsi_cola_500ml # 按顺序列出所有SKUnc必须和names里的数量一致否则训练时类别索引会越界。转换过程中要注意类别 ID 的顺序YOLO 标签里的数字类 ID 是依据data.yaml里names的顺序而不是你的文件夹顺序。我见过一个项目标注时用的 ID 和 names 表错位导致模型训练时认识的类别和标签不一致准确率却还很高——因为它把两种长得很像的商品当成了一类这类错误最危险。转换完成后就可以用最简命令训练了yolo train datadataset/data.yaml modelweights/yolov11s.pt epochs100 imgsz640 batch16 lr00.01yolo train是 Ultralytics 的命令行入口。data指定数据集配置model指定起始权重epochs100是训练轮数imgsz640是输入图片尺寸batch16是批大小lr00.01是初始学习率。第一次跑的时候Ultralytics 会读取模型结构并加载预训练权重同时打印网络结构摘要。如果你的显存是 8GBbatch16 配 imgsz640 可能刚好但若训练中途报CUDA out of memory就调到 batch8 或 imgsz512不要硬扛。3.3 训练参数怎么设imgsz、batch、epochs与数据增强的取舍训练参数不是越猛越好。很多人在一张 1080p 的货架照片上有 200 个小商品直接 imgsz640 训练结果小目标被缩得太小几乎看不见。常见做法是把 imgsz 提高到 960 或 1280但显存占用按平方增长batch 必须降低。我一般会先看标注框的平均大小如果大多数框的宽高都在原图的 5% 以下imgsz 至少要 960否则后面再怎么调模型都没用。这里有个血泪经验不要因为 GPU 显存大就无脑开 imgsz1280大图训练会让模型过度关注纹理反而在真实环境中因为摄像头分辨率不足而翻车。epochs 也不是越大越好。100 epoch 对货架这种中大规模数据集足够如果数据量小几百个 epoch 很容易过拟合验证集 mAP 开始下降后就停。Ultralytics 默认开了早停patience50不用关。学习率用默认的 0.01 配合余弦退火即可如果 loss 震荡降低到 0.005。重点要动的是数据增强参数。Ultralytics 默认开启 mosaic、fliplr 等但货架图片有强方向性水平翻转要谨慎——价签文字翻转后会变得不可读模型可能学到错误线索。我在训练中会关闭垂直翻转并降低透视增强权重让模型专注于商品本身而不是过期促销牌。训练完成后模型权重保存在runs/detect/train/weights/best.pt。注意是best.pt而不是last.ptlast.pt只是最后一个 epoch 的权重通常不如 best 准。接下来我们进入推理和小目标优化阶段。4. 让模型看清小目标YOLOv11的小目标优化与推理结果保存4.1 货架商品为什么是小目标重灾区感受野与特征图的关系YOLOv11 沿用了 FPAN 多尺度特征融合结构输入图片缩放到imgsz后经过若干次下采样会有三个尺度的输出头分别负责大、中、小目标。但很多时候货架商品几十像素宽而小目标的输出头对应的是 80x80 的特征图每个网格的原始感受野可能已经覆盖了周围好几个商品这让小目标区分度很低。我在实际测试中发现依云矿泉水瓶如果画面里只有半个瓶身模型很容易把它漏检或者和旁边的苏打水混检。解决思路有三个层面按性价比排序第一提高输入分辨率让目标占更多像素第二在推理时做切片TTA把大图切块分别检测第三从网络结构上增强小目标分支比如给 YOLOv11 加一个更高分辨率的检测头或者引入注意力模块。前两个是工程手段立竿见影最后一个是研究手段网上常提到的YOLOv11 小目标优化里HCANet 这类注意力网络就是尝试用注意力引导模型关注小而重要的区域。但在项目初期我强烈建议先做前两件事用便宜的手段换精度再决定是否动模型结构。4.2 三个立竿见影的小目标优化切片推理、多尺度训练和注意力模块切片推理是解决小目标最直接的方法。把一张 1920x1080 的货架图分割成 3x3 的九个 640x640 的小块分别送入模型然后把所有检测框映射回原图坐标。这样每个商品在原图中的尺寸不变但检测时它占输入图像的比例变大了。代价是推理时间约为原来的 9 倍所以只适合门店每日低频盘点不适合实时流。Ultralytics 的predict方法不直接支持自动切片但你可以用 Python 脚本实现。多尺度训练也值得开Ultralytics 默认会在训练时随机缩放 0.5~1.5 倍但只限于同一个 batch 内的统一缩放。如果想针对小目标更激进可以把scale设为 0.3~0.9让模型见过更多被缩得很小的实例增强小目标鲁棒性。不过要配合足够大的imgsz否则小目标本来就只有几个像素再缩小就真的消失了。还有一个常见做法是修改yolov11s.yaml中的anchors。YOLOv11 默认锚框是根据 COCO 统计的先验你用货架数据训练时可以先用 k-means 重新聚类你的标注框得到更贴合商品的锚框尺寸替换配置文件中的值。不过我在实践中发现YOLOv11 的 anchor-free 分支对这种修改并不敏感改锚框收益不如提高分辨率来得高。所以我的建议是预算精力集中在输入分辨率和切片推理锚框调参可以作为最后的手段。给出一个切片推理并保存结果的 Python 脚本这是实际项目里经常要用到的工具import cv2 import torch from ultralytics import YOLO import numpy as np model YOLO(runs/detect/train/weights/best.pt) def slice_inference(image_path, save_path, slice_size640, overlap0.2): img cv2.imread(image_path) h, w img.shape[:2] results_all [] step int(slice_size * (1 - overlap)) # 生成切片坐标按步长滑动 for y in range(0, h, step): for x in range(0, w, step): x1 min(x slice_size, w) y1 min(y slice_size, h) # 保证切片尺寸不小于模型要求 crop img[y:y1, x:x1] if crop.shape[0] 64 or crop.shape[1] 64: continue res model.predict(crop, imgsz640, conf0.25, verboseFalse)[0] for box in res.boxes: cx, cy, bw, bh box.xywh[0].tolist() orig_x x cx - bw / 2 orig_y y cy - bh / 2 results_all.append([ int(box.cls[0]), float(box.conf[0]), orig_x, orig_y, orig_x bw, orig_y bh ]) # 在原图上绘制所有切片检测结果 for cls, conf, x1, y1, x2, y2 in results_all: cv2.rectangle(img, (int(x1), int(y1)), (int(x2), int(y2)), (0, 255, 0), 2) label f{model.names[cls]} {conf:.2f} cv2.putText(img, label, (int(x1), int(y1)-5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0,255,0), 1) cv2.imwrite(save_path, img) return results_all这段代码的关键在坐标还原每个切片在网络内部被缩放到了 640 大小但输出框的xywh是相对于切片的归一化坐标必须乘上切片实际尺寸再加上切片左上角在原图中的偏移量。step是滑动步长overlap0.2让相邻切片有 20% 的重叠避免商品刚好被切缝切成两半。重叠区域里的同一商品可能被重复检测所以后面还需要去重这个我们放到库存管理章节讲。脚本中model.predict的conf0.25是置信度阈值太低会出很多误检太高会漏检小目标一般 0.25~0.35 是个安全区间。4.3 预测之后怎么保存结果两种常用的保存方式与可视化YOLOv11 保存推理结果很灵活。最简单的是用model.predict时指定saveTrueUltralytics 会把带框的图片保存到runs/detect/predict下。但这在批量处理门店图片时不够用因为你要的不是可视化图片而是结构化结果。第二种是直接读取检测结果并序列化比如存成 JSON 或写入数据库。以下代码展示如何把一张图的检测结果保存为 JSONfrom ultralytics import YOLO import json model YOLO(best.pt) results model.predict(shop_01.jpg, imgsz960, conf0.3, verboseFalse)[0] output { image_file: shop_01.jpg, detections: [] } for box in results.boxes: cls_id int(box.cls[0]) conf float(box.conf[0]) x1, y1, x2, y2 [float(v) for v in box.xyxy[0].tolist()] output[detections].append({ sku: model.names[cls_id], confidence: conf, bbox: [x1, y1, x2, y2] }) with open(shop_01_det.json, w, encodingutf-8) as f: json.dump(output, f, ensure_asciiFalse, indent2)这段代码的results.boxes.xyxy返回的是绝对像素坐标直接可存。注意model.names是从训练配置继承的类别名如果你做过类别映射确保推理时加载的权重和names一致。另外results.boxes是按置信度降序排列的但不同 SKU 之间没有排序后续去重和计数时注意不要假设顺序。5. 库存动态管理落地的避坑与常见问题排查从识别到计数5.1 现象模型在测试集上很好一上货架就乱——光照和遮挡排查现象训练集 mAP 0.85验证集 0.80看起来不错但拿到门店现场测试模型频繁漏检深色包装商品比如黑咖啡、深色酱油瓶有时候把阴影里的饮料箱当成空位。原因货架场景的光照变化远大于你的离线数据集。门店的灯管色温、空调出风口阴影、商品包装的强反光都会改变颜色分布测试集如果都是晴天拍摄模型会把晴朗当成隐性特征。深色商品在暗处对比度低特征被背景吞没。解决在采集阶段刻意覆盖不同光照条件特别是阴天、夜晚室内灯光、阳光直射货架的场景。如果无法重新采集就用数据增强模拟对训练集随机调整亮度、对比度、饱和度让模型对光照不敏感。Ultralytics 的训练配置里有hsv_h、hsv_s、hsv_v这几个增强参数默认值已经开启但强度不够我可以将hsv_v从默认 0.4 调到 0.6hsv_s从 0.7 调到 0.8。另外在推理侧使用灰度均衡或 CLAHE 预处理能缓解局部阴影。我的一次真实经历是把曝光不足的测试图强行提亮后漏检率从 40% 降到 12%。5.2 现象类别总是认错——相似商品和标签噪声的处理现象同品牌的乌龙茶和绿茶包装几乎相同只是标签颜色不同模型经常把两款口味弄混导致库存统计里某个 SKU 的数量虚高另一个虚低。原因模型在低分辨率下只能看到颜色和形状如果两种商品包装在同一货架的反射条件下颜色相近就会混淆。还有一个隐蔽原因是标签错误标注人员在框选时把相邻的绿茶框给了乌龙茶这等于喂给模型错误答案。数据噪声对细分类的伤害比想象中大有时候 5% 的错标就能让准确率掉 10 个点。解决第一对容易混淆的 SKU采用差量标注策略——人工检查每张图中这些易混品类的标签确保边界和类别都正确。第二在训练时给这些少数类提高权重Ultralytics 没有直接提供类别权重但你可以用class_weight搭配loss_scale实现或者简单地对易混类别的图片做更多复制。第三提高 imgsz 到 1280 让模型看到足够大的标签区域。如果还是分不清就该考虑引入 OCR 识别包装上的文字辅助分类而不是只靠视觉特征。5.3 现象库存计数对不上——重复检测与漏检的解决现象一张货架图里明明有 12 瓶某饮料算法数出来 14 个因为相邻两瓶被识别成同一个或者同一瓶在切片重叠区域被检测两次另外一排深处的小瓶装被前面的大罐子挡住模型看不到漏掉 2 个。原因重复检测源于切片重叠和 NMS 参数过松漏检源于严重遮挡和小目标模型只能看到可见部分如果一个商品被遮挡了 70%它确实不应该被算作有货因为从视觉上看它已经不可售。解决重复检测的解法是用非极大值抑制和时序去重。在切片场景里我写的slice_inference会返回多个重复框可以先用全局 NMS 合并。Ultralytics 的model.predict内部已经做了全局 NMS但对跨切片的重复框无效因为它们是分开推理的。所以你需要自己实现一个 IoU 去重把同一类别、IoU 大于 0.4 的框合并成一个置信度取最高。漏检的问题要结合库存语义如果商品被遮挡但货架层板反射或摆放规律暗示后面还有应该通过排面计数补齐。比如按行检测把同一行的最小、最大 x 坐标与标准商品宽度相除。这些规则要由后端库存系统来做模型只负责输出可见框。我的经验是不要试图让模型数出绝对准确的数量而是让它输出当前可见排面数然后结合每个 SKU 的单排最大容量推算缺货深度。5.4 现象训练慢且显存爆——硬件与参数的边界现象用 16GB 显存训练 imgsz960batch16一上去就CUDA out of memory降 batch 后训练一个 epoch 需要 40 分钟完全无法迭代。原因显存占用主要由输入特征图大小和 batch 大小决定。imgsz960 时特征图是 640 的 2.25 倍显存也接近 2.25 倍加上梯度、优化器状态16GB 很可能不够。解决优先降 batch因为 batch 对显存的影响是线性的而 imgsz 是二次的。同时打开 Ultralytics 的ampTrue混合精度训练显存能省一半。如果显存还是不够就把imgsz降回 640并用切片推理处理小目标。另外可以考虑冻结前 10 层做迁移学习能减少显存和训练时间。Ultralytics 的freeze参数可以指定冻结层数例如freeze10会让前 10 层参数不更新。我通常在数据量小于 2000 张时冻结 10 层等到 loss 不再下降再解冻微调。这里有个玄学冻结过多时模型可能学不到货架特征所以要在训练曲线上观察第一次下降后是否停住了停住就解冻。6. 让库存动态管理真正跑起来时序去重与补货预警的落地技巧6.1 用视频流和帧间匹配实现动态库存一个轻量跟踪方案静态图识别只能得到某一时刻的库存快照。要做动态管理还需要连续帧之间的去重和跟踪。常见做法是给检测到的每个目标分配一个 ID然后用 IoU 与上一帧目标匹配。当摄像头固定时货架上的商品位置基本不变只是偶尔被顾客碰动所以简单的位置匹配就足够。我会为每个跟踪目标记录一个最后一次被看到的时间一旦连续 N 帧没有匹配就认为该商品被移动或售出了库存量减一。这里要注意不要用过于复杂的跟踪器货架商品不会高速运动你用 ByteTrack 反而是杀鸡用牛刀。class TrackState: def __init__(self): self.tracks {} # track_id - (bbox, last_seen_frame) def update_tracks(detections, frame_id, iou_thr0.4): # detections是当前帧的检测框列表每个框为 [sku, x1, y1, x2, y2] # 对每个框找上一帧IoU最大的track进行匹配 for det in detections: best_tid None best_iou iou_thr for tid, (old_bbox, last_frame) in self.tracks.items(): iou compute_iou(det_bbox(det), old_bbox) if iou best_iou: best_iou iou best_tid tid if best_tid is not None: self.tracks[best_tid] (det_bbox(det), frame_id) else: new_id max(self.tracks.keys(), default0) 1 self.tracks[new_id] (det_bbox(det), frame_id)这个轻量跟踪不依赖深度学习计算量可以忽略。compute_iou就是标准交并比计算。关键参数iou_thr设为 0.4太高会导致同一目标轻微移动就产生新 ID太低会让不同商品匹配到一起。如果你发现库存抖得厉害检查这个阈值是不是太严。6.2 从库存计数到补货预警规则怎么写才不误报最后一步是把识别和跟踪结果转成业务动作。我的做法是每轮扫描结束统计每个 SKU 的可见排面数再与安全库存阈值比较。如果某 SKU 数量低于阈值且持续 10 分钟没有恢复就生成补货任务。这个持续 10 分钟非常重要因为顾客临时拿起又放回、补货员正在上架都会导致检测值瞬时波动。用时间窗口过滤可以避免系统每天产生几十条假警报。还有一个易踩的坑不要把同一个商品在摄像头视野内停留的帧数直接累加为库存数。货架商品只要没有移动它就会持续出现在每一帧里正确的做法是记录当前实时的唯一目标数而不是累计检测次数。我见过一个团队把检测次数当成库存结果一天下来同一瓶可乐被计数几千次。所以在跟踪方案里库存量等于self.tracks中在最近 N 帧内活跃的 track 数量而不是历史累计值。总结一下我自己的教训做货架库存管理最大的风险不是模型精度而是业务流程对视觉不确定性的容忍度。哪怕模型做到 99% 准确那 1% 的错误在成千上万个 SKU 上也是不可接受的。所以一定要给后端留一个人工复核和规则纠正的入口让模型和人类各司其职。希望这些实操细节能帮你把 YOLOv11 真正落到货架上如果你从环境配置开始走一遍至少能避开我踩过的那几个大坑。希望帮到你。本文还有配套的精品资源点击获取