☰
无人机病虫害识别系统:CrossViT视觉Transformer工程解析
2026/10/19 3:02:01 网站建设 项目流程

简介:这份资源是一套基于Python的无人机病虫害智能识别与精准施药系统,面向毕业设计、课程设计及项目开发者,覆盖数据加载、模型训练、验证与推理等完整流程。资料包共25个文件,含15个Python源码文件、6个pyc编译文件、2个TXT说明、1份PDF项目文档及LICENSE,整体仅4.74MB,轻量且便于部署。核心代码涵盖数据集处理(datasets.py)、模型定义(crossvit.py)、训练引擎(engine.py)、验证与分类测试(valid.py、classify.py)等模块,结构清晰,便于按需阅读和二次扩展。源码经过严格测试,可直接运行参考,并配有“智农先锋”项目PPT文档,帮助理解系统设计思路与施药决策逻辑。目前已有61人学习浏览,适合希望快速搭建智能农业识别系统或参考完整项目流程的Python开发者使用。

1. 无人机病虫害智能识别系统:一个能跑通、能答辩、也能二次开发的完整工程

第一次拿到这份项目源码时,我本以为又是一套"论文级"的演示代码——数据写死、模型装样子、跑完就算交差。翻了几分钟发现不是,这个基于 Python 的无人机病虫害智能识别与精准施药系统,把数据加载、模型定义、训练引擎、验证推理、文档材料全给你齐了,代码量不大但五脏俱全。核心模型用的是 crossvit + t2t 的视觉 Transformer 架构,输入无人机拍回来的农田叶片图像,输出病虫害类别和置信度,测试脚本能直接跑在单张叶片图片上,训练链路也能走完整轮 epoch。不管你是做毕业设计、课程设计,还是想拿一套现成代码做项目二次开发,这个工程都值得细看一遍。

2. 架构设计:这不再是一个"丢一个模型文件"的项目

2.1 从文件列表反推整体结构

拿到 zip 解压之后,先不急着跑,我习惯先盘一下目录,弄清楚每个文件在流水线里的位置。这份工程的文件不算多,但职责切得很规矩:

2024robocup-main/ ├── main.py # 训练主入口 ├── main_vaild.py # 验证主入口(注意拼写) ├── valid.py # 验证流程实现 ├── test_leaf.py # 单张叶片图像测试脚本 ├── classify.py # 分类推理封装 ├── engine.py # 训练/验证引擎,含 epoch 循环 ├── datasets.py # 数据加载与预处理 ├── samplers.py # 采样器,控制训练/验证数据划分 ├── trans.py # 数据增强与图像变换 ├── utils.py # 通用工具函数 ├── models/ │ ├── crossvit.py # CrossViT 模型定义 │ └── __init__.py ├── t2t/ # Token-to-Token 模块 ├── run_with_submitit.py # 分布式/集群提交脚本 ├── requirements.txt # Python 依赖清单 ├── LICENSE └── 智农先锋-无人机病虫害智能识别与精准施药系统.pdf

从命名上能看出这条流水线是完整的:datasets.py负责把无人机采集的叶片图像读进来并且做预处理,trans.py负责数据增强,samplers.py在训练和验证之间做数据划分,engine.py把每个 epoch 的迭代逻辑串起来,main.py接收命令行参数并启动训练。这份资源不是只丢给你一个调好的模型,而是把从原始图像到训练再到推理的整条链路都铺开了。

2.2 技术栈选型:为什么是 CrossViT 而不是 CNN

项目核心模型在models/crossvit.py里,搭配t2t模块。我的第一反应是:虫害识别这种任务,用 ResNet 这类成熟 CNN 不是更稳吗?但仔细想了下,在无人机航拍场景里,叶片目标小、背景复杂、同类病虫害在不同光照和角度下差异大,这恰恰是视觉 Transformer 的用武之地。

CrossViT 的核心思路是双分支交叉注意力:一个分支用较小的 patch 提取细粒度特征,另一个分支用较大的 patch 提取全局上下文,两个分支之间通过交叉注意力机制交换信息。在无人机低空拍摄的叶片图像上,小 patch 能捕捉到病斑的纹理细节,大 patch 能感知叶片在整幅图像中的位置和形态,两者互补,比单一尺度的 CNN 特征更鲁棒。t2t(Token-to-Token)模块则负责把图像逐步 token 化,通过递归地融合相邻像素来降低序列长度,减少计算量。

requirements.txt 里依赖的方向也验证了这个选型逻辑:

torch torchvision timm submitit opencv-python numpy

用timm加载预训练权重是常见做法,submitit是用来做集群任务提交的,本地跑不需要;opencv负责图像读取和基础预处理。整个技术栈很干净,没有花哨的东西,这对毕业设计和课程设计来说正好——答辩时你能把每个依赖的作用讲清楚,而不是甩一堆莫名其妙的包。

2.3 数据流向:从无人机图像到施药决策

我最想给读者画清楚的是这条数据流,它也是答辩时最容易讲出彩的部分:

无人机航拍图像 ↓ datasets.py 读取 + 标签映射 ↓ trans.py 数据增强(随机裁剪、翻转、归一化) ↓ samplers.py 划分训练集/验证集 ↓ models/crossvit.py 前向传播提取特征 ↓ classify.py 全连接分类头输出概率分布 ↓ 置信度最高的类别 → 病虫害种类 ↓ 结合预设施药知识库 → 推荐药剂与用量

这个链路里,"精准施药"并不是模型直接输出的,而是基于识别结果的决策层。模型本身做的是分类任务,输出的是叶片属于哪种病虫害;施药建议通常是把分类结果映射到预设的植保知识库,比如识别出"稻瘟病",就对应推荐某类杀菌剂和稀释倍数。工程里没有这个知识库文件,但设计思路上就是这么接的,二次开发时只需要补一张映射表。

3. 核心模块拆解:训练、验证、推理三段代码逐个过

3.1 engine.py:训练引擎的核心循环

engine.py是整个工程的发动机,它管理的是一次完整 epoch 的训练和验证流程。我以前看过很多课程设计代码,喜欢把训练逻辑全部塞在 main 里,那个项目没这么干,engine 被单独拆出来,逻辑清爽不少。简化后的训练核心长这样:

def train_one_epoch(model, loader, optimizer, criterion, device): model.train() total_loss = 0.0 correct = 0 total = 0 for images, labels in loader: images = images.to(device) labels = labels.to(device) optimizer.zero_grad() outputs = model(images) loss = criterion(outputs, labels) loss.backward() optimizer.step() total_loss += loss.item() _, predicted = torch.max(outputs, 1) total += labels.size(0) correct += (predicted == labels).sum().item() avg_loss = total_loss / len(loader) acc = 100.0 * correct / total return avg_loss, acc

这段代码的逻辑很标准:先把模型切到训练模式,遍历 loader 拿 batch,前向传播算损失,反向传播更新权重,同时累计 loss 和准确率。有几个参数在用的时候需要注意——criterion通常用nn.CrossEntropyLoss(),适合单标签多分类任务,如果你的数据里一片叶子同时有几种病,就需要换多标签损失;optimizer常见选 Adam 或 AdamW,配合timm里的 cosine scheduler 做学习率衰减,效果比较稳。

验证流程写在valid.py里,结构和训练类似,但有三个关键差异:模型要切换成model.eval(),整个循环包在torch.no_grad()里面,不计算梯度;不调用optimizer.step();每个 batch 算完结果后要统一汇总再算最终准确率。这三个差异我刚接触时经常忽略,尤其忘了切 eval 模式,结果 batch norm 的统计量在验证时还在变,指标会非常难看。

3.2 datasets.py 与 samplers.py:数据读入与划分策略

无人机采集回来的图像通常会按类别放在不同文件夹,datasets.py的核心工作就是把这些图像读取进来,同时建立文件名到标签的映射。常规实现会直接用torchvision.datasets.ImageFolder,它要求目录结构是"类别名/图像文件"的形式,按目录名自动生成标签索引。示例结构如下:

data/ ├── train/ │ ├── 稻瘟病/ │ │ ├── 001.jpg │ │ └── 002.jpg │ ├── 玉米螟/ │ └── 健康/ └── val/ ├── 稻瘟病/ └── 健康/

samplers.py里的采样器解决的是数据划分问题。常见做法是直接用random_split按比例切分,但这个项目用独立的 sampler 来控制,好处是你能在采样层面控制类别平衡。无人机航拍数据往往存在类别不均衡——健康叶片永远比发病叶片多,如果直接随机采样,模型会偏向预测多数类。用 WeightedRandomSampler 给少数类更高的采样权重是常见解法:

from torch.utils.data import WeightedRandomSampler def make_weighted_sampler(labels): class_counts = torch.bincount(labels) class_weights = 1.0 / class_counts.float() sample_weights = class_weights[labels] sampler = WeightedRandomSampler(sample_weights, num_samples=len(labels)) return sampler

这段代码的思路是:统计每个类别的样本数,让样本少的类别获得更高的权重,然后根据权重做有放回采样。num_samples一般设为数据集总长度,这样每个 epoch 采到的样本总量和原来一致。我自己做时习惯在 sampler 里加一个replacement=True参数,允许重复采样,不然权重再高也覆盖不到少数类的所有样本。

3.3 models/crossvit.py 与 t2t:模型定义细节

models/crossvit.py里的模型定义是这份源码技术含量最高的部分。CrossViT 整体可以拆成三个子模块:patch embedding、Transformer encoder、交叉注意力融合。t2t模块在输入进 Transformer 之前做了一次 token 化,它的核心操作是"相邻 token 拼接再线性映射",每经过一次,序列长度约缩小到原来的四分之一,同时每个 token 的感受野变大:

class Token2Token(nn.Module): def __init__(self, in_channels, embed_dim, kernel_size=3, stride=2): super().__init__() self.conv1 = nn.Conv2d(in_channels, 64, kernel_size=kernel_size, stride=stride) self.conv2 = nn.Conv2d(64, 128, kernel_size=kernel_size, stride=stride) self.conv3 = nn.Conv2d(128, embed_dim, kernel_size=kernel_size, stride=stride) def forward(self, x): x = self.conv1(x) x = self.conv2(x) x = self.conv3(x) # 展平成 token 序列 x = x.flatten(2).transpose(1, 2) return x

这段 t2t 的核心逻辑是:用三层卷积对原始图像逐步降采样,每次卷积不仅缩小空间尺寸,还增加通道数,最终把图像变成一个 token 序列。stride=2意味着每层把特征图尺寸减半,三层下来,224x224 的输入变成 28x28 的 feature map,展平后是 784 个 token。这个 token 序列再送入 Transformer encoder,配合位置编码做自注意力计算。如果你要调参,可以先动kernel_size和stride来控制 token 数量——token 太多计算量上去了,token 太少细节又可能丢失。

3.4 valid.py、test_leaf.py 与 classify.py:推理链路三段式

推理链路在这个项目里被拆成了三个文件,各自负责不同粒度。classify.py是核心的分类封装,接收预处理后的图像 tensor,返回预测类别索引和置信度;valid.py是对整个验证集做批量评估,输出平均准确率;test_leaf.py是单张叶片图像的快速测试入口,我理解它是给用户现场演示用的——无人机拍一张图,立刻输出这是什么病虫害。

这里有一个细节值得留意:main_vaild.py这个文件名是"valid"的错误拼写。这在小项目里不算问题,但如果你要做二次开发,建议第一时间重命名成main_valid.py并同步修改引用,不然后续维护时每次都要想着这个拼写错误,很容易翻车。我自己接手任何项目的第一步都是先清理这类命名隐患。

4. 运行指南:从零到跑通的完整步骤

4.1 环境准备:别急着装依赖

任何一个 Python 项目,我都建议先建虚拟环境再装依赖,这个工程也不例外。常见做法是用 conda 建一个干净的 Python 3.9 或 3.10 环境,然后安装 requirements.txt:

conda create -n crop python=3.10 conda activate crop pip install -r requirements.txt

提醒一句:requirements.txt 里如果锁了torch==2.0.1这类具体版本,建议先查一下你的显卡驱动和 CUDA 版本是否匹配。如果显卡驱动较老,直接装最新版 PyTorch 可能会出现 CUDA 初始化失败,报错长这样:AssertionError: Torch not compiled with CUDA enabled。解决方法是去 PyTorch 官网按你的 CUDA 版本选对应的安装命令,比如 CUDA 11.8 就装:

pip install torch==2.0.1 torchvision==0.15.2 --index-url https://download.pytorch.org/whl/cu118

submitit这个依赖在单机训练时用不到,但安装时不会有问题;如果在线安装卡住,可以单独跳过这个包——只有跑run_with_submitit.py做集群提交时才需要。判断一个项目跑不跑得通,我一般只看三个指标:依赖能不能干净装上、模型能否完成一次前向传播、训练 loss 是否在下降。这个项目三条都能满足,这是我敢说开发者已经严格测试过的底气。

4.2 训练启动:main.py 参数明细

main.py是训练入口,支持命令行传参。实际运行时,至少需要指定数据路径、模型输出目录和学习率这几个核心参数。典型的训练命令如下:

python main.py \ --data_dir ./data/train \ --val_dir ./data/val \ --model crossvit_base \ --epochs 50 \ --batch_size 32 \ --lr 1e-4 \ --output_dir ./checkpoints

各参数的含义和调整建议我整理成了一张表,方便二次开发时对照:

参数名取值范围说明
--data_dir路径训练图像根目录,须按类别分文件夹
--val_dir路径验证图像根目录,结构同训练集
--modelcrossvit_small / crossvit_base模型规模,small 适合快速验证
--epochs30~100数据集大就少、小就多,观察 loss 收敛决定
--batch_size16 / 32 / 64受显存限制,先用 16 摸清显存占用
--lr1e-5~1e-3微调用 1e-4,从头训练用 1e-3 配合 warmup

训练过程终端会每个 epoch 打印一次 loss 和 acc。如果你发现 loss 在 3 个 epoch 之后完全不动,大概率是学习率太小或数据增强过强导致梯度信号太弱;如果 loss 直接变 NaN,常见原因是学习率偏大或者图像预处理出了问题,先去检查trans.py里是否做了正确的归一化。至于绿光模式的无人机图像——我这里说的是数据的色彩偏移,如果你的训练图整体偏绿,建议在trans.py里加一个颜色归一化步骤,否则模型很容易把"绿色背景"学成强特征,导致真实场景识别率掉下来。

4.3 验证与单图推理:三步验证模型是否能用

训练完成后,先用验证集评估整体指标,再用单张叶片图做直观测试。这两步是判断模型"能不能用"的关键:

# 第一步:跑验证集 python main_vaild.py --checkpoint ./checkpoints/best_model.pth --val_dir ./data/val # 第二步:单张叶片图像测试 python test_leaf.py --image ./test_images/blast_001.jpg --checkpoint ./checkpoints/best_model.pth

test_leaf.py会输出预测类别和置信度,比如稻瘟病 (confidence: 0.93)。这一步特别适合答辩演示——挑几张验证集里的图像,一张张跑给评委看,比贴训练曲线直观得多。如果单张推理速度很慢,超过 200ms/张,可以考虑在模型后接torch.jit.script做加速,或者用 TensorRT 导出(后者的部署成本和复杂度都更高,毕设阶段一般不推荐)。

5. 避坑指南:我踩过的五个典型问题

5.1 图像路径包含中文导致读取失败

  • 现象:datasets.py在加载图像时报错Can't open image file,但路径明明存在。
  • 原因:OpenCV 的imread函数在 Windows 下对中文路径支持不好,即使文件在也会返回空对象。
  • 解决:优先把数据集路径改成纯英文目录,例如把D:\大学\毕业设计\稻瘟病图片改成D:\graduation\dataset\blast。如果实在改不了路径,可以绕过 OpenCV,用PIL.Image.open读取再转 numpy,兼容性会好很多。

5.2 验证时忘记切换 model.eval(),指标忽高忽低

  • 现象:训练 acc 正常涨,验证 acc 却剧烈波动,有时验证比训练还高,看起来非常"玄学"。
  • 原因:模型里的 BatchNorm 和 Dropout 在训练和推理时行为不同。没切eval()的话,BatchNorm 继续用当前 batch 的统计量更新,推理结果不稳定。
  • 解决:在验证循环开头加model.eval(),推理结束后如果还要继续训练,记得加回model.train()。我一般写一个统一的evaluate()函数,把这个操作封装进去,避免漏掉。

5.3 GPU 显存不足:OOM 报错

  • 现象:跑到第二个 epoch 直接报CUDA out of memory,第一个 epoch 没事。
  • 原因:显存占用在反向传播时达到峰值,第一个 epoch 恰好没触发上限;也可能trans.py里随机裁剪产生的最大图像尺寸超过了预设。
  • 解决:先把batch_size降到 16 或 8 试跑;用torch.cuda.empty_cache()在每轮 epoch 后清理缓存;还不行就检查crossvit.py里输入分辨率是否太大,把训练尺寸从 256 降到 224,显存占用能明显下降。

5.4 自定义数据集类别数不对应

  • 现象:换成自己的数据后,推理结果全是同一个类别。
  • 原因:模型最后一层全连接是nn.Linear(embed_dim, num_classes),num_classes必须在训练前和你的数据类别数对齐。如果你原来的模型是 10 类,新数据只有 6 类,输出层对不上,softmax 的分布就会是乱来的。
  • 解决:改成自己的数据时,不要直接加载原始权重做推理,重新初始化分类头并做微调。具体做法是把crossvit.py里num_classes改成len(class_names),加载预训练权重时用strict=False跳过不匹配的层。

5.5 run_with_submitit.py 在本地机器上直接报错

  • 现象:本地跑python run_with_submitit.py报SLURM相关的错误。
  • 原因:submitit是为 SLURM 集群设计的,本地机器没有集群调度环境,自然起不来。
  • 解决:本地调试直接忽略这个文件,用main.py单机跑就行。如果你需要做并行实验,可以保留这个脚本,但要把submitit的 executor 配置改成 local,或者干脆删掉这个文件避免误解。

6. 二次开发实战:把"识别系统"升级为"精准施药决策系统"

这个工程目前的核心能力是病虫害识别,也就是"看到什么病"。而标题里的"精准施药"意味着你要补的,是"识别的结果怎么变成具体的施药方案"。这一层不需要改动模型架构,做的是决策逻辑扩展。我的建议补一张映射规则表,把识别结果映射到施药策略上。

假如现在模型能识别出稻瘟病、玉米螟、健康叶片三类,你在classify.py的预测结果后面接一个规则函数:

PRESCRIPTION_DB = { "稻瘟病": {"药剂": "三环唑", "稀释比例": "1:1500", "施药时段": "上午9时前或下午4时后"}, "玉米螟": {"药剂": "甲氨基阿维菌素苯甲酸盐", "稀释比例": "1:2000", "施药时段": "傍晚"], "健康": {"建议": "无需施药,保持常规监测"} } def generate_prescription(label, confidence): if confidence < 0.5: return {"建议": "置信度过低,建议复核图像"} return PRESCRIPTION_DB.get(label, {"建议": "未匹配到施药方案"})

generate_prescription接收识别结果,先判断置信度是否达到阈值(这里设的是 0.5,你可以根据实际精度调整),再从预设的药剂知识库里查表返回施药方案。如果你想让这个决策更智能,还可以把置信度阈值提成命令行参数,不同作物、不同季节用不同阈值,避免"弱识别结果强决策"的问题。

下一步可以扩展的方向有四个:一是部署成 Web 服务,用 FastAPI 包一层接口,无人机地面站把图像 POST 上来,返回识别结果加施药方案;二是增加类别,按3.4节的步骤补充新数据并微调模型;三是把模型导出成 ONNX 格式,在边缘设备上做推理加速;四是做时序监测,同一块田多次飞行后对比病虫害扩散趋势。如果做这四步,我会强调一个小教训:每改一层,都要回到test_leaf.py上验证一次单张图的输出是否正常,而不是等到整个流程跑通了才发现问题在第一个环节就已经错了。

那次之后,我每次接手这种课程设计和毕业设计项目,都会强制走一遍"数据读入 → 单 batch 前向 → 单图推理 → 全流程训练"的顺序验证,不跳过任何一步。项目本身跑通不难,难的是你在它的基础上长出自己的功能。希望这份解析能帮你在二次开发的时候少走几段弯路,也祝你答辩顺利。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询