MATLAB激光点云病虫害识别:从预处理到模型训练
2026/9/14 12:49:29 网站建设 项目流程

简介:这是一套基于激光点云与深度神经网络(VGG19)的农作物病虫害识别Matlab实现源码,面向农业工程、计算机视觉方向的初学者和研究人员,可用于快速搭建从数据加载、模型训练到测试评估的完整实验流程。压缩包共942个文件,大小仅8.38MB,以930张jpg果树叶片图像为训练与验证数据,同时包含TrainData训练集、Validation验证集、labelname标签索引、VGG19训练主函数、测试代码和混淆矩阵绘制脚本,并附有说明文档和MATLAB实时脚本,目录结构清晰,便于按模块调用。已有146人学习下载,适合参考深度学习在农业植保中落地方法的入门与进阶者。资源可直接完成端到端训练测试:用VGG19TrainCode训练模型,再用TestCode验证效果,并通过Visibleplot输出混淆矩阵辅助分析识别表现,可作为论文实验、课程设计或病虫害识别系统原型的可复现基准。

1. 激光点云病虫害识别系统,为什么难在数据而不是网络

激光点云做病虫害识别,真正拉开系统差距的不是神经网络选型,而是点云数据这一侧的约束。三维点云自带几何结构,理论上比二维图像更能反映病斑的凹陷、凸起和早期形态变化,但实际从扫描仪拿到一株作物的点云,到神经网络真正吃到干净的特征输入,中间要处理噪声、稀疏、密度不均、反射强度漂移等问题。很多源码包在模型部分写得很完整,却在读数据的第一步就把方向带偏了。这套方案解决的是智慧农业里人工巡检效率低、二维图像难以反映植株三维结构的问题,适合做精准植保、农业机器人和遥感应用的工程师,也适合想在 matlab 里把点云处理与深度学习串成一条完整管线的开发者。做这类系统,第一课不是调网络,而是先把点云预处理和标签设计想清楚。

2. matlab 激光点云预处理:从原始点云到神经网络可用的特征张量

2.1 为什么选 matlab:点云工具箱与深度学习工具箱怎么配合

matlab 在点云处理上最大的优势是自带 Computer Vision Toolbox 和 Lidar Toolbox,pcreadpcdenoisepcdownsamplepcsegdist这些函数直接把常见的滤波、降采样、分割需求封装好了,不需要自己从头写数据结构。深度学习这边,Deep Learning Toolbox 提供trainNetworkdlnetworktrainingOptions,配合imageInputLayerfeatureInputLayer就能把点云转换后的特征张量喂进网络。两个工具箱的衔接点是:点云处理的结果最终要落到一个固定尺寸的张量上,要么是投影图像,要么是体素网格,要么是定长采样的点特征矩阵。这个落点决定了后续网络结构的选择。

值得一提的是,matlab 的调试体验比多数开源框架直观,点云可以随时用pcshow可视化,错误样本能逐帧回放。对于几十个类别以内、样本量不大的病虫害识别场景,matlab 一条龙做完是可行的;样本量到万级以后,再考虑把数据导出给其他框架训练。

2.2 点云预处理参数:降采样、去噪、裁剪的三组常用值

激光点云原始数据里,地面点、环境噪声、扫描边缘的飞点都会干扰神经网络。常见的处理顺序是:先统计滤波去噪,再体素降采样,然后按 ROI 裁剪出作物区域。下面是 matlab 里对应的一组代码,参数值可以根据扫描距离和作物行距调整。

ptCloudIn = pcread('tree_007.pcd'); % 去除离群噪声点,Threshold 控制邻域标准差倍数 ptCloudDenoised = pcdenoise(ptCloudIn, 'Threshold', 1.5); % 体素降采样,gridStep 是体素边长,单位与点云坐标一致(米) gridStep = 0.02; ptCloudDown = pcdownsample(ptCloudDenoised, 'gridAverage', gridStep); % 按矩形 ROI 裁剪,只保留作物区域 roi = [0 1 -0.5 0.5 0 2]; % [xmin xmax ymin ymax zmin zmax] indices = findPointsInROI(ptCloudDown, roi); ptCloudCrop = select(ptCloudDown, indices); pcshow(ptCloudCrop);

Threshold设为 1.5 到 2.0 之间是常见做法。设得太小会把正常叶片边缘的点也当噪声删掉,设得太大又起不到滤波作用。gridStep的取值要看点云密度:扫描距离近、点密度高时用 0.01,远距离用 0.03 到 0.05。降采样的核心目的不是压缩体积,而是让不同株作物的点密度趋于一致,否则网络会学到密度特征而不是形态特征。ROI 裁剪这一步经常被忽略,但田间扫描数据里地面和背景点往往占一半以上,裁掉它们能显著减少后续聚类的计算量。

2.3 把点云转成特征张量:投影图与体素的 matlab 代码

点云不能直接输入卷积神经网络,常见做法是转成多通道投影图或体素网格。投影图适合复用成熟 CNN,体素适合保留三维结构,但内存开销大。这里给一个把点云投影成三通道图像的 matlab 函数,三个通道分别是高度、反射强度和点密度。

function img = projectPointCloud(ptCloud, gridSize) loc = ptCloud.Location; x = loc(:,1); y = loc(:,2); z = loc(:,3); intensity = ptCloud.Intensity; % 若无强度通道,可全部置 1 if isempty(intensity) intensity = ones(size(z)); end xEdges = linspace(min(x), max(x), gridSize(2) + 1); yEdges = linspace(min(y), max(y), gridSize(1) + 1); xi = discretize(x, xEdges); yi = discretize(y, yEdges); valid = ~isnan(xi) & ~isnan(yi); idx = sub2ind(gridSize, yi(valid), xi(valid)); nPixels = prod(gridSize); heightMap = accumarray(idx, z(valid), [nPixels 1], @mean, 0); intensityMap = accumarray(idx, intensity(valid), [nPixels 1], @mean, 0); countMap = accumarray(idx, ones(sum(valid), 1), [nPixels 1], @sum, 0); heightMap = reshape(heightMap, gridSize); intensityMap = reshape(intensityMap, gridSize); countMap = reshape(countMap, gridSize); % 归一化到 0-1,避免极端值主导 heightMap = heightMap / (max(heightMap(:)) + eps); intensityMap = intensityMap / (max(intensityMap(:)) + eps); countMap = log1p(countMap) / log(max(countMap(:)) + 1); img = cat(3, heightMap, intensityMap, countMap); end

discretize把每个点的 x、y 坐标映射到网格索引,accumarray按索引聚合。高度通道反映叶片和病斑的起伏,反射强度通道对叶片含水量变化敏感,密度通道可以区分茂密与稀疏区域,这三个通道拼在一起比单用灰度图信息量大得多。投影图的尺寸一般用 224×224 或 128×128,太小会丢失病斑细节,太大则训练变慢且容易过拟合。

2.4 标签设计:点级、株级、框级三种粒度怎么选

病虫害识别系统有一个决定成败的步骤:标签粒度。不同粒度对应不同的网络输出和标注成本,做之前一定要想清楚。

标签粒度标注内容网络输出形式标注成本适用场景
点级每个点标为病斑/健康逐点分类(分割)极高需要精确定位病斑位置
株级每株作物标一个类别整株一个类别巡田筛查、按株决策
框级三维框包含病斑区域目标检测框需要统计病斑数量和大小

实际做巡田类系统,株级标签最容易落地。点级标注在三维点云上操作非常慢,一株作物几万个点逐个标不现实;框级标注需要专门的标注工具,而且病斑形状不规则,框的边界很难定。株级标签配合第 4 章的“分割+聚类+分类”流程,标注成本低,又能直接输出“哪一株有病、病得重不重”这种农艺上关心的结果。等到需要精确定位早期病斑时,再考虑用株级模型先筛出疑似株,只对这部分做点级细标注。

3. 神经网络模型选型与训练参数:投影 CNN、体素网络与 PointNet 的取舍

3.1 三种可落地的神经网络输入形式在 matlab 里的实现成本

输入形式matlab 实现方式优点缺点
多视图投影图imageInputLayer+ 现成 CNN可迁移学习,训练快丢失部分三维遮挡信息
体素网格imageInputLayer或自定义层处理 3D 数据保留三维结构内存占用大,稀疏体素浪费计算
原始点云(PointNet 风格)featureInputLayer/ 自定义dlnetwork保留完整几何信息需要固定点数,实现复杂度高

投影图路径在 matlab 里实现成本最低,可以直接用resnet18resnet50做迁移学习;体素路径需要把点云数据转换成五维张量,内存消耗大,通常 32×32×32 的体素已经让显存吃紧;PointNet 风格路径最灵活,但 matlab 没有官方内置 PointNet 层,需要自己定义网络结构,适合对深度学习框架比较熟悉的开发者。三种路径不冲突,可以在同一份源码里并存,先用投影图跑通基线,再逐步增加输入信息量。

3.2 投影图路径:用 resnet18 迁移学习快速跑通

迁移学习是点云样本量不足时的首选。病虫害样本的标注成本高,几百到几千株是常态,从头训练一个深度网络基本会过拟合。matlab 里用resnet18做迁移学习的代码框架如下。

numClasses = 5; % 健康、早期、中期、晚期、其他 lgraph = layerGraph(resnet18); % matlab 版本不同层名不同,用 analyzeNetwork 确认具体名称 lgraph = replaceLayer(lgraph, 'input_1', ... imageInputLayer([224 224 3], 'Name', 'pest_input')); lgraph = replaceLayer(lgraph, 'fc1000', ... fullyConnectedLayer(numClasses, 'Name', 'pest_fc')); lgraph = replaceLayer(lgraph, 'ClassificationLayer_...', ... classificationLayer('Name', 'pest_output')); options = trainingOptions('sgdm', ... 'InitialLearnRate', 1e-3, ... 'MiniBatchSize', 32, ... 'MaxEpochs', 30, ... 'LearnRateSchedule', 'piecewise', ... 'LearnRateDropFactor', 0.3, ... 'LearnRateDropPeriod', 10, ... 'ValidationFrequency', 20, ... 'Plots', 'training-progress'); net = trainNetwork(imdsTrain, lgraph, options);

replaceLayer的层名在不同 matlab 版本里不一致,尤其input_1和最后的分类层,装好代码后先运行analyzeNetwork(lgraph)检查一遍。初始学习率用 1e-3 而不是默认的 1e-2,因为迁移学习是在预训练权重上做微调,步子太大会破坏已经学好的底层特征。优化器选择sgdm而非adam,在中小样本量下sgdm的泛化通常稍好,收敛也稳。学习率每 10 轮降到原来的 0.3 倍,让训练后期用更小的步长精细调整。

3.3 PointNet 风格网络:自定义层的核心代码和训练循环

如果投影图路径无法满足精度要求,可以尝试 PointNet 风格的网络:直接消费原始点云,用共享 MLP 对每个点提取特征,再用最大池化把整株点云聚合为一个全局特征。最大池化保证了置换不变性,即点的输入顺序不影响输出,这是点云网络与普通网络的本质区别。

% X: 单株点云,ndev 1024×6 % 每个点有 xyz、强度、局部密度、高度 共 6 个特征 inputSize = 1024; numFeatures = 6; sharedMLP = [ featureInputLayer(numFeatures, 'Name', 'point_feat') fullyConnectedLayer(64, 'Name', 'fc1') reluLayer('Name', 'relu1') fullyConnectedLayer(128, 'Name', 'fc2') reluLayer('Name', 'relu2') ]; mlpNet = dlnetwork(sharedMLP); % 训练循环中的一次前向传播 % dlX: 1024×6 dlarray,每次喂一株作物的定长采样点 feat = forward(mlpNet, dlX, 'Outputs', 'relu2'); % 1024×128 pooled = max(feat, [], 1); % 1×128,置换不变 globalFeat = fullyconnect(pooled, W_cls, b_cls); % 最后接分类层

这里最关键的是把所有点云都采样到固定点数inputSize。点多的随机下采样,点少的随机重复采样补足,否则dlarray没法组成 batch。forward的输出是每个点的高维特征,max操作沿点维度聚合得到整株的全局描述子。这个结构比投影图多保留了完整的三维坐标信息,对病斑凹陷、叶片卷曲这类立体特征更敏感。缺点是需要自己写完整的训练循环,包括梯度计算、损失回传和验证逻辑,工程量大一些。

3.4 训练参数:学习率、batch、类别权重与数据增强

病虫害识别有一个先天问题:健康样本远多于病斑样本,且早期病斑在整株中的占比很小。这会直接导致网络把所有样本都判成健康类,因为整体准确率依然很高。解决思路有三个。

类别权重是最直接的手段。matlab 的classificationLayer支持ClassWeights参数,把病斑类别的权重调高,让网络对少数类样本的错误预测付出更大代价:

weights = [1.0, 3.0, 5.0, 8.0, 1.0]; % 分别对应五个类别 layer = classificationLayer('Name', 'output', 'ClassWeights', weights);

batch size 不要贪大,32 以内比较合适。点云投影图的单张尺寸是 224×224×3,显存压力比普通图像大,batch 设 64 以上容易让训练因内存不足中断,而且小 batch 配合sgdm反而能带来正则化效果。另一个训练细节是数据划分:一定要按株划分训练集和验证集,不能把同一株作物的点云切一半分到两边。否则网络记住了植株个体的特征而不是病斑的共性特征,验证集准确率虚高,田间换一批作物立刻打回原形。数据增强的内容放在第 5 章展开,这里先记住一个原则:增强操作必须模拟真实扫描差异,不能引入数据分布之外的形态。

4. 病虫害识别系统的推理闭环与源码工程组织

4.1 推理流程:分割、聚类、分类串成一条管线的顺序

训练好的模型要落地成“输入一片点云、输出每株作物的健康状态”,不能直接整片云送进网络。田间扫描得到的点云包含地面、杂草、相邻植株,混在一起分类结果没有意义。合理的推理管线分三步:第一步去掉地面和背景,第二步把连在一起的植株按空间距离切分,第三步对每个切出来的植株块单独做分类。顺序不能反,先分割再聚类再分类,每一步的输出都是下一步的输入,任何一步参数不对都会直接影响最终结果。

4.2 按株聚类再分类的 matlab 代码

% 第 1 步:用平面拟合去掉地面点 maxDistance = 0.05; [~, ~, outlierIdx] = pcfitplane(ptCloudDown, maxDistance); plants = select(ptCloudDown, outlierIdx); % 第 2 步:按距离聚类,把不同植株分开 minDistance = 0.1; [labels, numClusters] = pcsegdist(plants, minDistance); % 第 3 步:逐株投影并分类 net = load('trainedPestNet.mat').net; classes = {'healthy', 'early', 'mid', 'late', 'other'}; for k = 1:numClusters clusterIdx = find(labels == k); clusterPts = select(plants, clusterIdx); % 点数太少的簇多半是噪声,直接跳过 if clusterPts.Count < 100 continue; end img = projectPointCloud(clusterPts, [224 224]); scores = predict(net, img); [maxScore, clsIdx] = max(scores); fprintf('Cluster %d: %s (%.2f)\n', k, classes{clsIdx}, maxScore); end

pcfitplanemaxDistance是点到拟合平面的最大距离阈值,0.05 米适合地面相对平整的田块;地面坡度大或杂草多时,先做一次降采样再拟合会更稳。pcsegdistminDistance决定两个点属于同一个簇的最大间距,取值取决于作物行距和植株冠幅,行距大的果园可以放宽到 0.3 米,密植作物要相应收紧。clusterPts.Count < 100这个阈值过滤了大部分飞点和远处扫描产生的稀疏噪声,避免把噪声簇当成病株送进网络。

4.3 评估:IoU、召回率与农业场景下的误报/漏报代价

评估指标不能只看整体准确率。病虫害场景里,漏检和误报的代价不对称:漏掉一棵病株可能导致病害扩散到整片田,误报一棵无非是多跑一趟人工复核。所以召回率比准确率重要,尤其早期病斑的召回率。

指标计算方式关注原因
株级准确率分类正确的株数 / 总株数总体效果参考
早期病斑召回率正确识别的早期病株 / 实际早期病株早期漏检损失最大
各类别 IoU预测与真实重叠区域 / 预测与真实并集区域病斑定位是否准确
误报率健康株被判为病株 / 健康株总数评估人工复核成本

计算这些指标时,按株统计而不是按点统计。按点统计会把病斑面积大但株数少的样本权重放大,导致指标好看但实际巡田能力差。

4.4 源码工程目录:数据、预处理、模型、训练、评估分开

拿到源码第一件事是看目录结构,目录组织直接反映代码的维护水平。一套可扩展的病虫害识别系统源码,目录应该长这样:

pest_detection/ ├── data/ │ ├── raw/ # 原始点云,按采集日期分文件夹 │ ├── preprocessed/ # 降采样、去噪、ROI 裁剪后的中间数据 │ └── labels/ # 株级标签 CSV,或标注工具导出的文件 ├── preprocess/ │ ├── buildProjectionImages.m # 点云→投影图 │ ├── makeVoxelGrids.m # 点云→体素网格 │ └── sampleFixedPoints.m # 定长采样(PointNet 用) ├── models/ │ ├── resnet_pest.m # 投影图分类网络定义 │ └── pointnet_pest.m # 自定义 PointNet 风格网络 ├── train/ │ ├── trainProjection.m # 投影图训练脚本 │ └── trainPointNet.m # 点云直接训练脚本 └── eval/ ├── computeMetrics.m # 混淆矩阵与各类指标 └── reviewErrors.m # 错误样本回放工具

preprocesstrain分离是关键。预处理脚本跑一遍,结果落盘到data/preprocessed,训练时直接读中间结果。不要每次训练都重新跑点云处理,那会让调参周期拉长好几倍。eval目录也要单独放,评估脚本应该能直接读模型文件和测试数据,输出指标并生成可视化报告,这样才能在调参时快速对比不同版本模型的效果。

5. 进阶:把识别准确率再往上推的三个实用技巧

5.1 用预训练主干做冷启动,再冻结浅层微调

样本量不足时,不要一上来就全网络微调。常见做法是分两阶段训练:第一阶段冻结resnet18的前几个残差块,只训练后端分类层,让新学到的病斑特征在预训练特征空间里先找到合适的位置;第二阶段解冻全部层,用很小的学习率微调。冻结浅层的核心逻辑是浅层学的是通用边缘、纹理特征,对任何视觉任务都有效,而深层特征才和具体任务强相关。

% 第一阶段:冻结前 40 层,只训练后面的层 lgraph = layerGraph(resnet18); layersToKeep = 40; for i = 1:layersToKeep layerName = lgraph.Layers(i).Name; if isa(lgraph.Layers(i), 'nnet.cnn.layer.Convolution2DLayer') lgraph = freezeWeights(lgraph, layerName); end end % 第二阶段再解冻微调,学习率降一档 options = trainingOptions('sgdm', ... 'InitialLearnRate', 1e-4, ... 'MaxEpochs', 20);

冻结层数不是固定的,取决于新数据集与预训练数据集的差异程度。如果是大田作物与 ImageNet 的自然图像之间的迁移,差异大,冻结浅层即可;如果换作物种类,差异小,可以把解冻层数加大。第二阶段的InitialLearnRate从 1e-3 降到 1e-4,防止大梯度破坏第一阶段的成果。

5.2 对点云更有效的三种数据增强方式

点云增强和图像增强思路不同,旋转、翻转都要考虑实际扫描场景。绕 Z 轴旋转最安全,因为扫描时水平角度天然有变化;绕 X 轴或 Y 轴大幅旋转会造出真实扫描不可能出现的姿态,反而干扰学习。随机丢弃点模拟遮挡和扫描盲区,随机缩放模拟不同扫描距离造成的尺度差异。

% 绕 Z 轴随机旋转 ±10° augPc = pctransform(pc, ... rigid3d(axang2rotm([0 0 1 deg2rad(rand * 20 - 10)]), [0 0 0])); % 随机丢弃 5% 的点,模拟遮挡 keepIdx = randsample(pc.Count, round(pc.Count * 0.95)); augPc = select(pc, keepIdx);

需要注意,坐标和反射强度的抖动幅度要小,因为病斑和健康叶片的反射强度差异本身不大,加太大噪声会把有效信息盖掉。如果体素网格输入是随机旋转前的点云生成的,旋转增强会破坏体素对齐关系,这种情况下优先用投影图路径做增强。

5.3 回放错误样本:比看准确率更能发现问题的一个操作

训练结束不要只看验证集准确率,把错误样本逐帧回放一遍,能发现很多指标看不出来的问题。比如健康株总被误判为晚期病斑,可能不是网络的问题,而是投影函数的归一化逻辑在低矮植株上把噪声放大了。

[C, order] = confusionmat(yTest, yPred); wrongIdx = find(yTest ~= yPred); for i = 1:numel(wrongIdx) idx = wrongIdx(i); img = readimage(imdsTest, idx); imshow(img); title(sprintf('真实: %s / 预测: %s', ... classes(yTest(idx)), classes(yPred(idx)))); waitforbuttonpress; end

回放时按错误类型归类:同一类病斑相互混淆,通常说明特征区分度不够,回到投影通道组合或网络层数上找原因;特定作物株型整体误判,通常是聚类参数把多株作物切成了同一簇,回到minDistance调整;某一扫描批次的样本集中错误,大概率是预处理参数与数据采集条件不匹配。把错误样本按类别堆积起来看,往往能发现某类病斑总被预测成另一类,这时回到投影参数去调整高度归一化或体素边长,比换一个更大的网络实际收益高得多。

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

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

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

立即咨询