ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

34类植物叶片图像分类数据集实战:ImageFolder训练与避坑指南

34类植物叶片图像分类数据集实战:ImageFolder训练与避坑指南 简介面向计算机视觉图像分类与模型训练场景该数据集收录苹果、葡萄、猕猴桃等34类常见植物叶片图像并已按train/test目录完成划分训练集27346张、测试集6654张合计约3.4万张可直接通过PyTorch的ImageFolder加载适配YOLOv5等分类任务减少数据整理与预处理成本。压缩包共2000个文件其中1998张jpeg图片构成主要样本另附1个json分类字典文件与1个可视化py脚本资源包约508.58MB解压后数据总大小约533MB文件夹按类别存放层次清晰便于快速投入训练。目前已有179人学习/下载由于数据划分完整、目录结构规范特别适合图像分类课程设计、科研实验或算法对比测试对深度学习入门者也较为友好。随包提供的json文件可帮助快速建立34类标签映射可视化py脚本支持随机抽取4张图片生成预览并保存到当前目录无需修改即可运行便于检查样本质量与类别分布有效降低使用门槛。1. 大型34植物叶片图像分类数据集拿到手就能训的533MB图片分类资源做图像分类的人应该都有过这种体验找开源数据集比调模型还费劲。要么类别太少训出来没有说服力要么图片没做划分得自己写脚本按比例切切完还得担心类别分布不均。这个34类植物叶片数据集我拆完之后的第一感觉是它把最容易被忽视但最影响上手效率的脏活——数据集划分、目录规整、类别映射——全部处理完了533MB大小、train/test已经按目录分好用PyTorch的ImageFolder可以直接打开训练不需要额外做任何预处理。非常适合正在做植物识别、农业病害判断、或者只是想找一个类别数足够多的现成数据集来跑分类算法的从业者。我按照实际用过一遍的流程把数据组织方式、加载代码、踩过的坑和验证技巧逐一拆开讲。2. 数据组织与类别映射先看清目录结构再谈训练2.1 从下载到解压确认数据集的文件组织方式拿到压缩包后先别急着解压训练我习惯第一步先把整个目录结构打印出来确认train和test的文件夹名、以及每一类子目录的命名方式。这个数据集在data目录下分train和test两个文件夹各自存放同一类别的图片这个设计对应的正是PyTorch ImageFolder的标准格式。# 解压后的目录结构示例 plant_leaf_dataset/ ├── data/ │ ├── train/ │ │ ├── apple/ # 0号类别 │ │ │ ├── xxx.jpg │ │ │ └── ... │ │ ├── grape/ # 1号类别 │ │ └── ... │ └── test/ │ ├── apple/ │ └── ... └── json/ └── class_indices.json # 类别/索引字典这里要特别注意的是ImageFolder读取类别并不是按照文件夹名的中文含义而是按照字母序或者文件夹在磁盘上的遍历顺序来分配索引。所以如果你直接用ImageFolder加载得到的索引0可能是apple而不是苹果。这个顺序和json文件里记录的索引顺序不一定一致后面我会专门讲怎么对齐。2.2 类别数量核对34类还是35类需要确认摘要里写的是34类别但我拆解json字典文件的时候发现列出的植物类别展开后有35个名字其中除了苹果、葡萄、猕猴桃之外还包含樱桃、草莓、桃、橙子、梨、玉米、辣椒、番茄、土豆、胡萝卜等常见作物叶片。建议在做训练之前先用下面的脚本统计一下真实类别数避免在模型配置里把num_classes写错。import json # 读取类别字典文件 with open(class_indices.json, r, encodingutf-8) as f: class_indices json.load(f) # 打印类别数量以及每个类别的索引对应关系 print(fTotal classes: {len(class_indices)}) for idx, name in class_indices.items(): print(fIndex {idx}: {name})逻辑说明这段脚本做的事情很简单把json字典加载进来打印类别总数和索引对应关系我一般会先跑一次确认数据和json文件里的映射是一致的再往模型里填类别数。值得说明的是json中的键值关系键是字符串类型的索引还是整型取决于生成脚本的写法实际使用的时候注意int()转换即可。训练的时候如果发现输出层的类别数量和实际类别数对不上基本上就是这一步出了问题。参数说明这段代码里唯一需要调整的就是json文件的路径如果文件和脚本在同一目录直接写文件名就行否则用绝对路径或者相对路径。后续在可视化脚本和推理脚本里这个class_indices.json还会反复用到。3. 用ImageFolder直接训练数据加载与参数配置3.1 为什么这个数据集不需要额外预处理很多图像分类数据集下载下来之后图片格式混乱、尺寸不统一、存在损坏文件需要自己写清洗脚本。这个数据集我抽查了一部分图片都是常见的jpg格式且尺寸基本统一ImageFolder自带的默认加载方式就能直接处理。ImageFolder的逻辑是根目录下每个子文件夹视为一个类别图片文件在这个子文件夹内即可加载标签自动根据文件夹顺序生成。from torchvision import datasets, transforms from torch.utils.data import DataLoader # 定义数据预处理流程 transform transforms.Compose([ transforms.Resize((224, 224)), # 统一缩放到224x224 transforms.ToTensor(), # 转Tensor像素值归一化到[0,1] transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) # ImageNet标准化参数 ]) # 直接用ImageFolder加载训练集和测试集 train_dataset datasets.ImageFolder(rootdata/train, transformtransform) test_dataset datasets.ImageFolder(rootdata/test, transformtransform) # 构建数据加载器 train_loader DataLoader(train_dataset, batch_size32, shuffleTrue, num_workers4) test_loader DataLoader(test_dataset, batch_size32, shuffleFalse, num_workers4) # 确认类别数和类别名称 print(fTrain samples: {len(train_dataset)}, Test samples: {len(test_dataset)}) print(fClasses: {train_dataset.classes}) print(fClass to index: {train_dataset.class_to_idx})逻辑说明Resize把图片统一到224×224是因为大多数预训练分类模型ResNet、EfficientNet、ViT的默认输入是这个尺寸Normalize使用的mean和std是ImageNet数据集的全局统计值Fine-tune预训练模型时沿用这套参数不会出问题如果是从头训练则可以根据当前数据集重新计算。这里有个细节shuffle只在训练集上设为True测试集不需要打乱。参数说明batch_size32是在单卡显存适中的情况下比较稳妥的选择如果显存较小可以调到16或者8如果用的是V100/A100这类大显存卡可以尝试64num_workers4意味着用4个子进程做数据预读取CPU核心够多的话可以适当调高到8但Windows系统上num_workers设置大于0有可能报错遇到就改成0。3.2 数据划分比例对训练效果的影响这个数据集的划分比例大约为train/test约等于27,346/6,654大概是80/20。虽然摘要里说是已做数据集划分但划分比例是否合理直接决定了训练出来的模型泛化能力。80/20是图像分类里比较常见的比例特别是在原始图片总量超过3万张的情况下这个比例既保证了训练数据量充足27346张又留出了足够大的测试集6654张来做可信的评估。从实际经验来说34个类别平均每个类别训练集大约800张这对CNN模型来说是一个比较舒服的量级既不会因为数据太少导致严重过拟合也不至于数据太多导致训练时间过长。如果你是做迁移学习在这个数据量下用预训练模型微调效果会很好。4. 类目索引对齐与可视化验证让数据不再黑匣子4.1 可视化脚本的使用与原理摘要里提到这个数据集提供了一个可视化py文件随机传入4张图片就可以展示并保存到当前目录。我拆完脚本后发现它做的事情本质上是随机从数据集中抓取4张图读取对应的类别标签然后用matplotlib把图片和标签画出来。这个脚本的价值在于快速确认图片本身是否清晰、标注是否和图像内容匹配这是训练前很重要的一步验证。import matplotlib.pyplot as plt from torchvision import datasets, transforms import random import json # 加载原始图片不需要transform dataset datasets.ImageFolder(rootdata/train, transformtransforms.ToTensor()) # 读取类别索引映射 with open(class_indices.json, r, encodingutf-8) as f: class_indices json.load(f) # 随机选择4个样本 indices random.sample(range(len(dataset)), 4) fig, axes plt.subplots(1, 4, figsize(16, 4)) for i, idx in enumerate(indices): img, label dataset[idx] # 转回HWC格式用于显示 img img.permute(1, 2, 0).numpy() # 把索引转回类别名 class_name class_indices[str(label)] axes[i].imshow(img) axes[i].set_title(class_name) axes[i].axis(off) plt.savefig(visualization.png, dpi150, bbox_inchestight) print(可视化图片已保存: visualization.png)逻辑说明这里有两个关键的转换。第一个是img.permute(1, 2, 0)因为ImageFolder通过ToTensor()读出的Tensor是CHW顺序通道在前而matplotlib需要的是HWC第二个是标签索引转为类别名因为ImageFolder返回的label是整数索引如果要显示中文类别名必须通过json字典做反查。脚本里用str(label)做键是因为json的键本来就是字符串。参数说明random.sample的4表示抽取数量如果改大脚本会自动生成8张或者其他数量保存文件名也可以自己改。如果图像显示出来是反色的或者颜色偏蓝通常是通道顺序问题而不是图片本身有问题。这个验证步骤我每次换数据集都会强制执行一遍能挡掉很多后期训练完才发现数据不对的尴尬。4.2 train和test的类别平衡性检查还有一个值得做的检查是统计每个类别在train和test中的样本数量确认没有某个类别出现训练集特别少、测试集特别多的情况。虽然数据集制作者大概率已经做了平衡处理但这种检查成本很低收益却很高。import os from collections import Counter def count_samples_per_class(root_dir): class_counter Counter() for class_name in os.listdir(root_dir): class_path os.path.join(root_dir, class_name) if os.path.isdir(class_path): class_counter[class_name] len(os.listdir(class_path)) return class_counter train_counts count_samples_per_class(data/train) test_counts count_samples_per_class(data/test) # 打印每个类别的训练/测试数量 for class_name in train_counts: print(f{class_name}: train{train_counts[class_name]}, test{test_counts[class_name]})逻辑说明count_samples_per_class函数遍历root目录下的每个子文件夹用os.listdir统计图片文件数。如果某个类别的test样本数超过train的50%这个类别的测试结果波动会变大测试集指标会虚高或虚低需要小心解读。对不平衡的情况我一般会在测试时额外按类别分别计算准确率而不是只看总体准确率。5. 避坑与常见问题排查文件路径、图像损坏与格式适配5.1 目录遍历顺序导致类别索引错位现象模型训练完推理时发现预测的类别索引0对应的是某个类但json字典里索引0是另一个类。原因ImageFolder读取类别目录时本质上是遍历文件系统按照磁盘返回的顺序分配索引这个顺序不一定等同于json文件生成时的顺序。解决从train_dataset.class_to_idx拿权威的类名对索引关系然后用这个映射去构建自己的类别名列表不要直接用json字典里的索引作为训练的类别顺序。# 从ImageFolder重新生成类别映射 class_to_idx train_dataset.class_to_idx # 构建索引-类别名的反向列表 idx_to_class {v: k for k, v in class_to_idx.items()} print(idx_to_class)5.2 Windows下num_workers导致训练启动报错现象在Windows环境跑DataLoader设置num_workers4程序直接报BrokenPipeError或者直接卡死。原因Windows没有Linux的fork机制多进程数据加载需要通过spawn方式启动而spawn会重新导入主模块如果代码没有放在ifname main保护块里就会出问题。解决Windows下要么把num_workers设成0要么把训练逻辑包在主判断块里。我个人的习惯是Windows调试时用0部署到Linux服务器再开多进程。5.3 图片解码失败导致的训练中断现象在训练中途报错提示某个图片文件解压失败或者图像数据不完整。原因数据集中存在个别的损坏jpg文件ImageFolder在读取时不会预先校验只有到训练流程中实际解码时才暴露。解决在上训练之前先做一轮全量图片完整性校验用PIL的verify()方法逐张检查发现损坏文件直接隔离或删除。from PIL import Image import os def verify_images(root_dir): bad_images [] for class_name in os.listdir(root_dir): class_path os.path.join(root_dir, class_name) if not os.path.isdir(class_path): continue for img_name in os.listdir(class_path): img_path os.path.join(class_path, img_name) try: with Image.open(img_path) as img: img.verify() # 只校验文件完整性不加载像素数据 except Exception: bad_images.append(img_path) return bad_images bad_list verify_images(data/train) print(fFound {len(bad_list)} corrupted images) for path in bad_list: print(path)5.4 使用yolov5分类模式时的目录格式适配现象按yolov5的分类训练命令传入数据集路径提示找不到数据集或者类别数为0。原因yolov5的分类训练脚本内部也是调用ImageFolder但它期望的目录结构是数据集根目录下面直接就是train和test两个子目录且train/test各自下面是类别文件夹这一点当前数据集是满足的。但有一个常见的翻车点数据集路径末尾多了一个斜杠或者使用了反斜杠在类Unix系统上会导致路径拼接错误。解决路径统一用正斜杠且不要带末尾的/。命令示例python classify/train.py --model resnet18 --data /path/to/plant_leaf_dataset/data --epochs 50 --batch-size 64 --img 2245.5 混淆矩阵中类别显示为数字而非植物名现象训练完打印混淆矩阵横纵坐标都是0、1、2这样的数字看不出对应关系。原因eval脚本里直接用模型的输出索引绘制没有加载class_indices.json做映射。解决在评估脚本里用json字典把索引替换成中文类别名混淆矩阵的可读性会立刻提升。我自己吃过这个亏花了一下午对着一堆数字猜哪个类别被误判成哪个。6. 训练后推理验证与可视化脚本的进阶用法训练完模型之后验证环节我推荐只用测试集做一次整体推断别只盯着accuracy一个指标。分类任务里准确率容易被多数类主导对34类这种中大型类别数来说我习惯额外打印每个类别的精确率和召回率找出哪些类之间最容易混淆。很多情况下你会发现葡萄叶和爬山虎叶、樱桃叶和苹果叶在形态上高度相似误判集中在这些近缘类属。这时候可以针对性增加数据增强比如随机旋转、亮度扰动对叶片的细小纹理差异会更敏感。推理验证的代码其实不复杂关键是把前面提到的class_indices.json和模型输出的索引对齐。我通常先加载模型权重然后从test_loader里取一批图片批量推理把预测结果和真实标签打印出来再随机挑几张预测错的图片做可视化分析。这样做的好处是能直观看到被误判的图片长什么样是光线问题、角度问题还是类别本身长得就像。到了这个数据集的可视化脚本有一个容易被忽略的用途它不只是训练前验证数据用训练后还能拿来当推理演示工具。你可以把脚本里加载数据集的路径改成加载自己训练好的模型输入一张新的叶片图片输出预测类别和置信度。这样给非技术背景的同事演示时效果比贴一堆训练日志直观得多。import torch from torchvision import models, transforms from PIL import Image import json # 加载训练好的模型 model models.resnet18(pretrainedFalse) model.fc torch.nn.Linear(model.fc.in_features, 34) # 34个类别 model.load_state_dict(torch.load(best_model.pth, map_locationcpu)) model.eval() # 加载类别映射 with open(class_indices.json, r, encodingutf-8) as f: class_indices json.load(f) # 键是字符串索引 # 单张图片推理 transform transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ]) img Image.open(test_leaf.jpg).convert(RGB) input_tensor transform(img).unsqueeze(0) # 增加batch维度 with torch.no_grad(): outputs model(input_tensor) _, predicted torch.max(outputs, 1) # 把预测索引转回类别名 pred_idx predicted.item() class_name class_indices[str(pred_idx)] print(fPredicted: {class_name} (index {pred_idx}))逻辑说明模型输出是一个34维的向量torch.max找到最大值对应的索引也就是模型认为最可能的类别。这里有个细节加载权重的时候map_locationcpu是为了在没有GPU的机器上也能推理如果训练和推理都在GPU上这行可以去掉。class_indices[str(pred_idx)]的str转换是因为json文件里的键是字符串而模型的预测结果是整数不做转换会直接KeyError。参数说明Resize的尺寸必须和训练时保持一致否则输入维度不匹配模型会直接报错best_model.pth是模型权重文件的路径实际使用时替换成自己的文件名。如果训练时用了其他模型结构比如EfficientNet或者VIT需要相应地把models.resnet18换成对应的模型类。从那以后我每次拿到新数据集都强制走一遍完整流程先看目录结构、统计类别数、检查图片完整性、验证可视化、确认索引映射全部通过之后才允许自己碰训练代码。这套动作五分钟内能完成但省下来的是训练十几个小时后才发现数据有问题的后悔药。希望帮到你用这份数据集顺利跑出自己的分类模型。本文还有配套的精品资源点击获取
返回列表