前阵子我接到一个和电子板卡一致性检测相关的需求:工厂里同一批次的PCB板卡在贴片回流焊之后,需要抽检有没有元件漏贴、贴偏、焊点桥连这类问题。原来的做法是人工目检配上放大镜,效率低,而且每个人判定的尺度不一样,检完一批还要复核一遍。项目要求用工业视觉的方式做一套自动检测方案,并且先在Matlab里把算法链路跑通,这样既方便调参,又能快速验证检测思路。
这个项目做完之后,我最深的感受是:一致性检测这个东西,算法本身并不复杂,真正决定成败的反而是前期的检测项定义、光学环境稳定性和配准精度。这篇文章就把整个方案的思路、参数推算、Matlab实现主链路,以及我实测中踩过的坑完整梳理一遍。想用工业视觉做PCB外观检测的同行,或者正在用Matlab写图像处理实验的同学,可以拿去当一条完整的参考路径。
1. 一致性检测先要定义清楚"检什么":检测项、模板与容差
1.1 一致性检测和缺陷检测是两码事
很多人一上来就想着"我要用深度学习识别所有缺陷",这是把需求拔高了。一致性检测的核心不是"识别"缺陷,而是比较:拿被测板卡和一块标准良品模板做逐区域对比,找出"不一样的地方"。换句话说,缺陷检测解决的是"这个东西坏没坏",一致性检测解决的是"这个东西和标准品像不像"。
这个区别决定了技术路线。一致性检测里,模板是已知的、固定的,检测目标也是已知的,所以传统图像处理手段完全够用,不需要标注大量缺陷样本。我在项目里第一阶段就没碰深度学习,全部用图像配准加差分加特征量测做下来,效果已经能覆盖大部分抽检场景。
1.2 电子板卡上值得做一致性检测的项目
PCB板卡的一致性检测并不是简单拍一张照片对比一下。常见检测项可以划分为以下几类。
| 检测项 | 风险等级 | 对应检测策略 |
|---|---|---|
| 元件缺失 | 高,功能失效 | 模板差分,比对该区域是否有元件轮廓 |
| 元件位置偏移 | 高,焊接不良 | 区域重心比对,计算贴装偏移量 |
| 元件极性反向 | 高,上电烧板 | 局部模板匹配或字符/色环比对 |
| 焊点桥连 | 高,短路 | 连通域分析,检查相邻焊盘是否粘连 |
| 焊锡过量/不足 | 中,可靠性隐患 | 区域灰度或面积统计 |
| 板面划痕/异物 | 中,外观不良 | 局部灰度突变检测 |
从这张表能看出,不同检测项需要不同的图像特征。有的要靠"比",比如元件缺失、多余物,直接差分就能看出来;有的要靠"量",比如偏移量、桥连长度,必须做几何特征提取。后面第4章我会详细讲这两类方法的配合。
1.3 一套检测方案成立的前提:模板的有效性
模板选不好,后面全白搭。我见过不少项目卡在"检测结果天天误报",最后查下来是模板本身有问题。
模板采集有几个基本要求:
- 必须是确认过的良品板卡,最好从当前批次里选,不要拿老批次或者不同版本的板子。
- 同一块板卡至少采集5到10张不同摆放位姿的图像,用来验证配准算法的稳定性。
- 板面必须清洁,不能有指纹、油污,否则这些污渍会被当成"标准特征"学进去。
另外注意,不要直接用PCB设计软件导出的CAD图当模板。CAD图是矢量图形,和相机拍出来的灰度纹理差异很大,直接差分会产生大量假阳性。我在项目里踩过这个坑,后来老老实实回到实拍良品图。
2. 图像采集的参数倒推:从检测精度到相机、镜头和光源
2.1 从最小检测目标推算像素分辨率
工业视觉里第一个问题永远是:你要看清多小的东西?像素分辨率是倒推出来的。
以检测元件偏移为例,实际生产中最常见的容忍范围是±0.1mm。检测算法要可靠识别0.1mm级别的偏移,至少需要让这个偏移量在图像中占4到5个像素,否则亚像素边缘对不准。按5个像素折算:
- 像素分辨率 = 0.1mm / 5pixel = 0.02mm/pixel
这个值是整个光学方案的设计基准。如果分辨率达不到,后面算法再精细也没用。
2.2 视野、分辨率与相机选型
视野(FOV)取决于要拍的板卡大小。假设一块典型的控制板是60mm × 60mm,那么横向需要的像素数就是:
- 横向像素数 = 60mm / 0.02mm/pixel = 3000像素
这说明200万像素相机(约1600×1200)已经不够了,至少要500万像素级别(2448×2048)才能同时覆盖视野和精度。如果板卡尺寸更大,比如200mm × 200mm,那就要考虑拼接或者提高相机分辨率。
我实际使用的是一台500万像素的工业面阵相机,配16mm定焦工业镜头,工作距离控制在200mm左右,光圈收到F8以增加景深。这里有个经验:宁可让图像稍微偏暗,也别把光圈开大,因为PCB板面有元件高度差,光圈太大会导致部分区域跑焦。
2.3 光源选型:不同缺陷吃不同角度
光源是工业视觉里被严重低估的部分。PCB表面是复杂的非朗伯面,元件、焊点、丝印的反射特性各不相同,选择什么角度、什么颜色的光,直接影响缺陷的对比度。
我在这个项目里的光源配置如下:
- 环形光源(0°到30°低角度):主要用于焊点检测。低角度光能把焊点的三维轮廓照出来,球形焊点会形成明显的亮环,桥连区域则不会有这个特征。
- 同轴光(垂直照射):用于检测板面划痕、丝印和字符。同轴光对镜面反射敏感,划痕在图像里会表现为暗条纹。
- 条形光(大角度掠射):用于检测元件是否翘起、浮高。掠射光会在元件边缘投下阴影,从阴影方向可以判断高度差异。
如果你的Matlab代码里做的是灰度差分,但拍出来的图光源不均匀,那预处理阶段就要花大量精力去补偿。光源这一项投入产出比极高,值得先花时间调好再写算法。
2.4 光学平台的几个实际细节
- 板卡定位:用一个带定位销的治具固定板卡,每次放下去的位置误差控制在±1mm以内。这样可以大幅减轻后期配准压力。
- 环境光隔离:给整个检测工位加遮光罩,或者把设备放在暗室环境里,排除日光灯频闪的影响。
- 相机安装:保证相机光轴垂直于板面。我测过,哪怕倾斜2度,板卡边缘的元件位置就会产生1到2个像素的偏移,误检率立刻上升。
3. 预处理与配准:检测精度的真正分水岭
3.1 预处理不只是"去噪"
预处理的目的不是让图好看,而是让后续的差分和特征提取对光照、噪声不敏感。我用的预处理链路是:
- 灰度化:
rgb2gray,如果后续要检测丝印颜色差异,则保留RGB三个通道分别分析。 - 高斯滤波:
imgaussfilt(I, 1.5),轻度去噪,注意核不要太大,否则会把细小缺陷抹掉。 - 顶帽变换:
imtophat(I, strel('disk', 50)),这一步很关键。PCB板面不同区域的反射亮度差异很大,顶帽变换能去掉大面积背景亮度不均匀,保留局部细小特征。
顶帽变换的原理可以理解为:用一个半径较大的圆盘形结构元素做开运算,得到一个"背景亮度估计",然后用原图减去这个背景。效果是暗区域被提亮,亮区域的背景被削平,让整个板面亮度趋于均匀。对差分检测来说,这能直接减少因为板卡批次间底色差异带来的误报。
3.2 配准的精度决定差分的可信度
这是整个项目里我体会最深的一件事:不做配准就直接差分,等于自杀。
如果不做几何配准,被测板卡和模板板卡哪怕只偏移了3个像素,差分结果在元件边缘、焊盘边缘就会出现一圈亮边,这些亮边会被误判为缺陷。实际治具定位精度能做到±1mm,换算成像素就是50个像素左右,根本没法直接比。
配准的目标是让被测图像和模板图像在像素级对齐,误差控制在亚像素到1个像素之间。达到这个精度,差分的可信度才有保障。
3.3 粗配准加精配准的落地方法
我的做法是两阶段配准。
第一阶段:粗配准。把两幅图分别二值化,得到板卡的整体轮廓,然后计算轮廓的质心和主轴方向。通过平移和旋转把被测图大致摆到模板的位置上。这一步能消除治具摆放带来的大位移和大角度偏差。
% 粗配准:计算连通域质心和方向 bw_t = imbinarize(imgaussfilt(template_gray, 2)); bw_s = imbinarize(imgaussfilt(sample_gray, 2)); stats_t = regionprops(bw_t, 'Centroid', 'Orientation'); stats_s = regionprops(bw_s, 'Centroid', 'Orientation'); % 位移量 shiftX = stats_t(1).Centroid(1) - stats_s(1).Centroid(1); shiftY = stats_t(1).Centroid(2) - stats_s(1).Centroid(2); angleDiff = stats_t(1).Orientation - stats_s(1).Orientation; % 构建相似变换矩阵 tform = affine2d([cosd(angleDiff) -sind(angleDiff) 0; sind(angleDiff) cosd(angleDiff) 0; shiftX shiftY 1]); sample_aligned = imwarp(sample_gray, tform, 'OutputView', imref2d(size(template_gray)));第二阶段:精配准。粗配准之后,两幅图还有1到2像素的残差。此时用归一化互相关(normxcorr2)做局部偏移修正,或者用SURF特征点匹配后求单应矩阵。我推荐先试normxcorr2,它不依赖特征点数量,对纹理较弱的板卡更稳定。
% 精配准:在画面中心区域取一块模板子图,做归一化互相关 center_patch = template_gray(200:400, 300:500); c = normxcorr2(center_patch, sample_aligned); [maxVal, imax] = max(abs(c(:))); [ypeak, xpeak] = ind2sub(size(c), imax(1)); % 计算偏移并重新平移 corr_offset = [(xpeak-size(center_patch,2)) (ypeak-size(center_patch,1))]; tform2 = affine2d([1 0 0; 0 1 0; corr_offset(1) corr_offset(2) 1]); sample_final = imwarp(sample_aligned, tform2, 'OutputView', imref2d(size(template_gray)));3.4 配准误差的量化与验收
配准不是"看起来差不多"就行。我做的验收方式是:在模板和被检图上人工标定10组对应点(比如元件本体上的特征角点、丝印字符的端点),配准后计算这些点的坐标残差。要求平均残差小于0.5像素,最大残差不超过1像素,才认为配准合格。
这里建议把配准后的叠加图像(模板半透明叠加到检测图上)可视化保存下来,排查问题时非常有帮助。我发现很多误检最终都能追溯到配准残差偏大,而不是算法本身有bug。
4. 一致性判定:差分量"有没有变",特征量"变了多少"
4.1 图像差分法的适用边界
图像差分是一致性检测最直观的手段:diff_img = imabsdiff(template_final, sample_final),然后做二值化,把差异区域提取出来。
差分法最适合以下场景:
- 元件缺失:该有元件的地方空白了,差分区域面积大且形状规整。
- 多余物/异物:模板中没有的物体,差分区域明显。
- 大面积色差:比如板面氧化变色,整体灰度差异显著。
但差分法的短板也很明显:对轻微偏移不敏感。一个电阻偏移0.3mm,在差分图上会出现"一侧亮边、另一侧暗边"的成对区域,如果用单纯的面积阈值判定,很容易误判成缺陷或者漏判成正常。所以差分法只能作为第一层筛查,输出候选区域,不能直接下结论。
4.2 基于regionprops的特征量测
差分筛选出的候选区域,需要进一步用特征量测来判定是否真正超差。
我在Matlab里用的是bwconncomp加regionprops组合,对每个候选区域提取以下几类特征:
- 面积(Area):区分微小噪声和真实缺陷的关键。
- 外接矩形和质心(BoundingBox, Centroid):用于计算元件相对模板位置的偏移量。
- 长宽比和偏心度(MajorAxisLength/MinorAxisLength, Eccentricity):用于区分"细长桥连"和"圆形异物"。
- 平均灰度差:候选区域的灰度差值大小,辅助判断缺陷严重程度。
以焊桥检测为例,两个相邻焊盘中间出现桥连,在差分图上表现为一条细长的亮带,连通域特征通常是"面积中等、长宽比很大、偏心度接近1",和球形锡珠(面积小、接近圆形)完全不同。用几何特征就能把两者区分开。
cc = bwconncomp(bw_regions); stats = regionprops(cc, 'Area', 'BoundingBox', 'Centroid', 'Eccentricity'); for k = 1:length(stats) if stats(k).Area < 30 continue; % 小于30像素的连通域视为噪声 end % 桥连特征:偏心度大且长宽比大,同时落在焊盘ROI内 bbox = stats(k).BoundingBox; aspectRatio = bbox(4) / max(bbox(3), 1); if stats(k).Eccentricity > 0.85 && aspectRatio > 2.5 % 标记为焊桥缺陷 end end4.3 判定规则的组合设计
一致性检测的判定逻辑不要设计成"一刀切",建议分成三级:
- 硬缺陷:差分面积大、几何特征明确,直接判NG,不经过人工复核。
- 疑似缺陷:面积处于阈值边缘、灰度差略超限,标记出来进入人工复核队列。
- 正常:差异小于噪声下限,直接通过。
这个设计能让产线在"漏检"和"误检"之间找到可调的平衡。实际运行中,我会把疑似缺陷的比例作为系统健康度的指标:如果一批板卡疑似缺陷率突然升高,优先排查是不是光源衰减、板卡批次变化或者治具松动,而不是急着调算法阈值。
5. Matlab实现的主链路与核心代码拆解
5.1 主流程框架
整个Matlab工程的结构不复杂,我按模块拆分成了四个脚本:
main_process.m:主入口,负责读取模板和待检图,调用各处理函数,输出检测报告。preprocess.m:预处理,输出顶帽变换后的灰度图。align_image.m:粗配准加精配准,输出配准后的待检图。detect_diff.m:差分、连通域分析和特征判定,输出缺陷标注图和数据表。
主入口的逻辑很直接:
template_gray = preprocess(imread('template.tif')); fileList = dir('samples/*.tif'); for i = 1:length(fileList) sample_gray = preprocess(imread(fullfile(fileList(i).folder, fileList(i).name))); sample_final = align_image(template_gray, sample_gray); [defectMask, stats, result] = detect_diff(template_gray, sample_final); if result.hasDefect imwrite(defectMask, fullfile('results', ['defect_' fileList(i).name])); end results(i) = result; end实际工程我只写了这四个模块,但每个模块内部的参数(顶帽半径、配准子图大小、连通域面积阈值)全部通过一个配置文件管理,方便调参而不需要改代码。
5.2 配准模块的工程化处理
配准模块里容易出错的是imwarp的OutputView设置。如果不指定输出视图,imwarp默认输出的尺寸会跟着变换矩阵走,导致和模板尺寸不一致,差分时矩阵维度对不上。这是新手最容易踩的坑。
我在align_image.m里的处理方式是:
tform_final = affinetform2d(...); % 组合粗配准和精配准的变换矩阵 sample_final = imwarp(sample_gray, tform_final, 'OutputView', imref2d(size(template_gray)));同时我会在配准完成后做一次尺寸和数据类型校验:
assert(isequal(size(sample_final), size(template_gray)), '配准输出尺寸不一致');还有一个细节:工业相机拍出来的图像如果是高动态范围RAW格式,读入之后要统一转成double类型再参与计算,否则差分计算时uint8的溢出问题会让结果变得很诡异。
5.3 差分与连通域分析的参数设置
差分阈值我用的是相对值而不是绝对值。因为被测图经过顶帽变换后灰度分布已经比较集中,但不同板卡间仍有微小差异,固定绝对阈值容易一边倒。
diff_img = imabsdiff(template_gray, sample_final); thresh = graythresh(diff_img); % Otsu全局阈值,先自动粗选 bw_diff = imbinarize(diff_img, max(thresh, 0.12)); % 下限保护这里设置下限0.12(归一化灰度)是为了防止Otsu在差异很小时把噪声也选成前景。二值化之后,用形态学开运算去掉孤立噪点:
bw_clean = imopen(bw_diff, strel('disk', 2));结构元素半径2的圆盘对3像素以下的孤立噪点有效,同时基本不伤真正的缺陷边缘。
5.4 批处理与检测报告输出
产线使用场景下,检测结果需要留档。我用writetable把每次检测的判定结果、缺陷数量、面积特征写进Excel,同时把缺陷标注图保存到指定目录。标注方法是在原图上用rectangle画框:
figure('Visible', 'off'); imshow(sample_final); hold on; for k = 1:length(stats) rectangle('Position', stats(k).BoundingBox, 'EdgeColor', 'r', 'LineWidth', 2); end saveas(gcf, fullfile('results', fileList(i).name)); close;需要注意Matlab在批处理时弹出figure窗口会拖慢速度,务必用'Visible', 'off'或者set(gcf, 'Visible', 'off')。批量跑几百张图时,这个细节能省下不少时间。
6. 实测结果、误检案例与后续优化
6.1 测试集和结果指标
我用50块已知状态的板卡做了验证,其中40块良品、10块人工挑出的不良品(包含元件缺失3块、偏移3块、桥连2块、丝印不良2块)。测试结果如下:
| 指标 | 数值 | 说明 |
|---|---|---|
| 缺陷检出率 | 100% | 10块不良品全部被标记 |
| 良品误检率 | 7.5% | 40块良品中有3块被标记为疑似缺陷 |
| 平均处理时间 | 约0.8秒/块 | 包含配准和差分计算,不含图像读取 |
3块良品被误检的原因查下来都是光照问题:两块是板卡表面有轻微水纹状污渍被顶帽变换放大,一块是治具定位柱踩到了元件的边缘导致配准参考点轻微偏移。这再次验证了前面说的——光学环境和治具稳定性对检测的影响远大于分类算法本身。
6.2 三个典型的误检案例
第一个案例是板卡反光。某个电解电容的金属外壳反光造成局部过曝,差分区域面积大于阈值被误判。解决办法是在预处理阶段对外壳高亮区域做灰度截断,把超过240的像素压到模板对应位置的值,让高光不再参与差分。
第二个案例是配准子图选得不好。我在精配准时选了画面中心的一片无特征区域做模板,结果normxcorr2的峰值非常平,偏移收敛很慢。后来改成选丝印密集的角落区域,相关峰变得尖锐,配准残差从2像素降到0.3像素。选配准参考区域时,优先选纹理特征丰富的区域,而不是画面中心的区域。
第三个案例是板卡轻微变形。长板卡两端受力不均导致中间区域有约1.5像素的局部形变,全局仿射变换纠正不了。如果遇到这种情况,可以考虑按区域分块配准,但会显著增加计算量。我的折中方案是画ROI时避开形变明显的板卡边缘区域,优先检测元件密集的中心区域。
6.3 可以继续深化的方向
如果后续要把这套方案推向更高通过率,我建议按顺序做三件事:
- 引入多光源图像融合。把环形光、同轴光、条形光分别采集的图像各自处理后在特征层融合,能明显降低反光类误检。
- 用深度学习分类器替代部分手工阈值判定。差分图提取的候选区域数量通常不大,用一个小型CNN对候选区域做二分类,可以把误检率再降一个量级。
- 对接产线MES系统。把检测结果实时写入数据库,并通过控制IO信号驱动分拣机构。Matlab做原型验证、生成报告没有问题,但真正上产线建议把核心算法迁移到C++或部署到工业视觉控制器上。
最后分享一段我自己的体会:这类视觉检测项目,总时间里有六成花在"让图像稳定"上,只有四成在写算法。Matlab的优势在于图像处理函数齐全、调参方便,能让你快速验证"这个缺陷到底用白光还是红光、用差分还是用连通域"这类问题。先把Matlab链路跑通,再考虑性能优化和迁移部署,是性价比最高的推进路径。如果你正在做类似的板卡检测研究,希望这篇文章能帮你少走几段弯路。