ARTICLE DETAIL

资讯详情

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

YOLOv8烹饪工具检测全流程:从数据集准备到ONNX部署

YOLOv8烹饪工具检测全流程:从数据集准备到ONNX部署 简介面向目标检测工程师与算法学习者的烹饪工具识别数据集包含盘子、叉、勺子、杯子、碗、刀六类目标的标注信息对应2107张图像的检测识别任务适用于餐饮场景下的物品识别与智能服务等实践。数据集已按训练与验证需求划分并附带data.yaml配置文件可无缝适配YOLOv5、YOLOv7、YOLOv8、YOLOv9、YOLOv10、YOLO11等主流YOLO系列算法下载后即可开展模型训练与效果验证。压缩包内共2000个文件包括158个YOLO格式的TXT标注文件记录类别索引与归一化后的中心点坐标及宽高和1842个VOC格式的XML标注文件两种标签目录分离便于按需选用或转换。整个压缩包大小约167.73MB结构简洁清晰省去手动标注与格式整理的繁琐步骤也降低了数据预处理门槛。目前已有84人学习使用适合希望快速获得高质量标注数据、专注算法调参与落地的中高级开发者。1. 一个烹饪工具检测数据集正好用来跑通 YOLO 的训评链路取餐台的摄像头要在几百毫秒内分辨出盘子和碗是否归位后厨监控要认出刀叉勺是不是掉进了备料桶这类需求落到算法侧就是一个典型的多类别目标检测问题。烹饪工具检测数据集包含 2107 张带标签的厨房场景图像覆盖盘子、叉、勺子、杯子、碗、刀六类目标目标尺寸跨度大、互相遮挡多、银色反光强很适合走完 YOLO 从数据准备到模型部署的完整链路。刚接触目标检测的人拿这个小数据集比直接调 COCO 更容易定位问题训练过几版模型的人类间相似度和样本不均衡又能当试验田。选 YOLO 的原因很直接单阶段、复现成本低、公开权重多两千多张图的一轮训练只要几十分钟。下面从标注格式讲起把训练参数、指标解读和误检排查一次理清。2. 解开烹饪工具数据集压缩包先核对 YOLO 标签格式这类数据集多以压缩包交付解压后常见两个平行目录images 放图片labels 放同名 txt偶尔附带 classes.txt 或 data.yaml。如果两者都没给类别顺序就得靠标签编号反推这一步错了后面训练出的 mAP 再高也没有意义。2.1 目录结构与类别映射常见解压布局如下cook_tools_dataset/ ├── images/ │ ├── IMG_0001.jpg │ ├── IMG_0002.jpg │ └── ... ├── labels/ │ ├── IMG_0001.txt │ ├── IMG_0002.txt │ └── ... ├── classes.txt └── data.yaml图片和标签同名不同后缀是 YOLO 体系的约定。用 CVAT、labelImg 这类标注工具导出时也会遵循同一套规则。如果压缩包里混入了 PNG 或 HEIC先统一转成 JPG再让 labels 里的文件名跟着图片名改一遍否则训练时会因为读不到标签而自动丢弃样本。classes.txt 的常见写法如下行号就是类别编号plate fork spoon cup bowl knifeplate对应 0fork对应 1依此类推。YOLO 训练时并不读取类名本身只把 txt 第一列的整数当成索引所以改类名只影响可读性编号顺序变动则会直接影响结果。建议把类别名写进 data.yaml并保持和 classes.txt 完全一致。先建一张类别映射表后面调参时随时回查class_id类名目标形状特征0plate大面积圆形多为俯拍1fork长条形状头部分叉2spoon长条形状头部呈椭圆勺窝3cup矮柱体杯口近圆4bowl宽边弧形边缘明显5knife条形刀身宽于刀柄2.2 归一化坐标的五个数字怎么读YOLO 标签里每个目标占一行格式是五个数字2 0.453125 0.517361 0.109375 0.183333从左到右分别代表 class_id、目标中心 x、目标中心 y、目标宽度 w、目标高度 h。后四项全部除以图片宽高做了归一化取值在 0 到 1 之间。好处是换 imgsz 训练时不会错乱坏处是不直观只看 txt 很难看出框在哪。转回像素坐标时把四个小数分别乘上图片宽或高即可。对烹饪工具这类目标这个表示法有一个天然盲区勺子和刀是长条标签框永远和图像轴平行。柄一旦斜着放框就会包进大量背景模型学出来的预测框偏大容易和旁边的盘子重叠。遇到这种情况不需要逐张重标把长条目标单独挑出来拍成水平摆放能明显减少标注噪声。另一个思路是改用旋转框检测方案但代价是整个训练链路都要换小项目不划算。2.3 用 Python 统计类别数量与异常标签拿到数据集后我一般先跑一个统计脚本确认六个类是否齐全。下面的脚本遍历 labels 目录统计类别个数同时把空标签和坏行打出来from pathlib import Path from collections import Counter lbl_dir Path(labels) class_names [plate, fork, spoon, cup, bowl, knife] counter Counter() bad_lines 0 for lbl_path in sorted(lbl_dir.glob(*.txt)): lines lbl_path.read_text(encodingutf-8).splitlines() if not lines: bad_lines 1 continue for line in lines: parts line.split() if len(parts) ! 5: bad_lines 1 continue cls_id int(parts[0]) if 0 cls_id len(class_names): counter[class_names[cls_id]] 1 else: bad_lines 1 print(f类别统计: {dict(counter)}) print(f空标签或坏行数: {bad_lines})脚本对每个 txt 逐行拆分不是五列、类别编号越界、文件为空都记入坏行。输出的类别分布能直接反映数据是否平衡如果 fork 比 spoon 少一半以上模型对样本少的类别 recall 会明显偏低。空标签不一定会报错但等于让 YOLO 把整张图当纯背景训练若空文件数量大回到标注工具里补框或者直接把图删掉。建议顺手把图片尺寸也统计一遍看有没有分辨率极端的样本。某批图是 1920×1080另一批是 512×512训练时统一缩放到 640 后小图里的盘子会被放大得发糊模型对大尺寸图的泛化就会变差。用 OpenCV 读前几十张图的宽高查出离群尺寸后单独处理比直接开训练更省事。3. 用 YOLOv8 训练六类烹饪工具模型目录划分、data.yaml 与参数表数据检查完就该进训练环节。网上聊“yolo第几代了”这类话题很容易被版本带偏在 2107 张图的小数据集上代际差异远小于数据质量差异。YOLOv8s 命令简洁、默认增强对小目标相对友好用它把流程跑通之后再迁到更新版本只换 model 参数。下面按 YOLOv8 命令行流程讲。3.1 先把数据集按 train/val 拆开压缩包里如果没有预设的 train/val 子目录需要手动拆一部分图做验证集。下面脚本按 9:1 拆分随机种子固定为 42保证每次拆分结果一样方便前后对比实验import random import shutil from pathlib import Path img_root Path(images) lbl_root Path(labels) for sub in (train, val): (img_root / sub).mkdir(exist_okTrue) (lbl_root / sub).mkdir(exist_okTrue) imgs sorted(img_root.glob(*.jpg)) random.Random(42).shuffle(imgs) val_size int(len(imgs) * 0.1) val_imgs imgs[:val_size] for img in val_imgs: shutil.move(str(img), str(img_root / val / img.name)) lbl lbl_root / (img.stem .txt) if lbl.exists(): shutil.move(str(lbl), str(lbl_root / val / lbl.name)) for img in imgs[val_size:]: shutil.move(str(img), str(img_root / train / img.name)) lbl lbl_root / (img.stem .txt) if lbl.exists(): shutil.move(str(lbl), str(lbl_root / train / lbl.name)) print(f验证集数量: {val_size})这段脚本把 images 下所有 jpg 打散前 10% 迁入 val其余迁入 train。标签如果存在就跟着图片一起迁移避免出现图有标签、标签目录没对应文件的情况。这里用lbl.exists()而不是报错退出原因是只想把无标签图也迁过去后续统一处理。如果有标签缺失数量很大就把脚本改成先统计再迁移别让数据悄悄少了一个类。3.2 写 data.yaml让 YOLO 认识这六个类拆完后在数据集根目录建 data.yamlpath: /home/you/cook_tools_dataset train: images/train val: images/val nc: 6 names: 0: plate 1: fork 2: spoon 3: cup 4: bowl 5: knifepath指向数据集根目录train和val是相对这个根的路径。names的编号顺序必须和标注时保持一致如果 classes.txt 里 cup 排在第 3 行这里第 3 项也必须写 cup。为验证顺序没写错拆分完后随机抽一个 txt 和同名图片用 OpenCV 把框画出来看一眼。这类问题最隐蔽的地方在于损失下降正常、mAP 也不低但预测时模型把勺子全认成了刀原因就是类别编号错位而不是模型没学好。3.3 训练命令与必调参数准备就绪后执行yolo detect train \ modelyolov8s.pt \ datadata.yaml \ imgsz640 \ epochs80 \ batch16 \ patience20 \ cacheTrue \ projectruns \ namect_yolov8s下面几个参数是这个小数据集上最值得关注的部分参数建议值理由modelyolov8s.pts 起步快带 COCO 预训练权重mAP 不足再换 mimgsz640适配大多数显卡显存对 2000 多张图足够epochs80配合 patience20损失多轮不降会自动停batch168G 显存也能跑显存充足可加到 32cacheTrue小数据集一次性载入内存减少磁盘 IO 对训练速度的干扰训练时控制台会打印 box_loss、cls_loss、dfl_loss 三条曲线这就是 YOLO 的损失函数构成box_loss 管预测框回归cls_loss 管分类dfl_loss 是分布焦点损失让回归更贴合边界。三者持续下降说明优化过程正常但如果损失曲线明显向下而 val 指标不动先回第 2.3 节的类别统计里找问题而不是继续加大 epochs。小数据集还要注意增强策略。YOLOv8 默认开启 mosaic对盘子和碗这类重叠目标有好处能逼模型适应遮挡但训练后期仍开着 mosaic模型会看到太多拼贴背景反而离真实厨房构图变远。Ultralytics 提供了 close_mosaic 参数可以在最后 10 轮关闭 mosaic让模型在接近真实分布的图像上收尾。4. 评估烹饪工具检测结果mAP 指标、混淆矩阵与误检排查训练完先跑一次验证别急着把权重交给部署方yolo detect val \ modelruns/ct_yolov8s/weights/best.pt \ datadata.yaml \ splitval这里指定 best.pt 而不是 last.ptbest 是验证集上表现最好的权重last 只是最后一轮结果两者可能差出好几个点。4.1 val 输出里的核心指标怎么读val 结束后控制台会打印每个类的 Precision、Recall 和 mAP并在输出目录里生成混淆矩阵和带框验证图。对烹饪工具场景重点看下面四个指标指标回答的问题什么时候优先看它Precision预测框里有多大比例是对的误检多、背景被框出来时Recall真实目标里有多大比例被找到漏检多、目标被跳过时mAP50IoU0.5 时的整体平均精度判断模型能否上线mAP50-95IoU 从 0.5 紧到 0.95 的整体均值判断预测框贴不贴合目标mAP50 对盘子和碗这类大目标是红线mAP50-95 决定框的贴合度。如果 mAP50-95 比 mAP50 低很多说明框经常包住半个桌面在计数场景里两个碗叠在一起时容易产生重复计数。如果业务只关心数量和是否归位mAP50 够用就不必为了刷高 mAP50-95 盲目换大模型。4.2 最容易互相认错的组合烹饪工具单个类差异不大评估时常见三类混淆碗和杯子俯拍都是近圆杯口带把时会更像碗勺子和叉子都是长条低分辨率下头部细节丢失刀身银色反光导致边缘断裂模型可能把刀把和刀身切成两段。这三类错的根源都指向标注框的形状长条目标框宽比接近圆形目标框中心重叠。确认这类问题不能只靠肉眼看验证图。val 生成的 confusion_matrix.png 是最直观的工具矩阵里对角线外哪格最亮哪个组合就最危险。比如刀被预测成勺说明标注样本里刀身和勺头的长宽比太接近。这时不要把时间花在调置信度阈值上阈值只能改变数量和误检比例改变不了类别内部的特征混淆。4.3 用预测结果反查误检再决定改标签还是换模型用 Python 统计验证集预测出的各类数量是很快的反查方式。预测标签位于 val 输出下的 labels 目录每个 txt 和图片同名先看格式再跑下面脚本from pathlib import Path from collections import Counter pred_dir Path(runs/ct_yolov8s/val/labels) pred_cnt Counter() for txt in pred_dir.glob(*.txt): for line in txt.read_text().splitlines(): pred_cnt[int(line.split()[0])] 1 print(预测类别计数:, dict(pred_cnt))把预测计数和真实计数并排放真实 cup 有 800 个预测只有 400 个第一反应不该是调模型而是回查这些图的标签特别是杯口反光区域有没有被漏标。漏标会让模型在训练时把该区域当背景这种问题加训练轮次也救不回来。标签确认无误后再考虑从 yolov8s 换到 yolov8m。对六类目标换大模型换来的提升有限但推理耗时同步上升。更划算的路径是用第 2.3 节的脚本筛出少数类的样本随机抽几十张检查标注一致性把刀柄没框全、勺子头被截断这类边界标注补好再续训十几个 epoch。多数情况下这一步带来的效果比改模型结构更直接。5. 把训练好的烹饪工具检测模型导出 ONNX单帧推理验收val 通过不代表部署通过。把 best.pt 放进服务端前常见做法是先导出 ONNX再用 onnxruntime 做一次单帧推理验收。这一步能同时确认两件事模型导出没有损坏CPU 上的耗时是否满足取餐台这类场景的实时要求。导出命令如下yolo export \ modelruns/ct_yolov8s/weights/best.pt \ formatonnx \ imgsz640 \ dynamicFalse \ opset12dynamicFalse把输入尺寸固定为 1×3×640×640能享受更快的推理优化代价是以后换摄像头分辨率需要重新导出。用 onnxruntime 做单帧验证只需十几行代码import cv2 import numpy as np import onnxruntime as ort sess ort.InferenceSession(best.onnx, providers[CPUExecutionProvider]) img cv2.imread(test.jpg) input_img cv2.resize(img, (640, 640)) input_tensor input_img[:, :, ::-1].transpose(2, 0, 1)[None] / 255.0 input_tensor input_tensor.astype(np.float32) out sess.run(None, {sess.get_inputs()[0].name: input_tensor})[0] out out[0].transpose(1, 0) # [8400, 5 nc] scores out[:, 4:].max(axis1) # 类别置信度 classes out[:, 4:].argmax(axis1) mask scores 0.25 cand_boxes out[mask]预处理里做了 BGR 转 RGB再归一化到 0 到 1。YOLO 在训练时用的通道顺序和 OpenCV 默认不同最容易踩的坑就是漏掉转色导致预测框全乱而损失看起来正常。上面的结果只是初筛候选真正的检测还要在候选框上做 NMS这一步我在验证阶段通常先不做直接用可视化判断模型有没有被导出成功。CPU 上单帧推理的时间主要花在 640×640 卷积计算上。如果实测超了业务的帧率预算优先降到 512 再导一版而不是先换推理引擎对盘子、碗这类大目标512 的精度损失通常小于耗时收益。量化 int8 放到最后一步做并且用静态尺寸导出动态形状会给量化过程增加额外的不确定性。最后一版如果要在多个摄像头角度复用给每个机位单独拍一组验证图按第 4.1 节的指标重新跑一遍 val 流程确认在同一套 data.yaml 下俯拍和侧拍指标的差距没有超过一个经验边界。这个边界因业务而异但总比直接拿一个机位的 mAP 去估算所有工位要可靠一些。本文还有配套的精品资源点击获取
返回列表