☰
基于HSV颜色空间与LBP纹理特征的水果图像识别系统Matlab实现
2026/10/7 7:15:52 网站建设 项目流程

简介:这是一套面向图像处理与计算机视觉初学者及课程设计者的水果识别MATLAB实践系统,聚焦纹理特征建模与无监督分类任务,适用于数字图像处理、模式识别等课程实验或本科毕设参考。资源包含LBP与MBLBP纹理算子实现、HSV色彩空间非均匀量化、K均值聚类核心算法,以及灰度共生矩阵等进阶特征提取模块,完整覆盖从预处理、特征融合到聚类判别的全流程。压缩包共5468个文件,主体为5004张BMP格式水果样本图像(含多角度、光照与遮挡变化),辅以229个PNG标注图、95个MATLAB源码(.m)、13个预训练数据(.mat)及可视化结果(.fig),整体容量88.29MB,结构清晰、模块解耦,便于逐层调试与功能替换。已有147人学习下载,提供可直接运行的主流程脚本、详细注释的子函数、典型水果(如苹果、橙子、香蕉)的聚类效果对比图及参数调优说明,显著降低复现门槛。

1. 项目概述:从“看”到“识”的水果图像识别系统

最近在整理硬盘,翻出来一个几年前做的Matlab项目,一个基于多种纹理和颜色特征融合的水果识别系统。当时做这个,主要是为了解决一个朋友在小型自动化分拣线上的需求——如何用摄像头快速、准确地识别不同种类的水果。市面上成熟的工业视觉方案太贵,而简单的颜色识别又容易受光照和背景干扰。于是,我就琢磨着把几种经典的图像特征提取方法组合起来,用Matlab快速搭建一个原型验证系统。

这个系统的核心思路很直接:模仿人眼和大脑的识别过程。我们看一个苹果,会注意到它的颜色(红色、绿色)、表面的纹理(光滑或有斑点)、以及大概的形状。这个系统就是把“颜色”交给HSV颜色空间去量化,把“纹理”交给LBP(局部二值模式)和MB-LBP(多块局部二值模式)去描述,最后用K均值聚类把这些特征“打包”成不同的类别,从而实现识别。整个项目源码打包成了一个.zip文件,包含了从图像预处理、特征提取、聚类训练到识别测试的全套Matlab脚本和函数。

如果你正在学习数字图像处理、模式识别,或者想用Matlab做一个完整的、有实际应用场景的课程设计或毕业设计,这个项目会是一个很好的切入点。它不涉及过于深奥的神经网络,而是立足于扎实的传统图像处理方法,能让你透彻理解特征工程在识别任务中的核心地位。接下来,我就把这个系统的设计思路、关键技术的实现细节、以及我踩过的一些坑,毫无保留地分享给你。

2. 系统核心设计思路与方案选型

做一个识别系统,首要问题是:用什么特征来区分不同水果?水果之间的差异,无外乎颜色、纹理、形状、大小。在静态图像识别中,形状和大小容易受拍摄角度、距离影响,鲁棒性较差。因此,我选择了以颜色和纹理作为核心特征,这两种特征相对稳定,且计算效率高。

2.1 为什么选择HSV而非RGB?

我们最常接触的是RGB颜色模型,但它有个致命缺点:对光照变化极其敏感。同一个红苹果,在强光下和阴影下,其RGB值会相差很大。这会给识别带来巨大干扰。

HSV颜色模型将颜色信息分解为:

  • H(色相):颜色的种类,如红、黄、绿。这是颜色最本质的属性,受光照影响小。
  • S(饱和度):颜色的纯度或鲜艳程度。
  • V(明度):颜色的明亮程度。

在水果识别中,色相(H)通道最能稳定地表征水果的固有颜色。一个成熟的草莓无论是放在亮处还是暗处,它的“红色”属性(H值)是相对恒定的。因此,提取HSV空间的H分量直方图作为颜色特征,是比RGB直方图稳健得多的选择。

注意:Matlab的rgb2hsv函数转换后,H、S、V的取值范围是[0, 1]。在计算直方图时,通常将H分量量化到若干个区间(例如0-179,模仿OpenCV的0-180),以减少计算量并增强鲁棒性。

2.2 纹理特征:LBP与MB-LBP的互补组合

颜色能区分橙子和香蕉,但区分蛇果和富士苹果(都是红色)就力不从心了。这时就需要纹理特征。

LBP(局部二值模式)是纹理分析的“瑞士军刀”。它的原理非常巧妙:对于一个像素点,比较它和周围3x3邻域内8个像素的灰度值,比中心亮的记为1,否则记为0。这样就得到一个8位的二进制数,再转换为十进制,作为该中心点的纹理编码。统计整幅图像所有LBP编码的直方图,就得到了图像的纹理特征。LBP计算简单,对灰度变化不敏感。

但基础LBP也有局限:它只关注微小的局部纹理,对更大尺度、更具结构性的纹理模式捕捉能力不足。例如,橙子表面的毛孔状纹理和香蕉皮的纵向条纹,在宏观上差异明显。

于是,我引入了MB-LBP(多块局部二值模式)。MB-LBP不再比较单个像素,而是比较图像中两个小矩形区域(Block)的平均灰度值。通过改变矩形块的大小和位置,MB-LBP能够捕捉从细微到宏观不同尺度的纹理信息。它就像是LBP的“升级版”,视野更广,对纹理结构的描述能力更强。

将LBP和MB-LBP的特征直方图拼接起来,就形成了一组既能描述微观纹理细节,又能刻画宏观纹理结构的复合纹理特征向量。这大大提升了系统对纹理相似但尺度不同的水果的区分能力。

2.3 分类器:为什么用K均值聚类而非SVM或神经网络?

有了特征,下一步就是分类。常见的分类器有SVM(支持向量机)、决策树、神经网络等。但我这里选择了K均值聚类。主要基于以下几点考虑:

  1. 项目阶段与目标:这是一个原型验证系统。K均值聚类是一种无监督学习方法,不需要大量精确标注的数据来训练一个复杂的分类模型。我只需要准备一些水果图片,系统能自动将它们聚成几类,我再去检查每一类对应哪种水果即可。这非常适合快速验证特征的有效性。
  2. 直观与可解释性:K均值聚类的过程非常直观——物以类聚。我可以清晰地看到,在特征空间里,苹果的特征向量彼此靠近,与香蕉的特征向量距离较远。这有助于我直观地分析和调试特征提取的效果。
  3. 计算效率:相对于训练一个SVM或小型神经网络,K均值聚类的计算开销更小,在Matlab中实现和迭代速度很快。
  4. 灵活性:如果我想增加一种新的水果,只需要将其图片加入数据集,重新运行一次K均值聚类即可。而监督学习模型则需要重新标注数据并训练。

当然,K均值聚类的缺点是要求预先指定类别数K,且对初始聚类中心敏感,对非球形分布的数据效果不佳。但在当前这个颜色和纹理特征分布相对明确的水果识别场景中,它完全够用,且优势明显。

整个系统的流程可以概括为:输入图像 -> 预处理(缩放、去噪)-> 分别提取HSV颜色直方图特征、LBP纹理直方图特征、MB-LBP纹理直方图特征 -> 将三个特征向量拼接成一个高维特征向量 -> 将所有训练图片的特征向量集合输入K均值算法进行聚类 -> 保存聚类中心(即“模板”)-> 识别新图片时,提取其特征向量,计算与各个聚类中心的距离,距离最近的类别即为识别结果。

3. 关键技术细节与Matlab实现解析

理论说清楚了,我们来看看在Matlab里具体怎么实现。这里会涉及一些关键函数和参数设置,我会解释为什么这么选。

3.1 图像预处理与HSV特征提取

预处理的第一步通常是统一尺寸和去噪。我用imresize函数将所有输入图像缩放到一个固定大小(比如256x256),这保证了后续提取的特征维度一致。对于简单的椒盐噪声,可以使用中值滤波medfilt2。

HSV特征提取的核心代码如下:

function colorHist = extractHSVFeature(imgRGB) % 转换为HSV颜色空间 imgHSV = rgb2hsv(imgRGB); % 提取H分量 H = imgHSV(:,:,1); % 将H分量从[0,1]量化到0-179(共180个bin) % 乘以179并取整,+1是为了让索引从1开始 H_quantized = floor(H * 179) + 1; % 计算直方图,180个bin colorHist = histcounts(H_quantized(:), 1:181); % 可选:进行L1或L2归一化,消除图片大小的影响 colorHist = colorHist / norm(colorHist, 1); % L1归一化,总和为1 end

实操心得:histcounts比旧的imhist函数更灵活高效。量化到180个bin是一个经验值,在计算复杂度和特征区分度之间取得了平衡。一定要做归一化,否则一张大图和一张小图的特征向量会因像素总数不同而失去可比性,严重影响聚类效果。

3.2 LBP纹理特征提取

Matlab图像处理工具箱中没有现成的LBP函数,需要自己实现。

function lbpHist = extractLBPFeature(imgGray) % imgGray 是灰度图像 [rows, cols] = size(imgGray); lbpCode = zeros(rows-2, cols-2, 'uint8'); % 边界不处理 % 3x3邻域LBP计算 for i = 2:rows-1 for j = 2:cols-1 center = imgGray(i, j); code = 0; % 定义8个邻域位置(从左上角开始顺时针) neighbors = [imgGray(i-1, j-1), imgGray(i-1, j), imgGray(i-1, j+1), ... imgGray(i, j+1), imgGray(i+1, j+1), imgGray(i+1, j), ... imgGray(i+1, j-1), imgGray(i, j-1)]; for k = 1:8 if neighbors(k) >= center code = code + 2^(k-1); % 注意索引,也可以按bit位赋值 end end lbpCode(i-1, j-1) = code; end end % 计算LBP直方图,经典LBP有256种可能值(2^8) lbpHist = histcounts(lbpCode(:), 0:256); lbpHist = lbpHist / norm(lbpHist, 1); % 归一化 end

注意事项:上述是最基础的LBP,实际中更常用的是“均匀模式”(Uniform LBP),它能将256维特征降至59维(58个均匀模式+1个非均匀模式混合),既能降维又能提升鲁棒性。在项目中,我强烈建议你实现均匀LBP。判断一个LBP编码是否为均匀模式的标准是:其二进制串中0/1的变化次数是否不超过2次。例如00111100(变化2次)是均匀的,而01010101(变化7次)则不是。

3.3 MB-LBP纹理特征提取

MB-LBP的实现比LBP稍复杂,关键在于计算图像积分图,以快速计算矩形区域的平均灰度。

function mblbpHist = extractMBLBPFeature(imgGray, blockSize) % blockSize: 用于比较的矩形块大小,例如3或5 [rows, cols] = size(imgGray); % 1. 计算积分图 intImage = integralImage(imgGray); % Matlab自带函数 % 2. 定义MB-LBP算子:这里以3x3的块为例,比较中心块与周围8个块 % 每个块的大小是 blockSize x blockSize radius = blockSize; mblbpCode = zeros(floor(rows/(3*radius)), floor(cols/(3*radius)), 'uint8'); idx = 1; for i = 1+radius: 3*radius: rows - 3*radius for j = 1+radius: 3*radius: cols - 3*radius % 计算中心块的平均灰度 centerBlockMean = computeBlockMean(intImage, i, j, radius); code = 0; % 定义周围8个块的位置(相对位置) positions = [-radius, -radius; -radius, 0; -radius, radius; 0, radius; radius, radius; radius, 0; radius, -radius; 0, -radius]; for k = 1:8 r = i + positions(k,1); c = j + positions(k,2); neighborBlockMean = computeBlockMean(intImage, r, c, radius); if neighborBlockMean >= centerBlockMean code = code + 2^(k-1); end end mblbpCode(idx) = code; idx = idx + 1; end end % 3. 计算直方图并归一化 mblbpHist = histcounts(mblbpCode(:), 0:256); mblbpHist = mblbpHist / norm(mblbpHist, 1); end function blockMean = computeBlockMean(intImage, startRow, startCol, size) % 利用积分图快速计算矩形区域和 % 积分图坐标是1-based,且区域和公式为:Sum = D + A - B - C % 其中A、B、C、D是积分图在矩形四个角的像素值 r1 = startRow - 1; c1 = startCol - 1; r2 = startRow + size - 1; c2 = startCol + size - 1; A = intImage(r1, c1); B = intImage(r1, c2); C = intImage(r2, c1); D = intImage(r2, c2); blockSum = D + A - B - C; blockMean = blockSum / (size * size); end

关键参数解析:blockSize是MB-LBP最重要的参数。blockSize较小(如3),捕捉的是中等尺度的纹理;blockSize较大(如7或9),则捕捉更大尺度、更全局的纹理结构。在实际项目中,我通常会提取多个不同blockSize的MB-LBP特征然后拼接,形成多尺度纹理描述子,效果更好,但计算量也会增加。

3.4 特征融合与K均值聚类

将上述三个特征向量(HSV直方图、LBP直方图、MB-LBP直方图)拼接起来:

function featureVector = fuseFeatures(hsvHist, lbpHist, mblbpHist) % 简单拼接 featureVector = [hsvHist, lbpHist, mblbpHist]; % 更优的做法:加权拼接 % 因为不同特征的数值范围和重要性可能不同 % weight_color = 0.4; weight_texture = 0.6; % featureVector = [weight_color * hsvHist, weight_texture * lbpHist, weight_texture * mblbpHist]; end

然后,对训练集所有图片提取特征,形成一个N x D的矩阵(N是图片数,D是特征维度)。使用Matlab的kmeans函数进行聚类。

% 假设 allFeatures 是 N x D 的特征矩阵 k = 5; % 假设我们有5种水果 [clusterIdx, clusterCenters] = kmeans(allFeatures, k, 'Distance', 'sqeuclidean', 'Replicates', 10);
  • 'Distance', 'sqeuclidean':使用平方欧氏距离,这是最常用的,计算量小。
  • 'Replicates', 10:这个参数至关重要。K均值聚类的结果受初始随机聚类中心影响很大。设置Replicates为10,意味着Matlab会用不同的初始中心运行聚类算法10次,然后选择其中总距离和最小的那次结果作为最终输出。这能有效避免因坏运气而得到次优解。

聚类完成后,clusterCenters就是一个k x D的矩阵,每一行代表一种水果的“平均特征模板”。保存这个矩阵和对应的水果标签,识别阶段就是计算新图片特征与所有模板的距离,找最小值。

4. 完整系统搭建与核心流程实现

有了各个模块,我们需要把它们串成一个完整的、可用的系统。这个系统通常分为两个主要阶段:训练阶段和识别阶段。

4.1 训练阶段流程与代码组织

训练阶段的目标是生成聚类中心(模板)和标签映射。我的项目文件结构通常如下:

fruit_recognition_system/ ├── data/ │ ├── train/ │ │ ├── apple/ │ │ ├── banana/ │ │ └── orange/ │ └── test/ ├── src/ │ ├── extractHSVFeature.m │ ├── extractLBPFeature.m │ ├── extractMBLBPFeature.m │ ├── fuseFeatures.m │ ├── trainSystem.m │ └── predictFruit.m └── main.m

trainSystem.m脚本的核心逻辑:

% 1. 设置路径和参数 trainDataPath = 'data/train/'; categories = {'apple', 'banana', 'orange', 'strawberry', 'lemon'}; k = length(categories); % 类别数 allFeatures = []; trueLabels = {}; % 2. 遍历所有训练图片 for catIdx = 1:length(categories) category = categories{catIdx}; folderPath = fullfile(trainDataPath, category); imgFiles = dir(fullfile(folderPath, '*.jpg')); % 假设是jpg格式 for i = 1:length(imgFiles) % 读取并转换为灰度图(用于纹理)和RGB(用于颜色) imgPath = fullfile(folderPath, imgFiles(i).name); imgRGB = imread(imgPath); imgGray = rgb2gray(imgRGB); % 3. 提取特征 hsvFeat = extractHSVFeature(imgRGB); lbpFeat = extractLBPFeature(imgGray); mblbpFeat = extractMBLBPFeature(imgGray, 3); % 使用3x3的块 % 4. 特征融合 fusedFeat = fuseFeatures(hsvFeat, lbpFeat, mblbpFeat); % 5. 收集特征和标签 allFeatures = [allFeatures; fusedFeat]; trueLabels{end+1} = category; end end % 6. 执行K均值聚类 [clusterIdx, clusterCenters] = kmeans(allFeatures, k, 'Distance', 'sqeuclidean', 'Replicates', 10, 'MaxIter', 1000); % 7. 建立聚类中心与水果标签的映射(这是关键且需要人工干预的一步) % K均值给出的clusterIdx是自动分配的,我们需要知道每个簇对应哪种水果。 labelMap = containers.Map('KeyType', 'int32', 'ValueType', 'char'); % 通常采用“投票法”:统计每个簇中出现次数最多的真实标签 for clu = 1:k indices = find(clusterIdx == clu); labelsInThisCluster = trueLabels(indices); % 使用mode函数找出众数 [dominantLabel, ~] = mode(labelsInThisCluster); labelMap(clu) = dominantLabel{1}; % 存储映射关系 end % 8. 保存模型(模板和映射) save('fruit_model.mat', 'clusterCenters', 'labelMap', 'categories'); disp('训练完成,模型已保存为 fruit_model.mat');

4.2 识别阶段(预测)流程

识别阶段就是加载模型,对新图片进行同样的特征提取,然后与所有模板比较。

predictFruit.m函数:

function predictedLabel = predictFruit(imgPath, modelPath) % 加载模型 load(modelPath, 'clusterCenters', 'labelMap'); % 假设模型文件包含这两个变量 % 读取并预处理图片 imgRGB = imread(imgPath); imgGray = rgb2gray(imgRGB); % 提取特征(必须与训练时完全一致!) hsvFeat = extractHSVFeature(imgRGB); lbpFeat = extractLBPFeature(imgGray); mblbpFeat = extractMBLBPFeature(imgGray, 3); testFeature = fuseFeatures(hsvFeat, lbpFeat, mblbpFeat); % 计算与所有聚类中心的距离 numClusters = size(clusterCenters, 1); distances = zeros(1, numClusters); for i = 1:numClusters % 计算欧氏距离 distances(i) = norm(testFeature - clusterCenters(i, :)); end % 找到距离最小的聚类中心索引 [~, minIdx] = min(distances); % 根据映射表得到预测标签 predictedLabel = labelMap(minIdx); end

4.3 主程序与交互示例

一个简单的main.m脚本可以这样写,用于演示整个流程:

% main.m clear; clc; % 阶段一:训练(如果已有模型,可跳过) fprintf('=== 水果识别系统训练阶段 ===\n'); trainSystem; % 运行训练脚本 % 阶段二:测试单张图片 fprintf('\n=== 水果识别系统测试阶段 ===\n'); model = 'fruit_model.mat'; testImg = 'data/test/test_apple_1.jpg'; predicted = predictFruit(testImg, model); fprintf('识别结果:图片【%s】中的水果是【%s】\n', testImg, predicted); % 显示图片和结果 img = imread(testImg); figure; imshow(img); title(['识别结果: ', predicted]);

5. 实战调试、常见问题与性能优化

理论跑通只是第一步,让系统在实际图片上稳定工作才是挑战。下面分享几个我调试过程中遇到的典型问题和优化技巧。

5.1 特征维度爆炸与降维处理

HSV(180维)+ LBP(256维)+ MB-LBP(256维) = 692维特征。对于只有几十张训练图片的小数据集来说,这很容易陷入“维数灾难”,即特征空间过于稀疏,导致聚类效果不稳定。

解决方案:

  1. 特征选择:不是所有维度都有用。可以使用简单的方差过滤,移除那些在所有样本上取值变化很小的特征维度(例如,某一直方图bin的值在所有图片中都接近0)。
  2. 使用均匀LBP:如前所述,将256维LBP降至59维。
  3. PCA主成分分析:这是最有效的降维方法之一。在训练阶段,对所有特征进行PCA分析,保留累计贡献率(例如95%)以上的主成分。在识别阶段,新特征也要投影到同样的PCA空间。
% 在trainSystem.m的聚类之前加入PCA [coeff, score, latent, ~, explained] = pca(allFeatures); % 计算累计贡献率,决定保留前多少个主成分 cumulative = cumsum(explained); retainDims = find(cumulative >= 95, 1); % 保留贡献率95%以上的成分 allFeaturesReduced = score(:, 1:retainDims); % 使用 allFeaturesReduced 进行聚类 % 同时必须保存 coeff 和 retainDims,用于后续预测时的同样变换

5.2 光照与背景干扰

这是图像识别永恒的主题。我们的系统对光照有一定鲁棒性(得益于HSV),但复杂的背景会引入大量无关纹理和颜色噪声。

解决方案:

  1. 前景分割(至关重要!):在特征提取前,尽可能将水果从背景中分割出来。对于放在纯色(如黑色、白色) conveyor belt 上的水果,可以用简单的颜色阈值法。对于复杂背景,可以尝试GrabCut等交互式分割算法,或者使用深度学习分割模型(如U-Net),但这超出了本项目的范畴。一个简单的改进是:在计算直方图前,先创建一个水果区域的掩膜(mask),只对掩膜内的像素进行统计。
  2. 颜色校正:如果光照条件极端,可以考虑使用简单的灰度世界算法或Retinex算法进行颜色恒常性处理,校正图像颜色。
  3. 数据增强:对训练集图片进行随机的亮度、对比度微调,模拟不同光照条件,可以让模型更鲁棒。

5.3 K均值聚类的陷阱与调优

  1. 如何确定K值?我们已知水果种类数,所以K是明确的。如果未知,可以使用“肘部法则”:绘制不同K值对应的聚类误差平方和(SSE)曲线,选择曲线拐点(肘部)对应的K。
  2. 空簇问题:有时聚类会产出一个没有任何样本的“空簇”。kmeans函数的'EmptyAction'参数可以设置为'singleton',将最远的点作为一个新簇的中心,或者'error'直接报错。
  3. 聚类中心初始化:使用'Replicates'参数多次运行是避免局部最优的标配。还可以使用'Start', 'plus'选项,它采用K-means++算法初始化中心点,通常比随机初始化效果更好、收敛更快。

5.4 系统性能评估

训练好模型后,需要用独立的测试集进行评估。

% 评估脚本 evaluateSystem.m testDataPath = 'data/test/'; categories = {'apple', 'banana', 'orange'}; confusionMatrix = zeros(length(categories)); for catIdx = 1:length(categories) category = categories{catIdx}; folderPath = fullfile(testDataPath, category); imgFiles = dir(fullfile(folderPath, '*.jpg')); for i = 1:length(imgFiles) imgPath = fullfile(folderPath, imgFiles(i).name); trueLabel = category; predictedLabel = predictFruit(imgPath, 'fruit_model.mat'); % 找到真实标签和预测标签在categories中的索引 trueIdx = find(strcmp(categories, trueLabel)); predIdx = find(strcmp(categories, predictedLabel)); if ~isempty(predIdx) confusionMatrix(trueIdx, predIdx) = confusionMatrix(trueIdx, predIdx) + 1; else fprintf('警告:预测标签【%s】不在类别列表中。\n', predictedLabel); end end end % 计算准确率 accuracy = sum(diag(confusionMatrix)) / sum(confusionMatrix(:)); fprintf('整体识别准确率:%.2f%%\n', accuracy * 100); disp('混淆矩阵:'); disp(array2table(confusionMatrix, 'RowNames', categories, 'VariableNames', categories));

通过混淆矩阵,你可以清晰地看到系统容易把哪种水果误识别成另一种,从而有针对性地优化特征或增加对应类别的训练数据。

5.5 从原型到实用化的思考

这个Matlab原型系统证明了“颜色+纹理+聚类”思路的可行性。但如果要投入实际应用,还需要考虑:

  1. 实时性:Matlab在算法验证上很方便,但部署到嵌入式设备或生产环境,可能需要用C++/Python(如OpenCV)重写核心算法,并进行大量优化。
  2. 鲁棒性升级:引入更强大的特征,如HOG(方向梯度直方图)描述形状,或直接使用预训练的CNN(卷积神经网络)模型进行迁移学习,准确率会有质的飞跃。
  3. 流程集成:识别系统需要与机械臂、传送带控制系统等联动,涉及串口通信、触发拍照、结果反馈等工控环节。

这个项目最大的价值,在于它提供了一个完整、清晰、可复现的图像识别技术栈,涵盖了从特征设计、融合到无监督学习的全流程。理解了它,你再去看更复杂的深度学习模型,就会明白那些“黑箱”到底在做什么,以及为什么需要海量的数据。

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

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

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

立即咨询