MATLAB水果识别实战:HSV非均匀量化+LBP纹理+粒子群优化全流程 简介这是一份基于MATLAB实现的水果识别完整项目源码面向毕业设计、课程设计及图像识别方向的项目开发人员覆盖从颜色空间处理到纹理特征提取的完整流程。项目中集成了HSV非均匀量化、K均值聚类、LBP与MBLBP算子、粒子群优化、灰度共生矩阵及多种纹理特征提取方法可用于水果图像分类识别算法的研究与二次开发。压缩包共2000个文件大小约87.85MB其中以bmp样本图片为主另有大量m源程序文件和jpg示例图以及少量配置文档与说明文件目录结构清晰便于按模块调用与学习。目前已有50人学习下载源码经过严格测试可直接运行参考。适合希望系统掌握特征提取与分类识别流程、需要快速搭建实验环境并在此基础上进行算法改进或功能扩展的MATLAB开发者。1. HSV非均匀量化与多特征融合的水果识别这门课设比你想的麻烦做matlab图像处理大作业或者毕业设计时拿水果识别练手是最常见的选择但也是最容易翻车的选题。多数人一开始以为只需提个颜色直方图再套个分类器就能交差真跑起来才发现红苹果和红番茄在颜色特征上几乎不可分猕猴桃表皮的绒毛纹路、菠萝表面的网格结构根本不是颜色特征能表达的光照稍微一变整张图的颜色直方图就整体漂移。这套基于matlab实现的水果识别程序把HSV非均匀量化、K均值聚类、LBP算子、MBLBP算子、灰度共生矩阵和粒子群优化串成一条完整流水线本质上走的是“颜色不够纹理凑、纹理不够优化凑”的工程路径。适合正在做毕业设计或课程设计的本科生直接复现也适合想把彩色图像识别基线做到位的入门研究者。2. 颜色特征的落地HSV非均匀量化与K均值聚类怎么配才稳选择HSV而不是RGB作为颜色特征的第一层核心原因是光照鲁棒性。RGB三个通道对亮度变化都极其敏感同一颗苹果在室外阴影和室内灯光下拍出来RGB向量可能天差地别而HSV把亮度单独拆到V通道色相H和饱和度S相对稳定。这是整个识别程序最底层的地基。2.1 HSV非均匀量化的设计逻辑H、S、V三个通道的量化边界非均匀量化的核心思想不是把H均匀切成16份、S均匀切成4份。人眼对色相的辨识能力在低饱和度时显著下降接近灰色的区域里色相基本没有意义如果S很低还在H上细分区段只是徒增维度。常见做法是分层量化当S低于0.15时把像素归为弱色类H不计入特征S介于0.15到0.5之间时H量化为4个主方向每段90度S高于0.5时H再细分到8个区段每段45度。V通道同样处理低于0.2的暗部像素和高于0.8的高光像素都放宽H的量化粒度因为暗部细节不可信高光区又容易过曝。function [qH, qS, qV] hsv_nonuniform_quantize(h, s, v) % hsv_nonuniform_quantize: HSV非均匀量化 % 输入h为色相角度[0,360), s和v为[0,1]区间归一化值 % 输出qH,qS,qV为量化后的离散索引, 后续可组合成单一整数 qH zeros(size(h)); qS zeros(size(s)); qV zeros(size(v)); for i 1:numel(s) % S通道三分类: 弱色/中饱和/高饱和 if s(i) 0.15 qS(i) 0; elseif s(i) 0.5 qS(i) 1; else qS(i) 2; end % V通道三分类: 暗部/正常/高光 if v(i) 0.2 qV(i) 0; elseif v(i) 0.8 qV(i) 1; else qV(i) 2; end % H通道按S和V的层级做非均匀量化 if qS(i) 0 || qV(i) 0 qH(i) 0; % 弱色/暗部: 色相不可信, 归零 elseif qS(i) 1 qH(i) floor(h(i) / 90) 1; % 中饱和: 只分4个主方向 else qH(i) floor(h(i) / 45) 1; % 高饱和: 分8个细分方向 end end end这段代码的核心逻辑是把连续量放进有限区间目标不是信息无损而是分类可用。量化的层级设计让S和V先把像素分到“弱色/有色/暗部”三个大类再在各类内部对H做不同粒度的细分。这样做的好处是红色在一张暗光图片里不会因为V的微小波动被硬拆成两个不同的bin。调参方面值得注意的边界有三个S的弱色阈值0.15、V的暗部阈值0.2、以及中高饱和分界的0.5。如果摄像头采集场景整体偏暗把V的暗部阈值从0.2提到0.3让更多暗部像素落入qV0避免它们被当作彩色特征参与统计。H的细分层级决定了颜色直方图的总维度上面的方案组合起来最多产生9×3×381个bin对水果识别这种小规模分类问题已经够用——bin开得再多直方图就会过分稀疏每个bin里像素太少统计意义变弱。量化完成后通常会为每张图统计量化值的直方图作为颜色特征向量这步直接对量化后的索引逐像素累加即可维度固定为81。我这里没有做归一化因为后续会把颜色特征与LBP、GLCM拼起来统一处理归一化留到拼接那步一次完成。2.2 K均值聚类在图像分割里的角色初始化与K值选择K均值聚类在整条流水线里不是用来直接分类水果品种的而是做像素级预分割——把量化后的颜色特征聚成若干区域把水果主体从背景、桌面、阴影里分离出来。水果图像的场景相对简单K取3到5通常就够取更大会把同一颗果实的渐变表面切成若干碎片随后统计区域特征时反而丢失整体性。取太小则容易把水果和相近颜色的背景糊在一起。%% K均值聚类在量化颜色特征上做图像预分割 img imread(apple.jpg); hsvImg rgb2hsv(img); % MATLAB内置转换, 返回H/S/V均在[0,1]区间 % 拉成像素向量 R size(hsvImg, 1); C size(hsvImg, 2); pixH reshape(hsvImg(:,:,1), [], 1) * 360; % H转换到角度 pixS reshape(hsvImg(:,:,2), [], 1); pixV reshape(hsvImg(:,:,3), [], 1); % 非均匀量化 [qH, qS, qV] hsv_nonuniform_quantize(pixH, pixS, pixV); featMat double([qH, qS, qV]); % K均值聚类, K取4: 背景/果实主体/果柄/阴影 rng(42); % 固定随机种子, 保证复现 k 4; [clusterIdx, ~] kmeans(featMat, k, MaxIter, 500, Replicates, 3); segMap reshape(clusterIdx, R, C);这段代码先把图像拉成像素向量逐像素量化后直接调用MATLAB的kmeans内置函数。这里有三个参数需要特别解释。Replicates3的意思是重复跑3次聚类取最优结果——K均值对初始中心极其敏感换一组随机初始化结果就可能完全不同多跑几次是最简单的后悔药。MaxIter500是单次迭代的上限如果收敛慢就加大到1000但真出现这种情况往往说明特征有问题。第三个隐含问题是特征归一化三列量化值的量纲虽然一致但qH的取值范围是0到8qS和qV是0到2欧氏距离会被qH主导这时建议先做zscore归一化再送进kmeans让三个通道的方差一致。K均值聚类的K值选择理论上是肘部法对K从2到8分别聚类计算每个K值下的组内平方和(WSS)画折线找拐点。但做图像分割时我一般直接结合任务场景判断——白背景加单颗水果K取3桌面有纹理或画面里有多颗水果K取5没有见过水果课设需要K大于7的情况。因为每多一个簇后面要处理的碎片区域就多一批特征计算量和误分割风险同时上升。这层聚类输出的segMap是整条流水线的坐标基石。后续无论是统计区域的颜色均值还是提取LBP、灰度共生矩阵都以segMap为掩码只处理果实区域而不是整幅图——背景像素对纹理特征的干扰在这里被一次性过滤掉。一个小技巧拿到segMap后可以用regionprops把面积最大的连通域挑出来作为果实主区域其他小碎片直接丢弃能显著减少后续LBP直方图的噪声。3. 纹理特征提取LBP、MBLBP与灰度共生矩阵的参数设置颜色特征解决“这是什么颜色”的问题纹理特征解决“表面长什么样”的问题。水果识别里最典型的案例就是菠萝——它的网格结构和橘子、橙子的平滑表皮在颜色上可能接近但纹理差异巨大。这一章把三种纹理特征讲透重点是参数怎么配。3.1 圆形LBP算子半径R与邻域点数P的搭配原则基础LBP在3×3邻域内比较中心像素和8个邻居的亮度大小形成8位二进制编码。但基础版有两个明显缺陷固定3×3邻域只能捕捉最细尺度的纹理而且必须落在整数像素上无法适配不同分辨率的图像。圆形LBP把邻域扩展到任意半径R采样点P均匀分布在圆周上采样点不是整数坐标时用双线性插值取亮度值。R决定纹理尺度R1捕捉果皮气孔这类精细结构R2或3捕捉菠萝网格这种中等尺度的结构P决定方向分辨率一般取8或16P太大会让直方图极度稀疏。function lbpCode circular_lbp(img, R, P) % circular_lbp: 圆形邻域LBP编码 % img: 灰度图像, R: 半径, P: 邻域采样点数 % 返回与输入图像等尺寸的LBP编码图(仅计算有效内部区域) img double(img); [h, w] size(img); lbpCode zeros(h - 2*R, w - 2*R); for p 0:P-1 theta p * 2 * pi / P; % 采样点角度均分 x R * cos(theta); y R * sin(theta); x0 floor(x); y0 floor(y); % 双线性插值坐标拆解 dx x - x0; dy y - y0; for i R1:h-R for j R1:w-R % 双线性插值取采样点亮度 xp i x0; yp j y0; v (1-dy)*((1-dx)*img(xp, yp) dx*img(xp, yp1)) ... dy*((1-dx)*img(xp1, yp) dx*img(xp1, yp1)); if v img(i, j) lbpCode(i-R, j-R) lbpCode(i-R, j-R) 2^p; end end end end end双线性插值是这段代码的关键因为采样点坐标通常是浮点数比如R1、P8时45度方向的采样点落在(0.7, 0.7)附近必须用周围4个整数像素的加权和来近似。实际使用时有两层时间开销P越大外层循环次数越多图像越大内层双重循环越慢。在水果识别场景中我一般先把图像缩放到256×256再计算LBP纹理特征对分辨率没有那么敏感但计算量能省四分之三。关于旋转不变性基础LBP编码对图像旋转敏感同一片果皮转个角度编码就不同。解决办法是循环右移取最小位串把256种模式压缩到36种旋转不变模式。这里有个取舍——旋转不变性减少了直方图维度、增强了鲁棒性但丢失了方向信息。菠萝网格是有方向性的纹理旋转不变反而可能削弱它的判别能力所以是否启用旋转不变要看具体水果类型。3.2 MBLBP的块尺寸与特征维度控制MBLBP(多块LBP)把比较单元从单个像素换成块均值。普通LBP的编码单元是中心像素和邻域像素逐一比较MBLBP则把中心点和邻域点都替换成一个小块的灰度均值——这个块大小由blockSize参数控制。块均值天然具有抗噪能力能抑制果实表面的细碎反光也更适合描述菠萝网格这种“区域性”纹理而非“点状”纹理。function mblockFeat mblbp_histogram(img, blockSize, P, R) % mblbp_histogram: 多块LBP直方图特征提取 % blockSize: 块边长(像素), P: 邻域块数, R: 邻域块间距(以块为单位) [h, w] size(img); padSize floor(blockSize/2); imgPad padarray(img, [padSize padSize], replicate); % 均值池化: 逐像素取周围块均值 meanMap zeros(h, w); for i 1:h for j 1:w block imgPad(i:iblockSize-1, j:jblockSize-1); meanMap(i, j) mean(block(:)); end end % 在均值图上计算圆形LBP, R的单位从像素变为块 meanMap mat2gray(meanMap); lbpMap circular_lbp(meanMap, R, P); mblockFeat hist(lbpMap(:), 0:(2^P-1)); end这段代码先做均值池化得到meanMap再对池化图调用前面写的circular_lbp函数。需要注意R的单位在这里已经变了——输入meanMap之后R指的是“隔几个块取邻域”不是“隔几个像素”。blockSize一般取3到5P8时直方图有256维。如果原始图像分辨率不高256维直方图里会有大量空bin更合理的做法是把LBP值按照uniform模式分组把256维压缩到59维。Uniform模式的定义是二进制编码中0到1或1到0的跳变次数不超过2实验表明自然纹理图像中绝大多数像素都属于这类。这个压缩在中等尺寸图片上几乎不损失准确率但直方图稀疏性问题大幅缓解。MBLBP和普通LBP在水果识别里的区别直观感受是普通LBP对猕猴桃表面的绒毛敏感MBLBP对菠萝网格这类有结构性重复的纹理更有效。实际项目里最常见的做法是把两者都算出来直方图拼在一起用——这也意味着特征维度直接翻倍必须留到后面的粒子群优化环节做选择不能无脑全喂给分类器。3.3 灰度共生矩阵灰度压缩、偏移方向与统计量选择灰度共生矩阵(GLCM)统计的是图像中相距某个偏移量(dx, dy)的两个像素共同出现的灰度概率分布。它不像LBP那样编码局部微观模式而是描述灰度在特定方向和距离上的空间关系。落地时三个参数最关键灰度级压缩、偏移方向和统计量组合。function glcmFeat glcm_features_4dir(imgGray, grayLevels, offsetDist) % glcm_features_4dir: 四方向灰度共生矩阵统计量提取 % imgGray: 灰度图, grayLevels: 灰度压缩级数(建议16), offsetDist: 偏移距离(建议1) % 灰度级压缩到[1, grayLevels] imgQ im2uint8(mat2gray(imgGray)); imgQ floor(double(imgQ) * (grayLevels-1) / 255) 1; % 四方向偏移: 0度/45度/90度/135度 offsets [0 offsetDist; -offsetDist offsetDist; -offsetDist 0; -offsetDist -offsetDist]; glcms graycomatrix(imgQ, NumLevels, grayLevels, ... Offset, offsets, Symmetric, true); stats graycoprops(glcms, {Contrast, Correlation, Energy, Homogeneity}); % 取四方向均值, 组成1x4的行向量, 最终特征长度16 glcmFeat [mean(stats.Contrast, 2); mean(stats.Correlation, 2); mean(stats.Energy, 2); mean(stats.Homogeneity, 2)]; end灰度压缩是GLCM计算的第一步原图255级灰度如果直接算矩阵会生成255×255的共生矩阵绝大多数格子是零既稀疏又慢。压缩到16级能保留主要的纹理对比信息同时把矩阵降到16×16计算量减少两个数量级。Symmetrictrue的含义是同时统计方向相反的两个偏移对等效于把四方向扩展到八方向但计算量不变。偏移距离offsetDist取1即可——水果表面纹理大多是近邻级别的取2或3会让灰度对的距离变长统计量从“相邻纹理”变成“相隔纹理”物理含义改变后反而不利于分类。四个统计量的物理意义要区分清楚。对比度衡量纹理沟纹的深浅数值大说明相邻像素亮度差异大菠萝的网格纹理对比度远高于苹果光滑表面。相关性衡量灰度在行或列方向的线性相关程度方向性强的纹理这个值波动明显。能量(角二阶矩)反映灰度分布的均匀程度平滑区域的能量值高。同质性(逆差矩)描述纹理局部变化的平缓程度。四个统计量拼成16维特征向量和LBP的256维直方图相比极其精简但在纹理分类里扮演着互补角色尤其适合区分表面颜色相近但质地不同的水果。4. 粒子群优化在这里到底干什么特征选择与自适应参数寻优粒子群优化算法在这套程序里的位置经常被搞错。它不是用来训练分类器也不是用来识别水果的而是用来解决两个让手工调参崩溃的问题特征选择和高维参数寻优。把颜色81维、LBP直方图256维、MBLBP直方图256维、GLCM统计量16维拼起来特征总数轻松超过600维而课程设计通常只有几百张训练图这个维度下分类器表现会急剧恶化。4.1 PSO特征选择粒子编码与适应度函数设计PSO做特征选择时每个粒子代表一个特征子集的候选方案。最直接的编码方式是二进制粒子位置向量长度等于特征总数每一位取1代表保留该特征取0代表丢弃。但标准PSO的速度-位移更新公式是为连续空间设计的直接套二进制需要做映射处理。我惯用的做法是让粒子在连续空间[0,1]里飞行适应度函数内部用0.5阈值把连续权值转成二值掩码——连续搜索加离散评估既保持了PSO的运动学特性又拿到特征选择的离散结果。function [bestMask, bestAcc] pso_feature_selection(featMat, labels, swarmSize, maxIter) % pso_feature_selection: 粒子群特征选择 % featMat: NxD特征矩阵, labels: Nx1标签 % 返回最优特征掩码和对应准确率 nFeat size(featMat, 2); w 0.9; wEnd 0.4; % 惯性权重线性递减区间 c1 2; c2 2; % 个体学习因子与群体学习因子 pos rand(swarmSize, nFeat); vel 0.1 * randn(swarmSize, nFeat); pBestPos pos; pBestVal inf(swarmSize, 1); for i 1:swarmSize pBestVal(i) fitness_eval(pos(i,:), featMat, labels); end [bestAcc, gBestIdx] min(pBestVal); gBestPos pBestPos(gBestIdx, :); for iter 1:maxIter wIter w - (w - wEnd) * iter / maxIter; % 惯性权重递减 for i 1:swarmSize r1 rand(1, nFeat); r2 rand(1, nFeat); vel(i,:) wIter * vel(i,:) ... c1 * r1 .* (pBestPos(i,:) - pos(i,:)) ... c2 * r2 .* (gBestPos - pos(i,:)); pos(i,:) pos(i,:) vel(i,:); pos(i,:) max(0, min(1, pos(i,:))); % 位置投影回[0,1] curVal fitness_eval(pos(i,:), featMat, labels); if curVal pBestVal(i) pBestVal(i) curVal; pBestPos(i,:) pos(i,:); end if curVal bestAcc bestAcc curVal; gBestPos pos(i,:); end end end bestMask gBestPos 0.5; % 按阈值转成二值掩码 end function errRate fitness_eval(weightVec, featMat, labels) % 适应度: 按0.5阈值选中特征后做5折KNN交叉验证误差率 mask weightVec 0.5; if sum(mask) 0 errRate 1; % 没有选中任何特征, 直接罚到最差 return; end subFeat featMat(:, mask); mdl fitcknn(subFeat, labels, NumNeighbors, 5, KFold, 5); errRate kfoldLoss(mdl); end这段代码里有三个设计决策值得展开。惯性权重w从0.9线性递减到0.4是最经典的PSO配置迭代前期惯性大粒子做全局搜索避免早熟后期惯性小粒子在最优解附近精细打磨。c1和c2控制的是“向自己历史最优飞”和“向群体最优飞”的加速度两者都取2是经典默认没有特殊理由不建议改动。适应度函数用5折交叉验证的KNN误差率——KNN不用训练、对特征子集的区分度响应直接是特征选择阶段最合适的评估器。实际运行时有个性能陷阱每次解算粒子适应度都要跑一次5折KNN如果特征维度600维、训练样本300条单代30个粒子的等待时间是数秒级别50代迭代就是十几分钟。缩短时间的实际做法是先对特征矩阵做主成分降维到60维以下再交PSO或者把5折交叉验证换成单次训练/验证集划分。PSO在这里追求的不是统计严谨性而是找一个远好于随机猜测的特征子集这个工程判断很重要。4.2 粒子群在连续参数寻优里的用法调GLCM、LBP和K值除了特征选择粒子群的第二个用途是连续参数寻优。GLCM的偏移距离、LBP的半径R和邻域点数P、K均值聚类的K值这些超参数在手工配置时靠经验在粒子群眼里就是一个多维连续优化问题。思路是把特征提取过程封装成一个函数句柄PSO负责在该函数句柄的参数空间里搜索最低分类误差的组合。function [bestParams, bestAcc] pso_continuous_tune(featExtractFunc, labels, lb, ub, swarmSize, maxIter) % pso_continuous_tune: 连续参数寻优 % featExtractFunc: 函数句柄, 输入参数向量, 输出特征矩阵 % lb/ub: 参数的搜素下界和上界 nDim length(lb); pos rand(swarmSize, nDim) .* (ub - lb) lb; vel 0.1 * randn(swarmSize, nDim); % 主循环与标准PSO一致, 略 % 适应度评估时调用: featMat featExtractFunc(round(pos(i,:))); % 其余逻辑同特征选择 bestParams round(gBestPos); end这种参数寻优方式最大的问题不是优化算法本身而是适应度函数太贵——每一代每个粒子都要重新对整库图像提取一次特征如果库里有500张图提取特征阶段每张图耗时2秒单代30个粒子就是500秒级别的等待。所以做参数寻优时我通常分两步先用随机网格搜索跑50组参数快速圈定平台区域再在这个小邻域里启用PSO精搜。PSO不怕高维连续参数但怕昂贵的适应度函数这是实际项目中最重要的边界条件。另外round(pos(i,:))这一步把连续值离散化是必须的——GLCM偏移距离1.3没有物理意义离散化后评估才能代表真实部署时的表现。5. 避坑清单水果识别程序开发中最常见的5个翻车点以下每一条都是水果识别项目里反复出现的真实问题按现象、原因、解决的顺序记录新手可以直接对照排查。5.1 HSV非均匀量化后特征区分度反而下降现象用上面这套量化代码红苹果和红番茄的颜色直方图余弦相似度超过0.9分类器直接无法区分。原因非均匀量化设计过于激进。S大于0.5后H被量化成8段每段45度这45度里包含了粉红、正红、暗红、橙红一大片色域而V通道的分区只有3档没能捕捉到明暗变化带来的视觉差异。另外jpg压缩会在果实边缘产生伪影导致个别像素S值从高饱和区跌到弱色区量化后H被直接归零。解决把S的高饱和判断阈值从0.5下调到0.35让更多像素进入细分通道。同时检查图像保存格式——jpg质量参数从90降到75时果实边缘的S值波动可能达到0.2这种损失是后续任何算法都无法弥补的。建议统一用png格式保存实验图像。5.2 K均值聚类每次运行结果不一致现象同一张图片同一条代码上一次把苹果分割成四个区域这一次苹果和背景糊成一团。原因kmeans的初始聚类中心是随机选择的不同初始值收敛到不同的局部最优解。Replicates3虽然重跑几次取最优但治标不治本。另一个深层原因是三列量化特征没归一化H列取值范围0到9S和V列0到2欧氏距离被H主导聚类结果偏向色相轴。解决先对特征矩阵做zscore归一化再聚类。更强的做法是先对整个像素集合做一次mini-batch粗聚类把得到的中心作为正式K均值聚类的Start参数这样初始中心有物理含义收敛稳定。rng(42)固定随机种子只保证本地复现换机器结果仍会不同要彻底可复现就务必传入初始中心。5.3 LBP直方图维度太高导致过拟合现象132张训练图LBP加MBLBP加GLCM拼出700多维特征KNN训练集准确率98%测试集掉到不足60%。原因高维小样本是分类器的大敌。700维特征空间里132条样本几乎每一条都独占一个区域欧氏距离度量失真最近邻变成了“最近噪声”。LBP直方图的256个bin里不少是零值这些零维度不提供任何判别信息只增加距离计算的干扰。解决无条件降维是第一步用PCA先压到50维以内。同时把LBP直方图改为uniform模式压缩到59维不损失精度还能大幅缓解稀疏性。直方图特征向量拼起来之前每段先做L1归一化让不同数量级的特征不互相压制。5.4 GLCM计算耗时过长现象512×512的图像用for循环手写灰度共生矩阵单张要跑20秒整库图片处理完需要几小时。原因三层for循环的行、列、灰度对遍历是性能灾难MATLAB的循环解释执行扛不住这种规模。另一个常见错误是把灰度级设成256——共生矩阵是256×256绝大多数格子是零矩阵构建过程中的稀疏访问开销远远超过实际计算量。解决直接用工具箱的graycomatrix内置函数不要手写三层循环。灰度级必须压缩到16或8。如果确实需要手写把灰度对累加改用accumarray向量化实现单张图耗时能从20秒压到2秒以下。图像先缩放到256×256再算GLCM纹理特征的判别能力几乎不受影响计算量减少约四分之三。5.5 粒子群优化训练时间长到无法接受现象粒子群迭代到20代后训练明显停滞跑完50代要两个小时调参等于受刑。原因适应度函数里嵌套了5折交叉验证的KNN每一次模型拟合都在700维特征上执行。粒子数30乘以迭代次数50再乘5折相当于7500次模型拟合这个计算量呈线性放大。维度高会让每次拟合本身变慢双重因素叠加后不可收拾。解决在PSO阶段放弃5折改用单次训练集/验证集划分速度提升约5倍。验证集误差虽然比交叉验证有偏差但用于PSO内部的相对比较完全够用。更彻底的做法是先PCA降维再进PSO700维降到60维后粒子的收敛速度和最终精度都有改善。6. 把全部模块串成一条可复现的识别管线从图片输入到分类结果前面所有模块最终要合成一条能对单张图片输出预测结果的流水线。下面给一个最小可运行的主函数框架参数通过modelParams结构体一次性传入方便整体复用和调参。function [predLabel, score] fruit_pipeline(imgPath, modelParams) % fruit_pipeline: 完整识别管线 % modelParams: 包含segK, lbpR, lbpP, blockSize, glcmLevel, glcmDist, featMask % 第一步: 读图并转HSV, 非均匀量化 K均值得到分割掩码 img imread(imgPath); hsvImg rgb2hsv(img); % ... 量化与分割过程同2.2节, 得到segMap % 第二步: 以segMap为掩码, 只对果实区域提取三路特征 % 颜色: 81维HSV量化直方图 % 纹理: LBP MBLBP GLCM统计量 % 使用modelParams中的半径、块尺寸、灰度级等参数 % 第三步: 拼接特征, 用modelParams.featMask做特征选择 featVec [colorFeat, lbpFeat, mblbpFeat, glcmFeat]; featVec featVec(modelParams.featMask); % 第四步: 归一化后送入训练好的KNN模型 featVec (featVec - modelParams.mu) ./ modelParams.sigma; [predLabel, score] predict(modelParams.knnModel, featVec); end这条管线的设计顺序有讲究分割掩码必须在特征提取之前完成因为LBP和GLCM一旦把背景像素卷进来特征就被桌面纹理污染了。我通常用segMap的最大连通域作为果实区域掩码regionprops可以一次性拿到所有连通域的面积和边界框过滤掉小碎片再进入特征提取——这一步对菠萝这类表面复杂的水果格外重要。关于验证方法我习惯用一个简单但有效的手段把整库图片随机打乱后取20%做测试集训练时只更新modelParams.featMask和KNN模型其他参数固定。主跑一次之后再用混淆矩阵看每个水果类别之间的互相误判情况——苹果和番茄互混说明颜色特征权重过高菠萝和杨桃互混说明纹理特征还需要调LBP的半径和块尺寸。这个项目做到最后我最深的感受是拿到任何图像处理的课程设计先写一条从图片到预测的完整流水线再回头逐模块调参。管线先立住后面每个坑才有对照物否则参数调来调去都不知道是哪一段引入了误差。希望帮到你。本文还有配套的精品资源点击获取