☰
基于Python的农作物病虫害图像识别毕设项目:从数据集到部署全流程
2026/10/12 23:49:35 网站建设 项目流程

简介:这是一份面向计算机相关专业学生与深度学习入门者的农作物病虫害图像识别项目源码包,可直接用于毕业设计、课程设计或项目立项演示。资源共373个文件,压缩包约91.56MB,以18个Python脚本为核心,配套12个pyc编译文件、2份md说明与2份txt文档,并包含大量卷积层与全连接层的权重、偏置、BN均值方差等模型参数文件,以及conf配置、日志与预览图片,完整覆盖从数据读取、模型构建到推理部署的工程链路。项目代码经过测试可正常运行,附带设计文档,适合人工智能、通信、自动化、物联网等方向的学习者借鉴。目前已有170人学习下载。读者可据此理解病虫害分类网络的目录组织与参数加载方式,掌握模型训练与推理的排错思路,并在此基础上修改网络结构或替换数据集,快速完成自己的毕设与课设任务。

1. 从一份毕设 zip 说起:农作物病虫害图像识别到底难在哪

每年三四月,实验室里总有人抱着一台笔记本,桌面上躺着一个叫「农作物病虫害图像识别项目python源代码及说明(2024毕设作品).zip」的压缩包,解压之后一脸茫然:train.py能跑,但换成自己的照片就全错;准确率写着 98%,可田里拍一张就翻车。这不是个例,而是这个方向最典型的现状——数据集里全是摆拍,真实场景全是玄学。

这个标题背后其实是一套完整的落地链路:Python 图像识别、农作物病虫害分类、可复现的源代码、以及一份能讲清楚原理的说明。它解决的不是「识别一片叶子有没有病」这么简单,而是让一个没有深度学习基础的人,能在本地把数据、模型、训练、推理四件事串起来,最后交出一份能答辩、能演示、能继续改的作品。适合谁?计算机、农学、自动化方向的本科生,以及想用 Python 快速验证一个视觉分类想法的工程师。下面我按自己带过几届毕设的经验,把这条路拆开讲。

2. 数据先行:病虫害图像数据集怎么选、怎么洗、怎么切

2.1 公开数据集与自采数据的取舍

做农作物病虫害识别,第一步不是写模型,是找数据。常见做法有两类:一类是公开数据集,比如 PlantVillage 这类叶片特写图,优点是标注干净、类别齐全,缺点是背景单一、光照均匀,模型学到的往往是「背景」而不是「病斑」;另一类是自采数据,用手机在田间拍,背景杂乱、光照多变,但更接近真实推理场景。

我的建议是两者混用:公开数据集做预训练和基线,自采数据做微调和验证。如果时间紧,至少保证每个类别 200 张以上,且训练集里必须包含一部分「非摆拍」样本。否则答辩时老师拿手机随手拍一张,模型直接输出一个高置信度的错误类别,场面会很难看。

数据清洗主要做三件事:去重(感知哈希或简单的像素差)、去模糊(拉普拉斯方差阈值)、去错标(人工抽查每个类别前 20 张)。这三步做完,往往能砍掉 10% 到 20% 的脏数据,比调参带来的提升更明显。

2.2 用 Python 做数据划分与增强的最小脚本

下面这段代码是我一般会放在prepare_data.py里的骨架,负责按类别划分训练/验证集,并做基础增强。它不依赖复杂框架,torchvision和Pillow就够。

import os import random import shutil from pathlib import Path from PIL import Image from torchvision import transforms # 原始数据目录结构:raw/类别名/xxx.jpg RAW_DIR = Path("raw") OUT_DIR = Path("dataset") SPLIT_RATIO = 0.8 # 训练集比例 SEED = 42 random.seed(SEED) # 基础增强:只对训练集做,验证集保持原样 train_tf = transforms.Compose([ transforms.Resize((224, 224)), transforms.RandomHorizontalFlip(p=0.5), transforms.RandomRotation(15), transforms.ColorJitter(brightness=0.2, contrast=0.2), ]) def is_blurry(img_path, threshold=100.0): """用拉普拉斯方差粗筛模糊图,数值越低越模糊""" import cv2 img = cv2.imread(str(img_path), cv2.IMREAD_GRAYSCALE) if img is None: return True return cv2.Laplacian(img, cv2.CV_64F).var() < threshold for cls_dir in sorted(RAW_DIR.iterdir()): if not cls_dir.is_dir(): continue imgs = [p for p in cls_dir.glob("*.jpg") if not is_blurry(p)] random.shuffle(imgs) n_train = int(len(imgs) * SPLIT_RATIO) for split, subset in [("train", imgs[:n_train]), ("val", imgs[n_train:])]: out_cls = OUT_DIR / split / cls_dir.name out_cls.mkdir(parents=True, exist_ok=True) for i, p in enumerate(subset): img = Image.open(p).convert("RGB") if split == "train": img = train_tf(img) else: img = transforms.Resize((224, 224))(img) img.save(out_cls / f"{i:04d}.jpg")

逻辑说明:先按类别遍历,用拉普拉斯方差过滤模糊图,再按 8:2 切分。训练集走增强管道,验证集只做 resize,保证评估结果稳定。参数上,SPLIT_RATIO在样本少时可以调到 0.7,threshold根据自己手机拍照的清晰度调,一般 80 到 150 之间。SEED固定是为了让每次划分一致,方便复现。

提示:增强不要加垂直翻转,叶片方向在农业场景里有意义;颜色抖动幅度也别太大,病斑颜色是重要特征。

2.3 类别不平衡与「背景类」的坑

田间数据天然不平衡,健康叶片往往远多于某种罕见病。直接训练会让模型偏向多数类。常见做法是给CrossEntropyLoss加weight参数,权重按类别频率的倒数计算;或者用重采样,但重采样容易过拟合少数类。我一般先用加权损失,效果不够再考虑 focal loss。

另一个坑是「背景类」。如果数据集里混进了大量非叶片图片,模型会学出一个「其他」类别,但真实推理时任何一张图都会被强行分到某个病类。解决办法是单独加一个background类,放土壤、天空、手部等负样本,比例控制在总数据的 5% 到 10%。

3. 模型选型与训练:从 ResNet 到轻量网络的取舍

3.1 为什么毕设场景优先选迁移学习

从零训练一个病虫害分类模型,需要几十万张图和大量算力,毕设周期根本不够。迁移学习是唯一现实的选择:用 ImageNet 预训练的 ResNet50、EfficientNet-B0 或 MobileNetV3,替换最后的全连接层,冻结主干先训分类头,再解冻部分层微调。

选哪个?如果答辩机器是普通笔记本、没有独显,MobileNetV3 或 EfficientNet-B0 更稳,推理快、显存小;如果有显卡且追求精度,ResNet50 是保险牌。我一般会先跑一个 MobileNetV3 基线,确认流程通了,再换大模型对比。

3.2 训练脚本的关键参数与早停策略

下面是一个基于 PyTorch 的训练骨架,重点在冻结策略、学习率和早停。

import torch import torch.nn as nn from torch.utils.data import DataLoader from torchvision import datasets, models, transforms device = torch.device("cuda" if torch.cuda.is_available() else "cpu") BATCH_SIZE = 32 EPOCHS = 30 LR_HEAD = 1e-3 # 只训分类头时的学习率 LR_FINETUNE = 1e-4 # 解冻后的学习率 PATIENCE = 5 # 早停耐心值 train_ds = datasets.ImageFolder("dataset/train", transform=transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]), ])) val_ds = datasets.ImageFolder("dataset/val", transform=transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]), ])) train_loader = DataLoader(train_ds, batch_size=BATCH_SIZE, shuffle=True, num_workers=4) val_loader = DataLoader(val_ds, batch_size=BATCH_SIZE, shuffle=False, num_workers=4) model = models.mobilenet_v3_small(weights=models.MobileNet_V3_Small_Weights.DEFAULT) for p in model.parameters(): p.requires_grad = False model.classifier[3] = nn.Linear(model.classifier[3].in_features, len(train_ds.classes)) model = model.to(device) criterion = nn.CrossEntropyLoss() optimizer = torch.optim.AdamW(model.classifier.parameters(), lr=LR_HEAD) scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=EPOCHS) best_acc, wait = 0.0, 0 for epoch in range(EPOCHS): if epoch == 5: # 第 5 轮解冻最后两个 block for name, p in model.named_parameters(): if "features.12" in name or "features.11" in name: p.requires_grad = True optimizer = torch.optim.AdamW(filter(lambda p: p.requires_grad, model.parameters()), lr=LR_FINETUNE) model.train() for x, y in train_loader: x, y = x.to(device), y.to(device) optimizer.zero_grad() loss = criterion(model(x), y) loss.backward() optimizer.step() scheduler.step() model.eval() correct = total = 0 with torch.no_grad(): for x, y in val_loader: x, y = x.to(device), y.to(device) pred = model(x).argmax(1) correct += (pred == y).sum().item() total += y.size(0) acc = correct / total print(f"epoch {epoch} val_acc {acc:.4f}") if acc > best_acc: best_acc, wait = acc, 0 torch.save(model.state_dict(), "best.pth") else: wait += 1 if wait >= PATIENCE: print("early stop") break

逻辑说明:前 5 轮只训分类头,让随机初始化的全连接层先收敛,避免一开始就破坏预训练特征;第 5 轮解冻features.11和features.12两个 block,学习率降到 1e-4。CosineAnnealingLR让学习率平滑下降,PATIENCE=5防止过拟合。参数上,BATCH_SIZE显存不够就降到 16,num_workers在 Windows 上设 0 更稳。

注意:ImageFolder要求目录名就是类别名,且类别顺序按字母排序,推理时务必用同一套映射,否则标签会错位。

3.3 评估指标别只看准确率

病虫害识别里,准确率会被多数类拉高。必须同时看混淆矩阵和每类的 F1。常见做法是训练完在验证集上跑一遍sklearn.metrics.classification_report,重点看召回率低的类别——那往往是样本少或特征不明显的病种。如果某个类召回率低于 0.6,优先补数据,而不是继续调参。

4. 推理与部署:把模型塞进一个能演示的界面

4.1 单张图片推理的最小闭环

训练完只是半成品,答辩要的是「上传一张图,出结果」。下面这段推理代码把模型加载、预处理、预测、置信度输出串起来。

import torch from torchvision import models, transforms from PIL import Image import torch.nn as nn CLASSES = ["健康", "早疫病", "晚疫病", "叶霉病"] # 必须与训练时字母序一致 device = torch.device("cpu") model = models.mobilenet_v3_small(weights=None) model.classifier[3] = nn.Linear(model.classifier[3].in_features, len(CLASSES)) model.load_state_dict(torch.load("best.pth", map_location=device)) model.eval() tf = transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]), ]) def predict(img_path): img = Image.open(img_path).convert("RGB") x = tf(img).unsqueeze(0) with torch.no_grad(): prob = torch.softmax(model(x), dim=1)[0] idx = prob.argmax().item() return CLASSES[idx], prob[idx].item() if __name__ == "__main__": label, conf = predict("test.jpg") print(f"预测:{label},置信度:{conf:.3f}")

逻辑说明:CLASSES必须和训练时ImageFolder的类别顺序完全一致,这是最常见的翻车点。map_location="cpu"保证在没有 GPU 的机器上也能加载。置信度低于 0.5 时,建议在界面上提示「不确定」,而不是硬给一个结果。

4.2 用 Gradio 快速搭一个演示界面

毕设演示不需要复杂前端,Gradio 几行就能出一个网页。把上面的predict包一层即可。

import gradio as gr def ui_predict(img): img = Image.fromarray(img) img.save("_tmp.jpg") label, conf = predict("_tmp.jpg") return f"{label}(置信度 {conf:.2f})" gr.Interface(fn=ui_predict, inputs=gr.Image(), outputs="text", title="农作物病虫害识别").launch()

参数上,gr.Image()默认返回 numpy 数组,所以要先转 PIL。launch()默认本地 7860 端口,答辩时提前跑一遍确认端口没被占。

4.3 推理速度与模型导出

如果答辩机器慢,可以把模型导出成 ONNX,用onnxruntime推理,CPU 上通常比 PyTorch 快 1.5 到 2 倍。导出时注意固定输入尺寸为 1x3x224x224,动态轴只保留 batch。常见做法是导出后跑 100 次取平均耗时,写进说明文档,答辩时是个加分项。

5. 避坑与排查:那些让毕设翻车的细节

5.1 训练准确率高、演示全错

现象:验证集 98%,现场拍一张健康叶子却识别成病害。原因通常是数据集背景单一,模型学到了「背景」而非「病斑」,或者演示图片的预处理和训练不一致。解决:训练时加入自采的杂乱背景图,推理时严格复用训练用的归一化参数,并检查图片是否被提前压缩。

5.2 类别标签错位

现象:模型输出「晚疫病」,但实际是「早疫病」,且置信度很高。原因:训练时ImageFolder按字母序生成类别索引,推理时手写的CLASSES顺序不一致。解决:训练完立刻打印train_ds.classes并保存成classes.json,推理时读取同一个文件,不要手写。

5.3 显存不足或训练中断

现象:跑到一半报 CUDA out of memory。原因:BATCH_SIZE太大,或num_workers过多导致内存泄漏。解决:把 batch 降到 16 或 8,num_workers设 2,并在训练循环里加torch.cuda.empty_cache()。如果还是不行,改用 MobileNetV3 并冻结更多层。

5.4 验证集准确率虚高

现象:验证集和训练集图片来自同一批拍摄,分布几乎一样。原因:划分时没有按拍摄批次或地块划分,导致数据泄漏。解决:按拍摄时间或地块做分组划分,同一批次的图要么全在训练集,要么全在验证集。

5.5 中文路径导致读取失败

现象:Image.open报错或返回 None。原因:OpenCV 和部分库对中文路径支持不好。解决:统一用英文路径,或在读取前用cv2.imdecode(np.fromfile(path, dtype=np.uint8), -1)绕过。

6. 让毕设多拿几分:可解释性与对比实验的具体做法

答辩时老师最爱问两个问题:「你怎么知道模型看的是病斑而不是背景?」和「你凭什么说这个模型好?」这两个问题都能用具体手段回答,而不是靠嘴。

第一个问题用 Grad-CAM。它能把模型关注区域热力图叠加到原图上,如果高亮落在病斑上,说明模型学到了正确特征。实现上,pytorch-grad-cam库几行就能出图,选最后一个卷积层作为目标层。我一般会挑三张图:一张正确、一张错误、一张置信度低,放在答辩 PPT 里对比,比任何文字都有说服力。

第二个问题靠对比实验。至少跑三组:MobileNetV3、ResNet18、EfficientNet-B0,固定同一份数据划分和增强策略,记录验证集准确率、每类 F1、单张推理耗时。表格里把参数量和模型大小也列上,说明你是在精度和速度之间做了权衡,而不是随便选了一个。常见做法是选 MobileNetV3 作为最终模型,因为它在你机器上推理最快,精度只差一两个点。

还有一个容易被忽略的技巧:把失败案例单独整理成一页。挑 5 张识别错误的图,分析原因——是光照太暗、病斑太小,还是类别本身相似。这页往往比成功案例更能体现你真的理解数据。我带过的学生里,凡是认真做了这一页的,答辩分数都不低。

最后说个我自己的习惯:每次改完代码,先跑一遍 10 张图的推理,确认输出和上次一致,再开始训练。这个「后悔药」动作帮我省过无数次因为标签错位、预处理不一致导致的整夜白跑。毕设拼的不是模型多先进,而是每个环节都能复现、能解释、能兜底。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询