☰
基于MATLAB的病虫害识别系统:从图像预处理到分类的完整实现
2026/10/14 22:24:09 网站建设 项目流程

简介:这是一套面向农业信息化与图像处理学习者的病虫害识别系统源码包,基于MATLAB实现,通过图像预处理、特征提取与分类器训练自动判别植物叶片的病虫害等级,适合具备一定编程基础、希望将机器学习落地到农业场景的学生与开发者。压缩包共95个文件,以84张jpg样本图片、10个m脚本和1个mat数据文件为主,图片按正常、轻微、中等、严重等灾害等级分目录存放,脚本覆盖训练、颜色空间转换、特征匹配与量化等环节,整体约7.62MB。目前已有4906人学习下载。资源完整呈现了从数据组织到模型训练、测试评估的流程,读者可据此复现灰度化、直方图均衡、LBP与GLCM特征提取、SVM分类等关键步骤,并借助示例图片调试验证,是理解图像处理与机器学习在植保领域应用的实用参考。

1. 从一张糊了的叶片照片说起:这套病虫害识别系统到底能干什么

去年帮一个做植保的朋友看代码,他拍着桌子说“模型在测试集上准确率 92%,怎么一到地里就瞎了”。我让他把手机里随手拍的叶片照片发过来——逆光、叶片只占画面三分之一、背景还有滴灌带。这就是绝大多数病虫害识别系统翻车的起点:实验室里干净背景、居中构图的图,和田间地头随手一拍,压根不是一回事。

这套基于 MATLAB 的病虫害识别系统,解决的正是“从拍到的叶片到给出病虫害类别”这条链路。它不追求发论文级别的 SOTA,而是把预处理、分割、特征提取、分类、结果可视化串成一条能在普通笔记本上跑通的流水线。适合谁?植保专业的学生做课程设计、农业物联网方向的工程师做原型验证、以及需要快速搭一个可解释分类 Demo 的开发者。MATLAB 的好处是矩阵运算和图像处理工具箱成熟,imread、rgb2gray、regionprops这些函数开箱即用,不用在环境配置上耗掉一整天。下面按“资源是什么、怎么跑、坑在哪”的顺序拆开讲。

2. 系统架构与核心模块:从图像输入到分类输出的完整链路

2.1 为什么选 MATLAB 而不是 Python 做这套系统

先说选型理由,不然后面每一步都会有人问“为什么不用 OpenCV”。这套系统的核心操作是图像预处理和特征工程,MATLAB 的图像处理工具箱(Image Processing Toolbox)在以下三件事上比裸写 Python 省事:

第一,颜色空间转换。病虫害识别高度依赖颜色特征,叶片健康区域是绿色,病斑区域偏黄褐或灰白。MATLAB 的rgb2hsv、rgb2lab一行搞定,Python 里用 OpenCV 要记cv2.COLOR_BGR2HSV还得注意 BGR 顺序,新手极易翻车。

第二,形态学操作。病斑分割后往往有噪点和孔洞,bwareaopen、imfill、strel这些函数参数直观,调试时可以直接imshow看中间结果。

第三,特征提取的批量处理。regionprops一次性返回面积、周长、偏心率、欧拉数等几十个属性,配合table类型做特征矩阵非常顺手。

当然边界也要说清楚:如果要做深度学习端到端分类,Python 生态更丰富;这套系统的定位是“传统图像处理 + 机器学习分类器”,适合样本量不大、需要可解释特征的场景。

2.2 五个核心模块的职责划分

整套系统拆成五个模块,每个模块对应一个函数文件,方便单独调试:

模块函数文件输入输出
图像预处理preprocess.m原始 RGB 图像去噪、增强后的图像
病斑分割segment.m预处理图像二值掩膜
特征提取extract_features.m原图 + 掩膜特征向量
分类识别classify_leaf.m特征向量类别标签 + 置信度
结果可视化visualize_result.m原图 + 掩膜 + 标签标注图 + 报告

这种拆法的好处是:分割效果不好时,只调segment.m,不用动分类器;特征区分度不够时,只改extract_features.m。我见过太多人把所有逻辑塞进一个脚本,改一行要重跑十分钟。

2.3 主流程代码与参数说明

主入口脚本main.m的逻辑如下:

% main.m - 病虫害识别系统主入口 clc; clear; close all; % 1. 读取图像(支持 jpg/png/bmp) imgPath = 'sample_leaf.jpg'; rawImg = imread(imgPath); % 2. 预处理:中值滤波去噪 + 限制对比度自适应直方图均衡 procImg = preprocess(rawImg); % 3. 病斑分割:基于 HSV 颜色空间的阈值分割 [mask, maskedImg] = segment(procImg); % 4. 特征提取:颜色矩 + 形状特征 + 纹理特征 features = extract_features(procImg, mask); % 5. 分类识别(加载预训练模型) load('trained_model.mat', 'model'); [label, score] = classify_leaf(features, model); % 6. 结果可视化 visualize_result(rawImg, mask, label, score); fprintf('识别结果:%s,置信度:%.2f%%\n', label, score*100);

逐段说明:preprocess里用medfilt2做 3×3 中值滤波,目的是去掉手机拍摄的椒盐噪声;adapthisteq做自适应直方图均衡,解决田间光照不均的问题。segment里先把图像转到 HSV 空间,因为病斑在 H 通道上和健康绿色区域差异最大,用hsv2rgb反查阈值范围比在 RGB 里硬调三个通道靠谱得多。extract_features返回一个 1×N 的特征向量,N 取决于你启用了哪些特征组,默认配置下是 18 维(6 个颜色矩 + 4 个形状 + 8 个纹理)。

提示:adapthisteq的ClipLimit参数默认 0.01,田间图像建议调到 0.02~0.03,否则暗部病斑会被过度增强成噪点。

3. 图像预处理与病斑分割:参数怎么调、中间结果怎么看

3.1 预处理三步走:去噪、增强、归一化

预处理不是可选项,是必选项。手机拍的叶片图有三个通病:噪点、光照不均、尺寸不一。对应三步处理:

function out = preprocess(img) % 统一尺寸到 512x512,保持长宽比 targetSize = [512, 512]; img = imresize(img, targetSize); % 中值滤波去噪,窗口大小 3x3 if size(img, 3) == 3 img = medfilt3(img, [3 3 1]); else img = medfilt2(img, [3 3]); end % 转灰度做光照归一化,再映射回 RGB gray = rgb2gray(img); grayEq = adapthisteq(gray, 'ClipLimit', 0.02, 'NumTiles', [8 8]); % 用均衡后的灰度图引导 RGB 通道调整 ratio = double(grayEq) ./ double(gray + eps); out = uint8(min(double(img) .* ratio, 255)); end

medfilt3的窗口[3 3 1]表示只在空间维度滤波,不跨通道,避免颜色串扰。adapthisteq的NumTiles设为[8 8],意思是把图像分成 8×8 块分别做直方图均衡,块太小会放大噪声,块太大等于全局均衡,8×8 是 512×512 图像的经验值。最后那个ratio映射是血泪经验:直接对 RGB 三个通道分别做均衡会导致颜色偏移,用灰度图的均衡比例去引导 RGB 调整,颜色保真度好很多。

3.2 HSV 阈值分割:病斑提取的关键参数

病斑分割的核心思路是:健康叶片在 HSV 空间的 H 分量集中在 0.25~0.45(绿色),病斑区域 H 分量偏低(黄褐)或饱和度骤降(灰白)。所以分割逻辑是“排除法”——先找到健康绿色区域,取反得到病斑候选区。

function [mask, maskedImg] = segment(img) hsv = rgb2hsv(img); H = hsv(:,:,1); S = hsv(:,:,2); V = hsv(:,:,3); % 健康绿色区域:H 在 [0.20, 0.45],S > 0.15,V > 0.15 healthy = (H >= 0.20) & (H <= 0.45) & (S > 0.15) & (V > 0.15); % 病斑 = 非健康区域,但排除背景(背景通常 S 很低或 V 很低) background = (S < 0.08) | (V < 0.10); lesion = ~healthy & ~background; % 形态学清理:去掉小于 50 像素的噪点,填充孔洞 lesion = bwareaopen(lesion, 50); lesion = imfill(lesion, 'holes'); % 只保留最大连通区域(假设一张图一个主要病斑) lesion = bwareafilt(lesion, 1); mask = lesion; maskedImg = img; maskedImg(repmat(~mask, [1 1 3])) = 0; end

参数逐个说:S > 0.15是饱和度下限,低于这个值说明颜色发灰,可能是病斑也可能是反光,需要结合 V 判断。V > 0.15排除阴影区域。bwareaopen的 50 是面积阈值,图像越小这个值要越小,512×512 下 50 大约对应直径 8 像素的噪点。bwareafilt(lesion, 1)只保留最大连通域,适合单叶片单病斑的场景;如果一张图有多个病斑,改成bwareafilt(lesion, 5)保留前 5 个。

注意:如果叶片本身偏黄(比如缺氮),H 分量会落在 0.15 附近,被误判为病斑。这种情况需要把健康 H 下限从 0.20 降到 0.12,或者改用 Lab 空间的 a 分量做辅助判断。

3.3 中间结果可视化:别等最后才发现分割错了

调试分割参数时,最有效的办法是把中间结果拼成一张图看:

figure('Name', '分割中间结果'); subplot(2,3,1); imshow(rawImg); title('原图'); subplot(2,3,2); imshow(procImg); title('预处理'); subplot(2,3,3); imshow(hsv(:,:,1), []); title('H 通道'); colormap(gca, 'hsv'); subplot(2,3,4); imshow(healthy); title('健康区域'); subplot(2,3,5); imshow(lesion); title('病斑掩膜'); subplot(2,3,6); imshow(maskedImg); title('病斑提取');

这张图能一眼看出问题:如果 H 通道图里病斑和健康区域灰度值接近,说明颜色空间选错了;如果健康区域掩膜把背景也包进去了,说明 S 和 V 的阈值太松。我一般会先把这张图调满意,再去跑分类。

4. 特征提取与分类器训练:18 维特征向量怎么来的

4.1 颜色矩、形状特征、纹理特征的提取代码

特征提取是整个系统里最影响准确率的环节。18 维特征的构成如下:

function feat = extract_features(img, mask) feat = []; % --- 颜色矩:对病斑区域计算 R/G/B 三通道的一阶矩和二阶矩 --- lesionPixels = img(repmat(mask, [1 1 3])); lesionPixels = reshape(lesionPixels, [], 3); for c = 1:3 channel = double(lesionPixels(:, c)); feat = [feat, mean(channel), std(channel)]; % 6 维 end % --- 形状特征:面积、周长、偏心率、欧拉数 --- props = regionprops(mask, 'Area', 'Perimeter', 'Eccentricity', 'EulerNumber'); if isempty(props) feat = [feat, zeros(1, 4)]; else feat = [feat, props(1).Area, props(1).Perimeter, ... props(1).Eccentricity, props(1).EulerNumber]; % 4 维 end % --- 纹理特征:灰度共生矩阵的 4 个统计量 × 2 个方向 --- gray = rgb2gray(img); gray(mask == 0) = 0; offsets = [0 1; 1 0]; % 水平、垂直两个方向 for k = 1:size(offsets, 1) glcm = graycomatrix(gray, 'Offset', offsets(k,:), ... 'NumLevels', 16, 'Symmetric', true); stats = graycoprops(glcm, {'Contrast', 'Correlation', ... 'Energy', 'Homogeneity'}); feat = [feat, stats.Contrast, stats.Correlation, ... stats.Energy, stats.Homogeneity]; % 8 维 end end

颜色矩用均值和标准差,是因为病斑的颜色分布比健康区域更“散”,标准差能捕捉这种离散度。形状特征里Eccentricity对条状病斑(如细菌性条斑病)区分度高,EulerNumber反映孔洞数量,对坏死型病斑有用。纹理特征用graycomatrix时NumLevels设为 16 而不是默认的 8,是为了保留更多灰度细节,但计算量会翻倍,512×512 图像下大约多花 0.3 秒。

4.2 分类器选型:SVM 还是随机森林

特征向量只有 18 维,样本量通常几百到几千,这个规模下 SVM 和随机森林都能用。我的建议是:

  • 样本量 < 500:用 SVM,核函数选 RBF,BoxConstraint设 1,KernelScale设'auto'。MATLAB 的fitcsvm会自动做标准化。
  • 样本量 500~5000:用随机森林,TreeBagger的NumTrees设 100,MinLeafSize设 5。随机森林对特征尺度不敏感,不用额外标准化。
  • 样本量 > 5000:考虑用集成方法fitcensemble的 AdaBoost 或 Bagging。

训练代码示例:

% 假设 X 是 N×18 特征矩阵,Y 是 N×1 类别标签 load('features_dataset.mat', 'X', 'Y'); % 划分训练集和测试集,7:3 cv = cvpartition(Y, 'HoldOut', 0.3); XTrain = X(training(cv), :); YTrain = Y(training(cv), :); XTest = X(test(cv), :); YTest = Y(test(cv), :); % 训练 SVM 分类器 t = templateSVM('KernelFunction', 'rbf', 'BoxConstraint', 1); model = fitcecoc(XTrain, YTrain, 'Learners', t); % 测试集评估 YPred = predict(model, XTest); acc = sum(YPred == YTest) / numel(YTest); fprintf('测试集准确率:%.2f%%\n', acc*100); % 保存模型 save('trained_model.mat', 'model');

fitcecoc是 MATLAB 做多分类 SVM 的标准函数,内部用一对多策略。BoxConstraint控制惩罚力度,值越大越容易过拟合,1 是保守起点。如果准确率上不去,先别急着调分类器,回头检查特征提取——我遇到过特征里混入了 NaN 导致 SVM 直接报错的情况,用sum(isnan(X))一查就露馅。

4.3 交叉验证与混淆矩阵:别只看准确率

准确率在类别不平衡时会骗人。比如健康样本 800 张、病斑样本 200 张,全猜健康也有 80% 准确率。必须看混淆矩阵:

% 5 折交叉验证 cvModel = crossval(model, 'KFold', 5); loss = kfoldLoss(cvModel); fprintf('5 折交叉验证损失:%.4f\n', loss); % 混淆矩阵 figure; confusionchart(YTest, YPred); title('测试集混淆矩阵');

混淆矩阵里如果某一类的召回率明显低,说明该类特征区分度不够。常见做法是给该类增加特征维度,比如病斑颜色偏灰的类别,加一个饱和度均值特征。

5. 避坑与排查:五个让系统从 92% 掉到 60% 的坑

5.1 坑一:训练集和测试集来自同一批图像

现象:交叉验证准确率 95%,换一批新图直接掉到 60%。

原因:同一片叶子拍了多张图,有的进了训练集有的进了测试集,模型实际上在“背图”而不是学特征。

解决:按叶片个体划分数据集,同一片叶子的所有照片只能出现在训练集或测试集之一。用unique按文件名前缀分组再划分。

5.2 坑二:HSV 阈值在阴天和晴天之间不通用

现象:晴天拍的图分割正常,阴天拍的图病斑全丢。

原因:阴天光照色温偏冷,H 分量整体偏移,固定阈值失效。

解决:在segment.m里加一个白平衡预处理,用illumwhite或简单的灰度世界算法校正色温,再做 HSV 分割。

5.3 坑三:imresize默认双线性插值导致小病斑消失

现象:原图能看到的小病斑,缩放到 512×512 后掩膜里没了。

原因:imresize默认'bilinear'会平滑掉小于 2 像素的病斑。

解决:改用'nearest'插值,或者先分割再缩放掩膜。我一般是在原图尺寸下做分割,最后把掩膜缩放到统一尺寸。

5.4 坑四:graycomatrix的NumLevels和图像位深不匹配

现象:纹理特征全是 0 或 NaN。

原因:图像是 uint8(256 级),但NumLevels设了 64,量化时把大部分像素归到同一级,对比度算出来是 0。

解决:NumLevels不要超过 32,或者先把图像量化到指定级数再算 GLCM。graycomatrix的NumLevels和GrayLimits要配合使用。

5.5 坑五:分类器保存后加载,特征顺序不一致

现象:训练时准确率 90%,加载模型预测新图结果全错。

原因:extract_features函数改过,特征维度顺序变了,但模型还是旧顺序训练的。

解决:在模型文件里同时保存特征名称列表,预测前用isequal校验。或者给特征提取函数加版本号,模型加载时检查版本。

6. 进阶技巧:用迁移学习特征替换手工特征

手工设计的 18 维特征在简单场景够用,但遇到多种病害混发、病斑形态接近的情况,区分度就到头了。这时候可以用预训练的 CNN 做特征提取器,把 18 维手工特征换成 4096 维深度特征,再接 SVM 分类。

MATLAB 里用activations函数提取特征:

% 加载预训练网络(需要 Deep Learning Toolbox) net = resnet18; % 或 squeezenet,更轻量 inputSize = net.Layers(1).InputSize(1:2); % 对每张图提取全连接层特征 function feat = deep_features(imgPath, net, inputSize) img = imread(imgPath); img = imresize(img, inputSize); img = augmentedImageDatastore(inputSize, img); feat = activations(net, img, 'fc1000', 'OutputAs', 'rows'); end

resnet18的fc1000层输出 1000 维,squeezenet的pool10层输出 1000 维,都比手工特征丰富得多。但要注意:深度特征需要更多样本才能发挥优势,样本少于 300 张时可能反而不如手工特征稳定。我一般会先用手工特征跑一版基线,再换深度特征对比,哪个好用哪个。

另一个实用技巧是数据增强。MATLAB 的imageDataAugmenter支持随机旋转、平移、缩放、翻转:

aug = imageDataAugmenter('RandRotation', [-20 20], ... 'RandXTranslation', [-10 10], ... 'RandYTranslation', [-10 10], ... 'RandXScale', [0.9 1.1], ... 'RandYScale', [0.9 1.1]);

RandRotation范围不要超过 ±30 度,否则叶片形态失真;平移范围控制在图像宽高的 5% 以内。增强后的样本量翻 5~10 倍比较合理,再多收益递减。

从那以后我每次搭识别系统,都强制先跑一遍“最差样本测试”——把手里最糊、最偏、光照最烂的十张图单独拎出来跑,这十张过了,再谈准确率。希望帮到你。

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

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

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

立即咨询