ARTICLE DETAIL

资讯详情

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

基于TensorFlow与Flask的水稻病虫害识别系统

基于TensorFlow与Flask的水稻病虫害识别系统 简介本资源是一套面向农业智能化开发者的水稻病虫害智能识别系统完整实现适用于具备Python基础并希望实践深度学习模型部署的中级开发者与高校科研人员。系统基于TensorFlow构建CNN图像分类模型结合Flask框架封装为可运行的Web应用解决田间图像上传、实时识别与结果可视化等实际问题。压缩包共1337个文件83.24MB涵盖387个Python核心脚本含模型训练、Flask路由与预处理逻辑、299个GIF动图多为界面交互演示或训练过程可视化、115个JS前端交互文件及48个PNG/17个JPG图像资源另有HTML/CSS/Bootstrap等前端支撑文件结构清晰模块解耦度高。目前已有67人学习下载提供可直接运行的完整源码、h5模型文件、IPython Notebook训练示例、环境配置说明及接口调用文档特别适合用于课程设计、毕业项目或智慧农业原型快速验证。 现在很多做农业信息化的小伙伴私信我问得最多的一个问题就是能不能搞一个“拿过来就能跑、跑起来就能用”的农作物病虫害识别系统其实这类需求在智慧农业、植保无人机、农技推广App里都非常常见。这篇文章我就用水稻病虫害这个场景完整走一遍“数据集准备 → TensorFlow模型训练 → Flask封装成Web服务 → 源码工程化”的整个过程。项目标题《基于Python与TensorFlow的水稻病虫害智能识别系统实现含Flask Web应用与完整源码》里的每个关键词我都拆开做了一遍Python负责数据脚本和一切调度TensorFlow负责图像分类模型的训练与推理Flask负责把模型包装成一个可跨平台访问的Web接口最终交付一个真正可以本地运行、可以二次开发的完整工程。不管你是刚入门的Python学习者还是已经做过简单分类项目的开发人员这套流程都能直接套用甚至连“模型输出结果如何传回前端”这类细枝末节我都会写清楚。我自己的体会是很多入门者卡住的地方往往不在模型结构而在“数据集质量”和“训练与部署的环境一致性”这两件事上。所以这篇文章不会只丢一堆代码而是把每一步的取舍、参数选择的原因、以及我实际测试时踩到的坑都交代明白。1. 项目选型为什么是TensorFlow Flask而不是其他组合先说结论这个组合在“学术试验”和“产品原型验证”两个阶段之间找了一个很舒服的平衡点。选型不一定要追捧最新技术关键看项目交付形态、团队熟悉度和部署成本。1.1 框架之争TensorFlow与PyTorch的取舍这两年PyTorch在研究圈的热度确实很高尤其Transformer类模型方面更活跃但TensorFlow的Keras高层API在“快速搭建CNN模型”这件事上依然非常顺手而且TensorFlow Serving和SavedModel格式在工业部署上很成熟。我这里选TensorFlow 2.x还因为模型训练完成后导出为SavedModel格式可以直接被Flask调用也可以无缝切换到TensorFlow Serving做高并发扩展。如果你用的是PyTorch当然也能实现同样的功能但就这个项目而言我需要一个“安装省心、代码直观、序列化模型直接做推理”的方案。TensorFlow 2.18目前的安装兼容性不错要注意的是版本匹配问题建议使用Python 3.9到3.11之间的版本不要直接上最新的Python 3.13否则编译和依赖安装会有很多兼容性麻烦。1.2 Web框架Flask解决“最后一公里”模型训练完只是第一步实际使用中你得让农户、农技员或者上游业务系统能够调用模型。Flask在这里承担的角色就像仓库门口的提货窗口——背后是模型这个“大仓库”但外面的人只需要递进去一张图片、拿到一份结果就行。具体到本系统Flask只暴露两个关键接口GET /返回前端拍照上传页面POST /predict接收图片返回识别结果和置信度这个流程用Flask写起来相当简单且Flask本身就是轻量级方案不需要像Django那样引入ORM、Admin后台等重组件。考虑到这是单人开发、原型验证阶段的项目Flask的灵活性足够高日后要扩展为FastAPI也可以处理方式类似。1.3 模型结构为什么用MobileNetV2做迁移学习水稻病虫害图像分类本质是一个细粒度图像识别任务——不同病害之间的差异有时候很微小比如稻瘟病和胡麻叶斑病在初期都可能呈现褐色斑点。因此模型的特征提取能力必须足够强。我的选择是MobileNetV2作为骨干网络。原因有三模型体积小参数量约350万单次推理在CPU上也能控制在几十毫秒内后续部署到边缘设备也不吃力。预训练权重基于ImageNet虽然ImageNet里没有水稻叶片但底层特征边缘、纹理、颜色渐变是通用的迁移学习能大幅减少对数据量的需求。在TensorFlow中直接加载applications.MobileNetV2非常方便不需要自己实现复杂的残差块。在MobileNetV2的基础上我移除了顶部分类层换成全局平均池化 全连接层 Dropout层 Softmax输出层。这样可以保留迁移学习得到的底层特征同时自定义最终分类粒度。2. 数据集准备别让标签质量拖垮整个模型很多初学者在Ubuntu或Windows上装好TensorFlow之后第一件事就是急着写模型结果训练出来的准确率惨不忍睹。原因八成不在模型而在数据集。2.1 数据来源与类别设置本系统的目标是识别常见的几种水稻叶部病虫害我按实际需求预设了5个类别类别标签类别名称典型症状bacterial_leaf_blight水稻白叶枯病叶片边缘出现黄白色条斑blast水稻稻瘟病叶片有梭形斑中央灰白色brown_spot水稻胡麻叶斑病椭圆形褐色小斑点hispa水稻铁甲虫危害叶片表面被啃食成白色膜状healthy健康叶片无病斑颜色均匀我建议数据集中每类至少准备300张以上带标注的图片。如果原始公开数据集不够可以先用搜索引擎采集、再人工清洗也可以使用PlantVillage等公开数据集中相近的作物叶片做数据增强扩充。更好的做法是拍摄真实的田间照片——真实环境的背景干扰、光照变化、叶片遮挡都是训练集要覆盖的内容。2.2 清洗与标注的几个要点采集到的图片不能直接扔给模型至少要做三步预处理移除重复图和严重模糊图尤其是网络抓取的图片很多是同一个来源反复转载。剔除背景占比过高的图片模型应该学习叶片本身而不是农田背景。当然实际推理时用户上传的可能就是带背景的图所以训练集中可以保留一部分背景但比例不要超过20%。统一文件命名和目录结构建议目录结构为dataset/train/blast/xxx.jpg、dataset/val/blast/xxx.jpg用ImageDataGenerator或tf.keras.utils.image_dataset_from_directory直接按目录读取避免额外维护CSV标签文件。2.3 数据增强用最少的图训出更稳的模型数据增强相当于用平移、旋转、翻转等方式给模型“制造”更多样本提高泛化能力。对于作物病害这类“斑点位置不固定、拍摄角度多样”的图像增强策略我建议这样设定rotation_range20旋转范围20度以内对应田间拍摄角度差异。width_shift_range0.2、height_shift_range0.2水平垂直平移比例模拟物体在画面中不同位置。shear_range0.15错切变换可以模拟不同视角下的形变。zoom_range0.2缩放范围模拟远近不同的拍摄距离。horizontal_flipTrue水平翻转对叶片识别完全合理因为左右翻转不会改变病害类别本质。fill_modenearest填充模式平移或旋转后边缘产生的空白区域用最近邻像素填充。注意数据增强只应该在训练集上做验证集和测试集必须保持原始状态否则验证结果会被“乐观地污染”模型实际的泛化能力可能更低。2.4 图片尺寸与数据加载方式MobileNetV2默认输入尺寸是224×224。过小的图片会丢失病害细节过大的图片浪费计算资源224×224是一个公认的折中选择。实际代码中可以用target_size(224, 224)统一规范。在数据加载方式上我推荐tf.keras.utils.image_dataset_from_directory它直接返回tf.data.Dataset能自动做shuffle和batch且与TensorFlow的流水线优化契合得很好。相比之下旧版ImageDataGenerator.flow_from_directory速度较慢且需要额外用preprocessing_function做归一化现在没必要用了。3. 模型训练迁移学习的具体实现与参数解读这一步是整个项目的核心也是大多数人容易翻车的地方。我会把每一步为什么这么做讲清楚并直接给出能跑的代码逻辑。3.1 训练集与验证集的划分把所有图片按8:2或7:2:1划分成训练集、验证集和测试集。这里测试集是必须保留的否则你无法评价模型在从未见过的数据上的表现。建议在目录上直接划分image_dataset_from_directory的validation_split参数虽然能在读取时按比例切分但我更推荐手动建立独立的train、val、test三个目录这样更可控也能防止每次运行随机划分结果不同导致的结果不可复现。3.2 加载预训练模型并冻结底层模型核心代码结构如下import tensorflow as tf from tensorflow.keras import layers, models IMG_SIZE 224 NUM_CLASSES 5 base_model tf.keras.applications.MobileNetV2( include_topFalse, weightsimagenet, input_shape(IMG_SIZE, IMG_SIZE, 3) ) base_model.trainable False # 冻结底层 model models.Sequential([ base_model, layers.GlobalAveragePooling2D(), layers.Dense(128, activationrelu), layers.Dropout(0.5), layers.Dense(NUM_CLASSES, activationsoftmax) ]) model.compile( optimizertf.keras.optimizers.Adam(learning_rate1e-4), losssparse_categorical_crossentropy, metrics[accuracy] )这里有两个细节值得解释base_model.trainable False冻结底层的预训练权重让反向传播只更新新增的全连接层。由于MobileNetV2的底层已经学会了丰富的通用特征我们只需要在它的特征之上训练一个“分类头”即可训练速度快且不容易过拟合。learning_rate1e-4迁移学习的调参重点在于学习率不能太大。因为新初始化的全连接层权重和预训练权重尺度差异较大学习率过大会导致损失震荡。3.3 训练过程的回调机制我建议加上三个回调函数否则训练过程很容易失控callbacks [ tf.keras.callbacks.EarlyStopping( monitorval_loss, patience5, restore_best_weightsTrue, verbose1 ), tf.keras.callbacks.ModelCheckpoint( models/rice_disease_model.h5, monitorval_accuracy, save_best_onlyTrue, verbose1 ), tf.keras.callbacks.ReduceLROnPlateau( monitorval_loss, factor0.5, patience3, min_lr1e-6, verbose1 ) ] history model.fit( train_dataset, validation_dataval_dataset, epochs50, callbackscallbacks, batch_size32 )EarlyStopping一旦验证损失连续5轮不再下降就停止训练并恢复到验证损失最小的权重防止过拟合。ModelCheckpoint只保存验证准确率最高的那次权重而不是最后一轮。因为我们观察训练过程常会发现验证准确率峰值往往出现在中间某一轮。ReduceLROnPlateau当损失停滞时降低学习率相当于让模型从“大步走”切换为“小步挪”有助于找到更细的最优解。3.4 不平衡数据与类别权重如果某些类别图片特别少比如“健康叶片”有500张而“铁甲虫危害”只有200张模型会倾向于把多数类预测得更准。处理方式是在fit里传入class_weight参数通过sklearn.utils.class_weight.compute_class_weight计算各类别的权重。对本项目来说如果不平衡问题不严重可以不加但要通过混淆矩阵观察。3.5 评估模型不能只看accuracy训练脚本执行完不要急着看准确率就结束。我习惯的做法是输出分类报告和混淆矩阵from sklearn.metrics import classification_report, confusion_matrix import numpy as np y_true [] y_pred [] for images, labels in test_dataset: preds model.predict(images) y_pred.extend(np.argmax(preds, axis1)) y_true.extend(labels.numpy()) print(classification_report(y_true, y_pred, target_namesclass_names))通过classification_report你可以看到每一类的精确率precision、召回率recall和F1分数。如果发现“稻瘟病”的召回率特别低说明这类样本被大量误判为其他类这时候优先补充这类样本、增加该类数据增强强度往往比调网络结构更有效。3.6 验证阶段检查过拟合的典型信号训练过程中如果出现训练准确率很高比如98%但验证准确率长期在80%左右徘徊就是典型的过拟合。这时候你该做的是降低模型复杂度减少Dense层的神经元数量或者增强Dropout比例。增加数据增强强度比如把rotation_range提到30。增加样本量这是最有效但成本最高的方法。我实测在这个水稻数据集上经过约15~20轮训练验证准确率能到达90%以上测试集准确率在88%~92%之间浮动。这个量级在原型验证阶段已经足够有说服力。4. Flask Web应用把模型封装成可上传图片的在线服务模型训练完后就要进入部署环节了。这里的核心思想是“模型只加载一次HTTP接口处理多次请求”不要把加载模型的操作放进每次请求的函数里否则内存会被频繁撑爆响应速度也会慢到无法接受。4.1 项目结构与依赖我建议整个Web应用按下面的目录组织rice_disease_system/ ├── app.py # Flask主程序 ├── requirements.txt # 依赖清单 ├── models/ │ └── rice_disease_model.h5 ├── utils/ │ └── preprocessing.py # 图片预处理函数 ├── static/ │ └── uploads/ # 上传图片临时目录 ├── templates/ │ └── index.html # 前端页面 └── dataset/ ├── train/ ├── val/ └── test/requirements.txt里至少要包含这些tensorflow2.18.0 flask3.0.0 pillow10.1.0 numpy1.26.4 gunicorn21.2.0注意pillow必须装Flask接收图片后的解码和尺寸调整全靠它。gunicorn是生产环境下替代app.run()的WSGI服务器Windows上如果需要本地调试可以不装Linux部署时建议使用。4.2 模型加载写一个单例类Flask应用启动后如果每次请求都重新加载模型不仅速度缓慢还会带来内存碎片。正确做法是让模型常驻内存from tensorflow.keras.models import load_model class RiceDiseaseModel: _instance None def __new__(cls): if cls._instance is None: cls._instance super().__new__(cls) cls._instance.model load_model(models/rice_disease_model.h5) cls._instance.class_names [bacterial_leaf_blight, blast, brown_spot, hispa, healthy] return cls._instance这个单例模式保证了模型只被加载一次后续请求复用同一个实例内存占用保持稳定。4.3 图片预处理训练与部署必须完全一致这是最容易踩坑的点。训练时我们用了image_dataset_from_directory搭配一个预处理层通常是Rescaling(1./255)部署时如果忘了做同样的归一化模型预测结果就会出现严重偏差。我专门写了一个预处理函数放在utils/preprocessing.pyfrom PIL import Image import numpy as np IMG_SIZE 224 def preprocess_image(image_bytes): img Image.open(image_bytes).convert(RGB) img img.resize((IMG_SIZE, IMG_SIZE), Image.Resampling.LANCZOS) img_array np.array(img, dtypenp.float32) img_array img_array / 255.0 img_array np.expand_dims(img_array, axis0) return img_arrayImage.Resampling.LANCZOS是高质量的重采样算法比默认的Image.BILINEAR效果更好对缩小图片时保留纹理细节有帮助。用convert(RGB)则是为了避免用户上传PNG透明通道或灰度图时导致通道数不一致。重要提示如果训练时用的预处理不是简单的除以255而是tf.keras.applications.mobilenet_v2.preprocess_input那么部署时也必须调用同一个函数二者一定不能混用。4.4 Flask主程序实现主程序app.py的核心逻辑如下import os import uuid from flask import Flask, request, jsonify, render_template from utils.preprocessing import preprocess_image from models.model_loader import RiceDiseaseModel app Flask(__name__) app.config[MAX_CONTENT_LENGTH] 8 * 1024 * 1024 # 限制上传8MB model_loader RiceDiseaseModel() 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: 未接收到图片文件}), 400 file request.files[file] if file.filename : return jsonify({error: 文件名为空}), 400 try: img_array preprocess_image(file.stream) preds model_loader.model.predict(img_array, verbose0)[0] pred_index int(preds.argmax()) confidence float(preds[pred_index]) result { class_id: pred_index, class_name: model_loader.class_names[pred_index], confidence: round(confidence * 100, 2), all_probs: { name: round(float(p), 4) for name, p in zip(model_loader.class_names, preds) } } return jsonify(result) except Exception as e: return jsonify({error: f推理失败: {str(e)}}), 500 if __name__ __main__: app.run(host0.0.0.0, port5000, debugFalse)注意几点host0.0.0.0是为了让局域网内的其他设备也能访问而不仅是本机localhost。如果你只想本机访问改成127.0.0.1即可。model_loader.model.predict(img_array, verbose0)中verbose0可以关闭TensorFlow在每次预测时打印进度条避免污染Flask日志。MAX_CONTENT_LENGTH对上传文件大小做了硬性限制防止有人恶意上传超大图片拖垮服务。4.5 前端页面前端用一个简单的HTML页面就够了核心是表单上传图片后用fetch把结果渲染到页面上。不必引入Vue或ReactFlask的Jinja2模板配合原生JavaScript完全能胜任。!DOCTYPE html html langzh head meta charsetUTF-8 meta nameviewport contentwidthdevice-width, initial-scale1.0 title水稻病虫害识别系统/title /head body h1水稻病虫害智能识别/h1 form iduploadForm input typefile namefile acceptimage/* required button typesubmit开始识别/button /form div idresult h3识别结果/h3 p idclass_name/p p idconfidence/p p idall_probs/p /div script const form document.getElementById(uploadForm); form.addEventListener(submit, async function(e) { e.preventDefault(); const formData new FormData(this); const resp await fetch(/predict, { method: POST, body: formData }); const data await resp.json(); document.getElementById(class_name).textContent 类别 data.class_name; document.getElementById(confidence).textContent 置信度 data.confidence %; document.getElementById(all_probs).textContent JSON.stringify(data.all_probs, null, 2); }); /script /body /html在fetch发出请求后我建议加一个加载状态的提示比如按钮变成“识别中...”避免用户重复点击这个细节对野外弱网环境尤其重要。5. 训练与部署的完整流程从零复现的全部命令很多刚接触这一套的人最大的问题不是代码逻辑而是不知道在命令行里执行哪些命令。这里我把从搭建环境到启动Web服务的完整命令串一遍Windows和Linux都适用。5.1 创建虚拟环境并安装依赖强烈建议使用虚拟环境不要把TensorFlow装进系统Python。我在实际项目里见过太多把系统Python搞坏、然后再也装不上包的惨案。python -m venv rice_venvWindows激活rice_venv\Scripts\activateLinux/macOS激活source rice_venv/bin/activate然后安装依赖pip install --upgrade pip pip install tensorflow2.18.0 flask3.0.0 pillow numpy如果安装TensorFlow时提示缺MSVC运行库或C编译环境Windows上需要先安装“Microsoft C Build Tools”选择“使用C的桌面开发”工作负载。Linux上如果GPU版本装不上先使用CPU版本跑通流程以后再解决CUDA和cuDNN的版本匹配问题。5.2 训练模型假设项目根目录下已经有了train.py脚本执行python train.py训练过程中模型会自动保存到models/rice_disease_model.h5。如果中途意外退出只需要重新运行ModelCheckpoint会从之前保存的最佳权重文件继续这里需要注意ModelCheckpoint.save_best_onlyTrue只会保存最佳权重不会自动从上次中断点恢复训练。如果你需要断点续训得额外使用tf.train.Checkpoint或把每轮权重都保存下来但作为单机训练直接重跑一遍损失也不大。5.3 启动Flask应用python app.py见到以下日志说明启动成功* Running on all addresses (0.0.0.0) * Running on http://127.0.0.1:5000浏览器打开http://127.0.0.1:5000就能使用页面了。5.4 生产环境部署用Gunicorn替代开发服务器Flask自带的开发服务器并发能力很弱局域网几个人同时访问就会卡。如果要做演示或内网部署建议用Gunicorngunicorn -w 4 -b 0.0.0.0:5000 app:app但要注意Gunicorn默认使用sync worker在Windows上不支持只能在Linux/macOS下使用。Windows生产环境可以考虑用waitresspip install waitress waitress-serve --listen0.0.0.0:5000 app:app多worker进程启动后每个worker都会各自加载一份模型内存消耗是worker数乘以模型大小这个预期要在计划内。5.5 常见的几个报错与解决办法这部分确实值得单独列出来每一项都是我实际遇到过、查了挺久才解决的。报错1ValueError: Cannot assign value to variable block_1_conv1/kernel:0 due to variable shape...这通常是模型训练时输入尺寸或类别数与加载时不一致所致。检查input_shape和最后一层Dense的个数是否与训练时一致。报错2OOM when allocating tensor...GPU显存不足。把batch_size从32减到16或8如果还不行就改用CPU推理。注意即使使用CPU推理TensorFlow也可能尝试预留GPU显存可以在代码开头设置import tensorflow as tf physical_devices tf.config.list_physical_devices(GPU) if physical_devices: tf.config.experimental.set_memory_growth(physical_devices[0], True)报错3Flask接收图片后PIL报错cannot identify image file通常是因为文件读取位置不对。用file.stream.read()读取字节串后再喂给Image.open()或者传BytesIO对象注意不要使用全路径文件名。报错4预测结果总是固定同一类大概率是输入图片没有被正确预处理特别是归一化方式不一致。我在模拟测试时把训练时的归一化从/255.0换成preprocess_input后模型输出的类别概率分布发生了剧烈变化这个错误非常隐蔽。6. 优化与扩展在原型基础上升级系统的四个方向整条链路跑通之后你会发现模型还有很大的优化空间。如果只是为了毕设或演示当前版本已经够用了。但如果要往真实业务方向走我根据自己的实践总结出四个方向优先级从高到低。6.1 从单图分类到批量识别目前的Web接口一次只能传一张图。但实际应用中农技人员一次可能拍了一二十张叶片图片。改进方法是增加一个批量接口支持上传zip压缩包或一次上传多个文件后端循环预测并汇总结果。这个改动对Flask代码量增加不大但实用价值非常高。6.2 引入更精细的病害分级病虫害识别不只关心“是什么病”还应该关心“严重程度”。这个需要把数据集从“类别标注”升级为“类别严重程度标注”比如稻瘟病分为轻度、中度、重度。这个可以在最后输出类别的同时再增加一个回归分支或者多分类分支。如果你不想改模型结构也可以把严重程度作为一个额外的分类任务单独训练一个模型。6.3 优化小目标识别与背景鲁棒性田间拍摄的图片经常有大量泥土背景、喷洒设备和遮挡物直接送入模型可能会被背景带偏。可以增加一个YOLO或其他目标检测模型先检测出叶片位置裁剪后再送入分类模型。这种“检测分类”的级联结构在农业视觉里非常常用。6.4 模型量化和轻量化部署如果最终要部署到树莓派或手机端可以用TensorFlow Lite对模型做量化把浮点模型转换成int8模型尺寸从14MB左右降到4MB左右推理速度在CPU上还能再快一些。转换脚本很简单import tensorflow as tf converter tf.lite.TFLiteConverter.from_keras_model(model) converter.optimizations [tf.lite.Optimize.DEFAULT] tflite_model converter.convert() with open(model_int8.tflite, wb) as f: f.write(tflite_model)不过量化后准确率通常会有1%~3%的下降需要重新在测试集上评估是否还在可接受范围内。关于扩展方向我个人的建议是优先做“批量识别”和“检测分类级联”这两者对真实用户感知的提升最明显。模型量化只在你确认部署目标设备之后再做否则优先级可以放低。整个项目从数据集清洗、模型训练到Flask部署、源码组织每一环都不会凭空造轮子而是基于当前主流实践做取舍。我也建议你拿到源码后别急着改网络结构先完整跑通一遍再用自己的数据替换数据集感受从训练到推理的完整链路。等这一步稳了再围绕业务场景做功能迭代你会有完全不同的从容感。本文还有配套的精品资源点击获取
返回列表