1. 从一个焊点说起:为什么球焊检验值得单独拎出来讲
做过半导体封装或者精密电子组装的朋友应该都有体会,焊点质量直接决定产品的电气可靠性和长期稳定性。尤其是**球焊(Ball Bonding)**这道工序,一根几十微米粗的金线或铜线,在几毫秒内被电弧烧成自由空气球,再被劈刀压到焊盘上,压力、温度、超声功率、时间四个参数稍微偏一点,焊球就会偏心、扁平、颈部断裂或者干脆没粘上。问题是这东西太小了,一条产线上每天几万到几十万个焊点,靠人眼在显微镜下一个一个看,既不现实也不可靠。
所以机器视觉在这个环节几乎是刚需。我最早接触这类项目的时候,用的是某款通用视觉软件搭流程,后来转到Halcon平台,才发现它内置的ball_seq.hdev这个例程把球焊检验的完整思路拆得非常清楚。这个例程的核心价值不在于代码有多复杂,而在于它展示了一套从图像预处理、焊球分割、特征提取到缺陷分类的完整方法论。你把这个例程吃透了,换成检测其他圆形焊点、锡球、凸点,思路基本可以直接迁移。
这篇文章我打算把这个例程从头到尾拆一遍。不是简单翻译注释,而是把每一步背后的为什么讲清楚——为什么用这个算子而不是那个,参数怎么定,实际跑的时候哪些地方容易翻车。适合已经有一点 Halcon 基础、想往工业检测方向深入的朋友,也适合刚接手焊点检测项目、需要快速搭出可用流程的工程师。代码我会贴关键片段,但更重要的是把逻辑链条讲通,这样你换一个项目也能自己推。
2. 例程整体设计思路拆解
2.1 球焊检验到底要检什么
先明确检测目标,不然写出来的流程就是无头苍蝇。球焊的典型缺陷有这么几类:焊球缺失(该有球的地方是空的)、焊球偏位(球没压在焊盘中心)、球径异常(偏大或偏小,反映烧球能量或线径问题)、颈部断裂(球和线连接处太细)、焊球变形(压扁过度,呈椭圆或不规则形状)。这个例程主要覆盖的是焊球的有无、位置和形状尺寸这几项,这也是视觉最容易量化、最稳定的部分。
ball_seq.hdev这个名字里的seq是 sequence 的意思,暗示它处理的是一组按顺序排列的图像,模拟产线上连续采集的多个焊点。这一点很关键——实际产线不会只拍一张图,而是一个视野里可能有几十个焊点,或者一条料带上连续排列。例程用序列的方式组织,就是为了让你理解批量处理和单张处理的差异。
2.2 为什么选择基于形状的分割而不是阈值
很多人拿到焊球图像的第一反应是阈值分割,因为焊球通常比背景亮或者比背景暗,对比度明显。但例程没有一上来就用全局阈值,而是走了一条更稳的路。原因在于:实际图像里光照不均匀是常态,同一个视野里左边焊球亮、右边焊球暗,一个固定阈值要么左边过分割要么右边欠分割。而且焊盘、走线、字符这些干扰区域的灰度可能和焊球重叠,纯灰度阈值根本分不开。
例程的思路是先做图像增强和预处理,把焊球区域从背景里凸显出来,再用基于形状特征的方式提取。它用到了dyn_threshold这类动态阈值算子,或者结合形态学操作来定位候选区域。动态阈值的好处是它比较的是像素和其邻域均值的关系,能自适应局部光照变化,对光照不均的鲁棒性比全局阈值强很多。这个选择背后的逻辑就是:工业现场的光照你永远没法做到完美均匀,所以算法必须自己具备抗光照波动的能力。
2.3 序列化处理的架构考量
例程把处理流程封装成可对多张图像循环调用的结构,这个设计不是随便写的。产线节拍通常很紧,一个视野的处理时间可能只有几十毫秒。如果你每张图都重新初始化、重新读参数,开销会累积。序列化处理让你可以把与图像无关的初始化工作(比如创建测量模型、设定参数)放在循环外面,循环里只做和当前图像相关的计算,这样能省下可观的時間。
另外,序列化还方便做统计和趋势分析。单个焊点的检测结果意义有限,但连续一批焊点的球径分布、偏位分布能反映设备状态。比如球径均值慢慢变大,可能是烧球电流漂了;偏位方向有系统性偏移,可能是劈刀对位有问题。例程虽然没有做完整的 SPC(统计过程控制),但它的结构为后续扩展留了口子。
3. 核心算子与关键细节解析
3.1 图像预处理:把焊球从背景里"捞"出来
预处理这一步的目标很明确:让焊球区域和背景的差异最大化,同时压制噪声。例程里通常会先做一次均值滤波或高斯滤波,目的是平滑掉传感器噪声和表面微小纹理。这里有个经验:滤波核大小要跟焊球尺寸匹配。如果焊球直径在图像上大概是 30 个像素,那滤波核用 5×5 到 9×9 比较合适;核太大焊球边缘会被糊掉,核太小噪声压不住。
接着是对比度增强。Halcon 里常用emphasize算子,它本质上是原图加上一个高频增强项,能让边缘更锐利。但要注意,emphasize会同时放大噪声,所以一般放在滤波之后用。参数上,MaskWidth和MaskHeight控制增强的尺度,Factor控制增强强度。我的习惯是 Factor 从 1.0 开始试,逐步加到 2.0 左右,边增强边看焊球边缘是否清晰且没有明显噪点。
提示:预处理阶段不要追求"好看",要追求"好分割"。有时候一张看起来灰蒙蒙的图,反而比锐化过度的图更容易稳定提取焊球。
3.2 焊球分割:动态阈值与形态学的配合
分割是整条流程的核心。例程用的方法我拆成两步理解:先粗定位,再精修。
粗定位阶段,dyn_threshold是主力。它的原理是:对每个像素,比较它和它邻域均值(用mean_image算出来的)的差值,如果差值超过设定阈值Offset,就判定为前景或背景。Offset的选取直接决定分割的松紧。太小会把噪声也当成焊球,太大则焊球边缘会被吃掉。实操中我会先用mean_image生成一张平滑图,然后dyn_threshold的Offset从 5 开始试,根据焊球是否完整来调整,一般落在 5 到 15 之间。
粗定位之后,焊球区域可能带着毛刺、小孔或者和邻近区域粘连。这时候形态学操作就上场了。opening_circle用来去掉小毛刺和孤立噪点,closing_circle用来填小孔和连接断裂区域。结构元的半径很关键——半径要小于焊球半径,否则焊球本身会被腐蚀掉。假设焊球半径约 15 像素,那结构元半径用 3 到 5 比较安全。
* 伪代码示意,展示预处理与分割的算子组合逻辑 mean_image (Image, ImageMean, 15, 15) dyn_threshold (Image, ImageMean, RegionDyn, 8, 'light') opening_circle (RegionDyn, RegionOpened, 3.5) closing_circle (RegionOpened, RegionClosed, 3.5) connection (RegionClosed, ConnectedRegions)connection算子把连通区域分开,这样每个焊球成为一个独立候选。后面就可以对每个候选做特征筛选了。
3.3 特征提取:用哪些量来判断焊球好坏
分割出候选区域后,不是所有区域都是焊球。可能有焊盘反光、异物、走线端点混进来。例程用一组形状特征来做筛选和评价:
- 面积(Area):最直接的筛选条件。焊球面积应该落在一个合理区间内,太小的是噪声,太大的是粘连或异物。
- 圆度(Circularity):
circularity算子算的是区域接近圆的程度,值越接近 1 越圆。正常焊球应该比较圆,变形球圆度会下降。 - 等效半径或直径:由面积反推,用来判断球径是否在规格内。
- 中心坐标(Row, Column):用来判断偏位,即焊球中心相对焊盘中心或标称位置的偏移量。
- 椭圆长宽比(Anisometry):反映焊球是否被压扁成椭圆。
这些特征里,圆度和面积是最常用的两个。筛选逻辑通常是:先按面积卡掉明显不是焊球的区域,再按圆度卡掉形状异常的,剩下的才进入尺寸和位置评价。
注意:圆度对边缘质量很敏感。如果分割出来的焊球边缘坑坑洼洼,圆度会偏低,导致好球被误判。所以前面分割和形态学修边的工作一定要做扎实,不能指望后面特征提取来补救。
3.4 参数怎么定:从焊球物理尺寸反推像素参数
这是很多人卡住的地方——例程里的参数是针对它自带图像调的,换一套相机和镜头就不适用了。正确的做法是从物理尺寸反推。
假设你的焊球实际直径是 80 微米,相机加镜头的视野是 5 毫米宽,图像宽度是 2448 像素。那么像素当量就是 5000 微米除以 2448 像素,约等于 2.04 微米每像素。焊球在图像上的直径就是 80 除以 2.04,约 39 像素,半径约 19.5 像素。有了这个数,面积范围、形态学结构元半径、滤波核大小就都有依据了。
面积的理论值就是 π 乘以半径的平方,约 1195 平方像素。实际因为边缘过渡和分割误差,面积会在理论值上下浮动,一般设 ±20% 到 ±30% 作为合格区间。结构元半径取焊球半径的六分之一到四分之一,也就是 3 到 5 像素。滤波核取焊球直径的五分之一左右,约 7 到 9。
这套反推方法的好处是可移植。你换了相机,只要知道新的像素当量,所有参数都能重新算出来,不用瞎试。
4. 完整实操流程与关键环节实现
4.1 从读图到显示:一个可跑通的骨架
我把例程的核心流程整理成一个可复现的骨架。实际写的时候,初始化部分放在循环外,处理部分放在循环内。
* 初始化:设定基于像素当量算出的参数 BallDiameterPx := 39.0 BallRadiusPx := BallDiameterPx / 2.0 MinArea := 3.14159 * BallRadiusPx * BallRadiusPx * 0.7 MaxArea := 3.14159 * BallRadiusPx * BallRadiusPx * 1.3 StructRadius := BallRadiusPx / 5.0 FilterSize := round(BallDiameterPx / 5.0) * 循环处理序列图像 for Index := 1 to NumImages by 1 read_image (Image, 'ball_' + Index$'02d') * 预处理 mean_image (Image, ImageMean, FilterSize, FilterSize) dyn_threshold (Image, ImageMean, RegionDyn, 8, 'light') * 形态学修整 opening_circle (RegionDyn, RegionOpened, StructRadius) closing_circle (RegionOpened, RegionClosed, StructRadius) * 连通域分离 connection (RegionClosed, ConnectedRegions) * 特征筛选 select_shape (ConnectedRegions, BallRegions, 'area', 'and', MinArea, MaxArea) select_shape (BallRegions, RoundBalls, 'circularity', 'and', 0.75, 1.0) * 对每个合格焊球做评价 count_obj (RoundBalls, BallCount) for BallIdx := 1 to BallCount by 1 select_obj (RoundBalls, SingleBall, BallIdx) area_center (SingleBall, Area, Row, Column) circularity (SingleBall, Circ) * 这里可以继续算偏位、球径等,写入结果数组 endfor * 显示与结果输出 dev_display (Image) dev_set_color ('green') dev_display (RoundBalls) endfor这个骨架跑通之后,你会发现它已经能处理大部分正常焊球了。但实际产线图像远比例程自带的复杂,下面几节讲的就是怎么应对真实场景。
4.2 偏位计算:焊球中心与焊盘中心的匹配
偏位是球焊检验里非常关键的一项。例程里如果涉及偏位,通常需要先知道焊盘的标称位置。这个位置可以来自 CAD 数据、示教时记录的位置,或者通过模板匹配在图像里找焊盘。
实操中我常用两种方式。一种是模板匹配定位焊盘:事先截一个焊盘区域做模板,用create_shape_model或create_ncc_model建模板,每张图里匹配出焊盘位置,再和焊球中心比较。另一种是基于规则阵列推算:如果焊点按固定间距排列,可以用第一个焊球的位置加上间距推算出所有标称位置。
偏位量就是焊球中心到标称中心的欧氏距离。判定阈值根据工艺规格来,比如允许偏位不超过球径的 25%。这里有个坑:如果焊盘本身位置就有波动,偏位计算会把焊盘波动也算进去。所以更稳的做法是同时输出焊球相对焊盘的实际偏移和焊盘相对标称的偏移,分开判断,这样出了问题能快速定位是焊球偏了还是焊盘偏了。
4.3 球径测量:面积反推与直接测量的取舍
球径测量有两条路。一条是用area_center得到面积,再反推等效直径,公式是直径等于两倍根号下面积除以 π。另一条是用smallest_circle或fit_circle_contour_xld直接拟合圆,得到直径。两者各有适用场景。
面积反推的好处是稳定,对边缘的小凹凸不敏感,因为它算的是整体面积。缺点是如果焊球和邻近区域有轻微粘连,面积会偏大。直接拟合圆的好处是能反映真实轮廓,对椭圆变形更敏感,但缺点是如果边缘分割不干净,拟合结果会跳。
我的经验是:常规球径监控用面积反推,变形分析用圆拟合。两个结果都算出来,如果差异大,说明焊球形状有问题,值得单独标记出来看。
4.4 批量结果的统计与输出
单张图处理完,结果要能存下来。例程通常用数组或文件输出。实际项目里我会把每个焊球的索引、中心坐标、面积、等效直径、圆度、偏位量、判定结果写成一个结构,最后统一导出 CSV 或者写入数据库。
统计维度包括:合格率、球径均值与标准差、偏位均值与最大值、圆度分布。这些统计量比单点结果有价值得多。比如球径标准差突然变大,说明烧球过程不稳定;偏位均值有方向性,说明对位系统有系统性偏差。
* 结果收集示意 BallResults := [] for BallIdx := 1 to BallCount by 1 select_obj (RoundBalls, SingleBall, BallIdx) area_center (SingleBall, Area, Row, Column) EquivDiameter := 2.0 * sqrt(Area / 3.14159) circularity (SingleBall, Circ) * 偏位需要和标称位置比较,这里假设 NominalRow/Column 已算出 Offset := sqrt(pow(Row - NominalRow, 2) + pow(Column - NominalColumn, 2)) BallResults := [BallResults, [BallIdx, Row, Column, Area, EquivDiameter, Circ, Offset]] endfor5. 常见问题与排查技巧实录
5.1 焊球分割不完整或过度分割
这是最高频的问题。表现是焊球缺一块,或者几个焊球粘成一片。排查顺序我一般这样走:
先看预处理是否合适。如果dyn_threshold的Offset太大,焊球边缘会被削掉,表现为球变小、圆度下降。把 Offset 调小试试。如果调小后噪声冒出来,说明滤波不够,先把滤波核加大。
再看形态学结构元半径。opening_circle半径太大会腐蚀焊球,表现为球变小甚至消失。半径太小则毛刺去不掉。用焊球半径的五分之一作为起点,上下微调。
如果焊球粘连,connection之后会得到一个大连通域。这时候可以尝试分水岭分割或者基于距离变换的分割。Halcon 里有watersheds和distance_transform可以用。思路是先算距离变换,找到每个焊球的"山峰",再以山峰为种子做分水岭,把粘连区域切开。
| 现象 | 可能原因 | 排查方向 | 调整手段 |
|---|---|---|---|
| 焊球缺一块 | Offset 太大 | 检查 dyn_threshold 参数 | 减小 Offset |
| 焊球变小 | 形态学腐蚀过度 | 检查 opening 半径 | 减小结构元半径 |
| 焊球粘连 | 分割过松或球间距小 | 检查 Offset 和间距 | 减小 Offset,或上分水岭 |
| 噪声被当成焊球 | 滤波不足或 Offset 太小 | 检查滤波核和 Offset | 加大滤波核,增大 Offset |
| 焊球边缘毛糙 | 分割边缘不平滑 | 检查预处理链 | 加 closing,或对轮廓做平滑 |
5.2 圆度计算偏低导致好球误判
圆度对边缘质量极其敏感。我遇到过分割出来的焊球肉眼看着挺圆,但圆度只有 0.6,原因是边缘有一圈锯齿。解决办法有两个:一是在分割后对区域做一次平滑,比如用smooth_contours_xld对轮廓平滑再拟合;二是适当放宽容度,把圆度阈值从 0.85 降到 0.75,同时结合面积和直径做综合判断,不单靠圆度一票否决。
提示:圆度阈值不要设得太死。不同批次、不同光照下焊球边缘质量会有波动,留一点余量比频繁调参更省心。
5.3 光照变化导致参数失效
这是产线视觉的通病。早上和下午的光照不一样,或者设备发热后光源亮度漂移,都会让固定参数失效。例程的参数是针对特定图像调的,直接搬到产线大概率要翻车。
应对策略分三层。第一层是硬件:尽量用稳定光源,加遮光罩,减少环境光干扰。第二层是预处理自适应:用dyn_threshold这类局部自适应算子,本身就比全局阈值抗光照变化。第三层是参数自整定:定期用标准件跑一遍,根据结果微调 Offset 等关键参数。如果条件允许,加一个光照反馈控制,实时监测光源亮度并补偿。
5.4 处理速度跟不上产线节拍
Halcon 本身效率很高,但如果你在循环里做了太多不必要的操作,速度会掉下来。优化方向:
- 缩小处理区域。如果焊点只出现在图像中间一条带,用
reduce_domain把 ROI 限制住,后续所有操作都在 ROI 内做,速度能提升好几倍。 - 降低图像分辨率。如果焊球在图像上有 40 像素,降到 20 像素也能测,但处理量少一半。前提是精度够用。
- 复用模型。模板匹配的模型、测量模型这些,建一次就够了,不要每张图重建。
- 并行化。Halcon 支持多线程,如果一台机器处理多个相机,可以把不同相机的图像分到不同线程。
我实测过一个案例,原始流程单张图 120 毫秒,把 ROI 缩小到原来四分之一后降到 35 毫秒,完全满足节拍要求。所以先看 ROI,再看分辨率,最后才考虑换硬件。
5.5 标定与像素当量不准导致尺寸误判
球径测量的绝对精度依赖像素当量。如果标定不准,测出来的直径系统性偏大或偏小。标定方法是用已知尺寸的标准件(比如标准玻璃板上的刻度)拍图,算出实际像素当量。注意标定要在和产线相同的光学条件下做,包括工作距离、光圈、光源。换了镜头或者动了相机位置,必须重新标定。
还有一个容易忽略的点:镜头畸变。如果视野边缘的焊球和中心的焊球测量结果有系统性差异,很可能是畸变没校正。Halcon 有完整的标定流程,用标定板可以同时算出内参和畸变系数,然后对图像做校正。
6. 从例程到产线:我的几点实操体会
ball_seq.hdev这个例程最大的价值是给你一个正确的起点,而不是一个能直接上产线的成品。它把球焊检验的骨架搭好了,但血肉需要你根据实际场景填。
我自己的习惯是,拿到任何新项目的图像,先不急着写完整流程,而是用 Halcon 的交互工具把单步效果看一遍。预处理完什么样,分割完什么样,特征筛选后剩几个,每一步都确认没问题再串起来。这样出了问题能快速定位是哪一步的锅,而不是面对一个黑盒瞎调。
另外,参数一定要做成可配置的,不要硬编码在代码里。不同产品、不同批次可能需要微调,做成配置文件或者界面参数,改起来不用重新编译。我见过太多项目因为参数写死在代码里,换个产品就要改代码重新部署,维护成本极高。
最后说一个心态上的事。视觉检测没有百分之百的完美方案,总有边缘案例会漏检或误检。关键是把误检和漏检控制在可接受范围内,并且建立反馈机制——把判定有疑问的图像存下来,定期人工复核,根据复核结果反过来优化参数。这个闭环建起来,系统才会越跑越稳。