☰
Halcon区域凸起缺陷检测:从Top-Hat到动态阈值的完整方案
2026/10/17 11:43:17 网站建设 项目流程

1. 区域凸起缺陷检测到底在检什么

1.1 从产线痛点说起

做过工业视觉的人大概都有这种体会:表面缺陷检测里,凹坑、划痕、脏污这类“减法型”缺陷相对好处理,因为背景和缺陷的灰度差异通常比较明显,阈值一卡、形态学一开,基本就能把目标抠出来。真正让人头疼的是凸起类缺陷——它不改变区域的整体轮廓,也不一定带来明显的灰度跳变,但它在高度方向上“鼓”出来了。典型场景包括:金属冲压件表面的微小凸包、注塑件表面的料花凸点、玻璃盖板上的异物凸起、锂电池极片涂层上的颗粒凸起、PCB焊盘上的锡珠等等。

这类缺陷的麻烦在于,如果你只用二维灰度图像去看,它可能只是几个像素的亮度变化,甚至和正常纹理混在一起,阈值稍微松一点就漏检,紧一点就误报。所以“区域凸起缺陷检测”这个题目的核心,不是简单地做一次阈值分割,而是要把“凸起”这个三维特征在二维图像里可靠地表达出来,再配合区域约束把缺陷定位到具体位置。

Halcon在这类任务里是一把好手,它提供的算子组合足够灵活,既能做基于灰度梯度的凸起增强,也能做基于形态学的局部对比度提取,还能结合区域特征做筛选。我下面就把整套思路拆开讲,从原理到参数到踩坑,尽量让你看完能直接上手改。

1.2 适合谁来参考

这篇内容适合三类人:一是刚接触Halcon、手里有表面检测项目但不知道从哪下手的视觉工程师;二是已经在用Halcon做缺陷检测、但凸起类缺陷漏检率居高不下、想找新思路的老手;三是做自动化设备集成、需要评估视觉方案可行性的朋友。我不假设你精通Halcon所有算子,但假设你会基本的图像读取、窗口显示和区域操作。如果你连read_image和dev_display都没用过,建议先补一下基础,再回来看这篇。

另外说明一点:凸起缺陷检测没有“一招鲜”的通用方案,不同材质、不同光照、不同缺陷尺度,参数差异很大。我给的是一套可复现的框架和调参逻辑,你拿到自己的图上,需要按我讲的思路去微调,而不是照抄参数就完事。

2. 整体方案设计与选型逻辑

2.1 为什么不用深度学习一把梭

现在一提到缺陷检测,很多人第一反应是上深度学习。但在这个具体场景里,我依然倾向于先用Halcon传统算子把方案跑通,原因有三点。第一,凸起缺陷的样本往往极少,产线上可能一天就出几个,你根本凑不齐训练集,标注成本还高得离谱。第二,传统算子的可解释性强,客户问你这个缺陷为什么被判出来,你能指着某一步说“这里梯度异常”,深度学习你只能给个热力图,沟通成本高。第三,Halcon传统方案在CPU上跑,节拍稳定,不需要GPU,设备成本低,维护也简单。

当然,如果你的缺陷形态极其复杂、传统算子怎么调都压不住误报,那再考虑深度学习也不迟。但在那之前,把传统方案吃透,往往能解决八成问题。

2.2 核心思路:把“凸起”转成“局部异常”

凸起缺陷在二维图像里的本质是什么?是局部区域的高度变化导致的光照反射差异。在明场照明下,凸起的顶部通常比周围亮,边缘可能偏暗;在暗场照明下则相反。不管哪种,它都表现为一种局部对比度异常——和周围邻域相比,这个小区域的灰度分布“不太一样”。

所以整套方案的核心逻辑就是:构造一个能放大局部对比度的响应图,然后在响应图上做阈值分割和区域筛选。具体来说,我通常走这条链路:

  1. 图像预处理:去噪、增强,把干扰纹理压下去。
  2. 局部对比度增强:用形态学或频域方法,把凸起响应提出来。
  3. 阈值分割:在响应图上分割出候选区域。
  4. 区域筛选:用面积、圆度、灰度均值等特征过滤掉误报。
  5. 结果输出:定位缺陷坐标,输出给下游。

这条链路里,第2步是灵魂,第4步是保命符。下面我逐步展开。

2.3 光照方案对检测效果的决定性影响

在讲算子之前,我必须先强调光照。很多人算法调半天没效果,最后发现是打光不对。凸起缺陷检测对光照极其敏感,因为凸起本身就是靠反射差异体现的。

我实测下来比较有效的两种打光方式:一是低角度环形光,光线掠射表面,凸起会在背光侧形成阴影,对比度拉满;二是同轴光,适合镜面材质,凸起顶部会形成亮斑。如果你的现场只能用普通漫射光,那凸起响应会很弱,算法再强也白搭。所以如果你有权限改硬件,先把光调好,能省掉后面一半的调参时间。

提示:在算法调试阶段,可以先用一张已知有凸起缺陷的图,手动改变打光角度拍几张对比,找到对比度最强的那个角度,再固定下来。这一步花十分钟,后面省十小时。

3. 核心算子解析与参数调优

3.1 局部对比度增强的三种武器

Halcon里做局部对比度增强,我常用的有三个方向,各有适用场景。

第一种:形态学Top-Hat变换。这是最经典的凸起检测手段。top_hat的本质是原图减去开运算结果,开运算会抹掉比结构元小的亮区域,所以Top-Hat的结果就是那些“比结构元小、又比周围亮”的局部凸起。结构元的大小要略大于缺陷尺度,比如你的凸起直径大概10像素,结构元半径取15左右。代码大概长这样:

read_image (Image, 'part_surface.png') rgb1_to_gray (Image, GrayImage) gen_disk (StructElement, 15, 'byte') top_hat (GrayImage, TopHatImage, StructElement)

Top-Hat的优点是快、简单、参数直观。缺点是如果背景本身有和大缺陷尺度相近的纹理,会一起被提出来,误报多。

第二种:局部均值差分。用一个大窗口的均值滤波结果减去原图,或者原图减去均值,得到局部偏差图。mean_image配合sub_image就能做。窗口大小同样要大于缺陷尺度。这个方法对缓慢变化的背景适应性好,但边缘处容易产生响应。

第三种:频域带通滤波。把图像转到频域,用带通滤波器保留缺陷对应的频率成分,再转回来。这个方法对周期性纹理背景特别有效,比如织物、网格状表面。但调参复杂,实时性差,我一般作为备选。

实际项目里,我八成情况用Top-Hat,一成半用局部均值差分,剩下半成才考虑频域。你可以先按这个优先级试。

3.2 阈值分割:动态阈值比固定阈值靠谱

响应图出来之后,下一步是分割。这里我强烈建议不要用固定阈值。为什么?因为产线上光照会有波动,工件批次之间表面状态也有差异,固定阈值今天调好明天就飘了。

我通常用dyn_threshold,它拿原图和一张平滑后的图做比较,逐像素判断偏差是否超过给定值。这个“给定值”是相对量,对整体亮度变化不敏感。用法是:

mean_image (TopHatImage, MeanImage, 31, 31) dyn_threshold (TopHatImage, MeanImage, RegionDyn, 8, 'light')

这里的8是偏移量,意思是像素比局部均值亮8个灰度级以上才算候选。这个值需要根据你的响应图对比度来定,一般取响应图里缺陷峰值和背景噪声峰值的中间偏上位置。

如果你发现缺陷和噪声混在一起分不开,说明响应图质量不够,要回到上一步加强增强,而不是在这里硬调阈值。

3.3 区域筛选:把误报挡在门外

分割出来的候选区域里,必然混着噪声、纹理、边缘响应。这时候就要靠区域特征筛选。我常用的特征和判断逻辑如下表:

特征算子典型用法注意事项
面积area_center过滤太小(噪声)和太大(背景)的区域上下限都要设,别只设下限
圆度circularity凸起通常偏圆,长条形多为划痕或纹理圆度阈值别设太高,0.5左右即可
灰度均值intensity凸起区域在响应图上均值应偏高结合响应图算,不是原图
矩形度rectangularity排除不规则纹理块对圆形凸起参考价值有限
紧密度compactness辅助判断区域是否紧凑和圆度配合用

筛选代码示例:

connection (RegionDyn, ConnectedRegions) select_shape (ConnectedRegions, SelectedRegions, 'area', 'and', 20, 500) select_shape (SelectedRegions, SelectedRegions2, 'circularity', 'and', 0.4, 1.0) area_center (SelectedRegions2, Area, Row, Column)

这里面积下限20、上限500,圆度下限0.4,都是经验值,你要按自己缺陷的实际像素尺度来定。定之前先用area_center把候选区域的面积打出来看看分布,再决定卡在哪。

注意:筛选条件不要一次加太多,容易把真缺陷也筛掉。我的做法是先只卡面积,看剩下的误报长什么样,再针对性地加特征。每加一个条件,都回头确认真缺陷还在。

4. 完整实操流程与现场记录

4.1 从读图到输出的完整代码框架

下面这套代码是我在一个金属表面凸起检测项目里实际用过的简化版,你可以直接拿去改。假设图像是灰度图,凸起缺陷直径在8到20像素之间。

* 1. 读取与预处理 read_image (Image, 'metal_surface.png') get_image_size (Image, Width, Height) rgb1_to_gray (Image, GrayImage) * 2. 去噪,保留边缘 median_image (GrayImage, MedianImage, 'circle', 3, 'mirrored') * 3. Top-Hat增强凸起响应 gen_disk (StructElement, 18, 'byte') top_hat (MedianImage, TopHatImage, StructElement) * 4. 动态阈值分割 mean_image (TopHatImage, MeanImage, 41, 41) dyn_threshold (TopHatImage, MeanImage, RegionDyn, 6, 'light') * 5. 区域筛选 connection (RegionDyn, ConnectedRegions) select_shape (ConnectedRegions, RegionArea, 'area', 'and', 15, 600) select_shape (RegionArea, RegionCirc, 'circularity', 'and', 0.35, 1.0) * 6. 结果输出 count_obj (RegionCirc, Number) if (Number > 0) area_center (RegionCirc, Area, Row, Column) * 这里可以把坐标输出给下游或画框显示 endif

这段代码里,median_image的窗口我取3,是为了去掉椒盐噪声但保留凸起边缘。如果你图像本身很干净,这步可以省。gen_disk的半径18,对应缺陷最大直径20像素左右,结构元要略大于缺陷,否则Top-Hat会把缺陷本身也当成背景抹掉。dyn_threshold的偏移量6,是我在这批图上试出来的,你的图可能要调。

4.2 参数是怎么一步步定下来的

很多人拿到代码直接跑,发现效果不对就懵了。我讲讲我定参数的过程,你照着做就能找到自己的值。

第一步,定结构元大小。先把top_hat的结果显示出来,如果缺陷在响应图里是一个完整的亮斑,说明结构元合适;如果缺陷中间是暗的、只有边缘亮,说明结构元太小,把缺陷内部也开掉了;如果响应图里缺陷很弱、背景纹理很强,说明结构元太大,把缺陷当背景了。我一般从缺陷最大直径的1.5倍开始试,上下浮动。

第二步,定动态阈值偏移量。把TopHatImage的灰度直方图打出来,你会看到背景噪声集中在一个低灰度区间,缺陷响应在更高的区间。偏移量就取两个区间之间的谷底附近。如果两个区间重叠,说明响应图对比度不够,回第一步调结构元,或者检查光照。

第三步,定面积范围。把ConnectedRegions里每个区域的面积打出来,真缺陷的面积应该在一个相对集中的区间,噪声面积通常很小,背景误报面积通常很大。卡在中间就行。

第四步,定圆度。把候选区域叠加到原图上目视检查,真凸起基本是圆的或椭圆的,划痕、纹理是长条的。圆度卡0.35到0.4能滤掉大部分长条误报,又不至于误杀椭圆凸起。

这套流程走下来,一般两三个小时能把一个稳定方案调出来。比盲目试参数快得多。

4.3 一个真实项目的调参记录

我之前做过一个注塑件表面凸点检测的项目,工件是黑色塑料,表面有细微的模具纹理。一开始用Top-Hat,结构元半径12,结果模具纹理全被提出来了,误报上百个。后来分析发现,模具纹理的尺度比凸点小,凸点直径大概15像素,纹理周期大概3像素。于是我把结构元半径加到20,纹理被开运算抹掉了,凸点保留下来。但新的问题是凸点响应变弱了,因为结构元太大,凸点边缘也被削掉一部分。

解决办法是先做一次高斯平滑,把纹理压下去,再做Top-Hat。gauss_filter窗口取5,纹理基本被抹平,凸点因为尺度大还能保留。然后再Top-Hat,结构元半径15,效果就出来了。最终误报控制在个位数,漏检为零。

这个案例说明,参数不是孤立的,预处理和增强要配合着调。你如果遇到类似情况,不妨试试先平滑再增强。

5. 常见问题与排查速查

5.1 漏检了怎么办

漏检是凸起检测里最要命的问题,因为客户往往对漏检零容忍。漏检的原因通常有三个:响应太弱、阈值太高、筛选太狠。

排查顺序我建议这样:先把TopHatImage显示出来,看缺陷位置有没有亮斑。如果没有亮斑,说明增强不够,要调结构元或换增强方法。如果有亮斑但没被分割出来,说明dyn_threshold偏移量太大,往小调。如果分割出来了但被筛掉了,把筛选条件逐个去掉,看是哪个条件杀的,然后放宽那个条件。

还有一种隐蔽的漏检:缺陷在图像边缘。Top-Hat在图像边缘会有边界效应,响应异常。解决办法是做一次gen_rectangle1把边缘区域排除,或者对图像做镜像扩展再处理。

5.2 误报太多怎么压

误报多的原因和漏检相反:响应图里噪声太多、阈值太低、筛选太松。但误报的排查更麻烦,因为你要搞清楚每个误报是什么。

我的做法是把误报区域一个个叠加到原图上,分类。常见误报类型和对策如下表:

误报类型表现对策
噪声点面积很小,随机分布加大面积下限,或预处理加中值滤波
纹理块面积中等,形状不规则调结构元大小,或加圆度筛选
边缘响应集中在工件轮廓附近用区域掩膜排除边缘
反光斑面积大,灰度很高加面积上限,或换打光
脏污形状随机,灰度不均加灰度标准差筛选

分类之后对症下药,比盲目调参高效得多。

5.3 光照波动导致方案不稳定

这是传统视觉方案的通病。今天调好的阈值,明天换一批工件就飘了。缓解办法有几个:一是用dyn_threshold代替固定阈值,它对整体亮度变化不敏感;二是加自动曝光或光源控制器稳光;三是在响应图上做归一化,把响应图除以它的均值或最大值,消除整体增益变化。

如果波动实在太大,可以考虑加一个参考工件,每次检测前先拍参考件,用参考件的响应做基准,动态调整阈值。这个方案复杂一点,但稳定性提升明显。

5.4 检测节拍跟不上

Halcon传统算子在CPU上跑,单张图通常几十毫秒。如果你的节拍要求很高,比如10毫秒以内,那要优化。优化方向:缩小ROI,只处理可能有缺陷的区域;降低图像分辨率,如果缺陷尺度允许;把median_image换成更快的mean_image;减少不必要的区域筛选步骤。

我实测下来,一张500万像素的图,上面那套流程跑下来大概40到60毫秒,取决于结构元大小和筛选步骤。如果你要更快,可以把Top-Hat的结构元换成矩形,gen_rectangle1比gen_disk快不少,代价是方向性稍差。

6. 进阶技巧与方案扩展

6.1 多尺度检测应对不同大小的凸起

实际产线上,凸起缺陷的大小往往不是单一的,可能既有5像素的小凸点,也有30像素的大凸包。单一结构元的Top-Hat很难同时覆盖。这时候可以用多尺度Top-Hat:用几个不同大小的结构元分别做Top-Hat,然后把响应图取最大值或求和。

gen_disk (StructElement1, 8, 'byte') gen_disk (StructElement2, 15, 'byte') gen_disk (StructElement3, 25, 'byte') top_hat (MedianImage, TopHat1, StructElement1) top_hat (MedianImage, TopHat2, StructElement2) top_hat (MedianImage, TopHat3, StructElement3) max_image (TopHat1, TopHat2, MaxImage1) max_image (MaxImage1, TopHat3, MaxImage)

这样小凸点被小结构元捕捉,大凸包被大结构元捕捉,响应图更全面。代价是耗时增加,但换来的是漏检率下降,值得。

6.2 结合高度信息做三维凸起检测

如果你的设备配了3D相机或激光轮廓仪,那凸起检测就简单多了,直接看高度图就行。但很多产线只有2D相机,这时候可以用光度立体技术:用多个不同方向的光源分别拍图,通过光度立体算法恢复表面法向量,再积分得到相对高度图。Halcon里有photometric_stereo算子支持这个。

光度立体对凸起缺陷的检测效果比单张图好很多,因为它直接拿到了高度信息,凸起就是高度图上的局部极大值,阈值分割非常干净。缺点是硬件成本高,需要多光源和同步控制,标定也麻烦。如果你的项目预算允许,这是个值得考虑的升级方向。

6.3 把检测结果可视化输出给下游

检测出来只是第一步,怎么把结果用好也重要。我通常会把缺陷区域画框、标号,输出一张结果图给操作员看,同时把缺陷坐标、面积、等级通过TCP或串口发给PLC。Halcon里用dev_display配合disp_message就能画,生产环境里可以用write_image存图留档。

如果要做缺陷分类,比如区分凸点和脏污,可以在筛选后对每个区域提取更多特征,用classify_class_mlp做个简单的分类器。但样本少的时候,规则分类往往比机器学习更稳。

7. 我踩过的坑和给你的建议

7.1 别忽视图像采集环节

我见过太多项目,算法调了一周没效果,最后发现是相机没对焦、光源角度偏了、镜头有灰尘。凸起检测对成像质量要求极高,因为你要捕捉的是微小的灰度差异。所以动手写代码之前,先确认图像本身是清晰的、对比度足够的、没有过曝欠曝的。这一步做好了,后面事半功倍。

7.2 参数要留调试接口

写Halcon程序的时候,别把参数写死在代码里。把结构元大小、阈值偏移量、面积范围这些做成变量,最好能通过界面或配置文件调整。产线换批次的时候,操作员能自己微调,不用每次都找你。我一般会做一个简单的参数面板,用create_dialog或者直接读INI文件,省去很多售后麻烦。

7.3 建立缺陷样本库

每次调试和运行中发现的缺陷图,都存下来,按类型分类。时间长了你就有了一个样本库,一方面可以用来验证新方案,另一方面如果以后要上深度学习,这些就是现成的训练数据。我现在的习惯是每处理一个项目,至少存50张真缺陷图和200张误报图,后面受益无穷。

7.4 关于Halcon版本的选择

Halcon不同版本在算子性能和稳定性上有差异。做凸起检测,我建议用18.11以后的版本,dyn_threshold和top_hat的底层优化更好,速度有提升。另外新版本的select_shape支持更多特征,筛选更灵活。如果你还在用很老的版本,遇到性能瓶颈可以考虑升级,但要注意算子兼容性,升级前先备份代码。

最后分享一个小技巧:如果你发现Top-Hat对某种缺陷效果不好,不妨试试lines_gauss算子,它对线状和脊状结构响应很好,有时候凸起边缘会被它捕捉到,作为补充手段挺有用。这个算子参数多,调起来费劲,但关键时刻能救场。

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

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

立即咨询