ARTICLE DETAIL

资讯详情

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

农作物病虫害图像识别毕设:从源码复现到边缘部署的完整指南

农作物病虫害图像识别毕设:从源码复现到边缘部署的完整指南 简介这份资源是2024年毕业设计级别的农作物病虫害图像识别项目面向计算机、人工智能、自动化、通信工程等专业的高校学生与科研人员可用于毕设、课程设计、作业或项目立项演示也适合具备一定基础的学习者进阶参考。压缩包共373个文件约91.56MB包含18个Python源码文件、12个pyc编译文件以及大量深度学习模型权重与BN层参数文件如conv、fc系列的weights、offset、scale、variance等另附txt说明、md文档、conf配置、jpg示例图和log日志完整覆盖模型定义、训练与推理所需的核心文件。目前已有169人学习下载。项目代码完整、资料齐全含设计文档经过测试可正常运行读者可据此理解卷积神经网络在病虫害图像分类中的实现流程借鉴网络结构、权重加载与配置管理方式并在此基础上修改扩展功能遇到配置或运行问题还可获得远程指导适合作为深度学习入门与实战的参考案例。1. 农作物病虫害图像识别项目一份毕设源码里真正值得复现的东西每年三四月计算机和农学交叉方向的毕设选题里农作物病虫害图像识别几乎是出现频率最高的一个。原因很直接数据集公开、任务定义清晰、效果肉眼可见答辩时放几张识别前后对比图就能讲明白。但真正动手做过的人都知道从「下载一个 zip 源码包」到「跑出能写进论文的准确率」中间隔着一堆没人告诉你的细节——类别不均衡、叶片背景干扰、训练集和田间实拍分布不一致任何一个都能让模型在测试集上好看、到真实场景里翻车。这个标题指向的是一套完整的毕设交付物Python 源代码加上说明文档核心任务是用深度学习做农作物叶部病害的分类或检测。它适合三类人正在找毕设选题、需要一份能跑通且能讲清楚技术路线的本科生想快速搭一个图像分类 baseline 再往上加改进的研究生以及需要把模型部署到边缘设备做田间巡检的工程人员。下面我按「数据怎么准备、模型怎么选、代码怎么跑、坑在哪」的顺序把这类项目里真正决定成败的环节拆开讲。2. 从 zip 到可运行环境依赖、目录与数据管线的搭建拿到一个图像识别项目的源码包第一件事不是急着python train.py而是先搞清楚它的目录约定和数据组织方式。绝大多数毕设级项目的结构是固定的一个data目录放按类别分文件夹的图像一个models目录放网络定义一个train.py加一个predict.py再加一个requirements.txt。理解这套约定比记住任何一行代码都重要因为后面所有的路径报错、标签错位、类别数不匹配根源都在这里。2.1 用 Anaconda 隔离环境并锁定依赖版本深度学习项目最怕的就是版本冲突。你系统里可能已经装了某个版本的 PyTorch但源码包依赖的是另一个版本直接pip install会把全局环境搞乱。我一般用 conda 建一个独立环境把 Python 版本也钉死。以这类项目常见的 PyTorch 技术栈为例# 创建独立环境Python 版本按源码说明选3.8 是兼容性最稳的一档 conda create -n crop_disease python3.8 -y conda activate crop_disease # 安装 PyTorchCUDA 版本按自己显卡驱动选没有 GPU 就用 cpu 版 # 下面这行是 CUDA 11.3 的示例具体以官方安装页为准 pip install torch1.12.1cu113 torchvision0.13.1cu113 -f https://download.pytorch.org/whl/torch_stable.html # 再装项目其余依赖 pip install opencv-python pillow numpy matplotlib scikit-learn tqdm这段命令的逻辑是先隔离再装框架最后补通用库。参数上最关键的是 Python 版本和 CUDA 版本——Python 3.8 对老版本 PyTorch 兼容最好CUDA 版本必须和你的显卡驱动匹配装错了会在torch.cuda.is_available()返回 False 时才发现。装完先跑一句验证import torch print(torch.__version__) print(torch.cuda.is_available()) # 有 GPU 且装对了会返回 True如果这里返回 False 而你有显卡八成是 CUDA 版本和驱动不匹配别急着改代码先把环境问题解决掉。2.2 数据集的目录约定与标签映射图像分类项目的数据组织几乎都是「一个类别一个文件夹」这是torchvision.datasets.ImageFolder的默认约定也是这类源码包最通用的做法。典型结构长这样data/ ├── train/ │ ├── 健康/ │ ├── 早疫病/ │ ├── 晚疫病/ │ └── 叶霉病/ └── val/ ├── 健康/ ├── 早疫病/ ├── 晚疫病/ └── 叶霉病/这里有个血泪经验类别文件夹的名字会被ImageFolder按字母序映射成 0、1、2……的标签中文名排序结果和你的直觉往往不一致。所以训练完保存类别映射预测时才能把数字还原成病名import os from torchvision import datasets train_dir data/train dataset datasets.ImageFolder(train_dir) # class_to_idx 就是文件夹名到数字标签的映射务必存下来 print(dataset.class_to_idx) # 保存映射预测阶段要用 import json with open(class_indices.json, w, encodingutf-8) as f: json.dump(dataset.class_to_idx, f, ensure_asciiFalse, indent2)参数说明ImageFolder会自动扫描子目录每个子目录名就是一个类别。class_to_idx是字典键是文件夹名值是标签。把它存成 JSON预测脚本加载后就能输出中文病名。这一步看着简单但每年都有人的毕设因为训练和预测的类别顺序对不上导致识别结果张冠李戴。2.3 数据增强与训练/验证划分的实操田间拍的照片和数据集里的照片分布差异很大光照、角度、背景都不同。数据增强是缩小这个差距最便宜的手段。常见做法是在训练时随机裁剪、翻转、调亮度对比度from torchvision import transforms train_transform transforms.Compose([ transforms.Resize((224, 224)), # 统一到网络输入尺寸 transforms.RandomHorizontalFlip(p0.5), # 随机水平翻转 transforms.RandomRotation(15), # 随机旋转 ±15 度 transforms.ColorJitter(brightness0.2, contrast0.2), # 亮度对比度扰动 transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], # ImageNet 统计量 std[0.229, 0.224, 0.225]) ]) val_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]) ])逻辑说明训练集用带随机的增强验证集只做确定性的缩放和归一化这是铁律——验证集加随机增强会让评估结果不稳定。归一化用的均值和方差是 ImageNet 的统计量因为后面用的多半是 ImageNet 预训练模型输入分布要对齐。参数上Resize的尺寸必须和网络输入一致RandomRotation的角度别开太大叶片旋转 15 度以内合理转到 90 度就不像真实拍摄了。3. 模型选型与训练迁移学习为什么是毕设的最优解毕设的时间通常只有几周到一两个月从零训练一个卷积网络既不现实也没必要。农作物病害数据集规模普遍不大几千到几万张这时候迁移学习几乎是唯一正确的选择。用 ImageNet 上预训练好的骨干网络换掉最后的分类头冻结前面几层再微调往往用十分之一的算力就能拿到比从零训练高得多的准确率。这一章讲清楚为什么这么选以及具体怎么落地。3.1 ResNet、MobileNet、EfficientNet 怎么选选骨干网络要看两个约束你的显卡显存以及最终要不要部署到边缘设备。下面这张表是我在几个类似项目里总结的对比骨干网络参数量224 输入单张推理耗时GPU适合场景ResNet18约 11M很快显存小、要快速出结果ResNet50约 25M中等追求准确率、显存够MobileNetV3约 5M极快要部署到手机或树莓派EfficientNet-B0约 5M快准确率和体积平衡如果只是做毕设答辩ResNet50 是稳妥选择准确率有保障论文里也好写。如果说明文档里提到要部署到边缘设备做田间巡检那 MobileNetV3 更合适它的深度可分离卷积就是为移动端设计的。选型没有绝对优劣关键是和你的硬件、部署目标匹配。3.2 迁移学习训练脚本的关键参数下面是一个最小可运行的训练循环把迁移学习的关键点都标出来了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) # 数据加载 train_ds datasets.ImageFolder(data/train, transformtrain_transform) val_ds datasets.ImageFolder(data/val, transformval_transform) train_loader DataLoader(train_ds, batch_size32, shuffleTrue, num_workers4) val_loader DataLoader(val_ds, batch_size32, shuffleFalse, num_workers4) # 加载预训练 ResNet50替换分类头 model models.resnet50(pretrainedTrue) num_classes len(train_ds.classes) # 类别数由数据决定别写死 model.fc nn.Linear(model.fc.in_features, num_classes) model model.to(device) # 损失函数和优化器 criterion nn.CrossEntropyLoss() # 只优化分类头和最后几层学习率别开太大 optimizer torch.optim.Adam(model.parameters(), lr1e-4) # 训练循环 for epoch in range(30): model.train() for imgs, labels in train_loader: imgs, labels imgs.to(device), labels.to(device) optimizer.zero_grad() outputs model(imgs) loss criterion(outputs, labels) loss.backward() optimizer.step() # 每个 epoch 后在验证集上评估 model.eval() correct total 0 with torch.no_grad(): for imgs, labels in val_loader: imgs, labels imgs.to(device), labels.to(device) preds model(imgs).argmax(dim1) correct (preds labels).sum().item() total labels.size(0) print(fepoch {epoch}, val acc {correct/total:.4f})逻辑说明pretrainedTrue加载 ImageNet 权重model.fc是 ResNet 的全连接层替换成你数据集的类别数。优化器只用一个较小的学习率1e-4因为预训练权重已经很好学习率太大会把学到的特征破坏掉。参数上batch_size受显存限制32 是常见起点显存不够就降到 16 或 8。num_workers是数据加载的进程数设成 CPU 核心数能加快读取Windows 上有时要设成 0 避免多进程报错。3.3 类别不均衡与过拟合的处理农作物病害数据集几乎必然类别不均衡——健康叶片样本多某些罕见病害样本少。这会让模型偏向多数类少数类识别率极低。两个常用手段一是给损失函数加类别权重二是用重采样。类别权重更简单from collections import Counter import numpy as np # 统计每个类别的样本数 targets [s[1] for s in train_ds.samples] counts Counter(targets) # 权重和样本数成反比样本越少权重越大 weights [1.0 / counts[i] for i in range(num_classes)] weights torch.tensor(weights, dtypetorch.float).to(device) criterion nn.CrossEntropyLoss(weightweights)参数说明weights是每个类别的损失权重样本少的类别权重大模型会更关注它们。注意权重别设得太极端否则少数类会过拟合。过拟合的另一个信号是训练准确率远高于验证准确率这时候加 dropout、加数据增强、或者早停都能缓解。4. 推理与可视化把模型输出变成能写进论文的结果训练完模型只是完成了一半毕设答辩和论文里真正需要的是可解释的结果单张图片的识别、批量测试的混淆矩阵、以及能直观展示模型关注区域的类激活图。这一章讲怎么把模型输出变成这些材料。4.1 单张图片预测脚本预测脚本要解决的核心问题是加载模型、加载类别映射、把图片预处理成和训练时一致的格式。任何一步不一致结果都会错。import torch import json from PIL import Image from torchvision import models, transforms device torch.device(cuda if torch.cuda.is_available() else cpu) # 加载类别映射 with open(class_indices.json, r, encodingutf-8) as f: class_to_idx json.load(f) idx_to_class {v: k for k, v in class_to_idx.items()} # 重建模型结构加载权重 model models.resnet50(pretrainedFalse) model.fc torch.nn.Linear(model.fc.in_features, len(class_to_idx)) model.load_state_dict(torch.load(best_model.pth, map_locationdevice)) model model.to(device).eval() # 预处理必须和验证集一致 preprocess 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) tensor preprocess(img).unsqueeze(0).to(device) # 加 batch 维度 with torch.no_grad(): probs torch.softmax(model(tensor), dim1)[0] conf, pred probs.max(0) print(f识别结果{idx_to_class[pred.item()]}置信度{conf.item():.4f})逻辑说明pretrainedFalse是因为要加载自己训练的权重不是 ImageNet 的。unsqueeze(0)给单张图加上 batch 维度因为模型期望输入是四维张量。softmax把 logits 转成概率max取最大概率对应的类别。参数上预处理必须和验证集完全一致少一个归一化都会让结果偏差很大。4.2 混淆矩阵与分类报告答辩时老师最爱问「每个类别的识别率怎么样」混淆矩阵和分类报告就是答案。用 scikit-learn 几行就能出from sklearn.metrics import confusion_matrix, classification_report import numpy as np model.eval() all_preds, all_labels [], [] with torch.no_grad(): for imgs, labels in val_loader: imgs imgs.to(device) preds model(imgs).argmax(dim1).cpu().numpy() all_preds.extend(preds) all_labels.extend(labels.numpy()) # 分类报告每个类别的精确率、召回率、F1 print(classification_report(all_labels, all_preds, target_nameslist(class_to_idx.keys()))) # 混淆矩阵 cm confusion_matrix(all_labels, all_preds) print(cm)参数说明target_names传类别名报告里就会显示中文病名而不是数字。混淆矩阵的对角线是正确分类数非对角线是错分哪个类别容易被误判一眼就能看出来。这份报告直接可以放进论文的实验章节。4.3 用 Grad-CAM 展示模型关注区域Grad-CAM 能生成一张热力图标出模型判断时重点看了图片的哪个区域。对农作物病害识别来说如果热力图集中在病斑上说明模型学到了正确特征如果集中在背景上说明模型可能走了捷径。这在论文里是很好的可解释性材料。from pytorch_grad_cam import GradCAM from pytorch_grad_cam.utils.image import show_cam_on_image import numpy as np # 目标层选最后一个卷积块 target_layers [model.layer4[-1]] cam GradCAM(modelmodel, target_layerstarget_layers) rgb_img np.array(img.resize((224, 224))) / 255.0 input_tensor preprocess(img).unsqueeze(0).to(device) grayscale_cam cam(input_tensorinput_tensor)[0] visualization show_cam_on_image(rgb_img.astype(np.float32), grayscale_cam, use_rgbTrue) Image.fromarray(visualization).save(gradcam_result.jpg)逻辑说明target_layers指定要可视化的卷积层一般选最后一个卷积块它的特征最语义化。GradCAM会计算该层梯度并加权特征图得到热力图。参数上输入图片要和训练时同样的预处理。生成的热力图叠加在原图上红色区域是模型关注的重点。5. 避坑与排查那些让毕设卡住一整周的问题这一章是我和身边人踩过的坑按「现象 → 原因 → 解决」写每一条都对应一个真实会卡住进度的问题。现象训练时 loss 一直是 nan或者准确率卡在随机水平不动。原因最常见的是学习率太大或者数据标签有问题。标签问题往往是类别文件夹里混进了非图片文件或者某个类别文件夹是空的导致ImageFolder映射错位。 解决先把学习率降到1e-5试一次再检查每个类别文件夹的图片数量用for c in train_ds.classes: print(c, len(os.listdir(...)))逐个确认空文件夹直接删掉。现象验证集准确率很高但拿真实田间照片预测全错。原因训练集和真实场景的分布差异太大。公开数据集多是实验室或标准拍摄条件下的叶片特写背景干净、光照均匀而田间照片背景杂乱、有土壤和阴影。 解决在训练增强里加大亮度和对比度扰动加入随机裁剪模拟不同构图如果条件允许自己拍几十张田间照片做微调哪怕每类只有十几张效果提升也很明显。现象DataLoader在 Windows 上报多进程错误或者训练中途卡死。原因Windows 上num_workers大于 0 时多进程数据加载有时会和主进程冲突尤其是脚本没有放在if __name__ __main__:保护下。 解决把训练代码包进if __name__ __main__:或者直接把num_workers设成 0。代价是数据加载慢一点但稳定。现象显存不够报 CUDA out of memory。原因batch_size太大或者输入分辨率太高。224 输入下 ResNet50 用 batch 32 大概要 6G 显存ResNet18 只要 3G 左右。 解决先把batch_size减半还不行就换更小的骨干网络或者把输入尺寸从 224 降到 192。别硬扛毕设阶段稳定跑通比追求大 batch 重要。现象预测时置信度普遍很低或者所有图片都预测成同一个类别。原因预测脚本的预处理和训练时不一致最常见的是漏了归一化或者Resize尺寸对不上。另一个可能是加载权重时模型结构没对上load_state_dict静默失败。 解决把预测脚本的preprocess和训练时的val_transform逐行对比确保完全一致加载权重时用strictTrue默认如果键不匹配会直接报错而不是静默跳过。6. 从毕设到能用模型量化与边缘部署的验证方法毕设做完很多人会想再往前一步——把模型放到树莓派或者手机上做成一个能拿着去田里拍的实际工具。这一步的门槛比训练高但也不是遥不可及。核心思路是模型量化加推理引擎替换把 PyTorch 模型转成体积更小、推理更快的格式。先说量化。PyTorch 自带的动态量化对卷积网络效果不错几行就能把模型体积压到原来的四分之一左右import torch.quantization # 动态量化主要量化全连接层对卷积网络友好 quantized_model torch.quantization.quantize_dynamic( model, {torch.nn.Linear}, dtypetorch.qint8 ) torch.save(quantized_model.state_dict(), model_quantized.pth)参数说明{torch.nn.Linear}指定要量化的层类型动态量化对 Linear 层收益最大。量化后模型体积变小CPU 推理速度提升明显代价是准确率可能掉一两个百分点需要重新在验证集上评估确认可接受。如果目标是手机端更彻底的做法是导出 ONNX 再用推理引擎跑# 导出 ONNXopset 版本按推理引擎支持选 dummy_input torch.randn(1, 3, 224, 224).to(device) torch.onnx.export(model, dummy_input, crop_disease.onnx, input_names[input], output_names[output], opset_version11)导出后可以用 ONNX Runtime 验证推理结果和 PyTorch 是否一致这是部署前必须做的对齐检查import onnxruntime as ort import numpy as np sess ort.InferenceSession(crop_disease.onnx) # 用同一张图分别跑 PyTorch 和 ONNX对比输出 onnx_out sess.run(None, {input: tensor.cpu().numpy()})[0] torch_out model(tensor).cpu().detach().numpy() print(最大差异, np.abs(onnx_out - torch_out).max())差异在1e-4量级以内说明导出正确差太多就是算子不兼容或者输入格式不对。这一步是部署的后悔药——等到设备上才发现结果不对排查成本会高得多。验证方法上我一般会准备一个「田间小测试集」自己用手机在不同光照、不同角度下拍二三十张人工标好类别专门用来评估部署后的真实表现。这个集合不参与任何训练纯粹当验收标准。如果在这个集合上准确率能到 80% 以上说明模型有实用价值如果掉到 50% 以下说明训练数据和真实场景差距还是太大得回去补数据。最后说一个我自己的习惯每次改完模型或者换完部署格式我都会把「训练集准确率、验证集准确率、田间测试集准确率」三个数字并排记下来。这三个数的差距比任何单个指标都更能说明问题——训练和验证差距大是过拟合验证和田间差距大是分布不匹配。这个习惯帮我省了很多次盲目调参的时间。希望帮到你。本文还有配套的精品资源点击获取
返回列表