1. 球焊检验项目的整体设计思路1.1 为什么选择这个例程作为切入口球焊检验在精密制造领域属于典型的“小目标、高反光、强干扰”检测场景。焊球直径通常只有几十到几百微米表面是金属材质在环形光源或同轴光源下极易产生镜面反射同时背景可能是陶瓷基板、PCB焊盘或引线框架灰度分布差异极大。很多刚接触机器视觉检测的工程师第一反应是用阈值分割加形态学处理但实际调参时会发现阈值稍微高一点焊球就碎了稍微低一点背景噪声全冒出来换一批产品又要重新调根本没法量产。ball_seq.hdev这个例程的价值就在于它没有走“单张图像调参”的路线而是用序列图像sequence的思路把整个检测流程拆成“定位—分割—特征筛选—结果输出”四个阶段每个阶段用不同的算子组合来应对焊球的物理特性。我个人的理解是这个例程真正想教给使用者的不是某个算子的用法而是一套可复用的检测框架当你面对类似的小尺寸、高反光目标时可以直接把框架搬过去只替换中间的分割策略和特征阈值。从行业背景来看球焊工艺广泛存在于半导体封装、微电子组装、精密传感器制造等环节。焊球的质量直接决定电气连接的可靠性常见的缺陷包括球径偏大或偏小、球体缺失、球体偏移、球体变形椭圆度超标、球体粘连等。传统的人工目检在放大镜下进行效率低、一致性差而且长时间作业容易视觉疲劳。机器视觉方案的核心诉求就是稳定复现人眼判断标准同时把节拍压到产线要求以内。这个例程适合谁参考我认为有三类人第一类是刚上手Halcon、想找一个完整项目练手的视觉工程师第二类是在产线上已经有用Halcon做定位或测量、现在需要增加焊球检测功能的开发者第三类是做设备集成的工程师需要评估Halcon能否满足某个球焊检测项目的精度和速度要求。不管哪一类读透这个例程都能少走很多弯路。1.2 序列图像处理的核心逻辑单张图像处理最大的问题是没有上下文。一张图里焊球亮了你无法判断它是真的焊球还是反光斑点一张图里焊球暗了你也不知道是光源衰减还是球体本身氧化。序列图像的好处是引入了时间维度和空间维度的双重约束。ball_seq.hdev里的序列通常来自两种场景一种是产线上连续采集的多帧图像焊球位置基本固定但光照和噪声在帧间有微小变化另一种是同一个焊球在不同光照条件或不同焦平面下的多张图像用来合成更完整的特征。例程里主要用的是第一种通过帧间差分或帧间累积来增强焊球信号、抑制随机噪声。具体来说序列处理的核心逻辑可以概括为三步。第一步是背景建模从多帧图像中估计出一个稳定的背景参考这个背景不包含焊球只反映基板和光照的缓慢变化。第二步是前景增强把每一帧与背景参考做差分得到只包含焊球和噪声的残差图再通过多帧累积把噪声平均掉。第三步是目标确认在累积后的残差图上做分割和特征筛选只有同时在多帧中稳定出现的区域才被判定为焊球。这套逻辑的优势在于抗干扰能力强。单帧图像里一个孤立的亮斑可能是灰尘反光但在序列里它不会稳定出现在同一位置累积后就被抑制了。反过来真正的焊球在每一帧里都在同一位置发亮累积后信号越来越强。我实测过在信噪比比较差的情况下序列处理能把检测稳定性提升一个档次误报率至少降一半。当然序列处理也有代价需要采集多帧图像节拍会变长需要存储中间结果内存占用增加算法复杂度上升调参维度变多。所以例程里并不是全程都用序列而是在分割阶段用序列增强到了特征筛选和结果输出阶段又回到单帧逻辑。这种“该用则用、不该用不用”的取舍是读这个例程时最值得体会的地方。1.3 检测框架的模块化拆解把ball_seq.hdev拆开来看整个流程可以分成四个模块每个模块的输入输出都很清晰。模块一图像采集与预处理。输入是相机采集的原始图像序列输出是经过灰度归一化和噪声抑制的图像。这个模块的关键是光照一致性如果序列中各帧的亮度差异太大后续差分就会引入伪影。例程里通常用scale_image或equ_histo_image做灰度拉伸用gauss_filter或median_filter做平滑。注意平滑核不能太大否则小焊球会被抹掉。模块二背景估计与差分。输入是预处理后的序列输出是残差图。背景估计的方法有很多例程里用的是帧间最小值或帧间中值因为焊球在每一帧里都是亮目标取最小值就能把焊球去掉剩下的就是背景。差分的时候用sub_image做逐像素相减再取绝对值。这一步的难点是背景漂移如果产线震动导致基板位置微动背景估计就会失准需要先做配准。模块三分割与候选区域提取。输入是残差图输出是候选焊球区域。例程里用的是动态阈值分割dyn_threshold因为残差图的灰度分布不均匀固定阈值很难兼顾亮区和暗区。动态阈值的掩膜尺寸要略大于焊球直径这样既能保留焊球边缘又不会把背景噪声带进来。分割后做连通域分析connection得到一个个候选区域。模块四特征筛选与结果输出。输入是候选区域输出是最终焊球位置和属性。例程里筛选的特征包括面积、圆度、直径、灰度均值、位置偏差。面积用来排除噪声和碎片圆度用来排除拉长或变形的区域直径用来判断球径是否合格灰度均值用来判断球体是否氧化或缺失。筛选阈值需要根据实际产品规格来定例程里给的只是参考值。这四个模块的划分不是固定的你可以根据实际项目把模块二和模块三合并也可以把模块四拆成“粗筛”和“精筛”两级。但不管怎么调整输入输出清晰、每步可验证这个原则不能丢。我见过很多项目把流程写成一锅粥出了问题根本不知道是哪一步的锅最后只能推倒重来。2. 核心算子与参数细节解析2.1 图像预处理阶段的算子选择预处理阶段的目标是让焊球和背景的对比度尽可能大同时把随机噪声压下去。ball_seq.hdev里用到的算子不多但每个都有明确的意图。rgb1_to_gray或rgb3_to_gray用于彩色相机转灰度。如果用的是黑白相机这一步可以跳过。转灰度的系数选择会影响焊球和背景的对比度例程里用的是标准系数但实际项目中可以尝试调整系数来增强特定颜色的焊球。比如金色焊球在蓝光下对比度更高就可以加大蓝色通道的权重。scale_image用于灰度线性拉伸。它的原理是把输入灰度范围[min, max]映射到[0, 255]。参数Mult和Add的计算方式是Mult 255 / (max - min)Add -Mult * min。例程里通常先用min_max_gray统计当前图像的灰度范围再计算这两个参数。注意如果图像里存在极亮的反光点max会被拉得很高导致整体拉伸不足。这时候可以先用median_filter把反光点压一下或者用min_max_gray的Percent参数忽略掉前后一定比例的像素。gauss_filter用于高斯平滑。它的核心参数是Sigma表示高斯核的标准差。Sigma越大平滑效果越强但焊球边缘也越模糊。对于直径几十微米的焊球Sigma通常取 0.8 到 1.5 之间。我个人的经验是先用Sigma1.0试如果噪声还是大就加到 1.5如果焊球边缘开始糊了就降到 0.8。不要用median_filter替代高斯因为中值滤波对高斯噪声的抑制效果不如高斯滤波而且会改变焊球的灰度分布。equ_histo_image用于直方图均衡化。它的作用是让灰度分布更均匀增强暗部细节。但这个算子有个副作用会放大背景噪声。所以例程里只在背景比较暗、焊球比较亮的时候用而且通常放在高斯滤波之前。如果背景本身灰度分布就比较均匀可以跳过这一步。提示预处理阶段不要过度处理。我见过有人为了追求“干净”的图像连续用了高斯、中值、均值三种滤波结果焊球边缘被抹得跟背景一样后面分割根本找不到。记住预处理的目标是增强对比度、抑制噪声不是把图像变成理想图。2.2 背景建模与差分的关键参数背景建模是序列处理的核心。ball_seq.hdev里用的方法是帧间最小值具体实现是先读取多帧图像到一个数组然后逐像素取最小值得到背景图。为什么用最小值而不是平均值因为焊球在每一帧里都是亮目标平均值会被焊球拉高最小值则能把焊球完全去掉只留下背景。实现的时候要注意两点。第一序列长度不能太短。如果只取 3 帧最小值可能还残留焊球边缘通常取 5 到 10 帧比较稳妥。第二序列中焊球的位置必须基本固定。如果产线震动导致焊球在帧间移动超过一个焊球直径最小值法就会失效背景里会出现焊球的“残影”。这时候需要先做图像配准用vector_to_hom_mat2d或proj_match_points_ransac把各帧对齐到同一坐标系。差分用sub_image实现参数是两幅图像和缩放因子。例程里通常写sub_image(Image, Background, Diff, 1, 0)表示逐像素相减不缩放。差分后取绝对值abs_image得到残差图。残差图里焊球是亮目标背景接近零。但实际中背景不会完全为零因为光照有波动、传感器有噪声所以残差图里会有低幅度的纹理。为了抑制这些纹理例程里用了threshold做一次粗筛把低于某个灰度的像素直接置零。这个阈值不能太高否则焊球边缘会被切掉也不能太低否则噪声全保留。我通常用min_max_gray统计残差图的灰度分布取max * 0.3作为粗筛阈值。如果残差图里焊球信号很强这个比例可以降到 0.2如果噪声很大可以升到 0.4。注意差分前一定要确保两幅图像的尺寸和类型完全一致。如果一帧是byte类型另一帧是int类型sub_image会报错。可以用convert_image_type统一类型。另外如果图像有 ROI 区域差分前要先reduce_domain否则 ROI 外的像素也会参与运算浪费计算资源。2.3 动态阈值分割的掩膜尺寸计算dyn_threshold是 Halcon 里处理不均匀光照的利器。它的原理是对原图做一次平滑得到参考图然后逐像素比较原图和参考图的差异差异超过Offset就判定为前景。关键参数有两个Offset和掩膜尺寸。Offset决定分割的灵敏度。Offset越小越多的像素被判定为前景包括噪声Offset越大只有差异明显的像素才被判定为前景但焊球边缘可能被漏掉。例程里通常取 5 到 15 之间。我个人的经验是先用Offset10试如果焊球分割不完整就降到 5如果噪声太多就升到 15。掩膜尺寸决定参考图的平滑程度。掩膜太小参考图会跟随原图的局部变化导致焊球内部也被判定为前景掩膜太大参考图过于平滑焊球边缘的梯度会被抹平分割出的区域比实际焊球大一圈。掩膜尺寸的经验公式是掩膜直径 ≈ 焊球直径 × 1.5 到 2.0。比如焊球直径 20 像素掩膜直径取 30 到 40 像素。在 Halcon 里dyn_threshold的掩膜是通过mean_image或gauss_filter生成的mean_image的Width和Height就是掩膜尺寸。分割完成后用connection做连通域分析得到一个个候选区域。这时候候选区域里既有真正的焊球也有噪声和碎片。下一步就是用特征筛选把它们分开。2.4 特征筛选的阈值设定逻辑特征筛选是决定检测精度的最后一道关卡。ball_seq.hdev里用到的特征包括面积、圆度、直径、灰度均值、位置偏差。每个特征的阈值设定都有讲究。面积用来排除噪声和碎片。面积下限通常取理论焊球面积的 0.5 倍上限取 1.5 倍。理论面积怎么算如果焊球直径是D面积就是π * (D/2)^2。比如D20像素面积约 314 像素下限取 157上限取 471。注意面积阈值不能设得太死因为焊球边缘的分割结果会有几个像素的波动。圆度用来排除拉长或变形的区域。圆度的定义是4 * π * Area / (Perimeter^2)完美圆的圆度是 1越接近 0 表示形状越不规则。焊球的圆度通常在 0.7 以上如果低于 0.6 就可能是变形球或两个球粘连。例程里通常取 0.6 到 0.8 之间。如果产品允许一定程度的椭圆度可以降到 0.5。直径用来判断球径是否合格。直径的计算方式有两种一种是2 * sqrt(Area / π)基于面积反推另一种是max(Distance)基于区域的最大内切圆或最小外接圆。例程里用的是前者因为计算简单且对边缘噪声不敏感。直径阈值根据产品规格来定比如规格是 20±2 像素那么下限 18上限 22。灰度均值用来判断球体是否氧化或缺失。氧化焊球的灰度均值会偏低缺失焊球的区域灰度均值接近背景。例程里通常取所有候选区域灰度均值的平均值然后设定一个偏差范围。如果某个区域的灰度均值低于平均值的 0.7 倍就判定为异常。位置偏差用来判断球体是否偏移。位置偏差的计算方式是先拟合所有焊球的理想网格或理想位置然后计算每个焊球实际位置与理想位置的欧氏距离。偏差超过阈值的焊球被标记为偏移。这个特征在球栅阵列BGA检测里特别重要。提示特征筛选的阈值不要一次定死。我通常的做法是先用一批合格品跑一遍统计每个特征的实际分布取均值 ± 3 * 标准差作为初始阈值然后用一批缺陷品验证看能不能把缺陷都筛出来最后根据误报和漏报的权衡微调。这个过程可能要迭代三四轮但一旦定下来后续量产就很稳。3. 完整实操流程与关键环节实现3.1 图像序列的采集与命名规范实操的第一步是采集图像序列。ball_seq.hdev里读取的是一组已经存好的图像文件命名通常是ball_01.png、ball_02.png这样的连续编号。实际项目中图像可能来自相机实时采集也可能来自离线录像。不管哪种来源命名规范都很重要因为 Halcon 的list_files和read_image依赖文件名排序。如果图像来自相机建议用open_framegrabber打开相机用grab_image_async连续采集每采一帧就存成文件。存文件的时候用write_image格式选png或tiff不要用jpg因为jpg是有损压缩会引入块状伪影影响后续差分。文件名可以用时间戳或递增序号但要保证按文件名排序后就是采集顺序。如果图像来自离线录像先用list_files列出所有文件再用read_image逐个读取。注意list_files返回的文件名顺序可能不是自然顺序比如ball_10.png会排在ball_2.png前面需要用tuple_sort或自定义排序函数重新排列。我通常用tuple_regexp_select提取文件名中的数字部分转成整数后排序再按排序结果读取。序列长度方面例程里通常用 5 到 10 帧。帧数太少背景估计不准帧数太多计算量增加而且如果产线有缓慢漂移长序列反而会引入误差。我个人的经验是如果产线稳定用 5 帧就够了如果光照波动大用 8 到 10 帧。采集的时候要保证焊球在每一帧里都完整可见不能被遮挡或切边。3.2 背景估计与差分操作的代码实现背景估计的代码实现比较直接。假设已经读取了Images数组长度是N那么逐像素取最小值的代码如下* 初始化背景图为第一帧 copy_image(Images[0], Background) * 逐帧取最小值 for Index : 1 to N - 1 by 1 min_image(Background, Images[Index], Background) endformin_image是逐像素取两幅图像的最小值循环N-1次后Background就是整个序列的最小值图。如果焊球在每一帧里都是亮目标最小值图里焊球就被去掉了只剩下背景。差分操作的代码如下* 对每一帧做差分 for Index : 0 to N - 1 by 1 sub_image(Images[Index], Background, Diff, 1, 0) abs_image(Diff, DiffAbs) * 粗筛去掉低幅度噪声 threshold(DiffAbs, Region, 20, 255) * 连通域分析 connection(Region, ConnectedRegions) * 特征筛选 select_shape(ConnectedRegions, Balls, area, and, 100, 1000) * 结果输出 count_obj(Balls, BallCount) * 这里可以把 BallCount 和 Balls 存起来后续做序列一致性判断 endfor这段代码里threshold的阈值 20 是粗筛阈值需要根据实际残差图的灰度分布调整。select_shape的面积范围 100 到 1000 也是参考值需要根据焊球实际尺寸调整。序列一致性判断是可选但推荐的一步。具体做法是对每一帧的检测结果做位置匹配只有连续多帧都检测到的焊球才被最终确认。匹配可以用distance_pp计算两个区域中心点的距离距离小于某个阈值就认为是同一个焊球。如果某个焊球只在少数帧里出现就判定为噪声或伪影从最终结果里剔除。3.3 分割与特征提取的完整代码链路分割和特征提取的代码链路比较长我把它拆成几个关键步骤来说明。第一步是动态阈值分割。代码如下* 生成参考图 mean_image(DiffAbs, RefImage, 31, 31) * 动态阈值分割 dyn_threshold(DiffAbs, RefImage, RegionDyn, 10, light) * 连通域分析 connection(RegionDyn, ConnectedRegions)mean_image的掩膜尺寸 31 是参考值需要根据焊球直径调整。dyn_threshold的Offset取 10light表示提取比参考图亮的区域。第二步是区域筛选。代码如下* 面积筛选 select_shape(ConnectedRegions, RegionArea, area, and, 150, 500) * 圆度筛选 select_shape(RegionArea, RegionCirc, circularity, and, 0.6, 1.0) * 直径筛选 select_shape(RegionCirc, RegionDiam, diameter, and, 15, 25)select_shape可以连续调用每次筛掉一部分不合格区域。注意circularity和diameter是 Halcon 内置的特征名直接用字符串指定即可。第三步是灰度特征提取。代码如下* 计算每个区域的灰度均值 intensity(RegionDiam, Images[Index], MeanGray, Deviation) * 灰度筛选 select_gray(RegionDiam, Images[Index], RegionGray, mean, and, 100, 255)intensity返回每个区域的灰度均值和标准差select_gray根据灰度特征筛选区域。第四步是结果输出。代码如下* 计算区域中心 area_center(RegionGray, Area, Row, Column) * 计算圆度 circularity(RegionGray, Circularity) * 输出结果 for i : 0 to |Row| - 1 by 1 * 这里可以把 Row[i], Column[i], Area[i], Circularity[i] 存到数组或文件里 endforarea_center返回区域面积和中心坐标circularity返回圆度。这些数据可以存成 CSV 或直接传给后续的判定逻辑。3.4 检测结果的可视化与判定输出检测结果的可视化很重要一方面方便调试另一方面方便产线操作员快速判断。ball_seq.hdev里通常用dev_display显示原图用dev_set_color设置颜色用dev_display显示检测到的区域。可视化的代码示例如下* 显示原图 dev_display(Images[Index]) * 设置颜色为绿色 dev_set_color(green) * 显示合格焊球 dev_display(RegionGray) * 设置颜色为红色 dev_set_color(red) * 显示不合格区域如果有 dev_display(RegionReject) * 显示文字标注 disp_message(WindowHandle, Ball Count: BallCount, window, 12, 12, black, true)判定输出方面通常需要输出每个焊球的坐标、直径、圆度、灰度均值以及一个总的合格/不合格标志。如果检测到不合格焊球还要输出不合格的类型面积异常、圆度异常、直径异常、灰度异常、位置偏移。这些信息可以存成文本文件也可以通过串口或网络接口发给上位机。我个人的经验是可视化不要只显示最终结果要把中间结果也显示出来。比如残差图、分割后的区域、筛选前的候选区域这些在调试阶段非常有用。等算法稳定了再把中间显示关掉只保留最终结果减少计算开销。4. 常见问题与排查技巧实录4.1 焊球分割不完整或过度分割这是最常见的问题表现是分割出的区域比实际焊球小一圈不完整或大一圈过度分割或者焊球内部出现空洞。原因一动态阈值的Offset不合适。Offset太大焊球边缘的梯度不够被判定为背景导致分割不完整Offset太小焊球内部的灰度波动被判定为前景导致过度分割或空洞。解决方法是调整Offset先用 10 试不完整就降到 5过度就升到 15。原因二掩膜尺寸不合适。掩膜太小参考图跟随原图局部变化焊球内部也被判定为前景掩膜太大参考图过于平滑焊球边缘梯度被抹平分割区域比实际大。解决方法是调整mean_image的Width和Height经验值是焊球直径的 1.5 到 2.0 倍。原因三预处理过度。高斯滤波的Sigma太大焊球边缘被模糊梯度变小动态阈值分割不出来。解决方法是减小Sigma或者把高斯滤波放在动态阈值之后。原因四光照不均匀。如果焊球处于暗区灰度值低动态阈值可能分割不出来。解决方法是先做光照校正用estimate_background_illumination或illuminate把光照拉平。提示排查分割问题时先把残差图、参考图、分割结果三张图并排显示一眼就能看出是哪一步出了问题。残差图里焊球信号弱说明预处理或差分有问题参考图里焊球轮廓清晰说明掩膜太小分割结果里焊球内部有空洞说明Offset太小。4.2 背景差分引入伪影背景差分引入伪影的表现是残差图里出现与焊球无关的亮斑或暗斑导致误检。原因一序列中焊球位置移动。如果产线震动导致焊球在帧间移动最小值法估计的背景里会残留焊球的“残影”差分时残影位置会出现伪影。解决方法是先做图像配准把各帧对齐到同一坐标系再做背景估计。原因二光照波动。如果序列中各帧的亮度差异大最小值法估计的背景会偏向暗帧差分时亮帧的背景区域会出现大面积伪影。解决方法是先做灰度归一化用scale_image或equ_histo_image把各帧的灰度分布拉平。原因三传感器噪声。如果相机噪声大残差图里会有随机噪点。解决方法是增加序列长度用更多帧来平均噪声或者用gauss_filter对残差图做平滑。原因四背景本身有纹理。如果基板有纹理或焊盘有图案最小值法估计的背景会保留这些纹理差分时纹理边缘会出现伪影。解决方法是先用median_filter或gauss_filter把背景纹理压掉再做差分。4.3 特征筛选阈值难以确定特征筛选阈值难以确定的表现是阈值设宽了缺陷漏检阈值设严了合格品误报。这个问题在换产品、换批次时特别突出。解决方法一统计法。先用一批合格品跑一遍统计每个特征的实际分布取均值 ± 3 * 标准差作为初始阈值。然后用一批缺陷品验证看能不能把缺陷都筛出来。最后根据误报和漏报的权衡微调。解决方法二自适应阈值。如果产品批次间差异大可以用自适应阈值。具体做法是对每一批产品先跑一遍统计特征分布然后根据分布自动计算阈值。Halcon 里可以用tuple_mean和tuple_deviation计算均值和标准差再动态生成阈值。解决方法三多特征联合判定。不要只依赖单一特征而是用多个特征联合判定。比如一个区域面积合格但圆度不合格仍然判定为不合格。联合判定可以降低单一特征阈值设定的压力提高鲁棒性。解决方法四分级判定。把判定结果分成“合格”、“可疑”、“不合格”三级。可疑的焊球交给人工复检或者用更高分辨率的相机重新采集。分级判定可以在保证检出率的同时降低误报率。4.4 检测节拍不满足产线要求检测节拍不满足产线要求的表现是单次检测耗时超过产线节拍导致产线降速或停机。原因一序列长度太长。序列长度增加背景估计和差分的计算量线性增加。解决方法是缩短序列长度从 10 帧降到 5 帧或者用增量式背景更新只保留最近几帧。原因二图像分辨率太高。高分辨率图像的计算量是低分辨率图像的数倍。解决方法是降低分辨率或者用 ROI 只处理焊球所在区域。原因三算子选择不当。有些算子计算量大比如gauss_filter比mean_filter慢dyn_threshold比threshold慢。解决方法是根据实际需求选择算子如果光照均匀就用threshold如果光照不均匀再用dyn_threshold。原因四没有并行化。Halcon 支持多线程和 GPU 加速可以用set_system(parallelize_operators, true)开启算子并行或者用set_system(cuda_enable, true)开启 GPU 加速。注意GPU 加速需要显卡支持而且不是所有算子都支持 GPU。提示优化节拍的时候先用count_seconds或profile统计每个算子的耗时找出瓶颈算子再针对性优化。不要盲目优化否则可能优化了不耗时的算子白费功夫。4.5 常见问题速查表问题现象可能原因排查方法解决措施焊球分割不完整Offset 太大、掩膜太大、预处理过度显示残差图和分割结果对比减小 Offset、减小掩膜、减小 Sigma焊球过度分割Offset 太小、掩膜太小显示参考图和分割结果对比增大 Offset、增大掩膜背景差分引入伪影焊球移动、光照波动、传感器噪声显示背景图和残差图图像配准、灰度归一化、增加序列长度特征筛选阈值难定产品批次差异大统计合格品和缺陷品的特征分布统计法、自适应阈值、多特征联合检测节拍不达标序列太长、分辨率太高、算子选择不当用 count_seconds 统计各算子耗时缩短序列、降低分辨率、换算子、并行化焊球内部空洞Offset 太小、焊球表面反光显示残差图和分割结果增大 Offset、调整光源角度焊球边缘毛刺噪声大、分割阈值低显示残差图增加高斯滤波、提高粗筛阈值误检背景纹理背景纹理强、差分不干净显示背景图和残差图预处理压纹理、增大差分阈值5. 光源与相机选型的实操经验5.1 光源类型对焊球成像的影响焊球是高反光金属目标光源选型直接决定成像质量。我试过几种常见光源各有优劣。环形光源安装方便光照均匀适合大多数焊球检测。但环形光源会在焊球顶部形成亮斑如果焊球表面有氧化或划痕亮斑形状会变化影响灰度一致性。解决方法是把环形光源的角度调低让光线斜射减少顶部亮斑。同轴光源光线垂直照射焊球顶部形成均匀亮斑适合检测焊球表面缺陷。但同轴光源对基板的反光也强如果基板是镜面材质背景会过亮对比度下降。解决方法是加偏振片或者用低角度光源补光。低角度光源光线斜射焊球边缘形成亮环适合检测焊球轮廓和偏移。但低角度光源对基板平整度敏感如果基板有翘曲光照不均匀。解决方法是增加光源数量从多个角度照射。背光源光线从背面照射焊球形成暗影适合检测焊球缺失和位置。但背光源需要透光基板如果基板不透明就用不了。解决方法是改用侧光源或同轴光源。我个人的经验是环形光源加低角度补光的组合最稳妥。环形光源提供均匀照明低角度光源增强焊球边缘两者结合可以兼顾表面缺陷和轮廓检测。如果预算允许可以用可编程光源根据不同产品切换光照模式。5.2 相机分辨率与焊球尺寸的匹配计算相机分辨率的选择取决于焊球尺寸和检测精度要求。计算公式是所需像素数 焊球直径 / 检测精度。比如焊球直径 100 微米要求检测精度 5 微米那么所需像素数 100 / 5 20 像素。也就是说焊球在图像里至少占 20 个像素才能分辨出 5 微米的差异。实际选型时还要考虑视场大小。如果视场是 10 毫米 × 10 毫米焊球直径 100 微米那么焊球在图像里的像素数 100 微米 / 10 毫米 × 相机水平分辨率。假设相机水平分辨率是 5000 像素那么焊球像素数 100 / 10000 × 5000 50 像素。这个分辨率足够检测 5 微米的差异。但要注意像素数不是越多越好。像素数增加图像数据量增加处理时间增加。而且像素数超过光学分辨率后再增加像素数也不会提升实际分辨率只是把模糊的边缘放大而已。光学分辨率的极限由镜头的数值孔径决定经验公式是光学分辨率 ≈ 0.61 × 波长 / 数值孔径。对于可见光波长约 0.5 微米数值孔径 0.1 的镜头光学分辨率约 3 微米。所以检测精度不可能优于 3 微米除非用紫外光或更高级的镜头。5.3 镜头放大倍率与工作距离的权衡镜头选型要考虑放大倍率和工作距离。放大倍率 相机传感器尺寸 / 视场尺寸。比如传感器尺寸是 2/3 英寸约 8.8 毫米 × 6.6 毫米视场是 10 毫米 × 7.5 毫米那么放大倍率 8.8 / 10 0.88 倍。工作距离是镜头前端到被测物的距离。放大倍率越大工作距离越短。比如 0.88 倍放大倍率的镜头工作距离可能只有几十毫米。如果产线空间有限工作距离太短会装不下。解决方法是换更长焦距的镜头或者用延长管。我个人的经验是先确定工作距离再选放大倍率。如果产线空间充足工作距离可以放到 100 到 200 毫米这样镜头选择范围大安装也方便。如果空间紧张工作距离只有几十毫米就要选短焦距镜头但短焦距镜头的畸变通常更大需要做畸变校正。提示镜头选型时不要只看放大倍率还要看景深。景深 2 × 波长 × (光圈数)^2 / (放大倍率^2)。放大倍率越大景深越浅。如果焊球高度有波动景深太浅会导致部分焊球模糊。解决方法是缩小光圈但缩小光圈会降低进光量需要增加光源亮度或延长曝光时间。6. 从例程到量产项目的扩展思路6.1 多工位检测的并行化改造例程里处理的是单个工位的图像序列。实际产线可能有多个工位每个工位都要检测。如果串行处理节拍会成倍增加。解决方法是并行化。Halcon 支持多线程可以用par_start和par_join开启并行任务。具体做法是把每个工位的检测封装成一个过程然后用par_start同时启动多个过程最后用par_join等待所有过程完成。注意并行任务之间不能共享图像变量每个任务要有独立的图像副本。如果工位数超过 CPU 核心数可以用任务队列。具体做法是把所有工位的图像放入队列启动与 CPU 核心数相同的线程每个线程从队列取任务处理。这样可以充分利用 CPU 资源又不会因为线程过多导致上下文切换开销。GPU 加速是另一种选择。Halcon 支持 CUDA可以把部分算子放到 GPU 上执行。但 GPU 加速需要显卡支持而且不是所有算子都支持 GPU。我个人的经验是如果工位数多、图像分辨率高GPU 加速效果明显如果工位数少、图像分辨率低CPU 多线程就够了。6.2 缺陷分类与数据追溯的集成例程里只做了合格/不合格判定没有做缺陷分类。实际项目中缺陷分类很重要因为不同缺陷对应不同的工艺问题。比如球径偏大可能是焊膏量过多球体偏移可能是贴装精度不够球体缺失可能是焊膏缺失或焊接不良。缺陷分类的实现方式是在特征筛选阶段对不合格区域进一步分析根据特征值判断缺陷类型。比如面积小于下限判定为“球体缺失”面积大于上限判定为“球体粘连”圆度低于阈值判定为“球体变形”灰度均值低于阈值判定为“球体氧化”位置偏差超过阈值判定为“球体偏移”。数据追溯方面每个焊球的检测结果都要存下来包括图像编号、焊球坐标、直径、圆度、灰度均值、判定结果、缺陷类型。这些数据可以存成 CSV 或数据库方便后续统计分析。如果发现某一批产品缺陷率偏高可以回溯到具体的图像和工艺参数找出问题根源。我个人的经验是数据追溯不要只存结果要存中间数据。比如残差图、分割区域、特征值这些在排查问题时非常有用。存储成本不高但排查问题时能省很多时间。6.3 换型生产的参数管理策略产线经常要换型不同产品的焊球尺寸、间距、基板材质都不同。如果每次换型都重新调参效率太低。解决方法是参数管理。参数管理的核心是把检测参数和产品型号绑定。具体做法是为每个产品型号建一个参数文件里面存所有可调参数包括预处理参数、分割参数、特征筛选阈值、光源亮度、相机曝光等。换型时操作员选择产品型号系统自动加载对应的参数文件不需要手动调参。参数文件的格式可以用 JSON 或 INIHalcon 里可以用read_tuple和write_tuple读写。参数文件要版本管理每次修改都要记录修改人、修改时间、修改原因。如果换型后检测效果不对可以快速回滚到之前的版本。我个人的经验是参数文件要分两级。一级是通用参数所有产品共用比如图像预处理参数二级是产品专用参数每个产品不同比如特征筛选阈值。这样换型时只需要加载产品专用参数通用参数不用动减少出错概率。6.4 系统集成与产线通信的注意事项检测系统最终要集成到产线和 PLC、上位机、执行机构通信。通信方式通常有串口、以太网、数字 IO 等。串口通信简单可靠适合数据量小的场景。Halcon 里可以用open_serial打开串口用write_serial发送数据。注意串口通信要约定好波特率、数据位、停止位、校验位双方必须一致。以太网通信适合数据量大的场景。Halcon 里可以用open_socket打开 socket用send_data发送数据。注意以太网通信要处理粘包和断线重连建议用 TCP 而不是 UDP因为 TCP 有重传机制可靠性更高。数字 IO 适合简单的合格/不合格信号。Halcon 里可以用open_io打开 IO 设备用write_io输出信号。注意数字 IO 的响应时间要满足产线节拍如果 IO 响应太慢会导致执行机构动作滞后。提示系统集成时一定要做联调测试。我见过很多项目算法在实验室跑得很好一到产线就出问题原因是通信延迟、IO 响应慢、电磁干扰等。联调测试要模拟真实产线环境包括最差工况比如光照变化、震动、温度变化等。只有联调通过了才能算真正交付。7. 个人实操体会与后续扩展方向7.1 调试过程中踩过的坑第一个坑是过度依赖例程参数。刚开始的时候我直接把例程里的参数搬到实际项目结果检测效果很差。后来才明白例程里的参数是针对特定图像调的换一批图像就要重新调。参数没有万能值只有针对具体场景的最优值。第二个坑是忽略光源的重要性。有段时间我一直在调算法参数怎么调都不满意后来换了一个光源问题迎刃而解。算法能做的有限如果成像质量不行再好的算法也救不回来。光源是机器视觉的地基地基不稳上面盖什么都是歪的。第三个坑是序列长度取太长。我以为序列越长背景估计越准结果发现序列太长反而引入误差因为产线有缓慢漂移长序列的背景估计跟不上漂移。后来把序列长度从 10 帧降到 5 帧效果反而更好。第四个坑是特征筛选阈值设太死。我一开始把阈值设得很精确结果换一批产品就误报。后来改成统计法用合格品的分布来自动计算阈值鲁棒性好了很多。7.2 这个框架还能用在哪些场景ball_seq.hdev的框架不仅适用于球焊检测还适用于很多类似场景。只要是小尺寸、高反光、序列图像的检测任务都可以套用这个框架。比如芯片引脚检测引脚也是小尺寸金属目标高反光可以用序列图像增强信号用动态阈值分割用特征筛选排除噪声。区别在于引脚是长条形特征筛选要用长宽比而不是圆度。比如液晶屏亮点检测亮点是小尺寸高亮目标背景是均匀的液晶屏可以用序列图像做背景估计用差分增强亮点用特征筛选排除灰尘。区别在于亮点是圆形特征筛选可以用面积和圆度。比如精密零件表面缺陷检测缺陷是小尺寸暗目标或亮目标背景是金属表面可以用序列图像做背景估计用差分增强缺陷用特征筛选排除纹理。区别在于缺陷形状不规则特征筛选要用面积、长宽比、灰度对比度等多个特征。我个人的体会是框架可以复用参数必须重调。不要指望一套参数打天下每个场景都要重新调参。但有了框架调参就有章可循不会像无头苍蝇一样乱撞。7.3 后续可以尝试的优化方向第一个方向是深度学习。Halcon 从 18.11 版本开始支持深度学习可以用read_dl_classifier加载预训练模型用apply_dl_classifier做分类。对于焊球检测可以用深度学习做缺陷分类把传统算法提取的特征作为输入或者直接用图像作为输入。深度学习的优势是鲁棒性强缺点是训练需要大量标注数据而且推理速度可能比传统算法慢。第二个方向是3D 检测。焊球是三维目标2D 图像只能看到投影无法判断球体高度和体积。如果用 3D 相机或激光轮廓仪可以获取焊球的三维点云用xyz_to_object_model_3d转成 3D 模型用fit_primitives_object_model_3d拟合球体得到球径、球心、球度等三维特征。3D 检测的优势是信息更全面缺点是设备成本高处理速度慢。第三个方向是在线学习。产线换型频繁每次换型都要重新调参效率低。如果系统能在线学习根据操作员的反馈自动调整参数就能减少调参时间。具体做法是操作员标记误报和漏报系统根据标记调整阈值逐步逼近最优参数。在线学习的难点是保证稳定性避免参数震荡。第四个方向是边缘计算。把检测算法部署到边缘设备比如工业相机内置处理器或嵌入式工控机减少数据传输延迟提高响应速度。边缘计算的难点是算力有限需要优化算法减少计算量和内存占用。这些方向我都在陆续尝试有些已经用在项目里有些还在实验阶段。不管哪个方向核心都是提升检测稳定性、降低调参成本、适应更多场景。ball_seq.hdev这个例程给了我一个很好的起点后续的扩展都是在这个框架上做加法。
阅读完成 · 觉得有帮助?