2026/9/14 9:44:59

MATLAB苹果分级实战:图像预处理与特征提取全解析

MATLAB苹果分级实战:图像预处理与特征提取全解析 简介一套面向苹果自动分级场景的基于MATLAB的水果检测系统设计资源适合图像处理、农业智能装备方向的毕业设计、课程实践或入门研究。资源以可直接运行的源代码与界面文件为核心完整展示了手机端采集苹果图像后所涉及的灰度化、滤波去噪、二值化、填充修复、形态学操作以及面积、周长、圆度、颜色分布等特征量的计算过程并依据这些特征完成苹果品质的自动分级构成一条从图像输入到检测输出的完整技术链路。压缩包共含七个文件包括一个脚本源文件、一个图形用户界面文件以及五张检测效果展示图整体大小仅约四百六十三千字节结构清晰目前已有1592人学习下载。通过研读源码与界面设计可以快速掌握MATLAB图像处理工具箱的调用方法并能够修改特征参数和分级阈值将方案扩展到其他果品适合用于快速搭建视觉检测Demo以及后续教学和二次开发。1. 把苹果分级放进MATLAB关键不是模型而是图像预处理的稳定性一条实际的分拣线摄像头斜装在传送带上方快门按下的瞬间苹果反光、背景阴影、传送带纹理全被拍进同一张图。用MATLAB做水果检测最容易翻车的地方不在最后的特征提取而在最开始的那几百毫秒——灰度化、滤波、二值化之间的参数彼此依赖一个阈值差0.05苹果轮廓就会多出一个豁口。orange_shibie.m这条脚本把整条链路串起来手机拍摄的图像经过灰度化、中值滤波、高斯滤波、二值化、空洞填充再用regionprops提取面积、周长、圆度和颜色比例按规则划分等级。它不需要GPU不需要训练几分钟就能跑通适合课程设计、实验室验证也适合想理解传统图像处理边界的工程师。下面把这条主处理线拆开讲。2. 预处理链路拆解灰度化、滤波与二值化的参数选择手机拍彩色图进来第一步是把RGB转灰度。为什么不用彩色直接分割因为彩色有两个问题一是三个通道在反光区域同时饱和阈值法很难统一二是计算量多三倍在GUI里连续调参时延迟明显。rgb2gray采用加权平均0.2989×R0.587×G0.114×B绿色权重最高。这个系数来自人眼对亮度的敏感度对红苹果来说红色通道其实更突出但灰度图已经足够把苹果和常见传送带背景分开。真正要注意的是尺寸。手机照片从几百万像素到1200万像素不等如果直接做面积分级不同距离拍出来同一个苹果面积差很大。常见做法是缩放到固定尺寸但要保持长宽比否则圆度会失真。2.1 手机图像先做灰度化与比例缩放img imread(a.jpg); if size(img, 3) 3 gray rgb2gray(img); else gray img; end targetSize 512; [h, w] size(gray); scale min(targetSize / h, targetSize / w); gray imresize(gray, scale, bilinear); [hh, ww] size(gray); padH targetSize - hh; padW targetSize - ww; gray padarray(gray, [floor(padH/2), floor(padW/2)], 0, both);逻辑说明先判断输入是否是三通道兼容灰度图直接输入然后按最长边缩放到不超过512像素不足的边用0填充。padarray默认前后都填[floor(padH/2), floor(padW/2)]是行和列各填一半。这样做的好处是图不会拉伸苹果的圆度数值才能跨图片比较。注意padarray填充的0是黑色会对后续Otsu阈值计算造成偏移所以后面在计算阈值时必须只取非黑边区域。另一个方案是用NaN填充但imbinarize不支持NaN所以保留黑色并配合validMask处理。2.2 中值滤波去反光高斯滤波压纹理手机拍摄产生的噪声主要来自JPEG压缩块的边缘和苹果表面的点状反光。反光是一个“亮脊”面积小且孤立中值滤波在3×3邻域里取中值可以单独干掉这些亮点。高斯滤波是按权重平均适合把果皮表面细纹和灰尘压平避免后续边缘检测时产生大量伪边界。滤波顺序不能乱先中值后高斯。如果先高斯孤立反光会被扩散成面状亮斑中值滤波对大面积亮斑就无能为力了。grayMed medfilt2(gray, [3 3]); graySmooth imgaussfilt(grayMed, 1.5);参数说明medfilt2的邻域窗口是3×3窗口调成5×5可以滤掉更粗的反光点但会把苹果直径边缘磨掉一圈导致周长偏小、圆度升高反而失真。imgaussfilt的sigma取1.5表示高斯卷积核的标准差sigma越大越模糊一般取1到2。超过2时苹果和背景之间的边界会被拉宽后面二值化会得到一条灰色过渡带形态学闭合都补不整齐。如果发现图像下半部分有阴影可以再加一步imtophat或imopen做背景均衡但那属于另一套逻辑。2.3 二值化的阈值选择与imfill空洞填充灰度化与滤波之后苹果和背景在灰度直方图上可能形成双峰但也可能只有一个峰。特别当苹果是暗红色时Otsu全局阈值可能把暗部果皮归到背景。graythresh返回的是归一化灰度值imbinarize默认把这个值作为阈值灰度大于阈值为1小于为0。由于前面用黑边填充过直接对整个图求阈值会让黑边面积拉低背景统计值所以这里先取出非黑边像素再算阈值。validIdx gray 0; level graythresh(graySmooth(validIdx)); bw imbinarize(graySmooth, level); % 苹果通常比背景暗所以翻转一次 bw ~bw; % 如果翻转后前景面积反而超过60%说明目标其实是亮色再翻回来 if sum(bw(:)) / numel(bw) 0.6 bw ~bw; end bw imclearborder(bw); bw bwareaopen(bw, 200); bw imclose(bw, strel(disk, 8)); bw imfill(bw, holes);逻辑说明validIdx记录非黑边区域graySmooth(validIdx)把所有有效像素变成一维向量这样Otsu阈值不会受黑色填充边影响。翻转和前景占比判断是配套的避免遇到浅色苹果时把背景当前景。imclearborder删除与图像边界相连的连通域黑边和贴边背景会被清掉。bwareaopen删除小于200像素的独立噪点imclose用半径为8的圆形结构元素把分割缺口补起来imfill填充连通域内部的孔洞主要是苹果反光造成的假空洞。实际操作中预处理参数的敏感度从高到低排列是这样的操作推荐参数作用参数过大后果中值滤波3×3去除反光点边缘变圆周长失真高斯滤波sigma1.5平滑表面纹理边界过渡带变宽形态学闭运算disk半径8修补轮廓缺口苹果间粘连无法分开面积过滤200像素清除噪点去掉过小真实目标如果拿到的是bmp或pngmedfilt2和imgaussfilt处理方式不变如果拿到的是RAW格式需要先做demosaic这里不展开。3. 特征量提取与苹果分级阈值设计预处理后拿到一张干净的BW二值图。接下来需要告诉电脑什么特征能代表苹果品质在传统MATLAB图像处理里regionprops是绝对主力。它可以一次性计算连通域的面积、周长、外接矩形、质心、长短轴长度。对于单个苹果我们取面积最大的连通域作为检测目标然后计算圆度。圆度公式对边界锯齿非常敏感这也是为什么必须在二值化之前做imclose。3.1 用regionprops计算面积、周长与圆度cc bwconncomp(bw); stats regionprops(cc, Area, Perimeter, BoundingBox, Centroid, ... MajorAxisLength, MinorAxisLength); [~, idx] max([stats.Area]); apple stats(idx); area apple.Area; perimeter apple.Perimeter; circularity 4 * pi * area / perimeter^2; fprintf(面积: %d, 周长: %.1f, 圆度: %.3f\n, area, perimeter, circularity);逻辑说明bwconncomp返回连通域结构regionprops按索引计算每个连通域的特征。max函数配合索引取出面积最大的连通域通常就是苹果主体。圆度公式4πA/P²在正圆时取1对边界的锯齿非常敏感周长P如果因为闭合不好导致边缘外扩圆度会明显下降。参数选择上Area是区域实际像素个数BoundingBox是外接矩形[x, y, width, height]Centroid是质心坐标。如果要支持多苹果同时检测可以把stats数组整体保留然后对每个连通域逐一计算圆度和颜色比例。但这个项目是单果分级所以取最大连通域是合理简化。MajorAxisLength和MinorAxisLength可以作为圆度的替代特征因为一个被横向拉长的苹果长短轴比值会偏低。3.2 用HSV颜色特征分离红色区域只看形状不完善。一个烂苹果表面有褐色斑块、粉色坏点面积和周长几乎不变。这时候要用颜色特征。RGB空间三个通道相关性高反光区域会让R、G、B同时升高没法按单一通道判断。HSV空间把色调H、饱和度S、亮度V拆开红色分布在H0附近刚好避开照明亮度变化。在MATLAB中rgb2hsv转换后的H范围是[0,1]红色对应H≈0.95到1以及0到0.05而不是很多OpenCV教程说的0度到10度。hsvImg rgb2hsv(img); hue hsvImg(:,:,1); sat hsvImg(:,:,2); redMask (hue 0.05 | hue 0.95) (sat 0.2); redRatio sum(redMask(:)) / sum(bw(:)); fprintf(红色像素占比: %.2f\n, redRatio);逻辑说明这里img是原始RGB图像不是灰度图。sat 0.2的作用是剔除掉反光造成的白色高光点因为这些点饱和度极低色调不稳定。红色占比是红色Mask与苹果前景区域像素数的比值。这一比值对同一品种的苹果比较稳定不同光线下会有变化所以阈值只适合在同一采集环境下使用。如果环境光偏暖需要先做白平衡或者在采集时固定色温。做MATLAB图像处理大作业时最容易忽略的就是这一点——软件里调好的阈值换个手机拍就失效。3.3 分级规则用面积、圆度、红色占比组成决策表传统分级不必须上深度学习的YOLOv8方案。对单个苹果设定面积、圆度、红色占比三组阈值用一条if-else链就能完成。下面这组阈值是针对2.1节缩放后512以内、背景填充后的示例直接照搬前需要重新标定。if area 60000 || circularity 0.65 grade 等外; elseif circularity 0.85 redRatio 0.75 grade 一级; elseif circularity 0.75 redRatio 0.5 grade 二级; else grade 三级; end分级逻辑对应关系如下等级面积圆度红色占比判断结果一级≥60000≥0.85≥0.75果形端正且颜色红二级≥60000≥0.75≥0.5略有畸形或着色不足三级≥60000其他其他明显缺陷等外60000或圆度0.65--过小或严重变形面积阈值要按实际采集距离标定建议先测10个已知等级样本取最小一级果面积作为下限。如果分拣的是一整盘苹果还会遇到多目标粘连需要分水岭分割才能把粘连区域切开这一版系统没有涉及。如果想把规则做得更细可以用SVM或决策树替换if-else但样本量少于100时决策树很容易过拟合反而不如阈值表可解释。4. 从脚本到系统手机端采集与GUI工程组织这个项目的资源包里包含orange_shibie.m和orange_shibie.fig。.m文件是主程序.fig是MATLAB GUIDE或App Designer生成的界面布局。典型的交互是界面上一个按钮触发回调回调里调用前面几段代码把结果显示在axes控件上。图窗和代码分开部署时只需要把.m和.fig放在同一目录然后运行orange_shibie即可。如果只想命令行批量测试可以直接调用函数版本。4.1 orange_shibie.m与orange_shibie.fig的分工function orange_shibie_Callback(hObject, eventdata, handles) [filename, pathname] uigetfile(*.jpg, 选择苹果图片); if isequal(filename, 0) return; end img imread(fullfile(pathname, filename)); imshow(img, Parent, handles.axesOriginal); [bw, grade, stats] detect_apple(img); imshow(bw, Parent, handles.axesMask); set(handles.textGrade, String, grade); end逻辑说明uigetfile打开文件选择对话框返回文件名和路径。handles结构体保存GUI内所有控件句柄set函数把分级结果显示在textGrade文本控件中。检测逻辑封装到子函数detect_apple让GUI回调只做界面调度这样命令行也能复用直接[bw, grade, stats] detect_apple(img)。注意detect_apple内部需要保持和orange_shibie.m相同的预处理参数否则界面点击和命令行批量测试结果会不一致。一个常见的坏习惯是把灰度阈值直接写在GUI回调里导致要调参时只能去.fig里找代码。正确做法是把所有图像处理步骤抽成独立函数GUI只负责采集路径和展示结果。4.2 手机图像采集的尺寸与光照约束手机拍摄不是无脑按快门。因为特征尺寸阈值严重依赖像素面积拍摄距离稍有改变面积会成平方变化。建议固定距离手机镜头离苹果平面35到40厘米镜头正对苹果中心不要俯仰。光照方面用白天自然光或均匀LED平板灯避免点光源造成的镜面反射。如果非要抵抗环境光可以在采集端加一个自动曝光锁定并在系统里做灰度归一化imgDouble im2double(img); imgNorm (imgDouble - min(imgDouble(:))) / (max(imgDouble(:)) - min(imgDouble(:)) eps);代码作用是把像素范围统一映射到[0,1]减少亮度差异对灰度阈值的影响。注意这一步要在灰度化和滤波之前做并且以原始彩色图的整幅范围计算。eps用于防止除以0。这个归一化只是对比度压缩不是白平衡不能替代固定光照。实际项目中如果苹果表面有强烈反光归一化反而会把反光区域放大这时候要优先调整光源角度。4.3 批量测试资源包里的a.jpg到e.jpg拿到资源包后不要先打开GUI先用命令行跑一遍所有图片确认预处理链路是否正确。用dir读取目录下所有jpg文件逐个调用检测函数输出等级。这段脚本适合快速复现结果。files {a.jpg, b.jpg, c.jpg, d.jpg, e.jpg}; for i 1:numel(files) img imread(files{i}); [bw, grade, stats] detect_apple(img); fprintf(%-10s - 面积 %d 圆度 %.3f 等级 %s\n, ... files{i}, stats.Area, stats.Circularity, grade); end逻辑说明fprintf中的%-10s左对齐文件名便于观察。每次循环都重新执行完整图像处理流程所以不需要手工clear变量。如果图片数量多建议把files改为dir(*.jpg)自动匹配但注意dir返回的是结构体数组要取.name字段。原图只有a.jpg到e.jpg五张说明作者在GUI里逐个演示命令行循环更适合连续性验证。还可以加tic/toc统计每张图平均耗时在课程设计的报告里作为系统性能数据。5. 分级阈值调优用人工标注混淆矩阵定容差面对一批新苹果不要嘴上说“阈值大概是多少”而是拿20张有代表性的样本手工标等级再写一个脚本统计当前阈值下的准确率。先准备ground_truth数组人工标记1表示一级2表示二级3表示三级4表示等外。然后循环调用detect_apple得到预测等级生成混淆矩阵。truth [1, 2, 3, 1, 2, 1, 1, 3, 4, 2, ... 1, 2, 3, 1, 2, 1, 4, 3, 2, 1]; pred zeros(size(truth)); for i 1:numel(truth) img imread(sprintf(test_%d.jpg, i)); [~, grade] detect_apple(img); map {一级, 二级, 三级, 等外}; pred(i) find(strcmp(grade, map)); end conf confusionmat(truth, pred); accuracy sum(diag(conf)) / sum(conf(:)); fprintf(总体准确率: %.2f%%\n, accuracy * 100);confusionmat统计每个真实等级被预测成哪个等级的频数。对角线之和除以总数得到准确率而不是精确率或召回率。分级的代价是不对称的把一级苹果打成二级损失利润把三级苹果打成一级影响品牌。所以面对不平衡样本准确率不够要单独看conf(:, 1)里有多少非一级被误判为一级。误判率过高时把红色占比阈值从0.75往上调。反之如果一级被大量打成二级就往下调0.05。每次只动一个阈值跑一遍confusionmat记录准确率最后取最高点。记录格式如下红比阈值准确率一级误判数调整建议0.6583%3一级判断太宽0.7590%1可用0.8582%0一级漏判太多调参时不要同时改面积和圆度否则无法定位是哪个特征造成的误判。把三个特征分开调面积阈值用最小组间差异确定圆度阈值用畸形果样本校准红色阈值用不同着色样本校准。经过这几轮调整分级结果才不是碰巧在a.jpg到e.jpg上好看。本文还有配套的精品资源点击获取