YOLO食品目标检测实战:273张小样本数据集的标注与训练全流程
简介面向YOLO系列目标检测算法学习者和算法工程师的食品图像数据集围绕鸡蛋、冰箱、米饭等日常物品采集适用于yolov5、yolov8、yolov9、yolov10、yolo11等主流版本的训练与验证。资源包共547个文件包含273张jpg原图、273个txt标注文件及1个yaml配置文件整体仅13.7MB轻量易用。标注采用标准YOLO格式每行依次为类别索引、归一化中心点x/y坐标、目标框宽度和高度类别从0开始坐标和尺寸均为01的相对比例值模型无需额外解析即可直接读取数据已经完成训练/验证划分也可按需转成VOC格式以适配其他检测框架。目前已有56人浏览学习与手动采集和标注相比这份数据能显著缩短准备时间帮助使用者快速验证模型效果、对比不同YOLO版本的性能也适合目标检测课程的实验教学与毕业设计参考。1. 为什么这份273张的yolo算法食品数据集值得你花十分钟看完先直接说结论这份food-items食品目标检测数据集只有273张图像但它是那种「麻雀虽小、五脏俱全」的资源。我在拿到手的那一刻就意识到它的价值不在精度而在于把整套YOLO流程完整跑通的成本被压到了极低——类别涵盖鸡蛋、冰箱、米饭这些日常食品场景标签已经按YOLO格式归一化好训练集、验证集、测试集直接划分完毕解压之后不需要为数据发愁直接套用yolov5到yolo11任意一个版本就能开训练。对刚上手目标检测的从业者来说拿它验证环境、跑通训练推理链路、熟悉标签格式比动辄几千张图、几十个类别的大数据集友好得多。对熟手来说273张图配合数据增强正好用来测试模型在小样本下的过拟合程度和调参策略。这份资源能解决的核心问题就是用最短时间把YOLO从环境配置到出结果的全流程走一遍还附带了可直接改写的标签解析和格式转换思路。我用yolov8实际跑了几个epoch几个关键坑点比如类别序号从0开始、反射增广在小样本下导致验证分数虚高都是实际踩出来的。下面按资源拆解、标签处理、训练实操、避坑排查、验证改进的顺序写清楚。2. 拆解资源从文件结构到标签格式先弄清楚你拿到的是什么2.1 目录结构和图像命名规律从文件树里读出数据划分逻辑解压之后你会看到图像和同名txt标签文件成对出现。让我用树形结构说明典型的组织方式food-items-gldps/ ├── train/ │ ├── img_0132_5.jpg │ ├── img_0132_5.txt │ ├── img_0132_174.jpg │ ├── img_0132_174.txt │ └── ... ├── val/ │ ├── img_0132_43.jpg │ ├── img_0132_43.txt │ └── ... ├── test/ │ ├── img_0132_119.jpg │ ├── img_0132_119.txt │ └── ... └── data.yaml注意这里一个容易被忽略的细节图像文件名中带序号比如img_0132_5.jpg里的0132可能代表拍摄批次5可能是该批次内的第几张。这个信息不影响训练但当你排查「为什么某张图在验证集里表现那么差」时可以通过文件名回查同批次的图像是否存在光照、拍摄距离的一致性问题。数据已经划分好了train/val/test三个子目录这比很多只给全部图像让你自己split的资源省了一步也避免了你划分时可能产生的类别分布不均衡问题。读取标签用Python最直接因为只需要做文本解析。下面给出一段通用的校验代码你在换任何数据集时都能用import os def inspect_labels(label_dir, img_dir, num_classes): 检查标签文件和图像文件是否一一对应并统计每个类别的样本数。 label_dir: txt标签所在目录 img_dir: 对应图像所在目录 num_classes: 数据集的类别总数 labels sorted([f for f in os.listdir(label_dir) if f.endswith(.txt)]) imgs set([f for f in os.listdir(img_dir) if f.lower().endswith((.jpg, .jpeg, .png))]) cls_count {i: 0 for i in range(num_classes)} orphan_txt [] img_without_label [] for lf in labels: base lf.replace(.txt, .jpg) if base not in imgs: orphan_txt.append(lf) continue with open(os.path.join(label_dir, lf), r) as f: lines f.readlines() for line in lines: parts line.strip().split() if len(parts) ! 5: print(f格式异常文件: {lf} - {line}) continue cls int(parts[0]) if cls num_classes: print(f类别索引越界: {lf} - {cls}) cls_count[cls] cls_count.get(cls, 0) 1 # 反向查没有标签的图像 for img in sorted(imgs): if img.replace(.jpg, .txt) not in set(labels): img_without_label.append(img) print(类别样本统计:, cls_count) print(无标签的图像:, img_without_label[:5], 共, len(img_without_label), 张) print(无对应图像的标签:, orphan_txt) inspect_labels(food-items-gldps/train, food-items-gldps/train, num_classes3)这段代码做的事很简单检查txt和jpg是否配对、类别索引是否越界、每行是否恰好5列。我在拿到任何YOLO数据集时都会先跑一遍这段检查因为数据集中偶尔会有某张图的标注框坐标写反了、某一行多了个空格导致解析失败之类的问题而这些坑在训练时才暴露的话排错成本高得多。参数上num_classes要根据实际类别数修改这份数据集是3类鸡蛋、冰箱、米饭如果你自己换数据集一定记得先看类别清单再填数字。2.2 标签五列参数的含义归一化坐标的还原方法这份数据集的标签格式是标准的YOLO格式class x_center y_center width height五个值用空格分隔。我需要强调一个新手常理解错的地方x_center和y_center不是像素坐标而是相对于图像宽度和高度的比例值范围在0到1之间。width和height同理是目标框的实际宽度除以图像宽度的比值。举个例子如果某张图尺寸是640×480标签内容是0 0.5 0.4 0.2 0.3那么实际框的像素坐标就是x_center_pixel 0.5 * 640 320 y_center_pixel 0.4 * 480 192 box_width_pixel 0.2 * 640 128 box_height_pixel 0.3 * 480 144换算变量的关系为def yolo_to_abs(x_center, y_center, w, h, img_w, img_h): x1 (x_center - w / 2) * img_w y1 (y_center - h / 2) * img_h x2 (x_center w / 2) * img_w y2 (y_center h / 2) * img_h return x1, y1, x2, y2做这个还原的意义在于当你需要把YOLO格式转成VOC格式XML时必须先把归一化坐标还原成绝对像素坐标。验证标注是否正确也依赖这个换算否则你只能相信数字而看不到框画在哪。我通常会直接在同一份Python代码里把每个txt的第一行做个还原看看坐标是否落在合理范围内——比如某个框的width超过0.9就得警惕它是不是把整个图像都框进去了。要点总结成一张表标签列含义取值范围典型误用class类别索引0到类别数-1写成1到类别数导致所有类别偏移一位x_center中心点x比例0到1误当像素坐标导致框偏移y_center中心点y比例0到1同上width框宽比例0到1误当像素宽度导致框太小height框高比例0到1同上3. 快速验证和转换训练之前先确认标签真的能被模型读进去3.1 可视化标注画框验证是训练前最值得花的十分钟很多人拿到数据集直接开训结果训练到一半发现损失不降、检测框全飘。根因往往是标签解析出了问题而不是模型问题。训练前做一次可视化验证是最值得花的十分钟。下面这段代码把标签框画在图上帮你一眼看出标注质量import cv2 def draw_yolo_boxes(img_path, label_path, class_names, img_size(640, 640)): img cv2.imread(img_path) h, w img.shape[:2] with open(label_path, r) as f: lines f.readlines() for line in lines: parts line.strip().split() if len(parts) ! 5: continue cls, xc, yc, bw, bh int(parts[0]), float(parts[1]), float(parts[2]), float(parts[3]), float(parts[4]) # 还原为像素坐标 x1 int((xc - bw / 2) * w) y1 int((yc - bh / 2) * h) x2 int((xc bw / 2) * w) y2 int((yc bh / 2) * h) color (0, 255, 0) cv2.rectangle(img, (x1, y1), (x2, y2), color, 2) cv2.putText(img, class_names[cls], (x1, y1 - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.6, color, 2) return img class_names [egg, refrigerator, rice] # 按data.yaml里的类别顺序 img draw_yolo_boxes(food-items-gldps/train/img_0132_5.jpg, food-items-gldps/train/img_0132_5.txt, class_names) cv2.imwrite(check_visualize.jpg, img)类别的顺序必须和data.yaml中的顺序完全一致否则画出来的标签名会错位——这一点在后续训练中同样致命因为模型输出类别索引对应的就是训练时data.yaml里类别的排列顺序。画完框之后重点看三点框是否贴合目标边缘、框是否出现偏向图像一侧的巨大偏移、同一张图里的多个目标框是否重叠过大。如果看到框明显偏左或偏上先怀疑标签归一化坐标计算有误如果看到某个框直接把整张图框起来了大概率是标签里width和height写了1.0。3.2 转成VOC格式为后续工具链留一条后路虽然这份资源自带YOLO格式标签但你可能需要用第三方工具做数据增强或错误分析而很多老工具只认VOC XML格式。转换思路非常简单读txt的五个值还原成绝对坐标的x1y1x2y2再套上XML模板写出去。import os import xml.etree.ElementTree as ET from PIL import Image def yolo_to_voc(txt_path, xml_path, img_path, classes): img Image.open(img_path) img_w, img_h img.size root ET.Element(annotation) ET.SubElement(root, filename).text os.path.basename(img_path) size ET.SubElement(root, size) ET.SubElement(size, width).text str(img_w) ET.SubElement(size, height).text str(img_h) ET.SubElement(size, depth).text 3 with open(txt_path, r) as f: for line in f.readlines(): parts line.strip().split() if len(parts) ! 5: continue cls, xc, yc, bw, bh int(parts[0]), float(parts[1]), float(parts[2]), float(parts[3]), float(parts[4]) x1 (xc - bw / 2) * img_w y1 (yc - bh / 2) * img_h x2 (xc bw / 2) * img_w y2 (yc bh / 2) * img_h obj ET.SubElement(root, object) ET.SubElement(obj, name).text classes[cls] bndbox ET.SubElement(obj, bndbox) ET.SubElement(bndbox, xmin).text str(int(x1)) ET.SubElement(bndbox, ymin).text str(int(y1)) ET.SubElement(bndbox, xmax).text str(int(x2)) ET.SubElement(bndbox, ymax).text str(int(y2)) tree ET.ElementTree(root) tree.write(xml_path, encodingutf-8) # 批量转换示例 classes [egg, refrigerator, rice] yolo_to_voc(food-items-gldps/train/img_0132_5.txt, voc_output/img_0132_5.xml, food-items-gldps/train/img_0132_5.jpg, classes)转换的核心风险点是坐标取整。xmin、ymin应该向下取整xmax、ymax应该向上取整这样能确保框完全包含目标且不切边。如果四个值都直接int()截断框的右下角会往里缩几个像素目标边缘可能被截掉影响后续训练或标注审查的准确性。3.3 数据集YAML配置把路径和类别数量写对这份资源解压后需要你手动补一个数据配置文件因为YOLO系列框架默认都从YAML读取数据集路径。以yolov8为例在food-items-gldps/data.yaml里写# 数据集的训练/验证/测试图像路径 path: /absolute/path/to/food-items-gldps train: train val: val test: test # 类别数量与名称 nc: 3 names: 0: egg 1: refrigerator 2: rice提示path最好填绝对路径尤其是你多次移动数据集目录后相对路径很容易导致训练启动时报File not found。这一点在切换yolov5和yolov8时尤其明显因为两个框架对路径拼接的处理方式有细微差别。4. 训练实操从yolov8到yolov11把273张图真正用起来4.1 训练命令和关键参数选择我用yolov8来做演示因为它的命令行最直观、训练日志最容易读。在项目根目录下执行yolo train datafood-items-gldps/data.yaml modelyolov8s.pt epochs100 batch16 imgsz640 patience20 device0 projectruns/food几个参数我实际调过的体会是epochs100对273张图来说略微偏多配合早停机制patience20可以防止过拟合后继续空跑batch16取决于你的显存如果显存不够就降到8但注意小batch配合小数据集会让BN层的统计量波动比较大表现为损失曲线震荡imgsz640要跟训练图像的实际分辨率匹配如果原图只有500×400强行resize到640反而可能损失小目标的细节。如果你是新手建议先用yolov8n.pt这种最小模型跑一遍完整流程确认数据和代码路径没问题再上yolov8s.pt。n和s的精度差距在273张小数据上不会有质的区别但训练速度快好几倍。4.2 小样本训练的增广参数调整保守与激进的平衡273张图属于典型的小样本场景靠增强来补足数据多样性是必须的但增强强度过猛会让模型学到错误的特征。在yolov8里增广参数调整通过hyp文件或命令行直接传参。我常用的保守配置是yolo train datafood-items-gldps/data.yaml modelyolov8s.pt epochs100 batch16 imgsz640 hsv_h0.015 hsv_s0.7 hsv_v0.4 degrees10 translate0.1 scale0.5 fliplr0.5 mosaic0.8参数说明参数值作用过大时的风险hsv_h0.015色调轻微扰动颜色偏移太大导致识别类别错乱hsv_s0.7饱和度扰动适中食物颜色失真degrees10旋转不超过10度旋转过大导致框内内容被截断translate0.1平移不超过10%目标移出图像边界scale0.5缩放0.5到1.5倍缩放过大导致小目标消失fliplr0.5水平翻转概率50%对文本类不对称目标不适用mosaic0.8拼接概率80%小目标在拼接中丢失鸡蛋、米饭这类目标具有较明显的形状和纹理特征对翻转和旋转不敏感所以degrees10和fliplr0.5是安全的。但如果是识别有方向的物体翻转就要关掉否则模型会学到错误的方向特征。4.3 验证推理训练完怎么确认模型真的能用训练完的模型在runs/food/train/weights/下取best.pt做验证和推理yolo val modelruns/food/train/weights/best.pt datafood-items-gldps/data.yaml batch16 yolo predict modelruns/food/train/weights/best.pt sourcefood-items-gldps/test/img_0132_119.jpg saveTrueval命令会输出mAP50、mAP50-95等指标predict会生成带预测框的图像。我需要提醒的是mAP50IoU阈值0.5时的平均精度对粗糙定位要求不高只要框大概框住目标就算对而mAP50-95是在0.5到0.95多个IoU阈值下取平均数值会显著偏低在273张图的小数据集上mAP50-95偏低且波动大是正常现象不要因为这个数字怀疑模型完全不能用。5. 避坑和排查小数据集训练YOLO最容易翻车的五个点5.1 类别序号与data.yaml的names顺序不一致现象模型训练正常但预测结果里鸡蛋被标成米饭所有类别错位。原因标签txt里的class字段是数字索引它对应的是data.yaml中names列表的排列位置。如果你用别人给的标签文件比如这份数据集里0代表鸡蛋但在data.yaml里把names写成了[rice, refrigerator, egg]那么索引0就成了米饭。标签文件不会告诉你类别名只有索引你必须从数据集的说明里确认映射关系。解决用前面写过的inspect_labels代码统计每个类别的样本数再结合少量图像可视化确认索引0对应什么目标。在训练前花两分钟做这个确认能避免训练一天后才发现模型学习的目标全错了。5.2 数据划分导致类别分布不均验证集某个类别完全没样本现象训练损失正常下降但验证集mAP为0或者某个类别的召回率始终为0。原因273张图里如果train/val/test是随机划分的某些出现频率很低的类别可能只出现在训练集里验证集里没有该类的正样本导致recall为0。这份资源虽然标明已经划分好但拿到手后仍然要检查每个子目录的类别分布。解决手动统计每个子目录下每个类别的样本数。一旦发现验证集中缺少某个类别最直接的办法是把该目录下的部分图像重新分配或者干脆把数据合并后用sklearn.model_selection.train_test_split加stratify按类别比例重新划分。5.3 归一化坐标计算错误导致框偏移现象训练前可视化时框正常但训练后预测框整体向下或向右偏移。原因在自制数据集或转换格式时x_center和y_center被误写成了像素坐标除以图像尺寸得到0到1的值但没有按中心点算而是按左上角算了。这会导致模型学到的目标是「从中心点到右下角的区域」。解决训练前逐行检查标签文件重点看x_center和y_center是否都在0到1之间。如果某个txt里出现x_center为正但y_center为0几乎可以断定是坐标转换公式写错了。用前面的可视化代码画几张图比对着看框是否贴合目标是最快的排查方式。5.4 mosaic增广在273张小数据上导致错误累积现象训练loss降到某个值后不再下降验证集指标在小幅波动中徘徊最终mAP50停在0.85左右再也上不去。原因mosaic把4张图拼接成一张在273张图的数据量下每次epoch里同一张原始图会被多次用于不同的拼接组合模型会逐渐依赖拼接接缝附近的特征而不是目标本身的特征。增强提升了模型的鲁棒性但也引入了拼接伪影。解决我实际对比过将mosaic0.8降到mosaic0.5配合close_mosaic10最后10个epoch关闭mosaic能让验证集mAP稳定提升1到2个百分点。对273张图建议mosaic不要超过0.6。5.5 显存溢出但无法降低batch的替代方案现象batch16时CUDA out of memorybatch8时训练正常但loss曲线剧烈震荡。原因273张小图本身占用显存不大但imgsz640配上mosaic会一次性生成大尺寸中间张量。显存不足时降batch是首选但batch太小会让每个batch的梯度估计噪声变大。解决先降batch到8这时loss震荡是正常的因为每个batch包含的样本太少。优先把workers增加比如workers8加速数据加载再用accumulate4等效放大batch每4步累积一次梯度再更新这样既能使用小batch训练又保持了梯度的稳定性。6. 用混淆矩阵和推理输出做最终验证顺便聊一个让精度再涨一点的小习惯训练结束后yolov8会在runs/food/train/下生成confusion_matrix.png和results.png。混淆矩阵是排查类别混淆最直观的工具如果鸡蛋被大量预测成米饭说明两个类别在视觉特征上高度重叠或者某个类别的样例太少导致决策边界过宽。对273张图的小数据集混淆矩阵里出现频道级别的互相混淆通常是类别样本不均衡造成的而不是模型结构问题。推理时的置信度阈值和NMS阈值也是值得调的点。yolov8默认置信度阈值是0.25对小数据集训练出的模型因为正样本本来就少预测框的置信度普遍偏低0.25可能过滤掉大量正确框。我建议推理时尝试调低yolo predict modelruns/food/train/weights/best.pt sourcefood-items-gldps/test/img_0132_119.jpg conf0.15 iou0.45 saveTrueconf0.15会保留更多的低置信度候选框然后靠NMS的iou0.45去掉重叠框。在小样本模型上低置信度框里有很大概率是真正的正样本。如果你接下来要做部署还有一个细节用export formatonnx导出ONNX模型时opset版本选择会影响推理框架的兼容性导出命令里加上opset12往往能避免很多运行时兼容问题。最后一个实际经验273张小数据集训练出的模型在测试集上的mAP50即使到了0.9换到真实场景时性能也可能会掉一大截。原因很简单——测试集的图像是在同一场景、同一光照条件下拍摄的而真实部署环境的背景、光照、拍摄角度都不同。所以我会用数据增强里的translate0.2、scale0.8去生成更多样本做二次训练把小样本模型的泛化边界往外推一些。从那以后我每次训练完小样本数据集都会先跑一遍增强后的验证集推理确认模型不是只在原始测试集上表现好。希望这份拆解能帮你在食品检测场景上少走一段弯路把273张图的每一张都用到刀刃上。本文还有配套的精品资源点击获取