YOLOv11零售货架识别:小目标优化与端侧部署实践
发布时间:2026/9/29 4:59:14来源:尧图网络
简介一份面向目标检测学习者的YOLOv11零售场景实战文档聚焦货架商品识别与库存动态管理两大核心任务系统讲解从需求分析、算法原理到系统搭建与实验评估的完整链路。文档基于单阶段检测算法的高效优势针对零售行业人工盘点效率低、库存数据不准、缺货与积压并存等实际痛点给出可落地的技术方案涵盖YOLO系列算法演进、网络结构与损失函数、数据采集与标注、模型训练与部署、库存预警与补货策略等关键环节并包含实验结果对比与系统整体性能分析适合具备一定深度学习基础、希望将YOLOv11应用于实际业务的开发者参考。资源包为单个PDF文件大小1.89MB共35页支持目录章节跳转及阅读器大纲快速定位结构清晰、图文完整。目前已有77人浏览学习内容编排贴近真实项目流程读者可直接复现系统搭建思路节省自行摸索的时间。1. 零售货架识别为什么 YOLOv11 值得拿来重新做一遍把 YOLOv11 用到零售货架商品识别上多数团队的第一版方案是拿通用预训练模型直接扫货架照片然后数框。实际跑下来会撞上两个问题货架上的商品又密又小几十个 SKU 挤在一层隔板里通用模型的漏检率居高不下而且“识别”只是第一步库存动态管理要的是缺货率、排面数这类能对账的业务数字不是一串坐标框。这篇笔记按一条能落地的路径来写货架数据怎么标注成 YOLOv11 能学透的格式训练参数怎么为小目标优化模型怎么在 Jetson Nano 这类端侧设备上完成部署最后又如何把检测框换算成库存系统敢直接用的数字。适合正在做门店巡检、无人货柜或自动盘点的工程师参考。2. 先定任务边界货架数据怎么标、库存指标怎么算2.1 SKU 粒度标注货架识别和通用检测的第一个分叉点货架识别项目首先要回答一个业务问题识别一个“商品”还是识别一个“SKU”这是它跟通用目标检测最大的区别。通用检测里“一瓶可乐”是一个目标但在零售盘点里“500ml 塑料瓶可乐”和“330ml 罐装可乐”是两个 SKU同框出现时模型必须区分开。凡是打算把识别结果接进库存系统的标注就要做到 SKU 粒度否则后续所有统计都是错的。做法上我一般先做 SKU 清单冻结。把一张货架图上出现的所有商品编号列成表标注类别名直接用 SKU 编号而不是中文名或商品俗名。这样做有三个好处类别数量稳定、标签文件干净、后续和 ERP 系统的 SKU 主数据能直接通过编号 JOIN。如果是新开门店SKU 清单还在变动就先跳过饮料、零食这类高频换品区等清单冻结后再标避免返工。标注尺度上定两条硬规则。第一一个商品不论被遮住多少只要它的可辨识中心区域瓶标、包装正面露出来就按一个目标标注中心区域被完全盖掉的不标。这样训练出来的模型不会在严重遮挡时输出一堆半框。第二同一排面相邻的同 SKU 商品如果两个外接框之间有肉眼可见的间隙拆成两个独立框完全紧贴的宁可合并成一个框。这个选择直接影响后面排面数计算的准确性值得在标注规范里写清楚。2.2 从检测框到业务指标排面数、缺货率、SKU 分布货架检测模型的直接输出是坐标框和置信度库存管理系统要的不是这些。实际项目里我用检测结果换算三个核心指标排面数、缺货率、SKU 位置分布。排面数指某层货架上某个 SKU 占几个商品位。人工盘点时数排面是核心动作模型输出后按货架行分组同一行内同 SKU 的框数就是排面数。缺货率的计算稍复杂一些同一行内两个相邻框的水平间隙超过一个排面宽度时判定为一个空排面空排面数除以该行总排面数就是缺货率。SKU 分布则用来做动销判断同一 SKU 的排面数在两次巡检之间明显减少多半意味着售罄或者下架可以自动推送补货任务。这些指标有一个共同的前置条件知道哪几个检测框属于同一层货架。常见两种做法一是人工在画面里标行分隔线ROI把每层隔板的高低位置存成 JSON二是用检测框的纵向中心点做聚类自动分行。固定点位摄像头我建议用人工 ROI一次标定长期有效移动巡检设备上用聚类更省事代价是偶尔分行错误需要人工复核。业务指标检测输出的换算方式数据依赖排面数同一货架行内同一 SKU 的框数行 ROI、SKU 类别缺货率行内相邻框水平间隙大于阈值的空排面数 / 总排面数行 ROI、间隙阈值SKU 分布全图按 SKU 类别汇总框数及位置SKU 类别2.3 为什么选 YOLOv11结构和算力上的取舍先回答一个常被问到的问题货架场景为什么值得升到 YOLOv11。从工程角度看YOLOv11 延续了 Ultralytics 的生态一个包解决训练、导出、推理全套流程团队成员上手成本低。结构上它去掉了 anchor用 C3k2 模块替换部分 C3显存占用更低推理速度在同类模型里不落下风这对端侧部署非常关键。货架场景目标密、尺寸小模型需要的是小目标召回率上的表现而不是极端精度YOLOv11 的参数量优势刚好落在实用区间。算力选型要跟巡检方式绑定。固定点位门店天花板摄像头、通道尽头工位可以用带 GPU 的工控机愿意牺牲一点功耗换取大模型精度如果是店员推着巡检车或者做轨道机器人端侧设备功耗受限典型的是 Jetson Nano 这类板子。选型时先算一笔账单张货架图的像素宽度、每张图平均目标数量、要求单图推理时延再反推用哪个尺寸的权重。实际经验是货架巡检不需要视频流级别的实时推理每隔几十秒取一帧完全够用这个节奏下 Jetson Nano 的算力是够的。项目启动时先把算力预算定下来再回头决定训练时的 imgsz 和模型尺寸能避免后续部署阶段模型切不动、反复重训的尴尬。3. 用 YOLOv11 训练货架识别模型参数设置与小目标优化3.1 环境配置与最小训练命令环境配置本身不难货架项目里真正的麻烦是训练机上同时装了多个深度学习框架torch 版本冲突是常见翻车点。我的建议是新建独立 conda 环境Python 用 3.9 或 3.10先装 torch再装 ultralytics。conda create -n yolo11 python3.9 conda activate yolo11 pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 pip install ultralytics第一行创建独立环境第二行激活。第三行装 torch 时显式指定 CUDA 11.8 的预编译包避免 ultralytics 依赖自动拉取最新版 torch 和训练机 CUDA 版本不匹配。如果训练机的 CUDA 是 12.x把 cu118 改成 cu121。装完先跑一句python -c import torch; print(torch.cuda.is_available())确认 GPU 可见再继续不要跳过这一步。数据格式按 YOLOv11 的要求组织一份 data.yaml、一个 images 目录、一个 labels 目录。一个容易踩的坑是标注工具导出的标签文件里会有空行或空目标训练时报错标注完先用脚本过滤一遍。yolo detect train dataretail_shelf.yaml modelyolo11n.pt epochs100 imgsz640 batch16这是最小训练命令。modelyolo11n.pt是官方预训练权重第一次跑通用流程用 nano 级就行正式训练再按显存升级到 s 或 m 级。retail_shelf.yaml里的 path 指向数据集根目录train 指向训练图片目录names 按 SKU 编号顺序列出。这套命令跑通之后再开始调参。3.2 三个必调参数imgsz、batch、epochs货架图片和自然场景图最大的差异是信息密度。一张 2048x1536 的门店照片里一个 SKU 可能只有 40x60 像素imgsz640 会把商品缩到大约 15x25 像素基本糊成一团。所以货架项目我一般把 imgsz 提到 1280 或 1536然后看显存决定 batch 能撑多大。这不是让模型变强而是让模型在训练时能看见原来的目标尺寸小目标检测的第一原则就是别让目标在输入图上缩水。batch 的设定没有玄学就是看显存。8GB 显存跑 imgsz1280batch 从 4 开始试OOM 就降到 2显存占用一直在 90% 以上也往下降一档。另一个有用的小开关是cacheTrue把图片缓存到内存省去训练中反复读盘的时间配合 batch 调参体验提升明显。epochs 方面货架数据量通常不大几千张图是常见规模。我一般先用 100 轮跑基线观察 val_box_loss 在第几轮开始不再下降然后按这个拐点乘 1.5 做正式轮数。有预训练权重在手时50 到 100 轮之间基本都能收敛关键是开着早停patience20训练曲线已经平了还空转是常见浪费。3.3 小目标优化SAHI 切片推理与注意力机制怎么选当 imgsz 拉满后 mAP 仍然上不去下一个有效手段是切片推理。开源库 SAHI 做的就是这件事把大图切成小 patch 分别推理再合并结果。这对货架图特别合适单层隔板上的商品可能只有几十像素高但整图很大直接缩小全图会让小目标不可识别。流程是训练时仍用 1280 输入推理时把原始大图按 640 切片加上重叠区推理后把边界框映射回原图坐标系。from sahi import AutoDetectionModel from sahi.predict import get_sliced_prediction detection_model AutoDetectionModel.from_pretrained( model_typeultralytics, model_pathbest.pt, confidence_threshold0.25, image_size1280, devicecuda:0, ) result get_sliced_prediction( shelf_01.jpg, detection_model, slice_height640, slice_width640, overlap_height_ratio0.2, overlap_width_ratio0.2, postprocess_typeNMM, postprocess_match_threshold0.5, ) result.export_visuals(export_diroutput/)这是典型的 SAHI 切片预测。slice_height 和 slice_width 设成与训练输入一致保证每个 patch 里目标的相对尺寸和训练时接近。overlap 比例 0.2 是为了防止目标恰好落在切片边缘被切掉一半。postprocess_type用 NMM 而不是 NMS因为同一个目标在不同 patch 里重复出现时NMM 对这类重叠框的抑制更稳定。跑完后 result 里的 object_prediction_list 包含每个目标的框、类别和置信度可以直接存 JSON。注意力机制是另一个改进方向。YOLOv11 自带 C2PSA 注意力对长条形货架行的语义理解有帮助想更激进一点HCA Net 这类外挂注意力网络也可以集成但训练复杂度和显存占用都会上升。我的经验是先跑通 SAHI确认切片增益再决定要不要动模型结构注意力改进的收益有时不如多标两百张难例图片来得实在。3.4 训练验证置信度分布和排查顺序货架模型的验证不能只看 mAP50。货架图片里不同 SKU 的目标数量极不均衡mAP 被大类平均之后容易掩盖小类的问题。训练完我建议跑一次验证集预测输出每张图的预测结果按置信度区间画出目标数量分布如果大量真实目标落在 0.2 置信度以下说明模型对这类目标的特征没学好优先去看是哪一类如果分布在 0.5 以上问题在业务阈值设定而不是模型。排查顺序固定成一条线先看数据标签有没有错最常见的是类别串号再看 imgsz 是不是把目标缩没了然后看训练曲线的收敛情况最后才调 NMS 阈值和置信度阈值。这个顺序能省掉大量无效调参。4. 在 Jetson Nano 上部署 YOLOv11导出、加速与推理的详细步骤4.1 从 best.pt 到 TensorRT engine两次转换与版本匹配训练机的 PyTorch 权重不能直接拿到 Jetson Nano 上跑。标准做法是先在训练机导出 ONNX再把 ONNX 拷到 Jetson 上用 TensorRT 转成 engine 文件。Ultralytics 的 export 命令把第一步做得很简单yolo export modelbest.pt formatonnx opset12 dynamicFalseopset12 是因为 Jetson 上的 TensorRT 版本通常不再更新到更高 opset 的解析能力保守的 12 兼容性最好。dynamicFalse固定输入尺寸推理时省去动态 shape 的开销。导出成功后把 best.onnx 拷贝到 Jetson Nano用 TensorRT 自带的 trtexec 转 engine/usr/src/tensorrt/bin/trtexec \ --onnxbest.onnx \ --saveEnginebest.engine \ --fp16--fp16在 Jetson Nano 上能带来明显的速度提升货架识别对精度不是极端敏感可以接受。如果你的板子是 NX 或 Orin也可以试 INT8 量化但需要准备校准数据集工作量是另一个量级不建议第一期就上。转完建议保存一份 engine 文件和对应的 ONNX、训练配置参数方便追溯。4.2 端侧推理脚本加载 engine、保存结果、性能压测engine 文件准备好之后直接用 ultralytics 的 Python API 加载。注意 Jetson 上也要装 ultralytics 包PyTorch 版本要和 JetPack 自带的 CUDA 匹配。下面的脚本覆盖加载、推理、保存结果三个动作from ultralytics import YOLO model YOLO(best.engine, taskdetect) results model.predict( sourceshelf_01.jpg, conf0.25, iou0.45, imgsz1280, saveTrue, projectruns/detect, nameshelf_result, save_txtTrue, save_confTrue, ) for r in results: for box in r.boxes: cls_id int(box.cls[0]) conf float(box.conf[0]) xyxy [round(v, 2) for v in box.xyxy[0].tolist()] print(fSKU {cls_id}, conf {conf:.2f}, box {xyxy})model.engine是 TensorRT 引擎推理在 GPU 上执行。saveTrue把标注后的图片存到runs/detect/shelf_result/save_txtTrue生成与图片同名的 txt 标签文件格式是class x_center y_center width heightsave_confTrue让 txt 里多一列置信度。这个组合是货架巡检里最常用的结果保存方式后续脚本直接读 txt 做业务换算。提示Jetson Nano 上首次加载 engine 会慢因为要做显存分配和上下文初始化这不是故障。压测性能时用 100 张图的目录做循环推理统计平均每张耗时不要拿单张图刚加载完时的数据当基准。4.3 定时巡检与结果落盘货架场景的合理推理节奏货架管理不需要视频流级别的帧率。常见做法是做成定时任务每隔半小时或一小时抓一帧送进模型推理结果写 JSON 或 txt传到门店本地服务器或云端。这个节奏下 Jetson Nano 的算力不但够用还留有余量。部署形态上有一个容易忽略的点相机的自动白平衡和曝光。货架灯光会在一天内变化同一位置上午和下午的照片色温不同。如果推理脚本固定从摄像头抓帧需要把相机的自动曝光关闭锁定曝光参数否则模型性能会随天气一起漂移。这个坑在下一章细说。5. 货架识别落地中的五个坑现象、原因与解决办法5.1 光照漂移导致全线漏检模型扛不住傍晚现象训练集里 mAP 到 0.9部署到门店白天效果尚可傍晚开始大量漏检补光灯亮起后误检激增。原因是训练数据大多来自办公室或白天门店采集样本里的光照分布是单峰的模型学到的是“这个亮度下的商品长什么样”而不是“商品本质长什么样”。解决分三步采集阶段刻意覆盖早晚两个时段和灯光模式各占至少 15%部署阶段关闭相机自动曝光锁定固定参数条件允许时做一次灰度增强训练的对比实验确认模型对亮度变化的依赖程度。这个坑不解决后面所有指标都是空中楼阁。5.2 邻架商品串框NMS 拦不住现象A 货架最上层的最右商品检测框跨到了旁边区域或者同一排面的商品被输出成两个上下重叠的框排面数莫名翻倍。原因是全局 NMS 只按 IoU 抑制同一个类别相邻货架之间同 SKU 的框在图像上不重叠或重叠很少不会被抑制。货架行本身的结构边界没有被模型感知。解决方式是在业务逻辑里加一道行内过滤先按检测框的 cy 坐标映射到行 ROI同一行内按 x 坐标排序对相邻框做二次判断类别相同且水平间距小于一定像素时保留置信度高的那个。这道后处理不增加训练成本却是排面数计算稳定的前提。5.3 SKU 换包装后识别率骤降类别体系设计缺陷现象某个 SKU 换了新包装识别率从 90% 掉到 40%误检集中在旧包装样本附近。原因是目标检测模型记忆的是包装外观不是商品本身。换包装等于在这个类别上失去了新样本模型只能把相似包装归给旧类。解决方式是把 SKU 主数据与模型类别解耦换包装的 SKU旧包装样本继续留在训练集里新增的包装图片单独加一类类别名按 SKU 编号加后缀区分比如 SKU1234_v2用新类别做增量训练。上线后前两周重点盯这个 SKU 的置信度分布波动大就拉出来复核。5.4 Jetson Nano 推理忽快忽慢不是模型问题现象单张图推理时间从 0.5 秒到 3 秒波动平均耗时远高于预期怀疑模型没转好。原因是 Jetson Nano 的 GPU 采用共享功耗和散热设计模块温度到 80 度以上会降频另外推理进程和其他系统进程抢内存也会造成卡顿。解决方式跑推理前用jetson_clocks锁高频推理循环里检查显存占用同时打开风扇或使用带散热底座的套件。压测性能时用连续 100 张图的循环推理平均耗时来评判不要拿单张图刚加载完时的数据当基准。5.5 推理结果目录覆盖保存路径的约定要早定现象脚本运行无报错输出目录是空的或者多次运行结果互相覆盖找不到对应货架的图片。原因是 Ultralytics 的 predict 在 project 相同且 name 相同时会自动递增目录名第一次是 shelf_result第二次是 shelf_result2脚本如果写死路径读旧目录就会读错或覆盖。解决方式每次运行传入带时间戳的 name比如shelf_result_20250115_1530并把返回结果的save_dir直接作为后续读取路径不要自己拼路径。这个习惯能省掉很多巡检脚本的数据对账时间。6. 从检测框到库存动态管理排面计算、验证节奏与数据回流6.1 把检测结果换算成排面数的脚本货架行 ROI 标好后排面数计算就是纯几何问题。下面的脚本读推理保存的 txt 结果按行 ROI 分组组内按 SKU 计数import json from pathlib import Path roi json.loads(Path(shelf_roi.json).read_text()) det_path Path(runs/detect/shelf_result_20250115_1530/labels) def row_of(cy, roi): for row_id, row in roi.items(): if row[top] cy row[bottom]: return row_id return None sku_facing {} for txt_file in det_path.glob(*.txt): for line in txt_file.read_text().strip().splitlines(): cls_id, xc, yc, w, h, conf map(float, line.split()) row_id row_of(yc, roi) if row_id is None: continue key (row_id, int(cls_id)) sku_facing[key] sku_facing.get(key, 0) 1 print(sku_facing)逻辑是遍历每个标签文件把每行按空格拆成 6 列因为推理时开了 save_conf用 y 中心点判断属于哪一行 ROI再按(行号, SKU 类别)计数。用 yc 归属而不是框 IoU 归属是因为行 ROI 是水平条带实现简单对轻微斜视角度也能容忍一定误差。6.2 先用一个货架跑通验证三天内的验收流程不建议一上来就铺全店。这个项目值不值得继续投入先用三天做一个验证选一个 SKU 种类 20 到 40 的货架固定相机位拍 10 张不同角度和时段的照片标注训练后部署到 Jetson Nano输出排面数和人工盘点表逐行对一遍统计排面数误差和缺货识别率。验证通过的标准建议定两条排面数识别准确率 90% 以上缺货位置识别准确率 80% 以上。达不到就先别扩量回到数据采集和标注环节补样本。这个验证结果也是向业务方要资源的最好材料。6.3 数据回流与模型版本管理让识别结果反过来修数据集落地运行一段时间后容易被忽略的是数据回流。推理结果里置信度低但真实存在的目标以及置信度高但实际不存在的目标都是下一轮训练的金矿。做法是给巡检脚本加一个保存困难样本的分支当一张图的置信度分布出现异常比如平均置信度低于 0.3或者某 SKU 框数骤减自动把原图存到 inbox 目录每周人工检视一次挑出有代表性的样本加入训练集。这看起来是额外工作但货架场景的 SKU 变化和灯光变化是常态没有数据回流机制的模型会在上线后一个月内悄悄退化。我自己做这类项目的一个习惯是训练集里永远保留每个 SKU 最早期的 10 张图和最近期的 10 张图防止模型被新样本带偏同时把模型版本号和训练集版本号写进每次推理结果的 JSON 文件头复盘时能知道某天的结果是用哪版模型跑出来的。货架识别这类项目的难点从来不在模型精度本身而在于让模型精度在真实门店的噪声里保持稳定。从数据标注到排面数计算再到样本回流这套流程能把 YOLOv11 的识别结果稳定地变成库存系统可用的数字。希望帮到你。本文还有配套的精品资源点击获取
网站建设高端定制企业官网