在《OpenCV-Python图像处理:腐蚀和膨胀原理及erode、dilate函数介绍:https://blog.csdn.net/LaoYuanPython/article/details/109441709》介绍了图像腐蚀和膨胀的基本原理,以及OpenCV-Python对应函数erode、dilate的语法以及简单应用。
本节我们准备通过代码验证介绍一些关于OpenCV-Python对应函数erode、dilate一点比较细节和冷门的知识。
本文是老猿关于图像腐蚀与膨胀系列博文之一,该系列包括如下博文:
为了进行相关验证,老猿准备了如下两个函数用于输出矩阵数据以及进行矩阵内容是否相同比较:
print2DMatrix用于输出2阶矩阵的每个元素,每列元素间用Tab键隔开,可以很方便地将数据拷贝到excel中使用:
def print2DMatrix(matrix):
"""
将2阶矩阵按照行和列方式打印输出每个元素
:param matrix: 需要打印的矩阵
:return: None
"""
if len(matrix.shape)!=2:
print("只能输出二阶矩阵的信息,传入实参不是二阶矩阵,退出!")
return None
x,y = matrix.shape
for i in range(x):
print(" ")
for j in range(y):
if matrix[i][j]==np.NaN:print(f' NAN',end=' ')
if matrix.dtype in(np.uint8,np.uint,np.uint16,np.uint32):print(f'{matrix[i][j]:4d}',end='\t')
else:print(f'{matrix[i][j]:4f}',end='\t')
print("\n")
cmpMatrix函数用于比较两个矩阵的内容是否完全一致,如果是输出True,否则输出False。
def cmpMatrix(m1,m2):
if m1.shape!=m2.shape:
return False
cmpM = m1==m2
return cmpM.all()
图片文件名为pictures.png,图片内包含了多个OpenCV处理的经典图片,每张图片从1-13进行了编号,如下:
上述图片中,除了7号和8号图片的噪点外,3号图片增加了彩色噪点,在9号图片中加了两种不同颜色噪点、11号图片靠近左上角加了黑色噪点、13号图片靠近左下位置加了白色噪点。
除了使用图片测试外,还准备了一个10*10的小矩阵进行测试,便于输出数据查看。该矩阵的生成方法如下:
img = np.zeros((10, 10),np.uint8)
for row in range(10):
for col in range(10):
img[row,col] = row*col
用代码生成一个长方形的长和宽都为偶数的核矩阵,然后对图像矩阵进行腐蚀和膨胀处理,看程序是否正常运行。
对应代码如下:
def kernalTest():#测试腐蚀和膨胀的核的作用函数
imgMatrix = cv2.imread(r'F:\pic\pictures.png', cv2.IMREAD_GRAYSCALE) #读入图片转成灰度图像矩阵imgMatrix
#构造小矩阵littleMatrix
littleMatrix = np.zeros((10, 10), np.uint8)
for row in range(10):
for col in range(10):
littleMatrix[row, col] = row * col
kernal64 = cv2.getStructuringElement(cv2.MORPH_ELLIPSE,(6, 4))
imgErode = cv2.erode(littleMatrix, kernal64, borderType=cv2.BORDER_CONSTANT, borderValue=0)
imgDilate = cv2.dilate(littleMatrix, kernal64, borderType=cv2.BORDER_CONSTANT, borderValue=0)
print("kernal64矩阵内容:")
print2DMatrix(kernal64)
print("littleMatrix小矩阵数据如下:")
print2DMatrix(littleMatrix)
print("littleMatrix小矩阵腐蚀后结果数据如下:")
print2DMatrix(imgErode)
print("littleMatrix小矩阵膨胀后结果数据如下:")
print2DMatrix(imgDilate)
imgErode = cv2.erode(imgMatrix, kernal64, borderType=cv2.BORDER_CONSTANT, borderValue=0)
imgDilate = cv2.dilate(imgMatrix, kernal64, borderType=cv2.BORDER_CONSTANT, borderValue=0)
cv2.imshow('imgErode', imgErode)
cv2.imshow('imgDilate', imgDilate)
cv2.waitKey(0)
cv2.destroyAllWindows()
kernalTest()
程序执行打印输出内容如下:
kernal64矩阵内容:
0 0 0 1 0 0
1 1 1 1 1 1
1 1 1 1 1 1
1 1 1 1 1 1
littleMatrix小矩阵数据如下:
0 0 0 0 0 0 0 0 0 0
0 1 2 3 4 5 6 7 8 9
0 2 4 6 8 10 12 14 16 18
0 3 6 9 12 15 18 21 24 27
0 4 8 12 16 20 24 28 32 36
0 5 10 15 20 25 30 35 40 45
0 6 12 18 24 30 36 42 48 54
0 7 14 21 28 35 42 49 56 63
0 8 16 24 32 40 48 56 64 72
0 9 18 27 36 45 54 63 72 81
littleMatrix小矩阵腐蚀后结果数据如下:
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 2 4 6 7 0 0
0 0 0 0 3 6 9 12 0 0
0 0 0 0 4 8 12 16 0 0
0 0 0 0 5 10 15 20 0 0
0 0 0 0 6 12 18 24 0 0
0 0 0 0 7 14 21 28 0 0
0 0 0 0 0 0 0 0 0 0
littleMatrix小矩阵膨胀后结果数据如下:
2 3 4 5 6 7 8 9 9 9
4 6 8 10 12 14 16 18 18 18
6 9 12 15 18 21 24 27 27 27
8 12 16 20 24 28 32 36 36 36
10 15 20 25 30 35 40 45 45 45
12 18 24 30 36 42 48 54 54 54
14 21 28 35 42 49 56 63 63 63
16 24 32 40 48 56 64 72 72 72
18 27 36 45 54 63 72 81 81 81
18 27 36 45 54 63 72 81 81 81
从上面输出内容可以看出,核矩阵第1行第4列值为1,为内接椭圆顶点,因此也是默认中心点的横坐标,值为3=6/2,按此推测,则默认中心点为(3,2),以此核实相关结果矩阵数据,与输出结果相同。
除了输出小矩阵的腐蚀情况数据,程序还输出了图像矩阵的截图,其中腐蚀后图像如下:
可以看到原图对应灰度图腐蚀后,除了常规的亮度区域变小黑色区域变大外,3号图片噪点增大,7号、9号、13号图片噪点消失,11号图片黑色噪点扩大。
图片膨胀后结果图像如下:
可以看到原图对应灰度图膨胀后,除了亮度区域变大黑色区域变小外,3号图片噪点基本消失,7号、9号、13号图片噪点扩大,11号图片黑色噪点消失。
经上述代码验证,核矩阵为长方形且长和宽为偶数情况下,腐蚀和膨胀都能正常执行。
用代码生成一个3*3的数据为浮点数的核矩阵,然后对图像矩阵进行腐蚀和膨胀处理,看程序是否正常运行。
对应代码如下:
def kernalTest():#测试腐蚀和膨胀的核的作用函数
littleMatrix = np.zeros((10, 10), np.uint8)
for row in range(10):
for col in range(10):
littleMatrix[row, col] = row * col
kernal33 = cv2.getStructuringElement(cv2.MORPH_RECT,(3, 3))
kernal33Float = kernal33.astype(np.float32)*0.1
print(f"kernal33矩阵元素类型为:{kernal33.dtype},数据内容如下:")
print2DMatrix(kernal33)
print(f"kernal33Float矩阵元素类型为:{kernal33Float.dtype},数据内容如下:")
print2DMatrix(kernal33Float)
imgErode = cv2.erode(littleMatrix, kernal33, borderType=cv2.BORDER_CONSTANT, borderValue=0)
imgDilate = cv2.dilate(littleMatrix, kernal33, borderType=cv2.BORDER_CONSTANT, borderValue=0)
imgErodeFloat = cv2.erode(littleMatrix, kernal33Float, borderType=cv2.BORDER_CONSTANT, borderValue=0)
imgDilateFloat = cv2.dilate(littleMatrix, kernal33Float, borderType=cv2.BORDER_CONSTANT, borderValue=0)
print("littleMatrix小矩阵数据如下:")
print2DMatrix(littleMatrix)
print(f"littleMatrix小矩阵采用普通0、1核矩阵腐蚀后矩阵元素类型为:{kernal33Float.dtype},结果数据如下:")
print2DMatrix(imgErode)
print(f"littleMatrix小矩阵采用采用普通0、1核矩阵膨胀后矩阵元素类型为:{kernal33Float.dtype},结果数据如下:")
print2DMatrix(imgDilate)
print(f"littleMatrix小矩阵采用浮点核矩阵腐蚀后矩阵元素类型为:{kernal33Float.dtype},结果数据如下:")
print2DMatrix(imgErodeFloat)
print(f"littleMatrix小矩阵采用浮点核矩阵膨胀后矩阵元素类型为:{kernal33Float.dtype},结果数据如下:")
print2DMatrix(imgDilateFloat)
if cmpMatrix(imgErode,imgErodeFloat):
print("使用普通0、1核矩阵腐蚀和采用浮点核矩阵腐蚀得到的结果矩阵相同")
else:
print("使用普通0、1核矩阵腐蚀和采用浮点核矩阵腐蚀得到的结果矩阵不相同")
if cmpMatrix(imgDilate, imgDilateFloat):
print("使用普通0、1核矩阵膨胀和采用浮点核矩阵膨胀得到的结果矩阵相同")
else:
print("使用普通0、1核矩阵膨胀和采用浮点核矩阵膨胀得到的结果矩阵不相同")
kernalTest()
程序执行打印输出内容如下:
kernal33矩阵元素类型为:uint8,数据内容如下:
1 1 1
1 1 1
1 1 1
kernal33Float矩阵元素类型为:float32,数据内容如下:
0.100000 0.100000 0.100000
0.100000 0.100000 0.100000
0.100000 0.100000 0.100000
littleMatrix小矩阵数据如下:
0 0 0 0 0 0 0 0 0 0
0 1 2 3 4 5 6 7 8 9
0 2 4 6 8 10 12 14 16 18
0 3 6 9 12 15 18 21 24 27
0 4 8 12 16 20 24 28 32 36
0 5 10 15 20 25 30 35 40 45
0 6 12 18 24 30 36 42 48 54
0 7 14 21 28 35 42 49 56 63
0 8 16 24 32 40 48 56 64 72
0 9 18 27 36 45 54 63 72 81
littleMatrix小矩阵采用普通0、1核矩阵腐蚀后矩阵元素类型为:uint8,结果数据如下:
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 1 2 3 4 5 6 7 0
0 0 2 4 6 8 10 12 14 0
0 0 3 6 9 12 15 18 21 0
0 0 4 8 12 16 20 24 28 0
0 0 5 10 15 20 25 30 35 0
0 0 6 12 18 24 30 36 42 0
0 0 7 14 21 28 35 42 49 0
0 0 0 0 0 0 0 0 0 0
littleMatrix小矩阵采用采用普通0、1核矩阵膨胀后矩阵元素类型为:uint8,结果数据如下:
1 2 3 4 5 6 7 8 9 9
2 4 6 8 10 12 14 16 18 18
3 6 9 12 15 18 21 24 27 27
4 8 12 16 20 24 28 32 36 36
5 10 15 20 25 30 35 40 45 45
6 12 18 24 30 36 42 48 54 54
7 14 21 28 35 42 49 56 63 63
8 16 24 32 40 48 56 64 72 72
9 18 27 36 45 54 63 72 81 81
9 18 27 36 45 54 63 72 81 81
littleMatrix小矩阵采用浮点核矩阵腐蚀后矩阵元素类型为:uint8,结果数据如下:
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 1 2 3 4 5 6 7 0
0 0 2 4 6 8 10 12 14 0
0 0 3 6 9 12 15 18 21 0
0 0 4 8 12 16 20 24 28 0
0 0 5 10 15 20 25 30 35 0
0 0 6 12 18 24 30 36 42 0
0 0 7 14 21 28 35 42 49 0
0 0 0 0 0 0 0 0 0 0
littleMatrix小矩阵采用浮点核矩阵膨胀后矩阵元素类型为:uint8,结果数据如下:
1 2 3 4 5 6 7 8 9 9
2 4 6 8 10 12 14 16 18 18
3 6 9 12 15 18 21 24 27 27
4 8 12 16 20 24 28 32 36 36
5 10 15 20 25 30 35 40 45 45
6 12 18 24 30 36 42 48 54 54
7 14 21 28 35 42 49 56 63 63
8 16 24 32 40 48 56 64 72 72
9 18 27 36 45 54 63 72 81 81
9 18 27 36 45 54 63 72 81 81
使用普通0、1核矩阵腐蚀和采用浮点核矩阵腐蚀得到的结果矩阵相同
使用普通0、1核矩阵膨胀和采用浮点核矩阵膨胀得到的结果矩阵相同
Process finished with exit code 0
将核矩阵的元素换成其他数字,如直接在kernal33的元素值上乘以2作为核的元素值,结论相同。
经测试验证核矩阵元素:
但测试时发现有个问题,就是当核矩阵元素为浮点数时,核矩阵的元素不能出现0、0.0等零值,否则会出现如下异常:
cv2.error: OpenCV(4.3.0) C:/projects/opencv-python/opencv/modules/imgproc/src/morph.simd.hpp:649: error: (-215:Assertion failed) _kernel.type() == CV_8U in function 'cv::opt_AVX2::`anonymous-namespace'::MorphFilter<struct cv::opt_AVX2::`anonymous namespace'::MinOp,struct cv::opt_AVX2::A0x30080e86::MorphVec::VMin<struct cv::hal_AVX2::v_uint8x32> > >::MorphFilter'
该问题具体原因老猿暂时没有去研究。
总体来说,除了浮点类型的核矩阵不能出现零值外,核矩阵(必须小于源矩阵)可以自定义(就如同上面代码定义littleMatrix矩阵一样),其各元素的值可以随意赋值,不一定非要通过getStructuringElement构建核矩阵,大家可以自己尝试一下。
在老猿所查阅的所有介绍腐蚀和膨胀的资料中,腐蚀和膨胀处理的图像都是灰度图,没有看到处理彩色图像的,查阅官网资料,明确告知源图像矩阵可以是任意通道数的。
为此老猿做了验证,直接使用彩色图像进行腐蚀和膨胀,我们来看效果。
def srcImgTest(imgfile):#测试腐蚀和膨胀的图像源类型函数
img = cv2.imread(imgfile)
kernal = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))
imgErode = cv2.erode(img, kernal, borderType=cv2.BORDER_CONSTANT, borderValue=0)
imgDilate = cv2.dilate(img, kernal, borderType=cv2.BORDER_CONSTANT, borderValue=0)
print("kernal:")
print2DMatrix(kernal)
cv2.imshow('imgErode', imgErode)
cv2.imshow('imgDilate', imgDilate)
cv2.waitKey(0)
cv2.destroyAllWindows()
srcImgTest(r'F:\pic\pictures.png')#处理彩色图像文件
腐蚀结果图像截图:
可以看到原图腐蚀后,总体图像变暗,除了常规的亮度区域变小浅色区域变大外,3号图片彩色噪点变大,7号、9号、13号图片噪点消失,11号图片黑色噪点扩大。
膨胀结果图像截图:
可以看到原图膨胀后,总体变亮,除了常规的亮度区域变大暗色区域变小外,3号图片彩色噪点与腐蚀一样变大,7号、9号、13号图片噪点变大,8号、11号图片黑色噪点消失。
因此可以得出结论,腐蚀和膨胀可以直接对彩色图像进行处理,可以用于消除部分噪点,并且这种处理方式无法通过灰度图进行腐蚀和膨胀再进行其他图像操作来达到相同效果,但总体来说灰度图的腐蚀和膨胀对图像轮廓的处理应用更广,同样有彩色图像腐蚀和膨胀无法替代的能力。
在《OpenCV-Python图像处理:腐蚀和膨胀原理及erode、dilate函数介绍:https://blog.csdn.net/LaoYuanPython/article/details/109441709》中介绍了腐蚀和膨胀函数的参数borderType的值的含义,但该值缺省值为None,具体对应的边界类型官网没有介绍。只是从边界类型取值说明中感觉BORDER_ISOLATED 这个值比较象。
构建一个矩阵,用所有可能的边界类型进行腐蚀和膨胀处理,与采用borderType为None的腐蚀和膨胀结果进行对比。
def bordtypeTest():
bordetype={cv2.BORDER_CONSTANT:"BORDER_CONSTANT",cv2.BORDER_REPLICATE:"BORDER_REPLICATE ",cv2.BORDER_REFLECT:"BORDER_REFLECT",
cv2.BORDER_REFLECT_101:'BORDER_REFLECT_101', cv2.BORDER_ISOLATED:'BORDER_ISOLATED'}
img = np.zeros((100,100),np.uint8)
for row in range(100):
for col in range(100):
img[row, col] = row*2+ col
kernal = cv2.getStructuringElement(cv2.MORPH_CROSS, (5, 5))
#kernal = np.zeros((3,3),dtype=np.uint8)
kernal[0,0] = 1
imgDefaultErode = cv2.erode(img, kernal)
imgDefaultDilate = cv2.dilate(img, kernal)
for k in bordetype:
v = bordetype[k]
if k==0:
imgErode = cv2.erode(img, kernal, borderType=k, borderValue=0)
imgDilate = cv2.dilate(img, kernal, borderType=k, borderValue=0)
else:
imgErode = cv2.erode(img, kernal, borderType=k)
imgDilate = cv2.dilate(img, kernal, borderType=k)
cmpErode = cmpMatrix(imgErode,imgDefaultErode)
cmpDilate = cmpMatrix(imgDilate,imgDefaultDilate)
if cmpErode:
print(f"bordtype={v}的腐蚀结果与缺省bordtype腐蚀图像相同")
else:print(f"bordtype={v}的腐蚀结果与缺省bordtype腐蚀图像不同")
if cmpDilate:
print(f"bordtype={v}的膨胀结果与缺省bordtype膨胀图像相同")
else:print(f"bordtype={v}的膨胀结果与缺省bordtype膨胀图像不同")
bordtypeTest()
bordtype=BORDER_CONSTANT的腐蚀结果与缺省bordtype腐蚀图像不同
bordtype=BORDER_CONSTANT的膨胀结果与缺省bordtype膨胀图像相同
bordtype=BORDER_REPLICATE 的腐蚀结果与缺省bordtype腐蚀图像不同
bordtype=BORDER_REPLICATE 的膨胀结果与缺省bordtype膨胀图像相同
bordtype=BORDER_REFLECT的腐蚀结果与缺省bordtype腐蚀图像不同
bordtype=BORDER_REFLECT的膨胀结果与缺省bordtype膨胀图像相同
bordtype=BORDER_REFLECT_101的腐蚀结果与缺省bordtype腐蚀图像相同
bordtype=BORDER_REFLECT_101的膨胀结果与缺省bordtype膨胀图像不同
bordtype=BORDER_ISOLATED的腐蚀结果与缺省bordtype腐蚀图像相同
bordtype=BORDER_ISOLATED的膨胀结果与缺省bordtype膨胀图像相同
从上述输出可以看出,除了bordtype=BORDER_ISOLATED与等于None效果完全相同,其他值不能完全相同,并且在多种不同图像下上述输出的比较情况还会发生变化,但BORDER_ISOLATED的比较情况固定不变。
在腐蚀和膨胀处理的参数中,iterations形参对应实参n(n大于1)的作用我们认为是针对每次腐蚀的结果再次进行腐蚀,直到执行腐蚀n次。
def iterationsImgTest(imgfile):#测试腐蚀和膨胀的图像源类型函数
img = cv2.imread(imgfile)
kernal = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))
imgErode1 = cv2.erode(img, kernal, borderType=cv2.BORDER_CONSTANT, borderValue=0)
imgErode2 = cv2.erode(imgErode1, kernal, borderType=cv2.BORDER_CONSTANT, borderValue=0)
imgErodeIterations = cv2.erode(img, kernal, iterations=2,borderType=cv2.BORDER_CONSTANT, borderValue=0)
if cmpMatrix(imgErode1,imgErodeIterations):
print("腐蚀1次结果图像和迭代腐蚀2次结果图像相同")
else:
print("腐蚀1次结果图像和迭代腐蚀2次结果图像不相同")
if cmpMatrix(imgErode2,imgErodeIterations):
print("腐蚀2次结果图像和迭代腐蚀2次结果图像相同")
else:
print("腐蚀2次结果图像和迭代腐蚀2次结果图像不相同")
iterationsImgTest(r'F:\pic\pictures.png')
腐蚀1次结果图像和迭代腐蚀2次结果图像不相同
腐蚀2次结果图像和迭代腐蚀2次结果图像相同
iterations确实是针对每次腐蚀的结果再次进行腐蚀,直到执行腐蚀次数与iterations的实参相等。
本文详细测试验证了OpenCV的腐蚀和膨胀erode、dilate函数在进行图像腐蚀和膨胀处理的一些细节,包括核矩阵的形状、核元素取值类型及值对腐蚀和膨胀的影响、图像矩阵是否支持彩色图片、bordType缺省值对应的边界类型分析以及迭代次数的作用,有助于大家深入理解OpenCV的腐蚀和膨胀erode、dilate函数的使用。
更多OpenCV-Python的介绍请参考专栏《OpenCV-Python图形图像处理 》
专栏网址:https://blog.csdn.net/laoyuanpython/category_9979286.html
如果阅读本文于您有所获,敬请点赞、评论、收藏,谢谢大家的支持!
小技巧: 可以通过点击博文下面的一键三连按钮实现对相关文章的点赞、收藏和对博客的关注,谢谢!
老猿的付费专栏《使用PyQt开发图形界面Python应用 》(https://blog.csdn.net/laoyuanpython/category_9607725.html)专门介绍基于Python的PyQt图形界面开发基础教程,付费专栏《moviepy音视频开发专栏》 (https://blog.csdn.net/laoyuanpython/category_10232926.html)详细介绍moviepy音视频剪辑合成处理的类相关方法及使用相关方法进行相关剪辑合成场景的处理,两个专栏都适合有一定Python基础但无相关知识的小白读者学习。
付费专栏文章目录:《moviepy音视频开发专栏文章目录》(https://blog.csdn.net/LaoYuanPython/article/details/107574583)、《使用PyQt开发图形界面Python应用专栏目录 》(https://blog.csdn.net/LaoYuanPython/article/details/107580932)。
对于缺乏Python基础的同仁,可以通过老猿的免费专栏《专栏:Python基础教程目录》(https://blog.csdn.net/laoyuanpython/category_9831699.html)从零开始学习Python。
如果有兴趣也愿意支持老猿的读者,欢迎购买付费专栏。