【数字图像处理】二值图和灰度图的形态学处理

形态学处理

二值图形态学处理

二值图形态学基本算子

二值图形态学图像处理通常在目标图像中卷积式地移动一个结构元素并进行逻辑运算。

腐蚀
腐蚀运算的结果是结构元素 B B B平移 x x x后仍包含在目标图像 A A A内的所有 x x x组成的集合:

A ⊙ B = { x ∣ B + x ⊆ A } A\odot B=\{x|B+x\subseteq A\} AB={xB+xA}

也就是说用结构元素找出在目标图像内部可以完全放下该结构元素的值为 1 的区域。腐蚀具有缩小图像中的物体和消除图像中比结构元素小的成分的作用。

膨胀
膨胀运算的结果是结构元素 B B B平移 x x x后与目标图像 A A A至少有 1 个非 0 元素相交时对应的结构元素的原点位置所组成的集合:
A ⊕ B = { x ∣ ( B + x ) ∩ A ≠ ∅ } A\oplus B=\{x|({B}+x)\cap A\neq\emptyset\} AB={x(B+x)A=}

膨胀运算能扩张图像中的物体。


开运算是先腐蚀后膨胀:
A ∘ B = ( A ⊙ B ) ⊕ B A^{\circ}B=(A\odot B)\oplus B AB=(AB)B

能够消除小物体、断开狭窄细长的连接带和平滑图形边缘。


闭运算是先膨胀后腐蚀:

A ⋅ B = ( A ⊕ B ) ⊙ B A\cdot B=(A\oplus B)\odot B AB=(AB)B

能够填充物体内细小空洞、连接邻近物体和平滑图形内边缘。

击中击不中
对于目标图像 A A A和结构元素 B B B,如果 A ∩ B ≠ ∅ A\cap B\not=\emptyset AB=,那么称 B B B击中A; 如果 A ∩ B = ∅ A\cap B=\emptyset AB=, 那么称B击不中A。形态学击中击不中变换表达式为:

A ⊛ B = ( A ⊙ B 1 ) ∩ [ A c ⊙ B 2 ] A\circledast B=\left(A\odot B_1\right)\cap\left[A^c\odot B_2\right] AB=(AB1)[AcB2]

其中 A c = 1 − A A^c=1-A Ac=1A 。通常令 B 2 = 1 − B 1 B_2 = 1 - B_1 B2=1B1 。击中击不中变换可以用来检测特定形状(结构元素的形状)所处位置。

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

np.set_printoptions(threshold=np.inf)
plt.rcParams['font.sans-serif'] = ['SimHei']


def img_erode(bin_im, kernel):
    '''腐蚀'''
    kernel_w = kernel.shape[0]
    kernel_h = kernel.shape[1]

    erode_img = np.zeros(shape=bin_im.shape)
    for i in range(1, bin_im.shape[0] - kernel_w + 2):
        for j in range(1, bin_im.shape[1] - kernel_h + 2):
            a = bin_im[i - 1:i - 1 + kernel_w,
                j - 1:j - 1 + kernel_h]  # 找到每次迭代中对应的目标图像小矩阵
            if np.sum(a * kernel) == np.sum(kernel):  # 判定是否“完全重合”
                erode_img[i, j] = 1
    return erode_img


def img_dilate(bin_im, kernel):
    '''膨胀'''
    kernel_w = kernel.shape[0]
    kernel_h = kernel.shape[1]
    dilate_img = np.zeros(shape=bin_im.shape)
    for i in range(1, bin_im.shape[0] - kernel_w + 1 + 1):
        for j in range(1, bin_im.shape[1] - kernel_h + 1 + 1):
            a = bin_im[i - 1:i - 1 + kernel_w,
                j - 1:j - 1 + kernel_h]
            dilate_img[i, j] = np.max(a * kernel)  # 若“有重合”,则点乘后最大值为0
    return dilate_img


def img_open(bin_im, erope_k, dilate_k):
    '''开:先腐蚀再膨胀'''
    open_img = img_erode(bin_im, erope_k)
    open_img = img_dilate(open_img, dilate_k)
    return open_img


def img_close(bin_im, erope_k, dilate_k):
    '''闭:先膨胀再腐蚀'''
    close_img = img_dilate(bin_im, dilate_k)
    close_img = img_erode(close_img, erope_k)
    return close_img



def hit_and_miss(A, B1, B2):
    A_c = 1 - A
    A_c_B1 = img_erode(A, B1).astype(np.uint8)
    A_c_B2 = img_erode(A_c, B2).astype(np.uint8)
    return A_c_B1 & A_c_B2


def hit_and_miss_cv(A, B1, B2):
    A_c = 1 - A
    A_c_B1 = cv2.erode(A, B1).astype(np.uint8)
    A_c_B2 = cv2.erode(A_c, B2).astype(np.uint8)
    return A_c_B1 & A_c_B2


if __name__ == "__main__":
    img = cv2.imread('proj03-images/wirebond-mask.tif', 0)
    ret, bin_img = cv2.threshold(img, 127, 1, cv2.THRESH_BINARY)

    kernel1 = np.ones(shape=(3, 3))  # 结构元素,可以为非规则形状
    kernel2 = np.ones(shape=(3, 3))  # 同上


    erode_im = img_erode(bin_img, kernel1)
    dilate_im = img_dilate(bin_img, kernel1)
    open_im = img_open(bin_img, kernel1, kernel2)
    close_im = img_close(bin_img, kernel1, kernel2)

    # erode_im = cv2.erode(img, kernel2)
    # dilate_im = cv2.dilate(img, kernel2)
    # open_im = cv2.morphologyEx(img, cv2.MORPH_OPEN, kernel=kernel1)
    # close_im = cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel=kernel1)


    kernel1 = np.array([[1, 0, 0], [0, 1, 0], [0, 0, 1]]).astype(np.uint8)
    kernel2 = 1 - kernel1
    img_hit_and_miss = hit_and_miss(bin_img, kernel1, kernel2)
    #img_hit_and_miss = hit_and_miss_cv(bin_img, kernel1, kernel2)

    print(np.sum(img_hit_and_miss))


    plt.subplot(231)
    plt.title("(a)二值图像", y=-0.2)
    plt.imshow(bin_img, cmap="gray")
    plt.subplot(232)
    plt.title("(b)腐蚀运算", y=-0.2)
    plt.imshow(erode_im, cmap="gray")
    plt.subplot(233)
    plt.title("(c)膨胀运算", y=-0.2)
    plt.imshow(dilate_im, cmap="gray")
    plt.subplot(234)
    plt.title("(d)开运算", y=-0.2)
    plt.imshow(open_im, cmap="gray")
    plt.subplot(235)
    plt.title("(e)闭运算", y=-0.2)
    plt.imshow(close_im, cmap="gray")
    plt.subplot(236)
    plt.title("(f)击中-击不中", y=-0.2)
    plt.imshow(img_hit_and_miss, cmap="gray")
    plt.show()

二值图连通分量提取、区域标记

将原图中的一个非零点不断膨胀,然后与原图相交,得到连通分量,之后将连通分量从原图去掉;循环以上操作获取多个连通分量,直到原图全为 0:

X k = ( X k − 1 ⊕ B ) ∩ A , k = 1 , 2 , 3 , … X_{\mathrm{k}}=\begin{pmatrix}X_{k-1}\oplus B\end{pmatrix}\cap A,k=1,2,3,\ldots Xk=(Xk1B)A,k=1,2,3,

X 0 X_0 X0为标记点,可指定或随机选取。

import cv2
import numpy as np
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif'] = ['SimHei']


def extract_connected_components(src):
 # 提取连通分量
    num_pixels = []  # 保存连通分量中的像素个数
    img_pixels = []  # 保存对应连通分量的图像

    canvas = np.zeros(src.shape, dtype=np.uint8)  # 将每次提取的连通分量放到这里
    kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))  # 3×3结构元做膨胀运算

    x, y = np.where(src > 0)  # 获取图像中值为1的点
    canvas[x[0]][y[0]] = 1  # 以第一个点为起始点

    while src.any():  # src 矩阵内部所有元素为零的时候,为False
        count = len(x)  # 求每个连通分量的像素点数
        tmp = canvas  # tmp暂存上一步的迭代操作

        src_dilate = cv2.dilate(canvas, kernel)  # 膨胀
        canvas = cv2.bitwise_and(src, src_dilate)  # 和原图求交集

        if (tmp == canvas).all():  # 判断两次迭代操作是否相等。相等的时候,一次提取结束;否则,继续迭代
            img_pixels.append(canvas)  # 将连通分量图片保存到列表
            src[canvas > 0] = 0  # 将该次的连通分量去掉
            canvas = np.zeros(src.shape, np.uint8)  # 将每次提取的连通分量放到这里

            x, y = np.where(src > 0)  # 获取图像中的前景像素点
            num_pixels.append(count - len(x))  # 将上次的点数 - 下次的点数 = 提取连通分量的像素点个数

            if len(x) != 0:  # src 还有前景像素的时候
                canvas[x[0]][y[0]] = 1  # 取起始点

    return num_pixels, img_pixels  # 返回每个连通分量个数和连通分量图像



def mark_connected_components(img,num):
    output = np.zeros((img.shape[0], img.shape[1], 3), np.uint8)
    for i in range(0, num):
        mask = dst[i] == 1
        output[:, :, 0][mask] = np.random.randint(0, 255)
        output[:, :, 1][mask] = np.random.randint(0, 255)
        output[:, :, 2][mask] = np.random.randint(0, 255)

    return output
img = cv2.imread("proj03-images/Chickenfilet with bones.tif", flags=0)


ret, img_bin = cv2.threshold(img, 200, 1, cv2.THRESH_BINARY)  # 二值化处理
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))  # 3×3结构元
img_erode = cv2.erode(img_bin, kernel)

num, dst = extract_connected_components(img_erode.copy())  # 传入拷贝图像
print(len(num))  # 打印连通分量的像素点个数

cv2.imshow('img', img_erode * 255)
cv2.waitKey()

output=mark_connected_components(img_erode,len(num))
cv2.imshow('output', output)
cv2.waitKey(0)


# # OpenCV
# num_labels, labels, stats, centroids = cv2.connectedComponentsWithStats(img_erode, connectivity=8)
#
#
# print('各连通量的点数:',np.sort(stats[1:,-1]))
#
# # 不同的连通域赋予不同的颜色
# output = np.zeros((img.shape[0], img.shape[1], 3), np.uint8)
# for i in range(1, num_labels):
#     mask = labels == i
#     output[:, :, 0][mask] = np.random.randint(0, 255)
#     output[:, :, 1][mask] = np.random.randint(0, 255)
#     output[:, :, 2][mask] = np.random.randint(0, 255)
# cv2.imshow('oginal', output)
# cv2.waitKey()
# cv2.destroyAllWindows()

对提取到的连通分量用不同颜色进行区域标记,得到最终结果:

原图结果
在这里插入图片描述

二值图细化算法

使用击中击不中获取与结构元素相同形状的图像边缘像素,然后将其删除,通过迭代多
次最终得到单像素宽带骨架。对于结构元素组:
{ B } = { B 1 , B 2 , B 3 , … , B n } \{B\}=\left\{B_{1},B_{2},B_{3},\ldots,B_{n}\right\} {B}={B1,B2,B3,,Bn}

对目标图像 A A A使用击中-击不中细化:

A = A ⊗ B i = A − ( A ⊛ B i ) , i = 1 , 2 , . . . , n A=A\otimes B_{i}=A-(A\circledast B_{i}),i=1,2,...,n A=ABi=A(ABi),i=1,2,...,n

如果在迭代过程中, ⊛ \circledast 操作后 A A A没有变化,则结束细化,输出细化结果。

import cv2

import numpy as np
def thin(img):
    hit1 = np.array([[0, 0, 0], [1, 1, 1], [1, 1, 1]])
    hit2 = np.array([[1, 0, 0], [1, 1, 0], [1, 1, 1]])
    hit3 = np.array([[0, 0, 0], [1, 1, 1], [1, 1, 1]])
    hit4 = np.array([[1, 1, 0], [1, 1, 0], [1, 1, 0]])
    hit5 = np.array([[1, 1, 1], [1, 1, 1], [0, 0, 0]])
    hit6 = np.array([[1, 1, 1], [0, 1, 1], [0, 0, 1]])
    hit7 = np.array([[0, 1, 1], [0, 1, 1], [0, 1, 1]])
    hit8 = np.array([[1, 1, 0], [1, 0, 0], [0, 0, 0]])
    kernels = [hit1, hit2, hit3, hit4, hit5, hit6, hit7, hit8]
    dst = img.copy()
    while True:
        tmp = dst
        for kernel in kernels:
            dst = dst - hit_and_miss(dst, kernel,  1-kernel)
        if (dst == tmp).all():
            return dst
        print(np.sum(dst != tmp))
img2 = cv2.imread("UTK.tif", 0)


ret2, img_bin2 = cv2.threshold(img2, 100, 1, cv2.THRESH_BINARY)  # 二值化处理



img_thin = thin(img_bin2)
cv2.imshow('img', img_bin2 * 255)
cv2.imshow('img_thin', img_thin * 255)
cv2.waitKey(0)

原图结果
在这里插入图片描述在这里插入图片描述

灰度图形态学处理

灰度图形态学基本算子

腐蚀
在灰度图像中,用结构元素 b ( x , y ) b(x,y) b(x,y)对输入图像 f ( x , y ) f(x,y) f(x,y)进行灰度腐蚀运算可表示为:

( f ⊙ b ) ( s , t ) = min ⁡ { f ( s + x , t + y ) ∣ ( s + x , t + y ) ∈ D f ; ( x , y ) ∈ D b } (f\odot b)(s,t)=\min\left\{f(s+x,t+y)|(s+x,t+y)\in D_{f};(x,y)\in D_{b}\right\} (fb)(s,t)=min{f(s+x,t+y)(s+x,t+y)Df;(x,y)Db}

式中, D f D_f Df D b D_b Db分别是 f ( x , y ) f(x,y) f(x,y) b ( x , y ) b(x,y) b(x,y)的定义域。求某点的腐蚀运算就是计算该点结构元素覆盖范围内各点最小值作为该点的腐蚀结果。 经腐蚀运算后,图像暗的区域范围将扩大。

膨胀
用结构元素 b ( x , y ) b(x,y) b(x,y)对输入图像 f ( x , y ) f(x,y) f(x,y)进行灰度膨胀运算可表示为:

( f ⊕ b ) ( s , t ) = max ⁡ { f ( s − x , t − y ) ∣ ( s − x , t − y ) ∈ D f ; ( x , y ) ∈ D b } (f\oplus b)(s,t)=\max\left\{f(s-x,t-y)|(s-x,t-y)\in D_f;(x,y)\in D_b\right\} (fb)(s,t)=max{f(sx,ty)(sx,ty)Df;(x,y)Db}

求某点的膨胀运算就是计算该点局部范围内各点的最大值作为该点的膨胀结果。经膨胀运算后,图像亮的区域范围将扩大。


开运算是先腐蚀后膨胀:
f ॰ b = ( f ⊙ b ) ⊕ b f^\text{॰}b=(f\odot b)\oplus b fb=(fb)b

开运算操作可以消除相对于结构元素尺寸较小的亮细节。


闭运算是先膨胀后腐蚀:

f ∙ b = ( f ⊕ b ) ⊙ b f\bullet b=(f\oplus b)\odot b fb=(fb)b

闭运算操作可以消除相对于结构元素尺寸较小的暗细节。



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

plt.rcParams['font.sans-serif'] = ['SimHei']


def img_erode(image, kernel, num=2):
    iChange = image.copy()
    num = int((kernel.shape[1] - 1) / 2)
    image = cv2.copyMakeBorder(image, num, num, num, num, 0)
    w = iChange.shape[1]
    h = iChange.shape[0]
    for i in range(h):
        for j in range(w):
            a = image[i:i + 2 * num+1, j:j + 2 * num+1]  # - kernel
            iChange[i, j] = np.min(a)
    return iChange


def img_dilate(image, kernel, num=2):
    iChange = image.copy()
    num = int((kernel.shape[1] - 1) / 2)
    image = cv2.copyMakeBorder(image, num, num, num, num, 0)
    w = iChange.shape[1]
    h = iChange.shape[0]
    for i in range(h):
        for j in range(w):
            a = image[i:i + 2 * num+1, j:j + 2 * num+1]  # - kernel
            iChange[i, j] = np.max(a)
    return iChange


def img_open(img, erope_k, dilate_k):
    '''开:先腐蚀再膨胀'''
    open_img = img_erode(img, erope_k)
    open_img = img_dilate(open_img, dilate_k)
    return open_img


def img_close(img, erope_k, dilate_k):
    '''闭:先膨胀再腐蚀'''
    close_img = img_dilate(img, dilate_k)
    close_img = img_erode(close_img, erope_k)
    return close_img


if __name__ == '__main__':
    img = cv2.imread("ckt_board_section.tif", flags=0)


    kernel1 = np.ones(shape=(5, 5))  # 此处声明结构元素,可以自定义为非规则形状
    kernel2 = np.ones(shape=(5, 5))  # 同上

    erode_im = img_erode(img, kernel1)
    dilate_im = img_dilate(img, kernel1)
    open_im = img_open(img, kernel1, kernel2)
    close_im = img_close(img, kernel1, kernel2)

    # erode_im = cv2.erode(img, kernel2)
    # dilate_im = cv2.dilate(img, kernel2)
    # open_im1 = cv2.morphologyEx(img, cv2.MORPH_OPEN, kernel=kernel1)
    # close_im = cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel=kernel1)

    plt.subplot(231)
    plt.title("(a)二值图像", y=-0.2)
    plt.imshow(img, cmap="gray")
    plt.subplot(232)
    plt.title("(b)腐蚀运算", y=-0.2)
    plt.imshow(erode_im, cmap="gray")
    plt.subplot(233)
    plt.title("(c)膨胀运算", y=-0.2)
    plt.imshow(dilate_im, cmap="gray")
    plt.subplot(234)
    plt.title("(d)开运算", y=-0.2)
    plt.imshow(open_im, cmap="gray")
    plt.subplot(235)
    plt.title("(e)闭运算", y=-0.2)
    plt.imshow(close_im, cmap="gray")
    plt.show()

在这里插入图片描述

灰度图形态学梯度

灰度图形态学梯度用膨胀运算结果减去腐蚀运算的结果,表达式为:

g = ( f ⊕ b ) − ( f ⊙ b ) g=(f\oplus b)-(f\odot b) g=(fb)(fb)

可用于提取灰度图的边缘。

原图结果
在这里插入图片描述在这里插入图片描述

灰度图 tophat 算法

灰度图 tophat 算法用原图像减去开运算结果,表达式为:
h = f − f ∘ b h=f-f^{\circ}b h=ffb

可用于提取出灰度图中较亮的细节。

原图结果

import cv2
def grad(img, erope_k, dilate_k):
    '''形态学梯度'''
    dilate_img = img_dilate(img.copy(), dilate_k)
    erode_img = img_erode(img.copy(), erope_k)
    return dilate_img - erode_img


def tophat(img, erope_k, dilate_k):
    open_img = img_open(img, erope_k, dilate_k).astype(np.uint8)
    # open_img=cv2.morphologyEx(img, cv2.MORPH_OPEN, kernel=dilate_k)
    return (img - open_img)


if __name__ == '__main__':
    img1 = cv2.imread("headCT-Vandy.tif", 0)
    img2 = cv2.imread("rice_image_with_intensity_gradient.tif", 0)
    kernel3 = np.ones(shape=(5, 5))  # 此处声明结构元素,可以自定义为非规则形状
    kernel4 = np.ones(shape=(21, 21))  # 同上

    # 实现
    # grad_im = grad(img1, kernel3, kernel3)
    # tophat_im = tophat(img2, kernel4, kernel4)

    # OpenCV
    grad_im = cv2.morphologyEx(img1, cv2.MORPH_GRADIENT, kernel3)
    tophat_im = cv2.morphologyEx(img2, cv2.MORPH_TOPHAT, kernel4)

    cv2.imshow('grad_im', grad_im)
    cv2.waitKey(0)
    cv2.imshow('img2', img2)
    cv2.imshow('tophat_im', tophat_im)
    cv2.waitKey(0)
  • 14
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

二进制人工智能

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值