简介:甘蔗植物病害图像分类数据集提供约19,000张已标注图片,面向深度学习图像分类方向的开发者、研究人员及农业AI学习者,可解决甘蔗红腐病、锈病、枯萎病及健康叶片等6类病害分类模型的训练与验证需求。数据已按训练集、测试集划分,每一类图片按目录分开存放,附带show.py可视化脚本和包含类别映射说明的json配置文件,便于快速检查样本分布、标注质量与类别均衡程度。资源共2000个文件,以jpg格式的图片为主(1998个),另附1个Python可视化脚本和1个类别说明json文件,7z压缩包整体大小约424.78MB。目前已有196人学习下载。读者可获得准备充分的高质量已标注数据、清晰的类别与划分说明,还可结合配套的图像分类网络改进与计算机视觉完整项目专栏,展开从数据预处理、模型训练到精度对比、算法实验的完整实践流程。
1. 甘蔗植物病害图像分类:19,000张已标注图像到底能干什么
做图像分类的同行拿到新数据集,第一反应通常不是写模型,而是先验货——尤其是像“甘蔗植物病害图像分类数据集【已标注,约19,000张数据】”这种标题,信息量其实藏在两个地方:一是类别是“病害”,意味着细粒度、类间差异小;二是“已标注”,意味着省掉了最脏最累的活,但你仍然要为标注质量买单。这篇笔记就把从验货、划分、训练到评估的整个流程拆开讲,重点回答三个问题:这19,000张数据能撑起一个什么样的分类模型、参数和划分怎么定才不翻车、以及哪些坑是农业图像里反复出现的。适合准备做作物病虫害识别的算法工程师、农业信息化方向的研究生,以及想评估这个数据方向值不值得投入的团队。
2. 先验货再动手:19,000张标注数据的体检清单与方法
标注数据不代表干净数据。我经手的多数标注图像数据集,拿到手都伴随三个不确定:类别是否平衡、标注是否统一、图像是否清晰。这三项直接决定训练策略,所以先做体检,再谈模型。
2.1 检查类别名与标注状态:看一眼分布的命令
常见做法是,标注方会提供一个类别清单或标注文件,里面记录了每张图像对应的病害类别。不管格式是 CSV、JSON 还是文件夹结构,第一步永远是统计每个类别的样本数。假设你已经把数据集解压到sugarcane/下,类别按子目录组织,用一条命令就能看分布:
# 统计每个类别子目录下的图像数量 for dir in sugarcane/*/; do cls=$(basename "$dir") cnt=$(find "$dir" -type f \( -name '*.jpg' -o -name '*.jpeg' -o -name '*.png' \) | wc -l) echo "$cls: $cnt" done这段脚本遍历sugarcane/下每个子目录,basename取出类别名,find按扩展名统计图像数量。如果你的标注是 CSV 形式,比如列出filename,label两列,用 Python 更直接:
import pandas as pd df = pd.read_csv("annotations.csv") print(df["label"].value_counts())参数说明:CSV 里的label列决定分组,如果标注文件用的字段名不是label,改成实际列名即可。目录统计方式的优点是快,缺点是有可能漏掉无标注文件;CSV 方式能看到的不只是数量,还能顺便检查是否有空标注、类别名拼写不一致等问题——这类问题在人工标注里太常见了。
2.2 从类别分布看数据能不能直接训练:不平衡度计算
拿到分布后,先算两个数:最少类别样本数和最大/最少类别比。甘蔗病害里,像锈病、赤腐病这种常见病往往占大头,而一些早期症状或区域性病害可能只有几百张。如果最少的类别少于 200 张,就要在训练里做类别加权或过采样,否则模型对少数类基本是放弃的。
我一般会顺手算一个变异系数(CV = 标准差 / 均值),用来量化不平衡程度。CV 小于 0.5 算温和,0.5 到 1.0 需要关注,超过 1.0 基本可以断定存在严重长尾。下面是计算脚本:
import pandas as pd import numpy as np df = pd.read_csv("annotations.csv") counts = df["label"].value_counts() cv = counts.std() / counts.mean() imbalance_ratio = counts.max() / counts.min() print(f"类别数: {len(counts)}, CV: {cv:.2f}, 最大/最小比: {imbalance_ratio:.1f}") print(counts)逻辑说明:counts.std() / counts.mean()计算变异系数,counts.max() / counts.min()计算极差比。如果极差比超过 10,训练时就需要用WeightedRandomSampler或在损失函数里做类别权重。这里的参数没有固定值,但 200 张是我个人的经验下限——低于这个数,即便有数据增强,模型也很难学到稳定的类别特征。
2.3 抽检图像质量与标注边界:模糊、过曝、错标样本的排查
数量只是第一步,质量才是玄学重灾区。农业图像里最常见的问题是:同一株甘蔗被拍了很多张、角度高度相似,或者背景里有大量土壤和杂草干扰。你需要从每个类别里随机抽 30~50 张,人工过一遍,重点看三件事:图像有没有模糊、有没有把背景(比如土壤、枯叶)当病害主体、标注边界是否把整片病斑都框进去。
这个环节可以用脚本辅助采样,加快人工检查的速度:
import random, os, shutil random.seed(42) os.makedirs("spot_check", exist_ok=True) for cls in os.listdir("sugarcane"): cls_dir = os.path.join("sugarcane", cls) files = [f for f in os.listdir(cls_dir) if f.endswith(".jpg")] picks = random.sample(files, min(30, len(files))) for f in picks: shutil.copy(os.path.join(cls_dir, f), os.path.join("spot_check", f"{cls}_{f}"))参数说明:random.seed(42)固定随机种子,保证抽检结果可复现;min(30, len(files))避免样本不足时报错。抽出来的图放进spot_check/,人工扫一遍就行。这个步骤不要省,因为后续所有训练策略都建立在“标注是可信的”这个假设上,一旦假设不成立,训练脚本写得再漂亮也没用。
3. 训练集与验证集怎么划分:按病害类型与拍摄条件分层切割
划分数据集的正确程度,决定了你评估指标的含金量。随意train_test_split在普通图像分类任务里问题不大,但在甘蔗病害这种场景里会埋雷:同一株甘蔗的多个角度照片如果同时出现在训练集和验证集,模型等于开卷考试,验证准确率虚高,到真实田间就露馅。
3.1 为什么要避免“同一植株不同角度进入不同划分”
甘蔗叶片上的病斑形态受光照、角度、生长阶段影响很大。标注方在采集时,往往会围绕一株病株拍多张图——正拍、侧拍、带标尺拍。如果随机划分,训练集里出现过某个病株的叶片 A,验证集里又出现同一病株的叶片 B,模型只要记住这个病株的颜色特征就能“猜对”,而这不是泛化能力。
解决办法是找到图像文件名里的样本 ID,按这个 ID 做分组划分,而不是按单张图像划分。文件名里一般会带类似plot_0123_leaf_1.jpg这样的信息,plot_0123就是分组键。如果没有现成 ID,也可以按拍摄时间或文件名前缀聚合。
3.2 一个分层划分脚本:按图像 ID 前缀与类别分层
下面这个脚本用GroupShuffleSplit实现“同一组只进一个划分”的划分逻辑:
import pandas as pd from sklearn.model_selection import GroupShuffleSplit df = pd.read_csv("annotations.csv") # 从文件名提取分组ID,假设形如 SUG_20230721_001_leaf.jpg df["group"] = df["filename"].str.extract(r"SUG_\d{8}_(\d{3})", expand=False) gss = GroupShuffleSplit(n_splits=1, test_size=0.2, random_state=42) train_idx, val_idx = next(gss.split(df, groups=df["group"])) train_df = df.iloc[train_idx] val_df = df.iloc[val_idx] # 检查验证集和训练集是否有重叠组 overlap = set(train_df["group"]) & set(val_df["group"]) print(f"重叠组数: {len(overlap)}") # 应为0 train_df.to_csv("train.csv", index=False) val_df.to_csv("val.csv", index=False)参数说明:test_size=0.2是常见经验值,19,000 张里分出约 3,800 张做验证,足够评估方差;random_state=42固定划分,方便复现。正则里的\d{8}匹配日期,(\d{3})是地块/植株编号,实际使用时按你文件名里的模式改。overlap的打印结果必须为 0,否则说明分组键提取错了。
3.3 划分后的数据是这样在训练脚本里被读取的
训练脚本里不该再自己划分数据,而是直接读train.csv和val.csv。这样划分逻辑和训练逻辑解耦,换数据、换划分策略都不会动训练代码。常见的做法是写一个轻量 Dataset 类:
import torch from torch.utils.data import Dataset from PIL import Image class SugarcaneDataset(Dataset): def __init__(self, csv_path, img_root, transform=None): self.df = pd.read_csv(csv_path) self.img_root = img_root self.transform = transform self.label_map = {label: idx for idx, label in enumerate(sorted(self.df["label"].unique()))} def __len__(self): return len(self.df) def __getitem__(self, idx): row = self.df.iloc[idx] img = Image.open(os.path.join(self.img_root, row["filename"])).convert("RGB") label = self.label_map[row["label"]] if self.transform: img = self.transform(img) return img, label参数说明:img_root是图像根目录,csv_path是划分好的 CSV。label_map按字母序生成索引,保证训练和验证用的是同一套映射。这里的关键点是,验证集图像路径必须真实存在,否则会在训练到一半时报文件找不到,最好在训练前先用脚本遍历一遍 CSV 里的文件名做存在性检查。
4. 用19,000张跑通一个图像分类基线:模型选择与关键参数
数据集确定后,模型选择反而不用太纠结。19,000 张对深度学习来说不算大,但也没有小到必须用经典 CNN 不可。关键是知道每个模型在这个数据规模下的预期表现,以及把超参调到训练能稳定收敛的程度。
4.1 基线模型怎么选:ResNet-50、EfficientNet-B3 与 ViT 的取舍
我的经验是:第一版基线直接用 ResNet-50,理由不是它最好,而是它最稳、最容易排查问题。随后再试 EfficientNet-B3 和 ViT-Small,三者在这个数据规模下的取舍如下:
| 模型 | 预期输入分辨率 | 训练难度 | 在这个规模下的表现预期 |
|---|---|---|---|
| ResNet-50 | 224x224 | 低,收敛快 | 稳,能给出可靠基线 |
| EfficientNet-B3 | 300x300 | 中,需要更长训练 | 通常比 ResNet-50 高1~3个百分点 |
| ViT-Small | 224x224 | 高,需要更多 epoch 和正则 | 未必超过 CNN,但能验证 Transformer 是否适合你的数据 |
ViT 在小数据集上容易过拟合,所以不建议一上来就跑。先拿 ResNet-50 摸清数据的底,再决定要不要上更复杂的结构。这也符合图像分类领域的主流经验:数据量不够大时,卷积模型的归纳偏置仍然值钱。
4.2 训练超参适配:epoch、batch、初始学习率与增强策略
常见做法是先用一个稳定的超参组合跑通,再按验证集结果逐步调整。我习惯的起点是:
| 超参 | 取值 | 说明 |
|---|---|---|
| 输入尺寸 | 224x224 | 跟预训练权重对齐 |
| Batch Size | 64 | 单卡 16G 显存够用;调大记得同步调学习率 |
| 初始学习率 | 1e-3 | AdamW 配 1e-3 起步,Cosine 衰减到 1e-5 |
| Epoch | 50 | 19,000 张够 50 epoch 看清楚趋势 |
| 权重衰减 | 1e-4 | 对细粒度分类偏保守,防止过拟合 |
| 数据增强 | RandomResizedCrop + Flip + ColorJitter | 病害图像颜色很重要,ColorJitter 强度不要太大 |
其中增强策略对农业图像要格外小心:病害识别的核心依据是病斑颜色和纹理,过强的色彩抖动会把“黄斑”抖成“绿斑”,等于给模型制造错误信号。我建议ColorJitter的brightness=0.2, contrast=0.2, saturation=0.1起步,不要一上来就重度增强。
MixUp 和 CutMix 是否要用?在 19,000 张规模下,CutMix 通常有帮助,它模拟了“一张叶片上有两种病害”的真实场景。但不建议第一版就加,先跑通无强增强的版本,确认数据没问题,再叠加 CutMix 看收益。
4.3 从训练到评估:一条命令跑出准确率与混淆矩阵
训练脚本里,每轮 epoch 结束都要在验证集上输出 top-1 准确率和损失,这是判断模型是否在收敛的硬指标。状态记录可以写成一个函数,回传当前 epoch 的完整评估结果:
@torch.no_grad() def evaluate(model, val_loader, criterion): model.eval() correct, total, val_loss = 0, 0, 0.0 all_preds, all_labels = [], [] for images, labels in val_loader: images, labels = images.cuda(), labels.cuda() outputs = model(images) loss = criterion(outputs, labels) val_loss += loss.item() * images.size(0) preds = outputs.argmax(dim=1) all_preds.extend(preds.cpu().numpy()) all_labels.extend(labels.cpu().numpy()) correct += (preds == labels).sum().item() total += labels.size(0) accuracy = correct / total return accuracy, val_loss / total, all_preds, all_labels参数说明:criterion用交叉熵即可;all_preds和all_labels保留下来,就是为了在训练结束后画混淆矩阵用。这里强调一点:不要只看准确率,尤其类别不平衡的时候。如果一个类占 60%,准确率 70% 可能意味着模型只在背常见类别。一定要跑到分类别召回,这个信息后面会直接指导你决定要不要做类别加权。
5. 常见避坑:标注噪声、类别不均衡与数据泄漏
这个标题下的坑,我踩过不少,挑五个最有代表性的讲。每条都是现象、原因、解决三段,方便你对照排查。
5.1 现象:验证集准确率 98%,下地测试直接掉到 75%
原因:大概率是划分泄漏。要么是同一植株的多张图被分进了不同划分,要么是按目录划分时同一个病株目录被切开了。我在甘蔗数据上见过最典型的案例就是文件名前缀分组没做,随机切分导致验证集“记住”了同一个体的纹理特征。
解决:回到第 3 章的GroupShuffleSplit,严格按样本 ID 分组重划。重划后如果准确率掉 10 个点以上,别慌,这个掉下来的数才是真实水平。可以再加一个按拍摄地块划分的交叉验证,进一步确认划分稳健性。
5.2 现象:模型把土壤和枯叶当好病斑,注意力完全不在叶片上
原因:田间图像的背景复杂,标注方如果只标了叶片区域,但模型看到的是全图,它会去学背景里的土壤纹理和枯叶颜色——这些特征和某些病害高度相关,导致误判。这本质上是“数据集偏置”问题,模型学的不是病害本身,而是背景。
解决:训练时加随机裁剪,模拟不同取景;评估时用 Grad-CAM 看注意力图,如果亮点集中在叶片边缘或背景,就要考虑换用带分割掩码的模型结构,或者用更严格的中心裁剪做预处理。也可以在抽检阶段就把背景过于杂乱、主体占比过小的图像剔除。
5.3 现象:准确率还行,但少数类召回率是 0
原因:类别不均衡时,交叉熵损失会被多数类主导。模型对少数类“放弃治疗”,预测时永远输出多数类,准确率却能保持在 70% 以上。这是图像分类里最常见的假象。
解决:两种手段配合使用。第一种是WeightedRandomSampler,让每个类别在一个 epoch 里被采到的次数相近;第二种是损失函数加权,给样本少的类别更高的权重。代价是训练时间变长、少数类可能过拟合,所以还要配合早停和更强增强。
5.4 现象:标注边界不一致,同一个病害有的标整叶、有的只标病斑
原因:多人标注时标准没对齐。甘蔗病害早期症状是斑点状,后期会蔓延整片叶。标注员 A 习惯只框病斑,标注员 B 习惯把整叶都标成病害,导致模型对“该看多大区域”的认知混乱。
解决:和标注方对齐一个明确的标注规范,比如“叶片面积超过 30% 出现病征才算该类别,否则标健康”。对于已有数据集,用训练集的难例挖掘辅助挑出标注不一致的样本,人工复标。这个环节最费时间,但回报也最大。
5.5 现象:训练 loss 降下去了,验证 loss 第 15 个 epoch 开始回升
原因:过拟合。19,000 张图像对 ResNet-50 来说是能过拟合的,尤其当类别间差异小、背景又相对单一的时候,模型开始死记训练样本。
解决:三种手段按顺序上:先增加增强强度(CutMix、RandomErasing),再调大权重衰减到 1e-4~3e-4,最后把 Dropout 加到分类头。如果你发现验证集提升尽量配合早停训练,一般patience=5就够。
6. 让分类结果可信:用混淆矩阵和注意力图验证病害部位
模型训练完,准确率只是一个数,真正让你敢把它拿到田间去试的,是两样东西:混淆矩阵和注意力图。
6.1 混淆矩阵:哪些病害容易互相认错
用第 4 章evaluate函数里保存的all_preds和all_labels画混淆矩阵,重点看两类错误:同属早期症状的类别互相混(比如锈病早期和赤腐病早期),以及健康样本被误判成病害的比例。前者说明特征不够区分,后者说明虚警率太高,会影响实际使用体验。
from sklearn.metrics import confusion_matrix import matplotlib.pyplot as plt cm = confusion_matrix(all_labels, all_preds) plt.figure(figsize=(10, 8)) plt.imshow(cm, cmap="Blues") plt.colorbar() plt.xlabel("Prediction") plt.ylabel("True Label") plt.show()参数说明:confusion_matrix的两个输入必须是标签索引,不是类别名。实际项目里,我通常会让混淆矩阵里“健康”那一行单独拎出来算虚警率,如果虚警率超过 5%,就要考虑提高判定阈值或者在模型后加一道“是不是病”的二分类过滤——这个附加分类器很好用,成本也低。
6.2 注意力图:模型在看叶片还是在看背景
Grad-CAM 是验证细粒度分类模型最直观的手段。选 10~20 个验证集样本,叠加注意力热力图,看高亮区域是否落在病斑上。这个步骤虽然有点“玄学”,但比任何指标都更能暴露数据集的偏置。如果你的注意力图持续高亮在叶柄、土壤或者水珠反光上,说明模型学偏了,需要回头清理训练数据或加强增强。
我个人的习惯是:每换一个数据集,落地前必定做一次“注意力图走查”,从每个类别里挑最难判的 3 张图出来看。这不是一次性的,而是每次训练版本更新都做一遍。做得多了,你就会发现哪些类别是模型真正理解了,哪些只是在蒙。这个过程帮我避免过不止一次“准确率很高、实际不能用”的翻车,也算是这几年做图像分类最值回票价的习惯。希望帮到你。
本文还有配套的精品资源,点击获取