形态学图像处理(Morphological Image Processing)

形态学图像处理(Morphological Image Processing)

前言

本博客为个人总结数字图像处理一课所写,并给出适当的扩展和相应的demo。

写博客跟做 checkpoint​ 很像,毕竟个人还不能达到那种信手拈来的境界,忘了就是从零开始训练,这就令人有些头疼了。

题外话说完,开始吧。

Note:

  • 笔者环境为 Ubuntu 24.04 LTS
  • 延续以往的写作风格,我会用自己的话来理解,而非简单直接地贴上原本的概念(目的不是写paper),那当然是尽量侧重通俗易懂了,除非我才疏学浅,没词了
  • 令人感到悲哀的是,我所谓的数字图像处理课的教材没这玩意(反正我也基本不用),我得参考一些资料,尤其是英文的资料。


目录

文章目录

  • 形态学图像处理(Morphological Image Processing)
    • 前言
    • 目录
    • 什么叫形态学图像处理(Morphological Image Processing)
    • 集合论须知的公式
    • 结构元素(structuring element)
      • 简要介绍
      • 邻域“窗口”算子(Neighborhood "window" operator)与结构元素
      • 结构元素的三大属性的影响
    • 简述算子是什么
    • 二值形态学的基本运算
      • 腐蚀(Erosion)
        • 腐蚀的解释与公式
        • 腐蚀的应用
        • 腐蚀操作的demo实现
      • 膨胀(Dilation)
        • 膨胀的解释与公式
        • 膨胀的应用
        • 膨胀操作的demo实现
      • 膨胀与腐蚀的关系
      • 开运算(Opening)
        • 开运算的简介
        • 开运算的demo实现
      • 闭运算(Closing)
        • 闭运算的简介
        • 闭运算的demo实现
      • 开闭运算的侧重
      • 顶帽运算(Top Hat)
        • 顶帽运算的简介
        • 顶帽运算的demo实现
      • 黑帽运算(Black Hat)
        • 黑帽运算的简介
        • 黑帽运算的demo实现
      • 梯度运算(Gradient)
        • 梯度运算的简介
        • 梯度运算的demo实现
      • 击中击不中变换(Hit - miss filter)
        • 击中击不中变换的解释与公式
        • 击中击不中变换的应用
    • 灰度形态学的基本运算
      • 灰度图像
      • Flat/一般腐蚀算子
      • Flat/一般膨胀算子
      • Flat腐蚀和膨胀算子和一般腐蚀和膨胀算子
    • 形态学边缘检测(Morphological edge detectors)
      • 简要介绍
      • 对于二值图像
      • 对于灰度图像
    • 参考资料
      • data
      • blog and related sources


什么叫形态学图像处理(Morphological Image Processing)

什么是形态学图像处理呢?

我们打一个比方,我们讲皮肉骨,图像的形态就好比是皮肉,支撑起皮肉的是骨头,我们的形态学处理就是做生物改造,通过改变里面的骨头,进而改变其外在的皮肉的表现。

怎么改变骨头呢?换骨头,按照定好的规则对于原本的骨头用定制的骨头进行替换。

简而言之,就是侧重于图像”皮肉“下的”骨头“的处理。

下面是正式一点的介绍:

形态学图像处理在图像分析中具有重要地位,广泛应用于计算机视觉、图像分割、特征提取等领域。

其主要操作包括膨胀、腐蚀、开闭运算、边缘检测等,这些操作基于集合理论,通过定义结构元素对图像进行处理,能够有效提取图像的形状、结构和特征信息,为后续的分析和理解奠定基础。


集合论须知的公式

概念,如无必要,就直接上公式

  1. 集合的并(Union):

A ∪ B = { x ∣ x ∈ A  or  x ∈ B } A \cup B = \{x \mid x \in A \text{ or } x \in B\} AB={xxA or xB}

  1. 集合的交(Intersection):

A ∩ B = { x ∣ x ∈ A  and  x ∈ B } A \cap B = \{x \mid x \in A \text{ and } x \in B\} AB={xxA and xB}

  1. 集合的补(Complement):

A ‾ = { x ∣ x ∉ A } \overline{A} = \{x \mid x \notin A\} A={xx/A}

  1. 集合的差(Difference):

A − B = { x ∣ x ∈ A  and  x ∉ B } A - B = \{x \mid x \in A \text{ and } x \notin B\} AB={xxA and x/B}

  1. 集合的反射(Reflection):

A ′ = { ( − x , − y ) ∣ ( x , y ) ∈ A } A^{'} = \{(-x, -y) \mid (x, y) \in A \} A={(x,y)(x,y)A}

  1. 集合的平移(Translation):

A + y = { ( x + y 1 , y + y 2 ) ∣ ( x , y ) ∈ A } A + \mathbf{y} = \{ (x + y_1, y + y_2) \mid (x, y) \in A \} A+y={(x+y1,y+y2)(x,y)A}


结构元素(structuring element)

简要介绍

提到图像形态学处理,我们绕不开的一个概念是结构元素,所谓的结构元素(也称核或模板)是一个小的二值(binary)图像,用于与输入图像进行数学操作,以提取图像的特定形状特征

什么是二值图像呢?就是一个尺寸较小 { 3 × 3 , 5 × 5 , … } \{3\times3,5\times5,\ldots\} {3×3,5×5,} 的图像(或者叫方阵),只包含0(背景,黑色),1(前景,白色)两种像素值。它是灰度/彩色图像分析系统中的中间抽象,常用于阈值分割、判断图像属性等,如文本和线条图形、文档图像处理。

结构元素有多种形状,常见的为方形圆形十字形线形等等。

确定了结构元素的大小和形状后,我们还需要确定结构元素中的原点(锚点),所谓原点,就是结构元素中用于与输入图像对齐的参考点,具体描述就是,结构元素跟原图像某个位置按运算的规则匹配上后,就会基于锚点和运算进行相应的像素值的处理。

综上,结构元素有三大属性,大小形状原点


邻域“窗口”算子(Neighborhood “window” operator)与结构元素

邻域“窗口”算子,同结构元素密切相关,其公式表示为:

W { f [ x , y ] } = { f [ x − x ′ , y − y ′ ] : [ x ′ , y ′ ] ∈ ∏ x y } W\{f[x, y]\}=\left\{f\left[x-x', y-y'\right]:\left[x', y'\right] \in \prod_{x y}\right\} W{f[x,y]}={f[xx,yy]:[x,y]xy}

这里, f [ x , y ] f[x, y] f[x,y] 是图像在坐标 ( x , y ) (x, y) (x,y) 处的像素值, Π x y \Pi_{xy} Πxy 定义了一个特定的邻域范围,它确定了在处理像素 f [ x , y ] f[x, y] f[x,y] 时,需要考虑的周围像素的集合范围。

它与结构元素的不同在于,它是用结构元素去选取图像中的像素集合,再根据具体的形态学操作对这些像素值进行组合计算,而结构元素本身并不进行计算操作,个人以为,这也是为什么结构元素又被叫做模板的原因。

说的通俗点,拿印章举例,结构元素就是印章的图案,邻域“窗口”算子则是印章的印油

这个公式也有两个变种,这两个变种,我们会在后面的二值形态学的基本运算中用到。

W ( − ) { f [ x , y ] } = { f [ x − x ′ , y − y ′ ] : [ x ′ , y ′ ] ∈ ∏ x y } W^{\left(-\right)}\{f[x,y]\}=\left\{f\left[x-x^{\prime},y-y^{\prime}\right]:\left[x^{\prime},y^{\prime}\right]\in\prod_{xy}\right\} W(){f[x,y]}={f[xx,yy]:[x,y]xy}

  1. 根据结构元素 Π x y \Pi_{xy} Πxy 的形状和大小,确定图像 f f f 中以 ( x , y ) (x, y) (x,y) 为中心的邻域->
  2. 对于结构元素 Π x y \Pi_{xy} Πxy 中的每一个点 ( x ′ , y ′ ) (x', y') (x,y) 计算图像 f f f 中对应的点 ( x − x ′ , y − y ′ ) (x - x', y - y') (xx,yy)->
  3. 获取该点的像素值 f [ x − x ′ , y − y ′ ] f[x - x', y - y'] f[xx,yy] ->
  4. 将所有这些像素值收集到一个集合中,这个集合就是 W ( − ) { f [ x , y ] } W^{(-)}\{f[x,y]\} W(){f[x,y]}​。

W ( + ) { f [ x , y ] } = { f [ x + x ′ , y + y ′ ] : [ x ′ , y ′ ] ∈ ∏ x y } W^{\left(+\right)}\{f[x,y]\}=\left\{f\left[x+x^{\prime},y+y^{\prime}\right]:\left[x^{\prime},y^{\prime}\right]\in\prod_{xy}\right\} W(+){f[x,y]}={f[x+x,y+y]:[x,y]xy}

同理。


结构元素的三大属性的影响

前面我们提到,结构元素有三大属性,那么这三大属性会怎样影响最后的处理效果呢?

大小,越大说明什么,变化大,影响的范围大,形态学的操作效果会更明显,对原本图像的“骨架”进行更大影响,相应的细节就可能在处理中发生丢失,小的话,则相反。

形状,不同的形状,意味着处理的侧重不同,结构元素的形状,决定了改造后的图像的“骨架”的倾向。

原点,可以理解为我们的工作中心,中心在哪里,处理的结果的落脚点就在在哪里。原点的不同使得最终“骨架”的变化方向,细节处理和特征的匹配有显著的影响。


简述算子是什么

我想,很多同我一样的初学者会有一个疑问,什么是算子?

我们知道函数,简单来说,函数的构成是输入,特定的规则,和将输入经过规则变换得到的结果。

算子的构成则是输入的函数,特定的规则,和将输入的函数经过规则变换得到的另一个函数的映射或操作规则。

换而言之,算子处理的对象,是函数


二值形态学的基本运算

二值形态学的基本运算包括腐蚀、膨胀、开运算和闭运算。这些运算在图像处理中用于提取图像的形状特征,如边界、区域和连通性等。

二值图像中像素用 0 0 0 (背景,黑色)和 1 1 1 (前景,白色)表示,是灰度/彩色图像分析系统中的中间抽象。

有一点值得注意,各种形态学的运算操作,都是基于腐蚀和膨胀操作的组合。

因此,我们可以说,在二值形态学下和灰度形态学下的形态学运算,我们只需要专门关注腐蚀和膨胀操作的不同即可,其余的组合运算就不需要反复强调。


腐蚀(Erosion)

腐蚀的解释与公式

首先我们得回答一个问题,为什么叫腐蚀?

字面意思来讲,就是将一个东西进行“消减”的操作,那么首先想到的效果就是,“变细”或者“变瘦”。在我们的二值图像中的表现,就是将前景物体(像素值为 1 1 1 )缩小。

把公式先端上来:

g [ x , y ] = A N D [ W ( + ) { f [ x , y ] } ] : = e r o d e ( f , W ) g[x,y]=AND[W^{(+)}\{f[x,y]\}]:=erode(f,W) g[x,y]=AND[W(+){f[x,y]}]:=erode(f,W)

其含义为对于每个像素 f [ x , y ] f[x, y] f[x,y] W ( + ) W^{(+)} W(+) 按照 Π x y \Pi_{xy} Πxy 定义的邻域范围移动结构元素,选取结构元素覆盖到的像素值进行逻辑与 A N D AND AND 操作。

当结构元素 W ( + ) W^{(+)} W(+) 在图像上移动时,只有当结构元素覆盖区域内的所有像素值都为 1 1 1 时,在腐蚀后的图像 g [ x , y ] g[x,y] g[x,y] 中,对应结构元素中心位置的像素值才为 1 1 1;只要该区域内有一个像素值为 0 0 0 ,那么腐蚀后对应位置的像素值就为 0 0 0

我想,得画图才能表示地更加清晰。

QQ20241113-142008

通过上图,再直观一点说,腐蚀操作是用结构元素扫描图像中的每一个像素,当结构元素与图像中对应区域完全匹配时,结构元素中心所对应的像素在腐蚀后的图像中才被置为 1 1 1,否则被置为 0 0 0

对于上面的公式,我们可能比较陌生,我们更熟悉的可能是下面这个公式:

A ⊖ B = { x ∣ ( B ) y ⊆ A } A \ominus B = \{ x \mid (B)_{y} \subseteq A \} AB={x(B)yA}

其中:

  • A A A:表示原始的二值图像,其中包含前景(值为1的像素)和背景(值为0的像素)。
  • B B B:表示结构元素,它是一个较小的二值图像,用于探测 A A A 中的特定形状或特征。
  • ⊖ \ominus :表示腐蚀操作。
  • ( B ) y (B)_{y} (B)y:表示结构元素 B B B 通过平移操作,使其原点移动到空间中的位置 y y y

简单地解释这个公式:若 ( B ) y (B)_{y} (B)y 仍包含于 A A A 中,则其所有 y y y 点组成的集合,称为 A A A B B B 腐蚀。


腐蚀的应用

  1. 去除噪声:去除孤立噪声点
  2. 图像细化:得到骨架结构
  3. 目标分离:分离粘连目标
  4. 特征提取:获取边界信息

腐蚀操作的demo实现

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

def save_pair_imgs(imgs,original_title,processed_title):
    plt.figure(figsize=(10, 8))

    plt.subplot(1, 2, 1)
    plt.imshow(imgs[0],'gray')
    plt.title(original_title)
    plt.xticks([]), plt.yticks([])

    plt.subplot(1, 2, 2)
    plt.imshow(imgs[1],'gray')
    plt.title(processed_title)
    plt.xticks([]), plt.yticks([])

    title = original_title + '_' + processed_title

    plt.savefig(f'../imgs/{title}.png', bbox_inches='tight')
    plt.show()

# 读取图像
image = cv2.imread('../data/Lena.bmp', 0)  # 0表示以灰度模式读取

# 定义结构元素
kernel = np.ones((5,5), np.uint8)

# 腐蚀操作
erosion = cv2.erode(image, kernel, iterations = 1)
save_pair_imgs([image,erosion],'Original Image','Erosion')

效果图如下:

Original Image_Erosion


膨胀(Dilation)

膨胀的解释与公式

膨胀,跟腐蚀相反,它是把图像进行一个“变胖”或者说“变粗”的操作。二值图像中的表现,就是将前景物体扩大。

端上公式:

g [ x , y ] = O R [ W ( − ) { f [ x , y ] } ] : = d i l a t e ( f , W ) g[x,y]=OR[W^{(-)}\{f[x,y]\}]:=dilate(f,W) g[x,y]=OR[W(){f[x,y]}]:=dilate(f,W)

当结构元素 W ( − ) W^{(-)} W() 在图像上移动时,只有当结构元素覆盖区域内的所有像素值都为 0 0 0 时,在膨胀后的图像 g [ x , y ] g[x,y] g[x,y] 中,对应结构元素中心位置的像素值才为 0 0 0;只要该区域内有一个像素值为 1 1 1 ,那么膨胀后对应位置的像素值就为 1 1 1

继续上图:

QQ20241113-171552

直观一点说,膨胀操作是用结构元素扫描图像中的每一个像素,当结构元素与图像中对应区域至少有一个匹配时,结构元素中心所对应的像素在膨胀后的图像中就被置为 1 1 1,否则被置为 0 0 0

同样地,我们通常看到的是下面的公式:

A ⊕ B = { x ∣ ( B ) y ∩ A ≠ ∅ } A \oplus B = \{ x \mid (B)_{y}\cap A \neq \emptyset \} AB={x(B)yA=}

其中:

  • A A A:表示原始的二值图像,其中包含前景(值为1的像素)和背景(值为0的像素)。
  • B B B:表示结构元素,它是一个较小的二值图像,用于探测 A A A 中的特定形状或特征。
  • ⊕ \oplus :表示膨胀操作。
  • ( B ) y (B)_{y} (B)y:表示结构元素 B B B 通过平移操作,使其原点移动到空间中的位置 y y y

简单地解释这个公式:若 ( B ) y (B)_{y} (B)y A A A 的交集不为空,则其所有 y y y 点组成的集合,称为 A A A B B B 膨胀。


膨胀的应用

  • 图像预处理:填充孔洞,连接断裂部分
  • 目标增强:突出物体轮廓,扩大目标区域
  • 形态学梯度计算:获取边缘信息


膨胀操作的demo实现

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

def save_pair_imgs(imgs,original_title,processed_title):
    plt.figure(figsize=(10, 8))

    plt.subplot(1, 2, 1)
    plt.imshow(imgs[0],'gray')
    plt.title(original_title)
    plt.xticks([]), plt.yticks([])

    plt.subplot(1, 2, 2)
    plt.imshow(imgs[1],'gray')
    plt.title(processed_title)
    plt.xticks([]), plt.yticks([])

    title = original_title + '_' + processed_title

    plt.savefig(f'../imgs/{title}.png', bbox_inches='tight')
    plt.show()

# 读取图像
image = cv2.imread('../data/Lena.bmp', 0)  # 0表示以灰度模式读取

# 定义结构元素
kernel = np.ones((5,5), np.uint8)

# 膨胀操作
dilation = cv2.dilate(image, kernel, iterations=1)
save_pair_imgs([image, dilation], 'Original Image', 'Dilation')

效果图如下:

Original Image_Dilation


膨胀与腐蚀的关系

  1. 对偶性

    d i l a t e ( f , W ) = N O T [ e r o d e ( N O T [ f ] , W ) ] dilate(f,W)=NOT[erode(NOT[f],W)] dilate(f,W)=NOT[erode(NOT[f],W)]

    e r o d e ( f , W ) = N O T [ d i l a t e ( N O T [ f ] , W ) ] erode(f,W)=NOT[dilate(NOT[f],W)] erode(f,W)=NOT[dilate(NOT[f],W)]

    也就是说,对前景进行膨胀,相当于对背景进行腐蚀,对背景进行腐蚀,相当于对前景进行膨胀。

  2. 但腐蚀不是膨胀的逆运算

    f [ x , y ] ≠ e r o d e ( d i l a t e ( f , W ) , W ) ≠ d i l a t e ( e r o d e ( f , W ) , W ) f[x,y]\neq erode(dilate(f,W),W)\neq dilate(erode(f,W),W) f[x,y]=erode(dilate(f,W),W)=dilate(erode(f,W),W)


开运算(Opening)

开运算的简介

开运算是先腐蚀后膨胀的组合操作:

o p e n ( f , W ) = d i l a t e ( e r o d e ( f , W ) , W ) open(f,W)=dilate(erode(f,W),W) open(f,W)=dilate(erode(f,W),W)

我们更常见的公式如下:

A ∘ B = ( A ⊖ B ) ⊕ B A \circ B = (A \ominus B) \oplus B AB=(AB)B

其中, A A A 是原始图像, B B B 是结构元素。

前面我们知道,腐蚀可以去除皮肉,露出"骨架",去掉噪声的同时会损失一些细节,那么我们就用膨胀操作,把它们长回来,那不就可以做到去掉噪声,又基本保留物体原本的轮廓了吗(腐蚀和膨胀没有逆运算的性质,所以说是基本)?

简单来说,开运算所做的就是去除小的前景部分(数值为1),也就是去除一些细小突出物、较小的粘连、小面积的噪声,同时保持了物体的基本形状。


开运算的demo实现

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

def save_pair_imgs(imgs,original_title,processed_title):
    plt.figure(figsize=(10, 8))

    plt.subplot(1, 2, 1)
    plt.imshow(imgs[0],'gray')
    plt.title(original_title)
    plt.xticks([]), plt.yticks([])

    plt.subplot(1, 2, 2)
    plt.imshow(imgs[1],'gray')
    plt.title(processed_title)
    plt.xticks([]), plt.yticks([])

    title = original_title + '_' + processed_title

    plt.savefig(f'../imgs/{title}.png', bbox_inches='tight')
    plt.show()

# 读取图像
image = cv2.imread('../data/Lena.bmp', 0)  # 0表示以灰度模式读取

# 定义结构元素
kernel = np.ones((5,5), np.uint8)

# 开运算操作
opening = cv2.morphologyEx(image, cv2.MORPH_OPEN, kernel)
save_pair_imgs([image, opening], 'Original Image', 'Opening')

效果图如下:

Original Image_Opening


闭运算(Closing)

闭运算的简介

闭运算是先膨胀后腐蚀的组合操作:

c l o s e ( f , W ) = e r o d e ( d i l a t e ( f , W ) , W ) close(f,W)=erode(dilate(f,W),W) close(f,W)=erode(dilate(f,W),W)

我们更常见的公式如下:

A ∙ B = ( A ⊕ B ) ⊖ B A \bullet B = (A \oplus B) \ominus B AB=(AB)B

其中, A A A 是原始图像, B B B 是结构元素。

我们为什么要闭运算?先膨胀,去除边界凹陷,包含掉背景噪声,填充孔洞,连接相邻,再腐蚀,就能将包含的背景噪声去除,也保留物体原本的主要特征。

闭运算去除的是小的背景部分(数值为0)。


闭运算的demo实现

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

def save_pair_imgs(imgs,original_title,processed_title):
    plt.figure(figsize=(10, 8))

    plt.subplot(1, 2, 1)
    plt.imshow(imgs[0],'gray')
    plt.title(original_title)
    plt.xticks([]), plt.yticks([])

    plt.subplot(1, 2, 2)
    plt.imshow(imgs[1],'gray')
    plt.title(processed_title)
    plt.xticks([]), plt.yticks([])

    title = original_title + '_' + processed_title

    plt.savefig(f'../imgs/{title}.png', bbox_inches='tight')
    plt.show()

# 读取图像
image = cv2.imread('../data/Lena.bmp', 0)  # 0表示以灰度模式读取

# 定义结构元素
kernel = np.ones((5,5), np.uint8)

# 闭运算操作
closing = cv2.morphologyEx(image, cv2.MORPH_CLOSE, kernel)
save_pair_imgs([image, closing], 'Original Image', 'Closing')

效果图如下:

Original Image_Closing


开闭运算的侧重

闭运算的侧重点,是去除小的背景部分保留主要​**特征**。

开运算的侧重点,是去除小的前景部分保留主要​**轮廓**。


顶帽运算(Top Hat)

顶帽运算的简介

顶帽运算也称为礼帽运算,它是原图像与开运算结果的差值。

其公式定义如下:

T = f − ( f ∘ b ) T = f - (f \circ b) T=f(fb)

顶帽运算能够提取出图像中比周围结构元素尺寸范围内背景更亮的部分,它主要用于分离图像中的明亮部分(如比周围区域亮的物体或噪声)与整体背景。

也就是,分离出噪声信息或者比元素图像更亮的边缘信息。


顶帽运算的demo实现

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

def save_pair_imgs(imgs,original_title,processed_title):
    plt.figure(figsize=(10, 8))

    plt.subplot(1, 2, 1)
    plt.imshow(imgs[0],'gray')
    plt.title(original_title)
    plt.xticks([]), plt.yticks([])

    plt.subplot(1, 2, 2)
    plt.imshow(imgs[1],'gray')
    plt.title(processed_title)
    plt.xticks([]), plt.yticks([])

    title = original_title + '_' + processed_title

    plt.savefig(f'../imgs/{title}.png', bbox_inches='tight')
    plt.show()

# 读取图像
image = cv2.imread('../data/Lena.bmp', 0)  # 0表示以灰度模式读取

# 定义结构元素
kernel = np.ones((5,5), np.uint8)

# Top Hat 操作
tophat = cv2.morphologyEx(image, cv2.MORPH_TOPHAT, kernel)
save_pair_imgs([image, tophat], 'Original Image', 'Top Hat')

效果图如下:

Original Image_Top Hat


黑帽运算(Black Hat)

黑帽运算的简介

黑帽运算的功能与顶帽运算相反,它能够提取出图像中比周围结构元素尺寸范围内背景更暗的部分,主要用于分离图像中的暗部分(如比周围区域暗的物体或阴影)与整体背景。

也就是,获取图像内部的小孔,前景色中的小黑点,或者比原始图像的边缘更暗的边缘部分。

黑帽运算是闭运算结果与原图像的差值。

其公式定义如下:

B = ( f ⋅ b ) − f B=(f \cdot b) - f B=(fb)f


黑帽运算的demo实现

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

def save_pair_imgs(imgs,original_title,processed_title):
    plt.figure(figsize=(10, 8))

    plt.subplot(1, 2, 1)
    plt.imshow(imgs[0],'gray')
    plt.title(original_title)
    plt.xticks([]), plt.yticks([])

    plt.subplot(1, 2, 2)
    plt.imshow(imgs[1],'gray')
    plt.title(processed_title)
    plt.xticks([]), plt.yticks([])

    title = original_title + '_' + processed_title

    plt.savefig(f'../imgs/{title}.png', bbox_inches='tight')
    plt.show()

# 读取图像
image = cv2.imread('../data/Lena.bmp', 0)  # 0表示以灰度模式读取

# 定义结构元素
kernel = np.ones((5,5), np.uint8)

# Black Hat 操作
blackhat = cv2.morphologyEx(image, cv2.MORPH_BLACKHAT, kernel)
save_pair_imgs([image, blackhat], 'Original Image', 'Black Hat')

效果图如下:

Original Image_Black Hat


梯度运算(Gradient)

梯度运算的简介

什么是梯度运算,梯度,我们下意识可以想到高数中的梯度。

梯度是一个向量,其方向指向标量函数增长最快的方向,其大小(或长度)表示该方向上的增长速率。

边缘通常是图像中物体与背景之间像素值变化剧烈的地方,因此,梯度运算可以突出图像中的边缘信息(位置和强度)

梯度运算的公式很多,常见的基本差分梯度公式如下:

  • 对于二维离散图像 f ( x , y ) f(x,y) f(x,y)

    • 水平方向梯度: G x = f ( x + 1 , y ) − f ( x , y ) G_x = f(x + 1,y) - f(x,y) Gx=f(x+1,y)f(x,y),它衡量了图像在水平方向上相邻像素值的变化。例如,在一个灰度图像中,如果从左到右像素值逐渐增加,那么 G x G_x Gx 在相应位置将为正值,表示从暗到亮的变化趋势;反之,如果像素值逐渐减小, G x G_x Gx 为负值,表示从亮到暗的变化。
    • 垂直方向梯度: G y = f ( x , y + 1 ) − f ( x , y ) G_y = f(x,y + 1) - f(x,y) Gy=f(x,y+1)f(x,y),用于检测图像在垂直方向上的像素值变化情况。与 G x G_x Gx​ 类似,其正负值反映了垂直方向上像素值的增减趋势。
    • 总的梯度幅值 G = G x 2 + G y 2 G = \sqrt{G_x^2 + G_y^2} G=Gx2+Gy2 ,这种计算方式综合考虑了水平和垂直方向的梯度变化,得到一个标量值,表示每个像素点的梯度大小。梯度值越大,说明该像素点处图像的变化越剧烈,越有可能是边缘位置。


梯度运算的demo实现

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

def save_pair_imgs(imgs,original_title,processed_title):
    plt.figure(figsize=(10, 8))

    plt.subplot(1, 2, 1)
    plt.imshow(imgs[0],'gray')
    plt.title(original_title)
    plt.xticks([]), plt.yticks([])

    plt.subplot(1, 2, 2)
    plt.imshow(imgs[1],'gray')
    plt.title(processed_title)
    plt.xticks([]), plt.yticks([])

    title = original_title + '_' + processed_title

    plt.savefig(f'../imgs/{title}.png', bbox_inches='tight')
    plt.show()

# 读取图像
image = cv2.imread('../data/Lena.bmp', 0)  # 0表示以灰度模式读取

# 定义结构元素
kernel = np.ones((5,5), np.uint8)

# 梯度操作
gradient = cv2.morphologyEx(image, cv2.MORPH_GRADIENT, kernel)
save_pair_imgs([image, gradient], 'Original Image', 'Gradient')

效果图如下:

Original Image_Gradient


击中击不中变换(Hit - miss filter)

击中击不中变换的解释与公式

击中击不中变换用于在二值图像中检测特定形状和结构,它基于结构元素与图像像素的匹配情况来确定目标形状或结构的位置。

击中击不中变换需要使用两个结构元素:结构元素 V V V(用于匹配前景,目标元素)和结构元素 W W W (用于匹配背景,目标元素的周围的背景元素)。对于图像中的每个像素点,当以该像素点为中心的图像区域与结构元素 V V V 在前景部分完全匹配,同时与结构元素 W W W 在背景部分完全匹配时,该像素点被标记为目标形状或结构的位置。

设二值图像为 f f f ,击中击不中变换后的图像为 g g g ,则击中击不中变换可以表示为:

g = f ⊛ ( V , W ) g = f \circledast (V,W) g=f(V,W)

其中 ⊛ \circledast 表示击中击不中变换操作。

具体计算时,先对图像 f f f 进行腐蚀操作,腐蚀结构元素为 V V V ,得到一个中间结果 f V f_V fV

再对图像 f f f 的补集(即背景部分)进行腐蚀操作,腐蚀结构元素为 W W W ,得到另一个中间结果 f W C f_W^C fWC ;最后将 f V f_V fV f W C f_W^C fWC 进行逻辑与操作,得到击中击不中变换的结果 g g g ,即:

g = ( f ⊖ V ) ∩ ( f C ⊖ W ) g = (f \ominus V) \cap (f^C \ominus W) g=(fV)(fCW)

其中 ⊖ \ominus 表示腐蚀操作, f C f^C fC 表示 f f f 的补集。

我们可以理解为完全匹配,前景和背景元素需要跟设定的完全一致。

上图:

image

Note:在击中击不中变换的结构元中,-1表示背景,1表示前景,0表示不关心


击中击不中变换的应用

  • 形状检测与识别:在工业检测中,用于检测产品表面特定形状的缺陷或标记,如检测电路板上的特定形状的焊点是否完整、有无缺陷等。通过设计合适的结构元素来匹配正常焊点的形状,利用击中击不中变换可以快速准确地找出不符合形状要求的焊点,从而实现产品质量的检测和控制。
  • 纹理分析:在纹理图像分析中,某些纹理特征可能具有特定的形状或结构模式,击中击不中变换可以帮助提取这些纹理特征,用于图像分类、分割等任务。例如在分析纺织品的纹理时,检测其中特定的编织图案或纹理结构,以判断纺织品的质量和类型。
  • 字符识别预处理:在光学字符识别(OCR)系统中,可作为预处理步骤,用于检测字符的特定结构部分,如字母中的孔洞(如字母“o”“p”等中的圆形孔洞)、笔画的端点和交叉点等特征,这些特征信息可以为后续的字符分类和识别提供重要依据,提高字符识别的准确性和效率。


灰度形态学的基本运算

灰度图像

灰度图像与二值图像不同,它的像素值范围为 x ∈ [ 0 − 255 ] , x ∈ Z x\in[0-255],x\in\mathbb{Z} x[0255],xZ,能够表示图像的明暗变化、纹理等细节。

对于灰度图像 f [ x , y ] f[x,y] f[x,y],我们可以通过阈值集合 T θ ( f [ x , y ] ) = { [ x , y ] : f [ x , y ] ≥ θ } , − ∞ < θ < + ∞ T_{\theta}(f[x,y])=\{[x,y]:f[x,y]\geq\theta\},-\infty<\theta<+\infty Tθ(f[x,y])={[x,y]:f[x,y]θ},<θ<+ 来分解图像,原始图像可由 f [ x , y ] = s u p { θ : [ x , y ] ∈ T θ ( f [ x , y ] ) } f[x,y]=sup\{\theta:[x,y]\in T_{\theta}(f[x,y])\} f[x,y]=sup{θ:[x,y]Tθ(f[x,y])} 重建。

阈值集合表示的是图像中灰度值大于等于的 θ \theta θ 所有像素点的集合。如果 θ = 128 \theta=128 θ=128 ,那么 T 128 ( f [ x , y ] ) T_{128}(f[x,y]) T128(f[x,y]) 就是图像中所有灰度值大于等于 128 128 128 的像素点的集合。

简单来说,对于灰度图像,我们可以通过一个叫做阈值化的操作来分解图像,然后可以通过 supremum​ 操作重建图像。


Flat/一般腐蚀算子

灰度形态学中,Flat腐蚀算子定义如下:

g [ x , y ] = min ⁡ { W ( + ) { f [ x , y ] } } : = e r o d e ( f , W ) g[x,y]=\min\{W^{(+)}\{f[x,y]\}\}:=erode(f,W) g[x,y]=min{W(+){f[x,y]}}:=erode(f,W)

其中:

  • f [ x , y ] f[x,y] f[x,y] 是原始的灰度图像在坐标 ( x , y ) (x,y) (x,y) 处的像素值
  • W ( + ) W^{(+)} W(+) 是结构元素相关的邻域“窗口”算子
  • g [ x , y ] g[x,y] g[x,y] 是腐蚀后的图像在坐标 ( x , y ) (x,y) (x,y) 处的像素值

其含义为,在图像的每个像素位置,取结构元素窗口 W W W 内的最小值作为该像素的新值。

那么,图像中的暗区域就会扩大,而明亮区域就会缩小(因为变成小值了,越小越暗)。

也就是,暗大明小。

灰度形态学中,一般腐蚀算子定义如下:

g [ x , y ] = inf ⁡ α , β { f [ x + α , y + β ] − w [ α , β ] } = e r o d e ( f , w ) g[x,y]=\inf_{\alpha,\beta}\{f[x+\alpha,y+\beta]-w[\alpha,\beta]\}=erode(f,w) g[x,y]=α,βinf{f[x+α,y+β]w[α,β]}=erode(f,w)

这里 inf​ 表示取下确界(类似最小值)。

其含义为,对于图像 f [ x , y ] f[x,y] f[x,y] 中的每个像素点 ( x , y ) (x,y) (x,y) ,要在结构元素 w [ α , β ] w[\alpha,\beta] w[α,β] 所覆盖的邻域内进行计算,即计算 f [ x + α , y + β ] f[x+\alpha,y+\beta] f[x+α,y+β](即图像在平移 ( α , β ) (\alpha,\beta) (α,β) 后的像素值)与(结构元素在 ( α , β ) (\alpha,\beta) (α,β) 位置的值)的差值,并取这些差值中的下确界作为像素点 ( x , y ) (x,y) (x,y) 经过腐蚀后的新值 g [ x , y ] g[x,y] g[x,y]


Flat/一般膨胀算子

灰度形态学中,Flat膨胀算子定义如下:

g [ x , y ] = max ⁡ { W ( − ) { f [ x , y ] } } : = d i l a t e ( f , W ) g[x,y]=\max\{W^{(-)}\{f[x,y]\}\}:=dilate(f,W) g[x,y]=max{W(){f[x,y]}}:=dilate(f,W)

与Flat腐蚀算子相反,它所做的是在图像的每个像素位置,取结构元素窗口 W W W 内的最大值作为该像素的新值。

那么,图像中的明亮区域就会扩大,而暗区域就会缩小(因为变成大值了,越大越亮)。

也就是,明大暗小。

灰度形态学中,一般膨胀算子定义如下:

g [ x , y ] = s u p α , β { f [ x − α , y − β ] + w [ α , β ] } = s u p α , β { w [ x − α , y − β ] + f [ α , β ] } g[x,y]=sup_{\alpha,\beta}\{f[x-\alpha,y-\beta]+w[\alpha,\beta]\}=sup_{\alpha,\beta}\{w[x-\alpha,y-\beta]+f[\alpha,\beta]\} g[x,y]=supα,β{f[xα,yβ]+w[α,β]}=supα,β{w[xα,yβ]+f[α,β]}

这里的 s u p sup sup 表示取上确界(类似于取最大值)。对于图像 f [ x , y ] f[x,y] f[x,y] 中的每个像素点 ( x , y ) (x,y) (x,y),在结构元素 w [ α , β ] w[\alpha,\beta] w[α,β] 的邻域内,计算 f [ x − α , y − β ] f[x-\alpha,y-\beta] f[xα,yβ](图像平移 ( − α , − β ) (-\alpha,-\beta) (α,β) 后的像素值)与 w [ α , β ] w[\alpha,\beta] w[α,β] 的和,并取这些和中的上确界作为像素点 ( x , y ) (x,y) (x,y) 经过膨胀后的新值 g [ x , y ] g[x,y] g[x,y]


Flat腐蚀和膨胀算子和一般腐蚀和膨胀算子

从名字上可以猜到,Flat算子其实是一般算子的特殊情况。

用途上就是,没特殊需要或者精度要求就用Flat算子简单处理即可,否则就要使用计算更加复杂的一般算子来处理。


形态学边缘检测(Morphological edge detectors)

简要介绍

我们知道,腐蚀去皮肉留下骨头,膨胀让骨头长出肉,那么我们将膨胀后的结果与腐蚀后的结果进行差异的比较,那么是不是就可以直接得到外面的皮肉的轮廓了?

正式点说,对原图像腐蚀会减小前景物体,对原图像膨胀会增大前景物体,那么两者的差异则是物体的边界轮廓。

但是有个前提如下:

  • e r o d e ( f , W ) ≠ f   A N D   d i l a t e ( f , W ) ≠ e r o d e ( f , W ) erode(f,W)\neq f \space AND \space dilate(f,W)\neq erode(f,W) erode(f,W)=f AND dilate(f,W)=erode(f,W)

在图像中,边缘检测可突出物体轮廓,有助于后续的目标识别、图像分割等任务,如检测图像中物体的边界。

  • 优点:保持边缘连通性,对噪声鲁棒性较好
  • 缺点:边缘定位精度相对较低,依赖于结构元素的选择


对于二值图像

g e d g e [ x , y ] = g d i l a t e [ x , y ]   X O R   g e r o d e [ x , y ] g_{edge}[x,y] = g_{dilate}[x,y] \space XOR \space g_{erode}[x,y] gedge[x,y]=gdilate[x,y] XOR gerode[x,y]

我们知道,二值图像只有 { 0 , 1 } \left\lbrace0,1\right\rbrace {0,1} 两种值,那么我们将膨胀运算后的结果与腐蚀运算后的结果进行逻辑异或运算就可以精确地提取出边缘像素,进而直接显示出物体的轮廓边界。


对于灰度图像

g e d g e [ x , y ] = g d i l a t e [ x , y ] − g e r o d e [ x , y ] g_{edge}[x,y] = g_{dilate}[x,y] - g_{erode}[x,y] gedge[x,y]=gdilate[x,y]gerode[x,y]


参考资料

data

  • Lena图等: http://www.eecs.qmul.ac.uk/~phao/IP/Images/

    检索到的出处: https://blog.csdn.net/nbu_dahe/article/details/114698790

blog and related sources

  • 图像数学形态学的基本原理与代码实现(腐蚀、膨胀、开闭运算):https://blog.csdn.net/deepsprings/article/details/107291741
  • 第8章:形态学操作: https://blog.csdn.net/weixin_57440207/article/details/122647000
  • 斯坦福大学Bernd Girod教授的《数字图像处理》课程的讲义: https://web.stanford.edu/class/ee368/Handouts/Lectures/2015_Autumn/7-Morphological_16x9.pdf

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/919579.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

LeetCode 面试经典 150 题回顾

目录 一、数组 / 字符串 1.合并两个有序数组 &#xff08;简单&#xff09; 2.移除元素 &#xff08;简单&#xff09; 3.删除有序数组中的重复项 &#xff08;简单&#xff09; 4.删除有序数组中的重复项 II&#xff08;中等&#xff09; 5.多数元素&#xff08;简单&am…

项目进度计划表:详细的甘特图的制作步骤

甘特图&#xff08;Gantt chart&#xff09;&#xff0c;又称为横道图、条状图&#xff08;Bar chart&#xff09;&#xff0c;是一种用于管理时间和任务活动的工具。 甘特图由亨利劳伦斯甘特&#xff08;Henry Laurence Gantt&#xff09;发明&#xff0c;是一种通过条状图来…

netty之内存泄露检测

写在前面 本文看下netty内存泄露检测相关内容&#xff0c;当然&#xff0c;这里的内存泄露不是bytebuf对象本身&#xff0c;是bytebuf关联的堆外内存。 1&#xff1a;实战 我们还是使用netty源码的example模块的echo例子&#xff0c;但是我们需要对server的handler稍微做些改…

服务器上部署并启动 Go 语言框架 **GoZero** 的项目

要在服务器上部署并启动 Go 语言框架 **GoZero** 的项目&#xff0c;下面是一步步的操作指南&#xff1a; ### 1. 安装 Go 语言环境 首先&#xff0c;确保你的服务器上已安装 Go 语言。如果还没有安装&#xff0c;可以通过以下步骤进行安装&#xff1a; #### 1.1 安装 Go 语…

如何去掉el-input 中 type=“number“两侧的上下按键

<el-input v-model.trim"row.length" type"number" min"0" placeholder""></el-input> // 如何去掉el-input-number两侧的上下按键 ::v-deep input::-webkit-outer-spin-button, ::v-deep input::-webkit-inner-spin-butt…

前端注册代码

代码 <template><el-card class"register" style"max-width: 480px ; background-color: aliceblue;"><template #header><div class"card-header"><span>注册</span></div></template><el…

【第六课】Rust所有权系统(二)

目录 前言 借用和引用 借用规则 切片和迭代器 总结 前言 上节课介绍了Rust中的所有权系统&#xff0c;简单回顾一下&#xff0c;rust的内存系统系统&#xff0c;每一块内存都有一个主人&#xff0c;主人对这块内存有着读写和释放的权限&#xff0c;当主人离开作用域之后&am…

1024程序员节:永无bug

引言 每年的10月24日是程序员节。这一天不仅是程序员们的节日&#xff0c;更是对整个行业的庆祝与思考。在这个特殊的日子里&#xff0c;我们不仅回顾过去一年的成就与挑战&#xff0c;也展望未来的发展与机遇。本篇文章将围绕程序员节的主题&#xff0c;探讨前端技术的最新动…

STM32设计学生宿舍监测控制系统-分享

目录 前言 一、本设计主要实现哪些很“开门”功能&#xff1f; 二、电路设计原理图 电路图采用Altium Designer进行设计&#xff1a; 三、实物设计图 四、程序源代码设计 五、获取资料内容 前言 本项目旨在利用STM32单片机为核心&#xff0c;结合传感器技术、无线通信技…

Node.js | Yarn下载安装与环境配置

一、安装Node.js Yarn 是 Node.js 下的包管理工具&#xff0c;因此想要使用 Yarn 就必须先下载 Node.js。 推荐参考&#xff1a;Node.js | npm下载安装及环境配置教程 二、Yarn安装 打开cmd&#xff0c;输入以下命令&#xff1a; npm install -g yarn检查是否安装成功&…

【MySQL】MySQL在Centos环境安装

&#x1f525;个人主页&#xff1a; Forcible Bug Maker &#x1f525;专栏&#xff1a; MySQL 目录 &#x1f308;前言&#x1f525;卸载不要的环境&#x1f525;检查系统安装包&#x1f525;卸载这些默认安装包&#x1f525;获取mysql官方yum源&#x1f525;安装mysql yum源…

selenium元素定位校验以及遇到的元素操作问题记录

页面元素定位方法及校验 使用比较多的是通过id、class和xpath来对元素进行定位。在定位前可以现在浏览器验证是否可以找到指定的元素。这样就不用每添加一个元素定位都运行代码来检查定位方式表达式是否正确。 使用XPATH定位 在浏览器F12&#xff0c;找到元素&#xff0c;在元…

LLM文档对话 —— pdf解析关键问题

一、为什么需要进行pdf解析&#xff1f; 最近在探索ChatPDF和ChatDoc等方案的思路&#xff0c;也就是用LLM实现文档助手。在此记录一些难题和解决方案&#xff0c;首先讲解主要思想&#xff0c;其次以问题回答的形式展开。 二、为什么需要对pdf进行解析&#xff1f; 当利用L…

小试牛刀-Anchor安装和基础测试

目录 一、编写目的 二、安装步骤 2.1 安装Rust 设置rustup镜像 安装Rust 2.2 安装node.js 2.3 安装Solana-CLI 2.4 安装Anchor CLI 三、Program测试 四、可能出现的问题 Welcome to Code Blocks blog 本篇文章主要介绍了 [Anchor安装和基础测试] 博主广交技术好友&…

Ubuntu 的 ROS 操作系统 turtlebot3 导航仿真

引言 导航仿真是机器人自动化系统中不可或缺的一部分&#xff0c;能够帮助开发者在虚拟环境中测试机器人在复杂场景下的运动与路径规划。 在 Gazebo 仿真环境中&#xff0c;TurtleBot3 配合 ROS 操作系统提供了强大的导航功能。在进行导航仿真时&#xff0c;首先需要准备地图&…

基于Java Springboot网络相册系统

一、作品包含 源码数据库设计文档万字PPT全套环境和工具资源部署教程 二、项目技术 前端技术&#xff1a;Html、Css、Js、Vue、Element-ui 数据库&#xff1a;MySQL 后端技术&#xff1a;Java、Spring Boot、MyBatis 三、运行环境 开发工具&#xff1a;IDEA/eclipse 数据…

AI 使用心态大转变:如何让 AI 成为日常工具

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

项目-摄像

树莓派摄像头使用方法 Camera教程 https://www.raspi.cc/index.php?cread&id53&page1 nanopc-t4 ​https://www.raspi.cc/index.php?cread&id53&page1 摄像头型号 Raspberry Pi Camera Rev 1.3 检测故障 dmesg | grep -i mipi piNanoPC-T4:~$ dmesg | …

基于SSM的农家乐管理系统+论文示例参考

1.项目介绍 功能模块&#xff1a;管理员&#xff08;农家乐管理、美食信息管理、住宿信息管理、活动信息、用户管理、活动报名、论坛等&#xff09;&#xff0c;普通用户&#xff08;注册登录、活动报名、客房预订、用户评价、收藏管理、模拟支付等&#xff09;技术选型&#…

RabbitMQ消息可靠性保证机制4--消费端限流

7.7 消费端限流 在类似如秒杀活动中&#xff0c;一开始会有大量并发写请求到达服务端&#xff0c;城机对消息进行削峰处理&#xff0c;如何做&#xff1f; 当消息投递的速度远快于消费的速度时&#xff0c;随着时间积累就会出现“消息积压”。消息中间件本身是具备一定的缓冲…