资讯动态

YOLOv11农业病害检测实战:从数据准备到ONNX部署全流程

发布时间:2026/10/17 16:14:29 来源:尧图企业网站定制
上个月我拿YOLOv11做了一轮农业病害检测的完整验证数据集用的是公开的PlantDoc从中筛了番茄和玉米的6类常见病害从标注格式转换、训练调参到导出ONNX推理全部走了一遍。整个过程确实踩到了一些数据侧的坑比如XML标注转YOLO格式、类别不均衡、病害之间视觉特征太接近导致的误检这些都不是模型本身难调而是数据处理没做细。如果你正在考虑用目标检测做作物病害识别这篇文章就把我实际跑通的流程拆开讲清楚包括选型理由、数据准备、训练命令、评估指标和部署思路适合已经会一点YOLO基础、但对农业场景还没上手的朋友参考。1. 选型思考为什么为了数病斑最后落在YOLOv11上1.1 图像分类和目标检测任务边界完全不一样很多人刚接触农业AI时第一个想法是做图像分类把一张叶片图片丢进去判断“番茄早疫病”或“健康”。分类模型确实简单但有一个前提图片里必须只有一片叶子而且病害区域要占据画面主体。真实场景不是这样你拿手机在田里拍一张照片画面里往往有七八片叶子有的叶子有病斑有的没有还有杂草、泥土、地膜做背景。这时候分类模型会非常痛苦它不知道应该聚焦哪片叶子预测结果也没有位置信息。目标检测不一样它一次输出“哪里有病”和“是什么病”也就是每个目标的边界框加上类别。农业需求里很多都依赖这个框例如统计染病叶片数量、计算病斑面积占比、引导定点喷药。所以从任务边界看农业病害识别更适合目标检测而不是分类。YOLO系列是单阶段检测器里工程落地最成熟的一支训练生态完整模型文件导出方便这也是我选它而不是更早的两阶段方案的主要原因。1.2 YOLOv11到底比YOLOv8改了什么值不值得升级YOLOv11是YOLO系列近期的迭代版本在主干和颈部结构上做了不少改动比如用新的C3k2模块替换了旧的C2f整体在同等参数量下特征提取效率更高。我实际的体感是同一份PlantDoc子集用YOLOv11s得到的收敛速度比YOLOv8s稍快一点最终mAP也略高而推理速度几乎没有下降。对农业病害场景来说YOLOv11带来的价值主要集中在两个方面一是小目标早期病斑的召回率更稳二是同精度档位下模型文件更小方便后面往边缘设备上塞。如果你之前在跑YOLOv5或者YOLOv8迁移到YOLOv11的成本很低因为调用方式基本一致训练脚本只需要改模型名数据格式完全通用。1.3 版本选择从n到x怎么匹配自己的显卡和场景YOLOv11官方给了n、s、m、l、x五个体积档位分别对应不同精度和速度。我在这个项目里用的是s原因很简单训练机器只有8G显存的消费级显卡s档位在batch为16、输入640分辨率的情况下刚刚好而且病害识别不需要像自动驾驶那样追求极致的召回s已经够用。版本定位推荐场景yolo11n轻量级速度快树莓派、低功耗工控机yolo11s速度精度均衡常规GPU训练与部署yolo11m精度更高显存占用上升离线分析、复杂背景yolo11l/x高精度推理较慢大图精细识别、科研验证如果你手里的显存只有4G建议直接用n把batch降到8输入分辨率降低到512也能跑。如果是要做高精度的科研统计可以考虑m或l但要接受推理速度变慢的事实。2. 数据准备PlantDoc和自定义数据集的取舍与转换2.1 PlantDoc数据集的结构和容易踩的坑PlantDoc是农业视觉领域用得比较多的公开数据集里面包含多种植物叶片的病害图片和标注框。我的做法是只抽取番茄和玉米筛出6个类别番茄早疫病、番茄晚疫病、番茄叶霉病、番茄细菌性斑点病、玉米锈病、玉米大斑病。类别数量控制在10个以内训练难度低也方便快速验证流程。这个数据集的标注格式是Pascal VOC风格也就是每个图片对应一个XML文件里面用bndbox节点记录坐标。直接用YOLO训练是不行的YOLO格式要的是每个图片一个TXT文件每行五个数字类别ID、归一化中心点x、归一化中心点y、归一化宽度w、归一化高度h。所以拿到数据集第一件事就是写转换脚本。还有几个隐蔽的坑要提前处理有些标注框越界也就是xmin、ymin、xmax、ymax超出了图像宽高必须裁剪否则训练时会报错或者产生无效框。类别名的大小写和拼写不统一比如Tomato_Early_Blight和tomato early blight可能同时出现转格式前统一成一份类别映射表。图像分辨率差异很大有的图片只有几百像素有的超过两千像素训练时统一resize到640后小图片里病斑可能缩得看不清。2.2 自定义数据的拍摄、标注注意事项如果你不想用PlantDoc或者想自建一块符合自己场景的数据集我的建议是不要直接拿手机随便拍。病害识别的核心特征是叶片的颜色和纹理变化所以拍摄时要保证叶片占画面一定比例20%到30%以上太小了标注出来的框基本没意义。尽量覆盖不同光照角度上午侧光、下午逆光、阴天都拍一点模型才不容易过拟合一个特定光线环境。标注工具我用的是X-AnyLabeling也用过LabelImg两者都能直接导出YOLO格式。标注时注意两点第一框要贴合病斑边缘不要为了省事把整片叶子都框进去那样模型的定位输出会很模糊。第二同一个叶片上如果同时有多种症状优先标注最典型的类别不然模型会被标签不确定性带偏。每类的边界框数量我建议至少300个起步500个以上会更舒服太少的话即使有预训练权重也容易过拟合。2.3 一段可复用的VOC转YOLO格式脚本从PlantDoc的XML转成YOLO格式核心逻辑其实很固定。我直接用下面这段脚本跑一遍就能生成所有TXT标注。import os import glob import xml.etree.ElementTree as ET def xml_to_yolo(xml_path, out_dir, class_names): tree ET.parse(xml_path) root tree.getroot() image_w int(root.find(size/width).text) image_h int(root.find(size/height).text) lines [] for obj in root.findall(object): name obj.find(name).text if name not in class_names: continue class_id class_names.index(name) box obj.find(bndbox) xmin float(box.find(xmin).text) ymin float(box.find(ymin).text) xmax float(box.find(xmax).text) ymax float(box.find(ymax).text) # 裁剪非法边界避免产生负数或大于1的坐标 xmin max(0, min(image_w, xmin)) xmax max(0, min(image_w, xmax)) ymin max(0, min(image_h, ymin)) ymax max(0, min(image_h, ymax)) if xmax xmin or ymax ymin: continue dw 1.0 / image_w dh 1.0 / image_h w xmax - xmin h ymax - ymin cx xmin w / 2.0 cy ymin h / 2.0 lines.append(f{class_id} {cx*dw:.6f} {cy*dh:.6f} {w*dw:.6f} {h*dh:.6f}) if not lines: return out_name os.path.splitext(os.path.basename(xml_path))[0] .txt with open(os.path.join(out_dir, out_name), w) as f: f.write(\n.join(lines)) if __name__ __main__: XML_DIR PlantDoc/annotations LABEL_DIR PlantDoc/labels os.makedirs(LABEL_DIR, exist_okTrue) class_names [ Tomato_Early_Blight, Tomato_Late_Blight, Tomato_Leaf_Mold, Tomato_Bacterial_Spot, Corn_Rust, Corn_Northern_Leaf_Blight, ] for xml_path in glob.glob(os.path.join(XML_DIR, *.xml)): xml_to_yolo(xml_path, LABEL_DIR, class_names)这段脚本里有一个容易忽略的细节类别顺序。转换脚本里的class_names列表顺序必须和之后data.yaml里的names顺序完全一致否则训练时类别ID对不上模型学出来全是错乱标签。我建议在转换完之后随便打开几个生成的TXT文件检查第一列数字对应的类别名是否正确。2.4 划分数据集和调整数据增强策略数据转换完成后按8:1:1划分训练、验证和测试集。这里要特别注意不能直接按文件顺序硬切先把所有文件名随机打乱再按比例切窗口保证每个子集都包含各类样本。数据增强方面YOLOv11训练时默认带mosaic、翻转、HSV色彩变化等。但农业病害识别对颜色非常敏感尤其是叶片黄疸、褐斑这一类症状如果色相扰动太强模型可能把健康的颜色变化也当成病害特征。我实际使用时会调低色彩扰动的幅度比如hsv_h保持在0.015左右饱和度扰动也不要太大让模型更多依赖真实纹理和斑块形状而不是被增强出来的奇怪颜色影响。类别不平衡也要处理。我统计了PlantDoc子集发现番茄叶霉病的样本量远少于玉米锈病。如果两类样本差太多模型会对大类过拟合。最简单的办法是对样本量少的类别做重复采样或者用更强的增强手段扩充小类也可以先不去管等第一轮训练完看混淆矩阵再针对混淆严重的类别补数据。3. 用Ultralytics训练YOLOv11环境、配置和一条启动命令3.1 环境安装与GPU检测容易卡住的地方训练环境我建议用Python 3.9或3.10新建一个独立环境避免和系统里其他项目冲突。核心依赖就两个PyTorch和ultralytics库。安装顺序上先装PyTorch再装ultralytics否则ultralytics会顺带装一个CPU版PyTorch后面检测不到GPU。conda create -n yolo11 python3.10 -y conda activate yolo11 pip install ultralytics装完先验证一下GPU能不能用import torch print(torch.__version__, torch.cuda.is_available())如果输出里torch.cuda.is_available()是False基本可以确定torch装了CPU版需要去官网根据系统选择合适命令重装。还有一个常见问题驱动版本太旧导致CUDA初始化失败。这个时候用nvidia-smi看驱动信息如果驱动太老就要升级驱动或者安装一个对应旧驱动能用的PyTorch版本。3.2 写data.yaml路径和names必须严格对应Ultralytics训练时需要一个数据集描述文件放在数据集根目录下面命名为data.yaml。我的文件内容大概是这样的train: D:/datasets/plantdoc_yolo/train/images val: D:/datasets/plantdoc_yolo/val/images nc: 6 names: - Tomato_Early_Blight - Tomato_Late_Blight - Tomato_Leaf_Mold - Tomato_Bacterial_Spot - Corn_Rust - Corn_Northern_Leaf_Blight这里最容易出问题的是路径。Windows下使用反斜杠路径可能导致解析异常我习惯全都用正斜杠或绝对路径。如果你把data.yaml放在数据集根目录相对路径也可以。names列表顺序必须和训练集labels文件夹里TXT第一列的类别ID一一对应这个坑我在前面强调过训练前一定要抽查两三个TXT文件。3.3 训练命令和关键参数怎么选数据集准备好之后训练命令其实非常简洁。我用的命令是这样yolo detect train \ modelyolo11s.pt \ datadata.yaml \ epochs120 \ imgsz640 \ batch16 \ optimizerAdamW \ cos_lrTrue \ patience30 \ projectagriculture_runs \ nameplantdoc_yolo11s \ seed2024逐个说下我为什么要这样设。modelyolo11s.pt会自动使用预训练权重而不是从零开始训练。病害数据和COCO数据虽然领域不同但预训练权重已经学到了通用纹理和边缘特征迁移过来能明显加快收敛。epochs120配合早停一般50到80轮就能收敛。imgsz640是默认配置对大多数叶片病害已经够用如果目标病斑特别细小后面再去试960。batch16受显存限制8G显存刚好能放下再大就爆显存。optimizerAdamW和cos_lr是我自己跑下来比较稳的组合loss曲线更平滑。3.4 训练过程中应该盯住哪些输出训练开始后终端会每一轮打印损失和指标包括box_loss、cls_loss、dfl_loss以及每类的precision、recall、mAP。不要只看精确率我更关注loss有没有回落到底、验证集mAP有没有持续上升。如果训练集loss降得很好但验证集mAP长时间徘徊通常是过拟合或者数据分布有问题可以早点停下检查数据。训练结束后runs/plantdoc_yolo11s/目录下会生成weights文件夹里面有best.pt和last.pt。best.pt是按验证集mAP保存的最佳权重后面评估和导出都用它。还有results.png里面是全部曲线可以直观看到mAP0.5的变化趋势。我习惯在训练途中打开这个图瞄一眼基本能判断还要不要继续跑。4. 评估和调优只看mAP还不够要会看混淆矩阵4.1 四个指标怎么理解训练完第一轮评估重点看这几个指标它们含义完全不同指标含义我自己的可用门槛Precision预测出的框里真正有病斑的比例0.8以上Recall真实病斑里被找出来的比例0.7以上mAP50IoU阈值0.5下的平均精度0.7以上mAP50-95从0.5到0.95多个IoU阈值的整体表现0.4以上就算可用对农业病害来说mAP50最容易冲高因为病斑检测对边界框的精细程度要求没那么高框大体准确就行。但如果你要做自动喷药或者面积计算边界框必须稳定这时候要关注mAP50-95不能只盯着mAP50好看就放过去。4.2 混淆矩阵里暴露出的病害相似性问题真正帮我把模型质量提上去的是混淆矩阵。训练完会在runs目录下生成confusion_matrix.png能清楚看到哪两个类别频繁互相认错。我这次发现番茄早疫病和番茄晚疫病之间误检率比较高原因是两者在叶片上都会形成褐色坏死斑早期症状非常接近。遇到这种视觉特征太近的情况一个思路是合并成一个大类比如统一叫“番茄叶斑病”这样模型压力小但业务上如果必须区分早疫和晚疫合并就没意义。另一个思路是专门去找那些两类有明显区别的典型样本尤其是早疫病病斑有同心轮纹、晚疫病病斑边缘偏水渍状把这类差异明显的样本扩充进训练集让模型学到更细的判别特征。4.3 我试过几个有效和无效的调优方向第一轮跑完我的mAP50在0.71左右后来做了几轮调整最终到了0.82左右。有效的做法按收益排序高分辨率微调把imgsz从640提到960再基于已有的best.pt继续训练30到50轮。因为很多病害病斑在原图里其实很小更高分辨率对召回提升明显。清洗数据把标注框明显偏大、或者包含大量非病斑背景的样本挑出来重新标。这个工作枯燥但收益比换任何模型结构都大。调低mosaic概率高mosaic确实能提高泛化但对病害小目标反而容易产生大量被遮挡的假样本我把mosaic降到0.5之后验证集上的召回率更稳定。没用的尝试也有比如直接把模型升到yolo11m在我的小数据集上不但训练更慢mAP反而没有明显提升。原因是数据量有限的时候大模型更容易过拟合而不是更强。5. 推理落地从模型文件到可用的检测接口5.1 导出ONNX并验证CPU推理模型训练完最先做的就是把best.pt导出成ONNX格式。ONNX的好处是跨平台、不依赖PyTorch环境后面用ONNXRuntime就能跑。yolo export modelruns/plantdoc_yolo11s/weights/best.pt formatonnx opset17 dynamicTruedynamicTrue表示允许输入尺寸动态变化方便后面传任意分辨率图片。导出后我用下面的脚本在CPU上简单验证了一下from ultralytics import YOLO model YOLO(best.onnx) results model.predict(test.jpg, conf0.25, iou0.45) for r in results: boxes r.boxes.xyxy.numpy() classes r.boxes.cls.numpy() scores r.boxes.conf.numpy() for box, cls, score in zip(boxes, classes, scores): print(box, cls, score)注意ONNX推理需要安装onnxruntimeCPU版和GPU版是不同的包。如果你只是做服务部署CPU版足够如果要上GPU再安装对应runtime性能还能提升一截。5.2 用一个FastAPI接口把检测服务包起来如果要给前端或小程序提供识别能力最简单的方式是用FastAPI封装一个图片上传接口。下面是一个精简但能直接跑的样例import cv2 import numpy as np from fastapi import FastAPI, UploadFile from ultralytics import YOLO app FastAPI() model YOLO(best.onnx) app.post(/predict) async def predict(file: UploadFile): data await file.read() img cv2.imdecode(np.frombuffer(data, np.uint8), cv2.IMREAD_COLOR) results model.predict(img, conf0.25, iou0.45, verboseFalse) boxes results[0].boxes.xyxy.numpy().tolist() classes results[0].boxes.cls.numpy().astype(int).tolist() scores results[0].boxes.conf.numpy().tolist() detections [ {box: box, class_id: cls, confidence: score} for box, cls, score in zip(boxes, classes, scores) ] return {detections: detections}这个接口直接接收multipart上传的图片文件返回坐标框、类别ID和置信度。实际部署时建议加一层请求大小限制还要处理异常图片读取失败的情况避免前端传一个损坏文件直接把进程打崩。5.3 边缘设备部署时的轻量化取舍农业大田场景里网络往往不稳定推理设备通常是工控机或者小型开发板直接把best.pt装上去不现实。我建议分几步做轻量化第一优先选yolo11n而不是s因为n的体积更小CPU推理延迟更低。第二导出ONNX后如果有编译到目标硬件专用格式的机会优先做速度提升明显。Ultralytics直接支持导出多种格式只要在format参数里指定即可。第三如果业务允许把输入分辨率固定到512或者416通过减小计算量换取实时性。病斑定位不需要非常高精度门框式的“有/无病害”判断其实512够了。如果部署环境只有CPU还要注意线程数设置。ONNXRuntime默认会用满CPU核数但服务器上可能还有别的服务在跑最好设置线程数上限留出系统资源避免高并发时整台机器卡死。最后说点个人感受这一轮做下来我最大的体会是模型侧真的不用花太多精力追新架构反而是数据侧的细节决定了项目的成败。PlantDoc作为一个公开数据集跑通流程没问题但如果你要真的在农田里用必须额外补拍大量本地环境的数据否则模型的泛化能力撑不过复杂背景的考验。还有一个很容易忽略的小技巧训练的时候不要贪心把epochs设到极大先跑50轮看趋势再继续续训能省掉很多无效等待时间。希望这篇文章能让你少走点弯路。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑