农作物病害识别源码包:从模型训练到云部署的完整技术链路
2026/9/24 19:57:27 网站建设 项目流程

简介:一份面向毕业设计与深度学习入门者的完整源码包,实现基于云技术与深度学习的农作物病虫害识别系统。项目覆盖图像预处理(调整大小、裁剪、数据增强)、卷积神经网络模型训练与优化、云端服务搭建及用户端上传识别全流程,并涉及TensorFlow、Keras、PyTorch等常见框架,适合作为课程设计、毕设或工程实战参考。压缩包共60个文件,约88.75MB,内含9个ipynb模型训练与对比笔记(ResNet50、VGG16/19、DenseNet121等经典网络)、Python服务端脚本、前端展示页面与CSS/JS样式、Dockerfile及AWS/GCP部署配置,并附有示例图片、Markdown说明文档等,目录结构清晰,便于按模块查阅。源码中提供了完整的云端部署指南与Flask本地运行示例,可快速启动。目前已有126人学习浏览,对需要系统了解深度学习图像识别落地流程的开发者很有帮助。

1. 云上跑通农作物病害识别:这份源码包把训练到部署的链路都备齐了

拿到一个农作物病虫害识别项目,最烦的不是写网络结构,而是把图像预处理、模型训练、Web 服务、云上部署这条线串起来。这份基于 Python 和深度学习的源码包,把这四段链路都备齐了:notebook 里有 ResNet50、DenseNet121、VGG16/19 多份训练脚本,TensorFlow、Keras、PyTorch、FastAI 四套框架都有对应实现,服务端是 Flask 应用,部署层给了 Dockerfile、AWS 和 GCP 的完整文档。你做毕业设计想跑对比实验,或者想快速验证一个病害识别落地方案,直接基于它改数据就行,不用从头搭。省掉的是踩环境坑、调部署配置的时间,这部分通常占项目周期的三分之一不止。

2. 技术栈选型与项目结构:双框架多模型加 Flask,从训练到部署是闭环

2.1 目录结构:训练脚本、服务代码、部署文档各司其职

把压缩包解开后,先把目录结构摸清楚再动手,能少走很多弯路。核心结构是这样的:

Plant_Disease_Detection-master ├── app/ # 视图、模型、静态资源 │ ├── view/ # 前端模板(上传页/结果页) │ ├── models/ # 训练好的模型文件 │ └── static/ # CSS/JS/用户上传的图片 ├── server.py # Flask 服务入口 ├── notebook/ # 模型训练 Notebook │ ├── Plant_Disease_RESNET50.ipynb │ ├── Plant_Disease_DenseNet121.ipynb │ ├── Plant_Disease_VGG16.ipynb │ ├── Plant_Disease_VGG19.ipynb │ ├── Plant_Disease_Detection_TensorFlow.ipynb │ ├── Plant_Disease_Detection_Keras.ipynb │ ├── Plant_Detect_PyTorch.ipynb │ ├── Plant_Disease_Detection_Fastai.ipynb │ └── plant_disease_detector.ipynb ├── Dockerfile ├── requirements.txt ├── app.yaml # GCP App Engine 配置 ├── deployment_guide/ │ ├── local_flask # 本地启动说明 │ ├── aws_deployment.md # AWS 部署文档 │ └── gcp_deployment.md # GCP 部署文档 ├── images/ # 文档配图 └── README.md

这份目录是典型的「三段式」结构:notebook 负责模型实验,app 和 server.py 负责服务化,deployment_guide 与 Dockerfile 负责环境一致性和上云。我拆过的不少毕设项目,最大的问题就是训练代码和服务代码混在一起,改一个参数要翻整份 notebook。而这份源码把服务入口放在独立的 server.py 里,模型单独放 app/models 目录,部署文档单独成 md 文件——你要改模型、改接口还是改部署环境,入口是清晰的。

特别说一下 app.yaml 这个文件。它是 GCP App Engine 的配置文件,定义了运行环境、入口命令和资源规格。仓库里同时在 deployment_guide 下给了 aws_deployment.md 和 gcp_deployment.md,说明作者本来就是按「一条链路两条部署路径」来设计的:本地用 Flask 跑通,上云时二选一。

2.2 多框架 Notebook 的定位:同数据跑多套组合的价值

看 notebook 目录,会发现作者给的不是一份训练脚本,而是多个网络 × 多套框架的组合。我按实际用途整理成下面这张表:

Notebook骨干网络框架建议用途
Plant_Disease_RESNET50ResNet50Keras/TensorFlow默认主线,先跑这个
Plant_Disease_DenseNet121DenseNet121Keras/TensorFlow数据集偏小时优先
Plant_Disease_VGG16VGG16Keras/TensorFlow学习用,结构直观
Plant_Disease_VGG19VGG19Keras/TensorFlow效果与 VGG16 差别不大
Plant_Detect_PyTorch自定义 CNNPyTorch需要动态图调试时
Plant_Disease_Detection_FastaiResNet 系列FastAI快速跑基线
Plant_Disease_Detection_TensorFlow自定义 CNNTensorFlow想从底层构建时
plant_disease_detector未指定通用综合基线版本

为什么要同时给这么多?两个原因。第一,做毕设或课程设计时,对比实验是答辩的硬指标,在同一个数据集上你比较了 ResNet50 和 DenseNet121,说明你做过选型分析;第二,工程上不同框架的部署链路差别不小,TensorFlow 的 SavedModel 在服务端加载最省事,PyTorch 用 TorchScript 也能上线,FastAI 训练方便但部署时通常要导出成底层框架的格式。

我的建议是别好奇地每个都跑一遍,那样几天就没了。优先跑 RESNET50,拿到准确率基线后,根据数据量判断要不要换 DenseNet121。数据量在几千张这个量级,DenseNet121 的稠密连接结构收敛更稳,不容易过拟合。VGG 系列当学习材料看看就行,VGG19 在大部分病害识别场景下比 VGG16 没有明显优势,但参数量和推理时间都涨了不少。

2.3 Flask 服务与模型解耦:分离设计带来的改造成本优势

server.py 是整个 Web 服务的入口,app/view 放前端 HTML 模板,app/static 放样式和上传图片,app/models 放训练好的权重文件。用户流程很直接:浏览器打开上传页,选择一张病害叶片图片,POST 请求打到 Flask 路由,服务端加载模型做推理,把类别和置信度传给结果页渲染。

这个架构最值得抄作业的一点,是模型文件与 Web 代码物理分离。你在 notebook 里重新训练了一个 DenseNet121,导出 h5 文件丢进 app/models,服务端代码一行都不用改。反过来,你想把识别结果从网页扩展成 JSON 接口给小程序调用,也只需在 server.py 里加一个 API 路由,不影响现有页面。

注意:如果后续要换模型,不要只替换文件而忽略类别列表。类别数量和顺序是在训练时定的,换了模型必须同步更新服务端的类别映射,否则推理结果会对不上。

3. 本地先把模型跑起来:环境配置与训练 Notebook 复现顺序

3.1 环境依赖:pip 和 Docker 两条路怎么选

先说明一下复现顺序:先在本地跑通一份训练 notebook,再启动 Flask 服务,最后才是上云。本地环境这一步最常翻车的是 Python 版本和框架版本互相打架,这问题有时候像个玄学,明明照着文档装的,就是跑不起来。所以我一般会用虚拟环境隔离。

# 用 conda 创建独立的 Python 3.9 虚拟环境 conda create -n plant python=3.9 -y conda activate plant # 先装基础依赖,再装深度学习框架 pip install -r requirements.txt # 如果你有 NVIDIA GPU,建议单独装 CUDA 版 TensorFlow pip install tensorflow-gpu==2.10.0

requirements.txt 这个文件在仓库根目录,里面列的是 numpy、pandas、matplotlib、scikit-learn、tensorflow、keras、pillow、flask 这些核心库。第一次跑项目,我不推荐直接用 Docker,原因很简单:notebook 训练阶段你大概率要反复调整代码,直接 pip 安装改动最快;Docker 放在后面部署阶段再用,那时候环境一致性才变成关键诉求。

如果你用的不是 conda 而是系统 Python,务必先建虚拟环境。我见过不少人图省事直接在系统环境里 pip install,结果把系统 Python 的依赖搞乱,后面装什么都报错,只能重装系统或者花半天排查。这一步没有后悔药,提前隔离是最省事的。

3.2 图像预处理:尺寸、裁剪、归一化参数为什么这么定

import numpy as np from PIL import Image def preprocess_image(img): """ 统一预处理函数,训练和 Web 推理都走这里。 支持传文件路径、PIL Image 对象或 Flask 上传的文件对象。 """ # 统一转成 PIL Image 并保证 RGB 通道 if isinstance(img, str): img = Image.open(img) elif hasattr(img, 'read'): img = Image.open(img) img = img.convert('RGB') # 先缩放到 256x256,再中心裁剪到 224x224 img = img.resize((256, 256)) crop = 224 left = (256 - crop) // 2 top = (256 - crop) // 2 img = img.crop((left, top, left + crop, top + crop)) # 转数组,归一化到 [0,1],再做 ImageNet 标准化 arr = np.array(img, dtype=np.float32) / 255.0 mean = [0.485, 0.456, 0.406] std = [0.229, 0.224, 0.225] arr = (arr - mean) / std # 加 batch 维度,模型需要 (1, 224, 224, 3) return np.expand_dims(arr, axis=0)

这段逻辑是几乎所有预训练模型的标准输入流程。先缩放再裁剪:直接 resize 到 224 会把叶片拉伸变形,先放大到 256 再取中心 224,能在保持主体内容的同时引入一点平移不变性。meanstd用 ImageNet 统计值,不是随便挑的——预训练权重是在 ImageNet 上学的,输入分布和训练时保持一致,特征提取能力才能完整继承。

这里注意两点:一是如果你的模型输入尺寸不是 224,这个函数里的crop变量和裁剪起点都要跟着改;二是预处理函数最好在训练和推理两端复用同一个模块,避免两边各写一份然后参数不一致。后面第 5 章会专门讲这个坑。

3.3 模型训练:以 ResNet50 为例的迁移学习参数组合

import tensorflow as tf from tensorflow.keras.applications import ResNet50 from tensorflow.keras.layers import Dense, Dropout, GlobalAveragePooling2D from tensorflow.keras.models import Model NUM_CLASSES = 10 # 按你自己的类别数修改 EPOCHS = 30 BATCH_SIZE = 32 LR = 1e-4 # 微调阶段学习率不宜过大 # 加载 ImageNet 预训练权重,去掉顶部分类层 base_model = ResNet50(weights='imagenet', include_top=False, input_shape=(224, 224, 3)) # 先冻结基础层,只训练新加的分类头 base_model.trainable = False x = base_model.output x = GlobalAveragePooling2D()(x) x = Dropout(0.5)(x) x = Dense(128, activation='relu')(x) x = Dropout(0.3)(x) output = Dense(NUM_CLASSES, activation='softmax')(x) model = Model(inputs=base_model.input, outputs=output) model.compile( optimizer=tf.keras.optimizers.Adam(learning_rate=LR), loss='categorical_crossentropy', metrics=['accuracy'] )

include_top=False的作用是丢弃 ResNet50 原有的 1000 类分类器,只保留前面的卷积特征提取部分。GlobalAveragePooling2D把最后一层卷积输出的特征图压成一维向量,相比 Flatten 参数更少,也不容易过拟合。两个 Dropout 层分别是 0.5 和 0.3,用于抑制分类头的过拟合,这个值在数据量不大时是安全的起手配置。

训练节奏上我一般这样控制:先冻结 backbone 训练 10 个 epoch 左右,等验证集 loss 不再下降,再把base_model.trainable设为 True,解冻最后 20 层左右,用 1e-5 的学习率微调。这一步是迁移学习里最容易出效果的技巧——直接解冻全部层会导致前期梯度震荡,结果往往不如只微调尾部。

4. 把识别服务推上云:Flask 应用、Docker 镜像与部署实操

4.1 server.py 的推理链路:从上传图片到返回类别

import os from flask import Flask, request, render_template from tensorflow.keras.models import load_model app = Flask(__name__, template_folder='app/view', static_folder='app/static') # 模型只加载一次,放在模块级别而不是函数里 MODEL_PATH = os.path.join(os.path.dirname(__file__), 'app', 'models', 'plant_resnet50.h5') model = load_model(MODEL_PATH, compile=False) # 按训练数据集的类别顺序填,顺序错一个,结果全乱 CLASS_NAMES = ['Apple_scab', 'Black_rot', 'Cedar_rust', 'healthy'] @app.route('/', methods=['GET', 'POST']) def predict(): if request.method == 'POST': file = request.files['image'] img = preprocess_image(file) # 复用 3.2 节的预处理 preds = model.predict(img)[0] idx = int(preds.argmax()) confidence = float(preds[idx]) return render_template('result.html', label=CLASS_NAMES[idx], confidence=confidence) return render_template('upload.html') if __name__ == '__main__': app.run(host='0.0.0.0', port=5000, debug=False)

模型加载放在模块顶层,整个进程生命周期只加载一次,这能省掉每次请求重新 load_model 的几秒开销。compile=False是因为推理阶段不需要编译优化器,能省一点内存。CLASS_NAMES的列表顺序必须和训练时保持一致——模型输出的只是索引,索引到类名的映射在服务端这一段,顺序错一位,识别结果就全乱了。

本地验证时直接python server.py,浏览器访问 5000 端口就能看到上传页面。仓库里 deployment_guide/local_flask 有启动步骤说明。

4.2 Dockerfile 解读:构建顺序与层缓存

FROM python:3.9-slim WORKDIR /app # 先拷贝依赖声明文件,利用 Docker 层缓存 COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt # 再拷贝服务端代码与模型目录 COPY server.py . COPY app/ ./app/ COPY notebook/ ./notebook/ EXPOSE 5000 CMD ["python", "server.py"]

Dockerfile 的指令顺序不是随便排的。Docker 构建时每一行指令都会生成一个缓存层,如果 requirements.txt 没变,RUN pip install这一层就能直接命中缓存,构建时间会大幅缩短。如果把COPY app/放在前面,每次改动代码都会导致 pip install 重新执行,本地调试还行,CI 里每次构建就是灾难。

CMD用了列表形式而不是字符串形式,["python", "server.py"]这种 exec 形式能保证进程收到正确的信号,方便 Docker 在停止容器时优雅退出。如果发现容器 stop 后要等很久才杀掉,多半就是 CMD 写成了 shell 字符串形式。

4.3 AWS 与 GCP 部署对比:两条路径的配置要点

维度AWS EC2GCP App Engine
服务类型IaaS 虚拟机PaaS 托管平台
环境控制完全自主受限但免运维
GPU 支持有 GPU 实例可选原生不支持,需搭配其他服务
适合场景要跑训练又要推理只做在线推理
配置入口安全组 / 入口规则app.yaml

AWS 这条路是标准的 Docker 远程部署流程,先把镜像推到 ECR,再到 EC2 上拉取运行:

# 本地构建并推送镜像到 AWS ECR docker build -t plant-disease . docker tag plant-disease:latest $ACCOUNT_ID.dkr.ecr.$REGION.amazonaws.com/plant-disease:latest docker push $ACCOUNT_ID.dkr.ecr.$REGION.amazonaws.com/plant-disease:latest # SSH 登录 EC2 实例,拉取镜像并启动容器 ssh ec2-user@$EC2_IP docker pull $ACCOUNT_ID.dkr.ecr.$REGION.amazonaws.com/plant-disease:latest docker run -d --name plant -p 80:5000 plant-disease

GCP App Engine 走的则是声明式配置:仓库里已经有 app.yaml,里面指定了运行时、入口命令和服务配置。执行gcloud app deploy即可完成部署,不需要手动管理服务器。选哪条路取决于你的使用场景,如果只是把训练好的模型变成一个在线接口,App Engine 省运维,但要注意它的实例规格上限;如果要挂 GPU 推理服务,必须走 AWS 这类提供 GPU 实例的平台。

提示:云服务器上拉取镜像后,第一次访问会有模型加载的延迟,建议部署完成后先手动触发一次推理,让模型常驻内存,再对外暴露入口。

5. 避坑指南:我在复现这套源码时踩过的五个坑

5.1 加载 h5 模型报错:Keras 与 TensorFlow 版本对不上

现象:用 tensorflow.keras.models.load_model 加载训练好的 h5 文件时,抛出一堆类似 Unknown metric function 或者 Unknown layer 的反序列化错误,模型权重明明没损坏。

原因:训练时用的 Keras API 和加载时的版本不一致。典型场景是 notebook 里用 Keras 2.x 训练保存的 h5,部署时环境里装的 TensorFlow 2.16 自带 Keras 3.x,函数路径和序列化格式都变了,旧权重找不到对应的符号。

解决:加载时显式声明自定义对象load_model(path, custom_objects={...}),或者更稳的做法——训练结束后用model.save_weights只保存权重,部署端用代码重建同样的网络结构再load_weights。前者一步到位,后者结构可控,能避开大部分序列化兼容问题。

5.2 上传图片后 404:静态资源路径配置不一致

现象:Flask 服务启动正常,上传页能打开,但页面样式全丢,点上传后路由返回 404,或者图片预览加载不出来。

原因:模板里引用的静态资源路径和 Flask 配置不一致。这个项目里 server.py 在根目录,而静态资源在 app/static,如果不手动指定static_folder,Flask 默认会去根目录找 static,自然是 404。

解决:创建 Flask 实例时显式指定static_folder='app/static'template_folder='app/view'。模板里统一用{{ url_for('static', filename='style.css') }}生成路径,不要手写绝对路径。

5.3 云端推理超时:开发服务器扛不住并发

现象:本地跑得好好的,部署到云上后第一次请求要十几秒,之后并发一上来就开始超时,页面转半天没结果。

原因:CPU 实例推理一张 224x224 图片大概需要 1 到 2 秒,加上模型加载时间,默认的 Flask 开发服务器是单线程的,请求全部排队,超时是必然的。

解决:换生产级 WSGI 服务器,用 gunicorn 起多个 worker。命令大致是gunicorn -w 4 -b 0.0.0.0:5000 server:app,4 个 worker 并发处理,吞吐量提升明显。模型加载到共享内存,各 worker 复用。

5.4 识别准确率骤降:训练和推理预处理不一致

现象:同一张测试图,在 notebook 里预测是对的,部署成 Web 服务后预测就错了,第一反应是模型坏了,仔细排查发现模型没坏。

原因:训练时做的是 resize 256 + 中心裁剪 + ImageNet 标准化,推理端可能只做了 resize 到 224,或者忘了先归一化到 [0,1] 再标准化。输入分布变了,模型的输出自然不稳定。

解决:把 3.2 节的preprocess_image函数抽成一个公共模块,训练和 Web 推理共用同一份代码,不要各写一遍。这个坑排查起来最费时间,因为它不报错,只是结果不对。

5.5 结果偏向常见类别:数据集分布不均衡

现象:训练准确率看着还行,但实际用的时候发现模型几乎不判某些类别,健康叶片也频繁被识别成常见病害。

原因:数据集类别分布不均,某类图片数量是另一些类的数倍。模型学到的是「多数类先验」,在不确定时倾向于输出样本量大的类别。

解决:训练前先统计每个类别的图片数,在model.fit里传class_weight参数给少数类加大权重,或者对少数类做数据增强的过采样。我先打印数据分布再决定怎么调,不做这步直接训,结果不可信。

6. 进阶:用自定义数据集微调并跑通端到端验证闭环

前面都是基于仓库自带数据流演示,真正要把这个项目用在你的场景里,一定得换自己的数据。我建议做一次「数据替换 → 微调 → 服务验证」的闭环,整个过程控制在半天内。

先按类别分文件夹组织数据,用 ImageDataGenerator 加载并做数据增强:

from tensorflow.keras.preprocessing.image import ImageDataGenerator train_gen = ImageDataGenerator( rescale=1./255, # 简化版归一化,和推理端保持一致 rotation_range=20, # 随机旋转 ±20 度 width_shift_range=0.2, # 水平平移 20% height_shift_range=0.2, # 垂直平移 20% zoom_range=0.2, # 随机缩放 horizontal_flip=True, # 水平翻转 validation_split=0.2 # 留出 20% 做验证集 ) train_data = train_gen.flow_from_directory( 'data/train', target_size=(224, 224), batch_size=32, subset='training', class_mode='categorical' )

flow_from_directory会自动把每个子目录名映射成类别索引,class_mode='categorical'输出 one-hot 标签,配合前面 3.3 节的 ResNet50 模型直接 fit。注意这里为了演示用了简化的rescale方案,和 3.2 节的手动标准化是两套方案,选一套后训练与推理必须一致。训练完成后用save_weights导出权重,再在 server.py 里把CLASS_NAMES改成你的子目录列表,改动集中在两处。

微调结束后做一次端到端验证:从训练集里抽出几十张模型没见过的图,走一遍「上传 → 预处理 → 预测 → 结果页」的完整流程,确认类别名称和置信度都对得上。这一步能同时暴露预处理不一致和类别映射错位这两个最隐蔽的问题。我从那以后每次换数据集,都强制先跑一轮这个闭环再谈部署,时间花得很值。这份源码的坑我已经替你踩过一遍了,照着上面的步骤来,能省不少时间,希望帮到你。

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

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

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

立即咨询