☰
基于CNN的农作物病虫害识别检测系统:数据集、训练与Flask部署全解析
2026/10/7 12:45:31 网站建设 项目流程

简介:基于深度学习卷积神经网络的农作物病虫害识别检测系统,是一套完整的高分毕业设计项目源码,主要面向计算机相关专业正在准备毕设的学生,以及需要真实项目练手的学习者。项目以CNN为核心,覆盖数据训练、模型评估与Web端识别展示全流程,并配备Python源码、运行说明和图像数据集,开箱可运行,适合快速复现或二次开发。

压缩包共56个文件,大小88.3MB,主要包含ipynb模型训练笔记、Python源码、环境配置文件、前端页面及图片素材等。其中ipynb笔记涵盖ResNet50、VGG16、DenseNet121以及PyTorch、TensorFlow、Keras、Fastai等多种实现,并串联数据加载、模型构建、训练评估到保存推理的完整链路,便于对照学习;另有Docker及云平台部署配置与说明文档,帮助理解从本地调试到线上发布的过程。目前已有98人浏览学习,项目架构完整、注释清晰,既可作为毕业设计参考,也可用于课程设计或期末大作业,具备较高的实用与收藏价值。

1. 这份资源解决了谁的痛点:从99分毕设到能跑起来的识别系统

拿到题目那天,大多数人不是不懂深度学习,而是资料堆了一桌,没一份能完整跑通。这套基于深度学习卷积神经网络的农作物病虫害识别检测系统,把整条链路一次补齐:按文件夹分好类的图像数据集、九个框架版本的CNN训练源码、能直接启动的Flask网页识别服务。毕设要的从来不是一个“看懂了”的模型,而是一个能演示、能截图、能扛住导师追问的完整闭环,这份资源就是干这个的。

适合正在赶论文、需要快速跑通并讲清卷积神经网络原理的学生,也适合想拿现成数据集练手迁移学习的从业者。接下来按我的习惯来拆:先盘文件,再跑模型,最后处理部署和那些最容易翻车的细节。

2. 先盘清家底:源码包里每一份文件是干嘛的

打开压缩包第一眼,会看到一堆英文目录和文档,容易懵。我建议按“入口文件 → 环境文件 → 模型训练 → 网页服务 → 部署脚本”这条线去看,而不是从头到尾读目录。这套项目组织得比较规整,根目录下 README.md 写了项目背景和启动路径,requirements.txt 锁定了依赖版本,server.py 是网页服务入口,真正花时间的是 notebook 目录下的九套训练代码。

2.1 从根目录到模型目录:这套项目怎么组织

先从外到里过一遍关键目录:

  • 根目录:README.md 必须第一个读,里面通常写了环境版本和快速启动命令;requirements.txt 是安装依赖的清单;server.py 是 Flask 服务端入口。
  • templates / static:网页前端模板和静态资源,server.py 渲染的就是这两个目录里的文件。
  • models:训练好的模型权重放这里,部署时直接 load_model 加载,不用重新训练。
  • notebook:九个 .ipynb 训练脚本,覆盖 VGG16、VGG19、ResNet50、DenseNet121、PyTorch、TensorFlow、Fastai、Keras 等多种实现。
  • local_flask、aws_deployment.md、gcp_deployment.md:本地启动和云端部署说明,Dockerfile 和 app.yaml 对应容器化与平台部署。

这个结构说明作者当年是按“训练 + 部署”两条线设计的,不是只交一个 Notebook 就完事。模型训练在 notebook 里完成,网页服务在 server.py 里调用训练好的权重,二者通过 models 目录衔接。

先做环境准备,这是所有步骤的前提:

cd plant_disease_detector python -m venv venv source venv/bin/activate pip install -r requirements.txt

说明:虚拟环境隔离依赖,避免和系统 Python 打架;requirements.txt 里如果锁定的是 TensorFlow 2.x,就按 2.x 跑,不要自作主张升到 3.x。激活虚拟环境后,后续所有训练和启动命令都在这个环境里执行。

2.2 九套Notebook是真的多模型,不是凑数

这可能是这份资源被低估的地方。同一个病虫害数据集,作者写了九套训练脚本,本质上是把“换骨架、换框架”这件事做成了对照实验。

Notebook 文件框架/模型建议用途
Plant_Disease_VGG16.ipynbKeras + VGG16入门首选,结构简单好讲
Plant_Disease_VGG19.ipynbKeras + VGG19加深版本,作对比实验
Plant_Disease_RESNET50.ipynbResNet50答辩主推,精度和速度均衡
Plant_Disease_DenseNet121.ipynbDenseNet121轻量方案,参数量少
Plant_Disease_Detection_TensorFlow.ipynbTensorFlow 原生 API想讲底层原理时用
Plant_Detect_PyTorch.ipynbPyTorch和 Keras 系列做框架对比
Plant_Disease_Detection_Fastai.ipynbFastai新手最快跑通的选择
Plant_Disease_Detection_Keras.ipynbKeras快速验证思路的版本
plant_disease_detector.ipynb综合版本端到端演示用

我一般建议新手拿 VGG16 入门,因为它结构规则、论文里容易画图;正式结果交给 ResNet50 出,最后论文里放两张图,一张是 VGG16 的结构图,一张是 ResNet50 的准确率曲线,导师看了会觉得你确实对比过。Fastai 版本留给时间特别赶的同学救急,几行代码就能跑到一个能用的结果。

2.3 部署文件怎么看:本地Flask与云端部署文档的定位

部署相关文件分三块:local_flask 是本地启动方式,Dockerfile 是把环境和模型打包成容器,app.yaml、aws_deployment.md、gcp_deployment.md 是对接云平台的。

它们的关系是这样:答辩演示用本地 Flask 最稳,不依赖外网,打开浏览器输入 localhost:5000 就能上传图片。云端部署是为了给评委一个公网链接,或者你后面想做成真正能访问的服务。Dockerfile 的作用是让环境可复现,换一台机器不用重新配 GPU 环境;app.yaml 是平台级部署配置,GCP 读取它之后会按描述启动服务。

提示:先跑通本地,再碰云端。我第一次直接跳过本地去配云平台,光环境就折腾了一晚上,最后发现本地跑的坑还没踩完。

3. 跑通第一套模型:用VGG16迁移学习把识别准确率抬到能答辩的水平

跑通整个项目,第一步不是训练,是先让一个模型完整跑完训练、评估、保存、推理的闭环。VGG16 最适合承担这个任务,因为它的结构足够经典,一个图就能讲清楚 CNN 的卷积池化全连接逻辑。但你需要理解为什么用“迁移学习”而不是从零训练,这是答辩时导师大概率会问的问题。

3.1 为什么选VGG16当第一个主跑模型:迁移学习的底层逻辑

从零训练一个 CNN 做图像分类,通常需要几十万张同分布图片。病虫害数据集即便质量不错,量级也很难撑起一个 VGG16 从随机初始化开始训练,结果往往是欠拟合或过拟合二选一。迁移学习解决的就是这个问题:ImageNet 上训好的模型,前几层卷积核已经学会了边缘、纹理、颜色斑块这些通用特征,这些特征对植物叶片同样有效。

具体操作是保留 VGG16 在 ImageNet 上学到的卷积基,把最后的 1000 类全连接分类头砍掉,换成适合当前类别数的全连接层。只训练新加的分类头,或者后面再解冻少量卷积层做微调。VGG16 的短板是参数量大,16 层结构里有大约 1.38 亿参数,训练和推理都偏慢,但作为第一个跑通的项目,它是最安全的。

3.2 数据加载与预处理:从文件夹到训练生成器

数据集已经按类别分好了子文件夹,这种结构可以直接用 Keras 的 ImageDataGenerator 读取,它会自动把子文件夹名映射成类别标签。

from tensorflow.keras.preprocessing.image import ImageDataGenerator train_datagen = ImageDataGenerator( rescale=1.0 / 255, # 像素值从0~255归一化到0~1 rotation_range=20, # 随机旋转±20度 width_shift_range=0.2, # 水平平移20% height_shift_range=0.2, # 垂直平移20% shear_range=0.2, # 错切变换 zoom_range=0.2, # 随机缩放20% horizontal_flip=True, # 水平翻转 fill_mode='nearest' # 填充像素用最近邻 ) valid_datagen = ImageDataGenerator(rescale=1.0 / 255) # 验证集只归一化 train_generator = train_datagen.flow_from_directory( 'dataset/train', # 训练集根目录 target_size=(224, 224), # VGG16 要求的输入尺寸 batch_size=32, # 每批32张 class_mode='categorical' # 多分类,配合softmax ) valid_generator = valid_datagen.flow_from_directory( 'dataset/valid', target_size=(224, 224), batch_size=32, class_mode='categorical' )

这段代码的关键是区分了训练集和验证集的预处理。训练集做了旋转、平移、缩放、翻转这一套数据增强,目的是扩大样本分布、降低过拟合;验证集只做归一化,不做任何增强,这样评估结果才能反映模型对真实图片的识别能力。class_mode='categorical' 表示标签是 one-hot 编码,对应后面的 categorical_crossentropy 损失函数。

target_size 必须和模型输入一致,VGG16 是 (224, 224, 3),换成别的模型时要跟着改。batch_size 32 是常见起步值,显存不够就降到 16。

3.3 构建模型与冻结策略:哪些层要冻、哪些层要训

拿到预训练模型后,第一件事是调用它的卷积基,并且把 trainable 设为 False。这一步是迁移学习的核心:冻结卷积基,让第一轮训练只调整分类头。

from tensorflow.keras.applications import VGG16 from tensorflow.keras import layers, models base_model = VGG16( weights='imagenet', # 载入ImageNet预训练权重 include_top=False, # 不要原来的1000类分类头 input_shape=(224, 224, 3) ) base_model.trainable = False # 冻结卷积基,只训练新增层 model = models.Sequential([ base_model, layers.GlobalAveragePooling2D(), # 把特征图压成向量,替代Flatten layers.Dense(256, activation='relu'), # 自定义全连接层 layers.Dropout(0.5), # 随机丢弃一半神经元防过拟合 layers.Dense(num_classes, activation='softmax') # 输出每个类别的概率 ])

GlobalAveragePooling2D 比 Flatten 好在参数量小得多,而且不容易过拟合,是现在迁移学习的常用写法。Dropout 0.5 是图像分类里的常规配置,训练时随机丢一半神经元,测试时全部参与。num_classes 是病虫害类别数,写代码前用len(train_generator.class_indices)确认一下。

3.4 训练管家:early stopping、checkpoint与epochs怎么设

训练配置里最值得说的不是 epochs 设多少,而是怎么让训练在合适的时候停下来并保存最好的模型。

from tensorflow.keras.optimizers import Adam from tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint model.compile( optimizer=Adam(learning_rate=1e-3), # 新分类头用1e-3安全 loss='categorical_crossentropy', metrics=['accuracy'] ) callbacks = [ EarlyStopping(monitor='val_loss', patience=5, restore_best_weights=True), ModelCheckpoint('model_vgg16.h5', monitor='val_accuracy', save_best_only=True) ] history = model.fit( train_generator, steps_per_epoch=train_generator.samples // 32, validation_data=valid_generator, validation_steps=valid_generator.samples // 32, epochs=30, callbacks=callbacks )

learning_rate 1e-3 是 Adam 优化器在迁移学习中的常见起点,如果用 1e-2 容易震荡,用 1e-4 收敛太慢。EarlyStopping 的 patience=5 意思是验证集损失连续 5 轮不下降就停止,restore_best_weights=True 保证停下时模型回到验证损失最低那轮的权重。ModelCheckpoint 只保存验证准确率最高的那一版,避免后面训练过拟合把好模型覆盖掉。

steps_per_epoch 这里用样本总数整除 batch_size,如果数据集量不是 32 的整数倍,Keras 会自动丢弃最后不满一批的数据,训练日志里会看到总步数和实际样本数不一致,这是正常的。

注意:第一轮训练只训新增层,通常 20~30 轮就能收敛。想再提精度,第二轮解冻卷积基的最后几层,改用 1e-5 的学习率微调,这是工程里最常用的两阶段训练法。

4. 换骨架跑对比实验:ResNet50与DenseNet121的精度-速度取舍

答辩时“为什么用这个模型”是最容易被追问的问题。只跑一个 VGG16 说服力不够,但你把 ResNet50、DenseNet121 都跑一遍,再给出对比数据,这个问题就变成了加分项。这套资源正好把这三个模型都备好了。

4.1 backbone参数对比:深度、参数量与推理速度

模型层数(近似)参数量推理速度特点
VGG1616约 1.38 亿慢结构规整,适合教学演示
ResNet5050约 2560 万中残差连接解决深层退化,精度高
DenseNet121121约 800 万较快密集连接,参数效率高,不易过拟合

这个表基本决定了三个模型的分工:VGG16 画结构图,ResNet50 出精度,DenseNet121 提速度。ResNet50 的残差结构让网络堆到 50 层也不会出现梯度消失;DenseNet121 每一层都连接前面所有层的特征图,所以参数少但特征复用充分。对小数据集来说,DenseNet121 其实比 ResNet50 更稳,过拟合风险更低。

4.2 用工厂函数批量训练:同一套数据换backbone

不要复制三份训练代码来跑三个模型,把模型构建逻辑写成函数,换 backbone 只需要改一个参数。

from tensorflow.keras.applications import ResNet50, DenseNet121 def build_model(backbone, input_shape=(224, 224, 3), num_classes=10): base_model = backbone( weights='imagenet', include_top=False, input_shape=input_shape ) base_model.trainable = False model = models.Sequential([ base_model, layers.GlobalAveragePooling2D(), layers.Dense(256, activation='relu'), layers.Dropout(0.5), layers.Dense(num_classes, activation='softmax') ]) return model model_resnet = build_model(ResNet50) model_densenet = build_model(DenseNet121)

这个函数的好处是保证三个模型除了 backbone 之外完全一致:同样在 ImageNet 权重上冻结卷积基、同样的 GlobalAveragePooling2D、同样的 256 维全连接和 Dropout。这样跑出来的对比结果才能归因于模型结构差异,而不是预处理或分类头设置不同。训练时保持 batch_size、epochs、数据增强策略都一样,最多给每个模型单独调一下 learning_rate。

数据增强上我建议三个模型共用同一份增强配置,DenseNet121 本身抗过拟合能力强,给它的增强强度甚至可以比 VGG16 弱一点,但为了对比公平,还是统一配置更严谨。

4.3 评估维度怎么选:别只盯着accuracy

训练完看 loss 曲线和 accuracy 只是第一步。真正能发现问题的评估是在测试集上算分类报告和混淆矩阵。

from sklearn.metrics import classification_report, confusion_matrix import numpy as np test_generator = valid_datagen.flow_from_directory( 'dataset/test', target_size=(224, 224), batch_size=32, class_mode='categorical', shuffle=False # 保持顺序,确保预测结果和标签对齐 ) pred_probs = model.predict(test_generator) pred_labels = np.argmax(pred_probs, axis=1) print(classification_report(test_generator.classes, pred_labels)) print(confusion_matrix(test_generator.classes, pred_labels))

shuffle=False 是这里最容易出错的地方。 flow_from_directory 默认会打乱数据顺序,评估时一旦打乱,预测结果就和真实标签对不上,算出来的准确率是错的。加上 shuffle=False 之后,test_generator.classes 就是每个样本的真实标签,pred_labels 是预测结果,两者下标一一对应。

混淆矩阵的价值在于找易混类别。比如两种叶斑病特征相似,模型总把 A 预测成 B,这时候你就能针对性去做数据增强,或者多采一些 A 类的样本。这份资源本身就是多框架实现,你还可以拿 PyTorch 版和 Keras 版的结果互相验证,同一个模型在不同框架下精度应该接近,如果差太多,通常是预处理或数据划分出了问题。

5. 把模型包装成Flask识别系统:部署思路与常见避坑

训练得到模型只是前半程,毕设演示是需要一个能上传图片、返回识别结果的可视化界面的。这就是 server.py 的价值。很多人卡在这一步,不是因为代码难,而是训练环境和部署环境没有对齐,导致模型在 Notebook 里好好的,一到 Flask 里就报错或者结果全乱。

5.1 从Notebook到server.py:把预测逻辑抽成接口

训练 Notebook 里的代码是面向分析的,部署代码要面向请求。核心区别是:模型只加载一次,而不是每次请求都加载;预处理逻辑要和训练时完全一致。

from flask import Flask, request, jsonify, render_template from tensorflow.keras.models import load_model from tensorflow.keras.preprocessing.image import img_to_array, load_img import numpy as np app = Flask(__name__) model = load_model('models/model_resnet50.h5') # 启动时加载一次 CLASS_NAMES = ['类名1', '类名2', '类名3'] # 顺序必须和训练时一致 def preprocess_image(file_storage): img = load_img(file_storage, target_size=(224, 224)) # 与训练尺寸对齐 img = img_to_array(img) img = img / 255.0 # 与训练时归一化一致 img = np.expand_dims(img, axis=0) # 加batch维度 return img @app.route('/predict', methods=['POST']) def predict(): file = request.files['image'] img = preprocess_image(file) probs = model.predict(img)[0] idx = int(np.argmax(probs)) return jsonify({ 'label': CLASS_NAMES[idx], 'confidence': round(float(probs[idx]), 4) }) if __name__ == '__main__': app.run(host='0.0.0.0', port=5000, debug=False)

逻辑说明:模型加载放在全局,启动时执行一次,预测时直接复用,否则每来一个请求就重新加载上百 MB 的权重,网页会卡到怀疑人生。preprocess_image 里做了两件大事:target_size 和训练保持一致,像素除以 255 和训练时保持一致。这两个一致稍有偏差,预测结果就会劣化。

CLASS_NAMES 的顺序也不是随便写的,flow_from_directory 会按文件夹名字典序生成类别映射,训练时的 class_indices 就是字典序。部署前把训练时的 class_indices 存成 JSON,部署时直接读,手写类名列表是高风险操作。host='0.0.0.0' 让服务监听所有网卡,答辩演示时同一局域网的电脑也能访问,而不是只能本机访问。

5.2 部署形态怎么选:本地Flask、Docker还是云平台

部署方式适用场景关键注意点
本地 Flask答辩演示、本地验证最简单,不依赖外网
Docker换机器、环境复现模型权重打进镜像,启动即用
GCP App Engine公网演示链接读取 app.yaml 配置启动
AWS公网演示或正式使用参考 aws_deployment.md 操作

本地跑是最稳的,启动 server.py 后访问 localhost:5000,上传一张叶片图片就能看到结果。Docker 的价值在环境一致性,你有多少台电脑就少配多少次 GPU 环境。云端部署是加分项,但要注意免费额度的限制,以及上传图片超时的配置,不是跑通 Flask 就能直接搬上去的。

提示:答辩前至少提前一天把本地 Flask 服务完整演示一遍,包括换一张没见过的图片测试。当天才第一次打开网页这种事,我不建议赌。

5.3 避坑:加载失败、OOM、结果全变成一个类

这一节是血泪经验,踩过的人都懂。

避坑1:load_model 加载模型报错或提示 Unknown layer

现象:server.py 启动时加载 .h5 文件,报错内容包含 Unknown layer 或无法解析。

原因:训练环境和你现在运行的 TensorFlow/Keras 版本不一致。老版本 Keras 保存的模型,新版本可能识别不了自定义层。

解决:先跑pip list对比两边的 keras 和 tensorflow 版本。最稳妥的做法是直接用资源自带的 requirements.txt 重建环境,不要用最新版 TensorFlow 去加载旧模型。如果必须换版本,用model.save('model.keras')新格式重新保存一次权重。

避坑2:训练时 GPU 显存不够,报 OOM

现象:跑到中途直接报ResourceExhaustedError: OOM when allocating tensor。

原因:batch_size 太大,或者模型本身的中间激活值把显存占满了。VGG16 的显存占用明显高于 DenseNet121。

解决:先把 batch_size 从 32 降到 16,再不行降到 8。如果数据量允许,同时把输入尺寸从 224 降到 192,显存占用会明显下降。再不够就换 DenseNet121,它的参数量是 VGG16 的零头。升级到 PyTorch 版本时还可以开混合精度训练。

避坑3:Flask 里预测结果几乎全是同一个类别

现象:模型在测试集上准确率正常,但在网页上上传不同图片,预测结果长时间不变,或者结果明显不符合常识。

原因:预处理没对齐。最常见的是训练时做了归一化,Flask 里忘了除以 255;或者 resize 尺寸不一致;再或者用 OpenCV 读取图片时,通道顺序从 RGB 变成了 BGR,特征完全错位。

解决:把数据加载和预处理抽成一个公共函数,训练脚本和部署脚本共用这一份代码,从根源上杜绝两处逻辑不一致。我现在的习惯是训练前先打印一张预处理后的图片确认没问题,部署时拿同一张图走一遍同样的预处理,再对比结果。

避坑4:预测类别总是错一个固定位置

现象:识别结果看起来是“对的那一类”的相邻类别,置信度还挺高。

原因:CLASS_NAMES 手写顺序和训练时 flow_from_directory 生成的 class_indices 顺序不一致。按字典序排的文件夹名和你自己整理的自然语言顺序很容易出现偏差。

解决:训练完成后立刻把 class_indices 存成 JSON,部署时直接加载这份映射,不要手动维护。这是我一直强调的习惯,因为这类 bug 不容易察觉,但它会让整个系统看起来完全不可用。

6. 把识别结果做扎实:置信度阈值、数据增强与模型瘦身的三步走

模型跑通、网页能识别之后,离“高分毕设”还差一步:让识别结果可解释、可辩护、演示时更快。我通常补三件事,按优先级排序。

第一件事是置信度阈值。训练好的模型即使对错图也会硬生生输出一个最高概率类别,哪怕这个概率只有 0.3。真正可用的系统应该允许“不确定”的答案。

threshold = 0.7 if probs[idx] < threshold: return jsonify({'label': 'unknown', 'confidence': round(float(probs[idx]), 4)})

阈值 0.5 是底线,0.7 是常用值。低于阈值的图片返回“unknown”,而不是强行给一个病害类别。这在简历上和答辩里是一个可以主动讲的加分点,说明你考虑过模型的可靠性边界。

第二件事是数据增强的针对性调整。病虫害识别和普通物体分类不一样,叶片在不同光照、不同拍摄角度下表现差异很大。rotation 和 zoom 是基础,我一般会额外加一点亮度调整,模拟阴天、强光和遮阴下的拍摄条件。增强强度别拉满,rotation_range 设 20 度、zoom_range 设 0.2 够用,太狠会把叶片本身的结构特征也给扭曲掉。

第三件事是模型瘦身。答辩现场用 CPU 跑 VGG16 会很吃力,换成 TensorFlow Lite 格式能明显提速。训练完把模型转成 .tflite,开发板和我们自己的电脑跑起来都流畅得多,这个动作本身就是工程能力的体现。

tflite_convert --saved_model_dir=./saved_model --output_file=model.tflite

从那以后我每次做图像分类的毕设项目,都强制走一遍“训练 → 部署 → 低置信度拦截”这三步,先跑通再调优,每一步都用文档固定下来。这套流程里的坑我踩过不少,希望你绕过去,希望帮到你。

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

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

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

立即咨询