ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

智能零售柜商品检测数据集:5000张真实场景图与YOLO11训练全攻略

2026/10/5 10:35:20 拓冰建站 浏览量
智能零售柜商品检测数据集:5000张真实场景图与YOLO11训练全攻略 简介面向智能零售柜商品检测场景的目标检测数据集资源适合从事新零售视觉算法研发的工程师、学生及竞赛选手使用可作为通用零售商品检测项目的数据补充。数据集采集自真实智能零售柜监控场景涵盖罐装饮料、袋装零食等常见商品标注标签共113个商品类别采用labelimg标注质量较高并提供VOC、COCO、YOLO三种主流格式可直接投入YOLO等算法训练。资源包共1个PDF文件大小约5.77MB因数据集体量较大托管于百度网盘PDF内附数据集基本情况介绍与获取方式。附赠YOLO11一键训练脚本支持GPU、CPU及MacM芯片多平台训练方案并附博主训练结果日志供参考。目前已有413人学习适合需要快速搭建零售商品检测基线、验证多格式标签兼容性的读者参考使用。1. 智能零售柜商品检测数据集5000 张真实场景图与三格式标签的落地价值做过零售柜商品识别的人都知道最耗时的从来不是调模型而是凑数据。货柜里罐装饮料反光、袋装零食褶皱、商品堆叠遮挡这些场景用公开数据集根本覆盖不到。这份智能零售柜商品检测数据集就是冲着这个痛点来的5000 张真实智能零售柜监控场景采集的商品图片覆盖罐装饮料、袋装零食等常见品类标注了 113 个商品类别同时提供 VOC(xml)、COCO(json)、YOLO(txt) 三种标签格式还附了一套 YOLO11 一键训练脚本GPU、CPU、Mac(M 芯片) 三平台都能跑。适合正在做智能零售柜商品检测项目的从业者也适合拿它当新零售场景目标检测的补充数据。下面从数据本身、格式转换、训练脚本、踩坑排查到进阶技巧一步步拆开讲。2. 数据集结构与三种标签格式先搞清楚手里拿到的是什么2.1 113 类商品的标注体系与目录组织这份数据集的核心价值在于「真实场景 细粒度类别」。113 个商品类别不是随便凑的罐装饮料类会细分到不同品牌和规格袋装零食类也会按品类拆开。这种细粒度对零售柜场景是必要的——同一个货道里可能并排摆着外观相近但 SKU 不同的商品粗粒度类别根本区分不了。标注用的是 labelimg这是目标检测圈里最常用的标注工具之一输出的是标准 PASCAL VOC 格式的 xml。标注质量方面从项目说明看是「标注质量高」实际使用前建议自己抽检一批重点看遮挡商品和边缘商品的框是否贴合。数据集目录常见组织方式是这样的retail_cabinet_dataset/ ├── images/ # 全部图片jpg 格式 │ ├── 000001.jpg │ ├── 000002.jpg │ └── ... ├── annotations_voc/ # VOC 格式 xml │ ├── 000001.xml │ └── ... ├── annotations_coco/ # COCO 格式 json │ └── instances.json ├── annotations_yolo/ # YOLO 格式 txt │ ├── 000001.txt │ └── ... ├── classes.txt # 113 类类别名 └── train_yolo11.py # 一键训练脚本拿到手第一件事是确认三件事图片总数是不是 5000 张、类别数是不是 113、三种格式的标签能不能和图片一一对应。我一般会先跑一个统计脚本把类别分布和每类样本数拉出来看看避免后面训练时发现某些类别只有个位数样本。import os from collections import Counter # 统计 YOLO 格式标签的类别分布 label_dir retail_cabinet_dataset/annotations_yolo class_counter Counter() for txt_file in os.listdir(label_dir): if not txt_file.endswith(.txt): continue with open(os.path.join(label_dir, txt_file), r) as f: for line in f: cls_id int(line.strip().split()[0]) class_counter[cls_id] 1 # 输出类别分布按样本数排序 for cls_id, count in class_counter.most_common(): print(fclass {cls_id}: {count} instances) print(f总类别数: {len(class_counter)}) print(f总标注框数: {sum(class_counter.values())})这段脚本读的是 YOLO 格式的 txt 标签每行第一个数字是类别 id后面是归一化的中心点和宽高。跑完能直接看到 113 类里哪些是长尾类别。如果发现某些类别样本数低于 50训练时就要考虑做数据增强或者类别加权否则模型对这类商品基本学不动。2.2 VOC、COCO、YOLO 三种格式的差异与选用三种格式不是随便给的它们对应不同的训练框架和工具链。VOC 是 xml一个文件对应一张图结构清晰但读取慢COCO 是单个 json 管全部标注适合大规模数据但解析要写代码YOLO 是每张图一个 txt归一化坐标直接喂给 YOLO 系列最省事。格式文件形式坐标表示适用框架读取速度VOC每图一个 xml绝对像素 xmin/ymin/xmax/ymaxFaster R-CNN、SSD慢COCO单个 json绝对像素 [x,y,w,h]Detectron2、MMDetection中YOLO每图一个 txt归一化 cx,cy,w,hYOLO 全系列快选哪个取决于你用什么框架。如果直接跑附赠的 YOLO11 脚本用 YOLO 格式最省事不用转换。如果要用 MMDetection 或者 Detectron2COCO 格式更顺手。VOC 格式主要是兼容老项目和 labelimg 的原始输出。这里有个容易翻车的点三种格式的类别 id 映射可能不一致。VOC 和 COCO 里类别是字符串名字YOLO 里是数字 id而数字 id 和 classes.txt 的行号对应关系必须确认清楚。我见过有人直接拿 COCO 的 category_id 去对 YOLO 的 cls_id结果训练出来所有类别都错位。正确做法是以 classes.txt 为准写个脚本把三种格式的类别映射统一校验一遍。import json import xml.etree.ElementTree as ET # 读取 classes.txt 作为基准类别列表 with open(retail_cabinet_dataset/classes.txt, r) as f: classes [line.strip() for line in f if line.strip()] # 校验 COCO json 的类别名是否和 classes.txt 一致 with open(retail_cabinet_dataset/annotations_coco/instances.json, r) as f: coco json.load(f) coco_names [cat[name] for cat in coco[categories]] print(COCO 类别数:, len(coco_names)) print(classes.txt 类别数:, len(classes)) # 找出不一致的类别 mismatch set(coco_names) ^ set(classes) if mismatch: print(类别名不一致:, mismatch) else: print(类别名完全一致可以放心使用)这段脚本做的是类别名对齐检查。COCO 的 categories 字段里每个类别有 id 和 namename 必须和 classes.txt 里的行内容一致。如果发现不一致要么是标注时改了名要么是导出时顺序乱了必须手动修正后再训练否则模型学到的类别和实际商品对不上。2.3 从 VOC 转 YOLO 的完整脚本与边界处理虽然数据集已经提供了 YOLO 格式但实际项目里经常需要自己转——比如你新增了一批标注或者要调整类别顺序。VOC 转 YOLO 的逻辑不复杂但边界情况不少图片尺寸要从 xml 里读坐标要归一化还要处理宽高为 0 的异常框。import os import xml.etree.ElementTree as ET def voc_to_yolo(xml_path, classes, output_path): tree ET.parse(xml_path) root tree.getroot() # 从 xml 里读图片尺寸 size root.find(size) img_w int(size.find(width).text) img_h int(size.find(height).text) lines [] for obj in root.findall(object): cls_name obj.find(name).text if cls_name not in classes: continue # 跳过不在类别表里的标注 cls_id classes.index(cls_name) bbox obj.find(bndbox) xmin float(bbox.find(xmin).text) ymin float(bbox.find(ymin).text) xmax float(bbox.find(xmax).text) ymax float(bbox.find(ymax).text) # 边界裁剪防止坐标越界 xmin max(0, min(xmin, img_w)) xmax max(0, min(xmax, img_w)) ymin max(0, min(ymin, img_h)) ymax max(0, min(ymax, img_h)) # 跳过无效框 if xmax xmin or ymax ymin: continue # 转归一化中心点和宽高 cx (xmin xmax) / 2.0 / img_w cy (ymin ymax) / 2.0 / img_h w (xmax - xmin) / img_w h (ymax - ymin) / img_h lines.append(f{cls_id} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}) with open(output_path, w) as f: f.write(\n.join(lines)) # 批量转换 classes open(retail_cabinet_dataset/classes.txt).read().strip().split(\n) voc_dir retail_cabinet_dataset/annotations_voc out_dir retail_cabinet_dataset/annotations_yolo_converted os.makedirs(out_dir, exist_okTrue) for xml_file in os.listdir(voc_dir): if xml_file.endswith(.xml): voc_to_yolo( os.path.join(voc_dir, xml_file), classes, os.path.join(out_dir, xml_file.replace(.xml, .txt)) )这段脚本的关键处理有三处一是从 xml 的 size 字段读图片宽高不能硬编码二是对坐标做边界裁剪防止标注时手抖画出界三是跳过宽高为 0 的无效框这种框在 YOLO 里会导致 loss 计算异常。转换完建议随机抽几张图用可视化脚本画框确认别直接开训。3. YOLO11 一键训练脚本三平台参数怎么设、日志怎么看3.1 GPU、CPU、Mac 三平台的启动差异附赠的 YOLO11 一键训练脚本最大的价值是抹平了平台差异。Ultralytics 的 YOLO11 本身跨平台但 GPU、CPU、Mac(M 芯片) 三者的启动参数和依赖不一样新手很容易在环境上卡住。GPU 平台NVIDIA 显卡需要装 CUDA 版的 PyTorch训练时 device 设为 0 或 0,1 多卡。CPU 平台不需要 CUDA但训练慢适合小批量验证。Mac(M 芯片) 用 MPS 后端device 设为 mpsPyTorch 要装支持 MPS 的版本。# GPU 平台确认 CUDA 可用后启动 python train_yolo11.py --device 0 --batch 16 --epochs 100 # CPU 平台batch 调小避免内存爆 python train_yolo11.py --device cpu --batch 4 --epochs 50 # Mac M 芯片用 mps 后端 python train_yolo11.py --device mps --batch 8 --epochs 50参数上batch 是最需要按平台调的。GPU 显存 8G 以上可以上 16CPU 建议 4 以内Mac 统一内存 16G 的话 8 比较稳。epochs 方面113 类细粒度分类任务100 轮起步比较合理CPU 平台如果只是验证流程可以降到 50。脚本内部一般会做这几件事检查数据集路径、生成 data.yaml、加载 YOLO11 预训练权重、启动训练、保存结果到 runs 目录。data.yaml 是 YOLO 训练的核心配置格式如下path: ./retail_cabinet_dataset train: images/train val: images/val nc: 113 names: 0: class_0 1: class_1 # ... 共 113 行nc 是类别数必须和 classes.txt 行数一致。names 是类别 id 到名字的映射顺序不能乱。如果脚本是自动生成 data.yaml 的训练前一定打开看一眼确认 nc 和 names 没问题。3.2 训练日志里的关键指标与早停判断训练日志不是拿来看 loss 下降就完事的。YOLO11 的日志里重点看这几个指标box_loss、cls_loss、dfl_loss、precision、recall、mAP50、mAP50-95。box_loss 是边界框回归损失cls_loss 是分类损失dfl_loss 是分布焦点损失。三个 loss 都应该是下降趋势如果某个 loss 震荡或者反弹说明对应部分出了问题。比如 cls_loss 不降可能是类别不平衡或者标签有错box_loss 不降可能是标注框质量差。mAP50 是 IoU 阈值 0.5 时的平均精度mAP50-95 是 0.5 到 0.95 多个阈值的平均。零售柜商品检测里mAP50 能到 0.85 以上算不错mAP50-95 通常低 10 到 20 个点。如果 mAP50 高但 mAP50-95 很低说明框的位置不够准可能是标注框偏大或偏小。早停判断上YOLO11 默认 patience 是 50意思是 50 轮没有提升就停。113 类任务建议把 patience 设到 30 到 50 之间。如果日志里 mAP 连续 20 轮不涨基本可以判断模型已经收敛或者卡住了这时候要么调学习率要么检查数据。# 从 results.csv 里读训练日志画关键指标曲线 import pandas as pd import matplotlib.pyplot as plt df pd.read_csv(runs/detect/train/results.csv) df.columns df.columns.str.strip() # 列名去空格 fig, axes plt.subplots(2, 2, figsize(12, 8)) axes[0, 0].plot(df[epoch], df[train/box_loss], labelbox_loss) axes[0, 0].plot(df[epoch], df[train/cls_loss], labelcls_loss) axes[0, 0].set_title(Training Loss) axes[0, 0].legend() axes[0, 1].plot(df[epoch], df[metrics/mAP50(B)], labelmAP50) axes[0, 1].plot(df[epoch], df[metrics/mAP50-95(B)], labelmAP50-95) axes[0, 1].set_title(Validation mAP) axes[0, 1].legend() axes[1, 0].plot(df[epoch], df[metrics/precision(B)], labelprecision) axes[1, 0].plot(df[epoch], df[metrics/recall(B)], labelrecall) axes[1, 0].set_title(Precision Recall) axes[1, 0].legend() plt.tight_layout() plt.savefig(training_curves.png)这段脚本读的是 YOLO 训练自动生成的 results.csv把 loss 和 mAP 曲线画出来。看曲线比看数字直观loss 曲线应该是平滑下降如果锯齿严重说明学习率太大mAP 曲线应该是上升后趋平如果一直不涨说明模型容量不够或者数据有问题。3.3 用训练好的权重做推理与批量验证训练完不是就结束了得拿权重实际跑一遍推理看看在真实图片上的效果。YOLO11 的推理接口很简单但零售柜场景有几个要注意的点小目标多、遮挡多、同类商品密集排列。from ultralytics import YOLO # 加载训练好的权重 model YOLO(runs/detect/train/weights/best.pt) # 单张图推理 results model(test_image.jpg, conf0.25, iou0.45) # 批量推理整个目录 results model.predict( sourcetest_images/, conf0.25, iou0.45, saveTrue, save_txtTrue, projectinference_output ) # 打印每张图的检测结果 for r in results: boxes r.boxes for box in boxes: cls_id int(box.cls[0]) conf float(box.conf[0]) xyxy box.xyxy[0].tolist() print(f类别 {cls_id}, 置信度 {conf:.3f}, 框 {xyxy})conf 是置信度阈值默认 0.25零售柜场景可以调到 0.3 到 0.4减少误检。iou 是 NMS 的 IoU 阈值默认 0.45商品密集排列时可以调到 0.5 到 0.6避免相邻商品被误删。save_txt 会把检测结果存成 YOLO 格式的 txt方便后续做精度评估。批量验证时建议把推理结果和人工标注对比算一下每个类别的 precision 和 recall。113 类里肯定有些类别表现好、有些差差的类别要么补数据要么在推理时单独调阈值。4. 避坑与排查这份数据集用起来最容易翻车的五个地方4.1 类别 id 错位导致训练全乱现象训练 loss 正常下降但推理时所有类别都识别错明明是罐装饮料却识别成袋装零食。原因YOLO 格式的 cls_id 和 classes.txt 的行号没对齐。常见于自己转换格式时用了 COCO 的 category_id 而不是 classes.txt 的顺序。解决训练前跑一遍类别对齐检查确认 YOLO txt 里的 cls_id 最大值不超过 nc-1且每个 id 对应的类别名和 classes.txt 一致。写个脚本随机抽 10 张图把 YOLO 标签画出来和原图对比肉眼确认框和类别都对。4.2 图片和标签文件名不匹配现象训练时报错「No labels found」或者训练完 mAP 极低。原因图片是 jpg标签是 txt但文件名前缀不一致。比如图片叫 000001.jpg标签叫 1.txt。解决写脚本检查 images 目录和 labels 目录的文件名是否一一对应。YOLO 要求图片和标签同名只是扩展名不同。如果发现不匹配批量重命名对齐。import os img_dir retail_cabinet_dataset/images lbl_dir retail_cabinet_dataset/annotations_yolo img_names {os.path.splitext(f)[0] for f in os.listdir(img_dir) if f.endswith(.jpg)} lbl_names {os.path.splitext(f)[0] for f in os.listdir(lbl_dir) if f.endswith(.txt)} missing_lbl img_names - lbl_names missing_img lbl_names - img_names print(f有图无标签: {len(missing_lbl)} 个) print(f有标签无图: {len(missing_img)} 个)4.3 Mac M 芯片训练时 MPS 后端报错现象Mac 上跑训练脚本报错「MPS backend out of memory」或者「Placeholder storage has not been allocated」。原因MPS 后端对某些算子支持不完整或者统一内存不够。YOLO11 的部分算子在 MPS 上会回退到 CPU导致速度慢甚至报错。解决先把 batch 降到 4 或 2看能不能跑通。如果还报错设置环境变量PYTORCH_ENABLE_MPS_FALLBACK1让不支持的算子回退到 CPU。实在不行就用 CPU 训练虽然慢但稳定。4.4 小目标漏检严重现象大件商品检测正常小件商品如口香糖、小包装零食大量漏检。原因YOLO11 默认输入尺寸 640小目标在特征图上只剩几个像素特征提取不充分。零售柜场景里小目标占比不低。解决把输入尺寸调到 1280或者用 YOLO11 的 P2 小目标检测头。数据增强里开启 mosaic 和 mixup增加小目标的出现频率。推理时把 conf 阈值降到 0.15 到 0.2先把召回拉上来。4.5 训练日志里 val 和 train 指标差距大现象train loss 一直降但 val mAP 不涨甚至下降。原因过拟合。113 类细粒度分类如果某些类别样本太少模型会记住训练集但泛化不了。解决先看类别分布长尾类别做数据增强或者过采样。加 dropout 和 weight decay降低模型复杂度。如果还不行考虑用预训练权重做微调而不是从头训。YOLO11 的预训练权重在 COCO 上训过迁移到零售柜场景能省不少事。5. 进阶技巧用 113 类标签做类别合并与分层训练113 类直接训不是不行但实际零售柜项目里很多时候不需要这么细。比如你只关心「饮料」和「零食」两大类那可以把 113 类合并成 2 类训练难度大幅下降精度也会提升。合并的逻辑是建一个映射表把细类 id 映射到粗类 id然后重写 YOLO 标签。# 类别合并把 113 类映射到 5 个大类 # 假设 classes.txt 前 30 个是饮料30-80 是零食80-100 是日用品100-113 是其他 merge_map {} for i in range(113): if i 30: merge_map[i] 0 # 饮料 elif i 80: merge_map[i] 1 # 零食 elif i 100: merge_map[i] 2 # 日用品 else: merge_map[i] 3 # 其他 # 重写 YOLO 标签 import os src_dir retail_cabinet_dataset/annotations_yolo dst_dir retail_cabinet_dataset/annotations_yolo_merged os.makedirs(dst_dir, exist_okTrue) for txt_file in os.listdir(src_dir): if not txt_file.endswith(.txt): continue with open(os.path.join(src_dir, txt_file), r) as f: lines f.readlines() new_lines [] for line in lines: parts line.strip().split() old_cls int(parts[0]) new_cls merge_map.get(old_cls, 3) new_lines.append(f{new_cls} { .join(parts[1:])}) with open(os.path.join(dst_dir, txt_file), w) as f: f.write(\n.join(new_lines))合并后 nc 改成 4names 改成四个大类名重新训一遍。通常合并后 mAP 会明显提升因为类别间差异变大了模型更容易学。另一个技巧是分层训练先用合并后的粗类标签训一个基础模型再用细类标签在基础模型上微调。这样模型先学会「这是饮料还是零食」再学「是哪个品牌的饮料」收敛更快小类别表现也更好。验证方面别只看 mAP。零售柜场景最终看的是实际识别准确率建议拿一批没参与训练的实拍图人工数一下正确识别和漏检误检的数量算一个业务准确率。这个数字比 mAP 更有说服力。从那以后我每次拿到新数据集都强制先跑一遍类别分布统计和文件名对齐检查再抽 20 张图可视化确认标注质量最后才开训。这三步花不了半小时但能省掉后面几小时的无效训练。希望帮到你。本文还有配套的精品资源点击获取