Halcon工业质检实战:尺寸测量、缺陷检测等5个核心案例
发布时间:2026/9/16 19:34:51来源:尧图网络
1. 为什么不直接上深度学习聊聊Halcon在工业质检里的真实定位先回应一个我经常被问的问题既然深度学习这么火为什么还要用Halcon做缺陷检测和尺寸测量直接上神经网络难道不香吗答案是产线不等人算法落地不只看准确率。我亲眼见过一个采购了高配GPU服务器的质检项目深度学习模型在验证集上跑出了99.7%的准确率结果一到现场就翻车。原因很简单过杀率太高产线上的好工件被误判成不良品每误杀一个就要人工复判反而拖慢了整个节拍。更麻烦的是你要说服现场工艺人员去接受一个说不出具体为什么判废的黑盒模型太难了。Halcon这类传统机器视觉方案的优势在三个地方可解释性强、实时性稳定、开发周期短。一个成熟的尺寸测量算法从搭建到调通几天时间就能搞定缺陷检测类的只要打光方案稳定半个月内可以交付。这对绝大多数中小批量产线的质检需求来说已经足够了。深度学习不是万能药传统算法也不是落后技术关键看怎么选、怎么用。这篇文章我打算用5个真实场景下的案例把Halcon最常见的应用方式串一遍包括尺寸测量、表面缺陷检测、模板匹配定位、Blob分析、OCR识别。每个案例都会给出完整的Halcon代码设计思路和关键参数说明保证你能照着思路直接改造落地。适合刚接触机器视觉的工程师、正在评估Halcon的自动化设备团队以及那些想在内部项目里快速验证视觉方案的硬件研发人员。2. 案例一一维测量——电池极耳间距检测一个measure_pos吃遍天2.1 场景需求和痛点锂电行业有个非常常见的检测项极耳间距测量。电芯的极耳是焊接上去的金属薄片间距一旦超差后工序焊接就可能直接报废。这类测量用卡尺或千分尺也能做但产线节拍快、数量大人工检测既不现实也不稳定。我在这个场景里选的技术路线是Halcon的一维测量工具。理由很简单极耳边缘在图像里是直线型的边缘一维测量本质上是沿着一条采样路径对灰度剖面做求导找到边缘点的位置再用像素当量换算成物理尺寸。2.2 像素当量怎么算决定你的测量精度天花板做尺寸测量之前必须先做像素标定。所谓像素当量就是每个像素对应多少毫米。计算方式分两种已知视野用标准量块或已知尺寸的工件拍一张图量出目标在图像里占了多少个像素用物理尺寸除以像素数。标定板标定用Halcon自带的标定助手或者find_calib_object算子处理标定板图像输出相机内参和畸变系数。很多新手会忽略镜头畸变直接用图像角落的像素当量去计算全场尺寸测大尺寸工件时误差会非常大。这里我建议如果被测尺寸在视野中心附近畸变影响通常在0.02mm以内但如果你要测的是全视野边缘的位置就必须先做畸变校正用change_radial_distortion_image算子对图像做重映射。2.3 一维测量的完整Halcon实现思路我看到很多教程会直接写measure_pos但没有讲清楚ROI的生成方式。实际项目中你不可能每次都在固定位置测量因为工件在传送带上有轻微的位置偏移。正确的做法是先用形状匹配定位工件整体位置。根据匹配结果生成测量ROI的区域。在ROI内执行一维测量。核心测量代码逻辑如下* 生成测量矩形中心为Row, Col角度Phi gen_measure_rectangle2 (Row, Col, Phi, Length1, Length2, Width, Height, Interpolation, MeasureHandle) * 执行边缘测量 measure_pos (Image, MeasureHandle, Sigma, Threshold, Transition, Select, RowEdge, ColumnEdge, Amplitude, Distance) * 计算两个边缘点之间的距离 distance_pp (RowEdge[0], ColumnEdge[0], RowEdge[1], ColumnEdge[1], Distance)这里面的关键参数Sigma高斯平滑系数越大对噪声抑制越强但边缘定位精度会下降。经验值一般是0.8到1.5之间。Threshold边缘灰度梯度阈值要大于图像中背景纹理和噪声产生的梯度幅值。可以先用amplitude输出值观察一下再定阈值。Transition指定检测正极性还是负极性边缘极耳边缘一般是白到黑或黑到白你根据打光方式决定。Select选择是检测第一个、最后一个还是所有边缘点。实测下来在500万像素相机、视野约40mm的配置下极耳间距测量的重复性误差能做到±0.01mm以内完全满足工艺要求。2.4 一维测量的进阶用法卡尺循环测量多个特征如果检测对象是异形工件一条测量路径不够用怎么办我通常的做法是写一个卡尺工具函数传入多个ROI参数循环执行并输出结果数组。伪代码如下* 定义多组测量矩形参数 MeasureParams : [Row1, Col1, Phi1, Length1_1, Length1_2, Row2, Col2, Phi2, Length2_1, Length2_2] for Index : 0 to |RowList| - 1 by 1 gen_measure_rectangle2 (RowList[Index], ColList[Index], PhiList[Index], Length1List[Index], Length2List[Index], Width, Height, nearest_neighbor, MeasureHandle) measure_pos (Image, MeasureHandle, 1.0, 30, all, all, RowEdge, ColumnEdge, Amplitude, Distance) * 取两侧边缘距离 distance_pp (RowEdge[0], ColumnEdge[0], RowEdge[|RowEdge|-1], ColumnEdge[|RowEdge|-1], WidthValue) Result[Index] : WidthValue close_measure (MeasureHandle) endfor这里有个非常值得说的细节gen_measure_rectangle2里有个Interpolation参数选nearest_neighbor速度快但精度低选bilinear精度高一些一般都用双线性插值就够用了。除非你的算法对亚像素精度要求特别高才需要进一步考虑bicubic但对大部分工业场景来说双线性已经是天花板附近了。3. 案例二金属磨砂面缺陷检测——为什么普通阈值分割会失效3.1 磨砂面带来的检测难题很多金属工件为了防止反光表面会做磨砂处理。这种表面在图像里呈现的是大量细微的随机灰度纹理看起来没什么规律。你想在这种背景里找一条轻微划痕直接用threshold分割会发现背景纹理的灰度波动比目标缺陷还要大阈值怎么调都分不干净。我早期做这类项目时先用中值滤波再阈值分割效果很差划痕和背景纹理被一并抹掉或者背景残留大量噪声点。后来换成频域滤波方案问题才搞定。3.2 频域高通滤波原理一句话讲透把图像从空间域变换到频域后背景纹理属于什么频率答案是高频成分。划痕呢也属于高频。听起来好像不行但实际上背景纹理是大量随机、均匀分布的细微高频成分而划痕是有方向性、连续性的线性特征。所以更有效的方式是先做低通滤波把图像整体平滑得到背景估计。原图减去背景估计得到包含缺陷的残差图。在残差图上做阈值分割。这套流程在Halcon里不需要真的去做FFT也可以用高斯滤波或者中值滤波实现但要控制好滤波器尺寸。3.3 基于高斯滤波差影法的Halcon实现核心代码如下* 输入磨砂金属表面灰度图 * 用高斯滤波器估计背景Sigma决定保留尺度 gauss_filter (Image, ImageGauss, 9) * 原图减去背景得到残差 sub_image (Image, ImageGauss, ImageSub, 1, 128) * 残差图阈值分割找出缺陷 threshold (ImageSub, RegionDefect, 30, 255)这里面最关键的参数是高斯核尺寸也就是那个Sigma9。它决定了背景纹理被抹平到什么程度。Sigma太小划痕的位置也被抹掉了一部分残差不完整Sigma太大背景纹理的残差变大噪声变多。训练的时候建议用几个不同Sigma做对比实验选一个在试拍样本上信噪比最高的值。如果你需要处理方向性明显的划痕比如平行拉丝纹理上找横向划痕还可以用bandpass_filter做带通滤波效果会更精准。不过这个需要你对频域分析熟悉新手先用高斯差影法更稳。3.4 缺陷确认Blob分析和特征筛选残差图里分割出的区域除了真实划痕还可能有灰尘、水渍、光照不均产生的假目标。这时候不要急着判NG先做Blob分析筛选* 连通域分析 connection (RegionDefect, ConnectedDefects) * 按面积筛选 select_shape (ConnectedDefects, SelectedDefects, area, and, 50, 9999) * 按形状筛选划痕通常长条状宽高比大 select_shape (SelectedDefects, FinalDefects, width, and, 3, 100) select_shape (FinalDefects, FinalDefects1, height, and, 10, 500)这里我会额外建议一个习惯把所有筛选参数做成外部变量用UI界面调试。项目交付后现场换产品批次时工艺人员只需要调整几个滑块就能匹配新情况不用改代码重新编译体验好很多。4. 案例三模板匹配定位——连接器Pin针检测前的关键一步4.1 为什么检测前必须先定位很多情况下你不能保证工件每次都精确落在相机视野的正中央。传送带抖动、治具间隙、机器人抓取误差这些都会让工件在图像里偏移几个像素甚至几十个像素。如果直接在一个固定的ROI里做检测边缘位置偏了测量结果就会出错。所以规范的做法是先在图像里找到工件的位置和角度再把检测ROI通过仿射变换映射过去让检测区域跟着工件走。4.2 基于形状的模板匹配参数怎么选Halcon里最常用的模板匹配算子是create_shape_model和find_shape_model。注意create_shape_model是基于形状轮廓的不是基于灰度值的它对光照变化相对不敏感比旧版的create_ncc_model更适合工业场景。关键参数的含义和选择逻辑参数含义经验建议NumLevels金字塔层数越大搜索越快但过大会丢失细节建议4到6具体看模板复杂度AngleStart/AngleExtent/AngleStep搜索角度范围和步长如果工件角度固定设为0如果有旋转给±30°就够MinScore最小匹配分数越低越容易命中但误检率也越高0.6到0.8起步视现场情况上调Greediness贪心系数越大搜索越快但容易漏检0.7到0.9调试时先用低值模板创建和匹配的代码如下* 创建形状模板 create_shape_model (TemplateImage, 5, -30, 60, 0.1, auto, no_pregeneration, use_polarity, 30, 10, ModelID) * 在运行图像中查找模板 find_shape_model (Image, ModelID, -30, 60, 0.6, 1, 0.5, least_squares, 6, 0.8, Row, Column, Angle, Score) * 根据匹配结果生成仿射变换矩阵 vector_angle_to_rigid (0, 0, 0, Row, Column, Angle, HomMat2D) * 将检测ROI通过仿射变换映射到当前图像位置 affine_trans_region (ModelRegion, TransRegion, HomMat2D, nearest_neighbor)4.3 匹配后的ROI动态映射省下大量重复开发我实际做过一个连接器Pin针检测项目检测内容是Pin针是否弯曲、间距是否均匀。整个流程是这样离线建模阶段拍一张标准图像手动框选出Pin针区域保存为ModelRegion作为后续所有检测的基础。在线检测阶段每次来图先找工件的位置得到仿射变换矩阵。把检测区域映射到当前图像上再在这个动态区域里执行边缘提取、间距测量。这样做的好处非常明显检测代码可以完全复用换不同型号的连接器只需要换模板文件不用改程序。4.4 模板匹配的常见坑我在调试中遇到过一个问题模板分数一直很低怎么都匹配不上。排查了很久发现是模板图像和运行图像的对比度极性反了。比如模板是暗背景亮工件到了现场变成了亮背景暗工件。解决方法是创建模板时把create_shape_model的Contrast参数从use_polarity改成ignore_global_polarity或者在模板制作时统一规范打光方向。另一个坑是工件表面有氧化色斑导致部分轮廓灰度变化剧烈被误识别成多个目标。这种情况下我会在创建模板前先把图像做一次中值滤波把杂点平滑掉再用滤波后的图像建模板稳定性会好很多。5. 案例四Blob分析——密封圈缺料检测一个select_shape就能搞定5.1 Blob分析适合什么样的检测Blob分析是机器视觉里最简单粗暴的一类方法但它绝不是过时的技术。对于表面缺陷、零件缺失、形状异常这类检测只要目标与背景的灰度差异足够明显Blob分析往往是效率最高、稳定性最好的方案。我做过一个橡胶密封圈的缺料检测项目。橡胶类零件因为是黑色的打光后和背景之间很容易形成高对比度。缺料的地方在图像上就是一个灰度异常的区域非常适合用Blob分析来找。5.2 从图像到缺陷区域完整流程解析* 全局阈值分割把密封圈从背景中提取出来 threshold (Image, RegionRing, 0, 80) * 提取连通域 connection (RegionRing, ConnectedRings) * 按面积筛选去掉杂散回点 select_shape (ConnectedRings, RingRegion, area, and, 20000, 999999) * 计算密封圈的最小外接圆和最大内接圆用于后续定位中心 smallest_circle (RingRegion, Row, Column, OuterRadius) inner_circle (RingRegion, RowIn, ColIn, InnerRadius) * 提取密封圈的内外轮廓区域 * 外圆区域减去内圆区域得到完整圈体 gen_circle (CircleOuter, Row, Column, OuterRadius) gen_circle (CircleInner, RowIn, ColIn, InnerRadius) difference (CircleOuter, CircleInner, RegionBand) * 在原图中提取圈体范围内的灰度分布 reduce_domain (Image, RegionBand, ImageRingBand)到这里圈体部分已经被精准框出来了。接下来要做的是在圈体范围内找缺料位置。缺料的灰度特征通常和正常橡胶不同更亮或更暗取决于打光方式所以可以在圈体区域内再做一次动态阈值* 在圈体区域内做动态阈值分割找出灰度异常区域 var_threshold (ImageRingBand, RegionDefects, 15, 15, 0.2, 10, dark) * 按面积筛选出真正影响质量的缺陷 connection (RegionDefects, ConnectedDefects) select_shape (ConnectedDefects, FinalDefects, area, and, 30, 999999)5.3 光照是关键算法只是辅助这个案例特别想强调一点Blob分析算法的好坏其实只占最终效果的一部分打光方案才是决定成败的核心。我最初测试的时候直接用环形无影光源结果密封圈表面反光严重某些角度下正常区域和缺料区域的灰度几乎一样阈值再怎么调也分不开。后来换了低角度环形光源加偏振片让光线以低角度照射圈体表面橡胶的漫反射特征被大大增强缺料区域和正常区域的对比度立刻拉了开来。所以做视觉项目我建议先花六成精力在打光上算法反而是相对简单的部分。打光不对再牛的算法也只能在垃圾输入里找垃圾输出。6. 案例五OCR识别——药瓶批号检测把Halcon当成读码器用6.1 OCR识别的适用场景产线上经常需要在药品包装、PCB板、食品罐底上识别打印的批号、日期、序列号。这些字符有的是喷码打印有的是激光打标质量参差不齐。Halcon自带OCR识别工具传统方案用MLP分类器就能解决大部分场景。我这边有一个药瓶铝箔封口的批号识别项目字符印在铝箔上背景有大量反光纹理字符本身是浅浅的喷墨印字对比度不算高。用工业读码器也能做但读码器是固定功能灵活性差用Halcon做识别区域、字符类型、判定规则都可以自己掌握。6.2 图像预处理不要一上来就OCROCR识别之前最关键的一步是图像增强。我常用的流程是从彩色图中分离出对比度最好的通道。铝箔封口一般偏暖色字符偏黑这时用灰度转换后字符和背景的对比度可能不够我会逐个检查R、G、B三个通道选择一个对比度最好的。做灰度拉伸把灰度范围从窄区间扩展到0到255。* 分解三通道 decompose3 (Image, ImageR, ImageG, ImageB) * 选择对比度最好的B通道根据实际效果选择 * 灰度拉伸 scale_image (ImageB, ImageScaled, 2.5, -150)这里scale_image的第二个参数Mult和第三个参数Add的选择思路是先观察图像的最小灰度和最大灰度计算出把它线性映射到0~255所需的乘数和加数。比如图像灰度集中在80到160那么缩放比例大约是255/(160-80)3.2减去的偏移是80*3.2256。6.3 OCR分类器的加载与识别Halcon的OCR识别有两种方式传统MLP分类器和基于深度学习的OCR。工业项目里我还是会用MLP为主因为它的速度更快模型文件小不需要GPU部署方便。* 读取训练好的OCR分类器 read_ocr_class_mlp (Industrial_0-9A-Z_NoRej, OCRHandle) * 字符分割阈值分割出字符区域 threshold (ImageScaled, RegionChars, 60, 255) connection (RegionChars, ConnectedChars) * 执行OCR识别 do_ocr_multi_class (ConnectedChars, ImageScaled, OCRHandle, ClassNames, Confidence)这里Industrial_0-9A-Z_NoRej是Halcon自带的一个工业字符集模型覆盖数字和大写字母适合喷码和激光打标场景。如果识别率不够你可以用Halcon自带的OCR训练工具针对你自己的字体样本重新训练一个分类器。6.4 识别结果校验逻辑识别完成后不是每个字符都有一个置信度分数。我一般会设定一个规则置信度低于0.6的字符标记为待人工判定整行识别结果不匹配预期格式也判定为NG。* 判断置信度是否低于阈值 if (Confidence 0.6) * 输出NG需要人工复判 endif * 校验批号格式例如期望是4位年份2位月份字母3位流水号 if (strlen(RecognizedString) ! 10) * 格式不对判定NG endif这个校验逻辑看着简单但实际很管用。很多误识别是单个字符有歧义通过格式规则可以挡掉一大部分不需要每次都对模型本身动刀。7. 从单案例到项目落地那些代码之外的关键经验7.1 算法能跑通和能上线是两回事前面5个案例每一个都只讲了核心算法部分。但真实项目里算法部分的代码量可能只占整个项目的20%剩下的60%是图像采集、界面交互、数据存储、通信对接最后的20%是打磨细节。我见过太多人把算法在离线图片上跑通了就以为项目完成了一半。实际上线时才发现相机帧率上不去、图片传来传去丢数据、通信接口超时、数据库写入太慢导致流程卡顿。这些才是最花时间的部分。7.2 Halcon与C#/C混合编程的几个建议很多设备软件是用C#写的Halcon提供.NET接口。我通常的做法是在Halcon里开发算法验证没问题后封装成函数导出为.hdvp或库文件再在C#里导入调用。这里有个坑Halcon的变量类型和C#的变量类型不完全对应。比如HTuple转int要用tuple.ToInt32()显式转换不能直接强转。还有图像对象HObject和HImage的区别很容易搞混。// C#里调用Halcon算子 HObject hoImage new HObject(); HTuple hvWidth, hvHeight; hoImage.ReadImage(test.png); hoImage.GetImageSize(out hvWidth, out hvHeight); int width hvWidth.ToInt32(); int height hvHeight.ToInt32();7.3 系统稳定性的三个关键点比较有参考价值的一条经验是项目上线前一定要做压力测试。我曾经遇到过一个项目单机测试时一切正常一上产线就蓝屏重启。排查了一圈发现是相机驱动和Halcon的USB带宽冲突因为多台相机同时采集的时候数据量太大导致系统资源耗尽。这类问题如果在算法开发阶段就提前规划可以有效规避。我的建议是相机采集和算法处理用不同线程采集线程只负责取图和缓存算法线程负责计算。尽量用千兆网相机配合硬触发模式避免USB带宽不稳定。运行时的License文件要提前规划好确认授权方式和有效期。8. 给新手的落地操作清单三天走完一个最简项目如果你是一个刚接触Halcon的新人想快速验证一个检测需求是否可行我建议按下面这个清单来操作准备一个稳定的光源环境。可以用环形无影灯加背光板保证目标与背景有足够的对比度。用Halcon自带的read_image载入图片先用inspect工具浏览灰度分布确认目标灰度范围和背景灰度范围。先用threshold和connection跑一次Blob分析看能不能把目标区域分出来。如果阈值分不出来试试gauss_filter差影法或者换成频域滤波。跑通了之后用get_image_size记录图像尺寸计算像素当量验证测量精度是否达标。最后再用create_shape_model加一步定位把ROI动态化完成整个流程。这个流程我带了不下十个新人基本都能在两到三天内上手。等你跑完一遍之后你就能体会到机器视觉的核心不是某个神奇的算子而是一整套从打光、采集、图像处理到判定逻辑的工程思维。我个人在做Halcon项目的最大体会是永远先搞清楚我要测什么、测多准、多快再决定用什么算法方案。不要一开始就把问题想复杂从最简单的阈值分割、Blob分析起步一步一步叠加轮廓提取、模板匹配、频域分析这些工具。等你积累了足够多的失败经验那时候再回头看深度学习你会有一个完全不同的视角——知道该在什么时候用它也知道什么时候不该用。
网站建设高端定制企业官网