opencv:腐蚀和膨胀基础操作,以及组合后的各种形态学操作

目录

1、简介

2、灰度图和二值图

3、腐蚀和膨胀  

3.1、腐蚀

3.1.1、binary_erosion API

3.1.2、cv2.erode API 

3.2、膨胀

3.2.1、binary_dilation API 

3.2.2、cv2.dilate API

4、开运算和闭运算

4.1、开运算  

4.2、闭运算

4.3、演示demo 

5、顶帽运算和黑帽运算

6、梯度运算


1、简介

本篇介绍图像处理中的形态学操作 ,包含 开运算和闭运算、顶帽和黑帽运算、梯度运算,以及击中不击中运算等。网上见到很多直接调用opencv库中的cv2.morphologyEx函数来实现的,今天我们来一探究竟,用演示图的方式,形象理解形态学操作的原理。

上述提到的几种形态学操作,大多是由图像腐蚀图像膨胀这两个基础操作组合而来的。所以,本篇文章详细介绍了,目前常用的两种图像腐蚀和膨胀函数的具体实现机制,方便大家更好理解形态学操作,以及自己如何设计形态学操作的功能。

2、灰度图和二值图

先介绍下数字图像中的灰度图像和二值图像:

  • 灰度图像:每个像素用一个强度值表示(单通道),范围从 0(黑色)到 255(白色)。
  • 二值图像:每个像素只有两个可能的值(非黑即白),0 和 255 或者 0 和 1,表示黑白图像。

python中使用 opencv 读取图像并将图像转换为灰度图。 

dst = cv2.imread(img_path,cv2.IMREAD_GRAYSCALE)

将灰度图像转换为二值图像。

retval, dst = cv2.threshold(src, thresh, maxval, type) 
  • src:输入图像,通常是灰度图像。
  • thresh: 阈值,像素值高于此阈值将会被设为 maxval,低于此阈值则为 0。
  • maxval:阈值化后的最大值。对于二值图像,通常是 255(白色)。
  • type:阈值化类型,决定了如何将像素值与阈值进行比较。常用的类型有:
    • cv2.THRESH_BINARY将大于阈值的像素设为 maxval,其余设为 0。
    • cv2.THRESH_BINARY_INV将小于阈值的像素设为 maxval,其余设为 0。
    • cv2.THRESH_TRUNC将大于阈值的像素设为阈值,其余像素保持不变。
    • cv2.THRESH_TOZERO将小于阈值的像素设为 0,其余像素保持不变。
    • cv2.THRESH_TOZERO_INV将大于阈值的像素设为 0,其余像素保持不变。

3、腐蚀和膨胀  

封装的腐蚀(膨胀)操作API,目前常用的有两种,分别是scipy.ndimage库中的binary_erosion(binary_dilation)函数,和opencv库中的cv2.erode(cv2.dilate)函数。

两个库的API实现机制略有不同,接下来会逐一讲解。

先来个总结,opencv中的边界填充方式(由结构元素中心点决定),腐蚀与膨胀采用的方式相同,均采用binary_erosion 的方式,而scipy库中的binary_erosion和binary_dilation,腐蚀和膨胀采用的填充方式不同。

3.1、腐蚀

腐蚀操作,使图像中的前景(白色像素)变小,通常用于去除小的噪点。

3.1.1、binary_erosion API

操作方式:结构元素以窗口滑动的方式(通常步长为1),覆盖原图的某一区域,在该区域内,重置结构元素中心点(黄色)所对应的像素点的像素值,大小为该区域内的最小像素值。

注意,二值图像中,黑色像素值为0,白色像素值为1。 

1、结构元素大小为 2*2 ,中心点(黄色)位置(2,2),腐蚀结果示例图如下。 

  • 为保证腐蚀后图像与原图大小一致,在操作前,会在原图左上角填充一行一列,默认填充像素值为0;
  • 但凡结构元素覆盖的区域有一个黑格,中心点对应的像素值就置为黑色了;
  • 只有区域内全为白色这一种情况,中心点对应的像素值才会置为白色,图中第5行5列(蓝色处)所示;

 2、结构元素大小为 3*3,中心点(2,2),腐蚀结果示例图如下。

  • 操作前,左上角填充了一行一列,右下角填充一行一列,保证腐蚀后图像大小一致;
  • 图中蓝色处,再次印证了,只有覆盖区域全为白色,才会置白;

总结:

1、结构元素中心确定,以下计算公式后均向上取整,h和w分别为结构元素高和宽。

        center_x = \frac{w+1}{2}; center_y = \frac{h+1}{2};

2、原图边界填充,填充多少,以及在哪个位置填充?均都和结构元素中心点有关。

  • 上图中,列举了三种情况,主要以中心点(黄色)为基准,看中心点的上下左右四方有多少个格子,就在那方填充对应行数/列数的边界。
  • 以2*2为例,中心点左方有一格,则在原图左方填充一列,上方有一格,则在原图上方填充一行,这正好与我们前面示例图一致。无论多大的结构元素,以此类推。 
  • 边界填充像素值,默认为0(黑色),可手动更改边界填充元素值。

3、结构元素覆盖区域,凡有一个黑格,置为0,只有全为白格时,才置为1。 

以下代码,使用 binary_erosion 函数实现二值图像的腐蚀操作,结构元素大小可自定义。

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

# 腐蚀函数
def erode(image, kernel_size):
    # 生成指定大小的结构元素
    kernel = np.ones((kernel_size,kernel_size),np.uint8)
    # 使用binary_erosion函数执行腐蚀操作,
    eroded_image = binary_erosion(image, structure=kernel)
    return eroded_image

# 创建一个7x7的二值图像矩阵
binary_img = np.array([[1, 0, 0, 0 ,1,1,1],
                   [0, 1, 1, 1, 1,0,0],
                   [0, 1, 1, 1, 0,0,1],
                   [0, 1, 1, 1, 1,0,0],
                   [0, 0, 0, 1, 1,0,0],
                    [0, 0, 0, 0, 0,1,0],
                   [1,1,1,1,1,0,0]
                   ],dtype=np.uint8)

# 腐蚀操作,结构元素大小kernel_size可自定义
erode_img = erode(binary_img,kernel_size=2)
plt.subplot(1,2,1),plt.imshow(binary_img,cmap="gray")
plt.xticks(ticks=np.arange(-0.5, 7, 1), labels=[])
plt.yticks(ticks=np.arange(-0.5, 7, 1), labels=[])
plt.grid(True)
plt.subplot(1,2,2),plt.imshow(erode_img,cmap="gray")
# 设置坐标轴刻度以适配网格线
plt.xticks(ticks=np.arange(-0.5, 7, 1), labels=[])
plt.yticks(ticks=np.arange(-0.5, 7, 1), labels=[])
plt.grid(True)
plt.show()

其中, binary_erosion 函数的一些重要参数说明如下。

  • structure:结构元素,自定义,默认像素值均为1
  • iteration:迭代次数,即可连续腐操作的次数;
  • border_value:边界填充像素值,默认为0,这也是前面提到的,大家可自行设置。

3.1.2、cv2.erode API 

cv2.erode 是 OpenCV 库中的一个函数,用于对图像执行腐蚀操作。

cv2.erode(src, kernel, iterations=1, borderType=cv2.BORDER_CONSTANT, borderValue=None)
  • src:输入图像, 必须是单通道(灰度图像)或多通道(如彩色图像)的图像矩阵;
  • kernel:结构元素(或称卷积核),定义了腐蚀操作的形状和大小;
  • iterations:腐蚀迭代次数;
  • borderType:边界填充模式,用于处理图像边界处的像素;
    • cv2.BORDER_CONSTANT:默认值,使用常量边界值(borderValue 参数指定)。
    • cv2.BORDER_REPLICATE:复制边界像素。
    • cv2.BORDER_REFLECT:反射边界像素。
    • cv2.BORDER_REFLECT_101:反射边界像素,101 表示反射的边界。
    • cv2.BORDER_WRAP:图像的边缘会环绕(回绕)。
    • cv2.BORDER_DEFAULT:默认边界类型。
  • borderValue:当 borderType为 cv2.BORDER_CONSTANT 时,指定边界的常量值;

opencv中的腐蚀操作,结构元素中心点的确定方式,和binary_erosion函数相同,填充方式仍然由中心点决定,不同点在于默认的边界填充像素值。

如下图例中,结构元素大小2*2,中心点确定方式以及填充方式同binary_erosion,左上角填充边界(上方填充一行,左方填充一列);边界填充的像素值,默认与左上角点相同,这点是不同于函数binary_erosion(默认都填充为0)。

以2*2类推,不同的结构元素,填充像素值看那个点呢?一个简单的方式,左上角填充看左上角点,右下角填充看右下角点。

 以下代码,对比了 binary_erosion腐蚀函数和cv2.erode腐蚀函数,分别执行后的展示图。

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

# # 配置字体
rcParams['font.sans-serif'] = ['SimHei']  # 指定使用黑体
rcParams['axes.unicode_minus'] = False  # 解决负号 '-' 显示为方块的问题

# 创建一个7x7的二值图像矩阵
binary_img = np.array([[1, 0, 0, 0 ,1,1,1],
                   [0, 1, 1, 1, 1,0,0],
                   [0, 1, 1, 1, 0,0,1],
                   [0, 1, 1, 1, 1,0,0],
                   [0, 0, 0, 1, 1,0,0],
                    [0, 0, 0, 0, 0,1,0],
                   [1,1,1,1,1,0,0]
                   ],dtype=np.uint8)

plt.subplot(1,3,1),plt.imshow(binary_img,cmap="gray"),plt.title("原图")
plt.xticks(ticks=np.arange(-0.5, 7, 1), labels=[])
plt.yticks(ticks=np.arange(-0.5, 7, 1), labels=[])
plt.grid(True)
# 结构元素大小kernel可自定义
kernel = np.ones((2, 2), np.uint8)
# cv腐蚀操作
cv_erode = cv2.erode(binary_img,kernel)
plt.subplot(1,3,2),plt.imshow(cv_erode,cmap="gray"),plt.title("cv2腐蚀图")
plt.xticks(ticks=np.arange(-0.5, 7, 1), labels=[])
plt.yticks(ticks=np.arange(-0.5, 7, 1), labels=[])
plt.grid(True)
# scipy 腐蚀
scipy_erode = binary_erosion(binary_img, structure=kernel)
plt.subplot(1,3,3),plt.imshow(scipy_erode,cmap="gray"),plt.title("scipy腐蚀图")
plt.xticks(ticks=np.arange(-0.5, 7, 1), labels=[])
plt.yticks(ticks=np.arange(-0.5, 7, 1), labels=[])
plt.grid(True)
plt.show()

结构元素大小为 2*2,执行后的结果如下图所示,略有不同。

结果不同,主要原因前面也讲过了,边界填充像素值不同,scipy统一填充0,cv2看的是左上角点,填充像素值为1,这是导致最后结果不同的原因。

大家可修改其中一个API的边界填充像素值即可。

scipy_erode = binary_erosion(binary_img, structure=kernel,border_value=1)

 或者修改cv2.erode()中的 borderValue 参数。

cv_erode = cv2.erode(binary_img,kernel,borderValue = 0)

3.2、膨胀

膨胀操作,会使图像中的前景(白色像素)扩展,通常用于填补小的空洞。

3.2.1、binary_dilation API 

操作方式:结构元素以窗口滑动的方式(通常步长为1),覆盖原图的某一区域,在该区域内,重置结构元素中心点(黄色)所对应的像素点的像素值,大小为该区域内的最大像素值。

1、结构元素大小为 2*2,中心点(黄色)为(1,1),示例结果图如下

  • 但凡覆盖区域有一白格,中心点对应像素点的像素值置为1 ;
  • 中心点左上角,所以向下添加一行,向右添加一列;

2、结构元素大小为 3*3, 中心点(黄色)为(2,2),示例结果图如下。

总结: 

1、结构元素中心确定,以下计算均向下取整,h和w分别为结构元素高和宽。

        center_x = \frac{w+1}{2}; center_y = \frac{h+1}{2};

2、边界填充方式,与腐蚀操作一样,以中心点为基准填充,见2.1节图示。

3、结构元素覆盖区域,凡有一个白格,置为1,只有全为黑格时,才置为0。

以下代码, 使用 binary_dilation 函数实现二值图像的膨胀操作,结构元素大小可自定义。

import numpy as np
from scipy.ndimage import binary_dilation
import matplotlib.pyplot as plt

# 创建一个7x7的二值图像矩阵
binary_img = np.array([[0, 1, 1, 1, 1, 1, 0],
                       [0, 1, 1, 1, 1, 0, 0],
                       [0, 1, 0, 1, 1, 0, 1],
                       [0, 1, 1, 1, 1, 0, 0],
                       [0, 1, 1, 1, 1, 0, 0],
                       [0, 0, 0, 0, 0, 0, 0],
                       [0, 1, 1, 1, 1, 0, 0]], dtype=np.uint8)


# 定义结构元素(膨胀内核)
def dilate(image,kernel_size):
    structure = np.ones((kernel_size, kernel_size), dtype=np.uint8)
    # 执行膨胀操作
    dilate_img = binary_dilation(image, structure=structure).astype(np.uint8)
    return dilate_img

# 显示结果
plt.subplot(1, 2, 1),plt.imshow(binary_img, cmap="gray", vmin=0, vmax=1)
plt.xticks(ticks=np.arange(-0.5, 7, 1), labels=[])
plt.yticks(ticks=np.arange(-0.5, 7, 1), labels=[])
plt.grid(True)
dilate_img = dilate(binary_img,kernel_size=3)
plt.subplot(1, 2, 2),plt.imshow(dilate_img, cmap="gray", vmin=0, vmax=1)
plt.xticks(ticks=np.arange(-0.5, 7, 1), labels=[])
plt.yticks(ticks=np.arange(-0.5, 7, 1), labels=[])
plt.grid(True)
plt.show()

3.2.2、cv2.dilate API

opencv中的膨胀操作API,参数同上3.1.2节中的腐蚀操作API。

cv2.dilate(src, kernel, iterations=1, borderType=cv2.BORDER_CONSTANT, borderValue=None)

结构元素中心点确定方式,和opencv中的腐蚀操作一样,填充方式以及像素值均同。 

以下代码,同样是对比 binary_dilation膨胀函数和cv2.dilate膨胀函数的效果图。

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

# # 配置字体
rcParams['font.sans-serif'] = ['SimHei']  # 指定使用黑体
rcParams['axes.unicode_minus'] = False  # 解决负号 '-' 显示为方块的问题

# 创建一个7x7的二值图像矩阵
binary_img = np.array([[1, 0, 0, 0 ,1,1,1],
                   [0, 1, 1, 1, 1,0,0],
                   [0, 1, 1, 1, 0,0,1],
                   [0, 1, 1, 1, 1,0,0],
                   [0, 0, 0, 1, 1,0,0],
                    [0, 0, 0, 0, 0,1,0],
                   [1,1,1,1,1,0,0]
                   ],dtype=np.uint8)


plt.subplot(1,3,1),plt.imshow(binary_img,cmap="gray"),plt.title("原图")
plt.xticks(ticks=np.arange(-0.5, 7, 1), labels=[])
plt.yticks(ticks=np.arange(-0.5, 7, 1), labels=[])
plt.grid(True)
# 结构元素大小kernel可自定义
kernel = np.ones((2, 2), np.uint8)
# cv膨胀操作,边界填充像素值设置围为0
cv_erode = cv2.dilate(binary_img,kernel,borderValue = 0)
plt.subplot(1,3,2),plt.imshow(cv_erode,cmap="gray"),plt.title("cv2膨胀图")
plt.xticks(ticks=np.arange(-0.5, 7, 1), labels=[])
plt.yticks(ticks=np.arange(-0.5, 7, 1), labels=[])
plt.grid(True)
# scipy 膨胀
scipy_erode = binary_dilation(binary_img, structure=kernel)
plt.subplot(1,3,3),plt.imshow(scipy_erode,cmap="gray"),plt.title("scipy膨胀图")
plt.xticks(ticks=np.arange(-0.5, 7, 1), labels=[])
plt.yticks(ticks=np.arange(-0.5, 7, 1), labels=[])
plt.grid(True)
plt.show(

上面代码中,将opencv的膨胀函数中的像素值填充置为0,保证与scipy方法的边界填充一致。会发现执行后结果仍不同,示例图如下。

结果不同的具体原因在于,scipy 膨胀函数 和 opencv 膨胀函数,结构元素中心点的确定方式不同,自然边界填充的方式也不同,尤其是偶数大小的结构元素。

代码里是2*2大小的结构元素例子,scipy确定的中心点是右下角点(2,2),自然右下方填充一行一列;而opencv确定中心点是左上角点(1,1),即左上方填充一行一列。

总结,opencv中的边界填充方式(由结构元素中心点决定),腐蚀与膨胀采用的方式相同,均采用binary_erosion 中的方式,而scipy库中的binary_erosion和binary_dilation,腐蚀和膨胀采用的填充方式不同

以上API的腐蚀和膨胀操作,我都只测试了矩形的结构元素,大家感兴趣也可测试圆形等结构元素。当然,以后有机会,我会及时补充更新的。

4、开运算和闭运算

在详细介绍开运算与闭运算之前,先介绍下opencv中的 cv2.morphologyEx 函数,该函数主要应用于形态学变换操作,包含开运算、闭运算、梯度运算、顶帽运算和黑帽运算等 。

 cv2.morphologyEx(  src, 
                    op, 
                    kernel, 
                    iterations=1, 
                    borderType=cv2.BORDER_CONSTANT, 
                    borderValue=None)
  • src: 输入图像,通常为二值图像。
  • op:形态学操作类型,常见类型如下:
    • cv2.MORPH_ERODE:腐蚀操作
    • cv2.MORPH_DILATE:膨胀操作
    • cv2.MORPH_OPEN:开运算
    • cv2.MORPH_CLOSE:闭运算
    • cv2.MORPH_GRADIENT:梯度运算
    • cv2.MORPH_TOPHAT:顶帽运算
    • cv2.MORPH_BLACKHAT:黑帽运算
  • kernel:结构元素(或内核),用于形态学操作的滤波器大小。一个二维数组,常用的形状包括矩形、椭圆和交叉形。
  • iterations: 可选参数,指定操作的迭代次数。默认值是 1。

4.1、开运算  

开运算:对二值图像 先腐蚀后膨胀,通常用于消除背景中的小白点和小毛刺。

直接调用 opencv 中的 cv2.morphologyEx  函数,即可实现开运算,非常方便。这里我想尝试对比下,组合第二节内容中的腐蚀和膨胀操作,是否与直接调用opencv库的实现机制一致。

以下是对比代码,main函数可选参数,type:开运算或闭运算,kernel_size:结构元素大小。 

import numpy as np
from scipy.ndimage import binary_dilation
import matplotlib.pyplot as plt

# 定义结构元素(膨胀内核)
def scipy_dilate(image,kernel_size):
    structure = np.ones((kernel_size, kernel_size), dtype=np.uint8)
    # 执行膨胀操作
    dilate_img = binary_dilation(image, structure=structure,border_value=1).astype(np.uint8)
    return dilate_img

def scipy_erode(image, kernel_size):
    # 生成指定大小的结构元素
    kernel = np.ones((kernel_size,kernel_size),np.uint8)
    # 使用binary_erosion执行腐蚀操作
    eroded_image = binary_erosion(image, structure=kernel,border_value=1).astype(np.uint8)
    return eroded_image

def cv_dilate(image,kernel_size):
    kernel = np.ones((kernel_size, kernel_size), dtype=np.uint8)
    # 执行膨胀操作
    dilate_img = cv2.dilate(image, kernel=kernel).astype(np.uint8)
    return dilate_img

def cv_erode(image, kernel_size):
    # 生成指定大小的结构元素
    kernel = np.ones((kernel_size,kernel_size),np.uint8)
    # 使用binary_erosion执行腐蚀操作
    eroded_image = cv2.erode(image, kernel=kernel).astype(np.uint8)
    return eroded_image

def apply_opening(binary_image,kernel_size = 2):
    kernel = np.ones((kernel_size, kernel_size), np.uint8)
    # 执行开运算
    open_image = cv2.morphologyEx(binary_image, cv2.MORPH_OPEN, kernel)
    return open_image

def apply_closing(binary_image, kernel_size=5):
    kernel = np.ones((kernel_size, kernel_size), np.uint8)
    # 执行闭运算
    closed_image = cv2.morphologyEx(binary_image, cv2.MORPH_CLOSE, kernel)
    return closed_image

def plt_draw(grid=7):
    plt.xticks(ticks=np.arange(-0.5, grid, 1), labels=[])
    plt.yticks(ticks=np.arange(-0.5, grid, 1), labels=[])
    plt.grid(True)


def mian(type = 0,kernel_size=2):
    binary_img = np.array([[0, 1, 1, 1, 1, 1, 0],
                           [0, 1, 1, 1, 1, 0, 0],
                           [0, 1, 0, 1, 1, 0, 1],
                           [0, 1, 1, 1, 1, 0, 0],
                           [0, 1, 1, 1, 1, 0, 0],
                           [0, 0, 0, 0, 0, 0, 0],
                           [0, 1, 1, 1, 1, 0, 0]], dtype=np.uint8)
    
    # 配置字体
    rcParams['font.sans-serif'] = ['SimHei']  # 指定使用黑体
    rcParams['axes.unicode_minus'] = False  # 解决负号 '-' 显示为方块的问题
    if type == 0:
        # 显示原图
        plt.subplot(2, 2, 1), plt.imshow(binary_img, cmap="gray", vmin=0, vmax=1), plt.title("原图")
        plt_draw()
        # cv先腐蚀后膨胀
        output_cv = cv_erode(binary_img, kernel_size=kernel_size)
        output_cv = cv_dilate(output_cv, kernel_size=kernel_size)
        plt.subplot(2, 2, 2), plt.imshow(output_cv, cmap="gray", vmin=0, vmax=1), plt.title("cv先腐蚀后膨胀")
        plt_draw()
        # scipy先腐蚀后膨胀
        output_sci = scipy_erode(binary_img, kernel_size=kernel_size)
        output_sci = scipy_dilate(output_sci, kernel_size=kernel_size)
        plt.subplot(2, 2, 3), plt.imshow(output_sci, cmap="gray", vmin=0, vmax=1), plt.title("scipy先腐蚀后膨胀")
        plt_draw()
        # 直接调用opencv库
        output2 = apply_opening(binary_img, kernel_size=kernel_size)
        plt.subplot(2, 2, 4), plt.imshow(output2, cmap="gray", vmin=0, vmax=1), plt.title("cv开运算")
        plt_draw()
        plt.show()
    elif type == 1:
        # 显示原图
        plt.subplot(2, 2, 1), plt.imshow(binary_img, cmap="gray", vmin=0, vmax=1), plt.title("原图")
        plt_draw()
        # cv先膨胀后腐蚀
        output_cv = cv_dilate(binary_img, kernel_size=kernel_size)
        output_cv = cv_erode(output_cv, kernel_size=kernel_size)
        plt.subplot(2, 2, 2), plt.imshow(output_cv, cmap="gray", vmin=0, vmax=1), plt.title("cv先膨胀后腐蚀")
        plt_draw()
        # scipy先膨胀后腐蚀
        output_sci = scipy_dilate(binary_img, kernel_size=kernel_size)
        output_sci = scipy_erode(output_sci, kernel_size=kernel_size)
        plt.subplot(2, 2, 3), plt.imshow(output_sci, cmap="gray", vmin=0, vmax=1), plt.title("scipy先膨胀后腐蚀")
        plt_draw()
        # 直接调用opencv库
        output2 = apply_closing(binary_img, kernel_size=kernel_size)
        plt.subplot(2, 2, 4), plt.imshow(output2, cmap="gray", vmin=0, vmax=1), plt.title("cv闭运算")
        plt_draw()
        plt.show()
        
if __name__ == "__main__":
    # type,0表示开运算;1表示闭运算
    # kernel_size,结构元素大小,默认为正方矩形
    mian(type = 0,kernel_size =2)

执行后的结果发现,使用opencv中的开运算,与opencv中的先腐蚀后膨胀效果一致,和scipy中的先腐蚀后膨胀效果就不一致。

不一致的原因,前面第3节也详细说明了,scipy和opencv中的腐蚀操作,默认的边界填充像素值不同,像素值可更改,尽管更改后,结果仍然不同,原因是scipy和opencv中的膨胀操作,边界填充方式不同(结构元素中心点不同,针对偶数大小的结构元素),总结这两项原因,才会导致最终结果的不同。不过,由此也确定了opencv库中的开运算,由腐蚀和膨胀(自己的)操作的组合实现

4.2、闭运算

闭运算:对二值图像 先膨胀后腐蚀,通常用于消除前景区域内的小黑点。

与开运算正好相反,对比代码同上,直接修改main函数中的type参数即可实现,示例结果图如下。

结论同开运算一致。

4.3、演示demo 

使用 gradio 前端工具,做了一个界面交互,通过滑动,选择不同大小的结构元素,动态演示开运算和闭运算的效果,这里实例主要从真实二值图像角度看效果。

切记,下方演示代码,输入为二值图像!!!

import gradio as gr
import cv2
import numpy as np

def apply_closing(binary_image, kernel_size=5):
    # 定义滤波器
    kernel = np.ones((kernel_size, kernel_size), np.uint8)
    # 执行闭运算
    closed_image = cv2.morphologyEx(binary_image, cv2.MORPH_CLOSE, kernel)
    return closed_image

def apply_opening(binary_image,kernel_size = 2):
    kernel = np.ones((kernel_size, kernel_size), np.uint8)
    # 执行开运算
    open_image = cv2.morphologyEx(binary_image, cv2.MORPH_OPEN, kernel)
    return open_image

if __name__ == "__main__":
    with gr.Blocks() as demo:
        with gr.Column():
            with gr.Column():
                input1 = gr.Image(label="二值图",image_mode="L")
            with gr.Row():
                with gr.Column():
                    thres1 = gr.Slider(label="阈值",value=1,minimum=0,maximum=40,step=1)
                    output1 = gr.Image(label="开运算",image_mode="L")
                    thres1.change(fn = apply_opening,inputs=(input1,thres1),outputs=output1)
                with gr.Column():
                    thres2 = gr.Slider(label="阈值", value=1, minimum=0, maximum=40, step=1)
                    output2 = gr.Image(label="闭运算",image_mode="L")
                    thres2.change(fn=apply_closing, inputs=(input1, thres2), outputs=output2)

    demo.launch(share = True)

执行上述代码后,点击后台分享的网址链接。

 演示操作如下,先选择二值图像,在滑动阈值,开运算与闭运算后的动态效果。

5、顶帽运算和黑帽运算

顶帽运算:开运算图 - 原图;黑帽运算:闭运算图 - 原图。

实现代码如下,同开闭运算,这里也是做了一个对比,main函数可选参数。

import numpy as np
from scipy.ndimage import binary_dilation
import matplotlib.pyplot as plt

# 定义结构元素(膨胀内核)
def scipy_dilate(image,kernel_size):
    structure = np.ones((kernel_size, kernel_size), dtype=np.uint8)
    # 执行膨胀操作
    dilate_img = binary_dilation(image, structure=structure,border_value=1).astype(np.uint8)
    return dilate_img

def scipy_erode(image, kernel_size):
    # 生成指定大小的结构元素
    kernel = np.ones((kernel_size,kernel_size),np.uint8)
    # 使用binary_erosion执行腐蚀操作,边界填充为1
    eroded_image = binary_erosion(image, structure=kernel,border_value=1).astype(np.uint8)
    return eroded_image

def cv_dilate(image,kernel_size):
    kernel = np.ones((kernel_size, kernel_size), dtype=np.uint8)
    # 执行膨胀操作
    dilate_img = cv2.dilate(image, kernel=kernel).astype(np.uint8)
    return dilate_img

def cv_erode(image, kernel_size):
    # 生成指定大小的结构元素
    kernel = np.ones((kernel_size,kernel_size),np.uint8)
    # 使用binary_erosion执行腐蚀操作
    eroded_image = cv2.erode(image, kernel=kernel).astype(np.uint8)
    return eroded_image

def apply_tophot(binary_image,kernel_size = 2):
    kernel = np.ones((kernel_size, kernel_size), np.uint8)
    # 执行顶帽运算
    open_image = cv2.morphologyEx(binary_image, cv2.MORPH_OPEN, kernel)
    return open_image

def apply_blackhat(binary_image, kernel_size=5):
    kernel = np.ones((kernel_size, kernel_size), np.uint8)
    # 执行黑帽运算
    closed_image = cv2.morphologyEx(binary_image, cv2.MORPH_CLOSE, kernel)
    return closed_image

def plt_draw(grid=7):
    plt.xticks(ticks=np.arange(-0.5, grid, 1), labels=[])
    plt.yticks(ticks=np.arange(-0.5, grid, 1), labels=[])
    plt.grid(True)

def mian(type = 0,kernel_size=2):
    binary_img = np.array([[0, 1, 1, 1, 1, 1, 0],
                           [0, 1, 1, 1, 1, 0, 0],
                           [0, 1, 0, 1, 1, 0, 1],
                           [0, 1, 1, 1, 1, 0, 0],
                           [0, 1, 1, 1, 1, 0, 0],
                           [0, 0, 0, 0, 0, 0, 0],
                           [0, 1, 1, 1, 1, 0, 0]], dtype=np.uint8)

    # 配置字体
    rcParams['font.sans-serif'] = ['SimHei']  # 指定使用黑体
    rcParams['axes.unicode_minus'] = False  # 解决负号 '-' 显示为方块的问题
    plt.subplot(2,2,1), plt.imshow(binary_img,cmap="gray"), plt.title("原图")
    plt_draw()
    if type == 0:
        # cv顶帽运算
        kernel = np.ones((kernel_size, kernel_size), np.uint8)
        tophot = cv2.morphologyEx(binary_img,cv2.MORPH_TOPHAT,kernel = kernel)
        plt.subplot(2,2,2),plt.imshow(tophot,cmap="gray"),plt.title("顶帽运算")
        plt_draw()
        # cv
        cv_tophot = cv_erode(binary_img,kernel_size=kernel_size)
        cv_tophot = cv_dilate(cv_tophot,kernel_size=kernel_size)
        cv_tophot = cv_tophot-binary_img
        plt.subplot(2,2,3), plt.imshow(cv_tophot,cmap="gray"), plt.title("cv")
        plt_draw()
        #scipy
        scipy_tophot = scipy_erode(binary_img, kernel_size=kernel_size)
        scipy_tophot = scipy_dilate(scipy_tophot, kernel_size=kernel_size)
        scipy_tophot = scipy_tophot-binary_img
        plt.subplot(2, 2, 4), plt.imshow(scipy_tophot,cmap="gray"), plt.title("scipy")
        plt_draw()
        plt.show()
    elif type == 1:
        # cv顶帽运算
        kernel = np.ones((kernel_size, kernel_size), np.uint8)
        tophot = cv2.morphologyEx(binary_img, cv2.MORPH_BLACKHAT, kernel=kernel)
        plt.subplot(2, 2, 2), plt.imshow(tophot, cmap="gray"), plt.title("顶帽运算")
        plt_draw()
        # cv
        cv_tophot = cv_dilate(binary_img, kernel_size=kernel_size)
        cv_tophot = cv_erode(cv_tophot, kernel_size=kernel_size)
        cv_tophot = cv_tophot - binary_img
        plt.subplot(2, 2, 3), plt.imshow(cv_tophot, cmap="gray"), plt.title("cv")
        plt_draw()
        # scipy
        scipy_tophot = scipy_dilate(binary_img, kernel_size=kernel_size)
        scipy_tophot = scipy_erode(scipy_tophot, kernel_size=kernel_size)
        scipy_tophot = scipy_tophot - binary_img
        plt.subplot(2, 2, 4), plt.imshow(scipy_tophot, cmap="gray"), plt.title("scipy")
        plt_draw()
        plt.show()

if __name__ == "__main__":
    # type,0表示顶帽运算;1表示黑帽运算
    # kernel_size,结构元素大小,默认为正方矩形
    mian(type = 0,kernel_size =2)

结果就不截图了,大家可复制代码直接演示。结论同开闭运算一致,opencv还是组合自己家的腐蚀膨胀操作实现的。

6、梯度运算

 梯度运算:膨胀图-腐蚀图。

 感兴趣的可以按照之前的思路,组合opencv家的腐蚀和膨胀实现,与opencv自带的函数结果是否一致。同时也可使用scipy家的腐蚀膨胀组合,分析下结果。

以上内容,如有错误,可打在评论区,以后会持续更新该篇内容!!! 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值