简介:这份智能病虫害防治系统资源包面向农业信息化开发者、计算机视觉学习者与智慧农业项目实践者,围绕图像识别在植物病虫害检测中的落地流程展开,涵盖数据收集、图像预处理、特征提取、模型训练、验证测试到部署应用与实时监测的完整链路,帮助读者理解如何借助机器学习算法自动识别并分类植物病虫害,减少对化学农药的依赖。压缩包共13个文件,约10.83MB,以6个Python脚本为核心,配合2张PNG示意图、1个HDF5模型文件、1个img-store数据存储、1个txt参数文件及说明文档,另含一个玉米叶部病害识别子项目压缩包,结构清晰便于按模块查阅。目前已有92人学习下载。读者可从中获取可运行的检测脚本、已训练模型、系统架构图与参数配置,用于复现识别流程、理解模型部署思路,并作为课程设计或智能农业设备集成的参考。
1. 拆开这个病虫害检测包:从 HDF5 权重到 MQTT 上报的完整链路
上周帮一个做温室大棚的朋友看他们的虫情测报设备,现场那套东西识别率忽高忽低,维护的人换了两拨都没定位到问题。拆开设备一看,模型是 Keras 存的.hdf5,推理脚本里图像尺寸写死成 224,但摄像头实际出图是 240×240,预处理那一步直接 resize 没做中心裁剪,边缘的叶片纹理全糊了。这类问题在病虫害检测项目里特别常见——模型本身没问题,坏在部署链路的细节上。
这个「智能病虫害防治系统.zip」就是一套把图像识别落到实处的工程包。它不只是一份训练代码,而是从model.hdf5权重、detect_publish.py推理发布、mqtt_subscriber.py订阅上报,到param.txt参数配置、ESP32-S3-EYE 硬件参考图都串起来了。适合两类人:一是想跑通「拍照→识别→上报」闭环的嵌入式或后端工程师,二是手里有玉米叶病害数据、想找个能直接改的推理框架的算法同学。包里还附了 LeNet 玉米叶病害识别的参考实现,方便对照轻量模型和 CNN 的差异。
2. 先看清目录再动手:文件职责与运行环境怎么搭
2.1 每个文件到底管什么
拿到压缩包别急着pip install,先把目录结构过一遍。这个包的文件命名比较直白,但有几个容易混淆的地方,我按实际调用关系拆一下:
| 文件/目录 | 职责 | 调用方 |
|---|---|---|
model.hdf5 | Keras 格式的推理权重 | detect_publish.py |
detect_publish.py | 读图、预处理、推理、发布结果 | 主入口 |
mqtt_subscriber.py | 订阅检测结果并落库/转发 | 独立进程 |
param.txt | 阈值、MQTT 地址、主题等配置 | 两个 py 都读 |
sample0~3.py | 分步骤示例,从读图到发布 | 学习用 |
LeNet-based-...zip | 玉米叶病害识别参考工程 | 对照学习 |
img-store/ | 测试图片存放 | 推理输入 |
Project_img/ | 架构图与硬件图 | 文档参考 |
sample0.py到sample3.py不是冗余文件,而是把完整流程拆成了递进式示例。我一般建议先跑sample0确认环境,再跑sample3看端到端效果,最后才动detect_publish.py。这样出问题能快速定位是环境、预处理还是发布环节。
2.2 环境依赖与版本约束
TensorFlow 2.x 对.hdf5的加载兼容性在 2.15 之后有变化,如果直接pip install tensorflow装到最新版,load_model可能报Unknown layer或bad marshal data。稳妥做法是锁一个 2.13~2.15 的区间:
# 建议用虚拟环境隔离,避免和系统里的 TF 冲突 python -m venv venv_bug source venv_bug/bin/activate # Windows 用 venv_bug\Scripts\activate # 锁版本,opencv 用于预处理,paho-mqtt 用于发布 pip install "tensorflow==2.15.0" opencv-python paho-mqtt numpy pillow这里tensorflow==2.15.0是实测能稳定加载该包权重的版本,再高会触发 Keras 3 的兼容层问题。opencv-python负责图像解码和 resize,paho-mqtt是 MQTT 客户端。如果目标设备是 ARM 架构(比如树莓派或 ESP32 边缘网关),TensorFlow 要换成tflite-runtime,但那就需要先把.hdf5转成.tflite,这一步后面第 5 章会讲。
2.3 参数文件先改这三处
param.txt是纯文本键值对,但格式没有统一标准,读的时候要按实际分隔符解析。常见的是key=value或key:value,我一般先cat看一眼:
cat param.txt # 典型内容长这样: # img_size=224 # mqtt_broker=192.168.1.100 # mqtt_port=1883 # topic=bug/detect/result # threshold=0.6必须改的三处:mqtt_broker换成你自己的 Broker 地址,topic换成项目实际订阅的主题,threshold根据误报情况调。阈值 0.6 是偏保守的,实际大棚场景光照差的时候可以降到 0.5,但低于 0.45 就会把健康叶片的阴影误判成病斑。改完存盘,两个 py 脚本启动时都会重新读,不用改代码。
3. 推理链路拆解:从读图到 MQTT 发布
3.1 预处理为什么不能直接 resize
detect_publish.py里最容易被忽略的就是预处理。很多人拿到图直接cv2.resize(img, (224,224)),但训练时如果用的是中心裁剪加归一化,推理也必须一致,否则就是典型的训练-推理不一致,模型在测试集上 95%,现场掉到 70%。
import cv2 import numpy as np def preprocess(img_path, img_size=224): # 用 BGR 读入,和训练时 OpenCV 读图保持一致 img = cv2.imread(img_path) if img is None: raise ValueError(f"读图失败: {img_path}") # 先等比缩放到短边等于 img_size,再中心裁剪 h, w = img.shape[:2] scale = img_size / min(h, w) new_w, new_h = int(w * scale), int(h * scale) img = cv2.resize(img, (new_w, new_h), interpolation=cv2.INTER_LINEAR) # 中心裁剪 start_x = (new_w - img_size) // 2 start_y = (new_h - img_size) // 2 img = img[start_y:start_y+img_size, start_x:start_x+img_size] # 转 RGB 并归一化到 0~1 img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) img = img.astype(np.float32) / 255.0 return np.expand_dims(img, axis=0)逻辑说明:先等比缩放保证叶片不变形,再中心裁剪保证输入尺寸固定。interpolation用线性插值,比最近邻在纹理上更平滑。归一化除以 255 是大多数 CNN 的标配,但如果你的model.hdf5训练时用了preprocess_input(比如 MobileNet 系列),这里要换成对应的预处理函数,否则输入分布对不上。参数img_size必须和训练时的输入尺寸一致,这个信息一般在训练脚本或模型 summary 里能看到。
3.2 加载模型与推理的坑
加载.hdf5时如果报Unknown layer: Functional之类,通常是 Keras 版本不匹配。另一个常见问题是模型保存时用了自定义层,加载要传custom_objects。
from tensorflow.keras.models import load_model # 加载权重,compile=False 避免反序列化优化器状态 model = load_model("model.hdf5", compile=False) # 打印输入输出形状,确认和预处理对得上 print("输入:", model.input_shape) print("输出:", model.output_shape) # 推理 preds = model.predict(preprocess("img-store/test_leaf.jpg")) class_id = np.argmax(preds[0]) confidence = float(preds[0][class_id]) print(f"类别: {class_id}, 置信度: {confidence:.3f}")compile=False很关键,推理不需要优化器和损失函数,带上反而容易因为自定义损失加载失败。model.input_shape打印出来如果是(None, 224, 224, 3),说明预处理尺寸对了;如果是(None, 128, 128, 3),那img_size要改成 128。这一步是排查尺寸问题的后悔药,别省。
3.3 MQTT 发布与订阅的对接
detect_publish.py负责把结果发出去,mqtt_subscriber.py负责收。两者靠param.txt里的 topic 对齐。发布端代码结构大致如下:
import paho.mqtt.client as mqtt import json def publish_result(broker, port, topic, class_id, confidence): client = mqtt.Client() client.connect(broker, port, 60) payload = json.dumps({ "class_id": class_id, "confidence": round(confidence, 3), "ts": int(time.time()) }) # qos=1 保证至少送达一次,retain=False 不保留最后一条 client.publish(topic, payload, qos=1, retain=False) client.disconnect()参数说明:qos=1适合病虫害上报这种不能丢的场景,但会增加一次确认往返;如果设备网络差、上报频率高,可以降到 0。retain=False是因为检测结果是时序数据,保留最后一条反而会让新订阅者收到过期信息。订阅端mqtt_subscriber.py里on_message回调解析 JSON 后落库或转发,注意payload.decode()后再json.loads,直接对 bytes 做 loads 在部分版本会报类型错误。
4. 避坑与排查:现场最容易翻车的五个点
4.1 识别结果全是同一类
现象:不管输入什么图,输出都是类别 0,置信度还很高。原因通常是预处理没做归一化,或者输入通道顺序错了(BGR 当 RGB 喂进去)。解决:打印preprocess返回数组的shape和min/max,正常应该是(1,224,224,3)且值在 0~1。如果 max 是 255,说明归一化漏了;如果 shape 是(1,3,224,224),说明通道在前,需要transpose。
4.2 MQTT 连不上但代码不报错
现象:脚本跑完没异常,但订阅端收不到消息。原因是paho-mqtt的connect是异步的,publish后立刻disconnect可能导致消息还没发出去就断了。解决:在publish后加client.loop_start()或手动client.loop()处理网络循环,或者用wait_for_publish()确认发送完成再断开。
4.3 模型加载报 bad marshal data
现象:load_model抛bad marshal data或OSError: Unable to open file。原因是.hdf5文件在传输中损坏,或者下载不完整。解决:核对文件大小,用h5py尝试打开验证完整性:
import h5py with h5py.File("model.hdf5", "r") as f: print(list(f.keys())) # 能列出层名说明文件没坏如果h5py也打不开,只能重新获取权重文件。
4.4 阈值调了没生效
现象:改了param.txt里的threshold,但误报依旧。原因是脚本里阈值是硬编码的,没读配置文件,或者读了但变量名对不上。解决:全局搜threshold,确认读取逻辑是float(config["threshold"])而不是0.6写死。另外注意有些脚本会在if confidence > threshold之前先做argmax,阈值比较的是 softmax 后的概率,不是 logits。
4.5 ESP32 端内存不够
现象:参考ESP32-S3-EYE-isometric.png部署时,模型加载到一半重启。原因是.hdf5直接跑在 MCU 上不现实,ESP32-S3 的 PSRAM 也扛不住完整 Keras 模型。解决:必须转成 TensorFlow Lite 并用tflite-runtime或 ESP-DL 推理,输入尺寸也要降到 96 或 128。这一步不是改代码能解决的,是架构层面的取舍。
5. 进阶:把 HDF5 转 TFLite 并验证一致性
如果你要把这套东西真正落到边缘设备上,.hdf5只是中间产物,最终得转成.tflite。转换本身不难,难的是转换后精度不掉、输入输出对得上。
import tensorflow as tf # 加载已训练好的 Keras 模型 model = tf.keras.models.load_model("model.hdf5", compile=False) # 定义转换器,开启默认优化 converter = tf.lite.TFLiteConverter.from_keras_model(model) converter.optimizations = [tf.lite.Optimize.DEFAULT] # 如果有代表数据集,可以开 INT8 量化,体积能压到 1/4 # converter.representative_dataset = representative_gen # converter.target_spec.supported_ops = [tf.lite.OpsSet.TFLITE_BUILTINS_INT8] tflite_model = converter.convert() with open("model.tflite", "wb") as f: f.write(tflite_model) print("转换完成,大小:", len(tflite_model) / 1024, "KB")转换后必须做一致性验证,不能直接扔到设备上就跑。我一般会拿同一批测试图,分别用 Keras 和 TFLite 推理,比对 top-1 类别是否一致:
import numpy as np # 加载 TFLite 解释器 interpreter = tf.lite.Interpreter(model_path="model.tflite") interpreter.allocate_tensors() input_details = interpreter.get_input_details() output_details = interpreter.get_output_details() def tflite_predict(img_array): # TFLite 输入 dtype 可能是 float32 或 int8,按实际调整 interpreter.set_tensor(input_details[0]['index'], img_array.astype(np.float32)) interpreter.invoke() return interpreter.get_tensor(output_details[0]['index']) # 对比 10 张图 match = 0 for i in range(10): img = preprocess(f"img-store/test_{i}.jpg") keras_pred = np.argmax(model.predict(img, verbose=0)) tflite_pred = np.argmax(tflite_predict(img)) if keras_pred == tflite_pred: match += 1 print(f"一致率: {match}/10")一致率低于 9/10 就要查量化参数或输入 dtype。INT8 量化后输入是 int8 的话,预处理里的归一化要改成(img - 128) / 128之类的对称量化公式,这个细节翻车率极高。另外param.txt里的img_size在 TFLite 版本里也要同步改,因为量化模型通常要求固定输入尺寸,不能动态 resize。
从那以后我每次转 TFLite 都强制走一遍「Keras vs TFLite 十图比对」,一致率不过 9 就不往下走。这套流程帮我在三个大棚项目里提前拦住了量化掉点的问题,希望帮到你。
本文还有配套的精品资源,点击获取