如果你不小心又亦或是专门寻找形态学相关知识,那么很高兴能和你们分享这篇文章。
首先,如果想了解形态学重建之孔洞填充原理,那么必须先了解什么是膨胀、什么是孔洞填充、什么是形态学重建、什么是测地膨胀,只有具备相关知识,我们才能把形态学重建之孔洞填充原理吃透。
话不多说!!我们看看
这是来源于冈萨雷斯数字图像处理(第三版)的公式,如果光看字面晦涩难懂,那看看例子:
那么怎么会这样呢?你可以这样理解,小正方形(即边长 d / 4 d/4 d/4的正方形)的中心可以在大的正方形里面运动,但是起不到膨胀效果,只有当它的中心点(即那个黑点)在边界运动时,是不是有 d / 8 d/8 d/8露在大正方形外面(看左边那个图),沿着边界绕一圈,是不是整个边长都会多了 d / 8 d/8 d/8,其实中心思想就是取并集,只要我的中心点在大正方形以内(包括边界),凡是能包含的地方,就是我俩共有的地方(即膨胀后的图形)。
同理可得:
孔洞:一个孔洞可以被定义为由前景像素相连接的边界所包围的一个背景区域。(左到右依次是未填充,填充一部分、填充完毕)
有点抽象是不是,其实它的意思就是先找孔洞的一个点,用结构元去膨胀,然后用原图像的补集进行约束(就是求个交集),不断重复膨胀,约束直至图形不改变(即收敛)就停止,与原图求个交集,孔洞就填上了,那我们看看如何填充的:( A A A原图, A c A^c Ac补集, B B B结构元,先在原图需要填充部分找一个点,进行膨胀)
到此,填充完毕,如果你还纠结,那么我们思路理一下,例如 X 1 X_1 X1为什么从第一张图变成第二张,是因为图1,是膨胀后的,还需要 A c A^c Ac补集约束一下,就是求个交集才是最后图形,其它以此类推,为什么到了 X 8 X_8 X8停下了呢,因为到这里已经收敛了,再膨胀也是 X 8 X_8 X8这张图形。
测地膨胀,说白了,和前面的膨胀思想有点区别,前面的膨胀其实就是直接膨胀,测地膨胀呢?其实就是有条件的膨胀,怎么说,我们看看图:
一开始膨胀了是3*3个格子,中间那图,然后再跟模板 I c I^c Ic求个交集,那么就是测地膨胀的思想:先膨胀后约束(即求交集)。
哈哈哈哈,是不是以为测地膨胀结束了,其实不是,这只是第一步,后面不断重复迭代,直至收敛,就是前面说到为什么到 X 8 X_8 X8就停止是一样的,前一跟后一个一样就没必要继续了。
首先, F ( x , y ) F(x,y) F(x,y)呢,其实求标记图像的方法,很简单,就是减去原始预想的边界值,其它地方为0,H则是我们要的结果。(看下图,对比下,是不是清楚点,边界取反,其它地方为0)
如果懂了,继续往下:用3*3的结构元 B B B去膨胀F。
然后多次迭代。
可以发现,其实图形迭代一次就收敛了(这图刚好凑巧,其它可能多次)
H是我们要的结果,那么左1图,就是H公式里面的函数, [ ] c []^c []c里面的其实就是补集的意思,所以对左1图取个补集就是H,细看,是不是填充好了。到此形态学重建之孔洞填充完成。
代码:(非我所写:参见链接)
import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt
img = cv.imread("text.jpg")
# 二值化
imgray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
imgray[imgray < 100] = 0
imgray[imgray >= 100] = 255
# 原图取补得到MASK图像
mask = 255 - imgray
# 构造Marker图像
marker = np.zeros_like(imgray)
marker[0, :] = 255
marker[-1, :] = 255
marker[:, 0] = 255
marker[:, -1] = 255
marker_0 = marker.copy()
# 形态学重建
SE = cv.getStructuringElement(shape=cv.MORPH_CROSS, ksize=(3, 3))
while True:
marker_pre = marker
dilation = cv.dilate(marker, kernel=SE)
marker = np.min((dilation, mask), axis=0)
if (marker_pre == marker).all():
break
dst = 255 - marker
filling = dst - imgray
# 显示
plt.figure(figsize=(12, 6)) # width * height
plt.subplot(2, 3, 1), plt.imshow(imgray, cmap='gray'), plt.title('src'), plt.axis("off")
plt.subplot(2, 3, 2), plt.imshow(mask, cmap='gray'), plt.title('Mask'), plt.axis("off")
plt.subplot(2, 3, 3), plt.imshow(marker_0, cmap='gray'), plt.title('Marker 0'), plt.axis("off")
plt.subplot(2, 3, 4), plt.imshow(marker, cmap='gray'), plt.title('Marker'), plt.axis("off")
plt.subplot(2, 3, 5), plt.imshow(dst, cmap='gray'), plt.title('dst'), plt.axis("off")
plt.subplot(2, 3, 6), plt.imshow(filling, cmap='gray'), plt.title('Holes'), plt.axis("off")
plt.show()
结果:
我解释下,(从上开始数)src图为原始图,Mask为它的补集,Marker 0 为标记图像F,其实应该有白边框,估计像素小看不见,Marker为膨胀约束后的最后结果,dst为Marker补集图(即H),Holes图为 d s t ∩ M a s k \color{purple}{dst \cap Mask} dst∩Mask,就是填充部分。
对于结构元的选择不宜过大,不然填充不上。
明显发现 3 ∗ 3 3*3 3∗3的结构元优于 7 ∗ 7 7*7 7∗7的,(看下图)因为结构元过大,两个图像一样,取个补集就是原图 I I I,所以孔洞没填充上,所以选择结构元得适中。
到 此 结 束 , 希 望 能 帮 助 到 你 \color{maroon}{到此结束,希望能帮助到你} 到此结束,希望能帮助到你