ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

基于Python机器学习的水稻病虫害自动识别系统实战

基于Python机器学习的水稻病虫害自动识别系统实战 简介本资源为基于Python机器学习的水稻病虫害自动识别系统面向农业信息化方向的学生、开发者及科研人员帮助解决水稻病虫害图像分类与智能识别问题适合具备一定Python与机器学习基础的中高级学习者。压缩包共312个文件约2.56MB涵盖xml配置、java与class后端源码、vue前端页面、js脚本、jpg与png图像样本、css样式及md说明文档等前后端结构完整便于二次开发与部署调试。资源包含完整的识别系统代码与配套数据读者可据此理解从数据预处理、模型训练到前端展示的全流程实现思路并参考目录结构快速定位核心模块。目前已有192人学习下载适合作为课程设计、毕业设计或农业AI项目的实践参考。1. 水稻病虫害识别为什么总在田头翻车从一张糊图说起去年双抢时节一个种粮大户发来一张照片稻叶上全是灰白色斑点问我这是稻瘟病还是胡麻叶斑病。我放大一看糊得连叶脉都看不清手机还是五年前的老机型。这就是水稻病虫害自动识别最真实的起点——不是实验室里的干净数据集而是逆光、抖动、带泥点子的田间照片。基于 Python 机器学习的水稻病虫害自动识别系统要解决的核心问题就一句话把农户手机拍的一张糙图映射到稻瘟病、纹枯病、稻曲病、二化螟、稻飞虱这几类高频病虫害标签上并给出置信度。它适合两类人一类是想把机器学习落地到农业场景的 Python 开发者一类是手里有标注数据、想快速搭出可用原型的农技人员。整套方案不需要 GPU 集群一台带独显的笔记本就能跑通训练和推理关键是把数据、特征和部署这三段路走顺。2. 数据从哪来、怎么标水稻病虫害数据集的构建与清洗2.1 先想清楚要分几类再决定拍多少张很多人一上来就想着「多分类越多越好」结果标到一半发现稻飞虱和稻蓟马的危害状在低分辨率下几乎一样标注一致性直接崩掉。我的建议是先锁定 6 到 8 个类稻瘟病、纹枯病、稻曲病、白叶枯病、二化螟危害、稻飞虱危害再加一个健康叶片类。每类至少 300 张有效图总量控制在 2500 到 4000 张之间这个量级用迁移学习足够出效果。采集时有个血泪经验别只拍病斑特写。要按「远中近」三档拍——远档拍整株或田块中档拍单叶近档拍病斑纹理。因为农户实际拍照距离不可控训练集里没有远档样本模型一遇到整株图就懵。每张图记录三个元数据拍摄距离档位、光照条件顺光/逆光/阴天、水稻生育期。这三个字段后面做数据增强和误差分析时非常有用。2.2 用 Python 做去重和坏图过滤田间采集的图里连拍产生的近似重复图能占到三成不清理会直接导致训练集和验证集泄漏。下面这段脚本用感知哈希做去重同时过滤掉过暗和过糊的图。import cv2 import numpy as np import os from imutils import paths def phash(image, hash_size8, highfreq_factor4): 计算感知哈希用于近似重复图检测 img_size hash_size * highfreq_factor gray cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) gray cv2.resize(gray, (img_size, img_size)) dct cv2.dct(np.float32(gray)) dct_low dct[:hash_size, :hash_size] med np.median(dct_low) # 大于中位数的置1否则置0 return (dct_low med).flatten() def hamming_distance(h1, h2): return np.count_nonzero(h1 ! h2) def filter_images(src_dir, dst_dir, blur_thresh80, dark_thresh40, dup_thresh6): os.makedirs(dst_dir, exist_okTrue) hashes [] kept, dropped_blur, dropped_dark, dropped_dup 0, 0, 0, 0 for p in paths.list_images(src_dir): img cv2.imread(p) if img is None: continue gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 拉普拉斯方差判断模糊程度 blur_score cv2.Laplacian(gray, cv2.CV_64F).var() if blur_score blur_thresh: dropped_blur 1 continue if gray.mean() dark_thresh: dropped_dark 1 continue h phash(img) is_dup False for old_h in hashes: if hamming_distance(h, old_h) dup_thresh: is_dup True break if is_dup: dropped_dup 1 continue hashes.append(h) cv2.imwrite(os.path.join(dst_dir, os.path.basename(p)), img) kept 1 print(f保留 {kept} 张模糊剔除 {dropped_blur}过暗剔除 {dropped_dark}重复剔除 {dropped_dup}) filter_images(./raw_images, ./clean_images)逻辑说明phash把图像转灰度后做离散余弦变换取左上角低频部分与中位数比较生成 64 位指纹。hamming_distance计算两个指纹不同的位数小于等于dup_thresh就判为重复。blur_thresh是拉普拉斯方差阈值低于 80 基本是糊图dark_thresh是灰度均值下限低于 40 说明曝光严重不足。这两个阈值不是死的阴天拍摄的样本整体偏暗可以把dark_thresh降到 30但别低于 25否则会把正常阴天图误杀。2.3 标注规范给病斑画框还是给整叶打标签如果只做分类整叶打一个标签就够了。但如果想定位病斑位置、给农户更直观的反馈就需要目标检测标注。我的折中做法是分类模型用整叶标签同时用 LabelImg 对每类抽 100 张画病斑框训练一个轻量检测模型做辅助可视化。标注时统一约定病斑框只框明显病斑区域不框整片叶一张图里同类病斑超过 5 个就只框最大的 3 个避免标注疲劳导致质量下降。标注文件用 YOLO 格式的 txt每行类别id 中心x 中心y 宽 高坐标全部归一化到 0 到 1。3. 用迁移学习搭识别模型从 ResNet50 到田间可用3.1 为什么选迁移学习而不是从零训练水稻病虫害公开数据集规模有限从零训练一个 ResNet 基本会过拟合到验证集都救不回来。迁移学习的逻辑是ImageNet 上预训练的卷积核已经学会了边缘、纹理、颜色块这些底层特征而病斑识别本质上也是纹理和颜色模式的判别底层特征可以直接复用。常见做法是冻结主干网络的前面若干层只训练后面的分类头和部分高层卷积块。我一般会先冻结全部主干只训分类头 5 个 epoch再解冻最后两个 stage 做微调学习率降到 1e-4。3.2 完整训练脚本与关键参数下面这段代码用 PyTorch 实现数据增强用 torchvision 自带变换重点看参数注释。import torch import torch.nn as nn import torch.optim as optim from torchvision import datasets, models, transforms from torch.utils.data import DataLoader, random_split # 数据增强训练集用随机裁剪、翻转、颜色抖动模拟田间光照变化 train_tf transforms.Compose([ transforms.Resize((256, 256)), transforms.RandomResizedCrop(224, scale(0.7, 1.0)), # 模拟不同拍摄距离 transforms.RandomHorizontalFlip(), transforms.RandomVerticalFlip(), transforms.ColorJitter(brightness0.3, contrast0.3, saturation0.2), # 模拟光照差异 transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) ]) val_tf transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) ]) full_ds datasets.ImageFolder(./clean_images, transformtrain_tf) n_val int(len(full_ds) * 0.2) n_train len(full_ds) - n_val train_ds, val_ds random_split(full_ds, [n_train, n_val]) val_ds.dataset.transform val_tf # 验证集不做随机增强 train_loader DataLoader(train_ds, batch_size32, shuffleTrue, num_workers4) val_loader DataLoader(val_ds, batch_size32, shuffleFalse, num_workers4) model models.resnet50(weightsmodels.ResNet50_Weights.IMAGENET1K_V2) # 冻结主干 for param in model.parameters(): param.requires_grad False # 替换分类头类别数按实际数据集改 num_classes len(full_ds.classes) model.fc nn.Sequential( nn.Dropout(0.4), nn.Linear(model.fc.in_features, num_classes) ) device torch.device(cuda if torch.cuda.is_available() else cpu) model model.to(device) criterion nn.CrossEntropyLoss(label_smoothing0.1) # 标签平滑缓解标注噪声 optimizer optim.AdamW(model.fc.parameters(), lr1e-3, weight_decay1e-4) scheduler optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max10) best_acc 0.0 for epoch in range(15): model.train() for imgs, labels in train_loader: imgs, labels imgs.to(device), labels.to(device) optimizer.zero_grad() loss criterion(model(imgs), labels) loss.backward() optimizer.step() # 验证 model.eval() correct, total 0, 0 with torch.no_grad(): for imgs, labels in val_loader: imgs, labels imgs.to(device), labels.to(device) preds model(imgs).argmax(dim1) correct (preds labels).sum().item() total labels.size(0) acc correct / total scheduler.step() print(fEpoch {epoch1}, Val Acc: {acc:.4f}) if acc best_acc: best_acc acc torch.save(model.state_dict(), best_rice_model.pth)逻辑说明RandomResizedCrop的scale(0.7, 1.0)是关键它模拟了农户从不同距离拍摄导致的病斑占比变化。ColorJitter的亮度对比度扰动模拟阴天和逆光。label_smoothing0.1是因为田间标注难免有错标标签平滑能防止模型对错误标签过度自信。AdamW的weight_decay1e-4配合Dropout(0.4)一起压制过拟合。学习率用余弦退火初始 1e-3 只作用于分类头因为主干是冻结的。3.3 微调阶段怎么解冻、学习率怎么设分类头训到验证准确率不再上升后解冻layer4和layer3做微调。这一步的学习率必须降下来我一般设 1e-4 甚至 5e-5否则预训练权重会被冲垮。解冻后 batch size 可以减半到 16因为显存占用上来了。微调 5 到 8 个 epoch 通常能再涨 2 到 4 个百分点。如果验证损失开始反弹立刻停别硬撑。4. 避坑与排查水稻病虫害识别落地时最容易踩的五个坑4.1 验证集准确率 95%田头实测一塌糊涂现象训练日志里验证准确率很好看但拿农户新拍的照片一测错得离谱。原因训练集和验证集来自同一批采集图随机划分后同一片叶子的不同角度图分别进了训练和验证造成数据泄漏。解决按田块或按采集批次划分数据集同一块田的图要么全在训练集要么全在验证集。如果做不到至少按拍摄时间划分别用随机划分。4.2 模型把背景当特征拍到田埂就判成健康现象一张明显有稻瘟病的图因为背景是绿色田埂模型判成健康。原因数据集中健康类样本的背景大多是纯稻田病斑类样本背景杂乱模型学到了背景与标签的伪相关。解决训练时加入背景随机替换增强或者用 Grad-CAM 可视化检查模型关注区域。如果热力图集中在背景而非病斑说明特征学歪了需要重新设计增强策略。4.3 类别不平衡导致稻曲病永远识别不出来现象稻曲病样本只有 80 张其他类都有 400 张以上训练后模型对稻曲病的召回率不到 30%。原因交叉熵损失被多数类主导少数类梯度贡献太小。解决用加权交叉熵权重设为类别频率的倒数或者用重采样对少数类做过采样。我一般两个一起上权重公式是weight total / (num_classes * count_per_class)。4.4 推理速度太慢手机端跑不动现象ResNet50 在服务器上单张推理 40ms但部署到手机端要 800ms 以上。原因ResNet50 参数量 2500 万移动端算力吃不消。解决换 MobileNetV3 或 EfficientNet-B0 做学生模型用知识蒸馏从 ResNet50 迁移。蒸馏时温度设 4软标签损失权重 0.7硬标签损失权重 0.3学生模型准确率通常只掉 1 到 2 个点但推理速度能快 5 倍以上。4.5 输入图片尺寸不统一导致 batch 推理报错现象单张推理正常凑成 batch 就报维度不匹配。原因农户上传的图片长宽比各异直接 resize 到 224x224 会拉伸变形而且不同尺寸无法堆叠。解决推理前统一做中心裁剪加 resize或者用transforms.Resize(256)后CenterCrop(224)。如果坚持保留长宽比就 batch size 设 1但吞吐量会掉。生产环境我一般用固定 224 输入训练时就用这个尺寸推理时保持一致。5. 从模型到可用系统推理服务封装与田间验证技巧模型训完只是半成品真正让农户用起来还需要一层推理服务。我一般用 FastAPI 把模型包成 HTTP 接口输入是 base64 图片输出是类别和置信度。下面这段是核心推理逻辑重点看预处理和置信度过滤。import torch import base64 import cv2 import numpy as np from PIL import Image from io import BytesIO from torchvision import transforms class RiceDiseasePredictor: def __init__(self, model_path, class_names, devicecpu): self.device torch.device(device) self.class_names class_names self.model torch.load(model_path, map_locationself.device) self.model.eval() self.tf transforms.Compose([ transforms.Resize((256, 256)), transforms.CenterCrop(224), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) ]) def predict(self, img_b64, conf_thresh0.6): img_bytes base64.b64decode(img_b64) img Image.open(BytesIO(img_bytes)).convert(RGB) tensor self.tf(img).unsqueeze(0).to(self.device) with torch.no_grad(): logits self.model(tensor) probs torch.softmax(logits, dim1)[0] conf, idx probs.max(dim0) conf conf.item() if conf conf_thresh: return {label: 不确定, confidence: conf, advice: 图片不够清晰请靠近病斑重拍} return {label: self.class_names[idx.item()], confidence: conf, advice: self._get_advice(self.class_names[idx.item()])} def _get_advice(self, label): advice_map { 稻瘟病: 建议使用三环唑或稻瘟灵注意控制氮肥用量, 纹枯病: 建议使用井冈霉素保持田间通风降湿, 稻曲病: 抽穗前 5 到 7 天用药预防可用苯醚甲环唑, 白叶枯病: 细菌性病害用叶枯唑避免串灌漫灌, 二化螟: 检查枯心苗比例达到防治指标用氯虫苯甲酰胺, 稻飞虱: 压前控后用吡蚜酮或噻嗪酮, 健康: 未见明显病害继续保持当前管理 } return advice_map.get(label, 请咨询当地农技人员)逻辑说明conf_thresh0.6是置信度门槛低于这个值不硬判直接返回「不确定」并提示重拍。这个设计比强行给一个错误答案体验好得多农户拍糊了会自己重拍而不是拿着错误结论去打药。CenterCrop保证输入尺寸固定_get_advice把类别映射到具体用药建议这一步是让系统从「识别工具」变成「决策辅助」的关键。田间验证有个技巧别只测准确率要测「拒识率」和「误报代价」。拒识率是返回「不确定」的比例控制在 15% 以内比较合理。误报代价是指把健康判成病害和把病害判成健康后者代价高得多所以阈值可以偏向「宁可拒识不可漏判」。我一般会把病害类的判定阈值降到 0.5健康类保持 0.6让模型对病害更敏感。最后说个习惯每次模型更新后我都会拿同一批 50 张「历史翻车图」跑一遍回归测试。这批图是过去半年里模型判错的典型样本专门用来防止新模型在旧问题上重蹈覆辙。这个习惯帮我省了至少三次线上事故。希望帮到你。本文还有配套的精品资源点击获取
返回列表