☰
Halcon IC外观缺陷检测:光学、定位与亚像素测量实践
2026/11/3 3:57:44 网站建设 项目流程

1. IC缺陷检测的需求拆解:先明确检什么,再想怎么检

IC检查这个活,在机器视觉圈子里算是老命题了,但真要把一条产线上的缺陷检测用 Halcon 做稳,坑远比想象的多。很多人拿到项目第一反应是打开 HDevelop 拖几个算子,threshold一跑、connection一连、select_shape一筛,界面上花花绿绿看着挺像回事,结果到了产线一跑,误报率百分之十几,操作员一天点几百次确认,项目就废了。问题不在算子用得对不对,而在于一开始就没把"要检什么"拆到算子级别。

我这里的"IC检查",指的是芯片封装完成之后、贴装之前的那道外观检测工序,覆盖引脚、本体、印字、焊球这几个面。不同封装形态侧重点完全不一样:小外形封装重点在引脚共面性和是否缺脚,方形扁平封装重点在本体崩边和丝印,球栅阵列封装的重点全在焊球阵列上,少球、球径偏小、球心偏移、相邻桥连,这几类缺陷的判定逻辑跟引脚完全不是一套。所以第一步永远是把检测项列成表,写清每一项的判定标准、公差、对应的物理尺寸,然后再倒推需要什么样的光学方案和算法路子。

1.1 一张检测项清单是所有工作的起点

我习惯在动手写第一行代码之前,先和工艺的人坐一起把这张表填完。表格里至少要落到五个字段:检测项名称、缺陷类型、判定标准(带具体数值)、对应的图像特征、以及"这一项误判了会怎样"。最后一个字段看起来多余,实际上是后面定阈值松紧的唯一依据。

举个实际的例子,同样是引脚偏移,偏移 0.02mm 和偏移 0.05mm 在图像上可能只差三四个像素,但如果这一项卡的是"引脚是否超出焊盘范围",那它的容忍度其实很高,属于次要项;而如果是"引脚间距是否符合贴装要求",那就成了关键项,宁可误报也不能漏。这种优先级排序直接决定了后面select_shape的区间怎么开、边缘阈值取多少、要不要上亚像素测量。

下面是我常用的一张简化清单模板,实际项目里字段会更多,但骨架就是这个:

检测项典型缺陷判定依据图像特征优先级
引脚数量缺脚、多脚数量与标准值一致亮区连通域个数关键
引脚形态弯曲、变形、压伤引脚长度/角度公差矩形拟合的长宽与角度关键
引脚共面翘脚、塌脚尖端连线偏离度亚像素边缘拟合直线关键
本体外观崩边、裂纹、气孔缺陷面积与对比度局部灰度异常重要
丝印字符漏印、错印、模糊字符一致性模板灰度相关性重要
焊球少球、球径异常、桥连面积/圆度/中心距圆形连通域特征关键

把这张表填完,你会发现有些项目根本不需要复杂的算法,纯 blob 分析就够了;有些项目看着简单,但图像特征极弱,得上亚像素或者多光源融合。这就是拆解的价值——它把"做个缺陷检测"这种模糊需求,变成了"在什么图像上,用什么特征,判什么条件"的具体工程问题。

1.2 为什么这个场景里 Halcon 的性价比高

做 IC 外观检测的软件平台不少,开源的有 OpenCV,商用的也有若干家。选 Halcon 的理由,落到实际使用上大概是这三条。

第一条是形状匹配的稳定性。IC 在料带或者托盘里的位置姿态每次都不一样,旋转几度、平移几百微米是常态。Halcon 的create_shape_model系列在有部分遮挡、光照轻微变化的情况下依然能给出稳定的位姿,而且带缩放和 anisotropic 变体的版本对镜头畸变和生产批次差异有不错的容忍度。这一块自己用 OpenCV 的matchTemplate或者特征点匹配去拼,调参工作量会大很多。

第二条是测量算子成熟。引脚共面性、球径这类需要亚像素精度的项,Halcon 的gen_measure_rectangle2配measure_pairs是现成的卡尺工具,边缘提取的Sigma、Threshold、Transition三个参数语义清晰,出来的InterDistance、IntraDistance直接能用。同样的东西用 OpenCV 得自己做一维灰度投影加亚像素插值,写出来不一定有它稳。

第三条是生态完整。从相机采集、标定、模板匹配、blob 分析、亚像素测量、OCR 到结果输出和界面显示,一条链路在一个环境里全走完,不用在几个库之间倒腾图像格式。对一个要交付给产线、后期还得有人维护的项目来说,这个价值比单纯算子的性能高低更重要。

当然也不是没有代价。Halcon 的运行时授权按模块卖,测量、匹配、OCR 分属不同包,预算紧张的时候得算清楚到底需要哪几个;另外代码可读性一般,一堆算子堆在一起,不加注释三个月后自己都看不懂。所以我在项目里会强制要求:每一个检测函数开头写清楚"输入图像什么状态、ROI 怎么来的、判定逻辑是什么、阈值为什么是这个值"。

1.3 节拍、误判率、检出率的三方拉扯

这三者永远在打架,而且没有全局最优解,只有针对具体产线的折中。一条产线要求 300ms 出一个结果,那图像分辨率、算法复杂度、是否上多光源多次曝光,全都要跟着压缩。这时候如果工艺那边再要求"0.03mm 的崩边必须检出",你就得回去跟他们谈:以 300ms 的节拍,用当前视野,那个尺寸的崩边只有两三个像素,图像噪声的水平跟它一个量级,检出率不可能做到 99%。

我的处理方式是把矛盾摆到桌面上量化。先算出在当前光学方案下,目标缺陷尺寸占几个像素,然后给出这个像素数对应的理论检出率区间(经验上,缺陷对比度明显的情况下,覆盖 5 个像素以上基本能稳;3 到 5 个像素需要仔细调阈值,误报会上升;3 个像素以下基本靠运气)。拿着这个数据去谈,要么放宽判定标准,要么加预算换更高分辨率的相机和更好的镜头,要么砍掉非关键检测项保节拍。这个过程走完,后面所有技术决策都有了依据,不会出现"做完了才发现根本达不到要求"的情况。

2. 光学方案与采图:检测上限在按下快门那一刻就定死了

这句话我逢人就说:图像质量决定了检测能力的上限,算法只是尽量逼近这个上限。一张对比度差、边缘糊、光照不均的图,后面用再花哨的算子也救不回来。IC 检测尤其如此,因为它的缺陷尺度普遍在几十微米量级,而且很多缺陷(比如封装体的细微裂纹、引脚镀层的轻微变色)本身就是低对比度的,全靠打光把特征"顶"出来。

2.1 打光方式决定了算法能有多简单

IC 检测里常用的几种打光方式,效果差异很大,我在不同检测项上的选择大致是这样的:

打光方式适用检测项效果特点注意事项
同轴光本体表面、丝印字符镜面反射均匀,字符对比度高表面划伤会被弱化,容易漏检
环形光(低角度)引脚轮廓、本体边缘边缘增强明显,轮廓清晰引脚镀层反光差异会导致亮度不均
背光(平行光)引脚外形尺寸、缺脚轮廓为纯黑剪影,尺寸测量最准只能测外轮廓,内部缺陷全看不见
暗场裂纹、划伤、异物微小凹凸在暗背景下发亮对表面洁净度敏感,灰尘也会亮
圆顶光字符、曲面本体无影均匀照明,字符最稳对比度平,弱缺陷会被抹平

实际项目里很少只用一种。做引脚外形测量,背光是最优解,剪影轮廓的边缘最锐利,亚像素拟合精度最高;但如果同时要检本体崩边和字符,就得再加一路同轴光或者圆顶光,分两次曝光。多光源切换会吃节拍,所以通常的做法是:把能合并的检测项合并到一路光下,实在合并不了的才分时曝光。

这里有个容易踩的坑:很多人为了省事,用一路环形光想把所有项都覆盖了,结果引脚边缘因为镀层反光不均,边缘阈值怎么调都有几个引脚的灰度值差一截,measure_pairs在那几个位置直接找不到边沿。这种情况不是算法问题,是光源角度和引脚侧面反射方向不匹配。解决办法要么换更低角度的环形光让侧面进入阴影,要么加偏振片压掉镜面反射。加偏振片这个手段在金属引脚检测里特别好用,成本也不高,值得优先试。

2.2 像素精度怎么倒推相机和镜头

这一步必须有计算过程,不能拍脑袋选型。我按一个实际例子走一遍。

假设要检的器件是 8 脚小外形封装,本体尺寸约 5mm×4mm,含引脚的整体尺寸约 6mm×5mm,引脚宽度 0.4mm,引脚间距 0.5mm,最小需要检出的缺陷是引脚侧面的 0.05mm 缺口。产线要求视野内一次拍完一个器件,并留出定位余量。

第一步定视野。器件整体 6mm×5mm,四周各留 2mm 余量给定位和搬运误差,视野取 10mm×8mm。

第二步定像素精度。最小缺陷 0.05mm,按前面说的经验,希望它至少覆盖 5 个像素,则像素精度 ≤ 0.01mm/px。取 0.008mm/px 留点余量。

第三步算相机分辨率。视野 10mm 除以 0.008mm/px 得 1250px,8mm 除以 0.008 得 1000px。所以至少要 1250×1000 的传感器,选 200 万像素(1600×1200)刚好,选 500 万像素(2448×2048)则像素精度能到 10/2448≈0.0041mm/px,缺陷覆盖 12 个像素,非常宽裕。我的建议是只要预算允许就往高选,因为像素精度每提高一倍,后面阈值的调整空间就大一圈,误报率能明显下降。但也要注意,分辨率越高,单帧数据量越大,处理时间越长,得跟节拍一起权衡。

第四步定镜头。这里分两条路。用远心镜头的话,直接按放大倍率选:传感器尺寸除以放大倍率就是视野。比如 2/3 英寸传感器(8.8mm×6.6mm),配 0.8 倍远心,视野是 11mm×8.25mm,正好覆盖需求。远心镜头的优势是几乎没有透视误差,器件高度不一致时测量结果不受影响,做尺寸测量必须用它。用普通定焦镜头的话,按 f = 工作距离 × 传感器尺寸 / 视野 估算,比如工作距离 100mm、传感器短边 6.6mm、视野短边 8mm,则 f ≈ 100×6.6/8 = 82.5mm,选 75mm 或 85mm 定焦。普通镜头便宜,但边缘有畸变和透视,做高精度测量要加标定校正。

我的实际选择倾向是:只要涉及尺寸测量和共面性判定,一律上远心镜头,别省这个钱。远心镜头带来的测量稳定性提升,比在后面算法里加一堆畸变校正和补偿逻辑划算得多。

2.3 采图稳定性比图像质量更更重要

这句话有点绕,但确实是现场最真实的问题。实验室里调出来一张漂亮的图不难,难的是连续跑八小时,第一张和第一万张的灰度分布基本一致。影响稳定性的因素里,光源衰减、环境光串入、相机温度漂移、镜头污染排在前四位。

对策上,我一般会做这几件事。一是光源用恒流驱动而不是恒压,避免亮度随温度漂移;二是相机镜头加遮光罩,把环境光彻底挡掉,这一条在车间有窗户的工位上尤其关键,下午阳光角度变化能让图像整体亮度飘一截;三是每次开机或者换批次时跑一次基准图像采集,把当前图像和标准图的平均灰度、对比度做个比对,偏差超过设定阈值就报警,让人先去擦镜头或者查光源,而不是让算法在坏图上硬判;四是在算法里加一层自适应归一化,用scale_image或者emphasize之类的算子把整体亮度和对比度拉到标准范围,这一层能吸收掉大部分缓慢漂移。

这里有个实操心得值得单独说:基准图像不要只存一张,建议存一批(比如 20 张不同批次、不同时段的合格品图像),每次比对时算当前图像与这批基准的最小距离,比跟单张比更鲁棒。我在一个项目里就是因为只存了一张基准图,那张图恰好是光源刚换的时候拍的,亮度偏高,后面所有正常品都被判成"光照异常",折腾了半天才找到原因。

3. 算法骨架:定位、跟随、分区域检测

整个检测流程的骨架其实很朴素:找到器件在哪、把器件摆正、在标准坐标系下按区域逐项检测、汇总结果。听起来简单,但每一步的实现方式选择会显著影响最终的稳定性和可维护性。

3.1 用形状匹配做基准定位

定位这一步的目的不是"找到器件",而是"得到一个准确的位姿参数",让后面所有的 ROI 都能跟着器件动。所以对定位的要求是精度高、重复性好,而不是速度快到极致。

模板的选择上,我一般用器件的本体轮廓或者一个特征明显的角。如果本体是纯色无纹理的,轮廓匹配(create_shape_model)比灰度匹配稳,因为它依赖的是边缘梯度而不是绝对灰度,对光照变化天然免疫。模板区域要包含足够多的边缘信息,太小的模板(比如只用一个小圆点)在噪声大的时候容易匹配飘。

参数上,角度范围按实际来定。料带里的器件通常旋转不超过 ±5 度,转盘上可能到 ±180 度。角度范围每扩大一倍,金字塔层数和搜索时间都会涨,所以不要图省事直接开 ±180。缩放同理,如果相机和工件的距离固定,缩放范围给 0.9 到 1.1 就够,别开太大,开了就容易匹配到错误的相似区域。

MinScore这个参数是现场调机时最常动的。默认 0.5 偏松,容易在空料位或者异物上误匹配;设到 0.7 到 0.8 之间通常比较稳。NumMatches设 1,因为一个视野里只有一个器件,多的都是误匹配。Greediness设 0.9 是速度和稳定性的常用折中,如果发现偶尔匹配不上,先降到 0.7 试试,还不行再查图像质量和模板质量。

* 生成模板:在标准姿态的合格品图像上截取本体区域 read_image (ImageModel, 'ic_template_01') rgb1_to_gray (ImageModel, GrayModel) gen_rectangle1 (ROIModel, 320, 410, 720, 910) reduce_domain (GrayModel, ROIModel, ImageReduced) create_shape_model (ImageReduced, 'auto', -0.09, 0.17, 'auto', 'auto', \ 'use_polarity', 'auto', 'auto', ModelID) * 在线匹配 find_shape_model (Image, ModelID, -0.09, 0.17, 0.75, 1, 0.5, \ 'least_squares', 0, 0.9, Row, Column, Angle, Score)

find_shape_model最后两个参数是SubPixel和NumLevels,这里给了'least_squares'和 0,意思是使用最小二乘做亚像素精化、金字塔层数自动。亚像素精化这一项建议一定开着,它能让位姿精度从整像素提升到 0.1 像素量级,对后续 ROI 跟随的准确性帮助很大。

3.2 仿射变换把 ROI 焊死在零件坐标系上

拿到位姿之后有两种处理方式:一是把 ROI 坐标跟着位姿变换过去,二是把图像整体反向变换回标准姿态。我强烈推荐第二种。

原因是这样的:如果用第一种,每个 ROI 的坐标都要经过affine_trans_region或者affine_trans_pixel变换,代码里到处是变换调用,调试的时候想在 HDevelop 里看一眼某个 ROI 到底在哪,还得再还原一次,非常痛苦。而且有些算子(比如gen_measure_rectangle2)需要的是矩形参数而不是 region,变换之后还得重新提取,很容易出错。

用第二种就清爽多了:一次性把整幅图像用affine_trans_image摆正到标准姿态,之后所有 ROI 的坐标都是常数,全部按模板训练时的坐标系来写,调试、维护、交接都直观。代价是插值会带来轻微模糊,所以插值方式选'bilinear'就够,不要用'cubic'(更慢且对边缘锐度反而没好处)。另外要注意图像边界会被填成黑色,所以 ROI 要保证在有效区域内。

* 把当前图像摆正到模板姿态 vector_angle_to_rigid (RowModel, ColModel, 0, Row, Column, Angle, HomMat2D) affine_trans_image (Image, ImageAligned, HomMat2D, 'bilinear', 'false')

vector_angle_to_rigid这里把模板的中心点和角度 0 映射到当前匹配到的位置和角度,得到的变换就是"把当前图像变回标准姿态"的逆变换。这个写法在实际项目里用得最多,注意第一个角度参数固定写 0(表示模板姿态就是标准姿态),不要写成AngleModel,否则方向会反。

摆正之后,所有 ROI 都是固定的矩形或者圆形,写死在代码里或者存到一个配置文件里。这一步做完,后面的检测逻辑就跟定位彻底解耦了,器件再怎么转、怎么偏,到检测函数手上的永远是一张标准姿态的图。

3.3 代码骨架的组织方式

HDevelop 里最容易被写成一坨的就是这一层。我的习惯是按检测项拆成一串 procedure,主流程只负责编排,每个 procedure 输入摆正后的图像,输出该检测项的结果(合格/不合格、缺陷数量、缺陷位置、测量值)。这样有几个好处:每个函数的输入输出明确,单独调试的时候可以直接给一张图进去看结果;加减检测项不影响其他逻辑;后期换成 C# 或者 C++ 封装的时候,一个 procedure 对应一个类方法,映射关系清晰。

主流程大概长这样:

* 主流程示意 read_image (Image, ImagePath) rgb1_to_gray (Image, GrayImage) * 1. 光照质量校验 check_illumination (GrayImage, IllumOK) if (not IllumOK) return_result ('LightError', 0, []) return () endif * 2. 定位 find_shape_model (GrayImage, ModelID, -0.09, 0.17, 0.75, 1, 0.5, \ 'least_squares', 0, 0.9, Row, Column, Angle, Score) if (Score < 0.7) return_result ('LocateFail', 0, []) return () endif * 3. 摆正 vector_angle_to_rigid (RowModel, ColModel, 0, Row, Column, Angle, HomMat2D) affine_trans_image (GrayImage, ImageAligned, HomMat2D, 'bilinear', 'false') * 4. 分项检测 check_pin_count (ImageAligned, R1, N1) check_pin_shape (ImageAligned, R2, N2) check_pin_coplanar (ImageAligned, R3, N3) check_body_defect (ImageAligned, R4, N4) * 5. 汇总输出 results := [R1, R2, R3, R4] defects := [N1, N2, N3, N4] return_result ('OK', sum(defects), results)

注意第 2 步的失败处理。定位失分的时候不要硬往下跑,直接返回"定位失败"让上位机处理(重拍、报警、剔除),因为在错误位姿下做出来的所有检测结果都是垃圾,跑下去只会污染数据统计。同理,光照校验失败也要提前退出。这个"早退"的写法在产线上非常重要,否则你会收到一堆莫名其妙的误报,回头查半天发现是定位飘了。

4. 四类检测项的落地实现

这一段是全文最实在的部分,每一项我都会给出完整的思路和关键代码,参数取值说明理由。

4.1 引脚类缺陷:从 blob 到亚像素测量

引脚检测通常拆成三个子项:数量对不对、形态对不对、共面性好不好。

数量检测最简单,用背光图做阈值分割,连通域计数就行。但要注意阈值不能写死,用binary_threshold的'max_separability'自动求阈值更稳,或者用threshold后配合形态学清一下毛刺。引脚之间如果有反光粘连,connection之后会连成一个,这时候用opening_circle开一个半径略大于引脚间隙的核就能断开。

threshold (ImageAligned, Regions, 80, 255) connection (Regions, ConnectedRegions) * 按面积和矩形度筛引脚 select_shape (ConnectedRegions, PinRegions, ['area','rectangularity'], 'and', \ [800, 0.7], [99999, 1.0]) count_obj (PinRegions, PinCount)

rectangularity这个特征在这里很好用,引脚在背光下是细长矩形,矩形度接近 1;而异物、毛刺、粘连体的矩形度明显偏低。两个特征联合筛选比只用面积靠谱得多。

形态检测主要抓弯曲、压伤和长短不一。做法是对每个引脚 region 求最小外接矩形,拿长宽比和角度做判断,再单独检测引脚尖端的位置。

smallest_rectangle2 (PinRegions, Row, Column, Phi, Length1, Length2) * 引脚长宽比异常:弯曲或压伤的典型表现 Ratio := Length1 / Length2 * 极端引脚的尖端位置散乱:长短不一的典型表现

smallest_rectangle2输出的Phi是主轴角度,Length1、Length2是半长和半宽。这里有个细节:正常引脚的Phi应该高度一致(都接近水平或垂直),如果有某个引脚的Phi明显偏离,说明它弯了。判断阈值可以设为偏离均值超过 2 度就报警,具体值要看引脚长度和实际公差,长引脚对小角度更敏感。

共面性检测是最考验精度的。逻辑是把所有引脚的尖端拟合成一条直线,然后算每个尖端到这条直线的距离,超过阈值的就是翘脚或者塌脚。做法是先用gen_measure_rectangle2在每个引脚尖端的预期位置生成一个卡尺,用measure_pairs找边沿,得到亚像素精度的尖端坐标,再用fit_line_contour_xld或者最小二乘拟合直线,最后distance_pl算距离。

* 对每个引脚生成卡尺,Length1沿引脚方向,Length2覆盖引脚宽度 for Index := 1 to |PinRows| by 1 gen_measure_rectangle2 (PinRows[Index-1], PinCols[Index-1], 0, \ 40, 12, Width, Height, 'bilinear', MeasureHandle) measure_pairs (ImageAligned, MeasureHandle, 1.0, 30, 'all', 'all', \ RowEdgeFirst, ColEdgeFirst, AmpFirst, \ RowEdgeSecond, ColEdgeSecond, AmpSecond, \ IntraDist, InterDist) close_measure (MeasureHandle) * 取第一个上升沿作为引脚尖端 endfor

参数上,Sigma给 1.0 是常用起点,它的作用是高斯平滑,值越大越抗噪但边沿定位越钝;Threshold给 30 表示边沿幅度至少要 30 个灰度才认,这个值要根据实际对比度调,太低会把噪声当边沿,太高会漏掉弱边沿。Length1和Length2分别是卡尺的半长和半宽,沿引脚方向给 40 像素左右保证覆盖尖端区域,垂直方向给 12 像素大约覆盖引脚宽度的一半多一点,别给太宽,宽了会把旁边引脚也框进来。

提示:measure_pairs返回的是成对的边沿(暗-亮-暗),如果引脚在背光下是黑剪影,那么一个引脚会产生一对边沿,IntraDist就是引脚宽度。这个值可以直接当尺寸测量用,比从 blob 里算宽度精度高一个数量级。

4.2 本体外观:差分阈值加形态学清理

本体外观检测的核心难点是"缺陷"和"正常纹理"的区分。封装体表面通常有均匀的哑光质感,但模具留下的合模线、顶针印、轻微色差都是常见的正常特征,如果全当缺陷处理,误报会爆炸。

我的做法是先用dyn_threshold做局部对比度分割,而不是全局阈值。原因是本体的整体灰度受打光影响会有缓慢变化,全局阈值要么在某一边过严要么过松,而dyn_threshold是拿当前图像和它的平滑版本做比较,只保留局部灰度突变的区域,天然对整体亮度变化免疫。

* 局部差分:抓取局部灰度突变 mean_image (ImageAligned, ImageMean, 15, 15) dyn_threshold (ImageAligned, ImageMean, RegionDyn, 8, 'light') connection (RegionDyn, ConnectedRegions) * 形态学清理:开运算去小噪点,闭运算连断开的缺陷 opening_circle (ConnectedRegions, RegionOpening, 2.5) closing_circle (RegionOpening, RegionClosing, 5.5) * 特征筛选:面积 + 对比度 select_shape (RegionClosing, DefectRegions, 'area', 'and', 60, 99999)

mean_image的窗口大小是关键参数,它决定了"局部"的尺度。窗口给 15×15,意思是宽度小于 15 像素的灰度突变会被保留,大于的在差分时被算进背景。所以这个值要跟缺陷尺度挂钩:如果要抓的崩边最小 0.05mm,像素精度 0.005mm/px,那是 10 个像素,窗口给 21×21 到 31×31 比较合适,给太小会把纹理噪声也保留下来。dyn_threshold的Offset给 8 是经验值,对比度高的缺陷可以给到 15 甚至 20,宁严勿松,因为这一项漏检的后果比误报轻。

opening_circle的半径 2.5 是用来消掉孤立噪点的,一般给 1.5 到 3 之间。closing_circle半径 5.5 是用来把断裂的缺陷连起来的(比如一条细裂纹在二值图上可能断成几段),但这里要小心,闭运算半径太大会把两个相邻的独立缺陷连成一个,面积判断就不准了。我的经验是闭运算半径不要超过最小缺陷尺寸的一半。

另外,合模线这类规则性特征可以单独做屏蔽。做法是在模板训练阶段人工标注出这些区域,存成 region 文件,检测时用difference减掉。这一步看着土,但实际效果比调参数好得多。

4.3 印字字符:模板比对与文本读取两条路

字符检测要分两种需求:一种是判断"印得清不清楚",另一种是判断"印的内容对不对"。前者是质量检测,后者是内容识别,实现方式完全不同。

质量检测用模板比对就够了。在标准姿态下截取字符区域的模板图,检测时用create_shape_model或者直接做灰度相关性比对(reduce_domain后减均值再算相关系数)。相关性低于阈值就判"印字不良",比如漏印、模糊、偏移。这种做法速度快、不需要训练字符库,适合只关心"有没有印好"的场景。

如果用的是形状匹配,要注意字符的笔画边缘在模板里必须清晰,所以打光上要保证字符对比度足够。用圆顶光加同轴光组合,字符的黑白对比能拉到 100 个灰度以上,模板相关性判断就很稳。反之如果字符浅、背景有纹理,匹配分数会在正常品上也飘,那就要先做图像增强,用emphasize或者scale_image把对比度拉起来。

内容识别就要上 OCR 了。Halcon 的做法是先分割出单个字符,再和字符集比对。这里的关键是字符分割要稳,因为印刷字符经常粘连或者断裂。

* 字符分割 threshold (CharImage, CharRegions, 0, 100) connection (CharRegions, ConnectedChars) * 形态学修正断裂 closing_rectangle1 (ConnectedChars, CharClosed, 2, 2) * 按列坐标排序,保证读序正确 sort_region (CharClosed, CharSorted, 'character', 'true', 'column')

sort_region这一步千万别漏。字符分割出来的顺序是按扫描顺序(从上到下、从左到右)的,对于单行字符可能没问题,但如果有两行或者字符高度不一致,顺序就会乱,读出来的字符串完全对不上。用'character'模式配合'column'方向可以按阅读顺序排好。

识别环节用create_text_model_reader配合find_text,或者用read_ocr加载现成的分类器。实际项目里我更倾向自己训练一个小型的 MLP 或者 CNN 分类器,用几百张样本训练,针对自己的字体调过之后准确率比通用分类器高不少,尤其是数字和字母容易混淆的场景。

注意:字符识别不要追求 100% 正确率,这在工业上做不到。合理的做法是设一个置信度阈值,低于阈值的判为"无法识别"交人工复核,而不是强行输出一个结果。我见过一个项目把置信度阈值设成 0 强制输出,结果把所有"8"都读成"B",批量误判。

4.4 焊球阵列:面积、圆度、位置三重约束

球栅阵列封装的焊球检测,逻辑跟引脚完全不同,它的核心是阵列结构。先要确定阵列的行列数和理想位置,然后把实际检测到的球跟理想位置一一对应,这样才能检出"少球"和"错位"。

第一步是分割。焊球在侧光或者暗场下是明亮的圆形,用threshold或者binary_threshold能分出来。分割后connection得到每个球的 region,然后直接area_center拿面积和中心。

threshold (ImageAligned, BallRegions, 120, 255) connection (BallRegions, ConnectedBalls) select_shape (ConnectedBalls, Balls, 'area', 'and', 300, 5000) * 面积、圆度、中心 area_center (Balls, BallArea, BallRow, BallCol) circularity (Balls, BallCircularity)

第二步是位置匹配。这一步是整个焊球检测的关键。做法是用所有检测到的球心拟合出一个规则网格:先算行间距和列间距的中位数,再以中位数作为标准间距,把每个球心投影到最近的理论格点上,然后统计每个格点上有没有球、球心偏移多少。

判定项计算方法典型阈值说明
少球格点上有球心映射的数量 < 理论数量缺少任意一个即报警最关键项
多球/桥连一个格点映射到多个球心数量 > 1 即报警通常伴随面积异常
球径异常面积换算等效直径与标准值比较偏差 > 15% 报警阈值随工艺能力调
球心偏移球心到理论格点的距离> 球径的 25% 报警反映贴装精度
球体变形圆度或长短轴比圆度 < 0.85 报警反映焊接质量

这里有个细节要注意:理论格点不是靠人工输入的,而是从检测结果自己拟合出来的。这么做的好处是对不同批次、不同器件的适应性好,坏处是如果少球太多(超过总数的 20%),拟合出来的网格就会偏。所以我会加一个保护:先假设理想网格是等间距的,用所有球心的行列坐标做聚类,取两个最大的聚类中心作为基准行列,再推算完整网格。如果拟合失败(比如检测到的球太少),直接报"检测异常"而不是硬判。

圆度这个特征在焊球检测里特别有用。桥连的两个球在二值图上是一个花生形,圆度会掉到 0.7 以下,面积却是正常球的两倍,两个特征一联合就能准确定位。而单个球如果有凹陷或者变形,面积可能正常但圆度偏低,这时候就要靠圆度来抓。

5. 现场调机常见问题与排查

算法在实验室跑通只是走完了三分之一,剩下的三分之二全在现场。这一段我把这些年踩过的坑整理出来。

5.1 误报占比过高怎么系统性地拆解

误报是现场最头疼的问题,也是最容易用错方法解决的。很多人的第一反应是"把阈值调松",结果误报少了,漏检上来了,过两天客户投诉漏出货,又得调回去,来回折腾。

正确的做法是把误报拆成几类分别处理。我的分法是:定位类误报、光照类误报、参数类误报、真实缺陷误报。

定位类误报的特征是误报位置随机分布,且经常伴随匹配分数偏低。排查方法是把匹配分数和误报图像一起记录下来,看误报时的Score分布。如果误报集中在低分区间,说明是定位飘了,要从模板和打光入手。常见原因是模板区域包含了会变化的特征(比如批次不同的印字),或者模板边缘太靠近图像边界导致部分遮挡。解决办法是换一个稳定的模板区域,通常是本体的几何轮廓而不是表面图案。

光照类误报的特征是误报在时间上有周期性或者跟环境相关(比如每天下午集中出现)。排查方法是记录每张图的平均灰度和标准差,画个趋势图看有没有漂移。这类误报靠调算法参数解决不了,必须从光源恒流、遮光、定期校准入手。

参数类误报的特征是误报位置规律,集中在图像的某个区域。这通常是因为该区域的光照不均匀或者 ROI 边界切到了正常特征。解决办法是把 ROI 往里收一点,或者对这一块单独用一套阈值(分区域参数)。分区域参数这个做法在项目里很常见,但要克制,区域分得越多,维护成本越高,交接给别人的时候没人搞得清楚。

真实缺陷误报这个最有意思,看起来是误报,实际上是工艺真的有波动,只是没到判定标准。这种情况要跟工艺的人一起看图像,往往能发现一些被忽略的渐变趋势,比如某台设备的引脚镀层就是偏暗一点。这时候正确的做法是把这个批次单独建一个基准,而不是简单放宽全局阈值。

5.2 节拍压不下来先看哪里

节拍不够的时候,优化顺序很重要,顺序错了会做很多无用功。

第一顺位永远是减少图像处理面积。整个视野 2448×2048,但你真正关心的只有器件所在的 1000×800 区域,那就先reduce_domain把无关区域裁掉再处理。这一条经常能省 50% 以上的时间,而且完全不影响精度。

第二顺位是降低金字塔层数和搜索范围。find_shape_model的时间跟角度范围、缩放范围、金字塔层数强相关。如果实测发现匹配总是落在很小的角度范围内,就把范围收窄。这一条能省 30% 左右。

第三顺位是合并重复计算。很多项目里每个检测函数都自己做一次mean_image或者threshold,重复劳动。把这些公共的预处理提到主流程做一次,结果传下去,又能省一截。

第四顺位才是优化算法本身。比如把connection换成基于行程的快速分割,或者把亚像素测量限定在必要的位置。这一条收益往往不如前面三条大,但工作量最大,所以放最后。

提示:优化之前一定先做时间测量,用count_seconds把每一段的耗时打出来存到日志里。我见过太多凭感觉优化的案例,改了半天发现瓶颈在一个完全没想到的地方,比如图像显示的dev_display调用。

5.3 问题速查表

下面这张表是我自己在项目里用的,遇到问题先查一遍,能省不少时间。

现象可能原因排查动作解决方向
匹配分数整体偏低打光变化或模板质量差看分数分布和图像亮度换模板区域、加光照归一化
匹配偶尔跳到错误位置MinScore 太松或存在相似结构打印误匹配时的位姿提高 MinScore、缩小角度范围
引脚边沿找不到卡尺位置偏、对比度不足单独显示卡尺和边沿点调 Length2、降低 Threshold
本体缺陷误报多差分窗口太小、纹理被当缺陷看误报区域是否集中在特定纹理加大 mean_image 窗口、屏蔽区域
字符识别率低字符粘连或分割顺序乱显示分割结果加形态学处理、用 sort_region 排序
焊球数量对不上阈值把暗球漏掉或把反光当球看二值图调阈值、加面积下限
长时间运行后误报增多光源衰减、镜头污染比对当前图和基准图亮度清洁、校准、加漂移报警
换批次后大量误报物料外观差异超出模板容忍度对比新旧批次图像分批次建模板、扩大缩放范围

这张表不是穷举,但它覆盖了八成以上的现场问题。用的时候有个技巧:先从"现象"这一列找到最接近的,然后按"排查动作"实际做一遍,不要跳过直接看解决方向。因为同样一个现象可能有不同原因,直接套解决方案很容易南辕北辙。

6. 上线之后还得守着的几件事

项目验收不代表结束,恰恰是另一段工作的开始。这一段说说上线之后必须做的维护动作,这些内容通常不会写在方案书里,但决定了这套系统能活多久。

6.1 参数固化与版本管理

所有阈值、ROI 坐标、模板路径、相机参数,必须全部外置到配置文件里,不能写死在代码里。这一点不是洁癖,是实战教训:产线上换一批物料,可能需要微调三四个阈值,如果这些值散落在十几个 procedure 里,改一次要半小时,还容易漏。放在配置文件里,改一行重载就行。

配置文件的格式我一般用 JSON 或者 INI,结构按检测项分组。每一组里除了参数值,还要记一个version和updated_by,方便回溯。模板文件本身也要版本化,命名带上日期和批次号,不要用model.shm这种通用名,改天换了模板都不知道哪个是最新的。

版本管理还有一层意思是"回滚能力"。每次上产线调整参数前,先把当前配置备份一份,命名带时间戳。出了问题五分钟内能恢复到上一个可用状态,这个能力比什么都值钱。我在一个项目里就是因为没做这个,某次调参之后误报暴增,又没人记得改之前的原值是什么,硬是花了两天才恢复。

6.2 样本回流和定期回归

每次误报和漏检,都要把图像存下来。存的时候连同当时的参数配置一起存,这样后面复现的时候环境是完全一致的。这些图像积累起来就是最好的测试集。

回归测试的做法是:每次改完参数,把积累的样本集整个跑一遍,统计误报率和漏检率。样本集里要专门保留那些"边界情况"——临界尺寸的缺陷、光照偏暗的图、有异物的图。一个健康的样本集应该包含合格品、各类缺陷品、以及一批"人眼也要犹豫一下"的疑难样本。

我个人的经验是,样本集到 500 张左右的时候,回归结果才比较有参考价值;到 2000 张以上,基本上能预测上线后的表现了。如果样本集里某类缺陷只有两三张,那这一类在产线上的检出率一定是不可信的,要专门去补采。

提示:样本采集不要只在调试阶段做。上线后每个月定期抽一批图像入库,尤其是季节变化、设备大修、换批次这些时间点。我遇到过一批误报是夏天车间温度高了之后相机噪声增大导致的,如果没在夏天采过样,这个问题永远发现不了。

最后说个实际操作中的体会。这套流程里最容易出错的从来不是算法,而是需求沟通和变更管理。检测项加一个、判定标准松一点、换个物料试试,这些口头上的"小改动"如果不落到文档和配置里,三个月后整个系统就没人敢动了。所以我在每个项目里都会坚持一件事:任何参数变更必须留记录,任何检测项变更必须更新检测项清单。看着繁琐,但这是让一套视觉系统能活过三年的唯一办法。

另外一个我觉得挺值的小技巧:在 HDevelop 里给每个 procedure 加一个调试开关,打开的时候把中间结果(二值图、ROI、拟合线、测量点)全部显示出来并单独存图到指定目录。现场出问题的时候,让人把那个目录的图打包发过来,不在现场也能定位问题。这个开关平时关着不影响节拍,出事的时候价值巨大。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询