简介:这份资源是一套基于Python实现的无人机病虫害智能识别与精准施药系统,面向计算机、人工智能、农业工程等专业的学生与开发者,可用于毕业设计、课程设计或项目开发练手,帮助解决农田病虫害自动检测与变量施药的实际问题。压缩包共25个文件,约4.74MB,以15个py源码文件为核心,辅以6个pyc编译文件、2个txt说明、1份pdf项目文档和1份license,涵盖模型定义、数据集加载、训练引擎、采样器与分类推理等模块,目录结构清晰,便于按功能定位代码。资源描述显示源码已通过严格测试,可放心参考并在此基础上延伸使用,项目文档对系统整体方案做了说明。目前已有61人学习关注。读者可从中获得完整的识别与施药流程实现、可复用的训练与推理脚本、模型与数据处理代码,以及项目文档带来的方案梳理,适合作为二次开发与论文撰写的参考起点。
1. 从一张叶片照片到一次变量喷洒:这套系统到底在解决什么
植保飞防的痛点从来不是“飞不起来”,而是“飞了但打错药、打错量”。我见过太多团队把无人机当成会飞的喷雾器:起飞前拍几张照片,落地后人工翻图判断有没有病斑,再凭经验决定喷多少。这套流程在几十亩地里还能凑合,一旦上到几百亩连片种植,人工判图的速度根本追不上病虫害扩散的速度,更别提“精准施药”这四个字了。
基于 Python 实现的无人机病虫害智能识别与精准施药系统,本质是把三件事串成一条闭环:机载或航拍影像里的病斑被模型识别出来,识别结果被换算成田块级的处方图,处方图再驱动变量喷洒机构按需下药。它适合做毕业设计、课程设计的学生,也适合想给现有飞防队加一层“视觉大脑”的工程团队。Python 在这里不是噱头,而是因为它把 OpenCV、PyTorch、GDAL、pymavlink 这些库粘在一起最省事,从读图到出喷洒指令,一条脚本链路就能跑通。下面我按“数据怎么来、模型怎么训、处方图怎么生成、喷洒怎么联动”这条线,把能复现的细节摊开讲。
2. 影像采集与数据集构建:别让第一帧就埋雷
2.1 无人机影像的采集参数怎么定
病虫害识别模型的上限,在快门按下的那一刻就决定了。我一般会按下面的参数表来设,这套参数在玉米、小麦、柑橘上都跑过,泛化性还算稳。
| 参数项 | 推荐值 | 说明 |
|---|---|---|
| 飞行高度 | 3~8 m | 低于 3 m 效率太低,高于 8 m 单株病斑像素不足 |
| 重叠率 | 航向 75%、旁向 65% | 保证拼接后无空洞,病斑不被裁断 |
| 光照条件 | 10:00~14:00 晴天 | 阴影少,颜色阈值稳定 |
| 相机设置 | 快门优先 1/800s 以上 | 防止运动模糊,模糊图直接废 |
| 分辨率 | 单张 ≥ 2000 万像素 | 病斑在图上至少占 30×30 像素 |
采集时最容易翻车的是“只拍病株不拍健康株”。模型没见过健康叶片,上线后会把所有绿色都判成病。我的做法是每个田块按“病、健、疑似”三类各采 200 张以上,疑似样本专门用来磨模型的决策边界。
2.2 用 Python 做数据清洗与增强
原始影像里混着模糊、过曝、重复帧,直接拿去标注就是给后面埋雷。下面这段脚本做三件事:拉普拉斯方差筛模糊、直方图均衡化、随机旋转裁剪增强。
import cv2 import numpy as np import os import random def is_blurry(image, threshold=100): """拉普拉斯方差小于阈值判定为模糊""" gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) return cv2.Laplacian(gray, cv2.CV_64F).var() < threshold def clean_and_augment(src_dir, dst_dir, target_size=(640, 640)): os.makedirs(dst_dir, exist_ok=True) for fname in os.listdir(src_dir): img = cv2.imread(os.path.join(src_dir, fname)) if img is None or is_blurry(img): continue # 模糊图直接丢弃 # 直方图均衡化,缓解逆光导致的对比度不足 img_yuv = cv2.cvtColor(img, cv2.COLOR_BGR2YUV) img_yuv[:, :, 0] = cv2.equalizeHist(img_yuv[:, :, 0]) img = cv2.cvtColor(img_yuv, cv2.COLOR_YUV2BGR) # 随机旋转 ±15 度 + 中心裁剪 angle = random.uniform(-15, 15) h, w = img.shape[:2] M = cv2.getRotationMatrix2D((w // 2, h // 2), angle, 1.0) img = cv2.warpAffine(img, M, (w, h), borderMode=cv2.BORDER_REFLECT) img = cv2.resize(img, target_size) cv2.imwrite(os.path.join(dst_dir, fname), img) clean_and_augment("./raw_images", "./clean_images")逻辑说明:is_blurry用拉普拉斯算子响应方差衡量清晰度,阈值 100 是经验值,低于它说明高频细节丢失,病斑边缘会糊成一团。直方图均衡化只对 YUV 的亮度通道做,避免色相偏移把病斑颜色改掉。旋转用BORDER_REFLECT填充边缘,比补零更接近真实背景。参数上,target_size设成 640×640 是为了对齐 YOLO 系列的输入,如果你用 Faster R-CNN 可以改成 800×1333。
注意:增强后的图必须和原图分开存,标注时只标原图,增强图继承标注文件,否则同一张病斑会被重复计数。
2.3 标注格式与类别体系
类别体系别贪多。我见过有人把“早期斑、中期斑、晚期斑、疑似斑”分成四类,结果每类样本不到 50 张,模型直接学废。常见做法是按“病害类型 + 严重度”两级来分,比如rust_mild、rust_severe、healthy,严重度只分轻中重三档。标注用 LabelImg 出 YOLO 格式的 txt,每行class_id x_center y_center width height,坐标全部归一化到 0~1。标完抽查 10% 的框,重点看有没有把叶脉阴影框成病斑,这种误标对模型的伤害比漏标还大。
3. 病虫害识别模型:从 YOLOv8 训练到边缘部署
3.1 为什么选 YOLOv8 而不是分类网络
病虫害识别有两个任务层次:一是“这张图有没有病”,二是“病斑在哪、多大”。分类网络只能回答第一个,而精准施药需要病斑的位置和面积来算药量,所以必须用检测网络。YOLOv8 在速度和精度上比较平衡,n 版本在 Jetson Nano 上能跑到 15 FPS 左右,够无人机实时处理。如果你用两阶段检测器,精度可能高 2~3 个点,但推理速度掉一半,机载端扛不住。
3.2 训练脚本与关键参数
from ultralytics import YOLO # 加载预训练权重,n 版本适合边缘设备 model = YOLO("yolov8n.pt") results = model.train( data="pest_data.yaml", # 数据集配置文件 epochs=150, imgsz=640, batch=16, lr0=0.01, # 初始学习率 lrf=0.01, # 最终学习率 = lr0 * lrf momentum=0.937, weight_decay=0.0005, warmup_epochs=3, # 前 3 轮预热,防止早期震荡 augment=True, # 开启 mosaic、mixup 增强 device=0, # 用 GPU 0 patience=30 # 30 轮无提升则早停 )逻辑说明:lr0=0.01配合lrf=0.01做余弦退火,前期快速下降后期微调。warmup_epochs=3很关键,病虫害数据集通常只有几千张,不加预热前几轮 loss 会炸。patience=30是后悔药,防止过拟合后还在傻跑。pest_data.yaml里要写清train、val路径和names类别名,路径用绝对路径,相对路径在服务器上经常找不到。
3.3 训练过程怎么判断有没有学歪
看三个信号:box_loss和cls_loss是否同步下降,如果 cls 降但 box 不降,说明分类学明白了但定位没学会,多半是标注框太松;mAP50在 20 轮内没到 0.5,检查类别是否失衡;验证集 loss 开始上升而训练集还在降,立刻停,这是过拟合的典型曲线。我一般会在第 50、100、150 轮各导一次权重,最后挑验证集 mAP 最高的那个,而不是最后一个。
3.4 导出 ONNX 并在边缘端推理
from ultralytics import YOLO model = YOLO("runs/detect/train/weights/best.pt") model.export(format="onnx", imgsz=640, simplify=True, opset=12)导出后可以用 onnxruntime 在机载计算机上跑,也可以转 TensorRT 进一步加速。simplify=True会做图优化,去掉冗余算子,推理能快 10%~15%。opset=12兼容性最好,别盲目上 17。推理时把置信度阈值设到 0.4,IOU 设 0.5,低于 0.4 的框大多是叶脉阴影误检,宁漏勿误,因为误检会导致多喷药。
4. 从识别结果到处方图:坐标换算与变量喷洒决策
4.1 像素坐标转地理坐标
模型输出的是像素框,喷洒需要的是经纬度。这一步靠无人机影像的 POS 数据和地面分辨率(GSD)来换算。假设飞行高度 5 m,相机焦距 8 mm,传感器宽度 13.2 mm,则 GSD = (5 × 13.2) / (8 × 5472) ≈ 0.0015 m/像素。每个像素框的中心点加上影像左上角的经纬度,再乘以 GSD,就能得到病斑的地理位置。
def pixel_to_geo(px, py, img_left_top_lon, img_left_top_lat, gsd, img_width): """像素坐标转经纬度,简化平面换算""" lon = img_left_top_lon + (px * gsd) / (111320 * np.cos(np.radians(img_left_top_lat))) lat = img_left_top_lat - (py * gsd) / 110540 return lon, lat逻辑说明:经度方向要乘cos(lat)做纬度校正,否则高纬度地区误差能到几十米。111320和110540分别是赤道处每度经度和纬度的米数。这个换算是平面的,田块小于 1 平方公里时误差可忽略,再大就得上投影坐标系。
4.2 生成处方图的栅格化方法
把每个病斑框按严重度赋一个喷洒等级,然后栅格化成 5 m × 5 m 的网格。轻症喷 30% 药量,中症 60%,重症 100%,健康区 0%。用 GDAL 写 GeoTIFF,每个像素值代表喷洒比例。
from osgeo import gdal, osr import numpy as np def create_prescription_map(width, height, geo_transform, spots, output_path): """spots: [(lon, lat, severity), ...] severity 0-1""" driver = gdal.GetDriverByName("GTiff") ds = driver.Create(output_path, width, height, 1, gdal.GDT_Float32) ds.SetGeoTransform(geo_transform) srs = osr.SpatialReference() srs.ImportFromEPSG(4326) ds.SetProjection(srs.ExportToWkt()) band = ds.GetRasterBand(1) arr = np.zeros((height, width), dtype=np.float32) for lon, lat, sev in spots: # 经纬度转回像素行列 col = int((lon - geo_transform[0]) / geo_transform[1]) row = int((lat - geo_transform[3]) / geo_transform[5]) if 0 <= row < height and 0 <= col < width: arr[row, col] = max(arr[row, col], sev) band.WriteArray(arr) ds = None create_prescription_map(1000, 800, (116.0, 0.00005, 0, 39.0, 0, -0.00005), [(116.01, 38.99, 0.6)], "prescription.tif")逻辑说明:geo_transform是 GDAL 的六元组(左上角经度, 经度分辨率, 0, 左上角纬度, 0, 负纬度分辨率)。arr[row, col] = max(...)保证同一网格有多个病斑时取最严重的等级,避免漏喷。输出 Float32 是为了保留小数比例,后面驱动 PWM 时直接乘最大流量。
4.3 处方图到喷洒指令的映射
处方图是栅格,喷洒是连续动作,中间要过一个“查表 + 插值”的环节。我一般把 0~1 的喷洒比例映射成 0~100% 的 PWM 占空比,再按飞行速度做前馈补偿:速度越快,单位面积停留时间越短,占空比要相应提高。这个补偿系数靠实测标定,公式是duty = base_duty * (v_ref / v_actual),v_ref取 3 m/s,v_actual从飞控实时读。
5. 避坑与排查:那些让我返工三次的细节
5.1 模型在实验室 mAP 0.9,到田里全是误检
现象:验证集指标漂亮,实际飞行时把土壤反光、枯叶、水管都框成病斑。原因:训练集全是近距离叶片特写,没有背景负样本。解决:往训练集里塞 20% 的纯背景图(土壤、天空、田埂),类别标成background,让模型学会“什么都不检”。
5.2 处方图和实际田块对不上,喷到隔壁地里
现象:按处方图喷药,边缘区域偏移了十几米。原因:POS 数据的经纬度是相机中心,而病斑框在图像边缘,没做视场角偏移补偿。解决:换算时把像素坐标先减去图像中心,再按焦距和高度算实际偏移量,加到 POS 坐标上。
5.3 变量喷洒响应延迟,起步和停止处药量堆积
现象:每趟开始和结束的地方药液明显偏多。原因:PWM 从 0 升到目标占空比有 200~300 ms 延迟,飞行速度 3 m/s 时对应 0.6~0.9 m 的堆积。解决:在处方图前后各留 1 m 的过渡带,占空比线性爬升,同时把喷头开关提前 200 ms 触发。
5.4 模型推理占用 CPU 过高,飞控线程被拖死
现象:开启识别后,无人机姿态控制出现卡顿。原因:识别和飞控跑在同一进程,Python GIL 导致线程争抢。解决:识别单独跑一个进程,通过共享内存或 UDP 把结果发给飞控进程,飞控进程只做轻量级解析。
5.5 训练到一半 loss 变 NaN
现象:第 80 轮左右 loss 突然变成 nan。原因:学习率太高加上某批数据里有异常框(宽高为 0)。解决:训练前用脚本过滤掉宽或高小于 2 像素的框,同时把lr0降到 0.005,加amp=False关掉混合精度(混合精度在异常数据下容易溢出)。
6. 进阶技巧:用 TensorRT 加速并把整套流程塞进 Jetson
6.1 TensorRT 量化与推理提速
ONNX 在 Jetson 上跑 YOLOv8n 大概 8~10 FPS,转成 TensorRT FP16 能到 25 FPS 以上,INT8 还能再快一倍但精度掉 3~5 个点,病虫害识别对精度敏感,我一般用 FP16。转换命令如下:
trtexec --onnx=best.onnx --saveEngine=best_fp16.engine --fp16 --workspace=2048--workspace=2048给 2 GB 显存做优化空间,Jetson Nano 只有 4 GB 内存,别设太大。转换完用trtexec --loadEngine=best_fp16.engine --shapes=images:1x3x640x640测一下延迟,正常应该在 40 ms 以内。
6.2 用多进程把识别和喷洒解耦
Jetson 上跑整套流程,最稳的架构是三个进程:采集进程读相机帧写共享内存,识别进程读共享内存跑 TensorRT 写结果队列,控制进程读结果队列算 PWM 并写串口。进程间用multiprocessing.Queue通信,队列长度设 2,满了就丢旧帧,保证实时性。下面是一个简化的骨架:
import multiprocessing as mp import numpy as np def capture_worker(shared_arr, lock): while True: frame = read_camera() # 伪代码,实际用 cv2.VideoCapture with lock: shared_arr[:] = frame def infer_worker(shared_arr, lock, result_queue): while True: with lock: frame = shared_arr.copy() detections = trt_infer(frame) # TensorRT 推理 if result_queue.full(): result_queue.get() # 丢旧结果 result_queue.put(detections) if __name__ == "__main__": shared_arr = mp.Array('f', 640 * 640 * 3) lock = mp.Lock() result_queue = mp.Queue(maxsize=2) mp.Process(target=capture_worker, args=(shared_arr, lock)).start() mp.Process(target=infer_worker, args=(shared_arr, lock, result_queue)).start()逻辑说明:mp.Array是共享内存,避免帧数据在进程间拷贝。lock保证读写不冲突。result_queue设 maxsize=2,识别慢于采集时自动丢帧,防止延迟累积。控制进程从队列取结果,按第 4 章的映射逻辑算 PWM,通过 pymavlink 或串口发给喷洒控制器。
6.3 验证整套系统是否真的“精准”
别只看模型 mAP,要看最终喷洒效果。我的验证方法是:在田里铺 10 张水敏纸,按处方图喷一遍,看纸上的雾滴覆盖密度是否和处方等级一致。轻症区雾滴密度应该是重症区的 30% 左右,偏差超过 15% 就说明映射环节有问题。这个测试比任何离线指标都实在,毕竟药打没打对,叶子知道。
这套系统我从零搭到能飞,前后返工了三次,最大的教训是:别在模型精度上死磕,先把坐标换算和喷洒延迟这两个工程问题解决掉,模型差 5 个点对最终效果的影响远小于喷错位置。如果你也在做类似的方向,建议先把第 4 章的坐标换算和第 5 章的延迟补偿跑通,再回头调模型。希望帮到你。
本文还有配套的精品资源,点击获取