ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

YOLOv11农业无人机病虫害检测实战:数据、训练与部署全解析

YOLOv11农业无人机病虫害检测实战:数据、训练与部署全解析 简介《农业无人机巡检实战YOLOv11实现作物病虫害实时识别与定位》是一份面向农业科技从业者、无人机应用开发者和计算机视觉学习者的实战型技术文档。文档围绕YOLOv11单阶段目标检测算法系统讲解作物病虫害数据采集、标注、清洗、增强与数据集划分再到模型训练、优化、评估的全部流程。内容覆盖无人机选型与搭载设备适配、数据传输与通信、模型轻量化及边缘端部署并结合具体农场案例展示病虫害实时识别与定位的实施过程与效益适合作为农业智能化项目的技术参考。资源包共1个PDF文件大小1.98MB全篇35页支持目录章节跳转可快速定位感兴趣的内容。已有79人学习下载适合需要将目标检测技术落地于农业巡检场景的读者。1. 从一张航片到一份“病虫害地图”YOLOv11到底解决了什么做农业无人机巡检的同行应该都有过这种体验飞一圈下来几百上千张航片晚上导进电脑里一张张看圈出病斑、数出虫害区域手脚快的人一小时也处理不完一个架次的数据。这个场景最痛的不是飞而是“看”。而 YOLOv11 这类目标检测模型解决的就是把“人眼盯屏幕”变成“模型自动框出病斑和虫害区域”——识别出是什么病害、给出置信度、标出位置顺带统计每块田的发病面积占比。这篇笔记我就围绕“农业无人机巡检 YOLOv11 实时识别与定位”这条主线把数据准备、模型训练、部署推理和常见翻车点一次讲透。先给结论YOLOv11 在农业场景里最大的价值不是精度分数而是“推理速度快 小目标检测上限高”。无人机航拍不同于手机拍叶子目标在整幅图里往往只占几十个像素模型能不能抓住这些小目标直接决定了这套方案能不能落地。本文适合手里已经有无人机和基础 Python 环境、想自己从零跑通一版检测模型的从业者也适合只想评估“这技术值不值得投入”的团队负责人。2. 无人机图像数据准备从视频抽帧到 YOLO 格式的完整链路2.1 为什么不能直接拿原始航片去训练很多新手拿到无人机拍的 4K 视频或 JPG 航片直接就开始标注然后丢进 YOLO 训练——十有八九翻车。原因有两个维度。第一无人机默认拍的是广角地面影像视场大但单株作物占比小直接把整张图缩放到 640x640 去训练一个病斑可能只有 2-3 个像素模型根本学不到纹理特征。第二航片存在重叠率同一株病株会出现在连续好几张照片里不经去重直接划分训练集和验证集会造成严重的数据泄漏验证分数虚高到没参考意义。正确的做法是先做“切片预处理”。把原始大图切成 640x640 或 1024x1024 的小块每块之间保留一定重叠率然后只保留含有目标的切片参与标注和训练。常见做法是先用 OpenCV 写一个滑窗切片脚本把一亩地的航片切成几百张子图再做一次人工筛选去掉完全没有作物的空白块。2.2 视频抽帧 滑窗切片脚本示例与参数解释下面这段脚本是我常用的抽帧加切片方案输入是无人机视频或照片文件夹输出是可直接进入标注工具的切片图集import cv2 import os from glob import glob def extract_frames(video_path, output_dir, interval30): 按间隔帧抽图避免连续帧重复度过高 cap cv2.VideoCapture(video_path) os.makedirs(output_dir, exist_okTrue) frame_count 0 saved_count 0 while True: ret, frame cap.read() if not ret: break if frame_count % interval 0: cv2.imwrite(os.path.join(output_dir, fframe_{saved_count:05d}.jpg), frame) saved_count 1 frame_count 1 cap.release() print(f抽取 {saved_count} 帧原始视频共 {frame_count} 帧) def sliding_window_crop(image_path, output_dir, crop_size640, overlap0.2): 滑窗切片overlap 控制相邻切片的重叠度 img cv2.imread(image_path) h, w img.shape[:2] step int(crop_size * (1 - overlap)) os.makedirs(output_dir, exist_okTrue) base_name os.path.splitext(os.path.basename(image_path))[0] idx 0 for y in range(0, h - crop_size 1, step): for x in range(0, w - crop_size 1, step): crop img[y:y crop_size, x:x crop_size] cv2.imwrite(os.path.join(output_dir, f{base_name}_{idx:03d}.jpg), crop) idx 1 print(f{base_name} 共生成 {idx} 个切片)逻辑说明extract_frames里的interval30表示每 30 帧抽一帧对应 30fps 视频就是每秒取一张兼顾信息密度和后续去重成本。sliding_window_crop里的overlap0.2是给相邻切片留 20% 的重叠区防止病斑恰好被切边界切断——这是训练数据增强之外最朴素的“保召回”手段。如果目标是水稻稻瘟病这类病斑极小的场景切片尺寸建议缩到 512让病斑在切片里的相对占比更大模型更容易学到判别特征。2.3 标注工具选型与格式转换LabelImg 到 YOLO txt 的四步流程切片准备好以后就是标注。常见做法是先用 LabelImg 或 X-AnyLabeling 做手工框选导出 Pascal VOC 格式的 XML再转成 YOLO 需要的 txt 格式。为什么不直接用 LabelImg 存 YOLO 格式因为团队协作或多轮复标时XML 的兼容性更好而且很多公开的农业病虫害数据集都是 VOC 格式。转换的四个步骤是解析 XML 中的 object 框坐标 → 将左上角坐标加宽高换算成中心点坐标 → 分别除以图片宽高做归一化 → 按“类别ID x_center y_center width height”写入 txt。下面给一段单文件转换的参考逻辑import xml.etree.ElementTree as ET import os def voc_to_yolo(xml_path, txt_path, class_map): tree ET.parse(xml_path) root tree.getroot() img_w int(root.find(size/width).text) img_h int(root.find(size/height).text) lines [] for obj in root.findall(object): name obj.find(name).text if name not in class_map: continue class_id class_map[name] box obj.find(bndbox) x1 float(box.find(xmin).text) y1 float(box.find(ymin).text) x2 float(box.find(xmax).text) y2 float(box.find(ymax).text) x_center (x1 x2) / 2 / img_w y_center (y1 y2) / 2 / img_h width (x2 - x1) / img_w height (y2 - y1) / img_h lines.append(f{class_id} {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}) with open(txt_path, w) as f: f.write(\n.join(lines))参数说明class_map是类别名字到 ID 的映射字典比如{稻瘟病: 0, 二化螟: 1}。注意归一化必须用该图片的实际宽高不能统一用 640否则框位会整体偏移。这个脚本只处理了单张实际使用时建议写批量循环同时在转换后抽几张图做可视化校验——把框画回原图确认没有坐标错位。这一步偷懒后面训练出来的模型框全是偏的找错能找一整天。3. 训练自己的 YOLOv11 检测模型环境配置、模型选型与参数调优3.1 Ultralytics 环境搭配适合 0 基础纯小白的安装步骤YOLOv11 目前最省事的落地路径是直接走 Ultralytics 的 Python 包它把训练、验证、导出、推理统一封装了。很多博客里说的“yolov11(ultralytics)环境配置适合0基础纯小白”本质就是三件事装 Python 3.8、用 pip 装 ultralytics、跑通官方预训练权重。# 建议使用 conda 创建独立环境避免污染系统 Python conda create -n yolo11 python3.10 -y conda activate yolo11 pip install ultralytics # 验证安装是否成功 yolo predict modelyolo11n.pt sourcehttps://ultralytics.com/images/bus.jpg如果这一步能正常打印检测结果说明环境没问题。GPU 用户还需要额外装 CUDA 版 PyTorch顺序是先装 torch 再装 ultralytics避免依赖冲突。常见错误是装完 ultralytics 之后发现 torch 是 CPU 版训练慢到怀疑人生检查方式是python -c import torch; print(torch.cuda.is_available())。3.2 迁移学习用 yolo11n.pt 还是从头训练做农业病虫害识别不建议从头训练。公开的作物病虫害数据集规模通常在几千到几万张比起 ImageNet 级别的预训练数据差了两个数量级从头训练不仅收敛慢而且极易过拟合。常见做法是加载 COCO 预训练权重做迁移学习只替换最后分类头冻结前几层或全量微调看数据规模而定。数据量在 3000 张以下时我一般会冻结 backbone 的前 10 层只训练 head 部分数据量超过 8000 张直接全量微调效果更好。冻结层数的操作在 Ultralytics 里没有直接开关需要手动遍历模型参数设置requires_gradFalse对纯小白来说略复杂所以更推荐一个折中方案先全量微调 50 个 epoch如果验证集出现明显过拟合训练 loss 降但验证 loss 升再回头冻结层重训。3.3 三个必调参数imgsz、epochs、batch 的取值逻辑YOLOv11 训练脚本里最关键的参数不是 learning rate而是这三个yolo train datacustom.yaml modelyolo11n.pt epochs200 imgsz640 batch16 device0imgsz直接决定小目标检测上限。叶片病斑在 640x640 图上可能只有 10x10 像素你把它压到 320 训练特征直接消失。一般建议是 640 起步显存允许的话用 1024。epochs不是越大越好农业数据背景相对单一200 epoch 足够配合早停反而比 500 epoch 更稳。batch主要受显存限制如果训练时显存溢出优先减小 batch 而不是 imgsz——因为减小 imgsz 的副作用比减小 batch 更难以察觉。datacustom.yaml是数据集配置文件核心字段包括path数据集根目录、train和val相对路径、names类别名列表。一个典型的配置长这样path: /data/agri_drone train: images/train val: images/val nc: 2 names: [rice_blast, stem_borer]3.4 小目标优化为什么要单独给航拍场景调 anchorYOLOv11 已经内置了自适应 anchor 计算训练时会根据你的数据集自动重新聚类 anchor 尺寸。但无人机视角下的目标尺寸分布极端——大量目标小于 32x32 像素——默认 anchor 仍然偏向常规物体尺度。常见做法是在训练前先统计标注框的尺寸分布确认小目标占比再决定是否启用额外的数据增强策略。Ultralytics 里和这个小目标问题直接相关的参数是scale和flipud。scale0.5表示随机缩放范围是 0.5 到 1.5对小目标场景建议改成scale0.3减小下采样概率。flipud0.5开启上下翻转对无人机俯拍图适用但如果你拍的是斜视角度航片上下翻转会让叶子纹理方向混乱建议关掉。直观来说无人机图是“俯视大地”不做翻转就已经很丰富了叠太多增强反而容易让模型学到错误的纹理方向。3.5 训练结果评估mAP50 和 mAP50-95 到底该看哪个训练结束以后 Ultralytics 会在runs/detect/train目录下输出一堆指标曲线新手容易只盯着 mAP50 看。这两个指标的含义有实质区别mAP50 只要求在 IoU 阈值 0.5 下框和真值重叠过半就算命中对定位精度要求低mAP50-95 是把 0.5 到 0.95 的 IoU 阈值都算一遍取平均对框的贴合度要求苛刻得多。农业巡检场景建议两个都看但侧重点不同。如果你做的是大范围虫害普查只需要知道“哪块田有虫”mAP50 够用如果你要做精准施药需要框出病斑边缘来控制喷头开关那 mAP50-95 必须达到 0.5 以上否则定位误差会直接导致漏喷或误喷。调参时如果 mAP50 涨但 mAP50-95 不涨说明模型“找到了目标但框不准”优先去调回归损失权重或换更高输入分辨率。4. 部署到无人机边缘端实时识别与定位的两种落地路径4.1 机载端实时推理的算力约束无人机机载电脑的算力远不如地面工作站YOLOv11 的 n 版本在 Jetson Orin Nano 上跑 640x640 输入大约能到 30-50 FPSs 版本大概 20-30 FPS。这里有个容易被忽略的点实时识别的瓶颈往往不是模型推理速度而是相机数据的读取和解码。USB 摄像头或 HDMI 采集卡在 Jetson 上的延迟可能达到 100-200ms你模型再快也补不回来。机载端实时推理的典型方案是用 CSI 接口相机或支持直接内存映射的网络相机图像先缩小到 960x540 再做模型推理检测结果直接叠加在视频流上同时把目标 GPS 坐标通过 RTK 定位模块写入 CSV。要做实时识别与定位“定位”靠的其实是无人机自身的 GPS/RTK 数据模型只负责告诉你“这个框里的病害类型”然后把框中心像素坐标换算成相机光心偏移角结合无人机姿态角算出地理坐标。这套坐标换算的精度受飞行高度和云台角度影响很大飞行高度 30 米时一个像素的误差可能就是几厘米。4.2 地面端离线批处理方案飞完再算更稳另一种更稳妥的做法是无人机只管采集降落后把航片拷贝到地面工作站批量推理。这种做法在农业植保队里非常常见——因为植保无人机的载重和供电都优先保证喷洒系统机载电脑往往只能跑个 n 模型检测精度上不去。地面端用 s 甚至 m 模型配一张 RTX 4070 显卡一张 20MB 的航片推理时间在 100ms 以内加上切片拼接的后处理整个流程比机载端更快更准。地面端的另一个优势是可以做大图拼接和重复检测去除。机载端因为每帧都独立推理同一株病株会连续出现在多帧画面里重复计数。地面端可以用 SIFT 特征做图像配准或者用简单粗暴的“检测框中心距离小于阈值即合并”策略去重。我做项目时更倾向后者省时间而且对精度影响可控。def merge_duplicate_boxes(boxes, distance_thresh30): 合并中心距离过近的检测框去重重复识别 merged [] used [False] * len(boxes) for i, box in enumerate(boxes): if used[i]: continue group [box] used[i] True for j, other in enumerate(boxes): if used[j]: continue center_i ((box[0] box[2]) / 2, (box[1] box[3]) / 2) center_j ((other[0] other[2]) / 2, (other[1] other[3]) / 2) dist ((center_i[0] - center_j[0]) ** 2 (center_i[1] - center_j[1]) ** 2) ** 0.5 if dist distance_thresh: group.append(other) used[j] True merged.append(group) return merged参数说明distance_thresh30表示两个框的中心点距离在 30 像素以内就视为重复检测。这个值要根据飞行高度和航片分辨率调整高度越低、分辨率越高同一个目标在相邻帧中的位移越大阈值要相应放大。group 里的多个框取置信度最高那个作为最终框避免平均框导致定位偏移。4.3 模型导出与推理脚本从 .pt 到 .onnx 再到 TensorRT机载端和地面端的部署形态不同但导出流程是一致的。Ultralytics 提供了一行命令导出多种格式yolo export modelbest.pt formatonnx imgsz640 opset12 yolo export modelbest.pt formatengine device0formatengine是导出 TensorRT 引擎只对 NVIDIA 设备生效导出时指定的imgsz会被固定运行时不能动态改分辨率。这一点要注意很多新手导出 engine 后换了个分辨率预测直接报错找半天原因。opset12是为了兼容 JetPack 里的老版本 TensorRT如果你用的是最新 JetPack 6.x可以适当调高到 13 或 14。导出为 engine 后推理脚本也需要同步调整TensorRT 引擎的输入输出是固定 shape不能再像 .pt 那样随意接收任意尺寸。建议在代码里写死imgsz并在预处理阶段做 letterbox 填充而不是直接 resize——letterbox 能保持目标不被拉伸变形对检测精度影响最小。5. 农业巡检测模型的避坑手册5 个高频翻车场景与对应解法5.1 现象模型把田埂误检成作物病害这是最常见的问题特征是在验证集上 mAP 很高但拿到新地块一测全是误报。原因是你训练集里的负样本太少——无人机航拍图里除了作物还有田埂、杂草、泥土、水渠这些背景如果没标注为“背景”也没有在训练时被当作负样本模型就会把纹理相似的区域一律当成目标。解决方法是先人工快速看一遍训练集切片的背景多样性如果发现绝大多数切片都是纯作物就去采集一些不同地块、不同光照、不同土质的负样本加入训练集。另一个简便做法是在数据增强里增加mosaic1.0和copy_paste0.3让模型见的背景组合更丰富。5.2 现象病斑太小导致漏检mAP50 很高但 mAP50-95 极低特征是小目标漏检率超过 50%尤其在大面积航片上。原因是切片后病斑即使放大了仍然只占图幅的很小比例模型的特征金字塔对小目标的响应天然弱于大目标。推荐解法是切换 YOLOv11 的变体。YOLOv11 有个专门优化小目标的思路是引入更细粒度的检测头或更高的特征分辨率对应到 Ultralytics 的层面最简单的做法是提升输入分辨率到 1024同时开启augmentTrue里的multi_scale参数让模型在训练时见到多种尺度的目标。进阶做法是修改模型结构在 backbone 后面额外接一个针对小目标的检测层这个需要改源码不是开箱即用建议先试分辨率方案。5.3 现象训练 loss 正常下降但每个 batch 速度慢到无法忍受这一般不是代码问题是数据加载瓶颈。无人机航拍切片都是几千像素的大图虽然训练时随机裁剪到 640但解码整张大图比解码小图慢很多CPU 数据加载跟不上 GPU 消费速度。Ultralytics 里workers参数默认值偏保守建议根据 CPU 核数调整yolo train datacustom.yaml modelyolo11n.pt workers8workers8一般能缓解但如果你的 CPU 核心数少或硬盘是机械盘瓶颈会转移到磁盘 IO这个参数反而过犹不及。还有一个隐蔽的坑航片如果是超大分辨率预处理时做 letterbox 会生成临时数组内存占用高设置cachedisk可以把预处理后的图提前缓存成 LMDB 格式大幅减少重复计算。5.4 现象训练集和验证集都是同一块田拍的性能虚高验证 mAP 达到 0.9换一个地块直接崩原因就是数据划分不科学。无人机航拍照片有空间相关性同一块田的病斑分布高度相似随机划分会把这些强相关图片同时放进训练集和验证集模型等于“记住了这块田”而不是学到了“病害的特征”。正确做法是按地块或飞行架次划分数据集而不是按单张图划分。如果一个数据集包含 10 块不同的田就按田块划分成 8 块训练、1 块验证、1 块测试。这样验证集的分数才真实反映跨地块的泛化能力。5.5 现象推理时返回结果为空但训练时 mAP 正常通常是推理预处理和训练预处理不一致导致。训练时 Ultralytics 会自动做 letterbox 和归一化但如果你自己写了推理脚本很容易忘记做归一化像素除以 255或者用了直接 resize 而不是 letterbox导致目标变形、模型输出为空。另外检查一下类别 ID 映射训练时的names顺序和推理脚本里的class_map是否一致差一位就全错。6. 进阶把 YOLOv11 的检测结果做成可发布的“病虫害分布图”模型只回答“这是什么、在哪”还远远不够用户要的是“这块田打不打药、打多少”。我一般会在检测后接一个可视化和统计脚本输出三样东西带框标注的巡检图、按网格统计的发病密度图、以及一份可以直接交给植保机作业的处方图CSV 格式。做法不复杂把航片地图按经纬度网格切块用检测框中心点做空间聚合统计落到每个网格里计算病害面积占比。网格大小按作业幅宽来定比如植保机喷嘴幅宽 5 米网格就设 5 米这样处方图可以直接对应到飞机的喷药开关。下面这段逻辑是网格统计的核心import pandas as pd import numpy as np def disease_density_map(detections, grid_size_m5, flight_altitude_m30, gsd_m_per_px0.02): 按网格统计病害密度输出处方图坐标 df pd.DataFrame(detections, columns[x_px, y_px, w_px, h_px, conf, class_id]) df[x_m] df[x_px] * gsd_m_per_px df[y_m] df[y_px] * gsd_m_per_px df[grid_x] np.floor(df[x_m] / grid_size_m).astype(int) df[grid_y] np.floor(df[y_m] / grid_size_m).astype(int) grid_stats df.groupby([grid_x, grid_y]).agg( disease_area_pct(w_px, lambda x: (x * gsd_m_per_px).sum() / (grid_size_m ** 2) * 100), det_count(conf, count) ).reset_index() return grid_stats逻辑说明gsd_m_per_px是地面采样距离即每个像素对应的实际地面尺寸这个值由飞行高度和相机焦距决定。不同高度飞出来的数据这个值完全不同最好从飞行日志里读取或用一个固定的高度换算——但要记得最后在报告里注明否则处方图会直接错位。grid_size_m5跟植保机作业幅宽对齐统计的是每个 5x5 米网格里的病害占面积比例。这套方案做到这个程度已经不是“跑通了一个模型”而是形成了一套可交付给农户或植保队的决策支持产品。我的经验是模型精度达到 0.8 的 mAP50 就可以试点不用死磕到 0.9 再上线因为实际使用中农户最关心的是“哪个网格有、大致有多大面积”而不是精确到每一株。等试点反馈回来再针对特定地块做增量训练投人投时间都更划算。希望这篇笔记能帮你少踩几个我当年踩过的坑。本文还有配套的精品资源点击获取
返回列表