目录
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分别为结构元素高和宽。
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分别为结构元素高和宽。
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家的腐蚀膨胀组合,分析下结果。
以上内容,如有错误,可打在评论区,以后会持续更新该篇内容!!!