数字图像处理——形态学操作(二值图像篇)

形态学操作

  • 内容简介
  • 1. 预备知识——结构元
  • 2. 形态学基本操作
    • 2.1 腐蚀和膨胀
    • 2.2 开操作与闭操作
    • 2.3 击中和击不中变换(hit and miss transform)
  • 3. 基本的形态学算法
    • 3.1 二值图像边缘提取
    • 3.2 孔洞填充
    • 3.3 连通分量的提取
    • 3.4 凸壳(convex hull)

内容简介

  本篇博客介绍的形态学图像处理操作。形态学在数学语言里是集合论。在图像处理中,形态学是很重要的分支,但是它又不像傅里叶变换等内容,包含详细的公式推导。通常形态学操作的数学表达都比较抽象,不太容易理解。因此,本篇博客打算以文字与实践相结合的方式进行阐述。
  本篇文章内容中,所有具有处理结果图像的形态学操作均是本人根据教材自己编写的代码,由于代码较多,之后会放在个人github中,如在公布之前有需要的读者,可以留言说明。
  本篇内容主要知识和相关操作图片来自冈萨雷斯第三版的《数字图像处理》,如对细节内容感兴趣的读者可以查阅相关书籍进行参考。本篇博文的内容与原书内容的顺序有所出入,如有不足欢迎留言指正,谢谢。

1. 预备知识——结构元

  其实结构元(SE,Structure Element)最直接的理解就是卷积操作中的卷积核,或者是空间域滤波中提到的滤波器。虽然形态学操作中结构元的形状可以是任意的,但是由于在图像操作中,为了方便计算,通常要求结构元是矩形的阵列,对于任意形状的结构元,如果不满足矩形的要求,则用0将其填充为矩形即可。
  另外,结构元内部的有效元素不像滤波器那样有权值,通常结构元中只分为两种元素,就是0和1,不会出现其他数值的系数。(当然对有些算法来说也有例外)。结构元对图像进行的操作也和卷积非常类似,就是由结构元的中心依次滑过图像,然后进行设计好的操作即可。接下来就开始介绍图像处理中常见的形态学操作。

2. 形态学基本操作

  形态学基本操作是形态学最重要的基础,几乎所有复杂的形态学操作都由基本形态学操作组合而成。在本章节中,主要介绍的是二值图像的形态学操作,因此本章中默认图像都是二值图像,且由0表示背景像素,1表示前景像素。
  下面结合集合论的数学表达以及实际使用案例对各种操作进行介绍。

2.1 腐蚀和膨胀

  1).腐蚀(erode)
A ⊖ B = { z ∣ ( B ) z ⊆ A } ⇔ A ⊖ B = { z ∣ ( B ) z ∩ A c = ∅ } A\ominus B=\{z|(B)_z\subseteq A\} \Leftrightarrow A\ominus B=\{z|(B)_z\cap A^c=\empty\} AB={z(B)zA}AB={z(B)zAc=}
  其中, A c A^c Ac是图像A的补集,即1-A。上式表达的意思就是,在z的移动下,如果结构元B能够完全包含在图像A中(完全包含意味着结构元B中的有效元素对应于图像A中的位置全都是前景像素。z的移动就是针对于图像A的所有像素范围的移动。
数字图像处理——形态学操作(二值图像篇)_第1张图片
  上图中,左边实线围住的9x9方格就是结构元B,红色代表其有效区域,白色代表其无效区域(0填充)。右侧实现代表图像A,浅蓝色部分代表A的前景像素,白色代表背景像素。当结构元滑动到1区域时,所有有效区域对应的像素都是前景像素(紫色区域,红+蓝),因此称此时 B ⊆ A B\subseteq A BA,此时,1号位置在结果图像中应该置1。反之,如果只有部分区域重合,如结构元处于2号位置,那么说明此时的2号点不满足腐蚀条件,应该在结果图像中置0。
  下面是腐蚀操作对实际图像的处理效果。

  • 第一张为原图。
  • 第二张为用11x11的全1模板作结构元进行的腐蚀,可以看到,由于细线的宽度小于11像素,所以细线不可能完全包含SE,因此细线部分被“腐蚀”。
  • 第三张图和第二张一样,只是使用的是opencv自带的cv2.erode函数,可以看到,和自己编写的代码结果优点区别,图像边界的轮廓并不是全黑的,因为我自己写的腐蚀操作是带padding的,也就是会根据SE的尺寸对图像周边填0,而教材中和opencv中的腐蚀函数是不对图像进行填充的,它们直接忽略了会使SE越出图像的像素点,因此那些点的像素值保留不变。
  • 第四张图结构元尺寸为15x15,可以看到,连最粗的线都被腐蚀了。
  • 第五张SE尺寸为45x45,就可以将除了中心方块以外的所有元素腐蚀掉。
    数字图像处理——形态学操作(二值图像篇)_第2张图片   数字图像处理——形态学操作(二值图像篇)_第3张图片   数字图像处理——形态学操作(二值图像篇)_第4张图片  数字图像处理——形态学操作(二值图像篇)_第5张图片   数字图像处理——形态学操作(二值图像篇)_第6张图片

  2).膨胀(dilate)
A ⊕ B = { z ∣ ( B ^ ) z ∩ A ≠ ∅ } ⇔ A ⊕ B = { z ∣ [ ( B ^ ) z ∩ A ] ⊆ A } A\oplus B=\{z|(\hat B)_z\cap A\ne\empty\} \Leftrightarrow A\oplus B=\{z|[(\hat B)_z\cap A]\subseteq A\} AB={z(B^)zA=}AB={z[(B^)zA]A}
  其中, B ^ \hat B B^称为结构元B的反射,指的是,结构元B绕结构元的中心(通常设置在SE矩阵中心)旋转180°后的新结构元。对于对称的结构元,就有 B = B ^ B=\hat B B=B^
  膨胀的集合含义就是,SE和图像的前景像素至少有一个交点,那么当前移动到的像素就置1,否则置0。在膨胀的条件下,之前图示中的1,2号区域均会被置1。(腐蚀时,2号区域是被置0的)。
  下面是膨胀操作对实际图像的处理效果。

  • 第一张图是原图,可以看到原图中的字母多数都有断裂现象,这里需要着重观察右下角的“year"单词;
  • 第二张图是opencv自带的cv2.dilate函数,用SE尺寸3x3的核得到的结果。可以看到,膨胀后,所有字母都变粗了(膨胀了),大部分断裂的区域都连接上了。但是这和教材中的处理结果略有不同,因为opencv提供的膨胀操作,不支持自定义结构元,其结构元是3x3的全一矩阵。而教材中使用的SE是3x3的四个角为无效元素的十字型结构元。
  • 第三张图是自己编写的dilate函数,结构元也使用和教材中一模一样,因此结果是和教材里一样的。注意观察可以发现,图3相比于图2膨胀程度更高,因此,"year"字母中的"ea"的断裂修复效果也比图2更好。
    数字图像处理——形态学操作(二值图像篇)_第7张图片 数字图像处理——形态学操作(二值图像篇)_第8张图片 数字图像处理——形态学操作(二值图像篇)_第9张图片

  3).腐蚀和膨胀的对偶性

  • ( A ⊖ B ) c = A c ⊕ B ^ (A\ominus B)^c=A^c \oplus \hat B (AB)c=AcB^
  • ( A ⊕ B ) c = A c ⊖ B ^ (A\oplus B)^c=A^c \ominus \hat B (AB)c=AcB^

2.2 开操作与闭操作

  开操作和闭操作都是基于腐蚀和膨胀操作的。
  1).开操作(opening)
A ∘ B = ( A ⊖ B ) ⊕ B A\circ B=(A\ominus B)\oplus B AB=(AB)B
  开操作就是先用SE腐蚀图像A,再对腐蚀后的结果进行膨胀。第一反应或许会觉得,这操作不是很多余吗?其实细想后发现,其中自有妙处。假设图像中存在很多细小琐碎的颗粒或噪声,那么经过第一步腐蚀后,这些细小的噪声就会被腐蚀而消失,尽管紧接着进行了膨胀操作,但是由于细小颗粒已经完全消失,因此即使膨胀也无法恢复。相反,那些图像中的主体,经过腐蚀后仍然存在,再经过膨胀就可以恢复原样。这么一说,开操作是不是非常有用呢?仔细理解形态学操作的含义非常重要,虽然形态学操作单独拆开看都不是很复杂,但是要在应用中发挥最巧妙的效果,需要使用者对形态学各种操作能得到的效果进行深入理解。否则只会弄巧成拙或完全无法发挥形态学图像处理的威力。
  开操作的性质:

  • A ∘ B A\circ B AB是A的一个子集(子图像);
  • 如果C是D的一个子集,则 C ∘ B C\circ B CB D ∘ B D\circ B DB的一个子集;
  • ( A ∘ B ) ∘ B = A ∘ B (A\circ B)\circ B=A\circ B (AB)B=AB

  以下是开操作的实际操作效果:

  • 图1是原图像,可以看到,在采集指纹的图像中,存在很多不需要的噪声干扰;
  • 图2是使用opencv的cv2.morphologyEx函数进行的图像开操作,其效果和教材中的开操作效果出入较大,猜测原因是opencv提供的形态学函数只支持输入结构元的尺寸(kernel),但是具体该参数如何生成结构元并未说明。所以具体原因需要阅读opencv源码后再进行分析,如果看完源码知道原因了会及时在这里更新。有知道的读者也可以留言说明。因此,这也说明了,不懂得原理没有自己实践实现过算法,只会调用第三方库是很危险的。因为你不理解别人的库中算法是如何实现的,和你实际想要的效果是否一致。因此,对于第三方库,源码阅读是非常必要的。
  • 图3是根据之前编写的腐蚀、膨胀操作代码实现的开操作,可以看到,这个效果和教材中是一模一样的,开操作使得细小的噪声完全消失了,而指纹主体也通过膨胀很好的恢复了。

数字图像处理——形态学操作(二值图像篇)_第10张图片 数字图像处理——形态学操作(二值图像篇)_第11张图片数字图像处理——形态学操作(二值图像篇)_第12张图片
  2).闭操作(closing)
A ∙ B = ( A ⊕ B ) ⊖ B A\bullet B=(A\oplus B)\ominus B AB=(AB)B
  闭操作和开操作正好相反,是先膨胀再腐蚀。如果图像中存在细小的孔洞或者缝隙,通过膨胀后就可以填充细小孔洞和缝隙,使其和主体连接起来,而再腐蚀的时候,只会腐蚀图像主体,而不会再恢复缝隙等瑕疵。因此,闭操作很适合拿来修补图像瑕疵的。
  闭操作的性质:

  • A是 A ∙ B A\bullet B AB的一个子集(子图像);
  • 如果C是D的一个子集,则 C ∙ B C\bullet B CB D ∙ B D\bullet B DB的一个子集;
  • ( A ∙ B ) ∙ B = A ∙ B (A\bullet B)\bullet B=A\bullet B (AB)B=AB
      以下是闭操作的实际操作效果:
  • 下图是在开操作得到的图3基础上进一步进行闭操作的结果图,可以看到,相比于之前的图像,闭操作将很多指纹中断裂的区域连接起来,使得指纹的图像更加完整。但是对于较大的裂缝,闭操作是无能为力的,当然你可以增大SE的尺寸,但是这样很可能使得指纹不同纹路也连接起来,反而得不偿失。
    数字图像处理——形态学操作(二值图像篇)_第13张图片
      3).开操作和闭操作的对偶性
  • ( A ∙ B ) c = ( A c ∘ B ^ ) (A\bullet B)^c=(A^c\circ \hat B) (AB)c=(AcB^)
  • ( A ∘ B ) c = ( A c ∙ B ^ ) (A\circ B)^c=(A^c\bullet \hat B) (AB)c=(AcB^)

2.3 击中和击不中变换(hit and miss transform)

  这个基础形态学变换就没有之前几个那么直观了。理解起来比较抽象。首先,假设有个结构元 B 1 B_1 B1 B 1 B_1 B1的有效元素共同组成了特定的形状。其次,设W是一个比 B 1 B_1 B1尺寸更大的全1模板,记 B 2 = W − B 1 B_2=W-B_1 B2=WB1,即从模板W中将 B 1 B_1 B1挖去。然后,击中和击不中变换就可以表达为:
A ⊛ B = ( A ⊖ D ) ∩ [ A c ⊖ ( W − D ) ] = ( A ⊖ B 1 ) ∩ ( A c ⊖ B 2 ) A\circledast B=(A\ominus D)\cap[A^c\ominus(W-D)]=(A\ominus B_1)\cap (A^c\ominus B_2) AB=(AD)[Ac(WD)]=(AB1)(AcB2)
  击中和击不中变换的直观理解
  假设有图像A(如下图1), A = C ∪ D ∪ E A=C\cup D\cup E A=CDE,表示A图像中有C,D,E三个前景子区域。如果我们想要在图像A中寻找是否存在形状D(图1中的45x45的正方形),怎么办?这时候,我们就可以将结构元 B 1 = D B_1=D B1=D,然后选择合适的W求出 B 2 = W − D B_2=W-D B2=WD,对图像进行击中和击不中变换,变换所得到的结果,如果A中存在和D一样的形状,则会留下一个点,该点是D形状所在位置的中心;如果不存在D,那么得到的图像就是空。
  其中,击中和击不中变换中的 A ⊖ B 1 A\ominus B_1 AB1是在图像中找到与 B 1 B_1 B1匹配的结果,即击中结果,如下图2。注意,击中并不代表说A图像中有与 B 1 B_1 B1一模一样的形状,比如左侧长方形,其中包含形状D,但是并不与D完全相同。所以才需要进一步进行击不中变换。而 A c ⊖ B 2 A^c\ominus B_2 AcB2中的 B 2 B_2 B2可以理解为一个框架的中间掏出一个与D形状一模一样的空缺(见下图5,黑色区域为45x45,白色区域为47x47),框架的白色区域通常设置为1个像素宽,图中为了显示而故意夸大了。然后将该框架试图通过图像A,白色区域表示实体,框架的实体和图像中的实体实会相互阻隔而无法通过的。因此可以看到,图像A中只有比形状D小的实体可以穿过 B 2 B_2 B2,这些 B 2 B_2 B2能穿过的地方的集合,就叫做击不中区域,如下图3中的白色区域,就代表框架 B 2 B_2 B2的中心对准白色区域任意点时,框架可以穿过图像(或者说图像A中的实体击不中框架)。
  综上所述,将击中结果和击不中结果相交,最终所得到的结果为下图4。相交的结果直观点可以理解为,既可以被D形状完全击中,又可以被以D形状为空的框架完全穿过的实体,那就肯定只能是和D形状一模一样的实体。图4中可以看到一个白点,这就说明图像A中存在与形状D一模一样的区域,且该区域中心的位置就是图4白点所在位置。
在这里插入图片描述  在这里插入图片描述  在这里插入图片描述  在这里插入图片描述  数字图像处理——形态学操作(二值图像篇)_第14张图片
  击中和击不中变换更常用的场景——击中变换
  在某些场合中,我们可能对检测某个集合内由1和0组成的特定模式感兴趣(这种情况下,要求结构元中的1对于图像的前景元素,结构元中的0对应图像的背景元素,结构元中的x则表示对该区域不感兴趣,可以为任意元素),此时,击中与击不中变换就不需要击不中变换(因为前景和背景都属于我们感兴趣的范围),而简单地转换成了特定模式的腐蚀操作。(和原始的腐蚀不同,原始的腐蚀的结构元中,结构元中为0的部分相当于这里的x,它不要求对于的图像像素一定是背景元素,而可以是任意元素)。
  以下图为例,假设此时的结构元为下图左的样子,其中红色为1区域,灰色为0区域,x为不感兴趣区域。那么,此时图像要匹配,则需要满足SE的红色区域对应的图像都为前景像素,灰色区域对应的图像均为背景像素,而x区域对应的图像则不感兴趣,不做要求。如果满足上述条件,则将当前位置的结果图像置1,或者说是结构元SE击中图像/在图像中找到了匹配。下图中间的实线区域代表图像。下图右侧的实线区域中,1号位置表示在图像中找到了SE的匹配;而2号区域,中间正上方和正下方不满足需要为背景图像的要求,因此该位置并未找到匹配。
数字图像处理——形态学操作(二值图像篇)_第15张图片

3. 基本的形态学算法

  有了形态学的基础操作,就可以将其进行组合得到基本的形态学算法,以下一一介绍。同样,下述算法都是基于二值图像的操作。基于灰度图像的形态学算法之后还会介绍。

3.1 二值图像边缘提取

β ( A ) = A − ( A ⊖ B ) \beta(A)=A-(A\ominus B) β(A)=A(AB)
  其中, β ( A ) \beta(A) β(A)是提取出来的A图像的边缘图像;B是适当的结构元,如果B尺寸比较大,那么通常提取到的边缘就会比较粗。稍微观察一下就会理解,二值图像的边缘其实就是图像A被腐蚀掉的部分。因此只要腐蚀前和腐蚀后的图像相减,就可以得到边缘。具体效果见下图:
数字图像处理——形态学操作(二值图像篇)_第16张图片  数字图像处理——形态学操作(二值图像篇)_第17张图片

3.2 孔洞填充

  算法步骤:

  • 首先,确认图像孔洞中的任意一个像素,将其标记为1,注意,如果有多个孔洞,则对于要填充的每个孔洞,都需要一个孔洞内部的任意像素。将其他位置都标记为0,作为迭代初始阵列 X 0 X_0 X0 X 0 X_0 X0的尺寸和标记孔洞的像素位置都要和原图像需保持一致。
  • 选择合适的结构元B,比如下图中的两种都可以。
    在这里插入图片描述
  • 进行如下迭代过程: X k = ( X k − 1 ⊕ B ) ∩ A c , k = 1 , 2 , . . . X_k=(X_{k-1}\oplus B)\cap A^c,k=1,2,... Xk=(Xk1B)Ac,k=1,2,...
  • 直到 X K = X K − 1 X_K=X_{K-1} XK=XK1则停止迭代。此时, d s t = A + X K dst=A+X_K dst=A+XK就可以得到孔洞填充后的图像,其中 X K X_K XK是将孔洞转换为前景像素后的图像。

  为什么迭代公式中要存在" ∩ A c \cap A^c Ac "这一步?这称为“条件膨胀”,有兴趣的读者可以试试去掉这一步,那么最终的孔洞膨胀的结果将会填满整个图像,得到一张全白的图。通过与 A c A^c Ac进行交集的操作,保证我们膨胀的过程中, X k X_k Xk图像始终不会将原图像的前景图像覆盖,也就是用原图像的前景像素限制了膨胀的空间,所以才叫条件膨胀。
  下面是对图像进行孔洞填充的示意图,左图为原图,右图为填充后的结果。中间的动图是填充过程。我只取了上半部分的孔洞进行填充,所以最终并没有把所有孔洞都填满(初始化标记孔洞位置太麻烦了)。另外,可以看到,其中有个孔洞的中间是有个白点的,而在填充时这个白点也没有被填(动图中的小黑点),所以可以从中理解条件膨胀的含义。
数字图像处理——形态学操作(二值图像篇)_第18张图片  数字图像处理——形态学操作(二值图像篇)_第19张图片  数字图像处理——形态学操作(二值图像篇)_第20张图片

3.3 连通分量的提取

  其实算法和孔洞填充一样,只是第一步初始化的时候,选择的标记像素点是想要提取的连通分量中的任意一点。第三步迭代的时候,条件膨胀从A的补集替换为A本身。这是很直观的,因为我们现在想要提取的是图像前景,因此需要将膨胀的元素限制在图像前景范围内。
  算法步骤:

  • 首先,确认图像连通分量中的任意一个像素,将其标记为1,注意,如果有多个连通分量,则对于要提取的每个分量,都需要一个连通分量内部的任意像素。将其他位置都标记为0,作为迭代初始阵列 X 0 X_0 X0 X 0 X_0 X0的尺寸和标记连通分量内部像素的位置都要和原图像需保持一致。
  • 选择合适的结构元B,比如下图中的两种都可以。
    在这里插入图片描述
  • 进行如下迭代过程: X k = ( X k − 1 ⊕ B ) ∩ A , k = 1 , 2 , . . . X_k=(X_{k-1}\oplus B)\cap A,k=1,2,... Xk=(Xk1B)A,k=1,2,...
  • 直到 X K = X K − 1 X_K=X_{K-1} XK=XK1则停止迭代。此时, d s t = X K dst=X_K dst=XK就可以得到提取的连通分量。

  如果你只想要提取图中某个连通区域,不希望提取其他区域,并且可以确认该连通区域一定会覆盖图像中某一点,那么这种算法可以很好地完成任务,提取想要的连通部分。
  连通分量的提取实践如下,使用的原图像是孔洞填充的原图像,只提取了其中两个较小的圆环:
数字图像处理——形态学操作(二值图像篇)_第21张图片

3.4 凸壳(convex hull)

  定义:如果集合A内任意两个点的直线段都在A的内部,则称集合A是凸形的。任意集合S的凸壳H是包含S的最小凸集。差集H-S称为S的凸缺
  算法步骤

  • 设原图像为A,首先,确定算法中需要使用的四种结构元,如下图。
    在这里插入图片描述
  • 对于每个结构元 B i , i = 1 , 2 , 3 , 4 B^i,i=1,2,3,4 Bi,i=1,2,3,4,均作如下迭代:
    • X 0 i = A X_0^i=A X0i=A
    • X k i = ( X k − 1 ⊛ B i ) ∪ X k − 1 , k = 1 , 2 , 3... X_k^i=(X_{k-1}\circledast B^i)\cup X_{k-1},\quad k=1,2,3... Xki=(Xk1Bi)Xk1,k=1,2,3...
    • 直到 X K i = X K − 1 i X_K^i=X^i_{K-1} XKi=XK1i,记录 D i = X K i D^i=X_K^i Di=XKi
  • 最终,得到图像A的凸壳为: C ( A ) = ⋃ i = 1 4 D i C(A)=\bigcup_{i=1}^4 D^i C(A)=i=14Di

  上述过程有两点说明,均很重要!
  1). 算法中的 ⊛ \circledast 仅包含击中变换的击中和击不中变换,关于什么是仅包含击中变换,可以参考2.3节最后的解释;
  2). 原《数字图像处理》教材中,迭代公式是 “ X k i = ( X k − 1 ⊛ B i ) ∪ A X_k^i=(X_{k-1}\circledast B^i)\cup A Xki=(Xk1Bi)A” ,经验证后发现是错的,迭代公式应该是 “ X k i = ( X k − 1 ⊛ B i ) ∪ X k − 1 X_k^i=(X_{k-1}\circledast B^i)\cup X_{k-1} Xki=(Xk1Bi)Xk1” 。读者也可自行验证。

  上述算法有一个明显的缺点,就是求出的凸壳可能不是包含原图像的最小凸集。优化最终结果有个常用的办法,就是让求出的凸壳不超过初始点集在水平和垂直方向上的尺寸。当然,也可以针对求出的结果进行进一步的限制生长,但是通常会损失算法效率,需要慎重考虑。
  凸壳算法的具体实践如下图,图1是原始图像,图2动图是整个求解凸壳的过程,动图可按2行3列观看,其中第一行第一列是 B 1 B^1 B1的迭代过程,第二行第一列是 B 2 B^2 B2的迭代过程,第一行第二列是 B 3 B^3 B3的迭代过程,第二行第二列是 B 4 B^4 B4的迭代过程,第一行第三列是每次迭代结束后叠加在一起的 C ( A ) C(A) C(A)
数字图像处理——形态学操作(二值图像篇)_第22张图片    数字图像处理——形态学操作(二值图像篇)_第23张图片

你可能感兴趣的:(数字图像处理)