
简介面向目标检测开发者的YOLO系列食品图像数据集包含鸡蛋、冰箱、米饭等常见食物与生活物品的标注样本标签采用YOLO标准格式每行记录类别索引、归一化中心坐标、目标框宽高适用于YOLOv5、YOLOv7、YOLOv8、YOLOv9、YOLOv10、YOLO11等主流算法的训练和验证。资源包共五百四十七个文件其中两百七十三张JPG图像与两百七十三个TXT标签文件一一配对另有一个YAML配置文件用来定义数据路径和类别名称整体大小为十三点七兆下载轻松快捷。数据集已经预先划分好训练集与验证集可以直接运行常用训练脚本进行模型训练和效果评估也支持转换为VOC格式以便其他检测框架使用。目前已有五十六人浏览学习该资源对于想快速获得干净、带标签的小型食品检测数据集来做算法实验、课程设计或毕业设计的读者这份资料能省去手动采图与标注的繁琐环节让人把精力聚焦在模型调参与检测性能优化上适合入门到进阶的YOLO学习者。1. 273张食品图YOLO能训出一个能用的模型吗冰箱里剩一碗米饭、保鲜层放了半盒鸡蛋想让摄像头自动认出这些food items你需要的就是食品数据集和YOLO算法这套组合。food-items-gldps.zip 这个 273 张带标签的小数据集恰好能把「图片进、检测框出」的最小闭环跑通。它的价值不在规模而在轻量标注齐全、类别贴近厨房真实场景适合用来验证迁移学习流程、做嵌入式零食柜识别的 Demo也适合刚接触目标检测的工程师练手。反直觉的一点是273 张图在小模型加预训练权重的配置下训练一轮只要几分钟反而能让你快速暴露标签问题和超参方向比一上来就啃几千张的大数据集更省时间。2. 拆开 food-items-gldps.zip先搞清标签格式和类别定义2.1 压缩包内部的三类文件图像、标注文件、类别清单解压后你会看到典型的检测数据集结构。图像是 JPG 或 PNG 格式的厨房场景照片分辨率不统一有的拍的是打开状态的冰箱内部有的是一碗米饭特写还有带包装的鸡蛋托盘。标注文件分两种常见形态一种是 VOC 风格的 XML每个文件对应一张图框坐标写在bndbox节点里另一种是 YOLO 风格的 TXT每行一个目标格式是class_id x_center y_center width height坐标全部做了归一化。类别清单通常叫classes.txt或label_map.txt逐行写类别名。拿到压缩包后第一件事不是急着训练而是确认标注是哪种格式。我的习惯是直接打开一张 TXT 看一眼如果每行有 5 个数且都是 0 到 1 的小数那就是 YOLO 格式如果 XML 里出现filename、width、height这种节点那就是 VOC。这个决定影响后面的转换脚本做错了会白跑一整轮训练。2.2 把 VOC 的 xml 坐标转成 YOLO 的 txt归一化计算脚本常见做法是统一转成 YOLO 格式因为 ultralytics 的训练接口只认 txt。转换的核心是把xmin, ymin, xmax, ymax换算成中心点坐标和宽高再除以图片宽高做归一化import xml.etree.ElementTree as ET import os def voc_to_yolo(xml_path, out_dir, class_map): tree ET.parse(xml_path) root tree.getroot() img_w int(root.find(size/width).text) img_h int(root.find(size/height).text) lines [] for obj in root.findall(object): name obj.find(name).text if name not in class_map: continue # 类别不在映射表里就跳过 box obj.find(bndbox) xmin float(box.find(xmin).text) ymin float(box.find(ymin).text) xmax float(box.find(xmax).text) ymax float(box.find(ymax).text) x_center ((xmin xmax) / 2) / img_w y_center ((ymin ymax) / 2) / img_h w (xmax - xmin) / img_w h (ymax - ymin) / img_h lines.append(f{class_map[name]} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}) if lines: out_path os.path.join(out_dir, os.path.splitext(os.path.basename(xml_path))[0] .txt) with open(out_path, w) as f: f.write(\n.join(lines))这里class_map是类别名到数字 id 的字典顺序要和后面data.yaml里的类别列表严格一致。最容易错的地方是忘了continue数据集里有些框的类别名带空格或者有特殊字符不在映射表里时脚本会抛 KeyError所以先过滤再写文件。另一个坑是图片宽高要从 XML 的size节点读不能拿图片文件自己算因为原始标注可能基于缩放后的版本两者一旦不一致转换后所有框都会偏。2.3 273 张图怎么切分train/val/test 比例与随机种子图片量小常见的切分比例是 8:1:1也就是 218 张训练、27 张验证、28 张测试。这里有一个隐藏需求数据集的标签里有鸡蛋、米饭、冰箱三类但每张图不一定包含所有类别直接随机切可能导致某一类只出现在训练集里验证集 mAP 永远是 0。所以我的做法是先按类别统计每张图的标签再分层切分import random import os from collections import Counter random.seed(42) # 固定种子保证每次切分结果一致 img_dir images txt_dir labels imgs [f for f in os.listdir(img_dir) if f.endswith((.jpg, .png))] # 统计每张图包含哪些类别 img_to_classes {} for img in imgs: txt os.path.join(txt_dir, os.path.splitext(img)[0] .txt) if not os.path.exists(txt): img_to_classes[img] [] continue with open(txt) as f: img_to_classes[img] sorted(set(line.split()[0] for line in f if line.strip())) random.shuffle(imgs) train, val, test imgs[:218], imgs[218:245], imgs[245:] print(train 各类别图片数:, Counter(c for img in train for c in img_to_classes[img])) print(val 各类别图片数:, Counter(c for img in val for c in img_to_classes[img]))打印计数后人工看一眼如果某个类别在 val 里是 0说明抽偏了重新换种子或者手动把带该类别的图补进验证集。217 张的量级里这个问题非常容易发生因为总共可能只有 20 张含「冰箱」的图随机切 10% 很可能一张都不进验证集。2.4 标注质量检查把边界框画回原图看一眼切分之前先跑一遍可视化脚本确认标注框和物体真实位置对得上。273 张图全看一遍不现实抽样 20 张就够发现大多数问题import cv2 with open(classes.txt) as f: names [line.strip() for line in f] for img_name in [000001.jpg, 000002.jpg, 000003.jpg]: img cv2.imread(os.path.join(images, img_name)) h, w img.shape[:2] txt os.path.join(labels, os.path.splitext(img_name)[0] .txt) with open(txt) as f: for line in f: cid, xc, yc, bw, bh map(float, line.split()) x1 int((xc - bw / 2) * w) y1 int((yc - bh / 2) * h) x2 int((xc bw / 2) * w) y2 int((yc bh / 2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(img, names[int(cid)], (x1, max(0, y1 - 6)), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 255), 2) cv2.imwrite(fcheck_{img_name}, img)把输出的check_*.jpg打开扫一遍重点看三件事框是不是包住了完整目标、类别标错没有、有没有漏标的小目标。食品数据集中漏标常发生在米饭这种背景色接近碗边的目标上半碗饭和空碗的边界模糊标注员很容易漏掉边缘的小块米粒。检查这一步花 10 分钟能省下后面一个小时的无效训练。3. YOLOv8 环境配置与训练273 张图跑通全流程3.1 yolo 环境配置最小依赖搞定训练ultralytics 把环境配置做得很薄虚拟环境里装一个包就能开始conda create -n yolo python3.10 -y conda activate yolo pip install ultralytics yolo predict modelyolov8n.pt sourcehttps://ultralytics.com/images/bus.jpg最后的yolo predict是为了验证环境可用它同时会把 yolov8n.pt 预训练权重下载到当前目录。这一步常见的问题是 PyTorch 和 CUDA 版本不匹配导致RuntimeError: CUDA unavailable如果你不是要在 GPU 上训练直接用 CPU 版 PyTorch 也能跑通这 273 张图只是每个 epoch 会慢不少。验证 CUDA 可用的命令是python -c import torch; print(torch.cuda.is_available())返回 False 就先装对应版本的 CUDA 版 torch再去跑训练。3.2 目录结构与 data.yaml让 YOLO 认识这个食品数据集ultralytics 的目录约定是 images 和 labels 同级train/val 各自成目录。整理完成后的结构如下food-items-gldps/ ├── data.yaml ├── images/ │ ├── train/ │ ├── val/ │ └── test/ ├── labels/ │ ├── train/ │ ├── val/ │ └── test/ └── classes.txtdata.yaml 是训练入口最关键的配置路径用相对当前工作目录的方式写方便换机器path: . # 数据集根目录相对或绝对路径均可 train: images/train val: images/val test: images/test nc: 3 # 类别数量 names: 0: egg 1: rice 2: fridgepath字段是坑最多的一个点。换机器后数据集根目录路径变了path: .要改成实际路径否则训练直接报AssertionError: train: ... does not exist。如果训练时报标签文件找不到先检查 labels 目录名和train字段是否指向了 images 下的对应目录ultralytics 是按 images 路径自动推导 labels 路径的两者必须严格同名同级。3.3 选 yolov8n 还是 yolov8s小数据集配小模型更稳273 张图属于典型的小样本模型选型直接决定能不能收敛。常见的选择是 yolov8n它的参数量约 310 万在单个 GTX 1060 上训练这批数据一个 epoch 也就几秒yolov8s 参数翻三倍但小数据集下精度增益有限反而更容易过拟合。下面的对比是一个参考模型参数量推理速度单图小数据集表现适用场景yolov8n约 3.1M快可在 CPU 上实时收敛快过拟合风险低273 张图的首选yolov8s约 11.1M较快需轻量 GPU精度略高但易过拟合数据量上千后再考虑yolov8m约 25.9M慢不推荐数据量上万时才值得yolo 预训练模型下载是自动进行的第一次跑训练命令时 ultralytics 会从官方仓库拉取对应权重不需要手动去网站找链接。指定modelyolov8n.pt即可如果想从零训练用modelyolov8n.yaml但在食品数据集这种规模下从零训练效果明显差于迁移学习不建议。3.4 训练命令与三个损失函数的判断依据训练命令按最小可用参数来先把流程跑通再调参yolo detect train \ datadata.yaml \ modelyolov8n.pt \ epochs100 \ imgsz640 \ batch16 \ lr00.01imgsz640是输入分辨率原始图片不足 640 的会被填充后缩放这个值不是越大越好270 张图用 640 足够batch16在显存不够时报CUDA out of memory降到 8 即可。训练过程会打印三个损失函数的值box_loss是边界框回归误差cls_loss是分类误差dfl_loss是分布聚焦损失负责框边界的精细度。判断收敛看验证集上的 mAP50 是否连续 20 个 epoch 不再上升而不是看训练损失训练损失会一路下降但验证指标可能早就停滞了。4. 273 张样本的训练策略过拟合、数据增强与迁移学习4.1 过拟合在小数据集上到底长什么样训练损失降到 0.02验证集 mAP50 却在 30 个 epoch 后开始下滑或者 mAP50 还算稳定但 mAP50-95 明显回落这就是过拟合的典型信号。273 张图撑不起一个模型的全部表达能力网络会去记忆训练集里某些背景的纹理而不是学习「鸡蛋」这个概念本身。具体表现是验证集上漏检增多尤其换了一个光线环境的冰箱图就认不出鸡蛋。应对过拟合不是压缩模型而是靠数据增强和迁移学习双管齐下。4.2 ultralytics 默认数据增强够用但口味要调ultralytics 内置了一套增强管线默认参数对通用目标检测是合理的食品场景则需要有针对性地微调。下面的表格是几个关键增强项及其默认值增强项参数默认值食品数据集的调整建议Mosaicmosaic1.0保持 1.0厨房场景切碎拼接后仍可辨认HSV 扰动hsv_h / hsv_s / hsv_v0.015 / 0.7 / 0.4降低 hsv_s米饭颜色过度饱和会让模型学偏随机翻转fliplr0.5保持 0.5镜像后食物仍是食物随机缩放scale0.5提高到 0.8让模型适应不同距离的拍摄平移translate0.1保持 0.1平移过大容易把目标切出画面调整方式不需要改代码在训练命令里直接加参数覆盖即可。我常用的一组是yolo detect train datadata.yaml modelyolov8n.pt \ epochs100 imgsz640 batch16 \ hsv_s0.3 scale0.8 translate0.15这样改的原因很直接食品数据集的拍摄距离变化大冰箱全景里的鸡蛋可能只有几十像素特写镜头里能占满全图scale 调大让模型见过更多尺寸变化而 hsv_s 调低是为了避免过度夸张的颜色扰动把米饭的纹理带偏。4.3 冻结骨干层做迁移学习freeze 参数怎么用迁移学习在 273 张图上的作用比任何花哨增强都明显。yolov8n.pt 在 COCO 上见过几百万张图骨干网络已经学到了通用的边缘、纹理、物体形状特征这些特征对食物同样有效。训练时冻结骨干层只让检测头适应新类别能显著降低过拟合。ultralytics 提供了 freeze 参数按层序号冻结yolo detect train datadata.yaml modelyolov8n.pt \ epochs100 batch16 freeze10freeze10 表示冻结前 10 层实际效果是骨干网络全部不参与反向传播只有检测头在更新。这样做的好处是训练更稳定几乎不可能把预训练权重学坏代价是如果食品数据和 COCO 的分布差异太大骨干层的特征可能不够用。对鸡蛋、米饭这种视觉特征明显的目标freeze10 是安全选择。如果发现验证 mAP 卡住不动再改成 freeze5 解冻一部分骨干层让高层特征也参与微调。4.4 mAP 卡在 0.7 上不去两个容易忽略的调参点第一个是学习率策略。ultralytics 默认用余弦退火但对小数据集线性下降往往更稳。训练命令加cosine_lrFalse试试如果验证损失曲线在后期震荡换成线性策略通常会好转。第二个是标签平滑。273 张图的标注难免有边界争议比如半碗米饭的框标签平滑可以让模型不那么自信地贴着训练标注走yolo detect train datadata.yaml modelyolov8n.pt \ epochs100 batch16 freeze10 \ cosine_lrFalse label_smoothing0.05标签平滑值从 0.05 起步太高会让损失变大且收敛变慢。这两个参数属于那种「调对了对 mAP 有半到两个点的提升」的细节但前提是前面的增强和冻结已经到位顺序不能反。在这里有一个通用的判断技巧训练完打开runs/detect/train/results.csv对比训练损失和验证损失如果验证损失曲线在某个 epoch 后转身向上就说明该提前早停或者增强正则了。5. 小数据集训练避坑手册273 张图跑 YOLO 的 5 个典型问题5.1 BN 崩溃val_loss 训着训着变成 nan现象训练到中后期日志里val_box_loss突然变成nan接着整个训练进程停止重启后问题依旧。原因BatchNorm 在小 batch 下统计量不稳定当 batch1 或 2 时BN 层的均值和方差估计失效数值溢出变成 nan。这个问题在 273 张图的验证集上尤其容易触发因为验证集只有 27 张图如果分布极端一个 batch 里全是同类目标BN 的统计量会剧烈波动。解决先用batch16或batch8训练别为了迁就显存把 batch 压到 4 以下。如果显存实在不够调低imgsz416而不是继续降 batch。另外可以把workers4加到训练命令里加快数据加载的同时也会让 BN 的统计更稳定。还有一个后手给验证阶段固定 BN 的 running statsultralytics 中可通过batch1配合rectTrue规避但根治办法是保证训练和验证的 batch 都不小于 8。5.2 混淆矩阵总和与样本数对不上现象训练结束后打开confusion_matrix.png把每个格子的数字加起来发现和验证集的真实目标总数差很多看起来像是漏了统计。原因混淆矩阵默认在置信度阈值 0.25 下统计也就是说预测置信度低于 0.25 的框会落进矩阵最右侧的background列而该列矩阵图上通常显示为深色但具体数值没标。另外一个原因是有部分目标本身标注了但被当作困难样本忽略阈值设得越高矩阵总和越小这是正常现象不是 bug。解决查看runs/detect/train/confusion_matrix.png时把background那一列也纳入计算如果想让矩阵总和等于 GT 数可以在验证时把置信度阈值降到 0.001但这不代表模型真实水平。正确用法是看对角线占比各类别对角线数字除以该行总和才是真正的召回率。顺手把测试集上每类的漏检图抽出来看一眼比盯着总和数字更有价值。5.3 鸡蛋样本是米饭的两倍类别不均衡拖低 mAP现象训练完成后鸡蛋的 mAP50 有 0.9米饭只有 0.6冰箱居中但单看每类的图片数量米饭的标注数确实偏少。原因273 张图里鸡蛋框可能有 400 个米饭框只有 200 个分类损失被多数类别主导少数类的梯度信号不够。解决ultralytics 里没有直接改 loss 权重的参数常见的做法是换modelyolov8n.pt后调整cls系数默认是 0.5对少数类适当调大到 0.7。另一个有效手段是做过采样把含米饭的图片在训练集里复制一份标签文件保持不变相当于提高采样概率。我一般把目录扫一遍按类别计数少于多数类一半的类别就多复制一轮import os, shutil from collections import Counter all_labels [] for f in os.listdir(labels/train): with open(os.path.join(labels/train, f)) as fh: all_labels.extend(line.split()[0] for line in fh if line.strip()) counter Counter(all_labels) max_c max(counter.values()) for f in os.listdir(labels/train): with open(os.path.join(labels/train, f)) as fh: cls_set set(line.split()[0] for line in fh if line.strip()) need any(max_c / counter[c] 1.8 for c in cls_set) if need: shutil.copy(os.path.join(labels/train, f), os.path.join(labels/train, dup_ f)) shutil.copy(os.path.join(images/train, f.replace(.txt, .jpg)), os.path.join(images/train, dup_ f.replace(.txt, .jpg)))注意跑完复制后 classes.txt 不需要改但 datasets 切分文件会被影响复制前后保持目录一致即可。5.4 验证集里画框全偏标签坐标和图像尺寸不匹配现象可视化检查时发现框整体偏移有的框比目标大一圈有的框落在物体边缘外侧训练 mAP 一直很低。原因标注时图像分辨率是 1920x1080但转换脚本读到的size是 640x480或者数据集发布者标注时用了缩放后的图原始图片却没同步缩放。归一化坐标在缩放图上计算又在原图上绘制偏差就出来了。解决用前面 2.4 的可视化脚本检查发现偏移后先确认 XML 里size和图片实际分辨率是不是一对。如果原图是 1920x1080 而标注的 width 是 640说明标注基于缩略图做的要么把原图统一缩到 640 分辨率再训练要么重新计算坐标。更快的临时方案是直接在训练命令里加imgsz640让统一缩放发生在进网络前框虽然偏但至少所有样本偏得一致模型还能硬学但这只是权宜之计根治是重新生成标签。5.5 训练 loss 卡住不降前 20 个 epoch mAP 一直是 0现象box_loss 稳定在某个值附近波动mAP50 连续 20 个 epoch 为 0日志里没有报错。原因最常见的是背景类别或类别 id 映射错位数据里标的是 0、1、2data.yaml 里 names 顺序写反了模型把所有目标都当成一个类来学其次是学习率过低lr00.001时模型基本在原地踏步。解决先是验证 data.yaml 的names顺序和标签文件里的 class_id 严格一致打开一个 txt 文件看第一列数字再对照classes.txt的排列。然后是学习率默认 0.01 对迁移学习偏高了我会降到 0.005 或 0.001 试一个 10 epoch 的短跑看损失是否开始下降。还有一种容易被忽略的情况数据集里混入了标签为空的图片一张全黑的图加一个空标签会拖住整个 batch 的学习信号把这种样本删掉再重跑。6. 验证、导出与下一步让 273 张图的模型真正用起来6.1 用 PR 曲线和 F1 值判断当前上限训练结束后不要只看 mAP50 一个数。打开runs/detect/train/PR_curve.png重点看每条曲线在召回率 0.8 附近是否还保持高精度如果曲线在召回率 0.7 就开始骤降说明模型对低置信度目标不敏感部署时要把置信度阈值调低一点。我在验证阶段的习惯是直接跑一遍测试集yolo predict modelruns/detect/train/weights/best.pt \ sourceimages/test save_txtTrue save_confTrue conf0.25把生成的 txt 和标注做对比逐个类别算精确率和召回率。对食品场景我一般要求目标类别 F1 达到 0.8 以上才算可用如果只有 0.7优先补数据而不是调参。6.2 导出 ONNX让模型脱离 PyTorch 跑起来落地到边缘设备的第一步是导出为 ONNX 格式yolo export modelruns/detect/train/weights/best.pt formatonnx imgsz640导出的best.onnx可以丢给 ONNX Runtime 或 TensorRT 做推理速度比原始 PyTorch 快不少。导出后验证一下输出张量的形状是否为(1, 84, 8400)其中 84 是 4 个框坐标加 80 个类别得分COCO 语义下的默认输出。如果你用的是自定义 3 类模型需要把 export 命令加opset12并检查类别数是否匹配。这一步最常见的翻车是 opset 版本太新、TensorRT 不认出现这种情况时我会加opset12重新导出。部署代码里需要自己解析输出张量做 NMS 后输出框输入图片的预处理要跟训练时一致归一化到 0-1、RGB 通道顺序、letterbox 填充。任何一个环节不一致推理效果都会打折扣。6.3 再进一步数据合成与 Transformer 检测头273 张图的上限是客观存在的到了验证集 mAP 不再涨的阶段常见的下一步是两件事一是用合成数据扩充把冰箱、米饭、鸡蛋的素材做随机抠图拼贴合成出几千张训练图再配合mosaic1.0增强效果通常比手动采集更稳二是换用带 Transformer 结构的检测头比如 YOLOv8 基础上替换 C2f 模块这类模型对全局上下文更敏感适合识别完整餐盘这类强上下文依赖的场景但需要至少上千张图才喂得饱。我的习惯是每训完一个版本就把 PR 曲线和失败样例截图存下来下一次迭代前后对比能直观看出增强手段哪一步真正起了作用。这轮在 food-items-gldps.zip 上跑完的同学如果手头还有别的食品数据可以试试把 273 张训练结果作为预训练权重继续微调下一批图迁移学习的收益往往比直接训新数据集高不少。希望帮到你。本文还有配套的精品资源点击获取