4. 形态学操作

形态学操作,说白了就是针对二值图像做「形状处理」。我刚开始接触机器视觉时,觉得这玩意儿不就是几个数学公式嘛。直到有一次在产线上处理一个金属表面的划痕检测,光照稍微一变,阈值分割出来的结果就惨不忍睹。后来我用了一组形态学操作,问题就解决了。嗯,从那以后我再也不敢小看这几个基础算子。

今天咱们就聊聊腐蚀、膨胀、开运算和闭运算。它们在缺陷检测里到底怎么用?什么时候该用哪个?我踩过哪些坑?

4.1 腐蚀与膨胀:最基础的两个操作

先看腐蚀。它的作用很简单——让白色区域「瘦一圈」。怎么做到的?用一个结构元素(比如3x3的方形)在图像上滑动,只有当结构元素覆盖的区域全是白色时,中心点才保留白色,否则变黑。

核心公式(腐蚀):

dst(x,y) = min(src(x+x', y+y'))  其中 (x',y') 在结构元素内

说白了:取邻域内的最小值。二值图里最小值就是0(黑),所以只要邻域内有一个黑点,中心就变黑。

膨胀正好相反——让白色区域「胖一圈」。取邻域内的最大值,只要邻域内有一个白点,中心就变白。

我个人习惯的记忆方法:

  • 腐蚀 = 侵蚀边缘,白色收缩
  • 膨胀 = 扩张边界,白色增长

你想想看,腐蚀就像把石头扔进酸里,表面被腐蚀掉一层;膨胀就像吹气球,越吹越大。

4.2 开运算与闭运算:组合拳的威力

开运算 = 先腐蚀后膨胀。闭运算 = 先膨胀后腐蚀。

为什么要组合?因为单独用腐蚀或膨胀,虽然能去掉噪声或填补空洞,但也会改变目标物体的尺寸。开运算和闭运算可以在不显著改变目标面积的前提下,完成噪声去除或空洞填补。

操作 顺序 效果 典型应用场景
开运算 腐蚀 → 膨胀 去除小白点噪声,断开窄连接 去除焊点周围的飞溅、消除毛刺
闭运算 膨胀 → 腐蚀 填补小黑洞,连接断开的区域 填补划痕中的断裂、连接断裂的字符

我曾经踩过的坑:开运算和闭运算的核大小选择非常关键。核太小,效果不明显;核太大,会把缺陷本身也抹掉。我建议从3x3开始试,逐步增大,直到噪声刚好被去除而目标缺陷仍然清晰可见为止。

4.3 在缺陷检测中的具体应用

咱们来看几个我在项目中实际用过的场景。

4.3.1 去除椒盐噪声

阈值分割后,图像上经常会有一些孤立的白点或黑点。这些点不是缺陷,是噪声。怎么办?

  • 白点噪声(白色背景上的黑点):用开运算。先腐蚀去掉小白点,再膨胀恢复目标大小。
  • 黑点噪声(黑色背景上的白点):用闭运算。先膨胀填掉小黑点,再腐蚀恢复目标大小。

我记得有一次做PCB焊点检测,分割后焊点周围全是细小的飞溅。用开运算,核选5x5,跑一遍,飞溅全没了,焊点形状基本不变。效果立竿见影。

4.3.2 连接断裂的缺陷

有些缺陷是连续的,比如划痕、裂纹。但分割后可能因为光照不均或对比度低,导致缺陷断裂成好几段。这时候闭运算就派上用场了。

实际案例:玻璃划痕检测

分割后的划痕断成了3段。我用闭运算,核大小15x3(水平方向拉长),先膨胀把断裂处连接起来,再腐蚀恢复宽度。最终得到一条完整的划痕,后续测量长度就准确了。

4.3.3 分离粘连的物体

有时候两个缺陷挨得太近,分割后连在一起了。这时候可以用腐蚀把它们分开,再用膨胀恢复大小。但要注意:腐蚀过度会把小缺陷直接抹掉。

我建议的做法是:先腐蚀到刚好分离,然后对每个连通域做标记,再分别膨胀回原来的大小。这样既分离了粘连,又保留了每个缺陷的真实尺寸。

4.4 形态学操作的知识体系

下面这张图总结了形态学操作的核心逻辑和应用场景。我把它画成了流程图,方便你理解什么时候该用什么操作。

形态学操作在缺陷检测中的应用决策流程 二值化图像 需要去除噪声还是填补空洞? 去除噪声 → 开运算 填补空洞 → 闭运算 先腐蚀去噪 再膨胀恢复大小 先膨胀填补 再腐蚀恢复大小 核大小选择:从3x3开始,逐步增大

4.5 代码示例:用OpenCV实现形态学操作

下面是一个完整的Python代码示例,展示了如何用OpenCV实现腐蚀、膨胀、开运算和闭运算。我习惯在代码里加上详细的注释,方便以后回头看。

import cv2
import numpy as np

# 读取图像并二值化
img = cv2.imread('defect.jpg', 0)
_, binary = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY)

# 定义结构元素(核)
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))

# 腐蚀
eroded = cv2.erode(binary, kernel, iterations=1)

# 膨胀
dilated = cv2.dilate(binary, kernel, iterations=1)

# 开运算:先腐蚀后膨胀
opened = cv2.morphologyEx(binary, cv2.MORPH_OPEN, kernel)

# 闭运算:先膨胀后腐蚀
closed = cv2.morphologyEx(binary, cv2.MORPH_CLOSE, kernel)

# 显示结果
cv2.imshow('Original', binary)
cv2.imshow('Eroded', eroded)
cv2.imshow('Dilated', dilated)
cv2.imshow('Opened', opened)
cv2.imshow('Closed', closed)
cv2.waitKey(0)
cv2.destroyAllWindows()

我的一点经验:

  • 核的形状也很重要。矩形核适合处理方形缺陷,椭圆核适合处理圆形缺陷,十字核适合处理细长缺陷。
  • iterations参数控制操作次数。一次不够就两次,但别贪多,否则目标形状会严重变形。
  • 如果图像里有多种类型的噪声,可以先用开运算去白点,再用闭运算填黑点,组合使用效果更好。

4.6 形态学操作的局限性

形态学操作虽然好用,但不是万能的。我遇到过几种情况,形态学操作搞不定:

  • 缺陷和噪声大小相近:核选小了去不掉噪声,选大了连缺陷一起抹掉。这时候得换方法,比如用连通域面积过滤。
  • 缺陷形状不规则:固定形状的核可能无法很好地匹配缺陷的几何特征。可以考虑用自适应核或多尺度形态学。
  • 灰度不均匀:形态学操作基于二值图,如果二值化本身就有问题,形态学也救不了。先得把预处理做好。

避坑指南:我曾经在一个项目里,用开运算去除焊点周围的飞溅。核选得太大,结果把焊点边缘也给腐蚀掉了,导致后续的焊点尺寸测量全部偏小。后来我改用先腐蚀分离、再对每个连通域单独膨胀的方法,才解决了问题。记住:形态学操作会改变目标尺寸,如果后续要做精确测量,一定要考虑这个影响。

4.7 小结

形态学操作是缺陷检测中不可或缺的工具。腐蚀和膨胀是基础,开运算和闭运算是它们的组合应用。选对操作、选对核大小、选对迭代次数,就能高效地去除噪声、填补空洞、分离粘连物体。