资讯动态

基于机器学习的缺陷水果分选:Matlab图像分类实战

发布时间:2026/10/21 21:43:35 来源:尧图企业网站定制
做缺陷水果分选这个项目我一开始把它想简单了。标题里的“机器学习算法”和“Matlab代码实现”看起来只是分类再加一段代码真正上手才发现流程里最花时间的不是调参而是数据。先说说我这边的场景一条水果分选线传送带上一批批果实经过相机系统要在几百毫秒内判断“这个果子能不能进下一道工序”。人工分选又慢又累而且标准因人而异所以方案落到机器视觉加机器学习上。这篇记录不是讲一个拍脑袋的Demo而是把整个研究过程从数据采集、特征设计、模型对比到Matlab落地代码完整拆开。如果你正在做类似的产线视觉项目或者你是学生拿它做课程设计下面每一节都能直接对照着做。我觉得这个项目最值得聊的不是某个模型有多厉害而是“为什么这么选、踩了哪些坑、怎么在代码里补上”。1. 这个项目到底在解决什么问题先把任务定义清楚否则后面全是糊涂账。缺陷水果分选本质上是一个图像分类问题输入是一张水果图像输出是这个果子的等级标签。我一开始以为要做得越全越好比如把缺陷框出来、给缺陷画轮廓。后来发现产线只关心最终去向合格、次品、还是直接剔除。所以你真正要训练的是一个能对整张图快速打标签的分类器而不是一个需要逐像素分割的复杂模型。从算法流程上讲整个项目分为五步图像采集、预处理和分割、特征提取、模型训练、评估与部署。每一步都决定了最后能不能落到实际产线上。比如采集阶段如果光照不稳后面无论如何调参效果都救不回来分割阶段如果背景像素混进来特征里就会混入噪点模型学到的全是噪声。为什么用Matlab而不是Python不是因为Matlab比Python更“正统”而是这个项目里有几个优势一是Matlab把图像标注、特征提取和机器学习集成在同一个环境里不用来回倒数据二是它有Image Labeler和Classification Learner这种图形化工具能快速做一轮基线三是后期要输出嵌入式C代码时Matlab的代码生成工具更顺手。当然Python也能做但如果你只是做研究验证和原型落地Matlab确实能省不少事。这里还需要明确一个边界我们可以做的缺陷是外观可见的缺陷比如碰伤、腐烂、机械划痕、虫眼和表面污斑。像内部褐变、苦味病这类看不见的毛病普通彩色相机解决不了那要上光谱成像或CT成本和复杂度完全不是一个量级。做项目前先把这个范围说清楚不然需求方永远会往里加“能不能顺便判断内部甜度”之类的需求。2. 数据准备比模型更磨人的步骤数据质量直接决定模型上限。我在这个项目上的真实感受是采集和标注占掉了整个项目一半以上的时间。一个分类模型如果训得不好大部分时候不是模型代码有问题而是数据里有脏东西。2.1 缺陷怎么定义缺陷不能笼统叫“坏的”要细分。比如碰伤它的特征是果肉受撞击后颜色变深、组织塌陷腐烂则是从伤口开始滋生霉菌表面颜色发暗、皱缩甚至渗液划伤是细长条的机械损伤虫眼则是小孔洞有时候周围还会变色。这些缺陷的视觉特征完全不同放在一个“坏果”标签里会让模型非常糊涂。我建议按实际产线的处理方式定标签而不是按学术上的精细分类。常见做法是分三类优等果、普通果、缺陷果。如果条件允许把缺陷果再拆成腐烂和机械伤也是可以的因为腐烂果会污染相邻水果必须单独剔除。但不要一开始就做十来个细分类类别越多数据需求成倍增加标注一致性也更难保证。2.2 采集和标注相机建议固定在传送带上方加遮光罩和漫射光源让果实表面受光均匀。拍的时候尽量让水果放在一个固定的托盘或者卡位上这样每一张图里水果的位置、大小、角度都接近模型就不用去学“位置变换”这种无关信息。图像分辨率不用特别高我试过从3840×2160缩到512×512之后准确率几乎没有下降而训练和推理速度却快了非常多。标注工具直接用Matlab里的imageLabeler可以打开图像序列手动框选ROI或者打像素标签。如果只是做整图分类更简单的做法是把图像按标签放进不同文件夹一个文件夹对应一个类别读数据时用imageDatastore直接搞定。我认为最好两人以上交叉抽检标注结果尤其要对“边缘果”达成统一标准。比如一个果子有一点变色到底算优等还是缺陷这类边界样本如果标注不一致模型学出来的边界就会很混乱。2.3 增强与样本平衡水果分选的数据集永远不会是理想均衡的好果特别多缺陷果里腐烂的又多一些碰伤的又是另一拨。类别不平衡直接会让模型偏向多数类尤其是用传统机器学习模型时决策边界容易被带跑。我试过两种平衡方式。一种是对少数类做过采样只把缺陷类图像反复放进去训练代价是容易过拟合另一种是数据增强对缺陷类图像做小幅度的旋转、镜像、亮度和对比度扰动相当于人工扩充了样本分布。增强要注意尺度不能太夸张比如旋转30度还可以接受90度翻转会让果子的朝向变得不真实反而引入偏差。关于样本量我的经验是如果每类少于100张先别急着上CNN老老实实走特征加SVM或随机森林路线如果每类有300张以上再考虑用预训练CNN做迁移学习。数据增强一定要从训练集生成验证集和测试集保持原始状态这是很多人容易踩的坑后面我会专门说数据泄漏的问题。3. 特征提取让传统模型吃饱传统机器学习模型本身不看图像它看的是从图像里抽出来的特征。特征设计得好不好直接决定模型上限。这部分我把颜色、纹理、形状三类特征都做了实际效果很有说服力。3.1 颜色特征水果表面缺陷最直观的表现就是颜色变化。青苹果上的褐色斑点、腐烂区域的暗黄和发黑这些在RGB空间里往往不够稳定因为RGB三个通道相关性太强而且对光照变化特别敏感。我一般把图像转到HSV空间用色相H和饱和度S通道来抓缺陷。具体提取的特征包括水果区域像素在H、S、V三个通道上的均值、标准差和偏度。均值描述整体色调标准差描述颜色分布是否均匀偏度则能捕捉到一小块异常颜色带来的分布不对称。一个苹果如果只有一小块烂斑颜色直方图的主体还是红色但偏度会出现明显变化。后来我又加了颜色直方图的分区间统计效果更稳一点因为直方图能保留更多的分布信息。3.2 纹理特征光靠颜色容易误判。有些品种的水果天生表皮就有花纹或者表面还带一点正常的棕色痕迹这类情况用纹理特征能明显拉低误报。我用的是灰度共生矩阵GLCM也叫GLCM纹理特征。它统计的是图像中两个相距一定距离的像素在灰度上同时出现的概率。在Matlab里调用graycomatrix和graycoprops就能直接算出对比度、相关性、能量、同质性四个统计量。腐烂区域的纹理通常更粗糙、更松散能量偏低、对比度偏高正常果皮纹理则相对均匀。这里有一个细节计算GLCM之前最好把图像灰度级降到32或64否则矩阵稀疏、统计量不稳定我一开始用256级跑出来的熵值对光照噪声非常敏感。3.3 形状特征形状特征的作用是补刀。比如碰伤的凹陷区域往往在分割后呈现出一个不规则暗色斑块。用regionprops可以提取缺陷区域的面积、周长、离心率、致密度等参数。我尤其喜欢“缺陷面积占整个水果面积的比例”这个特征因为腐烂果的斑块通常占比很高而一些轻微碰伤占比很小。在实际流程里我会先做一个规则如果缺陷面积占比超过10%直接判为剔除不送进模型。这种规则和机器学习不冲突反而能提高系统稳定性。毕竟规则简单、可解释现场调试也方便。模型处理的是那些面积占比在边缘区间的模糊样本。3.4 特征选择与PCA把所有特征拼起来之后维度很容易冲到几十上百。维度太高时传统模型容易出现过拟合也就是“维度灾难”。我在项目里用PCA做降维取累计解释方差到90%以上的前几个主成分。记得PCA只能在训练集上做再把训练集的均值和变换矩阵套用到测试集上不能拿全量数据一起PCA。除了PCA还可以用sequentialfs做前向特征选择逐个判断哪些特征对分类贡献大。实验结果发现颜色特征贡献最大纹理其次形状特征锦上添花。如果某个特征在验证集上让准确率掉了0.5个百分点以上我就直接去掉它减少现场部署时的计算量。4. 模型选择和参数实践模型选择这个环节我做了三种SVM、随机森林、迁移学习CNN。三者各有利弊放到同一个评估框架里对比后结论非常清楚。4.1 SVM为什么是起手式SVM是这类小样本特征分类的经典选择。原因在于它对高维数据尚有不错的泛化能力尤其在样本量几百到一千的情况下比深度学习更稳。我用的是RBF核的C-SVM。RBF核能把原始特征映射到更高维空间适合颜色、纹理特征边界不是线性可分的情况。两个关键参数是BoxConstraint和KernelScale。BoxConstraint等价于正则化强度越大越容易过拟合越小越容忍误分类KernelScale控制RBF核的宽度。我采用交叉验证搜索C在0.1到10之间KernelScale在0.01到1之间用网格搜索找到验证集F1最高的组合。还要强调一点特征必须先标准化否则量纲差异会让SVM的几何间隔失真。多分类问题如果用SVM我建议直接用fitcecoc做一对多或者纠错输出多分类不要手动拆多个二分类器。我第一个版本就是手动拆三个二分类器结果类别重叠区间的样本被多个模型互相打架排查起来很头疼。4.2 随机森林做对照随机森林用的是TreeBagger。它的好处是在特征噪声较大时不容易崩而且它能输出每个样本属于各类的概率对后续调整决策阈值很有用。我设置300棵树MinLeafSize设为5。树太多并不会继续提升精度反而会增加内存和推理时间。300棵和500棵在我们数据集上准确率只差0.2%左右但推理时间多了近一倍。随机森林还有个隐藏福利可以输出特征重要性。用OOBPermutedPredictorDeltaError能看到哪些特征对分类贡献最大。我在项目里发现纹理的“对比度”和颜色通道“H均值”排在最前这给我后来的特征简化提供了直接依据。4.3 迁移学习CNN到了迁移学习这一步我反而要泼一点冷水。网络上很多教程默认CNN一定比传统模型强但在小样本的水果分选场景下不是这样。我第一次用预训练ResNet-18跑不到两百张的训练集验证集准确率只有76%反而不如SVM的91%。原因很简单数据不足以微调整个深层网络最后学到的还是通用图像特征而不是属于这个产线的分选知识。当我把数据量扩到每类300张以上并只微调最后一两个全连接层时CNN才追上来达到94%左右。训练时我用的是augmentedImageDatastore做在线增强输入尺寸resize到224×224初始学习率调到1e-4MaxEpochs控制在10到15加上早停防止过拟合。如果你没有GPUCNN跑起来会非常痛苦这也是传统模型在产线落地时仍然有存在价值的原因之一。4.4 模型对比结论最终我在不上数据增强的图片集上做了对比结果大致如下模型准确率缺陷召回率推理耗时调试难度SVMRBF91%87%2ms中随机森林90%85%5ms低迁移学习CNN94%91%20ms高这组数据只代表我这个数据集的结论不代表普遍规律。但可以看到CNN只有在准确率优势足够大时才值得为那额外的推理时间买单。如果产线要求每秒处理10个果SVM完全够用如果要求更高准确率且能接受硬件成本再上GPU加速的CNN也不迟。我的建议是先用传统模型跑通流程同时并行收集更多数据给CNN留好后路。5. Matlab代码实现细节代码部分我尽量给出可复用的骨架。下面这些代码块我实际跑过但你要根据自己现场的相机、水果颜色和缺陷特点调整参数。5.1 环境配置需要这三个工具箱Image Processing Toolbox、Statistics and Machine Learning Toolbox、Deep Learning Toolbox。如果你只做SVM和随机森林第三个可以先不装。建议版本不低于R2021a因为新版的数据存储和训练接口更稳定。代码组织上我建议分成四个脚本01_build_dataset.m负责把图像文件夹整理成数据表02_extract_features.m负责遍历图像并写特征CSV03_train_models.m负责训练和保存模型04_evaluate.m负责画混淆矩阵和指标。这样调试时不用反复跑整个流程特别是特征提取非常耗时跑一次能存下来就用缓存。5.2 预处理和分割片段预处理的核心是把水果从背景里分离出来。我的场景是黑色传送带和固定托盘所以颜色分割就够了。示例代码如下im imread(apple_001.jpg); im imresize(im, [512 512]); % 统一输入尺寸 im imgaussfilt(im, 1.2); % 轻度去噪半径不要太大否则边缘糊掉 % 背景分割假设背景灰度明显更低 gray rgb2gray(im); mask gray 40; % 根据现场打光调这个阈值 mask imfill(mask, holes); mask bwareafilt(mask, 1); % 只保留最大连通区域也就是水果本体分割这一步是后面所有特征的输入。如果你发现some水果表面有高光反光导致内部出现孔洞用imfill补上如果水果和背景颜色太接近建议换成HSV阈值分割或者用segmentImage这个交互式工具先分析一下合适的颜色空间。5.3 特征提取函数特征提取抽成一个函数输入是原图和掩膜输出是一行特征向量。颜色特征用掩膜内的像素统计纹理特征也要只对掩膜内区域计算否则背景像素全部混进去特征直接废掉。function feats extractFruitFeatures(im, mask) % 颜色特征HSV统计 hsv rgb2hsv(im); stats (ch) struct(mean, mean(ch(mask)), ... std, std(ch(mask)), ... skew, skewness(ch(mask))); H stats(hsv(:,:,1)); S stats(hsv(:,:,2)); V stats(hsv(:,:,3)); colorFeats [H.mean H.std H.skew S.mean S.std S.skew V.mean V.std V.skew]; % 纹理特征GLCM I rgb2gray(im); offsets [0 1; -1 1; -1 0; -1 -1]; glcm graycomatrix(I, Offset, offsets, NumLevels, 32, ... GrayLimits, [0 255]); glcm mean(glcm, 3); % 四个方向平均 statsGLCM graycoprops(glcm, {Contrast,Correlation,Energy,Homogeneity}); textureFeats [statsGLCM.Contrast statsGLCM.Correlation ... statsGLCM.Energy statsGLCM.Homogeneity]; % 形状与占比特征 props regionprops(mask, FilledArea, Area, ... Eccentricity, Solidity, ... BoundingBox); if isempty(props) feats [colorFeats, textureFeats, 0, 0, 0, 0, 0]; return; end fruitArea props(1).Area; defectArea sum(mask(:) (hsv(:,:,1) 0.5)); % 示意此处用色相判断缺陷区域 areaRatio defectArea / fruitArea; shapeFeats [areaRatio, props(1).Eccentricity, props(1).Solidity, props(1).BoundingBox(3)/props(1).BoundingBox(4)]; feats [colorFeats, textureFeats, shapeFeats]; end这段代码里我故意把颜色判断写得很示意化因为给青色苹果和红色苹果判断缺陷时色相阈值完全是两套东西。实际项目里我会先用二级分类器检测缺陷区域或者用异常检测思路把偏离正常颜色分布的区域标出来再统计它们的属性。从效率角度讲提取几百张图的特征并不慢但如果几千张图建议使用parfor并行循环Matlab的并行池能把时间压到原来的四分之一左右。5.4 训练与验证特征文件生成之后训练代码就非常干净了。SVM和随机森林共用一个特征表格只改训练函数就好。data readtable(fruit_features.csv); data.label categorical(data.label); rng(2025); % 固定种子方便复现 cvp cvpartition(data.label, Holdout, 0.3); trainIdx training(cvp); testIdx test(cvp); Xtrain data{trainIdx, 1:end-1}; Ytrain data.label(trainIdx); Xtest data{testIdx, 1:end-1}; Ytest data.label(testIdx); % 标准化 mu mean(Xtrain); sigma std(Xtrain); Xtrain (Xtrain - mu) ./ sigma; Xtest (Xtest - mu) ./ sigma; % SVM二分类 svmModel fitcsvm(Xtrain, Ytrain, ... KernelFunction, rbf, ... BoxConstraint, 1, ... KernelScale, auto); % 随机森林 rfModel TreeBagger(300, Xtrain, Ytrain, ... Method, classification, ... MinLeafSize, 5, ... OOBPrediction, on); % 预测与评估 svmPred predict(svmModel, Xtest); rfPred str2double(predict(rfModel, Xtest)); % TreeBagger返回字符串类别PCA如果要加进去一定按照我前面说的方式做这里给出标准写法[coeff, score, ~, ~, explained] pca(Xtrain); k find(cumsum(explained) 90, 1, first); XtrainPca score(:, 1:k); XtestPca (Xtest - mean(Xtrain)) * coeff(:, 1:k);注意Xtest已经做了标准化所以这里直接减mean(Xtrain)没问题。如果不放心可以在标准化之后保存mu和sigma部署时用同一套参数处理新样本。CNN部分的代码框架是这样net resnet18; lgraph layerGraph(net); % 查看要替换的层名不同版本有差异 newFc fullyConnectedLayer(2, Name, fruit_fc); newSoftmax softmaxLayer(Name, fruit_softmax); newClass classificationLayer(Name, fruit_classoutput); lgraph replaceLayer(lgraph, fc1000, newFc); lgraph replaceLayer(lgraph, fc1000_softmax, newSoftmax); lgraph replaceLayer(lgraph, ClassificationLayer_fc1000, newClass); % 数据存储与增强 augimdsTrain augmentedImageDatastore([224 224], imdsTrain, ... DataAugmentation, imageDataAugmenter( ... RandRotation, [-15 15], RandXTranslation, [-5 5], ... RandYTranslation, [-5 5], RandXScale, [0.9 1.1])); options trainingOptions(adam, ... InitialLearnRate, 1e-4, ... MaxEpochs, 10, ... MiniBatchSize, 32, ... ValidationData, augimdsVal, ... ValidationFrequency, 10, ... Plots, training-progress); trainedNet trainNetwork(augimdsTrain, lgraph, options);层名在不同Matlab版本可能不一样用analyzeNetwork(lgraph)看过之后再替换千万不要照抄。跑CNN之前最好确认一下有没有GPU没有GPU就用CPU训练时间会拉长很多。6. 评估指标怎么算分选项目不能只看准确率。假设缺陷果只占5%你全判成好果都有95%准确率但这对产线没有任何意义。关键要关注缺陷类的召回率。6.1 混淆矩阵和核心指标混淆矩阵用confusionchart画一眼能看出哪个类别容易被漏掉。核心指标我一般这么算confmat confusionmat(Ytest, svmPred); TP confmat(2,2); FP confmat(1,2); FN confmat(2,1); TN confmat(1,1); precision TP / (TP FP); recall TP / (TP FN); f1 2 * precision * recall / (precision recall); fpr FP / (FP TN);我要的三个指标是优等果误判率好果被扔掉、缺陷果漏检率坏果流出、以及综合F1。产线上更看重前两个因为漏检直接导致消费者投诉误判则带来原料浪费。将决策阈值往缺陷方向调一点漏检率会下降但误判率会上升这个平衡要根据客户接受度来定。6.2 使用概率阈值而不是硬分类大多数分类器都能输出概率分数不一定非要卡在0.5。我在随机森林的输出predict会得到两个类的分数于是直接设置如果缺陷概率大于0.6判缺陷小于0.3判合格中间区域进入人工抽检通道。这种三段式策略在全自动和全人工之间找到了一个缓冲现场反馈非常好。所以在评估阶段不要只报告一个准确率而要给出PR曲线或者至少给出“固定召回率下的误报率”。我会把决策阈值画成曲线给需求方解释清楚你想少漏掉一个烂果就要接受多扔掉几十个好果曲线把这种权衡摆到桌面上。7. 我踩过的坑这些坑有技术层面的也有管理层面的但每一条都真实消耗过项目时间。我按严重程度列一下。7.1 光照变化毁掉整个特征空间第一次实验我在同一个场景拍的训练集上准确率达到93%一搬到产线白天工位准确率直接掉到71%。原因是白天环境光渗入图像色温和亮度不断变化。解决方案是加物理遮光罩和固定光源然后在代码里加白平衡校准。这里没有任何模型技巧能替代硬件层面的稳定。7.2 数据泄漏PCA做错了都不知道早期我把全部数据放在一起做PCA然后用降维后的数据划分训练集和测试集验证准确率高达98%当时还很兴奋。后来发现测试集的信息已经通过PCA的均值和变换矩阵混进了训练阶段属于典型的数据泄漏。改正之后准确率回落到91%。这其实是很多特征工程项目里最隐蔽的坑因为代码不会报错结果看起来还特别漂亮。同理数据增强也不能在划分数据集之前做。如果先把全部图像旋转翻倍再做划分同一条原始图的增强版本可能同时出现在训练集和测试集里模型就在“背答案”。7.3 标注标准不一致有一阵子模型老是把带柄的水果误判成缺陷我排查后发现标注人员甲把所有果柄区域都标为背景标注人员乙却保留果柄。同一个部位在不同样本里标签相反模型当然学不到稳定边界。解决方法是写标注手册逐条定义什么算缺陷、什么算正常生理特征并定期抽检标注一致性。否则模型在训练过程中会不断摇摆。7.4 类别不平衡被随机森林放大了随机森林虽然对噪声鲁棒但类别不平衡时它会偏向多数类尤其当缺陷类样本太少树几乎学不到有区分度的分裂规则。我用两种手段缓解一是对缺陷类做数据增强二是对少数类设置更高的样本权重在Matlab里用prior参数指定先验。先验设置要谨慎设置过高会把好果大量误判成坏果。7.5 不要把推理时间想得太理想CNN的准确率确实高但在一台普通工控机的CPU上跑一次推理可能要50毫秒加上图像采集和机械手动作整个周期很容易超过产线节拍。后来我尝试过把输入图从224×224降到128×128推理时间减了一半准确率只掉1个百分点。所以在选模型之前先锁定产线节拍再倒推允许的推理时间这个顺序不能反。8. 一点经验之谈最后聊几句非技术的体会。做分选项目算法只是最后一步前面数据、光照、机械结构里任何一个环节出问题算法都要背锅。我强烈建议先花半天时间到产线旁边看工人怎么分拣不要急着采集数据先把“缺陷”的定义和现场的实际比例搞清楚。工人手一摸就知道这个果发软但图像上可能只是颜色暗了一点点这两者之间的信息差决定了你需要多强的特征和模型。还有一点Matlab代码不是写完训练完就能交差一定要做一个模型保存和加载的闭环训练好之后用save保存模型现场用load加载再写一个只依赖图像输入和工具箱的推理脚本。这样换一台设备、换一套相机测试成本会低很多。如果模型文件比较大比如CNN网络动辄几十MB记得考虑现场设备的存储和加载时间。这个项目后续如果想扩展可以在数据集上继续做两类事情一是收集更多现场运行数据慢慢把边界样本喂进去做增量训练二是在同一套代码框架里对比传统特征和深度学习特征的融合效果。水果分选的难点永远在尾部的小概率缺陷谁能在那些样本上稳定判断谁才是真正把系统做到了可落地。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑