
简介基于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确认视野范围、然后固定相机光圈把参数写进一个配置文件不要写死在代码里。希望这些经验能帮到你少走几步我踩过的弯路。本文还有配套的精品资源点击获取