MATLAB水果识别实战:传统图像处理实现颜色分割与分类
简介:这是一份面向MATLAB初学者与图像识别入门者的水果识别程序源码包,通过图像处理与机器学习技术实现对不同种类水果的自动分类,可应用于课程设计、学术研究及农业自动化、超市果蔬分类等场景。压缩包共11个文件,包含5张jpg水果样本图、4个m脚本文件、1个txt说明文档和1个fig图形文件,整体约77KB,其中m文件承载预处理、特征提取与分类识别的核心流程,jpg提供青椒、西红柿、香蕉、梨等测试样本,txt为运行指引,fig保存界面或结果图形。程序覆盖灰度化、二值化、边缘检测等预处理步骤,并涉及颜色直方图、LBP、HOG等特征提取方法,以及SVM、决策树等分类器训练与测试评估思路。目前已有2679人学习下载,适合希望理解图像识别基本流程、提升MATLAB编程技巧的读者参考借鉴。
1. 从一堆烂水果照片到能跑的识别程序:这个 MATLAB 项目到底能干什么
实验室的桌子上堆着三十多张用手机拍的水果照片,苹果、香蕉、橘子混在一起,光照忽明忽暗,有的还带着阴影。带我的师兄丢过来一句:“你试试用 MATLAB 把这些分出来,别用深度学习,就传统图像处理。”我当时的第一反应是——这能行吗?后来翻到这个 MATLAB 水果识别程序,才发现它正好就是为这种场景写的:不依赖任何深度学习工具箱,纯靠颜色空间转换、形态学操作和区域特征做分类,把一张随手拍的水果图变成带标签的识别结果。
它适合谁?如果你正在上数字图像处理、模式识别这类课,需要交一份能演示、能改参数的课程设计;或者你手头有工业分拣、农产品初筛的小需求,想先用轻量方案验证可行性,这个项目就是很好的起点。整个程序围绕 MATLAB 的 Image Processing Toolbox 展开,核心文件不多,但把“读图—预处理—分割—特征提取—分类—可视化”这条链路走得很完整,拿来当骨架改比从零写要省太多事。
2. 技术选型与整体架构:为什么用传统图像处理而不是深度学习
2.1 选型逻辑:轻量、可解释、不挑机器
这个项目最核心的决策就是放弃深度学习路线。原因很实际:水果识别在颜色和形状上的区分度本来就高,苹果偏红、香蕉偏黄、橘子偏橙,用 HSV 颜色空间做阈值分割就能拿到相当干净的区域,没必要上卷积网络。传统方法的另一个好处是可解释——每一步处理完你都能 imshow 出来看,哪一步翻车了一眼就知道,不像神经网络那样是个黑匣子。
从依赖上看,整个项目只要求 MATLAB 基础环境加 Image Processing Toolbox,不需要 Deep Learning Toolbox,也不需要 GPU。这意味着在普通办公电脑甚至老旧的实验室机器上都能跑起来,对课程设计这种交付场景非常友好。常见做法是配合 App Designer 做一个简单的 GUI,把图片加载、识别按钮和结果显示框拼在一起,演示的时候点两下就行。
2.2 模块划分:六个函数各管一段
项目按处理流程拆成了几个独立函数,主脚本负责串联。典型的目录结构长这样:
fruit_recognition/ ├── main.m % 主入口,串联整个流程 ├── load_image.m % 读图与尺寸归一化 ├── preprocess.m % 滤波、增强、去噪 ├── segment_fruit.m % 颜色空间分割 + 形态学 ├── extract_features.m % 提取颜色/形状/纹理特征 ├── classify_fruit.m % 基于规则的分类器 ├── show_result.m % 结果可视化与标注 └── dataset/ % 存放测试图片 ├── apple_01.jpg ├── banana_01.jpg └── orange_01.jpg这种拆法的好处是每个环节可以单独调试。比如分割效果不好,你只需要改 segment_fruit.m,不用动分类逻辑;想换分类器,也只影响 classify_fruit.m 一个文件。我一般会在每个函数开头加一段参数注释,写清楚输入输出和可调阈值,后面改起来不用来回翻。
2.3 数据流:一张图从进入到出结果
主流程的数据流很清晰:读入 RGB 图像后先统一缩放到固定尺寸(常见是 256×256),避免大图拖慢处理;接着做中值滤波去噪,再转 HSV 空间;在 HSV 上用阈值把水果区域和背景分开,得到二值掩膜;对掩膜做开闭运算去掉小噪点和填补空洞;然后用掩膜抠出原图里的水果区域,提取颜色直方图、面积、圆度、长宽比等特征;最后送进规则分类器判断类别,把结果和置信度画回原图上。
提示:尺寸归一化这一步别省。不同手机拍出来的分辨率差异很大,不统一尺寸的话,后面基于像素数的面积特征会完全失去可比性。
3. 核心模块拆解:分割、特征提取与分类的代码实现
3.1 图像预处理:滤波和颜色空间转换
预处理的目标是把噪声压下去、把颜色信息整理成更容易分割的形式。代码大致是这样:
function img_hsv = preprocess(img_rgb) % 统一尺寸,兼顾速度和细节 img_rgb = imresize(img_rgb, [256, 256]); % 中值滤波去椒盐噪声,窗口别开太大否则边缘糊 img_filt = medfilt2(img_rgb(:,:,1), [3 3]); img_filt(:,:,2) = medfilt2(img_rgb(:,:,2), [3 3]); img_filt(:,:,3) = medfilt2(img_rgb(:,:,3), [3 3]); % 转 HSV,H 通道对光照变化更鲁棒 img_hsv = rgb2hsv(img_filt); end这里有几个参数值得说。imresize 的目标尺寸 256×256 是速度和精度的折中,如果你要识别的水果在画面里占比很小,可以提到 512×512,但处理时间会明显上升。medfilt2 的窗口用 [3 3] 是常规选择,窗口越大去噪越狠但边缘也越糊,水果轮廓本来就不算复杂,3×3 够用。转 HSV 是关键一步,RGB 三个通道都和亮度耦合,光照一变阈值就失效,而 HSV 的 H 通道基本只反映颜色种类,对阴影和反光容忍度高得多。
3.2 颜色分割与形态学处理
分割是整个项目里最需要调参的环节。思路是在 H 通道上给每个水果设一个色相区间,落在区间内的判为水果:
function mask = segment_fruit(img_hsv, fruit_type) H = img_hsv(:,:,1); S = img_hsv(:,:,2); V = img_hsv(:,:,3); % 按水果类型设定色相范围(H 取值 0~1) switch fruit_type case 'apple' % 红色系,H 在 0 附近或接近 1 mask = (H < 0.05 | H > 0.95) & (S > 0.4) & (V > 0.2); case 'banana' % 黄色系 mask = (H > 0.12 & H < 0.18) & (S > 0.4) & (V > 0.4); case 'orange' % 橙色系 mask = (H > 0.05 & H < 0.12) & (S > 0.5) & (V > 0.4); end % 形态学开运算去小噪点,闭运算填内部空洞 se = strel('disk', 5); mask = imopen(mask, se); mask = imclose(mask, se); % 只保留最大连通区域,去掉背景干扰 mask = bwareafilt(mask, 1); end色相阈值是这套方法的核心参数。苹果的红色在 H 上跨越 0 边界,所以要写成H < 0.05 | H > 0.95这种双区间;香蕉和橘子的区间相对集中。S 和 V 的下限用来排除灰白背景和过暗区域。形态学那两行的顺序不能反——先开后闭,开运算先把零星噪点清掉,闭运算再把水果内部因为反光造成的空洞补上。bwareafilt 保留最大连通域是个实用技巧,能自动丢掉画面边缘的杂物。
注意:如果你的测试图背景是纯白或纯黑,这套阈值很好用;但背景里有其他暖色物体时,分割会误判,这时候要么换背景,要么加一步基于位置或形状的二次筛选。
3.3 特征提取:颜色、形状、纹理三路并进
分割出掩膜后,就可以在水果区域上算特征了。项目里主要用了三类:
function feats = extract_features(img_rgb, mask) % 颜色特征:H 通道均值,反映主色调 img_hsv = rgb2hsv(img_rgb); feats.hue_mean = mean(img_hsv(:,:,1), 'all', 'omitnan'); % 形状特征:面积、圆度、长宽比 stats = regionprops(mask, 'Area', 'Perimeter', ... 'BoundingBox', 'Centroid'); feats.area = stats.Area; feats.circularity = 4 * pi * stats.Area / (stats.Perimeter^2); bbox = stats.BoundingBox; feats.aspect_ratio = bbox(3) / bbox(4); % 纹理特征:灰度共生矩阵的对比度 gray = rgb2gray(img_rgb); glcm = graycomatrix(gray, 'NumLevels', 8); props = graycoprops(glcm, 'Contrast'); feats.contrast = props.Contrast; end颜色特征用 H 均值最直接,苹果、香蕉、橘子的 H 均值天然分得开。形状特征里,圆度4πA/P²对苹果和橘子这种接近圆形的物体取值高,香蕉因为细长会明显偏低;长宽比进一步强化这个区分。纹理特征用灰度共生矩阵的对比度,香蕉表面的纹理比苹果光滑表皮要丰富一些,可以作为辅助判据。regionprops 返回的 stats 是结构体,字段名要和调用时写的一致,写错了 MATLAB 会直接报错,这点新手容易踩。
3.4 规则分类器:用 if-else 也能分得准
分类部分没有用任何机器学习工具箱,就是基于阈值的规则判断:
function [label, score] = classify_fruit(feats) % 按优先级依次判断,命中即返回 if feats.circularity > 0.75 && feats.hue_mean < 0.1 label = 'apple'; score = feats.circularity; elseif feats.aspect_ratio > 1.8 label = 'banana'; score = feats.aspect_ratio / 3; elseif feats.hue_mean >= 0.05 && feats.hue_mean < 0.15 label = 'orange'; score = 1 - abs(feats.hue_mean - 0.1) * 10; else label = 'unknown'; score = 0; end end规则顺序有讲究。圆度加色相先判苹果,因为苹果的圆形特征最稳定;长宽比判香蕉,细长形状几乎不会误判;剩下的橙色系归橘子。score 是个粗略的置信度,用特征值离阈值的距离估算,方便在界面上显示“像不像”。这套规则在背景干净、光照均匀的图片上准确率不错,但阈值是硬编码的,换一批数据可能就要重新调。
4. 跑起来:环境准备、运行步骤与参数调试
4.1 环境要求和启动方式
跑这个项目需要 MATLAB R2018b 或更高版本,以及 Image Processing Toolbox。检查工具箱是否安装,在命令行敲ver看列表里有没有 Image Processing Toolbox 就行。没有的话通过附加功能管理器装一下。
启动方式有两种。命令行方式最直接:
% 在 MATLAB 命令行里执行 cd('fruit_recognition'); % 进入项目目录 img = imread('dataset/apple_01.jpg'); result = main(img); % 主函数返回识别结果 disp(result.label);如果项目带了 GUI,直接运行main.m或者对应的 App 文件,界面上会有“加载图片”和“开始识别”两个按钮,点完结果会标注在原图上。第一次跑建议先用 dataset 里自带的图,确认流程通了再换自己的照片。
4.2 关键参数怎么调
调参是这个项目绕不开的环节,几个最常动的参数和它们的调整方向:
| 参数 | 位置 | 作用 | 调大效果 | 调小效果 |
|---|---|---|---|---|
| 目标尺寸 | preprocess.m | 归一化分辨率 | 细节多、速度慢 | 速度快、细节少 |
| 滤波窗口 | preprocess.m | 去噪强度 | 更平滑、边缘糊 | 保边缘、噪点多 |
| 色相区间 | segment_fruit.m | 分割范围 | 纳入更多像素 | 分割更严格 |
| 形态学半径 | segment_fruit.m | 开闭运算核 | 去噪强、可能吞细节 | 保留细节、噪点多 |
| 圆度阈值 | classify_fruit.m | 苹果判据 | 更严格 | 更宽松 |
调参的顺序建议从分割开始。先把 mask 用 imshow 显示出来,看水果区域是不是完整、背景有没有残留,分割对了再去看分类结果。分割不对的情况下调分类阈值是白费功夫。
4.3 用自带数据验证流程
验证流程是否正常,可以写个小脚本批量跑一遍 dataset:
% 批量测试,统计识别情况 files = dir('dataset/*.jpg'); for i = 1:length(files) img = imread(fullfile('dataset', files(i).name)); result = main(img); fprintf('%s -> %s (score: %.2f)\n', ... files(i).name, result.label, result.score); end跑完看输出,如果大部分图片都能给出合理标签,说明流程没问题。有个别识别错的先别急着改代码,把那张图的中间结果(mask、特征值)打出来看看是哪一步出的问题,定位清楚再动手。
5. 避坑与常见问题:那些让我重跑好几遍的坑
5.1 分割结果全是噪点
现象:mask 显示出来是一片散点,完全看不出水果轮廓。原因通常是色相阈值设得太宽,把背景里的杂色也纳入了。解决方法是先把原图转 HSV,用impixelinfo在图上点几个水果像素和背景像素,看它们的 H 值分别是多少,据此收窄区间。另外 S 和 V 的下限也要检查,背景如果是浅色,S 值低,加个 S 下限就能滤掉。
5.2 苹果被识别成橘子
现象:红色苹果的识别结果偶尔跳到 orange。原因是苹果的 H 值在 0 附近,而橘子的区间从 0.05 开始,边界处有重叠。解决办法是在分类规则里把苹果的判断放在前面,并且给苹果的色相条件加上H > 0.95这个分支,确保跨 0 边界的红色被苹果先接住。规则分类器的顺序本质上就是优先级,把最不容易混淆的判据放前面。
5.3 换一批图片后准确率暴跌
现象:自带数据跑得好好的,换成自己拍的照片就大面积识别错误。这几乎都是光照和背景差异导致的。传统阈值方法对成像条件敏感,这是它的固有短板。应对办法有两个方向:一是拍摄时尽量用纯色背景、均匀光照,从源头减少变量;二是在预处理里加一步白平衡或直方图均衡,把不同图片的亮度和对比度拉到相近水平,减小域偏移。
5.4 处理大图时卡到怀疑人生
现象:用几千万像素的手机原图跑,每一步都要等好几秒。原因是所有操作都是逐像素的,图越大越慢。解决很简单,在 preprocess 第一步就 imresize 到 256×256,后面所有计算都在小图上做。如果确实需要高分辨率输出,可以只在小图上算 mask 和特征,最后把 mask 放大回原尺寸再叠加显示。
5.5 regionprops 报字段错误
现象:调用 regionprops 后取 stats.Circularity 报错说字段不存在。原因是 regionprops 的属性名要用它支持的字符串,圆度对应的属性名是 'Circularity' 没错,但如果你在调用时没把它写进参数列表,返回的结构体里就不会有这个字段。检查regionprops(mask, 'Area', 'Perimeter')这行,需要哪些属性就显式列哪些,别指望它全返回。
6. 二次开发:从规则分类器升级到可训练的分类流程
规则分类器跑通之后,最自然的改造方向就是把它换成可训练的分类器,让阈值从数据里学出来而不是手调。这一步不需要动前面的分割和特征提取,只替换 classify_fruit.m 就行,接口保持不变——输入特征结构体,输出标签和分数。
具体做法是先用 extract_features 把 dataset 里所有图片的特征批量算出来,存成一个特征矩阵,标签单独存一列。然后用 MATLAB 自带的分类器应用或者 fitcknn、fitcsvm 这类函数训练一个模型:
% 批量提取特征,构建训练集 files = dir('dataset/*.jpg'); X = []; Y = {}; for i = 1:length(files) img = imread(fullfile('dataset', files(i).name)); mask = segment_fruit(preprocess(img), 'apple'); % 分割逻辑复用 feats = extract_features(img, mask); X = [X; feats.hue_mean, feats.circularity, ... feats.aspect_ratio, feats.contrast]; Y{end+1} = extract_label(files(i).name); % 从文件名解析标签 end % 训练 KNN 分类器并做交叉验证 mdl = fitcknn(X, Y, 'NumNeighbors', 3); cvmdl = crossval(mdl, 'KFold', 5); fprintf('交叉验证准确率: %.2f%%\n', ... (1 - kfoldLoss(cvmdl)) * 100);这样改的好处是分类边界由数据决定,换一批图片时只要重新标注、重新训练,不用再手动调那一堆 if-else 阈值。特征维度也可以扩展,比如把 H 通道的直方图分箱后拼进去,或者加上 Hu 矩这类形状不变量。验证方法上,交叉验证的准确率比单次划分更可靠,样本少的时候尤其明显。
我自己的习惯是,每次改完分类器都先把混淆矩阵打出来看,哪个类别容易被误判一目了然。有一次苹果和橘子互相误判得厉害,查了半天发现是特征里 hue_mean 的区分度不够,加了一维 H 直方图特征之后就分开了。从那以后我每次动特征或分类器,都强制走一遍混淆矩阵加交叉验证,不再只看总准确率那个数。希望这个项目能帮到你,把传统图像处理这条链路真正跑通、改顺。
本文还有配套的精品资源,点击获取