ARTICLE DETAIL

资讯详情

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

罐装饮料识别实战:从COCO数据集到YOLOv8训练全流程

罐装饮料识别实战:从COCO数据集到YOLOv8训练全流程 简介罐装饮料识别数据集包含一千多张实拍图片覆盖薯片、东鹏特饮、红牛、芬达、养乐多、可乐、雪碧、王老吉、AD钙奶、金典、特仑苏、蒙牛、伊利、旺仔牛奶等常见饮品面向目标检测与识别任务适合算法初学者或开发者训练、评估饮料检测模型。标注采用COCO格式全包共1681个文件其中1676张jpg图片搭配3个json标注文件和2个txt类别文件压缩包仅45.57MB轻量易用可直接接入YOLO、MMDetection等主流检测框架。目前已有823人学习下载尤其适合零售货架识别、智能售卖机、库存盘点等落地场景。利用这批数据可完成数据预处理、模型微调与结果验证省去自行采集和标注的时间帮助使用者将精力集中于算法优化与业务落地。1. 罐装饮料识别一千多张标注图能直接喂给 YOLO 的训练集做零售场景的视觉项目最容易卡住的地方不是模型选型而是数据。要识别货架上的可乐、雪碧、红牛、东鹏特饮、王老吉这类罐装饮料网上公开数据集不是太杂就是太干净真正贴近国内商超货架实拍场景的少之又少。这份「罐装饮料识别」数据集解的就是这个渴——一千多张真实拍摄的图片标注格式是 COCO覆盖了薯片、东鹏特饮、红牛、芬达、养乐多、可乐、雪碧、王老吉、AD 钙奶、金典、特仑苏、蒙牛、伊利、旺仔牛奶十多个常见 SKU适合做目标检测模型的训练和微调。对正在做零售陈列识别、自动补货、货架巡检这类项目的人来说这份数据能省掉大量自己拍照和标注的时间。数据格式上COCO 标注意味着你可以直接用它训练 Detectron2、MMDetection或者花十分钟转成 YOLO 格式喂给 YOLOv8。下文我会把数据集结构拆开给出转换脚本、训练配置、验证指标解读以及我在实际跑这个数据时踩过的几个坑。2. 数据先摸底COCO 格式标注里有什么以及哪些 SKU 值得训2.1 这份 COCO 标注的字段构成用文本编辑器打开标注 JSON第一眼会看到典型的 COCO 三段式结构images、annotations、categories。实测这份数据集的images字段里每张图都有id、file_name、width、height没有多余的 EXIF 干扰信息。annotations里的关键是bbox和category_id两个字段前者是[x, y, width, height]绝对坐标后者对应categories里的类别索引。import json with open(annotations/instances_train.json, r, encodingutf-8) as f: coco json.load(f) print(图片数量:, len(coco[images])) print(标注数量:, len(coco[annotations])) print(类别数量:, len(coco[categories])) # 统计每个类别的实例数 from collections import Counter cat_count Counter([ann[category_id] for ann in coco[annotations]]) for cat_id, cnt in cat_count.most_common(): cat_name next(c[name] for c in coco[categories] if c[id] cat_id) print(f{cat_name}: {cnt} 个实例)这段代码先加载标注文件然后统计每个类别的实例数量。运行结果基本能看出数据分布常见饮料如可乐、雪碧的样本量较多而像 AD 钙奶、旺仔牛奶这类可能略微偏少。如果某个类别的实例数少于 50训练时就要考虑数据增强或者单独处理否则模型很容易欠拟合。数据分布直接决定训练策略。如果只是验证算法流程把所有类别直接丢进去训练没问题如果要上线建议对样本少的类别做针对性增强或者补充采集。2.2 哪些 SKU 适合直接训哪些要留意从类别清单看这份数据覆盖了比「罐装饮料」更广的零售场景薯片是高反光包装东鹏特饮和红牛是金属罐养乐多是典型的矮胖瓶型金典、特仑苏是利乐砖包装。从产品形态上可以分三组金属罐组可乐、雪碧、芬达、红牛、东鹏特饮。罐体反光明显光照变化时表面纹理差异大适合测试模型对反光物体的鲁棒性。塑料瓶/矮瓶组养乐多、AD 钙奶。体积小密集摆放时相互遮挡严重是检测难点。利乐砖/纸盒组金典、特仑苏、蒙牛、伊利、旺仔牛奶。外形方正特征稳定相对好检测。如果你的实际场景主要盯金属罐这组数据直接可用。如果需求偏向利乐砖样本可能要再加。有一个容易踩坑的细节是数据里同一 SKU 可能存在多视角图片——俯拍、平视、侧视混在一起这其实是好事增强泛化能力但需要确认验证集和训练集是同分布否则离线指标会虚高。2.3 数据划分建议原始数据没有内置 train/val 划分需要自己切分。我的做法是按 8:1:1 划分训练、验证、测试注意用train_test_split的stratify参数按类别分层避免某个类别在验证集里消失。import os import random import shutil import json # 先读取所有图片和标注再按比例切分 images coco[images] annotations coco[annotations] # 按图片 id 分组标注 ann_map {} for ann in annotations: ann_map.setdefault(ann[image_id], []).append(ann) img_ids [img[id] for img in images] random.seed(42) random.shuffle(img_ids) train_ids img_ids[:int(len(img_ids)*0.8)] val_ids img_ids[int(len(img_ids)*0.8):int(len(img_ids)*0.9)] test_ids img_ids[int(len(img_ids)*0.9):] # 按划分写回新 JSON 文件 def dump_subset(ids, out_path): sub_images [img for img in images if img[id] in ids] sub_anns [] for img in sub_images: sub_anns.extend(ann_map[img[id]]) json.dump({images: sub_images, annotations: sub_anns, categories: coco[categories]}, open(out_path, w)) dump_subset(train_ids, annotations/instances_train.json) dump_subset(val_ids, annotations/instances_val.json) dump_subset(test_ids, annotations/instances_test.json)分层划分的意义在于保证类别分布一致性。如果随机切分小样本类别可能会全部掉进训练集导致验证集指标虚高或失真。实测里stratify对 14 类且样本不均衡的数据集影响很大建议务必加。3. 把 COCO 转成 YOLO 格式转换脚本与四个边界坑3.1 转换脚本怎么写YOLO 格式和 COCO 最大的差异是坐标体系。COCO 的bbox是绝对像素坐标[x, y, width, height]YOLO 要求的是归一化后的中心点坐标[cx, cy, w, h]全部除以图片宽高。如果直接复制坐标不改训练出来的模型边框会偏移得离谱。import os import json def convert_coco_to_yolo(coco_json, img_dir, out_dir): with open(coco_json, r, encodingutf-8) as f: coco json.load(f) os.makedirs(out_dir, exist_okTrue) # 建立 category_id 到 yolo_label 的映射按 categories 的索引顺序 cat_id_to_label {} for idx, cat in enumerate(coco[categories]): cat_id_to_label[cat[id]] idx # 建立 image_id 到文件名和尺寸的索引 img_info {img[id]: img for img in coco[images]} # 按图片聚合标注 ann_map {} for ann in coco[annotations]: ann_map.setdefault(ann[image_id], []).append(ann) for img_id, anns in ann_map.items(): img img_info[img_id] w, h img[width], img[height] txt_path os.path.join(out_dir, img[file_name].rsplit(., 1)[0] .txt) with open(txt_path, w) as f: for ann in anns: x, y, bw, bh ann[bbox] # COCO bbox 转为 YOLO 中心点归一化坐标 cx (x bw / 2) / w cy (y bh / 2) / h nw bw / w nh bh / h label cat_id_to_label[ann[category_id]] f.write(f{label} {cx:.6f} {cy:.6f} {nw:.6f} {nh:.6f}\n) convert_coco_to_yolo( annotations/instances_train.json, images/train, labels/train )脚本逻辑不复杂核心就两件事坐标换算和类别索引对齐。有个细节容易被忽略——cat_id_to_label的映射不是直接取category_id而是按照categories列表的索引顺序重新编号。如果 COCO 的category_id是 1 到 14而 YOLO 的类别编号从 0 开始直接用原 id 会导致类别错位模型训练时 loss 能降但推理结果全乱。转换之后还要做一步校验查看生成的 txt 文件内容确认坐标值都在 [0,1] 区间内、类别编号不超过类别总数。如果出现大于 1 的坐标回去检查原始 COCO 的bbox有没有超出图片边界。3.2 四个很容易翻车的边界坑坑一图片尺寸不一致。数据集里的图片不是统一分辨率有的 800×600有的是 1920×1080。YOLO 训练时会把图片 resize 到统一尺寸但标注坐标是按原图归一化的所以没问题。问题出在如果你先 resize 图片再转换标注坐标就必须按新尺寸重算。我建议保留原图让 YOLO 的 dataloader 在训练时统一处理。坑二类别编号越界。COCO 的categories列表如果不按顺序排列或者某个category_id有跳号比如从 1 直接跳到 5转换脚本的idx枚举就会错乱。我一般会在转换前打印一遍cat_id_to_label映射表用眼睛核对一遍。坑三空标注文件。如果某张图片在 COCO 里没有对应标注脚本不会生成 txt。这本身没错但 YOLO 训练时如果labels目录里缺了某个文件会报错。我把这类图片直接过滤掉不参与训练省得后面排查。坑四中文路径问题。数据集里如果图片文件名带了中文比如「东鹏特饮_001.jpg」Linux 下没问题Windows 下容易编码报错。我统一重命名成 ASCII 文件名顺带把标注文件里的file_name也替换掉。3.3 转换后的目录结构建议按下面的目录结构组织这符合 YOLOv8 的默认读取习惯dataset/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ ├── labels/ │ ├── train/ │ ├── val/ │ └── test/ └── data.yamldata.yaml是 YOLO 训练必需的配置文件内容如下path: /absolute/path/to/dataset train: images/train val: images/val test: images/test names: 0: cola 1: sprite 2: fanta 3: redbull 4: dongpeng 5: yogurt 6: ad-milk 7: jindian 8: telunsu 9: mengniu 10: yili 11: wantwantnames的索引和转换脚本里的label是对应的从 0 开始。注意path必须是绝对路径相对路径在 YOLOv8 的不同版本里表现不一致我遇到过相对路径在命令行和 Python API 两种调用方式下解析结果不同的情况。4. YOLOv8 训练实操配置、命令与参数调优4.1 训练配置与命令行数据集准备好之后直接用 YOLOv8 训练。先确认环境里装了ultralytics包然后写一个训练脚本方便调整参数和复现实验。我习惯用 Python 脚本而不是纯命令行因为可以记录每次实验的配置。from ultralytics import YOLO # 加载预训练权重用 COCO 预训练的模型做迁移学习 model YOLO(yolov8n.pt) # 轻量版先跑通流程 # 训练参数配置 results model.train( datadataset/data.yaml, epochs100, imgsz640, batch16, lr00.01, optimizerSGD, patience20, augmentTrue, projectruns/detect, namecan_drink_v8n, device0, # 如果有 GPU 用 0否则用 cpu seed42, )参数含义imgsz640输入图片统一 resize 到 640×640。如果原始图片较小且小目标多可以降到 512 提速如果图片高清且目标大用 768 或 1024 能提升小目标召回率。batch16显存不够就降到 8。这个数据集一千多张图batch 16 在 8GB 显存上跑 YOLOv8n 没问题。lr00.01YOLOv8 默认就是 0.01配合 SGD 优化器对中小数据集比较稳。如果 loss 震荡明显降到 0.005。patience20验证集指标连续 20 轮不提升就早停防止过拟合。训练结束后runs/detect/can_drink_v8n/weights/best.pt就是验证集上表现最好的权重。如果只追求速度和轻量部署yolov8n够用如果离线指标不达标可以换yolov8s或yolov8m参数量翻倍但 mAP 通常能提升 35 个点。4.2 训练过程怎么看训练日志里重点盯几个指标box_loss边框回归损失、cls_loss分类损失、dfl_loss分布损失以及验证集上的mAP50和mAP50-95。前 20 轮主要观察 loss 是否稳定下降如果 loss 直接爆炸或者 NaN优先检查学习率是否过大、数据标注是否有非法值。mAP50是 IoU 阈值 0.5 下的平均精度mAP50-95是 0.5 到 0.95 多个阈值下的平均精度。对罐装饮料检测来说mAP50目标做到 0.95 以上mAP50-95做到 0.75 以上算合格。如果你的场景是货架巡检对定位精度要求高重点看后者。训练完成后用val模式跑一遍测试集model YOLO(runs/detect/can_drink_v8n/weights/best.pt) metrics model.val(datadataset/data.yaml, splittest) print(metrics.box.map50) # mAP50 print(metrics.box.map) # mAP50-95如果测试集指标比验证集低很多超过 5 个点要么是数据划分不均匀要么是过拟合了。过拟合的典型特征是训练 loss 持续下降但验证 loss 不再下降此时加大augment强度或增加weight_decay。4.3 类别不均衡的应对这份数据里如果可乐、雪碧的样本量是 AD 钙奶、旺仔牛奶的好几倍模型大概率会对大样本类别过拟合。YOLOv8 没有直接的类别权重参数我一般用两种方式处理一种是对小样本类别做额外的数据增强——复制粘贴增强Copy-Paste把 AD 钙奶的实例随机粘贴到训练图上增加其出现频率。另一种是直接对小样本类别做离线增强生成增强样本加入训练集。# 用 imgaug 对小样本类别图片做增强示例 import imgaug.augmenters as iaa import cv2 aug iaa.Sequential([ iaa.Fliplr(0.5), iaa.Affine(rotate(-15, 15)), iaa.Multiply((0.8, 1.2)), # 亮度变化 ]) # 对每个小样本类别的图片生成 3 张增强图 for img_path in small_sample_images: img cv2.imread(img_path) for i in range(3): aug_img aug(imageimg) cv2.imwrite(faug/{img_path.stem}_{i}.jpg, aug_img)注意增强后的图片要同时更新对应的标注 txt不能只增强图片不改标注。实际跑下来对小样本类别做 3 倍增强mAP50 平均提升 23 个点代价是训练时间增加 10% 左右整体划算。5. 训练后的验证与推理部署只看 mAP 不保险5.1 可视化验证才是王道mAP 指标只能说明整体精度看不到具体问题。训练完我习惯随机抽 20 张验证集图片做推理把检测框和置信度画出来肉眼检查以下三类典型问题漏检货架深处的饮料罐没被识别出来通常是因为目标太小或遮挡严重可以考虑提高输入分辨率。误检把薯片袋识别成可乐罐红色包装之间容易混淆需要补充负样本或增加类别间判别性。定位偏移检测框只框住了一半罐体常见于罐体边缘被遮挡或者标注本身不够精确。用下面的脚本批量导出可视化结果import os import cv2 from ultralytics import YOLO model YOLO(runs/detect/can_drink_v8n/weights/best.pt) os.makedirs(visualize, exist_okTrue) for img_name in os.listdir(dataset/images/val)[:20]: img_path os.path.join(dataset/images/val, img_name) results model.predict(img_path, conf0.25, imgsz640) # 保存带框的图片 annotated results[0].plot() cv2.imwrite(fvisualize/{img_name}, annotated)conf0.25是置信度阈值实际部署时通常提高到 0.40.5减少误检。可视化结果里如果某个类别的框总是压着物体边缘回查原始标注的质量——这个数据集有些框是略松的需要自己微调。5.2 导出 ONNX 做边缘部署如果模型要部署到 Jetson、RK3588 这类边缘设备先导出 ONNX 再转 engine 或 rknnmodel YOLO(runs/detect/can_drink_v8n/weights/best.pt) model.export(formatonnx, imgsz640, opset12, simplifyTrue)导出后会生成best.onnx用 ONNXRuntime 做推理验证import onnxruntime as ort import numpy as np import cv2 session ort.InferenceSession(best.onnx, providers[CUDAExecutionProvider, CPUExecutionProvider]) img cv2.imread(test.jpg) img_resized cv2.resize(img, (640, 640)) img_rgb cv2.cvtColor(img_resized, cv2.COLOR_BGR2RGB) input_tensor img_rgb.astype(np.float32) / 255.0 input_tensor np.transpose(input_tensor, (2, 0, 1))[None, ...] outputs session.run(None, {images: input_tensor}) # outputs 包含检测框、置信度、类别按模型输出格式解析ONNX 推理时最常踩的坑是输入张量的归一化方式——YOLOv8 要求除以 255 且通道顺序是 RGB。如果输出检测框全偏移多半是这里没对齐。另一个坑是opset版本Jetson 上老版本 TensorRT 对 opset 12 支持更稳新版本算子可能不支持建议固定 opset 12。5.3 模型在边缘设备上的实际效果如果实际场景是货架摄像头固定机位建议把imgsz降到 480推理速度提升明显精度损失一般不超过 2 个点。如果场景是手持扫码枪拍到的图片分辨率通常较高直接用原图推理或者做切片——大图切成 640×640 的窗口逐块推理再合并结果比直接缩放效果好。6. 进阶用法用这份数据集做迁移学习与小样本扩展6.1 在新场景下快速适配这份数据集覆盖了常见的罐装饮料品牌但实际部署时大概率会遇到新品牌——产品包装换新、地区限定口味、竞品上新。重新标注一千张图代价太高我一般先用这份数据做预训练再用新场景的几十张图片微调。# 用已有的 best.pt 做初始化训练自己的新数据 model YOLO(runs/detect/can_drink_v8n/weights/best.pt) model.train( datadataset/new_sku.yaml, epochs50, imgsz640, batch8, lr00.005, # 降低学习率防止破坏已有特征 freeze10, # 冻结前 10 层保留通用特征 namenew_sku_finetune, )freeze10是微调的关键参数冻结前几层卷积可以防止新数据量少时把底层特征学坏。小样本微调时lr0一般降到原训练的 1/5 到 1/2训练轮次减半。实测用 50 张新图微调mAP50 能到 0.85 左右比从零训练高不少。微调时的另一个技巧是数据增强的差异化配置新样本少所以增强强度要更高——hsv_h0.02、hsv_s0.5、hsv_v0.4、degrees10。YOLOv8 默认的增强参数已经比较均衡对小样本场景需要调高强度。6.2 扩充新类别时的注意事项当向训练集里加新饮料类别时先看新类别的实物形态是否和现有类别接近。比如「王老吉」和「红牛」都是红罐但王老吉的罐体更高如果只靠颜色特征区分很容易混淆。加新类别前先人工检查百来张经典样本确认同类之间的特征差异是存在的。一个比较隐蔽的问题是COCO 格式里新类别如果用的category_id和原有类别的 id 重复标注文件里的categories段不会报错但训练时类别编号会混乱直接导致 loss 不降。加类别时第一件事是重新生成一份干净的data.yaml并全量转换标注不要手动改 JSON。6.3 部署后的持续迭代模型上线后定一个 feedback loop每周从现场抓 50 张图人工标注后混入训练集再微调。我的习惯是每两周跑一轮微调每次用最新积累的 100200 张真实场景图epochs30lr00.003。这样模型会逐渐适应实际光照、货架角度和遮挡情况准确率会越滚越稳。从那以后我每次训练前都会强制走一遍流程先看类别分布热力图再检查标注质量抽样然后才动手转换格式和调参。这套流程花不了二十分钟但能挡住后续两小时的排查。希望这份数据集的实战流程对你有帮助跑通一次之后你会对瓶颈在哪有自己的判断。本文还有配套的精品资源点击获取
返回列表