简介:面向图像分类任务的学习者与研究者,这套快餐食物图像数据集涵盖十种常见类别:烤土豆、汉堡、炸鸡、甜甜圈、薯条、热狗、披萨、三明治、墨西哥玉米卷和塔可。每张图片均带有明确类别标签,可用于训练和验证 CNN、迁移学习等视觉分类模型,也适合构建小型图像识别演示项目。压缩包内共两千个文件,以1998张 JPG 标注图像为主,并附有 Python 脚本与 JSON 映射文件,便于快速划分数据和读取标签;包体约 483.48MB,目录结构清晰,解压即可使用。已有 381 人学习并下载。借助附带脚本,可直接完成数据集拆分、标签映射和预处理流程,适用于图像识别教学实验、快餐品类自动识别原型及小型竞赛数据支撑,能显著缩短从数据到模型验证的时间;在实际场景中,也可为自助点餐识别、食品质量抽查和饮食习惯分析等应用提供基础数据。
1. 做数据集之前,先说清楚为什么需要它
做图像分类项目的人,十有八九都卡在数据上。我一开始做快餐食物识别,最先想到的是去 Food-101 这类公开数据集里找现成的,结果发现两个问题:一是类目太多太杂,训练的模型在手机上跑不动;二是很多图片带着浓重的背景干扰,桌子上的餐具、饮料、甚至人手都拍进去了,模型学到的是"场景"而不是"食物"。后来我又试过用网络爬虫自己抓图,结果抓回来一堆带水印的、卡通化的、甚至完全和类别挨不上边的图,清洗成本比训练成本还高。
折腾了一圈之后,我决定干脆自己动手做一个"10 种常见快餐食物图像分类数据集"。这个数据集的目标很明确:只保留快餐场景中最高频的 10 类食物,每类图片数量均衡,背景干净,标注统一,能直接用于图像分类模型训练,也能无缝衔接到 YOLOv8 这类目标检测框架上做二次标注。一共收集了近 12000 张图片,每类约 1100 到 1200 张,已按 8:1:1 划分好训练集、验证集和测试集。
这篇文章就把我整个构建流程、踩过的坑、以及用这个数据集训练分类模型的实际效果完整记录下来。希望给正在做图像分类、或者正准备"用 YOLOv8 训练自己的数据集"的朋友一些可复用的经验。无论是做外卖热量估算、餐饮门店的出品检测,还是做智能推荐,这套思路和流程都能直接迁移过去。
2. 类别选型:10 类快餐食物到底怎么定
2.1 选类标准:高频消费、视觉可区分、场景单一
数据集好不好用,类别定义是第一步,也是最容易被忽略的一步。我当时定这 10 类食物,不是拍脑袋选的,而是按三条标准筛选出来的。
第一,消费频次要高。数据集的最终价值要体现在实际业务里,如果选一些冷门菜品,模型的落地价值就大打折扣。汉堡、薯条、披萨、炸鸡、热狗这些,是快餐店菜单上永远雷打不动的常青款。第二,视觉上要有区分度,但也不宜过于简单。这样训练出来的模型才真正具备图像分类能力,而不是靠颜色死记硬背。比如"汉堡"和"三明治"如果定义不清,很容易让标注的人纠结,也会让模型的分类边界模糊。第三,图片获取难度要可控。部分小众食物在开源图库中的有效图片量太少,就算强行入选,类别不平衡的问题也会让后续训练非常痛苦。
最终我选定的 10 类分别是:汉堡(burger)、薯条(fries)、披萨(pizza)、炸鸡(fried chicken)、热狗(hot dog)、三明治(sandwich)、炸鸡块(chicken nuggets)、墨西哥卷饼(burrito)、炒饭(fried rice)、沙拉(salad)。这 10 类覆盖了西式快餐和中式快餐的高频单品,也保留了"炸鸡"和"炸鸡块"这种视觉接近、容易混淆的组别。
2.2 容易混淆的类别,正是数据集的价值所在
如果你只是把完全不相干的 10 类食物放在一起,模型随便练练都能到 95% 以上的准确率,但这样的数据集几乎没有实战价值。我做数据集的时候,故意保留了几组"死亡组合"。
最典型的是汉堡和三明治。两者的视觉结构非常相似,都是面包夹馅料,区别在于面包类型、馅料层次和整体轮廓。还有一个容易出问题的是炸鸡和炸鸡块,一个是带骨或不带骨的炸鸡块件,一个是小颗粒的鸡块,拍摄角度一变,人眼都要仔细分辨。正是因为这些组合有难度,模型在训练后才能学到更细粒度的纹理、轮廓和色彩特征,而不是靠"有个圆形面包"这种粗粒度特征去分类。
我在实际标注过程中也发现,类别定义必须落到文字上,要形成一份标准化的标注规范给标注员使用。比如"三明治"明确为两片面包片夹馅料,而"汉堡"必须是带有汉堡胚(通常是带芝麻的圆面包)的品类。有了这个规范,后续数据清洗时就不会凭感觉删图、改图。规范的建立,比多拉几百张图片更有价值。
3. 数据采集与清洗:最脏最累,但决定数据集上限
3.1 多渠道采集方案与取舍
数据采集我用了三个渠道:开源数据集筛选、图片搜索引擎批量下载、自己拍摄实景补足。开源数据集方面,我主要从 Food-101 里筛选出与这 10 类匹配的子集,同时从 Kaggle 上找了几个单个食物的分类数据集做补充。图片搜索引擎方面,我写了一个基于 Python 的爬虫脚本,用关键词加"快餐""外带""纸盒包装"等限定词,批量下载高清大图。自己拍摄的部分占比不大,但很重要,主要是用来补足那些"俯拍视角""外卖盒包装"等网络图上比较少见的角度。
三个渠道的比例大概是 6:3:1。完全依赖开源数据会继承它本身的噪声,完全依赖爬虫又会陷入版权和质量的泥潭,所以混合采集效果好一些。顺手说明一下,如果只是自用学习,网络图片问题不大;如果要商用,建议优先用自己拍摄的图片,或者严格筛选 CC0 协议的图源,版权红线必须心里有数。
3.2 清洗规则:删图比找图更考验判断力
我清洗图片的时候,制定了一套非常机械的淘汰规则。只要命中任意一条就直接删除:分辨率低于 300×300 的;带有明显水印、Logo 或网络平台角标的;图中包含多种食物且没有明确主体目标的;卡通、手绘、3D 渲染风格的;食物被遮挡超过一半以上的;以及明显是重复图的。
这里面最容易被忽略的就是"多物体混杂"的情况。比如一张图里有汉堡、有薯条、还有一杯可乐,人眼能识别出汉堡,但图像分类数据集在制作时,最好只保留包含单一主体的图片。原因很简单,分类器的训练范式是"一张图对应一个标签",如果图中出现两个主体,模型学到的特征就会混乱。这也是我要强调的一点:图像分类数据集的"干净",指的不是画质有多高,而是"画面语义和标签必须高度一致"。
清洗流程上,我先用脚本做初筛,把分辨率不合格、格式异常的图片自动删除,再人工快速浏览一遍剩余的图片。12000 张图人工浏览会花掉好几个小时,但这一步无论如何不能省。第一次清洗后,我保留了约 12500 张,再经过特征向量去重和二次人工筛选,最后稳定到接近 12000 张。
4. 数据集结构设计与标注规范
4.1 目录结构:直接对标 ImageFolder 格式
为了让数据集开箱即用,我最终采用了两套结构。第一套是纯分类格式,就是 PyTorch 的 ImageFolder 标准目录结构,方便用 torchvision 直接加载。第二套是为目标检测预留的 YOLO 格式目录,只是在原始图片基础上放了占位的标注文件,方便后续用 YOLOv8 训练目标检测模型时,直接在同一个数据集上扩展。
分类格式的目录结构如下:
fastfood10/ ├── train/ │ ├── burger/ │ ├── fries/ │ ├── pizza/ │ ├── fried_chicken/ │ ├── hot_dog/ │ ├── sandwich/ │ ├── chicken_nuggets/ │ ├── burrito/ │ ├── fried_rice/ │ └── salad/ ├── val/ └── test/每类图片在三个子集中的数量比例控制在 8:1:1,而且划分的时候要保证同一个来源的图片不会同时出现在训练集和验证集中。比如某个连锁品牌的宣传图抓下来 30 张,这 30 张在划分时需要整体归入同一个子集,否则会出现严重的数据泄漏。验证集和测试集的准确率看起来很高,实际上一上线就掉点,多半是因为评估时"见过"这些图片了。
4.2 标注格式:分类标签的隐性坑
分类数据集的标注看起来最简单,就是"文件夹名字就是标签",但实际操作时有几个细节很坑。一是文件名不要用中文,也不要用特殊符号,避免在 Windows 和 Linux 之间迁移时编码出问题。我统一用"类别名_序号.jpg"的格式,比如 burger_0001.jpg。二是标签映射表要单独存一个 JSON 文件,把类别名和数字 ID 的对应关系固定下来。因为不同的训练脚本会对类别按字典序排序,如果映射表没有固化,每次跑完模型,predict 的结果对应到具体类别时,很容易对错号。
标签映射表我放在数据集根目录下,内容就是这样一个 JSON:
{ "0": "burger", "1": "burrito", "2": "chicken_nuggets", "3": "fried_chicken", "4": "fried_rice", "5": "fries", "6": "hot_dog", "7": "pizza", "8": "salad", "9": "sandwich" }这个表看似不起眼,但能帮你省掉无数"模型准确率很高,但部署的时候预测结果对不上"的烦恼。我在做模型部署的时候,就吃过这个亏,后来才养成了每个数据集都固化映射表的习惯。
5. 用这个数据集训练图像分类模型:从 ResNet 到 YOLOv8 的思路
5.1 分类模型训练的参数配置
数据集就绪后,我先用 ResNet50 跑了一版图像分类基线模型。加载方式直接用了 torchvision 的 ImageFolder 接口,配合标准的 ImageNet 均值和方差做归一化。训练策略上,输入尺寸统一为 224×224,Batch Size 设为 64,初始学习率 0.001,采用 Cosine Annealing 的调度策略,训练 60 个 epoch。数据增强我用了随机裁剪、随机水平翻转、随机旋转 15 度和颜色抖动。这项配置比较常规,但实际测下来效果不错。数据增强的作用在食物数据集上体现得尤其明显,因为实拍场景中食物的摆放角度、拍摄距离、光线条件差异很大,增强相当于免费扩充了样本空间。
迁移学习部分是重点。我用了在 ImageNet 上预训练好的权重,冻结了前几层,只微调最后几个残差块和全连接层。这里有一个很实用的心得:食物图像和 ImageNet 的自然图像存在较大的分布差异,所以冻结层数不宜太多。我测试过冻结 BN 层所有参数的做法,结果收敛速度明显变慢,后来改成所有层都能训练,只对骨干网络部分采用较小的学习率(骨干网络学习率乘 0.1),收敛效果就好了很多。
5.2 训练结果与混淆分析
最终在测试集上,ResNet50 的 Top-1 准确率到了 93.7%。分类效果最差的几组组合,果然早就在意料之中:汉堡和三明治互相混淆,炸鸡和炸鸡块互相混淆。所以我又用 YOLOv8 的目标检测思路做了一次对比实验。严格来说,目标检测和纯图像分类不是同一个任务,但 YOLOv8 也支持分类头。我在同样的数据集上用 YOLOv8-cls 跑了一遍,默认参数下准确率到了 94.2%,收敛速度比 ResNet50 快了不少。
从混淆矩阵里提取了两个改进方向:一是增加汉堡剖面图的样本量,因为很多混淆图片都是汉堡的俯视图与三明治的俯视图高度相似;二是对炸鸡块这类小目标食物,需要保证拍摄图片中食物的面积占比足够大。这些结论反过来又指导了数据集的迭代,形成了一个"训练—分析—补数据—再训练"的闭环。
我用 YOLOv8 训练分类模型的命令行配置也贴出来,方便你直接参考:
yolo classify train data=fastfood10 model=yolov8n-cls.pt epochs=60 imgsz=224 batch=64换成自己的数据集时,只需要把 data 参数指向你的数据集根目录,模型会自动识别 train、val、test 子文件夹。中文路径务必不要出现,否则会报各种奇怪的读取错误。
6. 常见问题与排查技巧实录
6.1 训练时图片读取报错
一次训练中频繁出现"PIL.UnidentifiedImageError: cannot identify image file"的报错,排查后发现网上爬下来的图片中有少量文件后缀是 .jpg,但实际是 WebP 或者 GIF 格式。这类图片单独打开看没问题,但批量加载时就出问题了。解决方法是写一个脚本,用 Pillow 把所有图片重新解码并转成标准 RGB 格式的 JPEG,统一编码、统一后缀,彻底解决格式混乱问题。
from PIL import Image import os root_dir = "fastfood10" for split in ["train", "val", "test"]: for cls_name in os.listdir(os.path.join(root_dir, split)): cls_dir = os.path.join(root_dir, split, cls_name) if not os.path.isdir(cls_dir): continue for fname in os.listdir(cls_dir): fpath = os.path.join(cls_dir, fname) try: img = Image.open(fpath) img = img.convert("RGB") new_name = os.path.splitext(fname)[0] + ".jpg" img.save(os.path.join(cls_dir, new_name), "JPEG") if new_name != fname: os.remove(fpath) except Exception as e: print(f"损坏图片: {fpath}, 错误: {e}") os.remove(fpath)6.2 验证集准确率高但测试集掉点
出现这种情况,十有八九是数据划分方式出了问题。我在第一次划分时就是随机划分的,结果同一个来源的连拍图被分到了训练集和验证集,导致验证集分数虚高。后来我改成"按来源分组"的划分方式,先把图片按 URL 的域名、文件名的前缀做聚类,再把整个组划入同一个子集,问题就解决了。做数据集的都知道一句话:验证集的意义是模拟真实场景中的分布,而不是和历史数据玩"猜谜游戏"。
6.3 类别不平衡的两个解法
虽然我刻意控制了每类图片数量在 1100 张以上,但在清洗过程中还是有个别类别掉到了 900 张左右。解决思路有两个:一是给图片少的类别加大采样权重,在 DataLoader 中设置 WeightedRandomSampler;二是对少数类做更强的数据增强,比如增加随机遮挡和 MixUp。两个方法我都试过,MixUp 的效果更明显,尤其是在炸鸡块这类纹理复杂、样本量偏少的类别上。不过 MixUp 会让训练的损失值看起来偏高,初期容易被误判为模型不收敛,这点要提前有心理准备。
6.4 图片重复率过高的问题
爬虫抓回来的图片里经常出现高度相似甚至一模一样的图,有些是不同网站互相转载,有些是同一条新闻里截出来的不同帧。如果这些重复图同时出现在训练集和测试集里,测试准确率会被泡沫式抬高。我用了感知哈希算法(pHash)做去重,把每张图缩放成 32×32 后计算哈希值,两两之间汉明距离小于 5 的视为重复图,只保留其中一张。实测下来,12000 张图里去掉了将近 900 张近似重复图,数据集质量提升非常明显。
7. 这个数据集后续的扩展空间
数据集的构建不是一锤子买卖,它是一个可以持续迭代的基础设施。目前这个 10 类版本主要用于图像分类任务的快速原型验证,下一步我打算在同样的数据基础上做目标检测标注,用 YOLOv8 训练一个能同时定位和分类的模型。前面说过,我的数据集目录里已经预留了 YOLO 格式的标注文件位置,只需要用 LabelImg 或 Roboflow 补画边界框即可。相比从零做检测数据集,这个成本低很多。
还有一个有价值的方向是数据增强的净化处理。快餐食物的拍摄非常受包装影响,同一个汉堡在纸袋里、在托盘上、在手持状态下,视觉特征差异很大。可以考虑用背景替换或者风格迁移来进一步增强模型的泛化性,但目前还在实验中。就现阶段的效果来看,这个 10 类快餐食物数据集在图像分类任务上已经能支撑起一个可演示、可部署的模型了。
最后再分享一个小技巧:数据集的版本管理一定要做。我没用专门的工具,就是在数据集目录里放了一个 dataset_info.json,记录每个版本的图片总数、类别分布、采集日期、清洗规则和已知问题。每次迭代数据集就更新这个文件。别小看这一步,过两个月再回头看,你会感谢当初记下来的这些细节。
本文还有配套的精品资源,点击获取