资讯详情 Halcon IC外观缺陷检测:光学、定位与亚像素测量实践
📅 2026/11/3 3:58:03
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 得 1250px8mm 除以 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、拟合线、测量点全部显示出来并单独存图到指定目录。现场出问题的时候让人把那个目录的图打包发过来不在现场也能定位问题。这个开关平时关着不影响节拍出事的时候价值巨大。