ARTICLE DETAIL

资讯详情

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

273张食品图像实战YOLO:小数据集目标检测避坑指南

273张食品图像实战YOLO:小数据集目标检测避坑指南 简介面向基于YOLO系列算法实战的食品目标检测数据集涵盖鸡蛋、冰箱、米饭等常见物品图像适用于yolov5至yolo11等多个版本模型的训练、验证与测试。资源已划分好数据集标签采用YOLO格式的归一化坐标类别、中心点、宽高可直接送入模型训练如需其他框架也可按需转换为VOC格式。包内共547个文件主体为273张JPG图像与273个对应TXT标注文件另附1个YAML配置文件用于定义类别和数据集路径。压缩包整体约13.7MB体积轻量上手门槛低便于快速下载和本地实验。目前已有56人学习下载。对于刚接触目标检测的初学者可借助这份数据快速跑通YOLO训练流程对于需要验证模型效果的开发者也能用它完成基准测试或迁移学习实验。图像与标签一一对应目录清晰能节省大量标注与整理时间。1. 273张食品图像练YOLO小数据集到底能不能打厨房里架一个摄像头想自动认出冰箱里的鸡蛋、台面上的米饭这听上去是 yolo算法 顺手就能解决的事真正上手才发现瓶颈从来不在模型而在数据。手上这份 food-items 食品数据集273张图像带标签内容覆盖鸡蛋、冰箱、米饭等常见对象——放在工业视觉项目里这个量级确实寒酸但配合预训练权重和严格的数据划分训练出一个能跑通的检测模型绰绰有余。这篇笔记就沿这条落地路径走一遍先拆开压缩包看标签装的是什么再把它喂给常见 YOLO 实现训练最后用验证指标判断这套食品数据集值不值得继续往深处投。适合刚学 YOLO 想找个干净数据集入门的开发者也适合手里只有小样本、要做嵌入式食品识别的工程师。2. 打开 food-items-gldps.zip先把标签格式看清楚再动手拿到压缩包别急着解压就开始训练这是我反复踩过的坑第一步永远是确认标注格式。文件名里的 gldps 只是打包时写的标识不代表任何标注规范真正是什么格式解压之后看文件后缀才知道。2.1 压缩包里通常躺着三种标签形态这类食品数据集解压后一般会有三样东西图像本体、标签文件、一个 classes.txt 或 label.txt。图像格式大多是 jpg 或 png标签则取决于当初用哪个标注工具导出最常见的三种如下表。标签形态文件后缀坐标单位常见来源YOLO 格式 txt.txt归一化坐标cx cy w h范围 0~1labelImg 的 YOLO 模式、部分爬虫自动化标注脚本VOC 格式 xml.xml像素坐标xmin ymin xmax ymaxlabelImg 的 VOC 模式、公开数据集二次导出COCO 格式 json.json像素坐标按图像 id 组织CVAT、labelme 转出、coco-annotator判断方法很简单同级目录下有没有 labels 文件夹没有就找 annotations 目录打开一个 txt 文件如果第一列是 0、1、2 这种整数后面四个是 0 到 1 之间的小数就是 YOLO 格式如果看到的是bndbox标签那就是 VOC xml。这份数据集如果带的是 txt省一步转换但建议仍检查一遍坐标范围和类别 id 顺序因为打包的人很可能改过类别清单。2.2 用脚本做格式归一化VOC 转 YOLO 的转换脚本与参数说明如果解压出来是 xml就需要转成 YOLO 训练要求的 txt。转换逻辑不复杂但两个细节错了整批标签都会废一是归一化必须用图像真实宽高二是类别 id 必须和 classes.txt 里的一一对应。下面这个脚本我一直在用改改 CLASSES 列表就能跑。import os import glob import xml.etree.ElementTree as ET # 按 classes.txt 的顺序填第一行是 id0顺序错了模型就会把语义学歪 CLASSES [egg, fridge, rice] def voc_to_yolo(xml_path, out_dir): tree ET.parse(xml_path) root tree.getroot() size root.find(size) img_w int(size.find(width).text) img_h int(size.find(height).text) out_lines [] for obj in root.findall(object): name obj.find(name).text if name not in CLASSES: # 不在类别清单里的对象直接跳过不要报错中断 continue class_id CLASSES.index(name) box obj.find(bndbox) x1 int(box.find(xmin).text) y1 int(box.find(ymin).text) x2 int(box.find(xmax).text) y2 int(box.find(ymax).text) # 注意 w/h 要用像素差除以图像宽高这里最容易出错 cx ((x1 x2) / 2) / img_w cy ((y1 y2) / 2) / img_h w (x2 - x1) / img_w h (y2 - y1) / img_h # 保留 6 位小数足够 YOLO 使用多了反而让文件变大 out_lines.append(f{class_id} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}) out_name os.path.basename(xml_path).replace(.xml, .txt) with open(os.path.join(out_dir, out_name), w) as f: f.write(\n.join(out_lines)) os.makedirs(labels, exist_okTrue) for xml_file in glob.glob(annotations/*.xml): voc_to_yolo(xml_file, labels)这段脚本里值得单独说的是 CLASSES 列表它的顺序必须和训练时 data.yaml 里 names 的顺序完全一致一旦错位模型会把鸡蛋学成冰箱、把米饭学成鸡蛋而且这种错位不会报任何错误只会在验证时表现为 mAP 极低。另一个参数是 6 位小数精度这是 YOLO 官方数据集常见精度训练时不会因为坐标精度掉点转完可以用下面的检查脚本兜底。提示转换前对原 annotations 目录做一次备份直接覆盖原 xml 的脚本都留个心眼。2.3 标签质量抽查图像和标签对不上的三种典型情况小数据集最怕的不是数量少而是标签里藏着脏数据。273 张图最多也就几百条标注人工抽查只需要十分钟这十分钟值得花。以下三种情况是食品类数据集里的高频问题。第一种是图像分辨率不一致。有些数据集里混入了不同来源的图有的 1920x1080有的 640x480但标注从原图导出后没有跟着变归一化坐标里出现负值或大于 1 的值。第二种是类别 id 错位常见表现是某个 txt 里出现了一个 classes.txt 里不存在的 id。第三种是空标签图还在但对应 txt 文件大小为零训练时这张图会被当成纯背景。下面这段脚本用来快速定位这三种脏数据。import os import glob for txt in glob.glob(labels/*.txt): with open(txt, r) as f: lines f.readlines() if len(lines) 0: print(f[空标签] {txt}) continue for line in lines: parts line.strip().split() if len(parts) ! 5: print(f[格式错误] {txt}: {line}) continue cls_id int(parts[0]) cx, cy, w, h map(float, parts[1:]) if cls_id 0 or cls_id len(CLASSES): print(f[类别越界] {txt}: {line}) if not (0 cx 1 and 0 cy 1 and w 0 and h 0): print(f[坐标越界] {txt}: {line}) # 中心点离边界太近的框训练时容易截断成碎片 if cx w / 2 or cx w / 2 1 or cy h / 2 or cy h / 2 1: print(f[边界越界] {txt}: {line})遇到越界框常见处理是过滤掉完全出画面的标注半出画面的框则用图像边界裁剪一遍再重新归一化。273 张图规模小手动修正完全可行如果发现空标签超过 10 张宁可删掉这几张图也不要放进训练集否则冰箱门会被模型学成背景纹理。3. 用 273 张图跑通 YOLOv8 训练目录划分与最小命令标签确认没问题接下来就把数据集喂给 YOLO。这部分按 YOLOv8 的常见流程来写命令和目录结构在 YOLOv5、YOLOv9 里也通用只有参数名略有差别。3.1 数据集目录结构images 和 labels 必须分家YOLO 检测训练要求图像和标签分开存放train/val 成对出现。很多初学者图省事把标签放在图片旁边一个文件夹里然后在 data.yaml 里指到同一层目录这种做法训练器直接报错找不到标签。标准结构如下。food_dataset/ ├── images/ │ ├── train/ │ │ ├── 001.jpg │ │ └── ... │ └── val/ │ ├── 005.jpg │ └── ... ├── labels/ │ ├── train/ │ │ ├── 001.txt │ │ └── ... │ └── val/ │ ├── 005.txt │ └── ... └── data.yamlimages 和 labels 下的 train 子目录文件名必须一一对应只有后缀不同jpg 对 txt。这里的核心逻辑是训练器会扫描 images/train 里的每张图然后去 labels/train 找同名文件找不到就跳过或者报 warning。273 张图切 80/20 的话train 约 218 张、val 约 55 张这个比例在小数据集上是安全的不要再往下降。3.2 数据划分脚本固定随机种子保证可复现切分数据时要固定随机种子否则每次跑训练验证集都不同模型好坏就没法对比了。下面这段脚本把图像随机打乱后按比例移入 train/val。import os import random import shutil SEED 42 # 固定种子换数字验证集就变了 VAL_RATIO 0.2 # 小数据集 20% 做验证比较稳 random.seed(SEED) imgs sorted(os.listdir(images)) random.shuffle(imgs) val_count int(len(imgs) * VAL_RATIO) print(ftrain: {len(imgs) - val_count}, val: {val_count}) def move_image(img, dst): base os.path.splitext(img)[0] shutil.move(fimages/{img}, fimages/{dst}/{img}) label_name base .txt label_src flabels/{label_name} if os.path.exists(label_src): shutil.move(label_src, flabels/{dst}/{label_name}) else: print(f[警告] 缺标签: {label_name}) for img in imgs[:val_count]: move_image(img, val) for img in imgs[val_count:]: move_image(img, train)这段脚本里最关键的是 sorted 之后再 shuffle保证每次运行结果一致。有人图省事直接os.listdir()不排序就 shuffle虽然也能跑但在不同文件系统上目录顺序不同可复现性就丢了。缺标签的警告不要忽略273 张图里如果缺 5 张以上先补齐再训练否则验证集指标会虚低。3.3 data.yaml 和训练命令必调的四个参数YOLO 训练的数据集描述文件是 data.yaml内容极少但一点都不能错。path 建议写绝对路径写相对路径在跑 val 的时候经常找不到文件。path: /home/user/food_dataset train: images/train val: images/val names: 0: egg 1: fridge 2: rice然后用下面这条命令启动训练。这里选 yolov8n.pt 而不是从头训练是因为 273 张图从零开始学特征绝对不够必须靠预训练权重兜底。yolo detect train \ modelyolov8n.pt \ datadata.yaml \ epochs100 \ batch16 \ imgsz640 \ workers0 \ patience20 \ seed42逐项说下我调整这些参数的习惯。batch16 在 273 张图上不会让梯度噪声太大显存 6GB 以下就改 8。imgsz640 是主流默认值如果鸡蛋、米饭这种小目标很多我会直接提到 960代价是训练时间变长。workers 在 Linux 服务器上可以设 4Windows 上建议设 0否则经常遇到 DataLoader 死锁的玄学问题。patience20 是早停耐心值20 个 epoch mAP 不涨就停省时间。训练日志里每轮都会打印 yolo损失函数 的三个分量 class_loss、box_loss 和 dfl_loss注意观察 class_loss 如果长期下不去多半是标注里的类别不均衡。4. 小数据集食品检测的避坑清单四个高频翻车现场273 张图像这个规模训练节奏和千图万图完全不一样。下面四个坑是我在类似小样本食品数据集上真实遇到过的按现象、原因、处理的顺序写清楚。4.1 训练震荡不收敛先查 batch size 和初始学习率现象是训练日志里的 box_loss 像锯齿一样上下跳动val 指标不升反降明明模型在变但验证集 mAP 纹丝不动。这种问题的原因在小数据集上排第一的是 batch size 太小加学习率太高。273 张图本身样本方差就大如果 batch4每个 step 看到的样本太少梯度方向剧烈抖动。解决路径是固定的先把 batch 提到 16 或 32如果显存不够就降 imgsz 而不是降 batch然后看初始学习率YOLOv8 默认 lr00.01小数据集我直接降到 0.001 并开启 warmup 让前面几个 epoch 先用小学习率探路。改完再训损失曲线通常会平滑很多。这套流程多跑几回就有经验了不收敛这事多少有点玄学但排查顺序别乱。4.2 冰箱被误检成背景类别区分度不够现象是验证集里 fridge 的 mAP 明显低于 egg 和 rice预测结果里常常把冰箱门上半截框出来或者标成 egg。原因是冰箱在视觉上是一大块白色或银灰色平面这种大面积低纹理区域和纯背景太像模型抓不到稳定的判别特征。处理上先检查标注口径是需要框住整个冰箱柜体还是只框一个门把手区域两种口径混在一起模型就懵了。另一个有效的调整是降低颜色类数据增强强度冰箱是白色如果 hsv_s 增强把白色甩成橙色模型会在「颜色正确」这个维度上学歪。把这类大面积对象单独看混淆矩阵如果不理想就得手动补 10 来张带明显厨房背景的冰箱图让模型学会「周围有台面、墙砖的地方才是冰箱不是一块白板」。4.3 鸡蛋小目标漏检mosaic 增强成了帮凶现象是验证集的预测结果里鸡蛋经常漏尤其是鸡蛋放在米饭上、放在冰箱隔层里的场景小目标密集且和背景颜色接近。小数据集上不少人依赖 mosaic 增强扩充样本但 mosaic 会把四张图拼在一起再缩放鸡蛋这种目标被缩到十几个像素的时候标签框也跟着缩成几乎一个点。解决方法有两条路一是把 mosaic 默认的 1.0 降到 0.3让大部分 epoch 用完整图片训练二是把 imgsz 从 640 提到 960小目标在更大分辨率下能保留更多像素特征。训练完之后再看 val 结果里鸡蛋的 AP如果涨了说明就是增强方式的问题。一个验证技巧是单独挑几张含鸡蛋数量最多的图看模型输出的框是偏大还是偏小偏大说明它学到的不是目标轮廓而是周围环境。4.4 标注框偏移导致损失降不下去现象是 box_loss 在训练后期一直横盘怎么调学习率都没用最后用 best.pt 验证时预测框明显比真实框大一圈。原因是部分标注的框画在了目标外围尤其是冰箱这种大目标标注时容易把周边墙面、阴影一起包进去。排查方法是用脚本把标注框回画到图上肉眼抽查 30 张。下面这段代码把 YOLO 归一化坐标转回像素坐标用 OpenCV 画框。import cv2 def draw_yolo_box(img_path, txt_path): img cv2.imread(img_path) h, w img.shape[:2] with open(txt_path) as f: for line in f: parts line.strip().split() if len(parts) ! 5: continue cls_id, cx, cy, bw, bh map(float, parts) x1 int((cx - bw / 2) * w) y1 int((cy - bh / 2) * h) x2 int((cx bw / 2) * w) y2 int((cy bh / 2) * h) color (0, 255, 0) if int(cls_id) 0 else (0, 0, 255) cv2.rectangle(img, (x1, y1), (x2, y2), color, 2) cv2.imwrite(check_ os.path.basename(img_path), img) draw_yolo_box(images/val/001.jpg, labels/val/001.txt)处理方式是回到标注工具里修正偏移的框然后重新导出。如果你不想重新标也可以在训练时把 label_smoothing 调高一点但这只是缓解不能根治。273 张图规模小一次手动修正最多半小时这半小时的回报比任何调参都高。5. 迁移学习加数据增强把 273 张图的有效样本量翻倍小数据集训练的核心思路只有一个——不要从零学习视觉特征而是让模型在预训练特征基础上做微调。这章把迁移学习和增强参数讲透同时给出一套在小数据集上经过验证的增强配置。5.1 加载预训练权重迁移学习的正确姿势YOLOv8 用modelyolov8n.pt启动训练时训练器会加载 COCO 预训练权重并自动替换最后一层检测头。为什么要这么做因为食品数据集里的鸡蛋、米饭、冰箱在 COCO 里都有近似类别蛋类、餐盘、冰箱类家电模型已经知道这些物体长什么样缺的只是对你这 273 张图里特定场景的适应。从零训练相当于让一个成年人重新学认字预训练微调则是教一个识字的人认你的笔迹。实际操作中要注意一点如果中断训练后想接着跑把 model 参数换成上一次生成的 last.pt而不是继续用 yolov8n.pt。yolo detect train \ modelruns/detect/train/weights/last.pt \ datadata.yaml \ epochs50 \ batch16 \ imgsz640这样接着训练时优化器状态和当前学习率都会保留。小数据集尤其推荐这种方式前面训了 80 个 epoch 的成果不会因为一次断电清零。5.2 增强参数怎么调才不破坏真实场景YOLOv8 默认开了整套增强策略对小数据集是好事但食品场景对颜色、翻转方向特别敏感不能照搬默认值。下面是我在这类数据上调整过的参数表。参数YOLOv8 默认值食品数据集建议值原因hsv_h0.0150.01色调偏移过大会让米饭变成紫色hsv_s0.70.3饱和度变化影响鸡蛋表面阴影识别fliplr0.50.5冰箱、台面左右翻转仍然真实flipud0.00.0上下翻转在厨房场景里违反物理规律mosaic1.00.3保留一定增强但避免小目标被切碎scale0.50.3过度缩放会让鸡蛋只剩几个像素translate0.10.1轻微平移可以保留这套配置的主要思路是「让模型看到更多构图变化而不是更多颜色变化」。厨房场景里光线、角度、遮挡是真实世界的主要方差色相偏移则是纯人工制造的噪声这套数据里冰箱门是白色你把白色调成橙色模型学到的就是「白色不是冰箱门」推理时直接漏检。hsv 增强在大多数视觉任务里是好东西但在食品检测里要克制。5.3 训练后验证看损失曲线和 mAP 而不是瞎调训练结束后的第一件事不是反复改参数重训而是看 runs/detect/train 目录下的 results.png。这张图上画着 yolo损失函数 的 class_loss、box_loss、dfl_loss 三条曲线以及验证集的 mAP50 和 mAP50-95。判断逻辑如下如果 box_loss 稳步下降说明标注框质量没问题如果 class_loss 降到一定程度后横盘说明某些类别在特征空间里靠得太近。最典型的例子是米饭和碎鸡蛋碎颜色都是黄白色形状都是不规则块状模型区分不了。这类问题靠调参数没用要么补样本要么考虑合并类别。跑一次正式验证用下面这条命令它会输出各类别的 AP 和混淆矩阵。yolo val modelruns/detect/train/weights/best.pt \ datadata.yaml \ conf0.25 \ iou0.5conf0.25 是置信度阈值验证时一般不用动iou0.5 是判断检测框是否匹配的 IoU 阈值对应 mAP50。如果项目对框位置要求高再看 mAP50-95 的值这个指标对框的精确度更敏感。273 张图的小数据集mAP50 到 0.7 以上就算合格mAP50-95 通常比 mAP50 低 20~30 个点这是正常的不需要焦虑。6. 用 mAP 和混淆矩阵验收判断这套数据集值不值得继续投入训练完成之后用 best.pt 跑一次整体预测把结果图和真实标注放在一起看这一步比任何指标都直观。下面这条命令会把预测结果自动存成带框的图片。yolo predict modelruns/detect/train/weights/best.pt \ sourceimages/val \ conf0.25 \ saveTrue保存的图里如果出现一个现象——冰箱框住了但鸡蛋漏了或者米饭框一半基本可以断定是样本不均衡。这时候再看验证输出的混淆矩阵对角线亮、非对角线暗说明模型学到位了如果某一行除了对角线外还有一大块亮点说明这个类别和另一个类别在特征层面无法区分。我对这套 273 张食品数据集的判断标准是mAP50 达标后先做一轮可视化抽检确认 90% 以上的真实目标都被框住再谈要不要补数据。补数据的优先级也有讲究——不是盲目加图而是按类别看 AP哪个类别的 AP 明显低于均值就只补那个类别的图。曾经我手里只有 200 张图的蔬菜数据集mAP50 卡在 0.55补了 60 张草莓硬光照图po 之后单一类别 AP 直接涨了 12 个点这就是踩坑经验也是这套流程里最有价值的一次投入。至于后续方向如果你想从检测升级到 yolo实例分割这个数据集现有的 bounding box 标注不够用需要重新画轮廓但用它验证实例分割流程的可行性是够的。最后一句话小数据集训练靠的是标注质量、增强克制、预训练兜底这三件事273 张图能不能用不在数量在你怎么用。希望帮到你。本文还有配套的精品资源点击获取
返回列表