(三)OpenCV中的图像处理之形态转换和图像梯度

注释:本文翻译自OpenCV3.0.0 document->OpenCV-Python Tutorials,包括对原文档种错误代码的纠正

3.5 形态转换

3.5.1 目标:

  • 学会不同的形态学操作,如:腐蚀、膨胀、开运算、闭运算等
  • 学会这些函数:cv2.erode()、cv2.dilate()、cv2.morphologyEx()

3.5.2 原理

形态学操作是指一些基于图像形状的基本操作。它通常在灰度图像上执行。该操作需要两个输入,一个是二值图像,另一个是结构元素(内核),它决定了操作的性质。两个最基本的形态学操作是腐蚀和膨胀,它们的变体是开运算、闭运算、渐变等。将以下面的图像为例逐个看到每个操作的结果。

3.5.3 腐蚀

腐蚀的基本思想就像土壤侵蚀,它侵蚀了前景物体的边缘(总是尽量保持前景物体白色)。那么它有什么作用呢?内核滑过图像,只有当内核下的所有像素都为1时,才能使原始图像中(10)中的像素置为1,否则将被侵蚀(置为0.

所以情况是,边界附近的的所有像素将被丢弃,这取决于内核的大小,因此前景物体的厚度或尺寸将会变小。有助于分离两个连接的对象。

erosion = cv2.erode(img,kernel,iterations =1)

3.5.4 膨胀

      正好与侵蚀相反,如果内核下至少有一个元素为“1”,那么像素元素置为“1”。它增加了前景对象的大小。通常,在噪声消除的情况下,腐蚀操作后是膨胀运算,因为腐蚀消除了白色的噪音,缩小了我们的对象,所以用膨胀扩大它,而噪声已经被消除了便不会再回来,但我们的对象面积增加了。该操作可以连接物体的断链部分。

dilation = cv2.dilate(img,kernel,iterations = 1)

3.5.5 开运算

先腐蚀后膨胀,有利于去除噪声,cv2.morphologyEx()

opening = cv2.morphologyEx(img, cv2.MORPH_OPEN, kernel)

3.5.6 闭运算

先膨胀后腐蚀,有助于去除前景物体上的小斑点或噪音。

closing = cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel)

3.5.7 形态学梯度

这是被腐蚀图像和被膨胀图像的区别。使结果看起来像找到对象的轮廓。

gradient = cv2.morphologyEx(img, cv2.MORPH_GRADIENT, kernel)

3.5.8 顶帽

这是输入图像和执行了开运算图像的区别。

tophat = cv2.morphologyEx(img, cv2.MORPH_TOPHAT, kernel)

3.5.9 黑帽

这是执行了闭运算的图像和输入图像之间的区别。

blackhat = cv2.morphologyEx(img, cv2.MORPH_BLACKHAT, kernel)

3.5.10 结构元素

Numpy的帮助下,我们在前面的栗子中手动创建了一个矩形的结构化元素,但在某些情况下,我们可能需要圆形/椭圆的结构化元素(内核)。OpenCV中有一个函数cv2.getStructingElement(),只需要传入内核的形状和大小,就可以得到想要的内核。

# Rectangular Kernel
>>> cv2.getStructuringElement(cv2.MORPH_RECT,(5,5))
array([[1, 1, 1, 1, 1],
       [1, 1, 1, 1, 1],
       [1, 1, 1, 1, 1],
       [1, 1, 1, 1, 1],
       [1, 1, 1, 1, 1]], dtype=uint8)

# Elliptical Kernel
>>> cv2.getStructuringElement(cv2.MORPH_ELLIPSE,(5,5))
array([[0, 0, 1, 0, 0],
       [1, 1, 1, 1, 1],
       [1, 1, 1, 1, 1],
       [1, 1, 1, 1, 1],
       [0, 0, 1, 0, 0]], dtype=uint8)

# Cross-shaped Kernel
>>> cv2.getStructuringElement(cv2.MORPH_CROSS,(5,5))
array([[0, 0, 1, 0, 0],
       [0, 0, 1, 0, 0],
       [1, 1, 1, 1, 1],
       [0, 0, 1, 0, 0],
       [0, 0, 1, 0, 0]], dtype=uint8)

下面的栗子结合了腐蚀、膨胀、开运算、闭运算、形态学梯度

# -*- coding: utf-8 -*-
'''
形态转换:腐蚀、膨胀、开运算、闭运算
1.形态转换需要两个输入,一个是二值图像(灰度图像),另一个是内核;
2.两个最基本的形态学操作是腐蚀和膨胀,它们的变体是开运算、闭运算、渐变等
以下栗子结合腐蚀、膨胀、开运算、闭运算,形态学梯度
'''

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

plt.rcParams['font.sans-serif'] = ['SimHei']  # 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False  # 用来正常显示负号

img = cv2.imread('14.png')
# 构造5*5的内核
kernel = np.ones((5, 5), np.uint8)
# 腐蚀:cv2.erode()
erosion = cv2.erode(img, kernel, iterations=1)

# 膨胀:cv2.dilate()
dilation = cv2.dilate(img, kernel, iterations=1)

# 开运算:先腐蚀膨胀,有助于去除噪声 cv2.morphologyEx()
opening = cv2.morphologyEx(img, cv2.MORPH_OPEN, kernel)

# 闭运算:先膨胀后腐蚀,有助于去除前景物体上的小斑点或噪声
closing = cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel)

# 形态学梯度:这是被腐蚀和被膨胀图像的区别,看起来更像找到轮廓的对象
gradient = cv2.morphologyEx(img, cv2.MORPH_GRADIENT, kernel)

# 顶帽:这是输入图像和执行了开运算图像的区别
tophat = cv2.morphologyEx(img, cv2.MORPH_TOPHAT, kernel)

# 黑帽:这是执行了闭运算和输入图像之间的区别
blackhat = cv2.morphologyEx(img, cv2.MORPH_BLACKHAT, kernel)

titles = ["原图", "腐蚀", "膨胀", "开运算", "闭运算", "形态学梯度", "顶帽", "黑帽"]
images = [img, erosion, dilation, opening, closing, gradient, tophat, blackhat]

for i in range(8):
    plt.subplot(2, 4, (i + 1)), plt.imshow(images[i]), plt.title(titles[i])
    plt.xticks([]), plt.yticks([])

plt.show()

结果如下:


3.6 图像梯度

3.6.1 目标:

  • 找到图像的梯度、边缘等
  • 学会这些函数:cv2.Sobel()、cv2.Scharr()、cv2.Laplacian()

3.6.2 原理

OpenCV提供了三种类型的梯度滤波器/高通滤波器,Sobel、Scharr和laplacian,这里会一一介绍。

1.Sobel导数和Scharr导数

Sobel操作数是高斯平滑加分散操作的联合,因此更能抵抗噪音。 您可以指定要采取的导数的方向,垂直方向或水平方向(分别为参数yorderxorder)。 您还可以通过参数ksize指定内核的大小。 如果ksize = -1,则使用3x3 Scharr滤波器,该滤波器比3x3 Sobel滤波器有更好的结果。请参阅使用的内核文档。

2.拉普拉斯导数

它计算由关系式 的拉普拉斯算子,该表达式中的导数都是Scharr导数,如果kszie=-1,那么下面的内核会用于滤波:

以下代码显示了在一张图上的所有操作,所有内核大小为5*5,输出图像的深度被置为-1,结果图的类型是np.unit8.


# -*- coding: utf-8 -*-

'''
图像梯度:OpenCV提供了三种类型的梯度滤波器/高通滤波器,Sobel、Scharr和laplacian
'''
import cv2
import numpy as np
from matplotlib import pyplot as plt

plt.rcParams['font.sans-serif'] = ['SimHei']  # 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False  # 用来正常显示负号
img = cv2.imread('sudoku.png',0)

laplacian = cv2.Laplacian(img, cv2.CV_64F)
sobelX = cv2.Sobel(img, cv2.CV_64F, 1, 0, ksize=5)
sobelY = cv2.Sobel(img, cv2.CV_64F, 0, 1, ksize=5)

plt.subplot(2, 2, 1), plt.imshow(img, cmap='gray')
plt.title("Original"), plt.xticks([]), plt.yticks([])
plt.subplot(2, 2, 2), plt.imshow(laplacian, cmap='gray')
plt.title("Laplacian"), plt.xticks([]), plt.yticks([])
plt.subplot(2, 2, 3), plt.imshow(sobelX, cmap='gray')
plt.title("sobelX"), plt.xticks([]), plt.yticks([])
plt.subplot(2, 2, 4), plt.imshow(sobelY, cmap='gray')
plt.title("sobelY"), plt.xticks([]), plt.yticks([])

plt.show()

结果如下:


3.6.3 一件重要的事

在我们的最后一个栗子中,输出数据类型是cv2.CV_8U或np.unit8.但是有一个小问题,做黑白转换时具有正斜率(它具有正值),而做白黑转换时具有负斜率(它具有负值)。所以当你将数据转换为np.uint8时,所有的负斜率均为零。简单来说,你错过了这个边缘。

如果要检测到这两个边,更好的选择是将输出数据类型保持为一些较高的形式,如cv2.CV_16Scv2.CV_64F等,取其绝对值,然后转换回cv2.CV_8U下面的代码演示了水平Sobel滤波器的这个过程以及结果的差异。

代码如下:

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

img = cv2.imread('sudoku.png', 0)

# Output dtype = cv2.CV_8U
sobelx8u = cv2.Sobel(img, cv2.CV_8U, 1, 0, ksize=5)

# Output dtype = cv2.CV_64F. Then take its absolute and convert to cv2.CV_8U
sobelx64f = cv2.Sobel(img, cv2.CV_64F, 1, 0, ksize=5)
abs_sobel64f = np.absolute(sobelx64f)
sobel_8u = np.uint8(abs_sobel64f)

plt.subplot(1, 3, 1), plt.imshow(img, cmap='gray')
plt.title('Original'), plt.xticks([]), plt.yticks([])
plt.subplot(1, 3, 2), plt.imshow(sobelx8u, cmap='gray')
plt.title('Sobel CV_8U'), plt.xticks([]), plt.yticks([])
plt.subplot(1, 3, 3), plt.imshow(sobel_8u, cmap='gray')
plt.title('Sobel abs(CV_64F)'), plt.xticks([]), plt.yticks([])

plt.show()

结果:



猜你喜欢

转载自blog.csdn.net/u014403318/article/details/80493192