
简介基于MATLAB实现的水果自动识别程序面向图像处理初学者、高校学生以及农业分拣相关开发者解决不同种类水果的快速分类问题。压缩包共11个文件包含5张水果样本图片青椒、西红柿、香蕉、梨等、4个M脚本、1个fig界面文件和1个说明文档整体仅77KB轻量易用。代码完整覆盖图像预处理、特征提取和分类器训练测试环节如灰度化、二值化、边缘检测、颜色直方图、LBP特征以及SVM/随机森林分类核心脚本shuiguoshibie串联流程RGB2bw.m等函数便于模块化复用。同时附有运行指南说明帮助快速在MATLAB中配置环境并复现识别效果也可替换数据集迁移到其他果蔬分类任务。该资源已有2673人学习适合希望通过实例掌握MATLAB图像识别与机器学习流程的读者。1. 项目思路拆解为什么选MATLAB做水果识别第一次被问到能不能用MATLAB做一个水果识别程序时我下意识觉得这又是一个典型的“课程设计”题目。但真做下来才发现它把数字图像处理里最核心的几块内容——图像分割、特征提取、模式识别——全串起来了当成入门计算机视觉的第一个完整项目来练手其实非常合适。先说清楚这个程序能干什么。你给它一张水果图片它能判断出图片里是什么水果比如苹果、香蕉、橙子、梨这些常见种类。看起来简单但背后涉及一套完整的图像处理流程先把水果从背景里抠出来再提取能代表这类水果的特征最后交给分类器做判断。这个流程放大了看和工业质检、农产品分拣线上用的视觉系统原理一模一样只是规模小得多。为什么用MATLAB而不是Python我个人的看法是MATLAB在图像处理这个场景下有三个不可替代的优势。第一它的矩阵思维和图像数据的存储方式天然契合读进来一张图就是一个三维数组做像素级操作非常直观不像Python要反复考虑numpy的维度问题。第二工具箱太全了Image Processing Toolbox里几十个现成函数imread、imshow、rgb2hsv这些基础操作几乎不用查文档极大缩短了开发周期。第三调试体验好变量区直接双击就能看数据长什么样图像每一步处理结果可以即时可视化和对比这对理解算法到底发生了什么特别有帮助。当然如果你是做深度学习方向想上CNN做端到端识别那MATLAB的Deep Learning Toolbox也能用但从开发效率和生态丰富度来说不如Python顺手。这个项目我用的是传统图像处理和机器学习路线这也是目前MATLAB做水果识别最主流、最稳定的方案——训练速度快逻辑透明每个环节都能控制。2. 图像预处理与背景分割这一步直接决定识别上限2.1 读图与颜色空间转换的逻辑拿到图像的第一步不是急着识别而是把“水果”和“背景”分开。这一步做不好后面所有特征提取都是在垃圾数据上做分析识别率天花板就被锁死了。读取图像用imread显示用imshow这个没什么好说的。关键在颜色空间的选择。MATLAB默认读进来的是RGB图像但RGB空间有三个致命问题三个通道之间相关性很高光照变化会让三个分量同时大幅波动而且RGB并不能很好地表达“颜色”这个人类感知概念。所以实际做颜色分析时我一般会转到HSV空间。HSV把颜色拆成色调Hue、饱和度Saturation、明度Value三个独立维度。色调分量基本不受光照强度影响——这太重要了因为实际拍摄时角度、光源、阴影都会造成明暗变化如果直接用RGB分量做阈值分割同一个苹果在亮处和暗处的像素值可能差好几倍。而不管光照怎么变苹果的“红”在色调轴上始终落在0到30度这个区间附近。这就是用HSV做颜色分割的核心优势。MATLAB里rgb2hsv一行代码就完成了转换返回的还是和原图同尺寸的三通道矩阵H、S、V各占一个通道。拿到H通道后就可以开始设计分割规则了。2.2 基于颜色阈值的分割方案及代码实现分割的思路是每种水果都有相对稳定的色调区间。比如橙子的主色调在HSV的H通道大约在10到25度香蕉在25到45度附近苹果的红色在0到10度青色苹果则在60到90度。但只用H通道还不够因为背景里可能有颜色相近的区域。比如拍摄时桌面是木色的那它的色调和香蕉就很容易混淆。这时候需要把S通道和V通道也纳入判断。S通道低说明颜色发灰、不鲜艳V通道极端低说明是黑色区域这些都不可能是我们要的水果。所以完整的分割条件是H、S、V三个条件同时满足才算目标区域。下面是我实际在用的分割代码骨架直接在MATLAB命令行或脚本里跑就行img imread(apple.jpg); hsvImg rgb2hsv(img); H hsvImg(:,:,1); S hsvImg(:,:,2); V hsvImg(:,:,3); % 苹果红色区域H在0~0.08对应0~30度S0.3V0.15 mask (H 0 H 0.08) (S 0.3) (V 0.15); % 形态学清理开运算去噪闭运算填洞 mask imopen(mask, strel(disk, 5)); mask imclose(mask, strel(disk, 10)); figure; subplot(1,3,1); imshow(img); title(原图); subplot(1,3,2); imshow(mask); title(分割掩码); subplot(1,3,3); imshow(bsxfun(times, img, cast(mask,like,img))); title(抠图结果);这里strel(disk, radius)是创建圆形结构元素半径选5到10之间比较合适。开运算可以去掉背景里零散的噪点闭运算能填补水果表面反光造成的孔洞。其实反光问题是水果识别的老大难苹果、橙子这类表面光滑的水果特别容易出现高光点高光区域在HSV里表现为S值很低、V值很高如果不做闭运算这些点会变成分割掩码上的黑洞直接影响后续面积、周长等形状特征的计算精度。提示如果目标水果和背景颜色接近单纯的颜色阈值就失效了。这时可以试试K-means聚类分割把像素按颜色聚成2到3类取包含图像中心点的那一类作为前景。MATLAB里用imsegkmeans(img, k)一句就能搞定。这个函数在工具箱里不需要额外装东西。3. 特征提取的设计颜色、形状、纹理缺一不可分割完成之后我们就得到了一张只含水果区域的二值掩码以及一张被抠出来的彩色水果图。接下来的核心问题是用什么特征来“描述”这个水果让计算机能够区分不同种类3.1 颜色特征均值、方差、偏度一起上颜色特征是最直观的。但注意不能用所有像素的平均颜色因为分割后的区域可能还有一些边缘毛刺、反光残留均值会被这些噪点污染一点。更稳的做法是只取掩码内的像素做统计这是用掩码的一个额外好处。每个颜色通道R、G、B或者H、S、V计算三个统计量均值反映整体色调、标准差反映颜色分布的集中程度、偏度反映分布是否对称。这样三个通道一共9维特征。偏度这个指标很多新手会忽略但它在区分青苹果和红苹果时很管用——红苹果的红色像素在低色调区集中偏度较高青苹果的绿色分布更均匀偏度自然就低。核心代码长这样% 用掩码提取目标区域像素 mask double(mask); R img(:,:,1) .* mask; G img(:,:,2) .* mask; B img(:,:,3) .* mask; % 找出掩码内的像素索引 idx find(mask 0); if isempty(idx) error(分割结果为空请检查阈值参数); end % 计算颜色矩 feature []; for channel {R, G, B} pix channel{1}(idx); feature [feature, mean(pix), std(pix), skewness(pix)]; end % feature现在是9维向量R均值/R标准差/R偏度/G均值/...3.2 形状特征从二值掩码里榨取信息形状特征是区分“长条形的香蕉”和“圆滚滚的苹果”的关键。只需要把之前分割得到的二值掩码送进regionprops函数就能一次性提取出几十个形状属性stats regionprops(mask, Area, Perimeter, MajorAxisLength, ... MinorAxisLength, Eccentricity, Circularity, ... Extent); area stats.Area; perimeter stats.Perimeter; eccentricity stats.Eccentricity; % 离心率0是圆1是直线 circularity 4 * pi * area / (perimeter^2); % 圆度1是标准圆挑几个重点解释一下。Eccentricity离心率是区分香蕉的利器——香蕉的细长形状对应离心率接近1而苹果、橙子这种近圆形果实离心率基本在0.3以下。Circularity圆度的定义是4π乘以面积除以周长的平方标准圆的圆度是1形状越复杂、边缘越崎岖圆度值越小。Extent是面积和最小外接矩形面积之比这个特征在区分“带叶子的草莓”和“光滑的番茄”时表现很好。这里有个细节regionprops默认是按8连通域计算如果分割掩码有多个不连通区域它返回的就是一个结构体数组每个元素对应一个区域。使用时建议先按面积排序只保留面积最大的区域——这通常就是目标水果其他小区域都是噪声。3.3 纹理特征用灰度共生矩阵描述表面质地颜色和形状已经能解决大部分区分问题但遇到颜色相近、形状也相近的水果比如青苹果和香梨就不够了。这时候纹理特征派得上用场。香梨表面有粗糙的果点青苹果表面光洁两者的纹理差异肉眼可辨计算机则可以通过灰度共生矩阵GLCM来量化这种差异。MATLAB里用graycomatrix和graycorprops两个函数就能实现。灰度共生矩阵统计的是“相隔某个距离和角度的两个像素点灰度值组合出现的频率”它的四个统计属性对比度、相关性、能量、同质性分别刻画了纹理的不同侧面。对比度大说明纹理沟纹深、视觉上粗糙能量大说明纹理均匀、变化平缓。% 先把图像转灰度 grayImg rgb2gray(img); % 只取目标区域生成ROI灰度图 roiGray grayImg .* uint8(mask); % 计算GLCM距离取1方向取0度、45度、90度、135度并求平均 glcms graycomatrix(roiGray, Offset, [0 1; -1 1; -1 0; -1 -1], ... NumLevels, 64, Symmetric, true); statsTexture graycoprops(glcms, {Contrast, Correlation, ... Energy, Homogeneity}); % 四个方向取平均避免方向性偏差 contrast mean([statsTexture.Contrast]); energy mean([statsTexture.Energy]);实际用下来我把颜色矩9维、形状特征5维面积、周长、离心率、圆度、Extent、纹理特征4维对比度、相关性、能量、同质性合起来一共18维特征向量。这个维度对水果识别来说已经足够特征太多反而容易过拟合而且训练速度会变慢。4. 分类器选型与训练KNN、SVM、BP神经网络的取舍特征提取完成后识别问题变成了标准的分类问题输入18维特征向量输出水果类别标签。我试过三种常见分类器各有适用场景下面是我实操下来的对比结论。4.1 三种分类器的对比与选择分类器适用场景优点缺点我的实测表现KNN小数据集、类别差异明显原理简单无需训练调参少预测时逐个计算距离速度慢18维特征下准确率约92%SVM中等数据集、非线性可分泛化能力强适合高维特征核函数和参数需要调用RBF核准确率能到96%BP神经网络大样本、特征与类别关系复杂能自动学习非线性关系训练时间长需要调网络结构样本量不足时容易过拟合KNN是个很好的起点。它的思路特别朴素“物以类聚”待识别样本的特征向量和哪个类别的训练样本最接近就属于哪个类。MATLAB里有个麻烦是新版把knnclassify函数移到Statistics and Machine Learning Toolbox里了旧脚本直接会报错需要改用fitcknn和predict这对新接口。第一次用的时候被这个坑卡了半天特地说一下。SVM是我最终选择的方案因为它在样本量不大我只有每类30张图左右的情况下表现最稳。SVM的核心思想是寻找一个超平面让不同类别的样本点到这个平面的间隔最大化。对线性不可分的数据通过核函数把特征映射到高维空间再找超平面。我用的是RBF高斯核参数方面关注两个值盒约束BoxConstraint控制错误分类的惩罚力度和核尺度KernelScale控制高斯函数的宽度。4.2 MATLAB完整训练流程训练阶段的完整流程如下% 假设已经提取了所有训练样本的特征存为矩阵features每行一个样本 % labels是对应的类别标签用1、2、3、4分别代表苹果、香蕉、橙子、梨 % 归一化这一步极其重要否则大数值特征会主导距离计算 % 比如面积是几千量级而圆度是0到1不归一化的话面积完全淹没其他特征 features normalize(features); % 新版MATLAB直接用normalize函数 % 划分训练集和测试集70%训练30%测试 cv cvpartition(size(features, 1), HoldOut, 0.3); trainIdx training(cv); testIdx test(cv); % 训练SVM分类器 svmModel fitcecoc(features(trainIdx,:), labels(trainIdx), ... Learners, templateSVM(KernelFunction, rbf, ... BoxConstraint, 1, ... KernelScale, auto)); % 在测试集上评估 predLabels predict(svmModel, features(testIdx,:)); accuracy sum(predLabels labels(testIdx)) / numel(testIdx); fprintf(SVM识别准确率%.2f%%\n, accuracy * 100); % 保存模型这个模型后面可以直接用于新图片的预测 save(fruitSVM.mat, svmModel);这里用fitcecoc而不是fitcsvm是因为我们需要做多分类4种水果fitcsvm只支持二分类。fitcecoc是“一对一”策略对4类问题自动训练6个二分类器最后投票决定结果省去了手写多分类策略的麻烦。注意normalize函数是R2018a引入的老版本会提示找不到函数。如果用的是2018a之前的版本需要用zscore替代。另外如果用fitcknnK值默认是1对噪声敏感建议用交叉验证选一个合适的K值一般3到5比较合适。5. 完整识别流程与GUI封装从训练好的模型到真正“识别”5.1 单张图片识别的完整代码模型训练好、保存成mat文件后识别阶段就非常轻量了。对新来的一张图片走一遍“预处理—分割—特征提取—预测”的流水线。我把这条流水线封装成了一个函数function label classifyFruit(imgPath, svmModel) % 步骤1读图、分割 img imread(imgPath); hsvImg rgb2hsv(img); H hsvImg(:,:,1); S hsvImg(:,:,2); V hsvImg(:,:,3); % 这里用的是多类别合并分割策略 % 检测几种常见水果的主色调区间合并成一个大掩码 maskRed (H 0.08) (S 0.3) (V 0.15); maskOrange (H 0.08 H 0.15) (S 0.3) (V 0.1); maskYellow (H 0.15 H 0.22) (S 0.25) (V 0.1); maskGreen (H 0.22 H 0.45) (S 0.2) (V 0.1); mask maskRed | maskOrange | maskYellow | maskGreen; % 取最大连通域 mask imopen(mask, strel(disk, 5)); mask imclose(mask, strel(disk, 10)); mask bwareafilt(mask, 1); % 只保留面积最大的连通域 % 步骤2特征提取颜色矩 形状 纹理18维 feat extractFruitFeatures(img, mask); feat normalize(feat); % 步骤3分类 label predict(svmModel, feat); endbwareafilt函数是个好工具一行就实现了“只保留最大连通域”省掉了regionprops加sort的繁琐写法。这个函数支持旧版通过bwareaopen加取反的方式实现但直接bwareafilt就没有兼容性问题。5.2 用App Designer做个简单的识别界面如果只是脚本运行每次都要改图片路径不太方便。我习惯用App Designer把程序包成一个带界面的工具即使不懂MATLAB的人也能直接用。这个其实不难核心布局就三个要素一个坐标区用来显示图片一个按钮用来加载图片一个文本框用来显示识别结果。操作步骤在MATLAB命令行输入appdesigner调出设计工具从左侧控件库拖一个Button、一个Axes、一个Label或Edit Field到画布上。双击按钮在回调函数里写代码逻辑。比如“打开图片”按钮的回调函数大概长这样% 按钮回调函数 function OpenImageButtonPushed(app, event) % 弹出文件选择对话框 [file, path] uigetfile({*.jpg;*.png;*.bmp, 图片文件 (*.jpg, *.png, *.bmp)}); if isequal(file, 0) return; % 用户取消 end % 读取并显示图片 imgPath fullfile(path, file); app.ImageAxes.imshow imread(imgPath); % 显示原图 app.ImageAxes.imshow(imgPath); % 修正应该用image或imshow函数 % 调用识别函数 label classifyFruit(imgPath, app.svmModel); app.ResultLabel.Text [识别结果 label]; end一个小提醒App Designer里主轴的显示方式跟传统figure的imshow不太一样正确的方式是用imshow函数并把父容器指定为坐标区或者直接用image函数避免显示出的图片变形或只显示一角。这个GUI封装起来以后整个程序的可用性就完全不同了从“一个只能改代码才能跑的脚本”变成了“一个能交给别人点两下就出结果的工具”。6. 常见问题与排查技巧实录水果识别这个项目看着简单实际跑起来坑还不少。我把自己踩过和一些朋友问过的问题整理一下基本覆盖了大多数新手的痛点。6.1 分割结果不干净的三种情况最典型的异常是掩码里包含大片背景区域。八成原因是阈值范围设得太宽。解决办法是把分割后的掩码显示出来检查微调HSV的上下界。另一个经常出现的问题是水果区域内有大量空洞这是高光反光导致的用imclose闭运算能解决大部分如果还不够可以试试imfill函数加holes参数专门填洞。如果背景里恰好有和水果颜色相近的物体比如红桌布上的红苹果那颜色阈值就怎么调都有问题。这时要么换背景要么上K-means聚类分割。毕竟成熟的分割方案从来不是单一方法的天下组合拳才是工程上的常态。还有一类分割结果“切多了”把阴影也切进来了。HSV分割时V通道下限设低一点或者分割前先做一次光照校正。简单做法是对灰度图做imtophat滤波能去掉不均匀照明的影响。6.2 识别准确率上不去从哪里入手如果分割看上去没什么问题但识别率就是不高我一般按这个顺序排查先怀疑特征提取代码。把提取的特征向量print出来逐维检查数值范围是否合理。比如圆度算出来大于1肯定是代码写错了面积是负数那肯定不对。再看训练集和测试集是否同分布。比如训练集全是白色背景拍的图片测试时拿了一张复杂背景的识别率断崖式下跌非常正常。确认是否做了特征归一化。这个太容易被忽略我一开始忘了归一化准确率直接从94%掉到60%教训惨痛。检查样本数量。每类少于20张训练图再好的分类器也难有稳定表现。可以考虑对图片做平移、旋转、缩放、加噪等数据增强把样本扩到50张以上。6.3 分类器报错的经典问题MATLAB版本导致函数调用方式变化是我遇到最多的坑。knnclassify被移除改用fitcknnsvmtrain不建议用了改成fitcsvm/fitcecoc。看到“Undefined function or variable”先别慌大概率是函数在新版本被替换了查一下文档就能解决。SVM训练报内存不足多半是特征维度太高或者样本量太大。水果识别特征最多几十维样本量几百个理论上是不会爆内存的。如果真遇到了先检查是不是训练数据里混入了异常值比如某个样本的特征全是NaN——这通常是分割结果为空导致的特征向量是空值。7. 写在最后的几个经验做水果识别这个项目带给我最直接的价值不是这个程序本身而是让我把图像处理和模式识别里最容易忽略的几个知识点串起来了。以前单独学直方图均衡、边缘检测、形态学操作的时候都是“学过就忘”但一旦它们成了某个目标链路上的必要环节印象就非常深刻。如果你是自己学习做这个项目我建议从最简单的开始先拿两类颜色差异大的水果比如红苹果和香蕉打通全流程准确率稳定了再逐步加类别。类和类之间差异越小对特征设计和数据质量的要求就翻着倍提高这个过程中踩的坑才是最值钱的。最后分享一个小技巧。训练好模型后把整个识别流程封装成函数以后不管拿到什么水果图片一行代码就能出结果。这种“工具箱化”的思路在项目越做越大的时候特别重要。我现在电脑里这个水果识别的工程文件还留着偶尔换一批新图片测试一下既是复习旧知识也为以后做更复杂的视觉项目留了个可扩展的底子。本文还有配套的精品资源点击获取