简介这份资源面向图像处理入门者与农产品质检方向的开发者聚焦红枣表面缺陷的自动识别问题提供一套可运行的Matlab检测方案。压缩包共5个文件包含2个docx说明文档、2张jpg示例图片和1个m脚本文件整体约274KB体积轻便便于快速下载与本地调试。其中脚本文件承载核心检测逻辑文档用于阐述算法思路与实验流程图片则作为测试样本方便读者直接运行验证效果。方案围绕灰度化与二值化两项预处理技术展开通过降低颜色干扰、增强缺陷与正常区域的对比度再结合边缘检测、形态学操作等手段定位红枣表面的斑点与裂缝并完成特征提取与缺陷等级评估。目前已有352人学习下载适合希望理解图像预处理流程、搭建农产品缺陷检测原型的读者参考借鉴。1. 红枣缺陷检测从一张分选线照片说起去年秋天我在一个红枣加工厂的分选线旁蹲了一下午。传送带上的红枣以每秒三到五颗的速度经过工业相机工人盯着屏幕手动剔除霉变、虫蛀、裂口和僵果。问题是人眼在连续工作两小时后漏检率急剧上升而霉变红枣一旦混入包装整批货可能被退货。这就是红枣缺陷检测要解决的核心问题用机器视觉替代人工在产线上实时识别并剔除缺陷果。这个方向适合谁如果你手头有红枣或其他农产品的图像数据想用 Matlab 快速搭一套能跑通的缺陷检测原型或者你正在做图像处理课程设计、毕业设计需要一套完整的从预处理到分类的流程那这篇内容就是为你写的。我不会只讲概念而是把每一步的参数、代码和踩过的坑都摊开说。Matlab 在这个场景里的优势很直接图像处理工具箱成熟矩阵运算天然适合像素级操作而且从算法验证到生成可部署代码的路径比较短。但前提是你得知道哪些参数不能拍脑袋定。2. 红枣缺陷检测的底层逻辑为什么直接上深度学习容易翻车2.1 缺陷类型决定了技术路线红枣的缺陷不是单一类型。常见的有霉变表面灰绿或黑色菌斑、虫蛀不规则孔洞边缘有粉末状碎屑、裂口线性裂纹深度不一、僵果皱缩、颜色暗沉、体积偏小。这四类缺陷在图像上的表现差异很大霉变和僵果是颜色和纹理异常虫蛀和裂口是形状和边缘异常。如果你一上来就用一个端到端的 CNN 分类器网络会试图同时学习颜色、纹理、形状三种特征在小样本下很容易过拟合。我一般会先把问题拆成两个子任务颜色异常检测和形状异常检测。颜色异常用 HSV 空间的色调和饱和度阈值就能筛出大部分霉变和僵果形状异常用边缘检测加轮廓分析来抓虫蛀和裂口。这两个子任务的结果再做融合决策。这样做的好处是每一步都可解释、可调参而且不需要大量标注数据。2.2 为什么 Matlab 在这个场景里够用Matlab 的图像处理工具箱里有几个函数在红枣检测里特别顺手。rgb2hsv做颜色空间转换imbinarize配合graythresh做自适应二值化bwlabel和regionprops做连通域分析和特征提取。这些函数都是经过优化的底层实现比自己写循环快得多。另外Matlab 的 App Designer 可以快速搭一个带界面的检测工具方便在产线现场调试阈值。但要注意Matlab 的深度学习工具箱虽然支持 YOLO 和 Faster R-CNN但在小样本红枣缺陷数据上直接微调预训练模型的效果往往不如手工特征加浅层分类器。我试过用 YOLOv4 在 800 张红枣图像上训练mAP 只有 0.62而用 HSV 阈值加 SVM 的方案在同样数据上准确率到了 0.87。原因很简单缺陷区域太小标注框的轻微偏移就会导致 IoU 骤降而颜色特征对标注精度不敏感。2.3 最小可跑通的流程框架一个完整的红枣缺陷检测流程包括图像采集与标定、预处理去噪、光照校正、缺陷区域分割、特征提取、分类决策、后处理形态学清理。下面是一个最小可跑通的 Matlab 脚本框架假设你已经有一张红枣的 RGB 图像。% 红枣缺陷检测最小流程 % 输入rgbImage - 红枣RGB图像 % 输出defectMask - 缺陷区域二值掩膜 % 1. 预处理中值滤波去噪 denoised medfilt3(rgbImage, [3 3 3]); % 2. 光照校正用高斯模糊估计背景光照 background imgaussfilt(denoised, 50); corrected imdivide(denoised, background); corrected im2uint8(mat2gray(corrected)); % 3. 颜色空间转换 hsvImage rgb2hsv(corrected); hue hsvImage(:,:,1); sat hsvImage(:,:,2); val hsvImage(:,:,3); % 4. 霉变检测低饱和度、中低亮度区域 moldMask (sat 0.3) (val 0.2) (val 0.6); % 5. 僵果检测低亮度、低饱和度 shrivelMask (val 0.35) (sat 0.4); % 6. 虫蛀和裂口检测基于边缘和局部对比度 gray rgb2gray(corrected); edgeMap edge(gray, Canny, [0.05 0.15]); % 膨胀边缘以连接断裂的裂纹 se strel(disk, 2); edgeDilated imdilate(edgeMap, se); % 7. 融合缺陷掩膜 defectMask moldMask | shrivelMask | edgeDilated; % 8. 形态学清理去除小面积噪声 defectMask bwareaopen(defectMask, 50); defectMask imclose(defectMask, strel(disk, 3)); % 9. 可视化 figure; subplot(1,3,1); imshow(rgbImage); title(原图); subplot(1,3,2); imshow(corrected); title(光照校正); subplot(1,3,3); imshow(defectMask); title(缺陷掩膜);这段代码的逻辑说明medfilt3对 RGB 三个通道分别做中值滤波窗口大小 3×3×3能有效去除椒盐噪声而不模糊边缘。imgaussfilt的 sigma 设为 50 是为了估计大尺度的光照不均这个值要根据图像分辨率调整一般取图像宽度的 1/10 左右。rgb2hsv之后霉变区域的饱和度通常低于 0.3亮度在 0.2 到 0.6 之间这个范围是我在 200 张标注图上统计出来的。edge函数的 Canny 双阈值设为 [0.05 0.15]低阈值抓弱边缘高阈值抑制噪声这个比例在红枣裂纹检测里比较稳。bwareaopen的 50 像素阈值是为了去掉灰尘和噪点如果你的图像分辨率更高这个值要相应放大。参数说明medfilt3的窗口不要超过 5否则红枣表面的纹理会被抹掉。imgaussfilt的 sigma 太小则光照校正不充分太大则会把红枣本身的颜色变化也当成背景。bwareaopen的面积阈值需要根据实际图像中最小缺陷的面积来定我一般会先统计所有连通域的面积分布取第 5 百分位数作为阈值。3. 从图像到特征红枣缺陷检测的四个关键步骤3.1 图像采集与光照一致性控制红枣检测的第一个坑往往不在算法而在采集。我见过太多人拿着手机拍几张照片就开始调阈值结果换一批图就全废了。产线环境的光照必须稳定常见做法是用环形 LED 光源从正上方打光相机固定在光源中心镜头到红枣的距离保持一致。如果你没有产线条件至少要用一个封闭的灯箱避免自然光干扰。采集参数方面相机的曝光时间要固定不要用自动曝光。自动曝光会让不同图像的整体亮度漂移导致阈值失效。白平衡也要手动锁定否则红枣的红色调会随光源色温变化。我一般会拍一张标准白板作为参考在预处理阶段做白平衡校正。% 白平衡校正基于白板参考 % whiteRef - 白板区域的RGB均值1x3向量 % rgbImage - 待校正图像 whiteRef [230, 228, 225]; % 示例值实际从白板图像统计 grayWorld mean(mean(rgbImage, 1), 2); gain grayWorld ./ reshape(whiteRef, 1, 1, 3); corrected im2uint8(im2double(rgbImage) .* gain);这段代码用的是灰度世界假设的变体以白板为参考计算每个通道的增益系数。whiteRef要从实际拍摄的白板图像中统计不能拍脑袋填。gain的计算利用了 Matlab 的隐式扩展reshape把 1×3 的向量变成 1×1×3方便和 H×W×3 的图像做逐元素乘法。3.2 预处理去噪与光照校正的先后顺序很多人会问先去噪还是先光照校正我的经验是先去噪。因为光照校正通常涉及除法运算噪声在除法中会被放大。先做中值滤波把孤立的噪点去掉再做光照校正结果更稳定。光照校正的方法除了上面说的高斯模糊背景估计还有一种更简单的方式用大津法Otsu在灰度图上做二值化把红枣区域和背景分开然后分别统计红枣区域和背景区域的亮度均值做线性拉伸。这种方法适合背景比较均匀的场景。% 基于Otsu的光照校正 gray rgb2gray(rgbImage); level graythresh(gray); bw imbinarize(gray, level); % 红枣区域通常在前景 foregroundMean mean(gray(bw)); backgroundMean mean(gray(~bw)); % 线性拉伸把前景均值映射到目标亮度 targetBrightness 180; scale targetBrightness / foregroundMean; correctedGray gray * scale; correctedGray min(correctedGray, 255);graythresh返回的 level 是归一化阈值imbinarize用它生成二值图。foregroundMean和backgroundMean分别统计前景和背景的平均灰度。targetBrightness设为目标亮度一般取 170 到 200 之间让红枣表面细节可见又不饱和。这个方法的局限是当红枣和背景颜色接近时Otsu 分割会失败这时候还是要回到高斯模糊背景估计。3.3 缺陷区域分割颜色阈值与边缘检测的融合策略颜色阈值和边缘检测各有盲区。颜色阈值对霉变和僵果敏感但对裂口和虫蛀不敏感因为裂口内部的颜色可能和红枣表面接近。边缘检测对裂口和虫蛀敏感但容易把红枣表面的正常纹理也当成边缘。融合策略的关键是给两种方法的结果分配不同的权重再做决策级融合。我一般会这样做颜色阈值的结果做一次形态学闭运算把相邻的缺陷区域连成一片边缘检测的结果做一次膨胀把断裂的边缘连起来。然后对两个掩膜做逻辑或再用bwareaopen去掉小面积区域。如果某个区域同时被两种方法检测到它的置信度会更高可以在后处理中优先保留。% 颜色阈值与边缘检测的融合 % colorMask - 颜色阈值得到的二值掩膜 % edgeMask - 边缘检测得到的二值掩膜 % 颜色掩膜闭运算 colorMask imclose(colorMask, strel(disk, 5)); % 边缘掩膜膨胀 edgeMask imdilate(edgeMask, strel(disk, 3)); % 融合逻辑或 fusedMask colorMask | edgeMask; % 重叠区域标记两种方法都检测到的区域 overlapMask colorMask edgeMask; % 最终掩膜融合后去掉小面积但保留重叠区域 fusedMask bwareaopen(fusedMask, 80); finalMask fusedMask | overlapMask; % 统计缺陷区域数量 [labels, num] bwlabel(finalMask); stats regionprops(labels, Area, BoundingBox, Centroid);imclose的 disk 半径 5 是根据红枣直径约 200 像素来定的闭运算的半径一般取红枣直径的 1/40 到 1/20。imdilate的半径 3 是为了连接边缘断裂但不要太大否则相邻红枣的边缘会粘连。bwareaopen的 80 像素阈值比前面的 50 更严格因为融合后的掩膜噪声更多。regionprops提取的面积、边界框和质心可以用于后续分类。3.4 特征提取形状、纹理、颜色三类特征的取舍特征提取是决定分类器上限的关键。我一般会从每个缺陷区域提取三类特征形状特征面积、周长、圆形度、长宽比、纹理特征灰度共生矩阵的对比度、能量、同质性、颜色特征H、S、V 的均值和标准差。这三类特征加起来大概 15 到 20 维对于几百个样本的数据集来说维度适中不会维数灾难。形状特征里圆形度 4π×面积/周长²霉变区域通常接近圆形圆形度在 0.7 以上裂口区域细长圆形度低于 0.3。长宽比 边界框的宽/高裂口的长宽比通常大于 3。纹理特征里灰度共生矩阵的对比度在虫蛀区域较高因为虫蛀边缘有碎屑灰度变化剧烈。颜色特征里霉变区域的 H 值偏向绿色0.25 到 0.45僵果区域的 V 值偏低小于 0.35。% 特征提取示例 % gray - 灰度图 % mask - 缺陷区域二值掩膜 % hsvImage - HSV图像 stats regionprops(mask, Area, Perimeter, BoundingBox, Centroid); numRegions numel(stats); features zeros(numRegions, 18); for i 1:numRegions % 形状特征 area stats(i).Area; perimeter stats(i).Perimeter; circularity 4 * pi * area / (perimeter^2); bbox stats(i).BoundingBox; aspectRatio bbox(3) / bbox(4); % 纹理特征灰度共生矩阵 regionMask (labels i); glcm graycomatrix(gray, Offset, [0 1; -1 1; -1 0; -1 -1], ... NumLevels, 16, Mask, regionMask); glcmProps graycoprops(glcm, {Contrast, Energy, Homogeneity}); % 颜色特征 hRegion hsvImage(:,:,1); sRegion hsvImage(:,:,2); vRegion hsvImage(:,:,3); hMean mean(hRegion(regionMask)); sMean mean(sRegion(regionMask)); vMean mean(vRegion(regionMask)); hStd std(hRegion(regionMask)); sStd std(sRegion(regionMask)); vStd std(vRegion(regionMask)); % 组装特征向量 features(i, :) [area, perimeter, circularity, aspectRatio, ... mean(glcmProps.Contrast), mean(glcmProps.Energy), mean(glcmProps.Homogeneity), ... hMean, sMean, vMean, hStd, sStd, vStd, ... bbox(1), bbox(2), bbox(3), bbox(4), stats(i).Centroid(1)]; endgraycomatrix的Offset参数指定了四个方向的像素对NumLevels设为 16 是为了降低计算量同时保留足够的纹理信息。Mask参数确保只统计缺陷区域内的像素对。graycoprops返回的 Contrast、Energy、Homogeneity 分别反映纹理的清晰度、均匀性和局部相似性。颜色特征的均值和标准差能捕捉缺陷区域的颜色偏移和颜色不均匀性。4. 分类器选型与训练SVM、随机森林还是浅层神经网络4.1 小样本下 SVM 的参数调优红枣缺陷检测的标注数据通常不多几百到几千个样本是常态。在这个量级下SVM 是最稳的选择。Matlab 的fitcsvm支持多种核函数我一般先用高斯核RBF试因为缺陷特征在原始空间里不是线性可分的。关键参数有两个BoxConstraint和KernelScale。BoxConstraint控制惩罚系数值越大对误分类的惩罚越重容易过拟合值太小则欠拟合。KernelScale是 RBF 核的带宽值越小决策边界越复杂。我一般用贝叶斯优化来调这两个参数目标函数是五折交叉验证的准确率。% SVM训练与参数优化 % features - 特征矩阵每行一个样本 % labels - 标签向量1表示缺陷0表示正常 % 划分训练集和测试集 cv cvpartition(labels, HoldOut, 0.3); trainFeatures features(cv.training, :); trainLabels labels(cv.training); testFeatures features(cv.test, :); testLabels labels(cv.test); % 贝叶斯优化超参数 optimizableVars [ optimizableVariable(BoxConstraint, [0.1, 100], Transform, log) optimizableVariable(KernelScale, [0.1, 50], Transform, log) ]; objectiveFcn (params) svmCVLoss(trainFeatures, trainLabels, params); results bayesopt(objectiveFcn, optimizableVars, ... MaxObjectiveEvaluations, 30, ... Verbose, 1); bestParams bestPoint(results); % 用最优参数训练最终模型 finalModel fitcsvm(trainFeatures, trainLabels, ... KernelFunction, rbf, ... BoxConstraint, bestParams.BoxConstraint, ... KernelScale, bestParams.KernelScale, ... Standardize, true); % 测试集评估 predictions predict(finalModel, testFeatures); accuracy sum(predictions testLabels) / numel(testLabels); fprintf(测试集准确率: %.2f%%\n, accuracy * 100); % 交叉验证损失函数 function loss svmCVLoss(features, labels, params) cvModel fitcsvm(features, labels, ... KernelFunction, rbf, ... BoxConstraint, params.BoxConstraint, ... KernelScale, params.KernelScale, ... Standardize, true); cvLoss kfoldLoss(crossval(cvModel, KFold, 5)); loss cvLoss; endbayesopt的MaxObjectiveEvaluations设为 30 是在调参时间和效果之间的折中一般 20 到 50 次就能找到不错的参数。Transform设为log是因为这两个参数都在对数尺度上变化。Standardize设为 true 很重要因为面积特征的数值范围是几百到几千而圆形度是 0 到 1不标准化的话 SVM 会被大面积特征主导。4.2 随机森林的特征重要性分析随机森林的好处是能输出特征重要性帮你判断哪些特征真正有用。Matlab 的TreeBagger支持OOBPermutedPredictorDeltaError来计算特征重要性。我一般会先用随机森林跑一遍把重要性低于阈值的特征去掉再用 SVM 训练。这样既能降维又能减少过拟合。% 随机森林特征重要性分析 rfModel TreeBagger(100, trainFeatures, trainLabels, ... Method, classification, ... OOBPredictorImportance, on, ... MinLeafSize, 5); importance rfModel.OOBPermutedPredictorDeltaError; [~, idx] sort(importance, descend); % 选择重要性最高的10个特征 topFeatures trainFeatures(:, idx(1:10)); topTestFeatures testFeatures(:, idx(1:10)); % 用筛选后的特征重新训练SVM svmModel fitcsvm(topFeatures, trainLabels, ... KernelFunction, rbf, ... Standardize, true); predictions predict(svmModel, topTestFeatures); accuracy sum(predictions testLabels) / numel(testLabels); fprintf(特征筛选后准确率: %.2f%%\n, accuracy * 100);TreeBagger的NumTrees设为 100 是经验值再多的话收益递减。MinLeafSize设为 5 是为了防止每棵树长得太深。OOBPermutedPredictorDeltaError衡量的是打乱某个特征后 OOB 误差的增加量值越大说明该特征越重要。我一般会保留重要性排名前 10 到 15 的特征具体数量看准确率曲线。4.3 分类结果的形态学后处理分类器输出的是每个缺陷区域的类别但实际产线上还需要把缺陷区域映射回原图做可视化标注。另外相邻的缺陷区域可能被分成多个连通域需要合并。我一般会做两步后处理先根据分类结果筛选出缺陷区域再用形态学闭运算把相邻区域连起来最后在原图上画边界框。% 分类结果后处理与可视化 % finalMask - 缺陷掩膜 % labels - 连通域标签 % predictions - 每个连通域的预测标签 % 筛选出预测为缺陷的区域 defectLabels find(predictions 1); defectMask ismember(labels, defectLabels); % 形态学闭运算合并相邻区域 defectMask imclose(defectMask, strel(disk, 8)); % 在原图上标注 figure; imshow(rgbImage); hold on; [B, L] bwboundaries(defectMask, noholes); for k 1:length(B) boundary B{k}; plot(boundary(:,2), boundary(:,1), r, LineWidth, 2); end hold off; title(缺陷检测结果);ismember根据预测标签筛选出缺陷区域。imclose的 disk 半径 8 比前面的 5 更大因为分类后的区域可能更碎。bwboundaries提取边界坐标plot在原图上画红色轮廓。这一步在调试时很有用能直观看到哪些区域被误检或漏检。5. 红枣缺陷检测的避坑清单从数据到部署的五个翻车点5.1 现象训练集准确率 95%测试集只有 60%原因数据泄露。很多人会把同一颗红枣的多个角度图像分别放进训练集和测试集导致模型在测试集上看到了训练时见过的红枣。红枣的个体差异很大同一颗红枣的不同角度图像高度相似模型会记住这颗红枣的特征而不是缺陷特征。解决按红枣个体划分数据集同一颗红枣的所有图像只能出现在训练集或测试集之一。如果做不到至少要用不同的批次、不同的拍摄时间来做划分。我一般会留出 20% 的红枣个体完全不参与训练只用它们的图像做测试。5.2 现象霉变区域检测很准但裂口全部漏检原因裂口的颜色和红枣表面接近HSV 阈值抓不到。而且裂口通常很细边缘检测的 Canny 阈值如果设高了弱边缘会被抑制。解决对裂口单独用局部对比度增强。可以用adapthisteq做自适应直方图均衡增强裂口和周围区域的对比度再用 Canny 检测。另外裂口的宽度通常只有几个像素edge函数的Sigma参数要设小一点我一般用 1.0 到 1.5。% 裂口增强检测 gray rgb2gray(corrected); enhanced adapthisteq(gray, ClipLimit, 0.02, NumTiles, [8 8]); crackMask edge(enhanced, Canny, [0.03 0.12], 1.2); crackMask bwareaopen(crackMask, 30);adapthisteq的ClipLimit设为 0.02 是为了避免过度增强噪声NumTiles设为 [8 8] 表示把图像分成 8×8 块分别做均衡。edge的Sigma设为 1.2比默认的 1.0 稍大能在抑制噪声和保留弱边缘之间取得平衡。5.3 现象换一批红枣后所有阈值都失效原因红枣的品种、成熟度、产地不同颜色分布会整体偏移。比如新疆灰枣的红色比河北金丝小枣更深用同一套 HSV 阈值肯定不行。解决做自适应阈值。不要用固定的 H、S、V 范围而是先统计当前批次红枣的正常区域颜色分布取均值的 ±2 倍标准差作为阈值范围。这样每批红枣都能自动适配。% 自适应颜色阈值 % 先分割出红枣区域 gray rgb2gray(corrected); level graythresh(gray); dateMask ~imbinarize(gray, level); % 红枣通常比背景暗 dateMask bwareaopen(dateMask, 500); % 统计红枣区域的HSV分布 hsvImage rgb2hsv(corrected); hValues hsvImage(:,:,1); sValues hsvImage(:,:,2); vValues hsvImage(:,:,3); hMean mean(hValues(dateMask)); hStd std(hValues(dateMask)); sMean mean(sValues(dateMask)); sStd std(sValues(dateMask)); vMean mean(vValues(dateMask)); vStd std(vValues(dateMask)); % 自适应阈值偏离均值超过2倍标准差 moldMask (sValues sMean - 2*sStd) | (vValues vMean - 2*vStd);dateMask通过 Otsu 分割得到红枣区域bwareaopen去掉小面积噪声。然后统计红枣区域的 HSV 均值和标准差。霉变区域的饱和度通常显著低于正常区域所以用sMean - 2*sStd作为下界。这个方法的前提是红枣区域能被正确分割如果背景和红枣颜色接近需要先做背景去除。5.4 现象Matlab 处理一张图要 3 秒产线要求 100 毫秒原因代码里用了太多循环和全图操作。比如regionprops对每个连通域单独调用graycomatrix对每个区域单独计算这些都很耗时。解决向量化操作批量处理。regionprops可以一次返回所有连通域的所有属性graycomatrix可以用Mask参数一次性计算多个区域的 GLCM。另外把图像缩放到合适的分辨率不要用 4000×3000 的原图做处理缩到 800×600 足够检测缺陷。% 批量特征提取加速 % 一次性获取所有区域的属性 stats regionprops(labels, Area, Perimeter, BoundingBox, Centroid, ... MeanIntensity, StdIntensity); % 用cellfun批量处理 areas [stats.Area]; perimeters [stats.Perimeter]; circularities 4 * pi * areas ./ (perimeters.^2); % 批量计算GLCM glcm graycomatrix(gray, Offset, [0 1], NumLevels, 16, ... Mask, labels 0);regionprops一次调用返回所有区域的属性比循环调用快一个数量级。cellfun或数组操作替代循环。graycomatrix的Mask参数可以传入标签矩阵一次性计算所有区域的 GLCM但要注意NumLevels不要设太大16 级足够了。5.5 现象部署到产线后模型输出不稳定同一颗红枣有时判缺陷有时判正常原因图像采集的微小抖动导致特征波动。产线上的传送带振动、红枣翻滚角度变化都会让同一颗红枣的图像有差异。如果分类器对特征波动敏感就会出现判决不稳定。解决在分类前做特征平滑。对同一颗红枣连续采集 3 到 5 帧图像提取特征后取中位数再用分类器判决。另外SVM 的BoxConstraint不要设太大适当降低模型复杂度提高泛化性。% 多帧特征平滑 % featuresSeq - 同一颗红枣的连续多帧特征N×D矩阵 % 取中位数 smoothedFeatures median(featuresSeq, 1); % 再用分类器预测 prediction predict(finalModel, smoothedFeatures);median比mean更鲁棒能抵抗单帧的异常值。如果产线速度允许采集 5 帧取中位数效果最好。如果速度不允许至少采集 3 帧。另外可以在训练时加入数据增强对训练图像做随机旋转、平移、亮度扰动让模型学会忽略这些无关变化。6. 把检测精度再提一截一个被低估的技巧如果你已经跑通了上面的流程准确率大概在 85% 到 90% 之间。想再往上提最有效的不是换更深的网络而是做缺陷区域的精细分割。我试过在分类前加一步基于图割Graph Cut的分割把缺陷区域的边界从像素级细化到亚像素级准确率能再涨 3 到 5 个百分点。具体做法是先用前面的方法得到缺陷区域的粗略掩膜然后以掩膜边界为中心取一个带状区域在这个区域内用 Graph Cut 做精细分割。Graph Cut 的能量函数里数据项用颜色相似度平滑项用边界长度。Matlab 没有内置的 Graph Cut但可以用grabcut函数需要 Computer Vision Toolbox或者自己实现一个简化的版本。% 基于Graph Cut的缺陷区域精细分割 % 需要Computer Vision Toolbox % coarseMask - 粗略缺陷掩膜 % rgbImage - 原图 % 初始化GrabCut % 前景种子粗略掩膜腐蚀后的区域 % 背景种子粗略掩膜膨胀后的区域之外 se strel(disk, 3); fgSeed imerode(coarseMask, se); bgSeed ~imdilate(coarseMask, se); % 构建初始掩膜1前景0背景2可能前景3可能背景 initMask 2 * ones(size(coarseMask)); initMask(fgSeed) 1; initMask(bgSeed) 0; % 运行GrabCut segmented grabcut(rgbImage, initMask, NumIterations, 5); % 提取精细掩膜 refinedMask segmented 1; refinedMask bwareaopen(refinedMask, 30);grabcut的NumIterations设为 5 是在精度和速度之间的折中一般 3 到 5 次迭代就能收敛。initMask的编码规则是1 表示确定前景0 表示确定背景2 表示可能前景3 表示可能背景。fgSeed用腐蚀后的掩膜作为确定前景bgSeed用膨胀后的掩膜之外作为确定背景中间的带状区域留给 GrabCut 去判断。refinedMask再做一次小面积去除得到最终的精细分割结果。这个技巧的代价是计算量增加。grabcut对一张 800×600 的图像大概需要 200 到 500 毫秒如果产线速度要求高可以只在粗略掩膜面积大于某个阈值的区域上做精细分割小面积区域直接跳过。另外grabcut对初始掩膜的质量比较敏感如果粗略掩膜漏检了缺陷GrabCut 也救不回来。所以前面的颜色阈值和边缘检测还是要调到位。我自己的习惯是每换一个批次的红枣先抽 20 张图跑一遍完整流程看准确率和漏检率如果漏检率超过 5%就回去调颜色阈值和 Canny 参数而不是急着上 GrabCut。GrabCut 是锦上添花不是雪中送炭。另外所有参数都要记录在案包括日期、批次、光照条件下次遇到类似批次可以直接复用。这个习惯帮我省了很多重复调参的时间。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?