欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2022-05-30 12:01:39
...

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

3.5 形态转换

3.5.1 目标:

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

3.5.2 原理

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

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

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()

结果如下:

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

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.拉普拉斯导数

它计算由关系式 (三)OpenCV中的图像处理之形态转换和图像梯度的拉普拉斯算子,该表达式中的导数都是Scharr导数,如果kszie=-1,那么下面的内核会用于滤波:

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

以下代码显示了在一张图上的所有操作,所有内核大小为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()

结果如下:

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

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()

结果:

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