简介:这是一套面向农业信息化与图像处理学习者的病虫害识别系统实践项目,基于MATLAB实现,通过叶片图像自动判别植物病虫害程度,帮助农业工作者快速诊断作物健康状况。资源包共95个文件,以84张jpg叶片样本图片、10个m脚本和1个mat数据文件为主,样本按正常、轻微、中等、严重等灾害等级分目录组织,脚本覆盖训练、颜色特征匹配、HSV量化与图像预处理等环节,压缩包约7.62MB。已有4912人学习下载,适合图像处理入门者与机器学习实践者参考。读者可从中获得一套完整的识别流程范例,涵盖图像预处理、特征提取、分类器训练与测试评估等关键步骤,并借助分级样本与颜色空间转换代码理解纹理、色彩特征在病害判别中的作用,为课程设计、毕业项目或农业智能应用开发提供可复用的代码框架与调试素材。
1. 田里拍张照,MATLAB 里跑一遍:这套病虫害识别系统到底解决什么问题
去年夏天帮一个做植保的朋友看代码,他手机里存了三百多张叶片照片,分类全靠人眼一张张翻,翻到后面自己都记混了哪张是早期斑点、哪张是虫害咬痕。这类场景其实特别适合交给一套图像识别流程去跑:拍照、预处理、分割病斑、提特征、分类输出。这份病虫害识别系统源码就是围绕这条链路搭的,技术栈压在 MATLAB 上,核心是图像处理加特征分类,不依赖深度学习框架,也不需要配 GPU 环境。适合两类人:一类是农业信息化方向的学生或开发者,想拿一个能跑通的完整案例改自己的课题;另一类是手里有叶片图像数据、想快速验证识别思路的工程人员。它不追求 SOTA 精度,胜在链路完整、每一步都能拆开看、改一个参数就能观察结果变化,这对二次开发来说比黑匣子模型友好得多。
2. 系统架构与目录结构:一条从图像输入到分类输出的完整链路
2.1 整体处理流程是怎么串起来的
这套系统的骨架是一条典型的传统图像识别流水线,没有神经网络那一层,所有环节都在 MATLAB 的图像处理工具箱里完成。整条链路可以拆成五段:图像采集与读入、预处理、病斑分割、特征提取、分类判定。每一段对应一个函数文件或脚本段落,段与段之间靠图像矩阵和特征向量传递数据,耦合度不高,这也是它好改的原因。
先看数据流。原始图像读进来是三维 uint8 矩阵,预处理阶段做灰度化、去噪、尺寸归一化,输出仍然是图像矩阵;分割阶段用颜色空间阈值或边缘检测把病斑区域抠出来,得到二值掩膜;特征提取阶段在掩膜约束下计算颜色矩、纹理特征和形状参数,拼成一个一维特征向量;最后分类器拿这个向量和训练阶段存下来的特征库比对,输出类别标签和置信度。整条链路里,分割是最容易翻车的一环,因为田间光照不均、叶片背景杂乱,阈值稍微偏一点,病斑就被吞掉或者把叶脉也算进去。
从工程角度看,这种分段式设计的好处是每一段都能单独调试。你可以在分割后直接把掩膜叠回原图看一眼,确认抠得对不对,再往下走特征提取。深度学习方案里这一步往往要靠可视化工具去猜,传统流程反而更透明。
2.2 目录结构长什么样
源码包的组织方式大致如下,不同版本可能略有出入,但主干模块是稳定的:
病虫害识别系统/ ├── main.m # 主入口,串起训练与识别流程 ├── data/ │ ├── train/ # 训练集,按类别分子目录 │ └── test/ # 测试集,结构同上 ├── preprocess/ │ ├── img_resize.m # 尺寸归一化 │ └── img_denoise.m # 去噪与增强 ├── segmentation/ │ ├── seg_hsv.m # HSV 颜色空间阈值分割 │ └── seg_edge.m # 边缘检测辅助分割 ├── features/ │ ├── color_moment.m # 颜色矩特征 │ ├── texture_glcm.m # 灰度共生矩阵纹理特征 │ └── shape_feature.m # 形状参数 ├── classifier/ │ ├── train_model.m # 训练分类器 │ └── predict_label.m # 单张预测 └── utils/ └── load_dataset.m # 数据集遍历加载这个结构里,main.m是唯一需要你手动改路径的地方,其余模块都是被调用的函数。data/train下按类别建子目录,比如「健康」「叶斑病」「锈病」「虫害」各一个文件夹,load_dataset.m靠目录名自动打标签,省去手写标签文件的麻烦。分类器模块默认用 SVM,MATLAB 自带fitcecoc就能跑多分类,不需要额外工具箱。
提示:如果你的 MATLAB 版本较老,
fitcecoc可能不可用,换成svmtrain加一对一投票也能实现,只是接口要改几行。
2.3 技术选型为什么落在 MATLAB 上
很多人第一反应是「为什么不用 Python 加 OpenCV」。这个问题得从使用场景看。这套系统的目标用户里有不少是农口院校的学生,他们的编程环境就是 MATLAB,课程作业、实验报告、课题验证都在这个环境里完成。MATLAB 的图像处理工具箱把灰度化、滤波、形态学操作、区域属性统计都封装成了单行函数,写起来比 OpenCV 少很多样板代码。比如求连通域面积,MATLAB 一句regionprops就出来了,Python 里要绕一圈。
另一个原因是调试体验。MATLAB 的变量工作区可以直接双击看图像矩阵,imshow和figure随手就能弹窗,分割效果不对时改个阈值立刻重跑,这种即时反馈对调参很关键。当然代价是部署不方便,MATLAB 代码要转 C 或者打包成独立应用才能脱离环境运行,这一点在二次开发时要提前想清楚。如果最终目标是嵌入式设备或者 Web 服务,建议把算法逻辑验证完之后再考虑移植,别一上来就纠结部署形态。
3. 核心模块拆解:预处理、分割与特征提取的代码实现
3.1 预处理:尺寸归一化和去噪为什么不能省
预处理看着简单,但它决定了后面所有环节的输入质量。尺寸归一化是为了让特征向量长度一致,分类器才能批量处理;去噪是为了压掉田间拍摄带进来的传感器噪声和灰尘颗粒,否则分割阶段会把噪点当成病斑。下面这段是预处理模块的典型写法:
function img_out = preprocess_image(img_path, target_size) % 读入图像,统一转成 RGB 三通道 img = imread(img_path); if size(img, 3) == 1 img = cat(3, img, img, img); end % 尺寸归一化,保持长宽比的前提下缩放到目标尺寸 img = imresize(img, target_size); % 转灰度后做中值滤波,抑制椒盐噪声 gray = rgb2gray(img); gray_denoised = medfilt2(gray, [3 3]); % 用去噪后的灰度图做引导,对彩色图做双边滤波 img_out = imbilatfilt(img); % 把去噪灰度图存回结构体,供后续分割复用 img_out_gray = gray_denoised; end这段代码里几个参数值得说清楚。target_size一般设成[256 256],太大增加计算量,太小丢失病斑细节,256 是精度和速度的折中点。medfilt2的窗口[3 3]适合椒盐噪声,如果图像噪声偏高斯型,换成imgaussfilt更合适。imbilatfilt是双边滤波,能在保边的同时平滑区域内部,对叶片纹理保留有帮助,但它的计算量比普通均值滤波大,批量处理几百张图时能感觉到延迟。
这里有个容易忽略的点:灰度图去噪之后又做了彩色双边滤波,两者结果没有合并。实际使用时,分割阶段如果走 HSV 颜色阈值,用的是彩色图;如果走边缘检测,用的是灰度图。所以函数返回时最好把两路结果都带出去,别只返回一个。我一般会把输出改成结构体,字段分别是rgb和gray,后面哪个模块要用哪个直接取,避免重复计算。
3.2 病斑分割:HSV 阈值法的参数怎么定
分割是整个系统里最需要手工调的部分。健康叶片是绿色,病斑通常偏黄、偏褐或者带灰白,在 HSV 空间里色相 H 的差异比 RGB 空间明显,所以用 H 通道做阈值更稳。下面是分割模块的核心逻辑:
function mask = segment_lesion(img_rgb, h_range, s_min, v_min) % 转 HSV 空间 hsv = rgb2hsv(img_rgb); H = hsv(:, :, 1); S = hsv(:, :, 2); V = hsv(:, :, 3); % 色相落在病斑区间,且饱和度和亮度不能太低 mask = (H >= h_range(1)) & (H <= h_range(2)) & ... (S >= s_min) & (V >= v_min); % 形态学闭运算填补病斑内部空洞 mask = imclose(mask, strel('disk', 3)); % 去掉面积过小的连通域,压掉噪点误判 mask = bwareaopen(mask, 50); endh_range是色相区间,MATLAB 里 H 的取值范围是 0 到 1,不是 0 到 360,这一点新手经常搞错。健康绿色大约在 0.25 到 0.45,黄褐色病斑大致落在 0.05 到 0.15 之间,具体值要拿你的样本图在colorThresholder工具里拖一遍才准。s_min控制饱和度下限,设太低会把灰白背景算进来,设太高会漏掉褪色病斑,一般从 0.2 起调。v_min压掉暗区,避免阴影被误判。
imclose的圆盘半径 3 是经验值,病斑内部如果有小孔,闭运算能填上;但如果病斑本身很碎,半径太大会把相邻病斑粘成一片,反而影响形状特征。bwareaopen的 50 是面积阈值,单位是像素,按 256×256 的图来算,50 像素大约对应直径 8 像素的小点,低于这个的基本是噪声。
注意:HSV 阈值法对光照很敏感。同一片叶子在晴天和阴天拍,H 值可能差出 0.05,建议在预处理阶段加一步白平衡或者灰度世界假设校正,把整体色偏拉回来,否则一套阈值换个数据集就失效。
3.3 特征提取:颜色矩、纹理和形状三路特征怎么拼
特征提取阶段要把分割出来的病斑区域转成分类器能吃的数值向量。这套系统用了三路特征:颜色矩描述病斑颜色分布,灰度共生矩阵描述纹理粗糙度,形状参数描述病斑轮廓。三路拼起来大概二十多维,对 SVM 来说够用,维度再高就容易过拟合。
颜色矩的写法很直接,一阶矩是均值,二阶矩是标准差,三阶矩是偏度:
function feat = color_moment(img_rgb, mask) % 只统计掩膜内的像素 pixels = img_rgb(repmat(mask, [1 1 3])); pixels = reshape(pixels, [], 3); pixels = double(pixels) / 255; feat = []; for c = 1:3 ch = pixels(:, c); mu = mean(ch); sigma = std(ch); skew = mean((ch - mu).^3) / (sigma^3 + eps); feat = [feat, mu, sigma, skew]; end endrepmat(mask, [1 1 3])把二值掩膜扩到三通道,再用逻辑索引取出病斑内的像素,这样背景不参与统计。除以 255 是做归一化,让三阶矩的数值不至于爆掉。eps是防止标准差为零时除零,虽然实际图像里很少出现,但加上更稳。
纹理特征用灰度共生矩阵,MATLAB 里graycomatrix加graycoprops两行就能出对比度、相关性、能量、同质性四个量。形状特征用regionprops取面积、周长、圆度、长宽比。这三路特征拼成一个行向量返回,训练阶段把所有样本的特征堆成矩阵,行是样本、列是特征,直接喂给fitcecoc。
这里有个实操细节:不同特征的量纲差别很大,面积可能是几千,颜色矩在 0 到 1 之间,直接拼在一起会让 SVM 的核函数被大面积特征主导。常见做法是在训练前对特征矩阵做 z-score 标准化,MATLAB 里normalize函数一行搞定,但要注意训练集和测试集必须用同一组均值和标准差,别各算各的。
4. 跑起来:从数据准备到单张预测的完整操作
4.1 数据集怎么组织才能被自动加载
这套系统对数据集的唯一要求是「按类别分目录」。data/train下面每个子目录名就是类别标签,load_dataset.m遍历子目录、读图、调预处理、提特征,最后返回特征矩阵和标签向量。目录名建议用英文或者拼音,避免中文路径在某些 MATLAB 版本下读取出问题。
每个类别至少准备 30 到 50 张图,太少的话 SVM 训练出来的超平面不稳,交叉验证准确率波动会很大。图像来源可以是公开的植物叶片数据集,也可以自己拍,但训练集和测试集要来自不同批次,别把同一片叶子拍十张分到两边,那样准确率虚高,实际用起来就露馅。
load_dataset.m的典型实现是两层循环:外层遍历类别目录,内层遍历该目录下的图像文件。每读一张就调一次预处理和特征提取,把结果追加到矩阵末尾。这个过程在几百张图上跑起来大概几十秒,如果图多到上千张,建议加个进度条或者分批存中间结果,不然中途出错全白跑。
4.2 训练与预测的两段式调用
训练脚本的核心就是三行:加载特征、训练分类器、保存模型。预测脚本则是加载模型、提特征、调predict。下面给出训练部分的骨架:
% 加载训练集特征与标签 [XTrain, YTrain] = load_dataset('data/train', [256 256]); % 特征标准化,保存均值方差供预测时复用 mu = mean(XTrain); sigma = std(XTrain); XTrain = (XTrain - mu) ./ (sigma + eps); % 训练多分类 SVM,核函数用 RBF model = fitcecoc(XTrain, YTrain, 'Learners', ... templateSVM('KernelFunction', 'rbf', 'Standardize', false)); % 保存模型和标准化参数 save('model.mat', 'model', 'mu', 'sigma');fitcecoc是 MATLAB 的多分类封装,内部用一对一策略把二分类 SVM 组合起来。templateSVM里Standardize设成false是因为前面已经手动标准化了,重复标准化会让数值范围被压两次。RBF 核的BoxConstraint和KernelScale可以用默认值先跑,准确率不满意再用bayesopt调,但别一上来就调参,先把数据质量和分割效果确认好。
预测阶段要注意标准化参数必须和训练时一致,所以model.mat里把mu和sigma一起存了。单张预测时先提特征,再用同一组参数标准化,最后predict(model, feat)拿标签。如果想看置信度,用predict的第二个返回值,不过多分类 SVM 的置信度是投票比例,不是概率,解读时别当成概率用。
4.3 一次完整运行的命令顺序
把流程串起来,实际执行顺序是这样的:先确认data/train和data/test目录结构正确,然后在 MATLAB 命令窗口切到项目根目录,运行main.m。main.m内部会依次调训练和测试,最后在命令窗口打印混淆矩阵和准确率。如果你想单独跑预测,先确保model.mat已经生成,再调predict_label.m并传入图像路径。
运行过程中最常见的报错是路径找不到,多半是main.m里的数据目录写成了绝对路径,换台机器就失效。建议全部改成相对路径,用fullfile拼接,跨平台也稳。另一个常见问题是图像格式不统一,混了 jpg 和 png,imread都能读,但透明通道处理不一样,png 带 alpha 通道时size(img, 3)会是 4,预处理里要加一步判断,把 alpha 通道丢掉再转三通道。
5. 避坑与排查:五个真实会卡住你的问题
5.1 准确率虚高,换个数据集就崩
现象是训练集和测试集准确率都在 95% 以上,但拿新拍的图一测,错得离谱。原因通常是数据泄漏:同一片叶子在不同光照下拍的多张图被分到了训练集和测试集两边,模型记住了叶片本身的纹理而不是病斑特征。解决办法是按「叶片」而不是按「图像」划分数据集,同一片叶子的所有照片只能出现在一边。另外检查一下特征标准化是不是在划分之前就做了,如果是,测试集的统计信息泄漏进了训练过程,也会虚高。
5.2 分割掩膜全是噪点或者全空
现象是imshow(mask)出来一片白或者一片黑。全白多半是 HSV 阈值区间设得太宽,把背景也算进去了;全空则是区间太窄或者饱和度下限太高。排查时先把原图转 HSV,用colorThresholder工具手动拖一遍,看病斑像素落在哪个区间,再把数值抄回代码。如果换了数据集就失效,说明光照差异大,回到预处理加白平衡校正,别在分割阶段硬调阈值。
5.3 特征矩阵出现 NaN 导致训练报错
现象是fitcecoc报「输入包含 NaN」。来源通常是颜色矩的三阶矩在标准差为零时除了零,虽然加了eps,但如果某个通道在掩膜内全是同一个值,sigma接近零,偏度还是会炸。另一个来源是regionprops在掩膜为空时返回空结构体,后续取值拿到 NaN。解决办法是在特征提取函数入口加判断,掩膜面积为零时直接返回全零向量并打警告,别让坏样本流进训练集。
5.4 批量处理跑到一半内存不够
现象是处理到第几百张图时 MATLAB 报内存不足。原因是load_dataset把每张图的中间结果都留在工作区,图像矩阵虽然不大,但几百张三维矩阵堆起来也占地方。解决办法是在循环内及时clear临时变量,特征提取完只保留特征向量,图像矩阵用完就释放。如果数据量确实大,改成每处理完一个类别就存一次中间结果到磁盘,最后再合并。
5.5 换 MATLAB 版本后函数找不到
现象是旧版本能跑的代码,新版本报某个函数未定义。常见的是imbilatfilt在较早版本里没有,或者fitcecoc的某些参数名变了。排查时先看报错函数属于哪个工具箱,用ver命令确认工具箱是否安装。替代方案是降级用imfilter加高斯核近似双边滤波,分类器换成fitcsvm加手动一对一循环。版本兼容问题没有银弹,最稳的办法是在项目 README 里写清楚验证过的版本范围,别让后来的人猜。
6. 二次开发:把分类器换掉、把流程拆开、把验证做扎实
拿到这套源码之后,最值得动的其实是分类器那一层。传统特征加 SVM 的组合胜在可解释,但精度上限摆在那里。如果你手里有几千张标注图,可以把特征提取模块的输出接给一个轻量级网络,MATLAB 的 Deep Learning Toolbox 支持直接读特征矩阵训练全连接网络,不用改前面的分割和特征代码,等于用最小改动换一次精度提升。具体做法是把train_model.m里的fitcecoc换成fitcnet,输入还是那个标准化后的特征矩阵,隐藏层设两层、每层 64 个节点先跑基线,过拟合了再加 dropout。
第二个改造方向是把分割和分类解耦成两个可独立替换的模块。现在main.m是硬编码串流程,改一处要动整个脚本。我一般会定义一个配置结构体,把阈值、目标尺寸、分类器类型都放进去,各模块从配置里读参数,这样换数据集时只改配置不改代码。配置可以存成config.mat或者 JSON,后者跨语言更方便,将来移植到别的环境时参数能直接搬。
验证方法上,别只看一个总体准确率。混淆矩阵要逐类看,病斑类别之间的误判往往集中在某两类上,比如早期叶斑和虫害咬痕在颜色上接近,光看总数发现不了。建议加一个按类别统计的召回率和精确率表格,哪类低就回去看那类的分割掩膜,多半是阈值把关键区域切掉了。交叉验证用 5 折就够,折数太多在小数据集上反而方差大。
| 改造点 | 改动位置 | 预期收益 | 风险 |
|---|---|---|---|
| 分类器换全连接网络 | classifier/train_model.m | 精度提升 5 到 10 个百分点 | 需要更多标注数据 |
| 参数配置化 | 新增config.m,各模块读取 | 换数据集不改代码 | 初期重构工作量 |
| 分割加白平衡 | preprocess/新增函数 | 跨光照稳定性提升 | 增加单张处理耗时 |
| 按类别统计指标 | main.m输出段 | 定位薄弱类别 | 无 |
最后说个习惯。我早期改这类系统时,总喜欢一次性把分割阈值、特征组合、分类器参数全调一遍,结果准确率涨了也不知道是哪一步的功劳,跌了更不知道回滚哪里。后来强制自己每改一个模块就跑一次完整验证,把准确率和混淆矩阵记在表格里,一次只动一个变量。这套流程看着慢,但省下来的排查时间远超那点等待。希望帮到你。
本文还有配套的精品资源,点击获取