ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

基于MATLAB的胶囊缺陷检测:从图像处理到分类器落地

基于MATLAB的胶囊缺陷检测:从图像处理到分类器落地

简介:基于MATLAB的胶囊缺陷检测项目资源包,面向药企质检工程师、计算机视觉初学者及AI开发人员,聚焦制药产线上胶囊缺损、污渍、形变等缺陷的自动识别,用深度学习与图像处理技术替代传统人工目检。包内共44个文件,以12个MATLAB功能脚本(.m)为核心,辅以多组tiff与png胶囊图像样本、4个.mat模板/模型文件以及readme和doc说明文档,整体约19.16MB。资源完整覆盖图像灰度化、直方图均衡化、滤波去噪、梯度引导滤波等预处理环节,包含基于HSV的去雾算法、模板匹配、Dice相似度计算等检测逻辑;特征提取方面既涉及SIFT、SURF、HOG等经典描述子,也支持通过卷积神经网络自动学习缺陷特征,借助MATLAB深度学习工具箱完成CNN模型构建、训练与超参数调优,支持二分类及多分类缺陷判别,并可扩展至视频流目标检测与实时识别。已有31人浏览学习,项目代码注释完整、目录层次清晰,配套doc文档对原理和用法作了解释,适合作为毕业设计、课程项目或企业质检二次开发的基础框架,便于快速上手、调试与二次部署。

1. 为什么胶囊缺陷检测首选 MATLAB?从样本量到上线的现实账

胶囊缺帽、破损、气泡、污渍这类问题,在药品和保健品产线上非常常见。很多团队一上来就想上深度学习,结果样本只有几百张,缺陷种类还不均衡,模型训出来在车间里一跑就翻车。而 MATLAB 做胶囊缺陷检测,优势不在“算法新”,在于链路短:图像采集、预处理、分割、特征提取、分类验证全在一个环境里完成,调试时能随时把中间变量画出来看,这对产线落地来说比任何 fancy 框架都实用。这篇文章就是按我自己做过的一套方案,从成像选型、图像处理链路、分类器调参到批处理上线,把每一步的参数和坑位讲清楚,适合正在做视觉检测方案评估或 MATLAB 图像处理项目的工程师参考。

2. 先搭成像再谈算法:背光方案与检测流程的选型逻辑

2.1 为什么先定光学方案,而不是先写代码

很多人做缺陷检测的第一反应是打开 MATLAB 写图像处理函数,这是顺序搞反了。胶囊是半透明塑料或明胶外壳,表面有弧形反光,光源角度稍微不对,好品上就会出现酷似裂纹的阴影。我经手的项目里,至少有一半的“算法误判”问题,最后都溯源到成像环境而不是代码逻辑。

胶囊检测最常见的成像方案是背光照明:胶囊放在漫射背光板上,相机从上往下拍。背光下胶囊呈现深色轮廓,内部缺陷(气泡、杂质、破洞)会表现为轮廓内部或边缘的灰度异常。这种方案的优点是:前景背景对比度极高,分割阈值好定;缺陷的细节在轮廓图上非常敏感;光源成本低,一块背光板加一个普通工业相机就能跑。

如果你要检测的是胶囊表面的印刷字符、颜色不均,那需要换成前光或同轴光。但大多数产线把“胶囊有没有破损或缺帽”作为第一道质检关卡,背光足够。

2.2 相机选型和图像采集:从文件读图到实时采集

MATLAB 读图像有两条路。第一条是直接用imread读相机保存的图片文件,适合做算法开发和离线验证;第二条是通过 Image Acquisition Toolbox 连接 USB 或 GigE 工业相机,实时抓帧,适合产线联调。

% 离线开发:批量读取一个文件夹里的胶囊图片 fileList = dir('capsule_samples/*.png'); % 列出所有png文件 img = imread(fullfile(fileList(1).folder, fileList(1).name)); % 实时采集:用Image Acquisition Toolbox连接USB相机 % 先用 imaqhwinfo 查看系统识别到的相机设备 camInfo = imaqhwinfo('winvideo'); % 打开第一个设备,分辨率设为1280x960 cam = videoinput('winvideo', 1, 'YUY2_1280x960'); preview(cam); % 先预览,调整焦距和光源位置

逻辑说明:离线和实时两条链路共用一个算法函数,只是输入源不同。开发阶段一定先用离线图片把算法调到稳定,再切实时,否则在车间里边调相机边调阈值,变量太多根本查不过来。preview是调光用的,焦距、光圈、背光亮度都确认好之后再start正式采集。

2.3 检测流程总览:七步链路和每一步的输入输出

一套完整的胶囊缺陷检测链路可以拆成七步:图像采集 → 灰度化 → 去噪 → 光照校正 → 阈值分割 → 形态学处理 → 特征提取与分类。前四步是图像预处理,目的是把“一张照片”变成“一个干净的胶囊轮廓”;后三步是核心判断单元,决定“这个胶囊是好是坏”。

预处理的好坏直接决定分割的稳定性,分割的稳定性又决定特征的可信度。很多工程师在特征提取上花大量时间,但分割出来的区域本身就带毛刺、带孔洞,后面做什么都是错的。所以这个链路里我最看重的不是分类器选什么,而是前三步的参数是否经得起换一批胶囊、换一条产线。

3. 从灰度图到缺陷判定:MATLAB 处理链路的核心代码

3.1 预处理三步:灰度化、去噪、光照校正

胶囊图片进来的第一件事是转灰度。如果是彩色相机拍的,rgb2gray就够了。去噪我一般用中值滤波而不是高斯滤波,因为中值滤波对边缘保持更好,胶囊轮廓如果被高斯模糊了,分割出来的边界位置会偏移好几个像素,直接影响尺寸测量。光照校正使用顶帽变换,这一步是背光场景的关键:背光板本身亮度不均匀,或者胶囊排列位置靠近光源边缘时,图像背景会有明显的亮度梯度,直接做全局阈值分割会导致一侧胶囊被切掉。

% 读图并灰度化 img = imread('capsule_01.png'); if size(img, 3) == 3 gray = rgb2gray(img); else gray = img; end % 中值滤波:3x3窗口去除传感器噪点,保留边缘 denoised = medfilt2(gray, [3 3]); % 顶帽变换:先做开运算估计背景亮度,再用原图减背景 se = strel('disk', 15); % 结构元素半径要大于胶囊宽度的一半 background = imopen(denoised, se); flat = denoised - background; % 得到光照均匀的前景图

逻辑说明:strel('disk', 15)的半径是本步最重要的参数。如果结构元素太小,开运算会把胶囊本身也当成背景的一部分,减出来的前景图里胶囊变淡;如果太大,背景亮度估计不准确,边缘处还是会残留阴影。我的调参习惯是取胶囊宽度的 50% 到 80%,比如胶囊占 30 个像素宽,就取半径 15 到 25。

3.2 分割与形态学清洗:把“图”变成“区域”

光照校正后的图,背景接近 0,胶囊前景亮度明显,用imbinarize加固定阈值做分割即可。阈值取 0.1 到 0.3 之间比较常见。选固定阈值而不是 Otsu,是因为顶帽变换后的灰度分布相对稳定,Otsu 在背景和前景面积比例剧烈变化时会跟着变,反而引入不确定因素。

% 固定阈值二值化 bw = imbinarize(flat, 0.15); % 形态学闭运算:把胶囊内部的细小孔洞填掉(多数是反光造成的伪缺陷) bw = imclose(bw, strel('disk', 3)); % 形态学开运算:去掉背景中的孤立噪点 bw = imopen(bw, strel('disk', 2)); % 连通域提取,每个连通域是一个候选胶囊区域 cc = bwconncomp(bw); stats = regionprops(cc, 'Area', 'BoundingBox', 'Eccentricity', 'Solidity', 'Image');

逻辑说明:imclose填洞和imopen去噪点的顺序不能换,先闭后开。如果先开运算,胶囊边缘上的细小毛刺会被放大成缺口,闭运算再去补就补不干净了。regionprops返回的BoundingBox是后续判断胶囊长宽比的基础数据,Image字段则用于提取单个胶囊的局部图做精细特征计算。这里有个细节:bwconncomp默认把整幅图所有连通域都找出来,你需要先用 Area 过滤掉面积小于 500 像素的小区域,否则背景上的灰尘也会被当成候选胶囊进入特征提取。

3.3 特征提取与规则分类:不用机器学习也能干活

分割完成后,每个候选区域都有一组几何属性和灰度统计量。胶囊正常形态是长椭圆:长宽比通常在 2.5 到 5 之间(具体取决于胶囊型号和拍摄角度),Solidity 接近 1(轮廓没有明显缺口),Area 在一个稳定范围内。缺陷则各有特征:缺帽的胶囊长宽比明显变小;破损的胶囊 Solidity 偏低;气泡胶囊内部灰度不均匀,区域内有高亮点。

% 遍历每个候选区域,用规则判断胶囊是否合格 for k = 1:numel(stats) area = stats(k).Area; bbox = stats(k).BoundingBox; ratio = bbox(3) / bbox(4); % 宽/高比 % 几何规则:面积和长宽比是否在正常范围内 if area < 5000 || area > 20000 label = 'defect'; elseif ratio < 2.5 || ratio > 5.0 label = 'defect'; else label = 'good'; end % 提取该区域的灰度特征,用于检测内部气泡 roi = imcrop(flat, bbox); grayMean = mean(roi(:)); grayStd = std(roi(:)); if grayStd > 30 % 灰度波动大,可能是内部异物或气泡 label = 'defect'; end end

逻辑说明:这套规则分类的好处是每个阈值都有物理含义:胶囊长宽比低于 2.5 意味着帽和身分离,高于 5 意味着胶囊横躺或图像包含两个胶囊粘连。灰度标准差阈值需要统计好品样本的分布来确定。我的做法是:取 50 个好品胶囊计算灰度标准差的均值加 3 倍标准差作为阈值上限。

规则分类适合项目一期快速出结果,但它的局限在于阈值是硬边界,批次间的尺寸公差、光照漂移都可能让好品误判。这时候就需要引入训练分类器。

3.4 用统计工具箱训练分类器:从规则到模型的平滑过渡

当规则分类的误判率压不下来,或者缺陷类型太多、规则写不过来时,可以把前面提取的特征向量组织成特征表,用fitcecoc训练一个多类 SVM 分类器。这里的关键不是模型选型,而是特征表怎么构建和验证怎么做。

% 构建特征表:每一行是一个胶囊样本,列是特征 % 假设已提取了100个样本的特征,存在变量 featTable 中 % featTable 的列:Area, Ratio, Solidity, GrayStd, GrayMean features = [areaVec, ratioVec, solidityVec, grayStdVec, grayMeanVec]; featTable = array2table(features, 'VariableNames', ... {'Area', 'Ratio', 'Solidity', 'GrayStd', 'GrayMean'}); featTable.Label = categorical(labelVec); % 'good' / 'defect' % 训练多类SVM分类器 Mdl = fitcecoc(featTable, 'Label'); % 交叉验证评估泛化能力 cvMdl = crossval(Mdl, 'KFold', 5); loss = kfoldLoss(cvMdl); fprintf('交叉验证损失: %.2f%%\n', loss * 100);

逻辑说明:fitcecoc内部把多类问题拆成多个二分类器,适合缺陷类型多的情况。KFold设 5 而不是 10,因为缺陷样本通常少,10 折会导致每一折的训练集里缺陷样本太少。交叉验证的损失如果超过 5%,不要急着调模型参数,先回去看特征——大概率是特征本身区分度不够,或者样本标注有噪声。注意特征表里的数值范围差异大(面积几千,长宽比只有几),SVM 对尺度敏感,最好先做归一化,可以用normalize(features)或zscore。

4. 分类器调参避坑记录:那些让检测翻车的 5 个常见问题

4.1 背光亮度不均导致分割后胶囊“缺胳膊少腿”

现象:同一帧图像里,左侧胶囊分割完整,右侧胶囊只有一半被识别,好的胶囊被判成缺帽缺陷。

原因:背光板老化或胶囊托盘放置偏离中心,导致视场边缘亮度低于中心区域,固定阈值 0.15 在暗区把胶囊前景也过滤掉了。

解决:在预处理阶段加入顶帽变换校正(见 3.1),阈值设置前先画一条穿过图像的灰度剖面线,确认背景亮度波动范围。用improfile沿着图像中心画一条水平线,如果背景灰度波动超过 20 个灰度级,优先加强光照校正而不是调阈值。

4.2 胶囊表面的镜面反光被误判成破洞

现象:好品胶囊的圆弧表面出现一个或多个亮斑,二值化后亮斑形成孔洞,Solidity 下降后被判为缺陷。

原因:背光散射不充分,点光源在胶囊弧面形成镜面反射。这在透明明胶胶囊上尤其明显。

解决:在光源前加一层漫射片(硫酸纸即可)消除镜面反射。算法侧做闭运算填洞时,结构元素半径取 3 到 5。如果反光区域特别大,闭运算填不掉,需要回到成像端处理,用偏振片消除镜面光是更彻底的办法。

4.3 长宽比阈值像玄学:同一型号换了批次全翻车

现象:胶囊长宽比阈值设 2.5~5.0,第一批样本测试通过率 98%,第二批样本来了之后好品误判率飙到 15%。

原因:不同批次的胶囊尺寸有公差,而且相机安装角度、传送带振动导致胶囊在图像里的投影长度有偏差,长宽比整体偏移了。

解决:不要用固定阈值,用统计方法从样本中学习:每次切换批次前,先取 30 个好品样本计算长宽比均值和标准差,用均值加减 4 倍标准差作为动态阈值。更稳妥的做法是增加一个“胶囊主轴角度”特征,把倾斜投影导致的长短变化也纳入判断。

% 动态阈值更新示例:基于好品样本统计 goodRatios = [3.1, 3.2, 2.9, 3.3, 3.0, 3.2]; % 从好品中测得的长宽比列表 mu = mean(goodRatios); sd = std(goodRatios); lowerBound = mu - 4 * sd; upperBound = mu + 4 * sd;

逻辑说明:4 * sd而不是3 * sd,是因为胶囊长宽比分布通常有重尾,用 3 倍标准差会误杀边缘的好品。这是我从统计过程控制里借来的经验,比单纯拉宽阈值上限更科学。

4.4 训练集很小,SVM 在测试集上成绩虚高

现象:SVM 交叉验证损失 1.5%,看起来很好;一到产线新拍的视频上实测,误判率 12%。

原因:数据划分是随机打乱的,同一个胶囊的不同拍摄帧同时出现在训练集和测试集里,模型实际上“记住”了这些高度重复的样本。产线新数据是不同时间拍的,光照和角度都有变化,模型立刻露馅。

解决:做时间切分验证。按拍摄时间把前 70% 作为训练集、后 30% 作为测试集,模拟“训练完再去现场”的真实场景。缺陷样本不足时,不要强行用 SVM,退回规则分类反而更稳。

% 按时间顺序切分,模拟上线场景 sortedIdx = sortrows([1:height(featTable); featTable.Timestamp]', 2); trainIdx = sortedIdx(1:round(0.7 * end), 1); testIdx = sortedIdx(round(0.7 * end) + 1:end, 1); trainTable = featTable(trainIdx, :); testTable = featTable(testIdx, :); Mdl = fitcecoc(trainTable, 'Label'); predLabel = predict(Mdl, testTable); acc = sum(predLabel == testTable.Label) / height(testTable);

逻辑说明:Timestamp列是拍摄时间,sortrows按时间排序后切分。如果时间信息没记录,至少要保证同一个胶囊的多帧图像不能同时出现在两个集合里,按照胶囊 ID 分组再划分。这个操作能挤掉大部分虚高水分。

4.5 背景纤维和灰尘被当成了缺陷目标

现象:二值化后图像里出现大量细长的小连通域,有些被判为“破损胶囊”,数量多到没法用。

原因:车间环境里空气中有纤维和粉尘,落在背光板上或胶囊表面,在背光下会形成高对比度的小目标。它们面积小,但形状细长,长宽比可能大于 5,正好误入缺陷判定区间。

解决:加两层过滤。第一层在连通域筛选中按面积设置下限,小于 500 像素直接丢弃;第二层在特征分类时增加圆度特征,纤维的圆度极低,胶囊即使是破损的圆度也不会低于 0.3。更根本的做法是在背光板前加一块防尘玻璃,定期清洁,减少环境干扰。

5. 进阶:把“能跑”变成“好用”——批处理、验证与上线小技巧

我自己的教训是:算法在单张图上跑通只是第一步,真正让方案有价值的是把它变成能自动处理成百上千张图、能输出检测报告、能随时调整参数的工具。这段讲三个实用技巧。

把检测逻辑包装成一个独立函数。输入一张图,输出标签和缺陷位置,内部流程全部封装。这样调试、批量处理、实时采集共用一套代码,几周后你自己回头看也不会晕。

function [label, bbox] = capsuleDetect(img, params) gray = rgb2gray(img); denoised = medfilt2(gray, [3 3]); background = imopen(denoised, strel('disk', params.seRadius)); flat = denoised - background; bw = imbinarize(flat, params.threshold); bw = imclose(bw, strel('disk', 3)); % ... 区域分析、特征提取、分类判断 ... label = 'good'; bbox = []; end

批量验证时保留调试图。每处理一张图,把分割结果标记出来保存成debug/文件夹,上线后出问题可以回看这一步在哪开始丢信息。这是最便宜的后悔药。

用writetable导出检测报告。产线需要的是“这批胶囊总数、缺陷数、缺陷类型分布”,而不是一张画了红框的图。把每颗胶囊的结果汇总成表格导出 CSV 或 Excel,比在 MATLAB 界面里截图有用得多。

参数都验证好之后,最后一步才是接实时相机。切实时的时候用preview确认视野范围、然后固定相机光圈,把参数写进一个配置文件,不要写死在代码里。希望这些经验能帮到你,少走几步我踩过的弯路。

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

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进