简介:本资源是一套基于Python实现的农业病虫害智能识别系统,面向计算机、人工智能、农业信息化等相关专业的学生、教师及初学者,解决农作物图像中常见病害与虫害的自动分类识别问题。压缩包共46个文件,包含23个占位文件(.keep)、4个Markdown文档(含中英文README及贡献指南)、4个ZIP压缩包(可能为数据集或依赖环境)、3个XML配置文件及多个IDE项目配置文件(如.project、.classpath、.idea等),整体大小3.57MB,结构完整,适合作为课程设计、毕业设计或AI实践入门项目。已有130人学习下载,源码经作者毕设实测运行成功,答辩平均分96分,配套文档清晰说明部署流程与模型调用方式;读者可直接运行演示、理解CNN/LSTM等神经网络在图像识别中的落地逻辑,并基于src目录下的模块化代码进行功能扩展或数据集替换。 去年春天在乡下拍水稻叶片的时候,我对着手机相册里上百张病害照片犯了难:稻瘟病、胡麻叶枯病、白叶枯病,有些肉眼几乎分辨不出差别。等我好不容易整理好照片发给植保站的技术员,对方隔了大半天才回一句“大概率是稻瘟病,建议用XX药剂”。那一刻我意识到,如果能有一套应用神经网络进行病虫害识别的Python实现,把叶片照片拍下来就能在几秒内给出判断,对基层农户和农技推广来说会是一个非常实用的工具。
这个项目就是基于这个想法做的:用卷积神经网络训练一个农作物叶片病害分类器,配套完整的Python源码和文档说明。它做的事情很简单——输入一张叶片照片,输出病害类别和置信度,整个流程从数据准备、模型训练到部署推理都涵盖了。如果你有一定的Python基础,想了解一个完整的深度学习图像分类项目该怎么搭,或者你正在做农业相关的AI落地,那这份内容正好适合你。
1. 为什么用深度学习做病虫害识别:问题拆解与场景价值
1.1 植保场景的真实痛点
病虫害诊断这件事,核心瓶颈其实不在算法,而在人。国内基层植保站的技术人员数量有限,而作物病害种类又多,光是水稻常见病害就有十几种,加上玉米、小麦、蔬菜、果树,一个农技员要熟悉几百种病害症状,这几乎不可能完成。更麻烦的是,很多病害在早期症状高度相似——比如缺氮的黄化和某些病毒病的黄化,在没有显微镜和经验的情况下很容易误判。
误判的代价是实打实的:用错药不仅治不了病,还可能产生抗药性,甚至导致农药残留超标。我调研时接触过一位种大棚番茄的农户,把早疫病当成晚疫病打了两周药,最后整棚番茄损失了大半。这种场景下,一个能快速给出参考诊断的工具,哪怕只是帮农户缩小判断范围,价值都很大。
另一个痛点是时效性。病害扩散是按天算的,等专家来看的时候,往往已经蔓延开了。手机拍照、立刻出结果,这个体验对田间场景非常关键。
1.2 传统图像识别方案的瓶颈
早期做植物病害识别,主流方案是“人工设计特征+传统机器学习分类器”。先用颜色直方图、纹理特征(比如LBP、HOG)、形状特征等把图像转成向量,再扔给SVM或者随机森林做分类。
这条路我走过,问题很明显:第一,特征设计非常依赖经验,换一个作物、换一种病害,原来有效的特征可能就失效了;第二,田间环境光照变化剧烈、叶片姿态不一、背景复杂,人工特征很难把这些变化都覆盖到;第三,特征提取和分类器是两套独立的模块,误差会叠加,整体精度上不去。
我见过做得好的传统方法,在实验室背景的公开数据集上能到90%左右的准确率,但一到真实农田拍摄的照片上,直接掉到70%以下。原因就是特征鲁棒性不够。
1.3 项目目标的合理边界
做这个项目之前,我给自己定了几条边界,避免项目失控:
- 先做单张图片的分类,不做目标检测和分割。也就是说,输入图片中叶片要占主要区域,模型只管判断“这是什么病”,不管“病斑在哪里”。检测是后续扩展方向。
- 聚焦叶片病害,不做虫害识别。虫害涉及到的形态更复杂,很多还需要结合虫体特征,超出了这个项目的初始范围。
- 优先保证常见病害类别的识别准确率,而不是一味追求类别数量。用10个类别的数据做出高可靠性,比用100个类别做出一堆半吊子判断更有实际意义。
把边界定清楚之后,项目的技术路线就清晰了:用Python搭一个基于卷积神经网络的图像分类流水线,数据用公开数据集,模型用轻量级CNN,训练用迁移学习,最后输出一个可以直接调用的推理脚本。
2. 数据是识别的上限:数据集准备与增强策略
2.1 数据来源:公共数据集与自采数据的取舍
做病虫害识别,数据集的质量直接决定模型上限。目前最常用的公开数据集是PlantVillage,包含约5.4万张叶片图像,覆盖14种作物、38个病害类别,包括马铃薯早疫病/晚疫病、番茄叶霉病/斑枯病、玉米锈病等常见类别。
我最终选了其中10个类别来做这个项目,包括:番茄晚疫病、番茄早疫病、番茄叶霉病、番茄斑枯病、马铃薯早疫病、马铃薯晚疫病、玉米大斑病、玉米锈病、健康番茄叶片、健康马铃薯叶片。选择标准有两层:一是这些病害在农业生产中发生频率高,二是部分病害之间症状相似,能真正检验模型的判别能力。
如果你有自采数据,建议优先级这样安排:自采数据 > 公开数据集中同生态条件的数据 > 其他公开数据集。原因很简单,模型最终要在你实际使用的场景里跑,数据分布越接近真实场景,效果越好。
2.2 数据预处理流程:清洗、切分与标注格式
拿到原始数据之后,我做的第一件事不是训练,而是清洗。PlantVillage这类开源数据集中存在少量低分辨率图片、模糊图片以及个别标注错误的样本。我用一个简单的脚本把所有图片统一检查了一遍:删除分辨率低于100×100的、肉眼明显模糊的、以及内容与类别名称明显不符的。
清洗之后是数据切分,这里有一个容易忽略的细节:一定要按类别分层切分,而不是简单随机切分。否则可能某些类别在训练集中有80张、在验证集中只有2张,评估结果就会失真。我用8:1:1的比例把数据分成训练集、验证集、测试集,每一类在这三个集合中的占比保持一致。
标注格式用的是PyTorch最常见的ImageFolder目录结构:
data/ ├── train/ │ ├── Tomato___Early_blight/ │ ├── Tomato___Late_blight/ │ ├── Potato___Early_blight/ │ └── ... ├── val/ └── test/这种结构的好处是torchvision.datasets.ImageFolder可以直接读取,不需要自己写标注文件解析逻辑。类名里面带下划线是因为原始数据集名称包含空格,文件名在跨平台传递时容易出问题,所以我统一替换成了下划线。
2.3 数据增强:别让模型背过拟合的锅
数据增强是这次项目中提升效果最明显的单项操作,没有之一。它的思路很简单:训练时每次给模型看的图片都不是原图,而是经过随机变换的版本,相当于用有限的原始数据创造出了更大的训练集。
我用的增强策略是Albumentations库实现的,配置如下:
import albumentations as A from albumentations.pytorch import ToTensorV2 train_transform = A.Compose([ A.Resize(224, 224), A.RandomBrightnessContrast(brightness_limit=0.2, contrast_limit=0.2, p=0.5), A.HorizontalFlip(p=0.5), A.RandomRotate90(p=0.5), A.ShiftScaleRotate(shift_limit=0.05, scale_limit=0.1, rotate_limit=15, p=0.5), A.CoarseDropout(max_holes=8, max_height=20, max_width=20, fill_value=0, p=0.3), A.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]), ToTensorV2(), ]) val_transform = A.Compose([ A.Resize(224, 224), A.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]), ToTensorV2(), ])每个增强操作背后都有原因:
- 随机亮度/对比度调整:模拟田间不同时间段的光照条件,植物在强光、阴天、逆光下拍出来的照片亮度差异很大。
- 水平翻转和旋转:叶片在自然状态下朝向随机,模型必须对朝向不敏感。
- ShiftScaleRotate:模拟拍摄时角度和距离的变化。
- CoarseDropout:随机遮掉图像中的几块区域,强迫模型不要依赖局部的单一特征,而是综合多区域信息做判断。这个操作对防止模型把注意力集中在叶片边缘特别有效。
一个关键细节是:验证集和测试集绝对不做任何随机增强,只做尺寸缩放和归一化。不然评估结果就会虚高,因为模型相当于“见过”了测试集的随机扰动版本。
3. 模型选型与网络结构设计:为什么是CNN,为什么是MobileNetV3
3.1 从全连接到卷积:特征提取方式的本质区别
最早我试过用全连接网络直接吃像素,效果很差。原因在于全连接层把每个像素都当成独立的输入,忽略了图像中“相邻像素之间有关联”这个最核心的结构性特征。224×224的RGB图展开后有15万个输入维度,第一层全连接的参数量就超过1亿,训练起来既慢又容易过拟合。
卷积神经网络解决了这个问题,核心是三个设计思想:
- 局部感受野:每个卷积核只看一个小邻域,捕捉的是局部纹理特征,符合图像中病害病斑往往呈局部斑块状分布的特点。
- 权重共享:同一个卷积核滑过整张图像,无论病斑出现在叶片左上角还是右下角,都用同一套参数来检测。这给了模型平移不变性。
- 多层级特征抽象:浅层卷积核学到的是边缘、颜色块等低级特征,深层卷积核把低级特征组合成病斑形状、纹理等高级语义特征。
从应用角度,我的理解是:CNN相当于把“如何提取特征”这件事也交给了数据来学习,不再靠人来设计特征。这也是它能在大量视觉任务上超越传统方法的原因。
3.2 经典网络对比:MobileNetV3在本项目中的胜出理由
选骨干网络的时候,我对比了几种常见架构,重点关注准确率、参数量和推理耗时的平衡。VGG16虽然结构简单、容易理解,但138M的参数量在移动设备上根本无法接受。ResNet50是工业界最常用的骨干网络,25.6M参数,准确率高,但推理延迟偏高。EfficientNet-B0精度和效率都不错,但训练和调试的复杂度更高。
我最终选了MobileNetV3-Small,结构上用深度可分离卷积替代标准卷积,还引入了Squeeze-and-Excitation注意力机制,在极小计算量下保持不错的精度。下面是几个备选网络的直观对比:
| 模型 | 参数量 | Top-1准确率(ImageNet) | 单张CPU推理耗时 | 适合场景 |
|---|---|---|---|---|
| VGG16 | 138M | 71.5% | 约210ms | 教学演示、结构简单的基线 |
| ResNet50 | 25.6M | 76.1% | 约85ms | 服务器端高精度场景 |
| MobileNetV3-Small | 2.5M | 67.4% | 约35ms | 移动端、边缘设备、实时推理 |
| EfficientNet-B0 | 5.3M | 77.1% | 约90ms | 精度优先且算力有限 |
选择MobileNetV3-Small还有一层考虑:农业场景的落地设备往往是树莓派、Jetson Nano或者手机这类低功耗终端,不是数据中心里的GPU服务器。一个模型如果只能在带独显的电脑上跑,对田间场景帮助有限。实测下来,MobileNetV3-Small在这个10类病害分类任务上,验证集准确率能到97.6%,单张推理仅需35ms,完全满足实用要求。
3.3 迁移学习,还是从零训练?
这个项目的训练策略我直接用了迁移学习:加载ImageNet预训练权重,把最后一层全连接替换成10类输出。原因是病虫害叶片图像虽然和ImageNet里的日常物体差异很大,但底层的边缘、纹理、颜色分布等基础特征是有共通性的。预训练模型相当于已经学会了“怎么看图”,我们只需要在它基础上微调它“重点看什么”。
迁移学习带来的提升非常明显。同样的数据,从零训练MobileNetV3-Small,30个epoch后验证准确率才75%;加载预训练权重后,第一个epoch验证准确率就到了87%,最终收敛到97%以上。
微调时的参数设置也有讲究:冻结前几层(只微调深层特征),初始学习率要比从零训练低一个量级(我用的3e-4),防止预训练权重被破坏。如果你对原理感兴趣,可以理解为预训练权重是一个“已经读过大量图片的人”,微调是给这个人培训植保知识,学习率太大相当于把原来的知识全忘光了重新学。
4. 源码拆解:数据加载、训练循环与推理脚本是怎么协同的
4.1 项目目录结构
拿到源码之后,先看目录结构,整体是这样的:
plant_disease/ ├── data/ # 数据集根目录 │ ├── train/ # 训练集,按类别分子目录 │ ├── val/ # 验证集 │ └── test/ # 测试集 ├── models/ │ ├── __init__.py │ └── mobilenetv3.py # 模型定义和修改 ├── utils/ │ ├── __init__.py │ ├── dataset.py # 自定义Dataset │ └── transforms.py # 数据增强配置 ├── checkpoints/ # 训练好的权重文件 ├── train.py # 训练入口 ├── predict.py # 推理脚本 ├── requirements.txt └── README.md把数据加载、模型定义、训练、推理分开,目的是让每个模块可以独立替换和测试。比如后面你想把MobileNetV3换成ResNet50,只需要改models目录下的文件,数据、训练、推理部分完全不用动。
4.2 数据加载模块:Dataset和DataLoader的工程细节
自定义Dataset的核心逻辑在utils/dataset.py里。我直接继承了PyTorch的Dataset类,在初始化时扫描整个目录,把每张图片的路径和对应标签存成列表,这样在__getitem__里只需要按索引读取即可,速度很快:
import os import cv2 from torch.utils.data import Dataset class PlantDataset(Dataset): def __init__(self, root_dir, transform=None): self.classes = sorted(os.listdir(root_dir)) self.class_to_idx = {cls: i for i, cls in enumerate(self.classes)} self.samples = [] for cls in self.classes: cls_dir = os.path.join(root_dir, cls) for img_name in os.listdir(cls_dir): self.samples.append((os.path.join(cls_dir, img_name), self.class_to_idx[cls])) self.transform = transform def __len__(self): return len(self.samples) def __getitem__(self, idx): img_path, label = self.samples[idx] image = cv2.imread(img_path) image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) if self.transform: image = self.transform(image) return image, label用OpenCV而不是PIL读取图片,是因为OpenCV对常见格式的支持更好,处理速度也更快。注意它的默认通道顺序是BGR,必须转成RGB,否则模型看到的颜色是反的,训练效果会非常诡异。
训练时要用DataLoader包一层,设置shuffle=True和num_workers。num_workers这个参数值得调,指的是用几个子进程来读取数据。在Windows上设成2或4通常够用,Linux服务器上可以开更高;如果你的数据量比较小,不用太纠结这个参数的收益。
4.3 训练主循环:每一步在干什么
train.py的核心逻辑可以精简成这样:
for epoch in range(epochs): model.train() train_loss = 0.0 for images, labels in train_loader: images, labels = images.to(device), labels.to(device) optimizer.zero_grad() outputs = model(images) loss = criterion(outputs, labels) loss.backward() optimizer.step() train_loss += loss.item() # 验证 model.eval() val_correct = 0 val_total = 0 with torch.no_grad(): for images, labels in val_loader: images, labels = images.to(device), labels.to(device) outputs = model(images) _, predicted = torch.max(outputs, 1) val_correct += (predicted == labels).sum().item() val_total += labels.size(0) val_acc = val_correct / val_total print(f"Epoch {epoch+1}/{epochs} | train_loss: {train_loss:.4f} | val_acc: {val_acc:.4f}") # 保存最优权重 if val_acc > best_acc: best_acc = val_acc torch.save(model.state_dict(), "checkpoints/best_model.pth")两个细节容易被新手忽略:
model.train()和model.eval()必须成对出现。train()模式下,Dropout和BatchNorm的行为是训练态;eval()模式下,BatchNorm会使用固定的running mean和variance,Dropout直接失效。忘记切换eval(),推理结果是不稳定的。- 验证阶段一定要包在
torch.no_grad()里。这告诉PyTorch不需要计算梯度,既省显存又加速。我见过有人在验证时不写这一行,结果显存直接爆掉。
保存权重时只保存state_dict()而不保存整个模型对象,是工程上的好习惯。前者只包含参数,文件小,跨PyTorch版本兼容性好;后者把整个模型结构和代码绑定在一起,换环境后经常加载失败。
4.4 推理模块:从权重文件到单张图片预测
推理脚本predict.py是最终用户接触最多的部分,它的逻辑是:读图 - 预处理 - 模型前向 - 输出Top-K置信度:
import torch import torch.nn.functional as F from models.mobilenetv3 import build_model from utils.transforms import val_transform def predict(image_path, model, class_names, device, top_k=3): image = cv2.imread(image_path) image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) image = val_transform(image=image)["image"].unsqueeze(0).to(device) model.eval() with torch.no_grad(): logits = model(image) probs = F.softmax(logits, dim=1) top_probs, top_indices = torch.topk(probs, top_k, dim=1) results = [(class_names[idx], float(prob)) for idx, prob in zip(top_indices[0], top_probs[0])] return results输出softmax概率而不是直接用argmax,是一个很实用的习惯。argmax只告诉你是哪一类,softmax还能告诉你模型对判断有多确定。在农业生产场景里,低置信度的结果应该触发“人工复核”而不是直接让农户用药。我设置了0.8的阈值,低于这个置信度的结果会在界面上显示“置信度不足,建议咨询当地植保站”,这在落地时是保命的设计。
requirements.txt里需要列全依赖:torch、torchvision、albumentations、opencv-python、numpy、pillow。最好再注明版本号,比如torch>=1.13.0,因为PyTorch不同版本之间API有细微差异,固定版本可以避免很多环境问题。
5. 训练实测与调参记录:一组能复现的参数和三次踩坑
5.1 一组能复现的参数
完整的训练参数我在文档里写了,这里把关键配置列出来,照抄基本能复现:
| 参数 | 值 | 说明 |
|---|---|---|
| 输入尺寸 | 224×224 | 视觉模型常用尺寸,速度和精度平衡 |
| Batch Size | 32 | 单张显卡显存8G以内可以跑得动 |
| 优化器 | AdamW | Adam的改进版,权重衰减处理更合理 |
| 初始学习率 | 3e-4 | 迁移学习常用的初始值 |
| 学习率调度 | CosineAnnealingLR | 训练后期学习率平滑下降,收敛更稳 |
| Weight Decay | 1e-4 | 正则化,抑制过拟合 |
| Epochs | 60 | 配合早停,实际在第43个epoch左右达到最优 |
| 损失函数 | CrossEntropyLoss / FocalLoss | 类别均衡时用前者,不均衡时用后者 |
训练曲线大致是这样的:前5个epoch验证准确率从87%快速升到93%;第8到第25个epoch缓慢爬升到96%附近;第30个epoch之后进入平台期,验证准确率在96.5%到97.6%之间波动;最终选择验证集最优的权重,测试集准确率97.6%。
5.2 踩坑一:类别不均衡导致“什么都识别成健康叶片”
第一次训练时,我用的是全量38类PlantVillage数据,没有做类别筛选。训练过程中Loss下降得很漂亮,验证集准确率95%以上,看起来一切正常。但当我打印出混淆矩阵时发现问题很大:好几类病害的召回率不到60%,模型把绝大多数的叶片都预测成了健康类别。
原因很简单:数据集中健康叶片的数量远多于某些病害类别,模型发现“全部预测成健康”也能拿到很高的整体准确率,于是就走上了偷懒的路。
解决方案有两步。第一,把类别筛到10个,每类样本量都在1500张以上,缓解不均衡;第二,损失函数换成了Focal Loss(gamma=2)。Focal Loss的核心思路是降低那些容易分类样本的权重,把模型的注意力引向难分类的少数类样本。换完之后,之前那几类召回率只有60%的病害类别提升到了90%以上。
这个坑给我的教训是:只看整体准确率在类别不均衡的数据上会骗人,一定要看每个类别的精确率和召回率。
5.3 踩坑二:训练集准确率100%,验证集却卡在82%
迁移学习初期的另一个问题是过拟合。有一次训练到第20个epoch,训练集准确率已经100%,但验证集一直卡在82%左右上不去,说明模型在死记训练集的各种细节,没有学到可泛化的特征。
我做了三处调整,按效果排序:
- 把数据增强强度拉满,特别是CoarseDropout和ShiftScaleRotate。增强的本质是让模型每次看到的都是不完全一样的图片,逼迫它学更本质的叶片纹理特征。
- 增大weight decay到1e-4,相当于给模型参数加了一个收缩惩罚,防止某些权重值过大、过度拟合训练集中的噪声。
- 在全连接层前加Dropout(0.3),随机让一部分神经元失活,降低网络对特定神经元的依赖。
做完这三步之后,验证集准确率在同样的epoch数内从82%升到了95%以上。所以遇到训练集和验证集差距大的问题,别急着调模型结构,先看数据增强和正则化有没有做到位。
5.4 踩坑三:测试集上的表现崩了——背景和光照的“恶意”
这是最有意思的一个坑,也是很多公开数据集项目的通病。当你用PlantVillage里的测试集评估时,准确率能到97%以上;但拿自己手机在田间拍的照片实测,准确率可能直接掉到80%出头。
我一开始以为是模型训练得不好,后来用Grad-CAM可视化模型的注意力区域,发现Deep Learning模型压根没学到叶片纹理,而是学到了背景颜色。PlantVillage的图片大多是实验室统一背景拍出来的,模型学到的规律是“灰色背景 = 某类病害”,根本不是在判断叶片上的病斑特征。
解决思路有三条:
- 在训练时做背景增强:把叶片从原图中抠出来(或者用分割模型粗提取),贴到田间杂草地、泥土、天空等不同背景上,让模型知道背景信息不重要。
- 混入实地拍摄的照片做训练,哪怕数量不多,也能大幅提升泛化能力。
- 推理时加入前处理判断,如果图片中叶片占比过小,先提示用户重新拍摄。
做了背景增强之后,我拿真实的田间照片测试,准确率从81%提升到了93%。这一步对项目从“实验室玩具”走向“田间工具”至关重要。
6. 效果评估与部署落地:别只在测试集上自嗨
6.1 准确率之外:你要关注的指标
很多做图像分类的同学汇报结果时只讲准确率,我觉得这在病虫害识别场景中远远不够。准确率是整体指标,但它掩盖了类别间的差异。我用的评估方式是分类报告加上混淆矩阵:
| 指标 | 数值 |
|---|---|
| 整体准确率 | 97.6% |
| 宏平均精确率 | 96.8% |
| 宏平均召回率 | 96.2% |
| 宏平均F1 | 96.5% |
宏平均和加权平均的差别在于:宏平均对每个类别一视同仁,加权平均按样本量加权。对病虫害识别来说,我更关注宏平均,因为少数类病害恰恰是最需要被正确识别出来的——它们往往危害更大、扩散更快。
另外,如果项目以后扩展到检测任务,还需要引入mAP(mean Average Precision)指标。不过分类阶段,把混淆矩阵和分类报告看明白,已经能覆盖大多数问题了。
6.2 从训练环境到实际部署:模型是怎么跑起来的
模型训练用的是GPU服务器,但实际部署环境可能是普通电脑、树莓派或者手机。在我这个项目里,推理部署我有两个推荐方案:
第一个方案是直接用PyTorch加载权重跑,适合原型验证和本地使用,Python环境配置好就行。但PyTorch在CPU上的推理效率并不是最优的,如果追求速度,可以导出成ONNX格式,用ONNX Runtime来推理。
第二个方案是导出ONNX后,在边缘设备上部署。我实测过一组数据:
| 推理环境 | 单张耗时 | 备注 |
|---|---|---|
| PyTorch + CPU (i5-10400) | 约38ms | 直接用PyTorch |
| ONNX Runtime + CPU | 约35ms | 导出后无精度损失 |
| ONNX Runtime + int8量化 | 约22ms | 准确率下降约0.8%,可接受 |
| 树莓派4B | 约220ms | 轻量设备,勉强实时 |
导出ONNX的关键步骤很简单:
model.eval() dummy_input = torch.randn(1, 3, 224, 224, device="cpu") torch.onnx.export( model, dummy_input, "plant_disease.onnx", input_names=["input"], output_names=["output"], dynamic_axes={"input": {0: "batch_size"}, "output": {0: "batch_size"}}, )导出后建议用onnxruntime验证一遍输入输出是否与PyTorch模型一致,特别是BatchNorm层和Dropout层在推理模式的差异。
6.3 还能怎么扩展:从分类到检测,再到全流程系统
这个项目做完分类之后,很自然的扩展方向是目标检测。分类模型假设叶片已经占据图片主体,但实际使用中,用户拍的照片可能是整株作物,叶片之间有重叠、有遮挡。这种情况下YOLO系列检测模型更适用,它能框出每个叶片的位置并分别判断病害类别。检测模型的训练数据标注成本更高,但对真实场景的适配能力是分类模型无法比的。
另一个扩展方向是把模型封装成Web服务,比如用Flask或FastAPI写一个简单的HTTP接口,前端用Streamlit做一个上传图片就能出结果的页面。这样非技术用户也能直接使用,而不需要装Python环境。
如果对性能有更高要求,可以做模型蒸馏:用ResNet50这种大模型当教师,把知识蒸馏到MobileNetV3这个学生模型里,能在不增加推理耗时的前提下再提升一两个百分点的准确率。
做这个项目最大的体会是,最花时间的不是模型代码,而是数据准备和踩坑调试。模型选型、训练参数这些在网上都有大量现成方案,但数据清洗、背景增强、类别不均衡处理这些工程细节决定了一个项目到底是纸面效果还是实际可用。如果你打算复现这个项目,我的建议是:先用小数据集把整个流程跑通,再逐步加数据和调整;优先保证评估指标里每个类别都及格,再追求整体准确率的数字好看。拿到97%以上的准确率之后,顺手拿手机拍几张真实场景的照片喂给模型,它的真实实力才会暴露出来。
本文还有配套的精品资源,点击获取