ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

基于CNN的农作物病虫害识别毕设项目:从训练到Flask部署全流程

2026/10/1 5:30:03 拓冰建站 浏览量
基于CNN的农作物病虫害识别毕设项目:从训练到Flask部署全流程 简介这份资源是面向计算机相关专业学生与深度学习入门者的农作物病虫害识别检测系统完整项目基于卷积神经网络实现图像分类与检测可作为高分毕业设计、课程设计或期末大作业的实战参考。压缩包共56个文件约88.3MB包含Python源码、Jupyter Notebook实验文件、数据集、运行说明文档及部署配置其中ipynb覆盖ResNet50、VGG16、VGG19、DenseNet121、PyTorch、TensorFlow、Keras、Fastai等多种模型实现另有Flask服务端脚本、Dockerfile与云部署指南便于对比不同框架的建模流程。项目已获98人学习代码经导师指导并评审通过结构完整、注释清晰小白也能按说明跑通。读者可从中掌握数据预处理、模型训练调参、评估指标分析到Web端部署的完整链路并借鉴多模型对比与工程化目录组织思路快速完成自己的识别类项目。1. 农作物病虫害识别毕设一份能跑通的 CNN 项目到底长什么样带过几届毕设之后我总结出一个规律真正卡住学生的从来不是「CNN 是什么」而是「拿到一份源码怎么让它在我这台破笔记本上跑起来」。这份基于深度学习卷积神经网络的农作物病虫害识别检测系统恰好就是冲着这个痛点来的——它不是一个只丢给你一个.ipynb就完事的半成品而是把 Flask Web 界面、多框架训练脚本、Docker 部署、云平台部署指南全都打包进去了。项目本身解决的是一个很具体的场景给定一张叶片照片判断它属于哪种病害比如苹果黑星病、番茄早疫病、马铃薯晚疫病等并给出置信度。技术栈是 Python 卷积神经网络训练侧提供了 ResNet50、DenseNet121、VGG16、VGG19 等多套骨干网络的 notebook推理侧用 Flask 起了一个可交互的 Web 页面。适合谁计算机相关专业正在做毕设的学生、需要交课程设计大作业的人以及想拿一个完整 CNN 项目练手但不想从零搭架子的人。评审分 99 分这个信息说明它的完成度是被导师认可过的代码结构、文档、部署说明都齐。2. 先看清项目骨架目录结构与技术选型逻辑2.1 从文件树反推这套系统怎么分层拿到一个压缩包我习惯先不急着装环境而是把目录结构过一遍。这份项目的顶层结构大致是这样的├── app/ # Flask 应用主体 │ ├── server.py # 后端入口加载模型并处理请求 │ ├── view/ # 前端页面模板 │ ├── models/ # 模型权重存放位置 │ └── static/ # CSS/JS/图片等静态资源 ├── notebook/ # 各框架训练脚本 │ ├── Plant_Disease_RESNET50.ipynb │ ├── Plant_Detect_PyTorch.ipynb │ ├── Plant_Disease_Detection_TensorFlow.ipynb │ ├── Plant_Disease_Detection_Fastai.ipynb │ ├── Plant_Disease_Detection_Keras.ipynb │ ├── plant_disease_detector.ipynb │ ├── Plant_Disease_DenseNet121.ipynb │ ├── Plant_Disease_VGG19.ipynb │ └── Plant_Disease_VGG16.ipynb ├── deployment_guide/ # 部署文档 │ ├── local_flask/ │ ├── aws_deployment.md │ └── gcp_deployment.md ├── Dockerfile ├── requirements.txt ├── app.yaml └── README.md这个结构透露了几个关键信息。第一训练和推理是分离的——notebook 负责训练出权重文件app 负责加载权重做在线推理。第二多框架并存不是炫技而是给你选择权如果你学校机房只装了 TensorFlow就用 TensorFlow 那套如果你自己电脑上 PyTorch 更熟就切 PyTorch。第三deployment_guide 里同时给了本地 Flask、AWS、GCP 三条路径说明作者考虑过「答辩时老师让你现场演示」这个场景。2.2 为什么选 ResNet50 作为主力骨干网络项目里提供了 VGG16、VGG19、DenseNet121、ResNet50 四种骨干网络的训练脚本但 README 和默认配置指向的是 ResNet50。这不是随便选的。VGG 系列结构简单、参数量大VGG16 大约 1.38 亿参数VGG19 更多。在植物病害这种类别数不算特别多通常几十类的任务上VGG 容易过拟合而且训练慢。DenseNet121 参数量少、特征复用强但密集连接的结构在显存受限时容易爆。ResNet50 大约 2500 万参数残差连接解决了深层网络的梯度消失问题在 ImageNet 上的预训练权重也最容易拿到迁移学习效果好。我一般会这样跟学生解释如果你显卡只有 4GB 显存优先用 ResNet50 做迁移学习冻结前面的卷积层只训练最后的全连接层这样 batch size 能开到 16 甚至 32。如果你非要用 VGG那就把输入尺寸从 224×224 降到 128×128但精度会掉几个点。2.3 数据集的组织方式与类别划分项目的数据集按照标准图像分类格式组织每个类别一个文件夹dataset/ ├── train/ │ ├── Apple___Apple_scab/ │ ├── Apple___Black_rot/ │ ├── Tomato___Early_blight/ │ └── ... ├── valid/ │ └── (同上类别结构) └── test/ └── (同上类别结构)这种类别名/图片的结构是ImageDataGenerator和torchvision.datasets.ImageFolder都能直接吃的格式。类别命名用了植物名___病害名的三下划线分隔这是 PlantVillage 数据集的标准命名习惯。如果你要换成自己的数据只要保持这个目录结构改一下类别数就行。提示替换数据集时务必保证 train/valid/test 三个子集的类别文件夹名称完全一致否则标签映射会错位训练出来的模型预测结果会全部偏移。3. 环境搭建与训练脚本实操从零跑通第一个 notebook3.1 依赖安装与 Python 环境配置项目根目录的requirements.txt是环境配置的起点。我建议不要直接pip install -r requirements.txt一把梭而是先看清楚里面有什么。典型的依赖包括tensorflow2.4.0 torch1.8.0 torchvision0.9.0 fastai2.3.0 flask2.0.0 numpy pandas matplotlib Pillow gunicorn这里有个坑TensorFlow 和 PyTorch 同时装在一个环境里版本冲突的概率不低。我的做法是给训练和推理分别建虚拟环境。训练用哪个框架就建哪个环境推理环境只装 Flask 对应框架的推理依赖。# 创建训练环境以 PyTorch 为例 python -m venv venv_train source venv_train/bin/activate # Windows 用 venv_train\Scripts\activate # 安装 PyTorch根据你的 CUDA 版本选择 pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 # 安装其他依赖 pip install numpy pandas matplotlib Pillow jupyter这段命令的逻辑是先隔离环境再装框架最后装辅助库。--index-url指定 PyTorch 官方源是为了避免从 PyPI 拉到 CPU 版本。如果你没有 NVIDIA 显卡把cu118换成cpu即可。参数说明cu118对应 CUDA 11.8这是目前比较稳的版本太新的 CUDA 12.x 在某些旧驱动上会翻车。3.2 用 ResNet50 notebook 完成一次完整训练打开Plant_Disease_RESNET50.ipynb核心训练逻辑大概是这样import torch import torch.nn as nn import torchvision.models as models from torchvision import transforms, datasets from torch.utils.data import DataLoader # 1. 数据增强与预处理 train_transform transforms.Compose([ transforms.Resize((224, 224)), # ResNet50 标准输入尺寸 transforms.RandomHorizontalFlip(), # 随机水平翻转增加泛化 transforms.RandomRotation(15), # 随机旋转 ±15 度 transforms.ToTensor(), transforms.Normalize( # ImageNet 预训练权重的均值方差 mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225] ) ]) valid_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] ) ]) # 2. 加载数据集 train_dataset datasets.ImageFolder(dataset/train, transformtrain_transform) valid_dataset datasets.ImageFolder(dataset/valid, transformvalid_transform) train_loader DataLoader(train_dataset, batch_size32, shuffleTrue, num_workers4) valid_loader DataLoader(valid_dataset, batch_size32, shuffleFalse, num_workers4) # 3. 加载预训练 ResNet50 并替换分类头 model models.resnet50(pretrainedTrue) num_classes len(train_dataset.classes) # 根据实际类别数自动确定 model.fc nn.Linear(model.fc.in_features, num_classes) # 4. 冻结前面的卷积层只训练全连接层 for param in model.parameters(): param.requires_grad False for param in model.fc.parameters(): param.requires_grad True # 5. 定义损失函数和优化器 device torch.device(cuda if torch.cuda.is_available() else cpu) model model.to(device) criterion nn.CrossEntropyLoss() optimizer torch.optim.Adam(model.fc.parameters(), lr1e-3) # 6. 训练循环 epochs 10 for epoch in range(epochs): model.train() running_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() running_loss loss.item() # 验证阶段 model.eval() correct 0 total 0 with torch.no_grad(): for images, labels in valid_loader: images, labels images.to(device), labels.to(device) outputs model(images) _, predicted torch.max(outputs, 1) total labels.size(0) correct (predicted labels).sum().item() print(fEpoch {epoch1}/{epochs}, Loss: {running_loss/len(train_loader):.4f}, fVal Acc: {100*correct/total:.2f}%) # 7. 保存模型权重 torch.save(model.state_dict(), app/models/resnet50_plant.pth)这段代码有几个关键决策点需要解释。第一transforms.Normalize用的均值和方差是 ImageNet 的统计值因为 ResNet50 的预训练权重是在 ImageNet 上训的保持输入分布一致才能最大化迁移学习效果。第二冻结卷积层只训全连接层这是小数据集上的标准操作——植物病害数据集通常几万张图从头训容易过拟合。第三batch_size32是 4GB 显存下的安全值如果你显存更小降到 16 或 8。第四num_workers4在 Windows 上有时会报错改成 0 即可。训练完成后权重文件保存到app/models/目录下Flask 应用启动时会从这里加载。3.3 训练过程中的参数调优与监控跑通第一个 epoch 只是开始真正决定毕设分数的是精度。我一般会盯这几个指标参数推荐值调整逻辑学习率1e-3冻结阶段→ 1e-4微调阶段冻结时用大学习率快速收敛解冻后用小学习率精细调整batch_size16~32显存够就往上加但别超过 64小数据集上大 batch 反而掉点epochs10~20看验证集准确率曲线连续 3 个 epoch 不涨就可以停数据增强翻转旋转颜色抖动颜色抖动对叶片病害识别特别有用因为光照条件变化大如果你发现训练集准确率 99% 但验证集只有 70%那就是过拟合了。解决办法加 Dropout、加 L2 正则、增加数据增强强度、或者干脆减少全连接层的神经元数量。4. Flask 推理服务与部署让模型真正能被访问4.1 server.py 的推理流程拆解app/server.py是整个系统的在线推理入口。它的核心逻辑是接收上传的图片 → 预处理 → 送入模型 → 返回预测类别和置信度。简化后的关键代码如下from flask import Flask, request, jsonify, render_template import torch from torchvision import transforms from PIL import Image import io app Flask(__name__) # 启动时加载模型只加载一次 device torch.device(cuda if torch.cuda.is_available() else cpu) model torch.load(models/resnet50_plant.pth, map_locationdevice) model.eval() # 类别名称列表顺序必须与训练时一致 class_names [Apple___Apple_scab, Apple___Black_rot, ...] 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]) ]) app.route(/) def index(): return render_template(index.html) app.route(/predict, methods[POST]) def predict(): if file not in request.files: return jsonify({error: no file uploaded}), 400 file request.files[file] img Image.open(io.BytesIO(file.read())).convert(RGB) input_tensor preprocess(img).unsqueeze(0).to(device) with torch.no_grad(): outputs model(input_tensor) probabilities torch.nn.functional.softmax(outputs, dim1) confidence, predicted torch.max(probabilities, 1) return jsonify({ class: class_names[predicted.item()], confidence: round(confidence.item() * 100, 2) }) if __name__ __main__: app.run(host0.0.0.0, port5000, debugFalse)几个容易翻车的点class_names的顺序必须和训练时ImageFolder自动排序的顺序完全一致否则预测结果会张冠李戴。model.eval()不能省否则 BatchNorm 层会用训练模式的统计量推理结果不稳定。torch.no_grad()包裹推理过程减少显存占用。4.2 本地启动与接口测试环境装好后启动服务只需要一行cd app python server.py浏览器打开http://localhost:5000就能看到上传页面。如果你想用命令行测试接口curl -X POST -F filetest_leaf.jpg http://localhost:5000/predict返回的 JSON 类似{class: Tomato___Early_blight, confidence: 94.37}。如果返回 500 错误先看控制台有没有报KeyError或RuntimeError大概率是类别名对不上或者模型权重路径写错了。4.3 Docker 与云平台部署的取舍项目提供了 Dockerfile 和 AWS/GCP 部署文档。Docker 方式适合你需要在不同机器上迁移演示的场景FROM python:3.8-slim WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY . . EXPOSE 5000 CMD [gunicorn, -b, 0.0.0.0:5000, server:app]构建和运行docker build -t plant-disease . docker run -p 5000:5000 plant-disease用 gunicorn 替代 Flask 自带的开发服务器是因为开发服务器不支持并发答辩时如果多个老师同时访问会卡死。-b 0.0.0.0:5000让服务监听所有网卡方便局域网内其他设备访问。至于 AWS 和 GCP 的部署文档我的建议是毕设答辩用本地 Flask 或 Docker 就够了云部署更适合你想把项目写进简历、展示工程能力的时候再折腾。云平台的坑在于安全组配置和模型文件上传文档里虽然有步骤但不同账号的界面有差异照着做也可能卡住。5. 避坑与常见问题排查5.1 模型加载报错KeyError 与 size mismatch现象启动 Flask 时抛出RuntimeError: Error(s) in loading state_dict for ResNet: Missing key(s) in state_dict或size mismatch for fc.weight。原因训练时保存的是model.state_dict()但加载时用了torch.load()直接加载整个模型对象或者类别数和训练时不一致导致全连接层维度对不上。解决统一用state_dict方式保存和加载。加载前先实例化模型结构再model.load_state_dict(torch.load(xxx.pth))。如果类别数变了重新训练最后一层。5.2 预测结果全部偏向同一类现象不管上传什么图片模型都返回同一个类别置信度还很高。原因最常见的是class_names列表顺序和训练时ImageFolder.classes的顺序不一致。ImageFolder按文件夹名称的字母序排列如果你手动写的列表顺序不同映射就错了。解决在训练脚本里打印train_dataset.classes把输出直接复制到server.py的class_names里。或者把类别列表存成 JSON 文件训练和推理都从同一个文件读。5.3 CUDA out of memory现象训练到一半报RuntimeError: CUDA out of memory。原因batch_size 太大或者没有及时释放中间变量。解决先把 batch_size 减半试试。如果还不行在训练循环里加torch.cuda.empty_cache()或者把num_workers调小。另外检查是不是在验证阶段忘了加torch.no_grad()导致计算图一直累积。5.4 Flask 上传大图后请求超时现象上传手机拍的高清叶片照片几 MB页面一直转圈最后 502。原因Flask 默认没有限制上传大小但预处理时把大图直接 resize 到 224×224 之前PIL 解码大图会消耗大量内存和时间。解决在server.py里加app.config[MAX_CONTENT_LENGTH] 5 * 1024 * 1024限制 5MB同时在预处理里先做一次缩略图裁剪再送进 transform。5.5 notebook 里的路径在本地跑不通现象notebook 里写的是/content/dataset/train这是 Google Colab 的路径本地跑就找不到文件。原因作者在 Colab 上调试的路径没改成相对路径。解决把所有绝对路径改成相对于项目根目录的路径比如dataset/train。或者在 notebook 开头加一段os.chdir()切换到项目根目录。6. 把毕设变成能讲清楚的作品验证方法与进阶技巧跑通代码只是及格线答辩时老师真正会问的是「你怎么知道这个模型是有效的」。我一般会让学生做三件事来验证。第一画混淆矩阵。用验证集跑一遍预测用sklearn.metrics.confusion_matrix生成矩阵看看哪些类别容易被混淆。比如番茄早疫病和番茄晚疫病在叶片上的视觉差异很小模型分错很正常但你要能解释为什么。第二做一组消融对比。用同一个数据集分别训 ResNet50、VGG16、DenseNet121记录验证集准确率和训练时间。表格一摆选 ResNet50 的理由就立住了。我见过太多毕设只跑了一个模型老师问「为什么不用别的」就答不上来。第三测试真实场景图片。从网上找几张不在数据集里的叶片照片或者自己用手机拍几张看看模型能不能给出合理预测。这一步经常翻车——数据集里的图片背景干净、光照均匀真实照片背景杂乱模型可能直接懵掉。如果翻车了这反而是个加分项你可以说「当前模型的局限在于训练数据分布单一后续可以通过增加真实场景数据做域适应」。# 混淆矩阵与分类报告 from sklearn.metrics import confusion_matrix, classification_report import seaborn as sns import matplotlib.pyplot as plt model.eval() all_preds [] all_labels [] with torch.no_grad(): for images, labels in valid_loader: images images.to(device) outputs model(images) _, predicted torch.max(outputs, 1) all_preds.extend(predicted.cpu().numpy()) all_labels.extend(labels.numpy()) cm confusion_matrix(all_labels, all_preds) plt.figure(figsize(12, 10)) sns.heatmap(cm, annotTrue, fmtd, cmapBlues, xticklabelsclass_names, yticklabelsclass_names) plt.xlabel(Predicted) plt.ylabel(True) plt.title(Confusion Matrix) plt.savefig(confusion_matrix.png, dpi150, bbox_inchestight) print(classification_report(all_labels, all_preds, target_namesclass_names))这段代码输出的分类报告里重点看f1-score而不是accuracy。如果某个类别的 f1 低于 0.7说明模型在这个类别上既容易漏检又容易误检答辩时主动提出来并给出改进思路比被老师问住要好。还有一个进阶技巧把模型导出成 ONNX 格式这样推理时不再依赖 PyTorch部署体积能小很多启动速度也快。对于毕设演示来说这意味着你可以在没有装 PyTorch 的机器上跑推理只要装onnxruntime就行。# 导出 ONNX dummy_input torch.randn(1, 3, 224, 224).to(device) torch.onnx.export(model, dummy_input, plant_disease_resnet50.onnx, input_names[input], output_names[output], dynamic_axes{input: {0: batch_size}, output: {0: batch_size}})导出后用onnxruntime加载验证一遍确保输出和 PyTorch 一致。这一步做完你的毕设就不只是一个「能跑的 notebook」而是一个有部署方案、有性能对比、有边界分析的完整作品。从那以后我每次带学生做毕设都要求他们在答辩前至少跑一遍混淆矩阵和真实场景测试因为这两步能暴露 90% 的问题。希望帮到你。本文还有配套的精品资源点击获取