简介:常见果蔬多类别图像分类数据集包含约4200张已标注图片,覆盖香蕉、苹果、梨、葡萄、橙子、黄瓜、胡萝卜、辣椒、洋葱、土豆等36个常见类别,适用于图像分类、目标识别及相关网络改进实验。数据已完成预处理,划分训练集、测试集、验证集,并按同一类别集中存放,可直接作为ResNet、VGG、EfficientNet等分类网络的输入,免去数据清洗与格式转换环节。压缩包共2000个文件,主体为1998张JPG图像,另附1个JSON类别映射文件与1个Python可视化脚本,借助show脚本可快速浏览各类别样本,核对标注质量与类别平衡性。包体大小约364.87MB,目录结构规整,便于配置训练与验证路径。已有119人学习下载,适合需要开展多类别果蔬识别、分类网络结构优化或数据增强实验的开发者与研究者,能够显著降低数据准备门槛,帮助聚焦模型设计与性能分析。
1. 图像分类练手先别急着调模型:这份36类果蔬标注数据集能省掉最磨人的数据准备
做果蔬图像分类,最耗时间的往往不是模型结构,而是数据。随手从网上下一个图片集,经常没标注、类别混杂、背景乱来,光清洗和整理就得耗掉一整天。这份约4200张的常见果蔬多类别数据集,把最磨人的活干完了再交付:36个类别,覆盖香蕉、苹果、梨、葡萄、橙子、黄瓜、胡萝卜、辣椒、洋葱、土豆等日常果蔬,已经标注完成并做过预处理,画好了训练、验证、测试三个子集,还带一个show脚本方便查看数据内容。对正在做毕设、跑入门图像分类实验、或者验证分类网络改进效果的人来说,这份资源拿过来就能直接接进PyTorch或TensorFlow的训练流程,省掉从零整理标注数据的时间,把精力放到真正的模型实验上。
2. 读懂数据集结构与标注体系:从文件夹到json的完整映射
2.1 三个子集的分工:训练集、验证集、测试集为什么必须分开
这份数据集在目录组织上有一个很关键的设计:把数据按训练、验证、测试三个用途划分好,各自存放同一类别的图片。常见的目录结构是这样的:
dataset/ ├── train/ │ ├── banana/ │ ├── apple/ │ ├── pear/ │ └── ... ├── val/ │ ├── banana/ │ ├── apple/ │ └── ... ├── test/ │ ├── banana/ │ ├── apple/ │ └── ... └── labels.json训练集用来更新模型权重,验证集用来在训练过程中做超参数选择和早停判断,测试集只在全部实验定稿后评估最终性能。这三个子集的分工不能混,尤其是测试集,我见过不少初学者把测试集也拿去调参,最后报告的准确率虚高得离谱,部署到真实场景马上现原形。
2.2 json标注文件里到底写了什么
数据集的标注信息集中在labels.json里,这是整个资源的索引核心。拿到数据集后的第一步不是直接开训,而是先把这个json读出来,搞清楚每个图片文件对应的类别标签和归属集合。常见的组织方式是一个字典或数组,包含文件名、类别ID、类别名、集合归属这几个字段。
import json from collections import Counter with open("labels.json", "r", encoding="utf-8") as f: labels = json.load(f) # 统计每个类别的样本数 train_counter = Counter() test_counter = Counter() val_counter = Counter() for item in labels: split_name = item["split"] # train / val / test class_name = item["class_name"] # 类别名,如 "banana" if split_name == "train": train_counter[class_name] += 1 elif split_name == "val": val_counter[class_name] += 1 else: test_counter[class_name] += 1 print("训练集类别数量:", len(train_counter)) print("训练集总量:", sum(train_counter.values())) print("验证集类别数量:", len(val_counter)) print("测试集类别数量:", len(test_counter))这段代码的关键在于分清json里的字段含义。“split”字段定义了当前图片属于哪个子集,“class_name”是给人看的类别名,真正给模型用的是类别ID。统计完你会发现36个类别在三个子集里都可能出现,但各自的数量不会完全一样,这很正常。
2.3 类别分布与样本量:36类都不均衡吗
果蔬图像分类的难点和通用物体分类不太一样,同类果蔬的外观差异非常大,一根香蕉可以是青的、黄的、带黑斑的,一个苹果可以泛红泛绿泛黄。类别之间的样本量也很难做到完全平均,某些类可能三百多张,某些类可能只有一百张左右,这种不均衡是真实的采集场景造成的。
运行上面的统计脚本后,重点关注两件事:一是每个类别是否在三个子集中都有分布,二是最少的类别样本量是否撑得起训练。如果某个类在训练集里只有几十张,建议给这个类单独加数据增强,不要用全局统一的增强强度,否则这个类很容易欠拟合。另外要确认json里的类别列表和文件名是否一一对应,这里最常见的坑是类别索引和类别名字典顺序对不上,后面避坑章节会展开讲。
3. 用show脚本做数据体检:可视化这一步不能省
3.1 跑通show脚本
拿到数据集后我一般先跑一遍自带的show脚本,这个是了解数据真实样貌的最快方式。数据集经过预处理后可以直接作为分类网络输入,但“经过预处理”不代表每张图都是完美的,人工抽查仍然必要。
python show.py --data_dir dataset --split train --num_samples 16 --output grid.png脚本会从指定的子集里抽取图片,拼接成一张网格图输出到本地。参数对应关系如下:
- data_dir:数据集根目录,脚本从这里定位类别子文件夹
- split:要抽查哪个集合,建议三个集合都跑一遍
- num_samples:每个类别抽取多少张,值越大覆盖越全,但单张网格图会变挤
- output:输出图片路径
跑完这一步,你手上就有了一张能直观反映数据分布的拼图,不用盲目去翻文件夹。
3.2 可视化输出怎么判断:图像质量与标注正确性
网格图出来后,逐类看三件事:图像内容是否确实属于该类别、背景是否过于杂乱、图片分辨率是否差异过大。果蔬分类的标注错误率往往比想象中高,比如把青番茄标成青椒、把梨标成苹果,这类混淆在人工标注中很常见。
另一个值得关注的是图像背景。如果训练集里大量图片都是单一白底或者厨房台面,模型很容易学到背景特征而不是果蔬本身的纹理。可视化时看到某几类背景风格高度一致,就要考虑在训练时加入背景扰动,否则换到真实环境会掉点掉得厉害。
3.3 类别均衡性回看:哪些类容易欠拟合
可视化还能帮助判断类别均衡性。摊开网格图后,如果某一类引出的图片明显少于其他类,或者图片内容单一(全是同一角度、同一光照),这类就属于高风险类别。训练时在数据加载阶段给这类图片更高的采样权重,或者用更强的增强策略。“我一般会拿show脚本配合2.2小节的统计脚本一起看,统计负责数字,可视化负责图像内容,两边对不上就说明有脏数据。”
4. 接进PyTorch训练管线:数据加载器与增强参数怎么设
4.1 用ImageFolder两行代码加载三个子集
目录结构已经是规范的按类分子文件夹,所以用torchvision自带的ImageFolder就能直接加载,不需要自己写Dataset类。
from torchvision import datasets, transforms from torch.utils.data import DataLoader # 训练集:带增强 train_transform = transforms.Compose([ transforms.RandomResizedCrop(224), transforms.RandomHorizontalFlip(), transforms.ColorJitter(brightness=0.3, contrast=0.3, saturation=0.3), transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) ]) # 验证集/测试集:只做尺寸统一和标准化 eval_transform = transforms.Compose([ transforms.Resize(256), transforms.CenterCrop(224), transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) ]) train_dataset = datasets.ImageFolder("dataset/train", transform=train_transform) val_dataset = datasets.ImageFolder("dataset/val", transform=eval_transform) test_dataset = datasets.ImageFolder("dataset/test", transform=eval_transform) train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True, num_workers=4, pin_memory=True) val_loader = DataLoader(val_dataset, batch_size=64, shuffle=False, num_workers=4, pin_memory=True) test_loader = DataLoader(test_dataset, batch_size=64, shuffle=False, num_workers=4, pin_memory=True)这段代码里有几处要特别说一下。RandomResizedCrop会在随机位置裁一块区域再缩放到224,相当于给了模型不同尺度、不同位置的视角,对果蔬这种主体位置不太固定的图像很有效。ColorJitter的三组参数分别控制亮度、对比度和饱和度的扰动幅度,果蔬颜色是区分品类的重要特征,扰动范围不宜过大,0.3是个相对稳妥的起点。
4.2 数据增强的取舍:哪些transform适合果蔬图像
果蔬分类有个特殊点:颜色纹理是核心判别依据,但也不能只在颜色上做文章。全局增强我推荐坚持三条:
- 随机裁剪和翻转是基础盘,必加
- 颜色扰动要克制,饱和度扰动幅度控制在0.3到0.4上下即可,暴力调色会让苹果和梨更难分
- 随机遮挡或Cutout这类强增强,只对训练集样本量偏少的类别单独启用
常见的做法是先用基础增强跑一版,然后盯着验证集准确率曲线判断是否需要加强。如果训练集loss持续下降但验证集不涨,说明增强强度不够;如果两边同时卡住,才考虑更激进的手段。
# 对样本量少的类别,单独启用一个更强的transform augment_transform = transforms.Compose([ transforms.Resize(256), transforms.RandomCrop(224), transforms.RandomHorizontalFlip(), transforms.RandomRotation(15), transforms.ColorJitter(brightness=0.4, contrast=0.4, saturation=0.4, hue=0.1), transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) ])注意这里的RandomRotation配上15度可以让黄瓜、胡萝卜这类有朝向差异的类别更鲁棒,但角度不宜再加大,转成竖着的香蕉就不对了。
4.3 训练超参设定与评估口径
果蔬36分类属于中等粒度识别任务,不算太难也不算简单。用ImageNet预训练的模型做迁移学习,初始学习率设在1e-3到3e-3之间,batch_size按显存尽量调大,冻结backbone先训5到8个epoch,然后解冻全部参数把学习率降到1e-4到5e-5微调10到15个epoch,这是最稳的套路。
验证集在训练过程中扮演的角色是早停判断。每个epoch结束都跑一遍验证集,记录loss和top-1准确率,连续5个epoch验证loss不降就果断回调checkpoint。测试集压轴,最终模型只评估一次,这个口径要从头守到尾。
import torch # 假设 model 已经是训练好的模型 def evaluate(model, loader): model.eval() correct = 0 total = 0 with torch.no_grad(): for images, labels in loader: images, labels = images.cuda(), labels.cuda() outputs = model(images) _, predicted = torch.max(outputs, 1) total += labels.size(0) correct += (predicted == labels).sum().item() return 100.0 * correct / total val_acc = evaluate(model, val_loader) test_acc = evaluate(model, test_loader) print(f"验证集准确率: {val_acc:.2f}%") print(f"测试集准确率: {test_acc:.2f}%")验证集可以反复跑来看不同checkpoint的表现,测试集只能碰一次,这是图像分类实验的铁律。如果验证集和测试集结果差距超过两个百分点,优先怀疑是数据分布偏移,不要急着换模型架构。
5. 果蔬分类避坑指南:这五条踩坑记录值得提前看
5.1 踩坑一:json里的类别顺序和文件夹顺序对不上
现象:训练时准确率起不来,loss下降缓慢,打印预测结果发现模型把香蕉的图片识别成第5类但显示的名字是葡萄。
原因:json里类别列表的索引和os.listdir扫描文件夹得到的顺序不一致,直接用列表索引当标签ID喂给模型,导致真实标签和文件名错位。
解决:永远不用列表索引做标签映射。读json时以class_name为键、category_id为值构造dict,再把ImageFolder的class_to_idx映射反向对齐到这份dict上。
5.2 踩坑二:辣椒和青椒这类相似类别互相混淆
现象:训练集准确率到92%之后怎么调都上不去,打开混淆矩阵发现“辣椒”和“胡萝卜”、“苹果”和“梨”这两组错误集中。
原因:相似类别在纹理上高度重叠,全局增强没针对性,模型抓不住细粒度差异。
解决:先跑一个baseline得到混淆矩阵,锁定最难分的类别对,单独给这些类加细粒度增强(随机crop小区域放大、锐度调整),必要时加一层特征约束。
5.3 踩坑三:训练集背景单一导致验证集虚高
现象:验证集准确率95%,把模型放到自己拍的图片上预测,结果一塌糊涂。
原因:数据集的图像主体居中、背景干净,模型学到了“背景=类别”的捷径,一旦背景变化就失效。
解决:训练时加入RandomResizedCrop+RandomErasing,用随机遮挡强迫模型关注主体,同时验证阶段额外准备一批自采图片做压力测试。
5.4 踩坑四:预训练权重的标准化参数被改掉
现象:用迁移学习做初始化,训练了十几个epoch验证集准确率还卡在60%以下。
原因:数据归一化用了自己的mean/std,和ImageNet预训练权重要求的[0.485, 0.456, 0.406]不一致,导致预训练特征全部白费。
解决:凡是加载torchvision预训练权重,transform里的Normalize必须用对应的mean和std,不能拿自己的数据集统计量去套。用自己统计的归一化参数只适用于从零训练的场景。
5.5 踩坑五:show脚本在图片尺寸差异较大时布局错乱
现象:运行show脚本报错,或者输出的网格图里图片被拉伸变形。
原因:部分原始图像的宽高比差异过大,脚本里固定的格子尺寸没有做自适应缩放。
解决:在show脚本的可视化函数里先做一次等比缩放再填充到网格,用PIL的thumbnail方法保准不会变形。这是改脚本时最省事的处理方式。
6. 用混淆矩阵做一次完整回看:验证集结果的三个检查点
训练结束后,光看准确率远远不够,拿验证集跑一次混淆矩阵,才能看清模型到底在哪些类别之间混淆。用sklearn一行就能算出来:
from sklearn.metrics import confusion_matrix import numpy as np # 收集验证集预测结果 all_preds = [] all_labels = [] with torch.no_grad(): for images, labels in val_loader: images = images.cuda() outputs = model(images) _, preds = torch.max(outputs, 1) all_preds.extend(preds.cpu().numpy()) all_labels.extend(labels.numpy()) # 计算混淆矩阵 cm = confusion_matrix(all_labels, all_preds) # 归一化,方便跨类别比较 cm_norm = cm.astype("float") / cm.sum(axis=1, keepdims=True) # 找出错误最集中的类别对 np.fill_diagonal(cm_norm, 0) row, col = np.unravel_index(np.argmax(cm_norm), cm_norm.shape) print(f"最容易混淆的类别对:真实类别{row},被预测成{col},比例{cm_norm[row, col]:.2%}")拿到混淆矩阵后我有三个固定检查点:第一看对角线占比,归一化后每个类别的正确识别率是否齐平,有没有某个类明显偏低;第二找非对角线上的峰值,找出混淆对,重点排查是标注噪声还是特征相似;第三回到show脚本把错分样本本身打出来看,一张张过一遍,判断错在数据还是错在模型。如果是同一种标注错误反复出现,说明标注阶段有系统性问题,需要处理标签后再训练。
从那以后我每次跑完一个分类实验,都会强制走一遍这轮“混淆矩阵+错分样本回看”流程,宁可花半小时看图片,也不在多轮训练后才发现数据有问题。这数据集本身质量已经不错了,但自己的训练流程是否规范,才是决定结果可信度的关键。希望帮到你。
本文还有配套的精品资源,点击获取