MATLAB水果识别项目全流程解析:从HSV分割到SVM分类
简介这是一份基于MATLAB的水果识别数字图像处理项目源码并配套答辩PPT、实验报告等文档资料面向毕业设计、期末大作业及课程设计场景的开发者。项目利用MATLAB图像处理技术实现水果图像识别与检测涵盖图像预处理、特征提取与识别等主要环节主程序代码附有详细注释经作者严格调试可稳定运行界面直观、操作简便适合需要完整参考方案或快速部署的高校学生及MATLAB入门者。资源包共16个文件以MATLAB程序文件.m、图窗文件.fig、演示图片.bmp/jpg、答辩PPT.pptx及实验报告.doc/pdf为主压缩后约17.73MB涵盖源码、界面截图、答辩演示等多层面内容另含项目说明与背景素材便于快速理解资源结构。已有184人学习下载项目曾获导师认可的高分评价具备较高的实际应用价值既能直接用于课程展示也可作为二次开发的起点。1. 从课程设计到答辩现场这个 MATLAB 水果识别项目到底在做什么每到学期末总有一批人被基于MATLAB的水果识别的数字图像处理项目这种标题卡住。它在讲一件很具体的事把一张包含水果的照片用数字图像处理的手段把水果从背景里抠出来再提取颜色、形状、纹理特征最后用分类器说清它是苹果、橙子还是香蕉。它解决的痛点很现实——课程设计或毕业设计必须有源码、能跑通、有实验数据还要有一份能让评审老师点头的答辩 PPT。适合谁正在为 MATLAB 图像处理大作业头秃的本科生以及需要快速交付一个完整图像识别案例的初学者。这条路不玄学但坑不少下面按我能确认的从业方案把整条线拆开讲。2. 分割与特征选型为什么水果识别大多先做颜色空间转换再做阈值处理2.1 颜色空间选型RGB 不是坏选择但 HSV 对光照变化更稳水果识别首先面临的问题是用什么描述一个水果。RGB 三个分量直接来自相机传感器但 R、G、B 之间高度相关光照一变化三个通道一起变阈值很难定。常见做法是先把图像转换到 HSV 空间让色相H、饱和度S、明度V分开。色相描述这是什么颜色对光照不敏感明度单独拎出来刚好用来处理阴影和高光。代码里转换就一行imgHsv rgb2hsv(imgRgb); % imgRgb是读取的RGB图像hsv各通道范围均为[0,1]转换后 H 通道的范围是 0 到 1对应色相环 0° 到 360°。红色水果苹果、番茄的 H 值会落在 0 附近或 1 附近因为色相环上红色既在 0° 也在 360° 附近绿色水果青苹果、青梨的 H 值大致在 0.2 到 0.45 之间。这个特性决定了分割阈值要写成多个区间而不是一个区间一刀切。参数上需要注意rgb2hsv 出来的 V 通道在背光场景下会整体偏低而 S 通道在高光区域会骤降。所以做阈值分割时不要单独用 H 通道常见的做法是 H 通道做主判断、S 通道做辅判断只有 H 落在目标区间且 S 大于某个下限的像素才算是水果候选。2.2 分割算法选型Otsu、K-means 和超像素的边界条件分割是水果识别里最决定成败的一步因为后端的特征提取全部建立在分割结果正确之上。三种主流方案各有适用场景先看对比分割方式适用场景主要参数典型坑Otsu 全局阈值单一水果、背景均匀graythresh 的归一化阈值背景复杂时失效K-means 聚类k2/3多水果、纹理背景聚类数、颜色空间初始中心敏感需固定随机种子超像素 图割水果与背景颜色接近超像素个数、平滑项权重计算量大不适合实时演示对于课程设计最常见且最稳妥的是 K-means 聚类。它不像 Otsu 那样依赖直方图的双峰性而是把像素按颜色分成几类再从中挑出水果类。实现时把图像 reshape 成 N×3 的矩阵每一行是一个像素的 HSV 值直接喂给 kmeanspix double(reshape(imgHsv, [], 3)); % kmeans对数据范围敏感HSV各通道已在[0,1]无需额外归一化 [idx, centers] kmeans(pix, 2, Distance, sqEuclidean, Replicates, 3); mask reshape(idx, size(imgHsv, 1), size(imgHsv, 2));逻辑说明kmeans 把每个像素归到离它最近的聚类中心Replicates3 表示从 3 组不同初始中心里挑误差最小的一次结果避免局部最优。聚类数取 2 时一般分出背景和水果两类取 3 时还能把果皮和果肉分得更细。参数上建议固定 Replicates 为 3太小容易翻车太大在答辩现场拖慢节奏。还要注意 kmeans 每次结果不完全一致演示前用 rng(0) 固定随机种子否则同一张图前后两次识别出不同结果会很难圆场。Otsu 不是没用它适合在水果和背景灰度差异明显时做快速预分割。用 graythresh 求阈值再 binarize 即可但它对阴影和彩色背景基本无能为力所以通常只用来做掩膜的粗筛后续要用形态学修补。2.3 特征提取的三个维度颜色、形状和纹理谁主谁次分割出水果区域后要把它变成一串数字。颜色特征最直接但不能只用 RGB 均值因为光照一变均值就飘。颜色矩是个好选择对 H、S、V 三个通道分别算一阶矩均值、二阶矩标准差和偏度得到一个 9 维向量。它的优势是计算极快且不需要预先知道水果上有几种颜色。形状特征用 regionprops 提取即可关键属性包括Area水果区域像素总数用于归一化其它量。MajorAxisLength 和 MinorAxisLength拟合椭圆的长短轴长宽比能区分香蕉和苹果。Extent区域面积与边界框面积之比苹果的 Extent 接近 1香蕉明显低于 1。圆度regionprops 在高版本 MATLAB 的某些模式里不直接返回 Circularity需要手动计算 4piArea/Perim^2。纹理特征对水果识别是辅助项因为多数水果表皮纹理不显著。如果要做灰度共生矩阵GLCM是标准做法用 graycomatrix 计算后取对比度、相关性、能量和同质性四个标量作为纹理描述。实际工程中颜色矩和形状特征占主导GLCM 只在区分表面光滑与表面粗糙的水果比如苹果和猕猴桃时才显出价值。3. 从原图到分类结果水果识别最小实现流水线与关键代码3.1 预处理流程中值滤波、Gamma 校正与统一尺寸分割之前先做预处理目的不是美化图片而是让后续分割和特征提取更稳定。我一般按三步走第一步如果原图是 RGB先转为 HSV原因前面说过。第二步对 V 通道做中值滤波窗口 5×5去传感器噪声。第三步判断是否需要 Gamma 校正——当 V 通道均值低于 0.4 或高于 0.7 时说明图像整体偏暗或偏亮用 imadjust 调整。v imgHsv(:, :, 3); % 中值滤波比均值滤波更能保留边缘对水果轮廓更友好 vFilt medfilt2(v, [5 5]); % imadjust默认使用[0,1]的灰度范围拉低值区以增强暗部细节 vAdj imadjust(vFilt, [0.2 0.8], [0 1]); imgHsv(:, :, 3) vAdj;注意 medfilt2 在高版本 MATLAB 里对部分图像类型需要先转成 uint8 或 double类型不对会报类型错误。imadjust 的两个向量参数分别表示输入灰度范围的下限和上限[0.2 0.8] 的意思是原图中灰度 0.2 以下的像素映射到输出 00.8 以上的映射到 1中间段线性拉伸。如果图像本身正常V 通道均值在 0.4~0.7就跳过 Gamma 校正省一步是一步。预处理最后要把图片统一为固定尺寸吗看情况。如果后面用的是传统特征提取加 SVM不需要统一尺寸因为颜色矩和形状特征里的长宽比是尺度无关的。但如果要用深度学习第 4 章会讲就必须 resize 到网络输入尺寸比如 227×227 或 224×224。3.2 分割实现HSV 阈值 K-means 双保险分割这一步我会用两个策略叠加而不是赌单一算法。策略一基于 HSV 先做硬阈值把明显是背景的像素比如桌面、地板颜色直接排除。策略二对剩余区域做 K-means 细分为水果/背景两类。硬阈值的 HSV 区间必须按水果种类配一组预设值。做课设时常见做法是准备一个区间表识别前让用户选择当前识别哪种水果或干脆提前配好水果类别H 范围0-1S 下限说明红苹果 / 番茄[0, 0.12] ∪ [0.88, 1]0.25红色跨越色相环两端橙子 / 橘子[0.05, 0.13]0.3橙色在红黄之间香蕉 / 芒果[0.1, 0.22]0.3黄色区间青苹果 / 青梨[0.22, 0.45]0.2绿色区间下界放宽注意这个表是经验值不同相机色彩表现会有偏移。落地时我的习惯是在测试集上统计每类水果 H 通道的均值和标准差按均值±3倍标准差反推区间而不是纯靠肉眼调。阈值分割之后马上接形态学处理这一步不能省maskBin mask 0; % 假设1代表前景水果 % 闭运算先膨胀后腐蚀填充水果内部由于高光产生的小孔洞 se strel(disk, 15); maskClose imclose(maskBin, se); % 移除面积小于200像素的孤立噪点防止特征提取被散点干扰 maskClean bwareaopen(maskClose, 200);strel(disk, 15) 的半径需要根据图像分辨率调整1080p 图像用 15 到 30低分辨率图像用 5 到 10。半径太小闭运算补不上高光孔洞太大会把两个相邻水果融成一个连通域。这一点会在第 5 章展开细说。3.3 特征向量构造把分割结果变成分类器能吃的数字分割完成得到 maskClean 后特征提取分两路走。一路用 regionprops 处理二值掩膜另一路用原始 HSV 图像结合掩膜计算颜色矩。% 只保留最大的连通域通常是主水果排除背景碎片 cc bwconncomp(maskClean); numPixels cellfun(numel, cc.PixelIdxList); [~, idxMax] max(numPixels); maskFinal false(size(maskClean)); maskFinal(cc.PixelIdxList{idxMax}) true; props regionprops(maskFinal, Area, Perimeter, ... MajorAxisLength, MinorAxisLength, Extent); % 手动计算圆度不依赖版本差异 circularity 4 * pi * props.Area / (props.Perimeter^2); aspectRatio props.MajorAxisLength / props.MinorAxisLength; % 颜色矩用掩膜提取HSV各通道的前景像素 hVec imgHsv(:, :, 1); hFg hVec(maskFinal); colorMoment [mean(hFg), std(hFg), skewness(hFg)];这段代码的逻辑是先把二值掩膜里面积最大的连通域挑出来这是因为分割后经常残留几个背景噪点最大连通域通常就是主水果。然后从 regionprops 中提取几何属性圆度手动计算避免不同 MATLAB 版本对 Circularity 属性支持不一致。颜色矩计算时用逻辑索引 hVec(maskFinal) 只取前景像素这样计算出的均值和标准差不会混入背景值。skewness 函数统计偏度描述颜色分布对称性。比如同是红色水果红苹果的 H 值偏度可能接近 0而部分品种偏橙的苹果偏度偏负这个特征在区分相似颜色时有边际效用但不决定成败。特征拼接时注意顺序和维度颜色矩 9 维H/S/V 各 3 个统计量形状特征 3 维圆度、长宽比、Extent总共 12 维。如果要加纹理特征再从 GLCM 里取 4 维变成 16 维。拼接顺序需要保持一致训练和预测时不能换位。3.4 分类器训练SVM 多分类与交叉验证的标准流程12 到 16 维的特征向量不需要太复杂的分类器SVM 用 RBF 核就够。MATLAB 里直接用 fitcecoc 做多分类它会自动把二分类器组合成多分类方案。% features是N×12的特征矩阵labels是N×1的分类标签 % Standardize设为true防止圆度和颜色矩之间的量纲差异影响距离计算 mdl fitcecoc(features, labels, ... Learners, templateSVM(KernelFunction, rbf, KernelScale, auto), ... Standardize, true, FitPosterior, true, KFold, 5); % 查看交叉验证正确率 acc kfoldLoss(mdl, LossFun, ClassifError); fprintf(5折交叉验证正确率: %.2f%%\n, (1 - acc) * 100);fitcecoc 的 KFold 参数直接把交叉验证和训练绑定在一起省去手动划分训练集的麻烦。KernelScale 用 auto 会按启发式估算尺度参数这个通常比手动指定更可靠。FitPosterior 设为 true 后可以输出预测概率答辩时能拿出来说这张图是苹果的概率 0.92比只报类别有说服力得多。这里有一个参数选择的关键点如果样本量很小比如每类只有 20 张图5 折交叉验证会把训练集压到 16 张模型容易欠拟合。此时可以调成 KFold3或者改用留一法Leave-One-Out。课设场景下常见做法是每类采集 30 到 50 张用 5 折交叉验证得到一个相对客观的正确率数字。训练完成后保存模型save(fruitModel.mat, mdl); % 预测新图时用load加载模型再从新图走一遍同样的预处理-分割-特征提取流程记住一个关键约束预测时的特征提取顺序必须和训练时完全一致包括滤波窗口、形态学参数、特征拼接顺序。任何一个参数不一致特征分布就变了模型表现会断崖式下降——这也正是很多人训练集 95%测试集 50%的根源。4. 从 80% 到 95%深度学习迁移、数据增强与评估闭环4.1 什么时候值得换成预训练网络做特征提取传统特征加 SVM 在纯色背景下能到 80%~90% 的正确率但一旦背景杂乱比如食堂托盘、木纹桌面、树叶遮挡手工特征就不够看了。这时常见做法是换成预训练卷积神经网络做迁移学习。用 MATLAB 的 Deep Learning Toolbox一行 alexnet 或 googlenet 就能加载预训练模型。net googlenet; % 加载预训练网络首次运行会提示下载权重 % 去掉原网络最后的分类层用激活层输出作为特征 layerName pool5-drop7; % 不同网络特征层名称不同用analyzeNetwork查看 trainFeatures activations(net, trainImds, layerName, OutputAs, rows);把每张图缩放成 224×224googlenet 的输入尺寸经过网络前向传播从 pool5-drop7 层取出 1024 维深度特征再喂给 SVM 或直接用全连接层微调。深度特征比手工特征强在它自动学习了苹果和橙子表面纹理差异这类很难用 GLCM 捕捉的模式缺点是特征维度从 12 跳到 1024对样本量要求更高。迁移学习的适用边界要讲清楚样本量少于每类 50 张时深度特征加 SVM 的优势不明显甚至可能因为特征维度太高而过拟合。样本量超过每类 200 张时直接 fine-tune 全网络比只取特征效果更好。课程设计的数据量通常卡在中间取中间层的深度特征是比较稳的选择。4.2 数据增强旋转、翻转和 HSV 扰动先扩容再训练样本不够是常态数据增强是后补手段。MATLAB 里用 imageDataAugmenter 做在线增强训练时每读一张图就随机做一次变换aug imageDataAugmenter(... RandRotation, [-15, 15], ... RandXTranslation, [-10, 10], ... RandYTranslation, [-10, 10], ... RandXScale, [0.9, 1.1], ... RandYScale, [0.9, 1.1]);增强参数的范围要克制。RandRotation 超过 20° 时不现实因为水果正常摆放不会倒着RandXScale 和 RandYScale 同时设太大会让水果形状失真。HSV 扰动也是常用手段可以在增强流程里对 H 通道加 ±0.02 的偏移模拟不同批次水果的颜色差异——但注意 H 通道是色相偏移过大就会把红苹果变成橙子那是在制造错误样本而不是在增强。增强后训练集要重新划分验证集不能把增强图混进验证集里。否则同一张原图的变换副本会同时出现在训练和验证中评估结果虚高答辩时评审一换图就暴露。4.3 混淆矩阵与单项识别率别被总正确率骗了总正确率 90% 听上去不错但要看细项。如果训练集里苹果 200 张、香蕉 20 张模型只要全猜苹果就能拿到 90% 的总正确率。答辩时评审老师通常会问哪两类最容易混——提前算好混淆矩阵才知道模型真正的短板在哪。% mdl是已训练的模型testFeatures和testLabels是验证集 predictedLabels predict(mdl, testFeatures); confMat confusionmat(testLabels, predictedLabels); % 按行归一化得到每类识别率对角线即各类正确率 classAcc diag(confMat) ./ sum(confMat, 2);如果发现青苹果和青梨的混淆率超过 30%说明特征里对形状的刻画不够。此时不要盲目加特征先画特征分布图看这两类在特征空间里到底重叠在哪几个维度上。用 gscatter 画前两个主成分的散点图一眼就能看出是颜色矩太近还是形状特征没拉开。5. 五个必踩的坑现象、原因与解决方案5.1 分割后水果边缘出现大量空洞轮廓不连续现象同一张图阈值分割出来的掩膜上水果内部有黑色斑点边缘像锯齿且不闭合。原因水果表面高光区域在 HSV 空间里饱和度极低饱和度低于 S 下限就会被误判为背景另外苹果柄处的阴影会让 V 通道跌到阈值以下造成轮廓缺口。解决先做闭运算再做开运算而不是只做闭运算。闭运算填洞开运算去毛刺顺序不能反。闭运算核半径约等于最大高光区域半径的 1.5 倍如果图像是 1920×1080我用 strel(disk, 20)如果是手机拍的大图先缩放到 800 像素宽再处理否则形态学核尺寸很难统一参数调起来像玄学。5.2 训练集正确率 95%现场随便拍一张只有 60%现象自己准备好的测试图上识别很好换一个真实场景宿舍灯光、手机拍摄立刻翻车。原因训练集和测试集同分布但实际现场拍摄的光照、相机白平衡、拍摄角度和训练集完全不同。这是课程设计里最普遍的问题本质是训练集覆盖率不够不是模型代码有 bug。解决采集训练集时覆盖至少三种光照条件白炽灯、日光灯、自然光并按 7:2:1 分成训练、验证、测试三份。验证集参与调参测试集只在答辩演示前跑一次。如果现场演示条件无法预知就在预测前加一步白平衡校正——用自写的灰度世界假设校正代码把 V 通道的整体偏移拉回训练集的平均水平。5.3 SVM 多分类输出概率与实际不符置信度全部偏高现象FitPosterior 设为 true 后预测概率经常出现 0.95 以上但正确率只有 80%给人一种模型很自信但总搞错的观感。原因SVM 的输出概率是由 Platt 校正拟合出来的不是真正的贝叶斯后验概率。当训练样本不均衡时这个拟合会产生系统性偏移少数类的概率被压低多数类的概率虚高。解决不要用概率阈值做决策只用它做排序即最可能的三个类别依次是什么。要得到相对靠谱的概率改用 fitcnet 训一个浅层神经网络或者用 fitcknn 的 ScoreTransform 选项设置 invlogit这两个模型的输出分布比 SVM 更接近真实后验。如果坚持用 SVM至少要在混淆矩阵旁边附上各类别平均概率让评审看到类别间的概率差异而不是单张图的绝对值。5.4 MATLAB 版本差异导致函数或属性不兼容现象在自己电脑上跑通的代码换到实验室电脑或答辩机器上报错 Unrecognized method 或 Invalid parameter name。原因MATLAB 从 R2017a 到 R2024b 期间多个图像处理和统计函数改了默认行为或属性名。例如 regionprops 的 Circularity 属性在部分版本不是默认输出imageDataAugmenter 的 RandRotation 参数在深度学习工具箱 18.2 之前叫 RandRot还有 fitcecoc 的 FitPosterior 选项在某些旧版本里不支持。解决写代码前用 ver 命令确认工具箱版本把版本差写在注释里。我在给课设做交付时就吃过这个亏代码在自己 R2023b 上跑得好好的答辩机器是 R2019a结果 regionprops 属性取不到现场改代码改得手心冒汗。避免的最稳妥办法是用 version 函数判断并分发或干脆在代码里手动实现所有兼容性敏感函数比如圆度就自己算不依赖内置属性。5.5 答辩演示时 GUI 点击识别卡顿十几秒现象GUI 界面设计好了点击识别按钮后程序转圈十几秒后才出结果评审老师已经等得不耐烦。原因回调函数里每次点击都重新读图像、重新分割、重新加载模型、重新提取特征甚至重新跑 kmeans 的多组迭代。尤其是 .mat 模型文件每次 load 都要经过磁盘 I/O几百 KB 的模型在机械硬盘上读起来并不算慢但配合大尺寸图像和串行计算就卡住了。解决把模型加载和图像预处理移到 GUI 的 OpeningFcn 回调里做一次识别回调里只用已经准备好的模型和新读入的单张图处理。另外用 coder.extrinsic 或 matlabpool 并行不是首选因为答辩机器不一定开了并行池。最直接的优化是把图像先缩放到固定宽度比如 800 像素分割和特征提取都基于缩放后的图速度能快到 2 秒内。演示前在自己的机器上按点击到出结果计一次时超过 3 秒就要精简步骤。6. 答辩 PPT 与验证闭环把识别率数字讲到评审愿意信答辩 PPT 不需要堆代码但要有一张完整的流程图和一套能自洽的验证数字。我的习惯是四页内容第一页放问题定义和方案总览三句话讲清为什么用 HSV K-means SVM——HSV 抗光照、K-means 适应复杂背景、SVM 小样本友好。第二页放分割前后对比图至少三张不同背景的图展示预处理到掩膜的中间结果。第三页放特征可视化用箱线图展示不同水果在圆度和长宽比上的分布差异这比一堆数字更有冲击力。第四页放混淆矩阵和单项识别率并主动指出青苹果和青梨混得最多原因是形状特征区分度有限既坦诚又有改进方向。现场演示前我习惯做一次冷启动测试关掉 MATLAB 重新打开直接运行主脚本看从零到出结果需要多久、有没有警告。这个步骤能抓到很多隐蔽问题——比如工作区里残留了之前跑过的变量导致当前运行没走完整流程。另外把测试图片放在一个独立文件夹里不在代码里写死绝对路径用 uigetfile 或相对路径读取避免换机器后路径失效。如果演示时网络不稳定提前把训练好的模型 .mat 文件放本地不要现场在线下载权重。还有个容易被忽视的点全程用第一人称把决策过程讲出来不要只念结果。评审问为什么 K 取 2 而不是 3时如实回答背景和前景两类最干净取 3 类会把果皮高光单独分出来但容易引入碎片——比背一段教科书定义可信得多。我自己带课设时反复提醒学生数字图像处理项目答辩的核心是让评审相信你踩过坑、知道坑在哪而不是相信你写了几千行代码。把 5.1 到 5.5 的坑各准备一句话解释比堆十个公式更能撑住场面。希望这些经验能帮你少走几步弯路把答辩现场的意外留给天气而不是留给代码。本文还有配套的精品资源点击获取