OpenCV 滤波与卷积之 —— 形态学操作

本文最后更新于:2022年5月21日 凌晨

本文摘录OpenCV 中的卷积、滤波相关操作内容,重点介绍 Opencv 中的形态学操作。

形态学操作

OpenCV 还提供了一种高效且易用的图像形态学变换接口。图像形态学有其特定的发展领域,特别是在计算机视觉发展早期,已经发展出了很多的形态学方法。大部分都是为某个特定目的而产生的,其中一些更是沿用了很长一段时间。基本上,所有的形态学操作都基于两种原始操作,接下来的讲述也将以这两点开始,循序渐进发展到更加复杂的操作,每个更加复杂的操作都将通过前面的方法来表示。

膨胀和腐蚀

最基础的形态学变换是膨胀和腐蚀,它们在许多方面得到了应用,比如消除噪声、元素分割和连接等。基于这两种操作,可以实现更复杂的形态学操作,用来定位强度峰值或孔洞、另一种形式的图像梯度等。

膨胀 / cv2.dilate

膨胀是一种卷积操作,它将目标像素的值替换为卷积核覆盖区域的局部最大值。这是一个非线性核的例子,因此不能用图表表示。通常,膨胀采用的核是一个四边形或圆形的实心核,其锚点在中心。膨胀的作用是使图中填充区域生长,填充为附近的最大值。

$$
\operatorname{dst}(x, y)=\max _{\left(x^{\prime}, y^{\prime}\right): \operatorname{ement}\left(x^{\prime}, y^{\prime}\right) \neq 0} \operatorname{src}\left(x+x^{\prime}, y+y^{\prime}\right)
$$

  • 函数使用
1
2
3
4
5
6
7
8
9
cv2.dilate(
src, # 源图像
kernel[, # 膨胀核
dst[, # 输出图像
anchor[, # 锚在元素中的位置,默认为 (-1, -1),表示锚点在核中心
iterations[, # 膨胀轮数
borderType[, # 像素外推法
borderValue]]]]] # 如果使用常数外推的话需要配置该值
) → dst
  • 示例代码
1
2
3
4
img = mt.cv_rgb_imread('img1.jpg', gray=False)
kernal = np.ones([1, 58])
res = cv2.dilate(img, kernal)
PIS(img, res)

腐蚀 / cv2.erode

与膨胀对应,腐蚀是与之相反的操作,腐蚀操作计算的是核覆盖范围内的局部最小值。

图像的形态学操作通常在阈值化操作后的布尔图像上进行,不过由于膨胀和腐蚀只是最大和最小操作,因此形态学操作也可以在强度图像上进行。

$$
\operatorname{dst}(x, y)=\min _{\left(x^{\prime}, y^{\prime}\right): \operatorname{element}\left(x^{\prime}, y^{\prime}\right) \neq 0} \operatorname{src}\left(x+x^{\prime}, y+y^{\prime}\right)
$$

  • 函数使用
1
2
3
4
5
6
7
8
9
cv2.erode(
src, # 源图像
kernel[, # 腐蚀核
dst[, # 输出图像
anchor[, # 锚在元素中的位置,默认为 (-1, -1),表示锚点在核中心
iterations[, # 腐蚀轮数
borderType[, # 像素外推法
borderValue]]]]] # 如果使用常数外推的话需要配置该值
) → dst
  • 示例代码
1
2
3
4
img = mt.cv_rgb_imread('img1.jpg', gray=False)
kernal = np.ones([1, 58])
res = cv2.erode(img, kernal)
PIS(img, res)

通用形态学函数

cv2.morphologyEx

包含了若干形态学操作的通用函数

官方文档

  • 函数使用
1
2
3
4
5
6
7
8
9
10
cv2.morphologyEx(
src, # 源图像
op, # 形态学操作
kernel[, # 核
dst[, # 输出图像
anchor[, # 锚在元素中的位置,默认为 (-1, -1),表示锚点在核中心
iterations[, # 操作轮数
borderType[, # 像素外推法
borderValue]]]]]) # 如果使用常数外推的话需要配置该值
→ dst
  • op 可选值
操作值 形态学操作
cv2.MORPH_ERODE 腐蚀操作
cv2.MORPH_DILATE 膨胀操作
cv2.MOP_OPEN 开操作
cv2.MOP_CLOSE 闭操作
cv2.MOP_GRADIENT 形态学梯度
cv2.MOP_TOPHAT 顶帽操作
cv2.MOP_BLACKHAT 底帽操作
cv2.MORPH_HITMISS 击中击不中操作

布尔图像上的开操作和闭操作

开操作和闭操作实际上是腐蚀和膨胀操作非常简单的组合。

开运算
  • 开操作先将图像腐蚀,然后对腐蚀的结果膨胀。

闭运输
  • 闭操作先将图像进行膨胀,然后对膨胀的结果进行腐蚀

非布尔图像上的开操作和闭操作

当对一幅非布尔型图像进行形态学操作时,闭操作最明显的效果是消除值小于邻域内的点的孤立异常值,而开操作消除的是大于邻域内点的孤立异常值。

开运算

闭运算

形态学梯度

接下来要叙述的操作是形态学梯度(Morphological Gradient),可以描述为膨胀与腐蚀的差值,用如下表达式描述:
$$
\operatorname{gradient}(\mathrm{src})=\operatorname{dilate}(\mathrm{src})-\operatorname{erode}(\mathrm{src})
$$

形态学梯度通常用于显示明亮区域的边界,然后便可以将他们看作目标或者目标的部分。用扩张的图像减去了收缩的图像,如此一来就找出了完整的边界。这与计算梯度不同,它不会关注某一个物体的周围。

顶帽和黑帽

最后两种操作是顶帽黑帽。这两种操作分别用于显示与其邻域相比更亮或更暗的部分。当试图根据物体的亮度变化分离依附于物体的某些部分时,就会用到这些方法。在生物体或细胞的显微镜图像上经常会用到这些方法。两种操作都是根据基础操作定义的,如下所示:

$$ \begin{array}{c} TopHat (\mathrm{src})=\mathrm{src}-\operatorname{open}(\mathrm{src}) / / Isolate brighter\\ BlackHat (\mathrm{src})=\operatorname{close}(\mathrm{src})-\mathrm{src} / / Isolate dimmer \end{array} $$
顶帽操作

黑帽操作

击中击不中操作

源图像仅支持二值图,会匹配模板,模板中的 1 对应源图的1,模板-1对应源图0,模板0忽略源图像素,按照当前规则匹配源图,匹配成功为1,失败为0,返回结果图

  • 示例代码
1
2
3
4
binary_array = (np.random.random([20, 20]) > 0.5).astype('uint8')
kernal = np.array([[1, -1, 1], [-1, 1, 1], [1, 1, 0]])
res = cv2.morphologyEx(binary_array, cv2.MORPH_HITMISS, kernal)
PIS(binary_array, res, kernal)

自定义核

OpenCV可以让你创建自己的核。在形态学上,核常常称为“构造元素”。因此供开发者创建自定义形态学核的函数叫cv2.getStructuringElement()

  • 函数使用
1
2
3
4
5
cv2.getStructuringElement(
shape, # 核形状
ksize, # 核尺寸
anchor # 锚点位置,默认(-1, -1),表示在核中心
)
  • shape
形状值 元素 描述
cv2.MORPH_RECT 矩形 $\mathrm{E}_{i, j}=1, \forall i, j$
cv2.MORPH_ELLIPSE 椭圆形 以ksize.width和ksize.height为两个半径做椭圆
cv2.MORPH_CROSS 交叉 $\mathrm{E}_{i, j}=1, i=anchor.y \text{ or } j=anchor.x$
  • 示例代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
cross = cv2.getStructuringElement(cv2.MORPH_CROSS, [5,5])
rect = cv2.getStructuringElement(cv2.MORPH_RECT, [5,5])
ellipse = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, [5,5])

-->
cross
array([[0, 0, 1, 0, 0],
[0, 0, 1, 0, 0],
[1, 1, 1, 1, 1],
[0, 0, 1, 0, 0],
[0, 0, 1, 0, 0]], dtype=uint8)

rect
array([[1, 1, 1, 1, 1],
[1, 1, 1, 1, 1],
[1, 1, 1, 1, 1],
[1, 1, 1, 1, 1],
[1, 1, 1, 1, 1]], dtype=uint8)

ellipse
array([[0, 0, 1, 0, 0],
[1, 1, 1, 1, 1],
[1, 1, 1, 1, 1],
[1, 1, 1, 1, 1],
[0, 0, 1, 0, 0]], dtype=uint8)

汇总示例

  • 示例代码
1
2
3
4
5
6
7
8
9
10
11
img = mt.cv_rgb_imread('test.jpg', gray=True)
kernal_1d = cv2.getGaussianKernel(5, 1)
kernal_2d = kernal_1d* kernal_1d.T
erode = cv2.morphologyEx(img, cv2.MORPH_ERODE, kernal_2d)
dilate = cv2.morphologyEx(img, cv2.MORPH_DILATE, kernal_2d)
open = cv2.morphologyEx(img, cv2.MORPH_OPEN, kernal_2d)
close = cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernal_2d)
gradient = cv2.morphologyEx(img, cv2.MORPH_GRADIENT, kernal_2d)
tophat = cv2.morphologyEx(img, cv2.MORPH_TOPHAT, kernal_2d)
blackhat = cv2.morphologyEx(img, cv2.MORPH_BLACKHAT, kernal_2d)
PIS([img, 'origin'], [erode, 'erode'], [dilate, 'dilate'], [open, 'open'], [close, 'close'], [gradient, 'gradient'], [tophat, 'tophat'], [blackhat, 'blackhat'])

示例源码

参考资料

  • 《学习 OpenCV3》 第十章