第九章 形态学图像处理

1.前言

本章主要从二值图像的形态学入手,了解了形态学的基础知识,不断组合类似于搭积木的样子得到更多的工具形式,并扩展到灰度图像的处理

2.预备知识

形态学有两类像素集合:目标元素结构元(SE),具体如下:

  • 目标元素:即我们常见的网格像素,通常是网格里的前景像素集合
  • 结构元(SE):按照前景像素和背景像素来定义,有时也包含无关元素

在这里插入图片描述
其中,与结构元有关的概念有反射和平移,令集合为 B B B,则 B B B相对于其原点的反射 B ^ \widehat B B 的定义为: B ^ = { w ∣ w = − b , b ∈ B } \widehat B=\{w|w=-b,b\in B\} B ={ww=b,bB}集合 B B B相对于点 z = ( z 1 . z 2 ) z=(z_1.z_2) z=(z1.z2)定义如下: ( B ) z = { c ∣ c = b + z , b ∈ B } (B)_z=\{c|c=b+z,b\in B\} (B)z={cc=b+z,bB}下面是有关集合反射和平移概念图
请添加图片描述

3.腐蚀和膨胀

腐蚀

腐蚀是一种消除物体边界点的过程。在数字图像中,腐蚀操作通常用于消除噪声和平滑边界。腐蚀操作将物体的边缘向内部收缩,从而消除物体内部的孤立点。令 A A A B B B Z 2 Z^2 Z2的两个集合, B B B A A A的腐蚀定义为: A ⊖ B = { z ∣ ( B ) z ⊆ A } A\ominus B=\{z|(B)_z\subseteq A\} AB={z(B)zA}表示在平移 z z z后的 B B B包含于 A A A的条件下, B B B A A A的腐蚀是所有 z z z的集合。

腐蚀操作的过程如下:

  1. 确定集合 A A A和结构元 B B B,以 B B B的原点从 A A A的左上角开始
  2. 仅当 B B B完全被 A A A包含时,保留 B B B的原点,其余情况不算
  3. 按照 A A A的像素格子一步步自上而下自左而右扫描,重复步骤2
  4. A A A的每个像素格子都扫描完,留下的 B B B原点集合就是 A ⊖ B A\ominus B AB

在这里插入图片描述
如上图,自左而右分别是集合 A A A、结构元 B B B A ⊖ B A\ominus B AB,其中画圈的是结构元 B B B的原点

膨胀

膨胀是与腐蚀相反的过程。在数字图像处理中,膨胀操作用于扩大物体边界,以检测物体的交点和断点。令 A A A B B B Z 2 Z^2 Z2的两个集合, B B B A A A的膨胀定义为: A ⊕ B = { z ∣ ( B ^ ) z ∩ A ≠ ∅ } A\oplus B=\{z|(\widehat B)_z\cap A\ne \varnothing\} AB={z(B )zA=}表示在 B ^ \widehat B B 的前景元素与 A A A的至少一个元素重合的条件下, B B B A A A的膨胀是所有位移 z z z的集合。

膨胀操作的过程如下:

  1. 确定集合 A A A和结构元 B B B,以 B B B的原点从 A A A的左上角开始
  2. B B B的原点在 A A A的范围内时,保留 B B B的所有像素格
  3. 按照 A A A的像素格子一步步自上而下自左而右扫描,重复步骤2
  4. A A A的每个像素格子都扫描完,留下的所有 B B B集合就是 A ⊕ B A\oplus B AB

在这里插入图片描述
如上图,自左而右分别是集合 A A A、结构元 B B B A ⊕ B A\oplus B AB,其中画圈的是结构元 B B B的原点

4.开运算与闭运算

开运算和闭运算都是基于腐蚀和膨胀的组合运算。

开运算

开运算是一种先膨胀后腐蚀的操作。具体来说,它首先对图像进行膨胀操作,使得所有的连通区域扩大,然后对这些区域进行腐蚀操作,消除那些在膨胀过程中产生的小孔或者细线。因此,开运算可以消除图像中的小噪声,并且能够平滑物体的边界

按照上述解释,结构元 B B B对集合 A A A的开运算定义为: A ∘ B = ( A ⊖ B ) ⊕ B A\circ B=(A\ominus B)\oplus B AB=(AB)B

以下是实例

# 读取图像
image = cv2.imread('./img9/Fig0905(a)(wirebond-mask).tif')

# 创建形状为正方形的结构元素
open_image = cv2.morphologyEx(image, cv2.MORPH_OPEN, cv2.getStructuringElement(cv2.MORPH_RECT, (30, 30)))#

plt.figure(figsize=(8,6))

plt.subplot(1, 2, 1)
plt.imshow(image)
plt.title('original_image')
plt.axis('off')

plt.subplot(1, 2, 2)
plt.imshow(closed_image)
plt.title('closed_image')
![请添加图片描述](https://img-blog.csdnimg.cn/ec7fba4d7b83414e993ce562e5b3ffb1.png)


plt.show()

请添加图片描述
可以看到,原图像经过开运算处理后许多细线的地方出现了明显的腐蚀效果,白色细线都没了,圆角部分也变成了平角

闭运算

闭运算是一种先腐蚀后膨胀的操作。具体来说,它首先对图像进行腐蚀操作,使得所有的连通区域缩小,然后对这些区域进行膨胀操作,填充那些在腐蚀过程中产生的空洞或者细线。因此,闭运算可以填充图像中的空洞,并且能够平滑物体的边界

结构元 B B B对集合 A A A的闭运算定义为: A ∙ B = ( A ⊕ B ) ⊖ B A\bullet B=(A\oplus B)\ominus B AB=(AB)B

import cv2
import numpy as np
import matplotlib.pyplot as plt
# 读取图像
image = cv2.imread('./img9/Fig0905(a)(wirebond-mask).tif')

# 创建形状为正方形的结构元素
closing_image = cv2.morphologyEx(image, cv2.MORPH_CLOSE, cv2.getStructuringElement(cv2.MORPH_RECT, (20, 20)))


plt.figure(figsize=(8,6))

plt.subplot(1, 2, 1)
plt.imshow(image)
plt.title('original_image')
plt.axis('off')

plt.subplot(1, 2, 2)
plt.imshow(closing_image)
plt.title('closing_image')
plt.axis('off')

plt.show()

请添加图片描述
与开运算相反,闭运算使图像部分细节更加膨胀,比如一些白色细线部分的顶端都有明显的加粗

5.击中-击不中变换

击中-击不中变换(HMT)通常描述图像中的目标检测,“击中”指找到图像中的特定目标,“击不中”则相反。通过腐蚀可以展示HMT的过程,令 I I I是一幅二值图像,分别使用检测前景和背景的结构元 B 1 B_1 B1 B 2 B_2 B2,则图像 I I I的HMT定义为: I ⊛ B 1 , 2 = { z ∣ ( B 1 ) z ⊆ A 和 ( B 2 ) z ⊆ A c } = ( A ⊖ B 1 ) ∩ ( A c ⊖ B 2 ) I\circledast B_{1,2}=\{z|(B_1)_z\subseteq A 和 (B_2)_z\subseteq A^c \}=(A\ominus B_1)\cap(A^c\ominus B_2) IB1,2={z(B1)zA(B2)zAc}=(AB1)(AcB2)通俗解释下,HMT就是:在 B 1 B_1 B1 B 2 B_2 B2分别在前景和背景中找到了匹配项的条件下,结构元 B 1 B_1 B1 B 2 B_2 B2同时平移z的集合,图例说明如下

请添加图片描述

6.基本形态学算法

在处理二值图像时,形态学的应用主要有提取边界、连通分量、凸壳、区域骨架、区域填充、细化、粗化和修建等算法方法,接下来主要介绍其中几种

边界提取

边界提取算法的原理是:使用合适的结构元 B B B腐蚀 A A A,然后求两者的差,即 β ( A ) = A − ( A ⊖ B ) \beta(A)=A-(A\ominus B) β(A)=A(AB)

孔洞填充

孔洞填充算法由集合膨胀、补集和交集为基础,在一个图像 I I I中集合 A A A元素为8连通边界,用前景像素填充所有的孔洞,用 X k X_k Xk表示当前的填补结果,则 X k = ( X k − 1 ⊕ B ) ∩ I c , k = 1 , 2 , 3... X_k=(X_{k-1}\oplus B)\cap I^c,k=1,2,3... Xk=(Xk1B)Ic,k=1,2,3...其中 B B B表示使用的结构元,每一步的 X k X_k Xk都在前一步的基础上进行填充,如下图所示
请添加图片描述

凸壳

在欧式平面中如果一个集合(图像)是凸的,那么连接集合的任意两点的线段都在集合内部。同理数字图像中由于都是像素形成的格子,如果连接边界点形成的欧几里得线段都包含在前景点集中,那么这个图像就是凸的

用以下式子找到某一图像的凸壳 X k i = ( X k i ⊛ B i ) ∪ X k − 1 i , i = 1 , 2 , 3 , 4 , k = 1 , 2 , 3.... X_k^i=(X_k^i\circledast B^i)\cup X_{k-1}^i,i=1,2,3,4,k=1,2,3.... Xki=(XkiBi)Xk1i,i=1,2,3,4k=1,2,3....其中 B i B^i Bi表示上下左右的四个结构元, A A A的凸壳为使用每个结构元收敛时的 X K i X_K^i XKi并集,即 C ( A ) = ⋃ i = 1 4 D i C(A)=\bigcup_{i=1}^4D^i C(A)=i=14Di

def convex_hull(img):
    img_right = img.copy()  # 用来迭代图像
    img_down = img.copy()
    img_left = img.copy()
    img_up = img.copy()

    kernel_right = np.array([[1, 0, 0], [1, -1, 0], [1, 0, 0]])  # 向右找凸壳的kernel
    kernel_down = np.array([[1, 1, 1], [0, -1, 0], [0, 0, 0]])  # 向下找凸壳的kernel
    kernel_left = np.array([[0, 0, 1], [0, -1, 1], [0, 0, 1]])  # 向左找凸壳的kernel
    kernel_up = np.array([[0, 0, 0], [0, -1, 0], [1, 1, 1]])  # 向上找凸壳的kernel

    while True:  # 右扩充
        imgRight = cv2.morphologyEx(img_right, cv2.MORPH_HITMISS, kernel_right,
                                    borderType=cv2.BORDER_DEFAULT)  # 向右做击中-击不中变换
        ret_right = img_right + imgRight  # 原图 + 变换的结果图

        if (ret_right == img_right).all():  # 两次迭代图像一致的话,处理结束
            X_right = ret_right  # 将向右扩充的图像保存
            break
        img_right = ret_right  # 将上步处理的结果返回到下一次

    while True:  # 下扩充
        imgDown = cv2.morphologyEx(img_down, cv2.MORPH_HITMISS, kernel_down, borderType=cv2.BORDER_DEFAULT)
        ret_down = img_down + imgDown

        if (ret_down == img_down).all():
            X_down = ret_down
            break
        img_down = ret_down

    while True:  # 左扩充
        imgLeft = cv2.morphologyEx(img_left, cv2.MORPH_HITMISS, kernel_left, borderType=cv2.BORDER_DEFAULT)
        ret_left = img_left + imgLeft

        if (ret_left == img_left).all():
            X_left = ret_left
            break
        img_left = ret_left

    while True:  # 上扩充
        imgUp = cv2.morphologyEx(img_up, cv2.MORPH_HITMISS, kernel_up, borderType=cv2.BORDER_DEFAULT)
        ret_up = img_up + imgUp

        if (ret_up == img_up).all():
            X_up = ret_up
            break
        img_up = ret_up

    # 将四次扩充的结果合在一块
    dst = cv2.bitwise_or(X_up, X_down)
    dst = cv2.bitwise_or(dst, X_left)
    dst = cv2.bitwise_or(dst, X_right)

    # 对凸壳进行限制,凸壳不超过前景像素的垂直和水平尺寸
    x, y = np.where(img)
    canvas = np.zeros(img.shape, dtype=np.uint8)
    canvas[x.min():x.max() + 1, y.min():y.max() + 1] = 255  # 产生一个水平和垂直限制的图像
    dst = cv2.bitwise_and(canvas, dst)  # 将凸壳限制

    return dst.astype(np.uint8)


img = cv2.imread('./img9/Fig0914(a)(licoln from penny).tif', 0)
_, img_bin = cv2.threshold(img, 50, 255, cv2.THRESH_BINARY)  # 二值化处理

dst = convex_hull(img_bin)  # 凸壳

# 显示图像
img_diff = dst - img
img_diff[img_diff > 0] = 200
img_diff += img_bin

cv2.imshow('img', np.hstack((img_bin, dst, img_diff)))
cv2.waitKey()
cv2.destroyAllWindows()
# 以上代码转载于https://blog.csdn.net/qq_44886601/article/details/127576903

请添加图片描述
如上述代码实现的效果所示,自左到右依次是原图,凸壳和两者的组合,能看到原图人像都被凸壳包含了

7.形态学重建

之前的形态学只涉及一幅图像,但形态学重建涉及两幅图像和一个结构元,一幅为标记图 F F F,另一幅为模版 G G G,用来约束重建

测地膨胀和腐蚀

测地膨胀指标记图像 F F F与结构元膨胀后和模版 G G G的交集, F F F相对于 G G G大小为1和n的测地膨胀定义分别为 D G ( 1 ) ( F ) = ( F ⊕ B ) ∩ G D_G^{(1)}(F)=(F\oplus B)\cap G DG(1)(F)=(FB)G D G ( n ) ( F ) = D G ( 1 ) [ D G ( n − 1 ) ( F ) ] D_G^{(n)}(F)=D_G^{(1)}[D_G^{(n-1)}(F)] DG(n)(F)=DG(1)[DG(n1)(F)]
请添加图片描述
与测地膨胀相反,测地腐蚀指标记图像 F F F与结构元腐蚀后和模版 G G G的并集, F F F相对于 G G G大小为1和n的测地腐蚀定义分别为 E G ( 1 ) ( F ) = ( F ⊖ B ) ∪ G E_G^{(1)}(F)=(F\ominus B)\cup G EG(1)(F)=(FB)G E G ( n ) ( F ) = E G ( 1 ) [ E G ( n − 1 ) ( F ) ] E_G^{(n)}(F)=E_G^{(1)}[E_G^{(n-1)}(F)] EG(n)(F)=EG(1)[EG(n1)(F)]

请添加图片描述

膨胀和腐蚀形态学重建

F F F相对于 G G G的膨胀重建 R G D ( F ) R_G^D(F) RGD(F) F F F相对于 G G G的测地膨胀,反复迭代至稳定,即 R G D ( F ) = D G ( k ) ( F ) R_G^D(F)=D_G^{(k)}(F) RGD(F)=DG(k)(F)直到 R G ( k ) ( F ) = D G ( k + 1 ) ( F ) R_G^{(k)}(F)=D_G^{(k+1)}(F) RG(k)(F)=DG(k+1)(F)如下图所示

请添加图片描述

G G G相对于 F F F的腐蚀重建 R G E ( F ) R_G^E(F) RGE(F)则相类似,只是把膨胀换成了腐蚀操作

8.灰度级形态学

首先要知道平坦和非平坦结构元含义,平坦结构元指二值灰度的结构元,非平坦表示多灰度值的结构元。结构元 b b b的反射表示为 b ^ ( x , y ) = b ( − x , − y ) \widehat b(x,y)=b(-x,-y) b (x,y)=b(x,y)

灰度腐蚀和膨胀

平坦结构元 b b b原点在 ( x , y ) (x,y) (x,y),则它对图像 f f f的灰度腐蚀和灰度膨胀分别为 [ f ⊖ b ] ( x , y ) = m i n ( s , t ) ∈ b { f ( x + s , y + t ) } [f\ominus b](x,y)=min_{(s,t)\in b}\{f(x+s,y+t)\} [fb](x,y)=min(s,t)b{f(x+s,y+t)} [ f ⊕ b ] ( x , y ) = m a x ( s , t ) ∈ b { f ( x − s , y − t ) } [f\oplus b](x,y)=max_{(s,t)\in b}\{f(x-s,y-t)\} [fb](x,y)=max(s,t)b{f(xs,yt)}而对于非平坦结构元 b N b_N bN,它对图像 f f f的灰度腐蚀和膨胀定义为 [ f ⊖ b N ] ( x , y ) = m i n ( s , t ) ∈ b ^ N { f ( x + s , y + t ) − b ^ N ( s , t ) } [f\ominus b_N](x,y)=min_{(s,t)\in \widehat b_N}\{f(x+s,y+t)-\widehat b_N(s,t)\} [fbN](x,y)=min(s,t)b N{f(x+s,y+t)b N(s,t)} [ f ⊕ b N ] ( x , y ) = m a x ( s , t ) ∈ b ^ N { f ( x − s , y − t ) + b ^ N ( s , t ) } [f\oplus b_N](x,y)=max_{(s,t)\in \widehat b_N}\{f(x-s,y-t)+\widehat b_N(s,t)\} [fbN](x,y)=max(s,t)b N{f(xs,yt)+b N(s,t)}针对灰度膨胀和腐蚀以下是一个实例,可以看到,经过膨胀的后的图像亮特征变大,暗特征变小,左中右的细黑连线几乎已经看不见了。腐蚀的效果则相反
请添加图片描述

灰度开闭运算

在形式上,开操作和闭操作的表达式与二值图像相同 f ∘ b = ( f ⊖ b ) ⊕ b f\circ b=(f\ominus b)\oplus b fb=(fb)b f ∙ b = ( f ⊕ b ) ⊖ b f\bullet b=(f\oplus b)\ominus b fb=(fb)b下图就是开闭运算的实例,可以看到在开运算中,所有亮特征都变小了,但与腐蚀结果不同,开运算对暗特征和背景的影响很小,闭运算则呈现相反的效果

请添加图片描述

灰度级形态学

基本的灰度级形态学主要有平滑、梯度、顶帽变换和底帽变换、粒度测定和纹理分割等,以下选择其中进行操作分析

形态学平滑

import cv2
import numpy as np
import matplotlib.pyplot as plt

f = cv2.imread('./img9/Fig0938(a)(cygnusloop_Xray_original).tif', 0)

plt.subplot(3, 2, 1), plt.imshow(f, cmap='gray'), plt.title('(a)Original Image'),plt.axis('off')

se = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (11, 11))

fo = cv2.morphologyEx(f, cv2.MORPH_OPEN, se)

plt.subplot(3, 2, 2), plt.imshow(fo, cmap='gray'), plt.title('(b)Open By Disk 5'), plt.axis('off')

foc = cv2.morphologyEx(fo, cv2.MORPH_CLOSE, se)

plt.subplot(3, 2, 3), plt.imshow(foc, cmap='gray'), plt.title('(c)Close B'), plt.axis('off')

focd = cv2.morphologyEx(f, cv2.MORPH_CLOSE, se)

plt.subplot(3, 2, 4), plt.imshow(focd, cmap='gray'), plt.title('(d)Close A'), plt.axis('off')

foce = cv2.morphologyEx(focd, cv2.MORPH_OPEN, se)

plt.subplot(3, 2, 5), plt.imshow(foce, cmap='gray'), plt.title('(e)Open D'), plt.axis('off')

fasf = f.copy()

for i in range(2, 6):
    se = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (2 * i + 1, 2 * i + 1))
    fasf = cv2.morphologyEx(cv2.morphologyEx(fasf, cv2.MORPH_OPEN, se), cv2.MORPH_CLOSE, se)

plt.subplot(3, 2, 6), plt.imshow(fasf, cmap='gray'), plt.title('(f)Open Close Alternate Filtering'),plt.axis('off')

plt.tight_layout()
plt.axis('off')
plt.show()

请添加图片描述
上述过程首先对原图进行开运算得到图像b,有点类似于锐化的感觉,中心的细节部分都有较明显的改善,再进行闭操作得到图c,对中心部分进行了模糊,图f是经过开闭操作选择滤波后的最终效果,整体的图像与原图相比产生了较好的平滑效果。

纹理分割

# image = cv2.imread('./img9/FigP0934(blobs_in_circular_arrangement).tif', 0)
import cv2
import numpy as np
import matplotlib.pyplot as plt

def morphological_texture_segmentation(image):
    # 定义结构元素
    kernel_radius_1 = 60
    kernel_radius_2 = 120
    kernel_1 = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (kernel_radius_1, kernel_radius_1))
    kernel_2 = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (kernel_radius_2, kernel_radius_2))
    gradient_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))

    # 第一步:执行闭运算以去除小斑点区域
    closed_image = cv2.morphologyEx(image, cv2.MORPH_CLOSE, kernel_1)

    # 第二步:执行开运算以去除大斑点间亮间距图像
    opened_image = cv2.morphologyEx(closed_image, cv2.MORPH_OPEN, kernel_2)

    # 第三步:计算边界并叠加到原图像上
    gradient_image = cv2.morphologyEx(opened_image, cv2.MORPH_GRADIENT, gradient_kernel)
    segmented_image = cv2.add(image, gradient_image)

    return closed_image, opened_image, segmented_image

# 加载原始图像
image = cv2.imread('./img9/Fig0943(a)(dark_blobs_on_light_background).tif', 0)

# 执行形态学纹理分割
closed_image, opened_image, segmented_image = morphological_texture_segmentation(image)

# 显示结果
plt.figure(figsize=(12, 8))

plt.subplot(2, 2, 1)
plt.imshow(cv2.cvtColor(image, cv2.COLOR_BGR2RGB))
plt.title('(a) Original')

plt.subplot(2, 2, 2)
plt.imshow(closed_image, cmap='gray')
plt.title('(b) Closed')

plt.subplot(2, 2, 3)
plt.imshow(opened_image, cmap='gray')
plt.title('(c) Opened')

plt.subplot(2, 2, 4)
plt.imshow(cv2.cvtColor(segmented_image, cv2.COLOR_BGR2RGB))
plt.title('(d) Segmented')

plt.tight_layout()
plt.show()

请添加图片描述
代码首先使用半径为60像素的圆盘形结构元对原图像进行闭运算,然后使用半径120像素的圆盘形结构元对闭运算结果进行开运算。接下来,使用3x3大小的矩形结构元计算开运算结果和原图像之间的梯度,并将边界叠加到原图像上得到分割图像。最后,使用matplotlib将四张图像显示在一张图上。最后的结果是将大斑点和小斑点分开了

9.章总结

本章学习了形态学的相关知识,从二值图像延伸到灰度图像。腐蚀和膨胀是基础,在腐蚀和膨胀的组合之下形成了开闭运算,并可以组合成更为复杂的工具。在二值图像中,学习到了运用形态学能够执行平滑、边缘检测、凸壳等任务,以及形态学重建,和之前的章节对应了起来,并延伸至灰度图像的相关处理,并简要实现了纹理分割的算法实例

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值