
聊芯片焊接锡点检测这件事我脑子里第一反应不是算法而是车间里那台老显微镜旁边堆着的报废板。锡点这东西在整块板子上占的面积可能连百分之一都不到但它决定了这块板子是进老化房还是进废料箱。虚焊、连锡、锡珠、偏位、气孔这五类问题随便漏一个出去后面可能就是整机返修甚至客诉。所以当年我第一次接触“机器视觉芯片焊接锡点缺陷检测”这个题目的时候心里的判断很直接这不是一个炫技的课题而是一个必须在产线上跑得住、误检率压得下去、换批次不用天天重调参数的工程活。这次拿到的这套东西是一份基于 Matlab 实现的芯片焊接锡点缺陷检测方案配套源码可以完整跑通从读图、定位、分割、特征提取到判定输出的全流程。它解决的问题很明确把原来靠人眼在放大镜或者显微镜下逐点扫的活交给相机加算法来做做到单帧百毫秒级出结果、判定标准统一、每块板子都有可追溯的记录。适合谁看我觉得有三类人一类是刚接手视觉检测项目、手里有 Matlab 但不知道从哪下手的工科同学一类是产线上想自己搭一套离线复检工位的工艺或设备工程师还有一类是拿这套源码当基线准备往深度学习方向迁移、想先搞清楚传统方法边界在哪的开发者。下面我按自己实际改这套代码的顺序把设计思路、参数怎么来的、坑在哪里一条一条摊开讲。1. 这套检测方案的整体设计与取舍1.1 锡点缺陷到底难在哪先把敌人摸清楚动手写代码之前得先弄明白要检的东西长什么样。芯片焊接的锡点常见形态是 QFP、SOP 这类封装引脚与焊盘之间的润湿角形成的弧形焊点或者功率器件下面的大面积焊盘。它的视觉难点集中在三件事上。第一件是尺度小、反差弱。一个合格焊点的宽度可能只有 0.2 到 0.4 毫米在整板图像里就是一小撮像素。而锡本身是高反光金属在环光下亮成一片白边缘过渡非常窄几个像素的模糊就能把圆度特征算歪。第二件是缺陷形态不规整。虚焊表现为润湿不良、爬锡高度不够视觉上是焊点面积变小、边缘发暗、形状变得扁平连锡是两个相邻焊盘之间多出一条锡桥锡珠是散落在阻焊层上的孤立小球气孔则是焊点内部出现的暗色空洞。这些形态有的靠面积判断有的靠形状判断有的必须靠灰度分布判断单一特征根本兜不住。第三件是背景干扰多。PCB 上有丝印、有阻焊油墨、有器件本体阴影、有助焊剂残留。助焊剂残留在很多批次里是半透明的淡黄色膜灰度上和微小锡珠非常接近这是最容易造成误检的一类干扰。我通常建议在写第一行代码之前先干一件事把过去三个月产线判为不良的板子收集二三十块按缺陷类型分类摆开用同一套光源拍一轮。这一步花不了两天但能让你后面所有的阈值都有依据而不是对着几张漂亮样本拍脑袋。1.2 为什么这套方案走传统视觉路线而不是深度学习现在一提缺陷检测很多人的第一反应是上模型。但这套方案走的是传统机器视觉路线固定光源、固定工位、几何特征加阈值判定。这个取舍我认为在锡点检测这个场景里是站得住脚的理由有三条。样本量不够。一条中等规模的产线某类缺陷一个月可能就出十几个。你要训一个稳定的分割网络少说也得上千张标注样本而且负样本容易凑、正样本极难凑。传统方法只需要几十个样本就能把阈值区间标出来。可解释性要求高。产线上判定一个板子不良工艺工程师一定会追问“凭什么”。传统方法的答案是清晰的面积占比 48%低于 60% 的下限所以判少锡。这种回答能被接受、能被复核、能被写进工艺文件。黑盒模型的输出在产线沟通成本太高。节拍和部署成本。这套 Matlab 实现在普通工控机上单帧 80 到 150 毫秒不需要独立显卡不用维护推理环境离线复检工位甚至一台笔记本就能跑。对于检测点位不多、缺陷类型相对固定的场景这个性价比很难被替代。当然传统方法的天花板也很明显它对光照变化的容忍度低换一个批次的板子阻焊颜色变了阈值可能就得重调。所以我在实际项目里的做法是传统方法做在线初筛模型做离线复核两头结合。这个话题后面第 4 章会展开。1.3 硬件链路与像素当量的换算算法再好成像不行全是白搭。这套方案对硬件的最低要求是工业面阵相机加远心镜头加可控光源加固定工装。下面这个换算过程是我每次做方案必算的也是很多人容易跳过的一步。假设检测目标是最小 0.3 毫米宽的焊点工艺要求能分辨出 0.02 毫米级别的边缘变化。按照数字图像处理的经验一个特征要被稳定测量至少需要 3 到 5 个像素的采样。取保守值 3 像素则像素当量需要满足0.02 mm ÷ 3 px ≈ 0.0067 mm/px即像素当量不大于 6.7 微米再算视场。选一台 2448×2048 分辨率的 500 万像素相机像元尺寸 5 微米配 1 倍远心镜头像素当量 5 μm ÷ 1 5 μm/px满足要求视场 2448 × 0.005 mm 12.24 mm纵向 2048 × 0.005 10.24 mm12×10 毫米的视场刚好覆盖一颗 QFP 或者两三个连接器区域。这时候一个 0.3 毫米的焊点约等于 60 个像素宽做圆度、面积、偏心这些几何特征完全够用。如果换 0.5 倍镜头视场放大到 24×20 毫米能一次拍一片但像素当量变成 10 微米边缘精度就吃紧了。光源这块我的经验是锡是镜面反射体打光方案比算法重要十倍。常用的两种组合一种是低角度环形光入射角 15 到 30 度靠焊点的三维弧面产生明暗过渡虚焊的扁平焊点在低角度光下会明显发暗另一种是同轴光靠平整表面的回光强弱判断焊点顶部平整的地方亮、凹坑和润湿不良的地方暗。实际项目里我更倾向低角度环形光配红色波段因为红色光在绿色阻焊层上反射率低背景会被压暗锡点的对比度反而拉得开。这个细节第 2 章细讲。2. 成像与预处理决定成败的百分之八十2.1 打光方案光没打对后面全是补丁我在改这套源码的过程中最大的感受是很多人把时间全花在调阈值和形态学参数上却不愿意花半天时间重新摆一次光源。结果就是参数越调越多逻辑越写越乱最后还是压不住误检。锡点检测的光源选择本质上是决定你要用“亮度特征”还是“形貌特征”来做判据。低角度环形光的逻辑是形貌光从侧面近乎贴着板面打过来焊点有弧度的地方才会把光反射进镜头扁平、塌陷、润湿不良的焊点反射角度不对就呈现暗区。这种方案对虚焊和少锡特别敏感因为这两类缺陷的共同视觉表现就是“塌下去了”。同轴光的逻辑是平整度光线垂直入射垂直返回只有正对镜头的平面才会亮。焊点顶部如果平整光滑就亮如果有凹坑、有气孔、有氧化就暗。这种方案对气孔和氧化类缺陷更敏感但对少锡的判断力不如低角度光。我的实际配置是低角度红光环形光为主加一路可选的同轴光做二次确认。环形光负责分割出焊点轮廓同轴光负责在轮廓内部找暗斑判断气孔。两路光分时曝光成本增加不多但缺陷覆盖面明显宽了。另外一定要加带通滤光片只让光源波长的光进镜头车间顶灯、设备指示灯这些杂散光全部挡掉这一招对长时间运行的稳定性帮助极大。还有一个容易被忽略的点是相机与板面必须严格垂直。远心镜头有一定的景深容差但倾斜超过两三度焊点一侧的反射光就进不了镜头整排焊点会出现明暗渐变阈值就完全失效了。工装设计时留个微调机构用一块标准平板做基准调平这一步花二十分钟能省后面几十个小时。2.2 从彩色到灰度通道选择与对比度增强彩色图进来第一件事是转灰度但直接rgb2gray往往不是最优解。前面说了用红光照明那么红通道的信噪比是最高的——绿板在红光下反射弱红通道里背景暗、锡点亮对比度天然就拉开了。蓝通道则相反背景和锡点灰度接近分割难度大。在 Matlab 里我会这样处理先把 RGB 三通道拆开分别计算每个通道的类间方差或者简单看一下锡点区域与背景区域的灰度差选差值最大的那个通道。代码大概是这样img imread(filePath); R img(:,:,1); G img(:,:,2); B img(:,:,3); % 用已知的焊盘区域掩膜和背景区域掩膜比较各通道的对比度 contrastR mean(R(maskPad)) - mean(R(maskBg)); contrastG mean(G(maskPad)) - mean(G(maskBg)); contrastB mean(B(maskPad)) - mean(B(maskBg)); [~, bestCh] max([contrastR, contrastG, contrastB]); grayImg double(img(:,:,bestCh));灰度出来之后还要做一步局部对比度归一化。这是压住批次差异的关键。整板不同位置的照度不完全均匀边缘会偏暗如果用全局阈值中间区域的焊点判合格、边缘区域的焊点被判少锡误检一大片。我的做法是在 ROI 内部减去局部背景用一个大尺寸的结构元做灰度闭运算得到背景估计然后用原图减去背景再做拉伸。这本质上是一种顶帽变换的思路能把不均匀照明的影响抹掉大半。se strel(disk, 25); % 尺寸要大于单个焊点小于照明变化尺度 bgEst imclose(grayImg, se); grayFlat grayImg - bgEst; % 背景压平 grayNorm mat2gray(grayFlat); % 归一化到 0-1结构元半径 25 像素是我在 5 微米当量下试出来的经验值对应实际尺度约 0.125 毫米比单个焊点大但远小于整板的照度变化周期。这个参数不能乱设设小了会把焊点本身当成背景减掉设大了压不平照度。2.3 分割与形态学参数不是拍脑袋定的分割这一步很多人直接一个graythresh就完事然后在样本上看着还行一上产线就崩。原因是 Otsu 是全局最优它会把整幅图分成两类而焊点只占图像的一小部分背景的丝印、阴影会严重干扰阈值的位置。我的做法是两级阈值先用 Otsu 得到一个粗阈值然后把它限制在一个经验窗口内。比如某批板子在当前光线下 Otsu 给出 0.42而我知道合格焊点的灰度中位数在 0.6 以上那么就把阈值钳制在 0.45 到 0.55 之间避免某张图里出现大片暗色丝印时 Otsu 把阈值拉低到 0.3导致整片焊点被误判。level graythresh(grayNorm); level min(max(level, 0.45), 0.55); % 钳制窗口按实际样本标定 bw imbinarize(grayNorm, level);二值化之后是形态学。这里有个反直觉的点形态学的目的不是“让图好看”而是“让特征算得准”。开运算去掉孤立噪点闭运算填补焊点内部的孔洞但结构元尺寸必须严格按像素当量推算不能随手写个 5。举个具体例子。工艺上把直径小于 0.05 毫米的孤立锡点定义为可接受不算锡珠缺陷那么换算成像素0.05 mm ÷ 0.005 mm/px 10 像素直径面积约 78 平方像素。如果开运算的结构元半径取到 6 像素以上直径 12 像素以下的小锡珠就会被整个吃掉真缺陷就漏了。所以开运算半径我一般取 2 到 3 像素只用来清掉单个像素的椒盐噪声。反过来闭运算要填补焊点内部的暗斑但如果焊点本身有真实气孔需要检出闭运算半径太大就会把气孔也填平。我的处理是先做闭运算提取焊点外轮廓再在同一位置的原始灰度图上单独检孔洞两套掩膜分开算互不干扰。这个思路在源码里体现得比较清楚也是我认为这套实现比一般教学代码更接近实战的地方。3. Matlab 实操从批量读图到判定输出3.1 定位Mark 点与模板匹配双保险真实产线上板子每次停的位置不可能完全一致工装重复定位精度再好也有几十微米偏差换算成像素就是十几个像素。如果 ROI 写死坐标偏移几个像素就可能把焊盘边缘切掉特征全歪。这套源码给了两种定位手段我在实际使用中是两个都用、互为备份。第一种是Mark 点定位。板子上一般有两个以上圆形或十字形的基准点用形态学加圆度筛选找出来计算它们的质心再和标准位置做仿射变换把整幅图校正到标准姿态。这种方法的精度最高能到亚像素级缺点是对 Mark 点的印刷质量有依赖。第二种是模板匹配兜底。从标准样本里裁一块包含三到五个典型焊点的区域做模板用归一化互相关在原图里滑窗搜索找到峰值位置反推偏移量。tpl imread(tpl_region.png); tplGray double(rgb2gray(tpl)); imgGray double(rgb2gray(img)); cc normxcorr2(tplGray, imgGray); [~, idx] max(cc(:)); [row, col] ind2sub(size(cc), idx); % 换算成模板左上角在原图中的坐标 offsetY row - size(tplGray,1) 1; offsetX col - size(tplGray,2) 1;这里有个性能细节必须提一句。normxcorr2是全图滑窗2448×2048 的图上跑一次模板匹配耗时可能到几百毫秒直接吃掉整个节拍预算。我的优化方式是先降采样粗定位再在原分辨率小窗口内精定位把图和模板都缩小四倍跑一次得到粗略位置然后在这个位置周围 ±40 像素的窗口里用原分辨率再跑一次。实测下来总耗时从 400 毫秒量级降到 60 毫秒量级精度不受影响。另一条路是直接裁剪 ROI 只处理感兴趣区域整板只关心焊点附近全图信息大部分是浪费的。两种定位方式冲突的时候怎么处理我的规则是以 Mark 点为准模板匹配置信度低于阈值时告警。如果 Mark 点找不到退回到模板匹配两个都对不上这块板子直接标记为定位失败转人工不允许带着错位往下判。产线上宁可漏检也不能错判这个原则要写死在逻辑里。3.2 分割、特征提取与判定规则定位完成之后把每个焊盘的位置从标准模板里查出来逐个裁 ROI做局部分割和特征提取。这一步是整套方案的核心我把判据整理成了一张表参数区间是我在多批样本上标定出来的参考值实际用要按自己的产品调整。缺陷类型主要判据辅助判据参考阈值区间说明少锡 / 虚焊锡点面积占比区域灰度均值面积占比 60%均值 0.55占比指锡点像素数除以焊盘理论面积多锡 / 锡包锡点面积占比外接框膨胀量面积占比 135%需排除相邻焊盘连通的干扰连锡相邻焊盘连通域合并焊盘间最小间隙间隙 3 px 且连通间隙按像素当量换算实际距离锡珠孤立小连通域面积与主体距离面积 20~300 px²距离 15 px需与助焊剂残留做颜色区分偏位质心与焊盘中心偏移偏移方向偏移 焊盘短边 15%有方向性要区分 X/Y气孔 / 空洞内部孔洞面积占比孔洞数量占比 8% 或单孔 40 px²必须在原始灰度图上检不能用闭运算后图代码上我用一个参数结构体把所有阈值集中管理方便现场调参也方便做版本记录。这个习惯强烈建议保留把阈值散落在代码各处的项目半年后自己都不敢改。P.minAreaRatio 0.60; % 少锡下限 P.maxAreaRatio 1.35; % 多锡上限 P.minCircularity 0.55; % 圆度下限圆形为 1 P.maxOffsetRatio 0.15; % 质心偏移比例上限 P.poreRatio 0.08; % 孔洞面积占比上限 P.minBlobArea 20; % 锡珠最小面积像素 P.maxBlobArea 300; % 锡珠最大面积像素 stats regionprops(bw, grayNorm, Area, Centroid, BoundingBox, ... Perimeter, Eccentricity, MeanIntensity); for k 1:numel(stats) a stats(k).Area; p stats(k).Perimeter; circ 4 * pi * a / (p^2); % 圆度 ratio a / padAreaPix; % 面积占比 ctr stats(k).Centroid; offX abs(ctr(1) - padCtr(1)) / padW; offY abs(ctr(2) - padCtr(2)) / padH; ... end这里要特别提醒一个计算陷阱圆度的分母是周长的平方而周长在二值图上是用边界像素链码估算的对噪声极其敏感。一个边缘稍微毛糙的焊点周长可能被高估 30%圆度从 0.9 掉到 0.5直接触发误判。解决办法是对二值图先做一次轻微的高斯平滑再二值化或者用bwperim加形态学平滑来稳定边界。我在实机上踩过这个坑一批板子的圆度全在 0.4 到 0.6 之间剧烈跳动查了半天才发现是曝光时间短了两个档边缘噪点变多。判定输出的设计上我不建议只给一个“OK/NG”的结论。每个焊点要输出结构化的判定原因比如{id: 17, result: NG, type: 少锡, areaRatio: 0.48, threshold: 0.60}。这条记录在后续追责和工艺改进时价值巨大能直接看出是哪个位置的焊点、偏了多少、趋势如何。3.3 结果可视化、报表与产线对接检测结果的可视化看着是锦上添花实际在调试阶段是效率倍增器。我的做法是在原图上叠加三样东西合格焊点画绿色框不良焊点画红色框并在旁边标注缺陷类型和关键数值同时在图像角落打上时间戳、工位号、判定汇总。Matlab 里用insertShape和insertText就行注意字号和线宽要按图像分辨率调1080p 的图上用字号 12、线宽 2 比较合适太小了看不清。如果装的是基础版没有 Computer Vision Toolbox直接手动画矩形也不复杂figure(Visible,off); imshow(img); hold on; for k 1:numel(results) bb results(k).BBox; if results(k).isNG rectangle(Position, bb, EdgeColor,r, LineWidth, 2); text(bb(1), bb(2)-8, results(k).type, Color,r, FontSize, 12); else rectangle(Position, bb, EdgeColor,g, LineWidth, 1); end end saveas(gcf, fullfile(outDir, sprintf(%s_result.png, baseName))); close(gcf);生成figure是有代价的一张图的开销可能上百毫秒批量跑的时候会严重拖慢速度。调试阶段用可视化正式跑关掉这个开关一定要留。报表这块我用writetable输出 CSV字段包括板号、时间、焊点总数、合格数、各类缺陷数、缺陷明细。这份 CSV 后面接一个简单的趋势统计就能看出某类缺陷是不是在某个时间段集中出现——很多时候问题出在钢网或者回流焊的温区而不是设备本身有了数据趋势才好和工艺沟通。和产线对接的部分这套 Matlab 代码本身不直接控制设备输出的是判定结果和图片存档。常见的对接方式是结果 CSV 落盘一个轻量脚本监控目录变化把 NG 板号推给 MES 或者点亮工位上的三色灯。这个衔接层用什么写都行关键是文件命名要包含工位号和精确到毫秒的时间戳避免并发写入冲突。4. 现场问题排查实录与避坑清单4.1 误检漏检的六类典型成因调参调到崩溃的时候最怕的是不知道问题出在哪一层。我把实际遇到过的典型问题整理成了下面这张表从上往下查基本能覆盖八成的情况。现象可能成因排查动作解决方向整批焊点被判定面积偏小曝光时间缩短或光源衰减查看灰度直方图峰值位置是否整体左移恢复曝光或更换光源重新标定阈值边缘焊点误检、中间正常照明不均匀边缘照度低拍一块白板看灰度分布加顶帽变换压平背景或改环形光为穹顶光锡珠误检率高助焊剂残留被当成锡珠抽取误检样本放大观察形态和颜色增加颜色判据用 HSV 中的饱和度区分圆度指标剧烈跳动边缘噪点导致周长计算不稳打印同一焊点多帧的周长数值加高斯平滑、稳定边界提取某些批次突然大面积误判阻焊颜色或板厚批次变化对比新旧批次的灰度均值阈值窗口化、金样件每班次复检定位失败率高Mark 点被阻焊覆盖或印刷偏移单独输出 Mark 点检测结果图改模板匹配兜底或增加 Mark 点数量我印象最深的一次是锡珠误检误检率一度到了 15%抽出来一看全部集中在板子边缘的几个固定位置。放大后发现那些位置的助焊剂残留由于回流温度偏低形成了略带黄色的小凸起在红光下亮度跟锡珠几乎一样。后来加了一个 HSV 饱和度判据——锡的饱和度低助焊剂残留的饱和度明显偏高——误检率直接降到 1% 以下。这件事的教训是灰度特征解决不了的问题往往换一个颜色空间就解决了不要在一个维度上死磕。还有一个反直觉的问题漏检往往不是算法不够灵敏而是分割阈值太灵敏。为了让弱对比的虚焊也能被检出有人把阈值调得很低结果是焊点周围的暗色过渡区域也被算进了锡点面积反而虚高虚焊被判成了合格。正确的做法是保持阈值稳定改用局部对比度增强来提升弱对比区域的可见度让同一个阈值在整幅图上表现一致。4.2 调参与验证的先后顺序调参这件事我见过最多的错误是顺序反了先调判定阈值发现误检压不住再去调形态学参数还是不行最后去动光源——结果光源一改前面所有参数全部作废。正确的顺序是严格自下而上的先固化光学。光源角度、亮度、曝光时间、增益全部锁定用标准样件拍一组图确认灰度分布稳定。再固化定位。用二十块板子跑定位统计偏移量的均值和标准差确认重复性在 ±2 像素以内。然后固化分割。只看分割后的二值图好不好看不看判定结果确保合格焊点的轮廓完整、无毛刺、无断裂。最后调判定阈值。用收集好的合格样本和各类缺陷样本统计每个特征的分布取两类分布的中间位置作为阈值。这个顺序的核心逻辑是上层参数的有效性依赖于下层的稳定性。光学不稳阈值就没有意义分割不准特征就是噪声。我在实际项目里会把这个顺序写成一份检查清单每次换产品、换批次都从头走一遍虽然麻烦但比事后救火省事得多。阈值取法上还有一个细节值得说。面积占比这个指标合格样本的分布本身就比较宽比如 75% 到 120% 都算正常而少锡样本可能在 40% 到 65%。如果简单取中点 70% 作为阈值边界上的样本就会反复横跳同一块板子今天判合格明天判不良。我的做法是在两类分布的间隙里留一段死区落在死区里的样本标记为“待复核”而不是强行二分类。这个设计在产线上非常实用因为人眼复核一小部分边界样本的成本远低于误判一整批的代价。4.3 上线前必须做的三件事第一件是金样件管理。选一块各项特征都处于中位数的合格板再选几块各类缺陷的典型板作为标准件封存。每班次开机第一件事是用金样件跑一遍合格板必须全过缺陷板必须全检出。这一步能挡住九成的“今天怎么全在误报”的突发状况因为它把问题定位在设备状态而非算法本身。第二件是重复性与一致性验证。同一块板子连续测 30 次看判定结果是否完全一致关键特征值的标准差是多少。这个数据决定了你的阈值死区该留多宽。位置重复性、灰度重复性、判定重复性三项任何一项抖动超过预期都不能急着上线。第三件是留一份可回溯的原始图档。每块被判 NG 的板子保存原始图、处理中间图、判定结果图三张。这份数据在后续优化模型、复盘客诉时是唯一的依据。存储成本现在很低一块板子三张图按 2MB 算一天两千块板也才几十个 G压缩一下完全扛得住。我见过太多项目上线半年后想优化结果发现当初一张原始图都没存只能从头再采数据。最后说一句我自己的体会。这套 Matlab 源码的价值不在于它写得多精妙而在于它把一条完整的检测链路——从像素当量换算、光源选择、背景压平、分割、形态学、特征提取到阈值判定——用可运行的代码串了起来。你拿它当基线改哪一步都能立刻看到结果变化这个反馈闭环对理解机器视觉的工程逻辑帮助极大。真正上产线的时候你需要补的往往不是算法而是工装、光源和样本管理这些看起来不“技术”的部分但恰恰是这些决定了系统能不能稳定跑一年。