新闻详情

新闻详情

首页 / 资讯中心 / 详情

玉米病害图像分类实战:2800张标注数据从训练到ONNX部署

发布时间:2026/9/28 16:43:22来源:尧图网络
玉米病害图像分类实战:2800张标注数据从训练到ONNX部署
简介这份玉米病害图像分类数据集面向从事农业图像识别、深度学习入门与CNN分类网络改进的开发者与研究者可用于训练和评估玉米叶片病害识别模型。数据集已标注共约2,800张图像涵盖尾孢叶斑灰、普通锈蚀等4个类别具体类别名称可查看包内json文件数据已按训练集、验证集、测试集划分各类别图片分别存放便于直接加载训练。资源包共2000个文件以1998张jpg图像为主另含1个py脚本与1个json标注文件压缩包约49.46MB其中show脚本可用于数据集可视化方便快速检查样本分布与图像质量。目前已有284人学习下载。对于希望复现或改进CNN分类网络的读者可结合配套的CNN分类网络改进系列内容在真实农业病害数据上完成训练、调参与对比实验积累从数据组织到模型评估的完整实践。1. 玉米病害图像分类数据集2800 张标注图能跑出什么名堂手里有一份约 2,800 张、已经标注好的玉米病害图像分类数据集第一反应往往不是兴奋而是犯嘀咕这点量够不够训一个能用的图像分类模型我的答案是够但前提是你得把它当成一个「小而脏」的真实数据集来对待而不是当成 CIFAR-10 那种洗干净的玩具。玉米病害识别这件事田间拍摄的照片光照乱、背景杂、病斑还经常跨类重叠大斑病和小斑病在早期肉眼都难分模型翻车是常态。这份数据集的价值不在于刷多高的准确率而在于它能让你完整走通一条从数据检查、划分、增强到训练、评估、部署的链路而且成本低到一台带显卡的机器一晚上就能出结果。适合谁看做农业 AI 落地的工程师、要交课程设计或毕设的学生、想拿真实数据练手图像分类的开发者。如果你手上正好有这份数据或者准备自己标一份类似的下面这套流程可以直接抄。我会把每一步为什么这么做、参数怎么设、哪里容易踩坑讲清楚尤其是那些不写出来你一定会后悔的细节。2. 先摸清数据底细2800 张标注图到底长什么样拿到数据集别急着写训练脚本先做数据审计。这一步能省掉后面 80% 的玄学问题。玉米病害分类常见的类别无非是健康、大斑病、小斑病、锈病、灰斑病这几类但不同来源的数据集类别命名和数量差异很大你得先确认自己手里这份到底分了几类、每类多少张、有没有严重的长尾。2.1 用脚本统计类别分布和图像尺寸先跑一段统计脚本把类别文件夹结构、每类数量、图像分辨率、通道模式都摸一遍。假设数据是按类别名/图片文件的目录结构组织的这是图像分类最通用的格式torchvision.datasets.ImageFolder和tf.keras.utils.image_dataset_from_directory都认这个结构。import os from collections import Counter from PIL import Image import hashlib DATA_ROOT corn_disease_dataset # 数据集根目录下面每个子文件夹是一个类别 def audit_dataset(root): class_counts Counter() size_counter Counter() mode_counter Counter() hashes {} duplicates [] for cls in sorted(os.listdir(root)): cls_dir os.path.join(root, cls) if not os.path.isdir(cls_dir): continue for fname in os.listdir(cls_dir): fpath os.path.join(cls_dir, fname) try: with Image.open(fpath) as img: class_counts[cls] 1 size_counter[img.size] 1 mode_counter[img.mode] 1 # 用文件内容哈希查重防止同图不同名 h hashlib.md5(img.tobytes()).hexdigest() if h in hashes: duplicates.append((fpath, hashes[h])) else: hashes[h] fpath except Exception as e: print(f坏图: {fpath} - {e}) print(类别分布:, dict(class_counts)) print(尺寸分布 Top5:, size_counter.most_common(5)) print(通道模式:, dict(mode_counter)) print(f疑似重复图对: {len(duplicates)}) for a, b in duplicates[:10]: print( 重复:, a, -, b) audit_dataset(DATA_ROOT)这段脚本干了四件事统计每类数量、统计分辨率分布、统计通道模式RGB 还是灰度、用像素哈希查重复图。逻辑说明一下用img.tobytes()做哈希而不是直接哈希文件是为了绕过 JPEG 压缩元数据差异同一张图存两次不同质量也能查出来。参数上DATA_ROOT换成你自己的路径即可如果类别文件夹里还有子目录需要改成递归遍历。跑完你大概率会发现几个问题某类只有一两百张某类有七八百张分辨率从 200x200 到 2000x1500 都有混着几张灰度图还有若干重复图。这些都得在训练前处理掉。2.2 长尾类别和重复图怎么处理类别不均衡是玉米病害数据集的通病因为健康叶片好拍某些病害发病期短、样本自然少。处理方式有三种按代价从低到高排策略做法适用场景代价重采样对少数类过采样多数类欠采样类别差距 3 倍以内低但欠采样丢信息类别权重损失函数里给少数类更大权重差距 5 倍以内低推荐首选数据增强对少数类做更强增强少数类绝对数量太少中需调增强强度补充采集再标一批少数类差距 10 倍以上高但最治本我一般先用类别权重CrossEntropyLoss(weight...)里传一个和类别频率成反比的张量几行代码就能缓解。重复图必须删否则同一张图既进训练集又进验证集准确率虚高上线就露馅。查重脚本输出的重复对保留一张删一张即可。注意删重复图之前先人工扫一眼有些「重复」其实是同一叶片不同角度哈希不同但语义接近这种要靠后面的划分策略隔离不能只靠哈希。3. 划分与增强让 2800 张图发挥出 8000 张的效果数据审计干净之后下一步是划分训练/验证/测试集再做增强。这两步直接决定模型是真学到病害特征还是记住了背景。3.1 分层划分别让验证集骗了你2800 张图我一般按 7:1.5:1.5 划成训练/验证/测试。关键是分层stratified保证每个类别在三个集合里的比例一致否则某个少数类可能全被分进训练集验证时那一类准确率直接是 0你还以为是模型不行。import os import shutil import random from collections import defaultdict from sklearn.model_selection import train_test_split DATA_ROOT corn_disease_dataset OUT_ROOT corn_split SEED 42 random.seed(SEED) # 收集所有样本路径和标签 samples [] for cls in sorted(os.listdir(DATA_ROOT)): cls_dir os.path.join(DATA_ROOT, cls) if not os.path.isdir(cls_dir): continue for fname in os.listdir(cls_dir): samples.append((os.path.join(cls_dir, fname), cls)) paths [s[0] for s in samples] labels [s[1] for s in samples] # 先分训练验证 和 测试stratify 保证类别比例 X_trainval, X_test, y_trainval, y_test train_test_split( paths, labels, test_size0.15, stratifylabels, random_stateSEED ) # 再从训练验证里分验证集 X_train, X_val, y_train, y_val train_test_split( X_trainval, y_trainval, test_size0.176, stratifyy_trainval, random_stateSEED ) def dump(X, y, split): for p, c in zip(X, y): dst_dir os.path.join(OUT_ROOT, split, c) os.makedirs(dst_dir, exist_okTrue) shutil.copy2(p, os.path.join(dst_dir, os.path.basename(p))) dump(X_train, y_train, train) dump(X_val, y_val, val) dump(X_test, y_test, test) print(ftrain{len(X_train)} val{len(X_val)} test{len(X_test)})逻辑上train_test_split的stratify参数按标签分层抽样random_state固定种子保证可复现。第二次划分的test_size0.176是算出来的验证集要占总量的 15%而训练验证占 85%所以 0.15/0.85≈0.176。参数SEED换成别的值会得到不同划分但同一篇实验里必须固定否则没法对比。划分完检查一下每个 split 下每类的数量如果某个类在验证集里少于 5 张评估结果波动会很大考虑合并极少数类或者补充数据。3.2 增强策略田间场景该加什么、不该加什么玉米病害图像的增强不能照搬 ImageNet 那套。田间照片的核心变量是光照、角度、遮挡和背景所以增强要往这些方向靠而不是乱加。import torch from torchvision import transforms # 训练集增强模拟田间拍摄变化 train_tf transforms.Compose([ transforms.Resize((256, 256)), # 统一尺寸短边缩放到256 transforms.RandomResizedCrop(224, scale(0.7, 1.0)), # 随机裁剪模拟不同拍摄距离 transforms.RandomHorizontalFlip(p0.5), # 水平翻转叶片左右对称合理 transforms.RandomRotation(15), # 小角度旋转模拟拍摄角度偏差 transforms.ColorJitter(brightness0.3, contrast0.3, saturation0.2, hue0.05), # 光照变化 transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]), ]) # 验证/测试集只做确定性变换 val_tf transforms.Compose([ transforms.Resize((256, 256)), transforms.CenterCrop(224), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]), ])逐项说明为什么这么设。RandomResizedCrop的scale(0.7,1.0)是关键田间拍摄距离远近不一裁剪范围太大会把病斑裁没太小又学不到整体形态0.7 到 1.0 是经验值。RandomRotation(15)只转 15 度因为叶片在自然状态下不会倒过来转太多会引入不真实的样本。ColorJitter的hue只给 0.05色相变化太大会把健康绿变成病态黄直接制造错误标签。归一化用的 ImageNet 均值方差如果你从头训练可以用数据集自己的统计量但用预训练权重就必须用 ImageNet 的。注意增强只在训练集做验证和测试集一律用确定性变换。我见过有人在验证集也加随机增强结果每次评估准确率都在跳排查半天才发现是增强的锅。4. 训练与评估从 ResNet 到 Transformer 的选型与调参数据准备好了进入模型环节。玉米病害分类这个任务2800 张图的量级我的建议是别一上来就上大模型先用轻量骨干跑通基线再考虑升级。4.1 骨干网络选型ResNet18 起步按需升级选型逻辑很简单数据量小、类别少通常 4 到 6 类模型容量不需要太大。ResNet18 或 EfficientNet-B0 这种量级的骨干配合预训练权重在 2800 张图上微调通常能到 90% 以上的验证准确率。如果你追求更高精度且算力够可以试 ConvNeXt-Tiny 或 ViT-Base但要注意 Transformer 类模型在小数据集上更容易过拟合需要更强的正则和更长的预热。import torch.nn as nn from torchvision import models def build_model(num_classes, backboneresnet18, pretrainedTrue): if backbone resnet18: model models.resnet18(weightsmodels.ResNet18_Weights.IMAGENET1K_V1 if pretrained else None) # 替换最后的全连接层适配我们的类别数 model.fc nn.Linear(model.fc.in_features, num_classes) elif backbone efficientnet_b0: model models.efficientnet_b0(weightsmodels.EfficientNet_B0_Weights.IMAGENET1K_V1 if pretrained else None) model.classifier[1] nn.Linear(model.classifier[1].in_features, num_classes) elif backbone convnext_tiny: model models.convnext_tiny(weightsmodels.ConvNeXt_Tiny_Weights.IMAGENET1K_V1 if pretrained else None) model.classifier[2] nn.Linear(model.classifier[2].in_features, num_classes) else: raise ValueError(f不支持的骨干: {backbone}) return model参数说明num_classes填你的实际类别数pretrainedTrue加载 ImageNet 预训练权重这是小数据集能训起来的关键。替换分类头时注意不同模型的属性名不一样ResNet 是fcEfficientNet 是classifier[1]ConvNeXt 是classifier[2]写错了不会报错但分类头没换训练 loss 会一直不降。4.2 训练循环与关键超参训练脚本我一般写成可配置的方便对比不同设置。核心超参就几个学习率、batch size、优化器、学习率调度、早停。import torch from torch.utils.data import DataLoader from torchvision.datasets import ImageFolder from torch.optim import AdamW from torch.optim.lr_scheduler import CosineAnnealingLR def train_one_epoch(model, loader, criterion, optimizer, device): model.train() total_loss, correct, total 0.0, 0, 0 for imgs, labels in loader: imgs, labels imgs.to(device), labels.to(device) optimizer.zero_grad() outputs model(imgs) loss criterion(outputs, labels) loss.backward() optimizer.step() total_loss loss.item() * imgs.size(0) correct (outputs.argmax(1) labels).sum().item() total imgs.size(0) return total_loss / total, correct / total torch.no_grad() def evaluate(model, loader, criterion, device): model.eval() total_loss, correct, total 0.0, 0, 0 for imgs, labels in loader: imgs, labels imgs.to(device), labels.to(device) outputs model(imgs) loss criterion(outputs, labels) total_loss loss.item() * imgs.size(0) correct (outputs.argmax(1) labels).sum().item() total imgs.size(0) return total_loss / total, correct / total # 主流程 device torch.device(cuda if torch.cuda.is_available() else cpu) train_ds ImageFolder(corn_split/train, transformtrain_tf) val_ds ImageFolder(corn_split/val, transformval_tf) train_loader DataLoader(train_ds, batch_size32, shuffleTrue, num_workers4) val_loader DataLoader(val_ds, batch_size32, shuffleFalse, num_workers4) model build_model(num_classeslen(train_ds.classes)).to(device) criterion nn.CrossEntropyLoss() optimizer AdamW(model.parameters(), lr3e-4, weight_decay1e-4) scheduler CosineAnnealingLR(optimizer, T_max30) best_acc 0.0 for epoch in range(30): tr_loss, tr_acc train_one_epoch(model, train_loader, criterion, optimizer, device) val_loss, val_acc evaluate(model, val_loader, criterion, device) scheduler.step() if val_acc best_acc: best_acc val_acc torch.save(model.state_dict(), best_model.pth) print(fepoch {epoch1}: train_acc{tr_acc:.4f} val_acc{val_acc:.4f} best{best_acc:.4f})超参说明学习率3e-4是微调预训练模型的常用起点如果 loss 震荡就降到1e-4如果收敛太慢就升到1e-3但别超过。weight_decay1e-4是轻量正则小数据集上能压一点过拟合。CosineAnnealingLR的T_max设成总 epoch 数让学习率平滑降到接近 0。batch size 32 在 8GB 显存上跑 ResNet18 没问题显存小就降到 16。早停没单独写用保存 best 权重的方式等效实现了。4.3 评估指标准确率之外必须看混淆矩阵准确率在类别不均衡时会骗人。假设健康类占 60%模型全预测健康也有 60% 准确率但毫无用处。所以必须看每类的 precision、recall、F1以及混淆矩阵重点看哪些病害之间在互相误判。from sklearn.metrics import classification_report, confusion_matrix import numpy as np torch.no_grad() def get_predictions(model, loader, device): model.eval() all_preds, all_labels [], [] for imgs, labels in loader: imgs imgs.to(device) outputs model(imgs) all_preds.extend(outputs.argmax(1).cpu().numpy()) all_labels.extend(labels.numpy()) return np.array(all_preds), np.array(all_labels) preds, labels get_predictions(model, val_loader, device) print(classification_report(labels, preds, target_namesval_ds.classes)) print(混淆矩阵:) print(confusion_matrix(labels, preds))跑完重点看混淆矩阵的对角线外哪些格子数值大。玉米病害里大斑病和小斑病、锈病和灰斑病是最容易混的两对如果这两对的误判率高说明模型没学到区分性特征要么是数据本身标注有歧义要么是增强把关键特征破坏了。这时候回去看误判样本的原图比调参有用得多。5. 避坑与排查玉米病害分类训练中最容易翻车的 5 个点这一章是我自己踩过的坑按「现象 → 原因 → 解决」写你对照着排查能省不少时间。现象一训练准确率很快到 99%验证准确率卡在 60% 不动。原因典型过拟合2800 张图对模型来说太少模型记住了训练集的背景和噪声。也可能是训练集和验证集有重复图导致验证集泄漏。 解决先跑第 2 章的查重脚本确认没有重复图然后加增强强度、加 weight_decay、加 dropout还不行就换更小的骨干或者冻结前面层只训分类头。现象二loss 一直是 nan 或者突然爆炸。原因学习率太大或者数据里有坏图全黑、全白、损坏文件或者标签越界。 解决学习率降到1e-4重试用第 2 章的审计脚本过滤坏图检查num_classes和实际类别数是否一致标签索引从 0 开始还是从 1 开始。现象三验证集准确率每次评估都在跳波动超过 5 个点。原因验证集太小或者验证集用了随机增强或者 batch norm 在验证时没切 eval 模式。 解决确认验证集 transform 是确定性的确认model.eval()和torch.no_grad()都加了验证集每类至少保证 10 张以上不够就从训练集挪。现象四某个类别 recall 特别低几乎全被预测成另一类。原因这两类本身视觉相似或者该类样本太少模型没学够或者标注时这两类就标混了。 解决回去看混淆矩阵里这两类的误判样本人工确认标注是否正确如果标注没问题对该类做过采样或加类别权重实在分不开就考虑合并这两类别硬分。现象五本地评估很好部署到实际场景准确率暴跌。原因训练数据和你实际要识别的场景分布不一致比如训练全是近距离特写实际是无人机航拍。 解决这个没有捷径要么补充目标场景的数据重新训练要么在预处理阶段做域适配。部署前一定用真实场景的图做一次测试别只看验证集数字。提示这五个坑里重复图泄漏和验证集增强是最隐蔽的两个因为它们不会报错只会让你的指标虚高等到上线才发现问题。养成训练前先审计、评估前先确认 transform 的习惯。6. 把模型用起来导出 ONNX 与推理脚本的一个实用技巧训练出 best_model.pth 只是半成品真正要用起来得能推理。我一般把模型导出成 ONNX这样不依赖 PyTorch 环境部署到边缘设备或者用其他语言调用都方便。这里给一个导出加推理的完整流程顺带讲一个我常用的技巧把预处理也塞进 ONNX 图里避免推理时预处理和训练时不一致。import torch import torch.nn as nn import numpy as np from PIL import Image from torchvision import transforms # 包装模型把归一化也放进计算图 class InferenceWrapper(nn.Module): def __init__(self, backbone): super().__init__() self.backbone backbone # 注册为 buffer导出时会被固化进 ONNX self.register_buffer(mean, torch.tensor([0.485, 0.456, 0.406]).view(1, 3, 1, 1)) self.register_buffer(std, torch.tensor([0.229, 0.224, 0.225]).view(1, 3, 1, 1)) def forward(self, x): # x 是 0-1 范围的 float 张量这里做归一化 x (x - self.mean) / self.std return self.backbone(x) model build_model(num_classes5, backboneresnet18, pretrainedFalse) model.load_state_dict(torch.load(best_model.pth, map_locationcpu)) model.eval() wrapper InferenceWrapper(model) dummy torch.randn(1, 3, 224, 224) torch.onnx.export( wrapper, dummy, corn_disease.onnx, input_names[input], output_names[logits], dynamic_axes{input: {0: batch}, logits: {0: batch}}, opset_version12, ) print(导出完成: corn_disease.onnx)逻辑说明InferenceWrapper把归一化作为 buffer 注册导出时会被固化进 ONNX 图这样推理端只需要把图片缩放到 224x224、转成 0-1 的 float 张量喂进去就行不用再手动减均值除方差。dynamic_axes让 batch 维度可变单张和批量推理都能用。opset_version12兼容性比较好太新的版本有些推理引擎不支持。推理脚本对应地就很简单import onnxruntime as ort import numpy as np from PIL import Image CLASSES [健康, 大斑病, 小斑病, 锈病, 灰斑病] # 按训练时的类别顺序填 session ort.InferenceSession(corn_disease.onnx, providers[CPUExecutionProvider]) def predict(image_path): img Image.open(image_path).convert(RGB).resize((224, 224)) x np.asarray(img, dtypenp.float32) / 255.0 # 只做 0-1 归一化 x x.transpose(2, 0, 1)[None, ...] # HWC - NCHW logits session.run([logits], {input: x})[0] probs np.exp(logits) / np.exp(logits).sum(axis1, keepdimsTrue) idx probs.argmax(1)[0] return CLASSES[idx], float(probs[0, idx]) label, conf predict(test_leaf.jpg) print(f预测: {label}, 置信度: {conf:.4f})这个技巧的价值在于训练和推理的预处理逻辑只写一次固化在模型里彻底杜绝「训练时归一化了推理时忘了」这类低级但致命的错误。我见过太多项目因为预处理不一致导致线上效果差一截排查起来还特别费劲因为模型本身没问题问题出在数据管道上。最后说个习惯。我做完任何一个图像分类项目都会留一份inference_demo.py和几张典型测试图在仓库里过几个月回头看还能一键跑通。玉米病害这个方向数据集的规模不是瓶颈标注质量和场景覆盖才是2800 张如果标得准、场景全比 2 万张脏数据有用得多。希望帮到你。本文还有配套的精品资源点击获取
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

Linux之Http<3>--表单 2026/9/28 18:53:17

Linux之Http<3>--表单

之前的代码编写,都是对网站资源的请求,等等 ,那些都是对于静态资源的获取但是还存在动态内容的获取,比如动态站点,支持用户进行登录,注册,支付,查找等功能功能静态资源请求 VS 动态请求1. 静态资源请求目标:.html、.png、.css、.js这类文件特点:文件内容…

阅读更多 →
用好 Pi:一份来自官方文档与社区实战的深度技巧指南 2026/9/28 18:53:17

用好 Pi:一份来自官方文档与社区实战的深度技巧指南

用好 Pi:一份来自官方文档与社区实战的深度技巧指南 如果你已经被各种 AI 编程 Agent 的"全家桶"压得喘不过气——臃肿的系统提示词、点不完的权限弹窗、永远猜不到它在想什么的"黑盒"——那么 Pi(pi.dev)可能是你一直在…

阅读更多 →
立创EDA + AI 生成 STM32 原理图教程:用 TaoToken 统一 Key 打通配置链路 2026/9/28 18:53:11

立创EDA + AI 生成 STM32 原理图教程:用 TaoToken 统一 Key 打通配置链路

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
OpenClaw 插件架构解析:从核心原理到 TaoToken 配置落地 2026/9/28 18:53:11

OpenClaw 插件架构解析:从核心原理到 TaoToken 配置落地

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
本地运行 OpenClaw 实操教程:Windows 下自动化办公 AI 工具搭建与 TaoToken 配置指南(含安装包) 2026/9/28 18:53:11

本地运行 OpenClaw 实操教程:Windows 下自动化办公 AI 工具搭建与 TaoToken 配置指南(含安装包)

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
从嘉立创EDA到AD:PCB 3D模型精准定位与结构验证全流程 2026/9/28 18:53:04

从嘉立创EDA到AD:PCB 3D模型精准定位与结构验证全流程

搞硬件设计的朋友应该都有过这种经历:PCB画完只是第一步,元器件高度对不对、外壳开孔位置准不准、接插件会不会和结构件互相顶住,这些靠眼睛看二维图纸根本看不出来。我自己的习惯是从嘉立创EDA/立创商城直接拿元器件的3D模型(STE…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉