主要参考:(美)拉斐尔·C.冈萨雷斯. 数字图像处理 第3版[M]. 阮秋琦,译. 北京:电子工业出版社, 2017: 633.


形态学重建

形态学重建涉及两幅图像和一个结构元:

  • Marker 图像:包含变换的起点,将被连续膨胀,直至收敛
  • Mask 图像:用来约束膨胀结果,即 Mask >= Marker
  • 结构单元(Structuring Element,SE):定义连通性

数学迭代式:
M a r k e r = ( M a r k e r S E ) M a s k Marker = (Marker \oplus SE) \cap Mask Marker=(MarkerSE)Mask
代码示例:

def imreconstruct(marker, mask, SE=np.ones([3,3])):
    """ 描述:以mask为约束,连续膨胀marker,实现形态学重建,其中mask >= marker 参数: - marker 标记图像,单通道/三通道图像 - mask 模板图像,与marker同型 - conn 联通性重建结构元,参照matlab::imreconstruct::conn参数,默认为8联通 """
    while True:
        marker_pre = marker
        dilation = cv.dilate(marker, kernel=SE)
        marker = np.min((dilation, mask), axis=0)
        if (marker_pre == marker).all():
            break
    return marker

孔洞填充

孔洞定义:被前景(白色)连通域包围的封闭的背景(黑***域,不限于圆形。如图所示。

原理:
以原图像的补集作为Mask,用来限制膨胀结果;以带有白色边框的黑色图像为初始Marker,用SE对其进行连续膨胀,直至收敛;最后对Marker取补即得到最终图像,与原图相减可得到填充图像。

M a r k e r ( x , y ) = { <mstyle displaystyle="false" scriptlevel="0"> 255 </mstyle> <mstyle displaystyle="false" scriptlevel="0"> , </mstyle> <mstyle displaystyle="false" scriptlevel="0"> ( x , y ) B o u n d a r y </mstyle> <mstyle displaystyle="false" scriptlevel="0"> 0 </mstyle> <mstyle displaystyle="false" scriptlevel="0"> , </mstyle> <mstyle displaystyle="false" scriptlevel="0"> o t h e r s </mstyle> Marker(x, y) = \left\{\begin{matrix} 255 & , & (x,y) \in Boundary\\ 0 & , & others \end{matrix}\right. Marker(x,y)={2550,,(x,y)Boundaryothers

代码示例:

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()

结果展示:

原图 text.jpg

选用不同尺寸的SE,进行填充对比:ksize=3x3(左),ksize=7x7(右)

由此可见, 如果选择的结构单元过大,膨胀操作会越过边界,膨胀到孔洞中,导致部分孔洞填充失败。就本例来看3x3的SE可以做到准确填充。

并且,对于大小不一的孔洞,3x3的SE同样可以做到准确填充。如图所示:

注: 关于孔洞填充的其他问题,可以参考博文 形态学填充孔洞的几个问题