1. 网格缺陷检测到底在检什么1.1 从一张“看起来没问题”的图说起做过工业视觉的人大概都有这种经历产线上传过来一张网格材料的图像肉眼扫过去觉得挺正常但客户要求你把它上面所有缺陷都找出来。等你真正放大去看才发现问题五花八门——有的网格线断了半根有的交叉点偏移了几个像素有的地方多出一小块异物还有的网格间距忽大忽小像是被拉伸过。这就是网格缺陷检测的典型场景。它面对的对象通常是规则排列的网格状结构比如金属滤网、丝印网版、显示屏像素阵列、光伏栅线、编织网布、蜂窝结构件等等。这类对象的共同特点是正常状态下有很强的周期性规律而缺陷恰恰是打破这种规律的地方。所以网格缺陷检测的核心思路本质上不是“找瑕疵”而是找规律被破坏的位置。这个思维转变很关键后面所有的算法选型、参数调整都围绕它展开。1.2 适合谁来看这篇内容如果你正在用Halcon做以下几类事情这篇内容应该能帮上忙手上有网格类产品的检测项目但不确定该用哪种算子组合已经写了检测逻辑但误检率居高不下想搞清楚问题出在哪想理解Halcon里频域方法、模板比对、形态学这几套工具在网格场景下各自的适用边界需要一套可以直接改参数就上手的参考方案我默认你已经装好了Halcon能看懂基本的算子调用知道什么是Region、XLD、Image这些基础概念。如果你是纯新手建议先把Halcon自带的例程过一遍再来看不然有些算子参数你会没有感觉。1.3 网格缺陷的常见类型盘点在动手写代码之前先要把缺陷分类搞清楚因为不同类型的缺陷检测策略完全不一样。我把它分成三大类第一类是结构性缺陷指的是网格本身的几何结构出了问题。比如断线某段网格线缺失、缺交点本该交叉的地方没交叉、线宽异常某段线突然变粗或变细、间距异常相邻网格间距偏离标准值。第二类是附着性缺陷指的是网格上多了不该有的东西。比如异物、毛刺、污点、氧化斑、残留胶体。这类缺陷的特点是它不属于网格结构本身但会覆盖或干扰网格。第三类是形变类缺陷指的是整体或局部的几何形变。比如网格被拉伸、扭曲、褶皱、局部凹陷。这类缺陷往往不是单个点的问题而是区域性的规律偏移。注意实际项目里这三类缺陷经常混在一起出现所以检测方案通常需要多套逻辑并行而不是指望一个算子解决所有问题。2. 方案选型为什么不能只靠一种方法2.1 频域方法的适用边界网格结构天然具有强周期性这让我第一时间想到的是频域方法。Halcon里对应的核心算子就是fft_image和fft_image_inv配合gen_bandpass、gen_bandfilter这类滤波器。原理不复杂把图像转到频域网格的周期性会在频谱上形成规则的亮点基频和谐波用带通或带阻滤波器把这些频率成分处理掉再转回空域剩下的就是不符合周期规律的部分也就是缺陷。这套方法对断线、缺交点、异物这类缺陷效果不错因为缺陷在频域里表现为宽频谱成分不会被窄带滤波器保留。但它有几个硬伤一是对局部形变不敏感因为形变后的网格频率会偏移可能落在滤波器通带内被保留下来二是参数敏感滤波器中心频率和带宽稍微偏一点结果就差很多三是边缘效应图像边界处的频谱泄漏会制造假缺陷。我试过在一个金属滤网项目上纯用频域方法断线检出率能到95%以上但褶皱类缺陷基本检不出来。后来加了模板比对才补上。2.2 模板比对的优势与代价模板比对或者说参考比对的思路更直接先建立一张“标准网格”的参考图然后把待检图和参考图做差分差异大的地方就是缺陷。Halcon里常用的算子是create_shape_model配合find_shape_model做定位然后用sub_image或dyn_threshold做差分。也可以用compare_variation_model做基于统计的比对。这套方法的优势是对形变类缺陷敏感因为形变会导致网格位置偏移差分后必然出现明显差异。而且逻辑直观调试起来容易理解。代价在于参考图的获取和配准。如果产品本身有轻微的位置和角度变化必须先做精确配准否则差分结果全是边缘噪声。另外如果产品批次之间有正常的工艺波动比如网格间距有±0.05mm的公差参考图就得覆盖这个波动范围否则正常品也会被误判。2.3 形态学方法的补位作用形态学方法在网格检测里往往扮演“清道夫”的角色。比如用opening_circle或opening_rectangle1去掉细小的网格线只留下大块的异物或者用closing填补断线处的小缺口再和原图比对找出断线位置。它的优势是速度快、参数直观适合做预处理或者后处理。但单独用它做检测对参数非常敏感结构元素大小差一个像素结果可能就完全不同。2.4 我的实际选型策略综合下来我在大多数网格检测项目里采用的是组合方案检测目标主方法辅助方法断线、缺交点频域滤波形态学闭运算验证异物、污点形态学开运算频域滤波交叉确认形变、褶皱模板比对网格间距统计线宽异常亚像素测量模板比对这套组合不是拍脑袋定的而是在几个项目里反复试出来的。下面我把每个环节的具体实现拆开讲。3. 实操过程从图像到缺陷标记3.1 图像预处理与网格增强拿到一张网格图像第一步不是急着检测而是把网格结构“提”出来。常用的做法是* 读取图像 read_image (Image, grid_sample.png) get_image_size (Image, Width, Height) * 转灰度如果是彩色图 rgb1_to_gray (Image, GrayImage) * 增强网格线对比度 emphasize (GrayImage, EmphasizedImage, 7, 7, 1.0) * 局部阈值提取网格线 mean_image (EmphasizedImage, MeanImage, 15, 15) dyn_threshold (EmphasizedImage, MeanImage, RegionDyn, 8, dark)这里emphasize的作用是增强高频成分让网格线更清晰。dyn_threshold用局部均值做参考比全局阈值更适应光照不均的情况。实操心得dyn_threshold的阈值参数不要设太小否则会把噪声也提出来。我一般从10开始试根据网格线和背景的对比度调整。如果网格线本身很细小于3像素建议先用zoom_image_factor放大两倍再处理。3.2 频域滤波检测断线与缺交点频域处理的关键是找到网格的基频。方法是对预处理后的图像做FFT然后在频谱图上找峰值。* 转频域 fft_image (EmphasizedImage, FFTImage) power_real (FFTImage, PowerSpectrum) * 找频谱峰值网格基频 gen_rectangle1 (ROI, 0, 0, Height/2, Width/2) reduce_domain (PowerSpectrum, ROI, PowerROI) local_max_sub_pix (PowerROI, gauss, 1, 3, Row, Col, Peak) * 根据峰值设计带阻滤波器 gen_bandfilter (Filter, bandpass, 0, 0, 0.1, 0.3, rft, Width, Height)实际项目中网格的基频通常对应频谱图上离中心最近的那几个亮点。把这些亮点对应的频率成分抑制掉网格的周期性结构就被去除了剩下的就是缺陷。* 应用滤波器 convol_fft (FFTImage, Filter, FilteredFFT) fft_image_inv (FilteredFFT, DefectImage) * 阈值分割缺陷 threshold (DefectImage, DefectRegion, 30, 255) connection (DefectRegion, ConnectedRegions) select_shape (ConnectedRegions, Defects, area, and, 20, 99999)注意gen_bandfilter的参数需要根据实际频谱峰值位置调整。我一般会先把频谱图可视化出来手动确认峰值位置再反推滤波器参数。这一步偷懒不得参数不对后面全白搭。3.3 模板比对检测形变与褶皱模板比对的第一步是建立参考。如果产品一致性很好可以直接拿一张标准品图像做参考。如果一致性一般建议用多张标准品做平均。* 建立参考模板 read_image (RefImage, grid_reference.png) rgb1_to_gray (RefImage, RefGray) * 创建形状模板用于配准 create_shape_model (RefGray, auto, -0.1, 0.1, auto, auto, use_polarity, auto, auto, ModelID) * 待检图配准 find_shape_model (GrayImage, ModelID, -0.1, 0.1, 0.5, 1, 0.5, least_squares, 0, 0.9, RefRow, RefCol, RefAngle, Score) * 仿射变换对齐 vector_angle_to_rigid (Height/2, Width/2, 0, RefRow, RefCol, RefAngle, HomMat2D) affine_trans_image (GrayImage, AlignedImage, HomMat2D, constant, false)配准之后做差分* 差分 sub_image (AlignedImage, RefGray, DiffImage, 1, 0) * 动态阈值提取差异区域 dyn_threshold (DiffImage, RefGray, DefectRegion, 15, light) connection (DefectRegion, ConnectedRegions) select_shape (ConnectedRegions, DeformDefects, area, and, 50, 99999)实操心得差分后的阈值选择很关键。阈值太小会把正常工艺波动也检出来太大又会漏掉轻微形变。我的经验是先用一批正常品跑一遍统计差分图像的灰度标准差然后取3倍标准差作为阈值起点。3.4 网格间距统计与线宽测量对于间距异常和线宽异常需要做定量测量。Halcon里可以用measure_pos或measure_pairs沿网格垂直方向做一维测量。* 生成测量矩形 gen_measure_rectangle2 (Row, Col, 0, Width/2, 10, Width, Height, nearest_neighbor, MeasureHandle) * 测量网格线位置 measure_pos (GrayImage, MeasureHandle, 1, 30, all, all, RowEdge, ColEdge, Amplitude, Distance) * 计算间距 tuple_diff (ColEdge, Spacing) tuple_mean (Spacing, MeanSpacing) tuple_deviation (Spacing, StdSpacing)如果某段间距偏离均值超过3倍标准差就标记为间距异常。线宽测量类似用measure_pairs可以同时得到上升沿和下降沿两者之差就是线宽。measure_pairs (GrayImage, MeasureHandle, 1, 30, positive, all, RowEdgeFirst, ColEdgeFirst, AmplitudeFirst, RowEdgeSecond, ColEdgeSecond, AmplitudeSecond, IntraDistance, InterDistance)注意测量矩形的方向和宽度要根据网格走向设置。如果网格有旋转先用find_shape_model得到角度再旋转测量矩形。测量宽度不要太大否则会跨过多条网格线导致测量结果混乱。3.5 多路结果融合与缺陷分类三路检测跑完之后会得到三组缺陷区域。这时候需要做融合和去重* 合并所有缺陷区域 union2 (DefectRegion1, DefectRegion2, UnionDefects) union2 (UnionDefects, DefectRegion3, AllDefects) * 去重合并重叠区域 connection (AllDefects, ConnectedAll) select_shape (ConnectedAll, FinalDefects, area, and, 10, 99999) * 按特征分类 select_shape (FinalDefects, BreakDefects, rectangularity, and, 0, 0.3) select_shape (FinalDefects, ForeignDefects, circularity, and, 0.7, 1.0)分类逻辑可以根据实际缺陷特征调整。比如断线通常细长矩形度低异物通常接近圆形圆度高形变区域通常面积大且不规则。4. 常见问题与排查技巧实录4.1 误检太多怎么办这是最常见的问题。误检来源通常有三个光照不均导致局部对比度差异大阈值分割时把正常区域也切出来了。解决办法是用illuminate或mean_image做背景校正或者改用dyn_threshold代替全局阈值。配准不准导致差分后边缘出现大量差异。解决办法是提高find_shape_model的精度或者改用find_ncc_model做灰度匹配。如果产品本身形变较大可以考虑用find_local_deformable_model。参数过敏感导致正常工艺波动被当成缺陷。解决办法是统计正常品的特征分布把阈值设在3倍标准差之外。我一般会跑20-30张正常品统计缺陷区域的面积、灰度差等特征然后定阈值。4.2 漏检怎么排查漏检比误检更危险因为客户通常不接受漏检。排查思路是先确认缺陷在图像上是否可见。如果图像本身就看不清那得先解决成像问题调光源、换镜头、改曝光。如果图像可见但检不出检查预处理是否把缺陷特征削弱了。比如emphasize参数太大可能把细小断线也增强了反而被后续滤波当成噪声滤掉。再检查检测逻辑是否覆盖了该缺陷类型。比如频域方法对形变不敏感如果你只用频域形变缺陷必然漏检。4.3 运行速度优化Halcon的频域操作比较耗时如果产线节拍要求高可以考虑缩小ROI只处理网格区域不要全图处理降低图像分辨率如果缺陷尺寸允许的话用optimize_fft_speed优化FFT速度把模板比对和频域检测并行化用多线程处理* 优化FFT速度 optimize_fft_speed (Width, Height, standard)实操心得optimize_fft_speed只需要在程序初始化时调用一次不要放在循环里。另外如果图像尺寸是2的幂次方FFT速度会明显更快。可以在预处理时把图像缩放到最近的2的幂次方尺寸。4.4 常见问题速查表问题现象可能原因排查方向解决手段边缘大量误检配准不准检查配准Score提高匹配精度或改用NCC匹配断线漏检滤波参数过宽检查频谱峰值位置调整带阻滤波器中心频率异物误检为断线分类特征选错检查区域矩形度调整分类阈值或增加特征维度运行速度慢全图FFT检查处理区域缩小ROI或降低分辨率正常品被误判阈值过严统计正常品特征分布放宽阈值到3倍标准差形变检不出只用频域方法检查是否启用模板比对增加模板比对分支4.5 几个容易踩的坑第一个坑是参考图选得太完美。有些工程师喜欢拿一张“零缺陷”的样品做参考但实际产线上正常品也有波动。结果就是参考图越完美误检越多。正确做法是用多张正常品做平均或者用create_variation_model建立统计模型。第二个坑是滤波器参数照搬例程。Halcon例程里的参数是针对特定图像的直接拿来用大概率不合适。必须根据自己图像的频谱特征重新设计。第三个坑是忽略图像边界。FFT的边界效应会在图像四周产生假缺陷。解决办法是在预处理时对图像做镜像扩展或者检测后把边界区域排除掉。第四个坑是测量方向搞反。网格如果是斜向的测量矩形方向必须跟着斜否则测出来的间距全是错的。我一般会先用edges_sub_pix提取网格线用fit_line_contour_xld拟合出方向再设置测量矩形。5. 参数计算与选型依据5.1 滤波器参数怎么定gen_bandfilter的参数格式是(Filter, bandpass, MinFreq, MaxFreq, MinAmp, MaxAmp, rft, Width, Height)。其中MinFreq和MaxFreq是归一化频率范围0到1。假设网格周期是10像素那么基频就是1/100.1。带阻滤波器的中心频率应该设在0.1附近带宽根据网格周期的波动范围定。如果周期波动±1像素频率范围就是1/11到1/9即0.091到0.111。实际操作中我会先把频谱图用power_real转成功率谱然后可视化出来手动找峰值位置。Halcon的local_max_sub_pix可以自动找峰值但有时候会把噪声峰值也找出来需要人工筛选。5.2 形态学结构元素怎么选结构元素的大小取决于网格线的宽度和缺陷的尺寸。如果网格线宽是5像素想去除网格线只留异物结构元素直径应该大于5像素比如7或9。如果想填补断线缺口结构元素直径应该大于缺口宽度。* 去除网格线 opening_circle (GridRegion, Background, 7.5) * 填补断线 closing_rectangle1 (GridRegion, ClosedRegion, 5, 5)注意结构元素越大处理速度越慢而且可能把真实缺陷也去掉。我一般从最小可能值开始试逐步增大直到效果满意。5.3 阈值设定的统计依据无论是dyn_threshold还是threshold阈值设定都不应该拍脑袋。我的做法是先跑一批正常品至少20张统计检测图像的特征分布。比如差分图像的灰度均值是5标准差是3那么阈值就设53×314。这样正常品被误判的概率大约在0.3%以下。再跑一批缺陷品确认所有缺陷的特征值都超过阈值。如果某个缺陷的特征值刚好在阈值附近说明这个缺陷太轻微可能需要调整成像方案或者放宽客户标准。5.4 测量参数的校准measure_pos和measure_pairs的Sigma参数控制边缘检测的平滑程度Threshold控制边缘幅度的最低要求。Sigma一般设1.0到2.0太大边缘会模糊太小噪声会干扰。Threshold根据边缘对比度定如果网格线和背景对比度是50灰度级Threshold设30左右比较合适。测量矩形的宽度Width参数应该略大于网格间距确保能覆盖至少一条完整的网格线。但也不能太大否则会跨过多条线导致测量混乱。6. 完整代码框架与扩展思路6.1 主流程代码结构把上面的内容串起来主流程大概是这样的* 初始化 read_image (Image, grid_sample.png) get_image_size (Image, Width, Height) optimize_fft_speed (Width, Height, standard) * 预处理 rgb1_to_gray (Image, GrayImage) emphasize (GrayImage, EmphasizedImage, 7, 7, 1.0) * 分支1频域检测 fft_image (EmphasizedImage, FFTImage) gen_bandfilter (Filter, bandpass, 0.08, 0.12, 0.05, 0.3, rft, Width, Height) convol_fft (FFTImage, Filter, FilteredFFT) fft_image_inv (FilteredFFT, DefectImage) threshold (DefectImage, FreqDefects, 30, 255) * 分支2模板比对 create_shape_model (RefGray, auto, -0.1, 0.1, auto, auto, use_polarity, auto, auto, ModelID) find_shape_model (GrayImage, ModelID, -0.1, 0.1, 0.5, 1, 0.5, least_squares, 0, 0.9, RefRow, RefCol, RefAngle, Score) vector_angle_to_rigid (Height/2, Width/2, 0, RefRow, RefCol, RefAngle, HomMat2D) affine_trans_image (GrayImage, AlignedImage, HomMat2D, constant, false) sub_image (AlignedImage, RefGray, DiffImage, 1, 0) dyn_threshold (DiffImage, RefGray, TemplateDefects, 15, light) * 分支3形态学检测 opening_circle (EmphasizedImage, Background, 7.5) sub_image (EmphasizedImage, Background, MorphDiff, 1, 0) threshold (MorphDiff, MorphDefects, 40, 255) * 融合 union2 (FreqDefects, TemplateDefects, UnionDefects) union2 (UnionDefects, MorphDefects, AllDefects) connection (AllDefects, ConnectedAll) select_shape (ConnectedAll, FinalDefects, area, and, 20, 99999) * 输出 dev_display (Image) dev_display (FinalDefects)6.2 后续可以扩展的方向这套框架跑通之后可以根据实际需求做扩展。比如增加缺陷分类用classify_image_class_mlp或classify_image_class_svm训练一个分类器把缺陷自动分成断线、异物、形变等类别方便后续统计和追溯。增加深度学习分支Halcon的深度学习模块read_dl_model、apply_dl_model可以用来做缺陷检测尤其适合缺陷形态复杂、传统方法难以描述的场景。但深度学习需要大量标注数据前期投入较大。增加SPC统计把每次检测的缺陷数量、位置、尺寸记录下来做统计过程控制。如果某个位置频繁出现缺陷可能是设备或工艺问题可以提前预警。增加3D检测如果网格有高度信息比如凹凸结构可以用3D相机或激光轮廓仪配合Halcon的3D算子做高度方向的缺陷检测。6.3 我个人的一些体会做网格缺陷检测这些年最大的感受是没有万能方案只有针对性的组合。频域方法、模板比对、形态学每种方法都有它的舒适区和盲区。真正靠谱的做法是把它们组合起来让各自的优势互补。另一个体会是成像比算法重要。我见过太多项目算法调了半天效果不好最后发现是光源没选对。网格类产品对光照角度非常敏感背光、同轴光、条形光的效果差异巨大。如果条件允许先在成像上花时间后面算法会轻松很多。最后一个体会是参数要有依据。不要凭感觉调参数要统计、要计算、要验证。我一般会建一个参数记录表把每次调整的参数和对应的检出率、误检率记下来这样后面换产品或者换批次时能快速找到合适的参数起点。提示Halcon的dev_get_window和dev_display在调试时很好用但正式运行时记得关掉否则会拖慢速度。可以用dev_update_off和dev_update_on控制显示刷新。这套方案我在几个不同类型的网格产品上都跑过断线检出率能到98%以上异物检出率95%左右形变检出率90%左右。具体数字取决于产品特性和成像质量但整体框架是通用的。如果你正在做类似的项目可以先按这个框架搭起来再根据实际效果微调参数。