形状学操作和图画梯度
一、形状学操作
主要有胀大、腐蚀、开(先腐蚀后胀大)、闭(线胀大后腐蚀)操作。用到的函数有:cv.erode(), cv.dilate(), cv.morphologyEx()
用法如下:
- cv.erode( src, kernel[, dst[, anchor[, iterations[, borderType[, borderValue]]]]] ) -> dst
- cv.dilate( src, kernel[, dst[, anchor[, iterations[, borderType[, borderValue]]]]] ) -> dst
- cv.morphologyEx( src, op, kernel[, dst[, anchor[, iterations[, borderType[, borderValue]]]]] ) -> dst
二、概念
形状学变换是基于图画形状的一些简略操作。它通常在二值图画上履行。它需求两个输入,一个是咱们的原始图画,第二个是所谓的结构元素或中心,这决定了操作的性质。两个根本的形状学算子是腐蚀和胀大。然后它的变体方式,如开,闭操作,核算梯度等。
1. 腐蚀操作
腐蚀的根本概念就像土壤腐蚀一样,它腐蚀了远景物体的鸿沟(一般将远景设置为白色,背景设置为黑色)。内核滑过图画(就像2D 卷积)。只有当内核下的一切像素都为1(远景)时,原始图画的扫描内核中心的一个像素(1或0)才被认为是1,不然它就会被腐蚀(变为零)。
因而,一切接近鸿沟的像素将被丢掉。因而,远景物体的边际被腐蚀,或许仅仅是图画中的白色区域减小。它对去除小的白噪声 ,别离两个衔接的对象等很有用。
import cv2 as cv
import numpy as np
import matplotlib
import matplotlib.pyplot as plt
%matplotlib inline
def cv_show(name, img):
cv.imshow(name, img)
cv.waitKey(0)
cv.destroyAllWindows()
def compare(imgs):
# for i in range(len(imgs)):
# imgs[i][:,-3:-1,:] = [255,255,255]
res = np.hstack(imgs)
cv_show('Compare', res)
img = cv.imread('fur.png')
# 胀大和腐蚀都需求自定义内核
kernel = np.ones((3, 3), np.uint8)
e = cv.erode(img,kernel)
compare([img, e])
能够看出一些毛毛刺刺被去除,可是原文本也被腐蚀了一部分
2. 胀大操作
与腐蚀操作作用相反。假如内核下只要有一个像素是“1”,那么内核中心像素元素便是“1”。因而,它增加了图画中的白色区域或远景物体的巨细增加。通常情况下,在去除噪音的情况下,腐蚀之后是胀大(也便是开操作)。因为,腐蚀除去了白色的噪音,但它也缩小了咱们的对象。所以咱们扩张它。因为噪音消失了,它们不会再回来,可是咱们的目标区域增大了。它也能够用来衔接一个物体的破损部分。
img = cv.imread('fur.png')
# 胀大和腐蚀都需求自定义内核
kernel = np.ones((3, 3), np.uint8)
e = cv.erode(img,kernel)
d_1 = cv.dilate(img, kernel)
d_2 = cv.dilate(e,kernel)
compare([e, d_1, d_2])
3. 开操作
开操作只是先腐蚀后胀大合并的一个名称。上面的例子表明,它在消除噪音方面很有用。这里咱们运用函数cv.morphologyEx()
cv.morphologyEx函数的第二个参数一共有五种取值:
- cv.MORPH_OPEN : 开操作,先腐蚀后胀大
- cv.MORPH_ClOSE:闭操作,先胀大后腐蚀
- cv.MORPH_GRADIENT: 提取鸿沟,利用胀大的成果减去腐蚀的成果
- cv.MORPH_TOPHAT: 输入图画减去开操作成果。
- cv.MORPH_BLACKHAT:输入图画减去闭操作成果
img = cv.imread('fur.png')
# 第二个参数是形状学操作的取值
kernel = np.ones((3, 3), np.uint8)
res = cv.morphologyEx(img, cv.MORPH_OPEN,kernel )
compare([img, res])
4. 闭操作
闭操作是开操作的敌对操作,闭操作先胀大后腐蚀。它在修补远景物体内部的小洞或物体上的小黑点时很有用。
img = cv.imread('fur.png')
kernel = np.ones((3,3), np.uint8)
closing = cv.morphologyEx(img, cv.MORPH_CLOSE, kernel)
compare([img, closing])
5. 求梯度
这是一个图画的胀大操作减去腐蚀操作所得成果,成果看起来就像物体的轮廓。
img = cv.imread('fur.png')
kernel = np.ones((3,3), np.uint8)
gradient = cv.morphologyEx(img, cv.MORPH_GRADIENT, kernel)
compare([img, gradient])
6. 弁冕操作
输入图画减去开操作所得图画。
img = cv.imread('fur.png')
kernel = np.ones((3,3), np.uint8)
tophat = cv.morphologyEx(img, cv.MORPH_TOPHAT, kernel)
compare([img, tophat])
7. 黑帽操作
输入图画减去关操作所的成果
img = cv.imread('fur.png')
kernel = np.ones((3,3), np.uint8)
blackhat = cv.morphologyEx(img, cv.MORPH_BLACKHAT, kernel)
compare([img, blackhat])
8. 构建元素
在前面的示例中,咱们凭借 Numpy 手动创建了一个结构元素。它是长方形的。但在某些情况下,您可能需求椭圆形或圆形的内核。因而,为此,OpenCV 有一个函数 cv.getStructuringElement ()。您只需传递内核的形状和巨细,就能够得到所需的内核。
- 内核形状有如下选项:
- cv.MORPH_RECT
- cv.MORPH_ELLIPSE
- cv.MORPH_CROSS
# Rectangular Kernel
cv.getStructuringElement(cv.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 椭圆内核
cv.getStructuringElement(cv.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
cv.getStructuringElement(cv.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)
# 作用对比
img = cv.imread('fur.png')
kernel_cross = cv.getStructuringElement(cv.MORPH_CROSS,(3,3))
kernel_rect = cv.getStructuringElement(cv.MORPH_RECT,(3,3))
cross = cv.morphologyEx(img, cv.MORPH_OPEN, kernel_cross)
rect = cv.morphologyEx(img, cv.MORPH_OPEN, kernel_rect)
compare([img, cross, rect])
能够看出,自定义的cross内核仍留下了以下白色斑驳,而rect内核得到了较为清晰的作用
三、图画梯度
本节包括对图画梯度和边际的一些介绍,主要由以下函数 cv.Sobel(), cv.Scharr(), cv.Laplacian()。
1. 概念
OpenCV 供给三种类型的梯度过滤器或高经过滤器:Sobel、Scharr和Laplacian,下面进行详细介绍
- 三者用法如下:
- cv.Sobel( src, ddepth, dx, dy[, dst[, ksize[, scale[, delta[, borderType]]]]] ) -> dst
- cv.Scharr( src, ddepth, dx, dy[, dst[, scale[, delta[, borderType]]]] ) -> dst
- cv.Laplacian( src, ddepth[, dst[, ksize[, scale[, delta[, borderType]]]]] ) -> dst
其间的ddepth代表了成果图画的通道数,-1则和输入图画保持一致。dx、dy别离代表了对x、y的导数阶数。
2. Sobel和Scharr概念
Sobel算子是联合高斯平滑加微分运算,所以抗噪声能力更强。 您能够指定要采用的导数方向,笔直或水平(别离经过参数 yorder 和 xorder)。 您还能够经过参数 ksize 指定内核的巨细。 假如 ksize = -1,则运用 3×3 Scharr 过滤器的得到的成果要比 3 x 3 Sobel过滤器成果更好。
-
三阶Sobel梯度矩阵:Gx=[−10+1−20+2−10+1]∗AandGy=[−1−2−1000+1+2+1]∗A\mathbf{G}_{x}=\left[\begin{array}{ccc} -1 & 0 & +1 \\ -2 & 0 & +2 \\ -1 & 0 & +1 \end{array}\right] * \mathbf{A} \quad \text { and } \quad \mathbf{G}_{y}=\left[\begin{array}{ccc} -1 & -2 & -1 \\ 0 & 0 & 0 \\ +1 & +2 & +1 \end{array}\right] * \mathbf{A} 核算得出负数成果或许超过了K比特上限时会进行切断:取为0或(2^k – 1),所以一般咱们会别离求出对x的导数(运用cv.convertScaleAbs取绝对值),在求对y的导数(取绝对值)后,运用cv.addWeighted()将两者相加
-
三阶Scharr梯度矩阵:Gx=[−303−10010−303]Gy=[−3−10−30003103]G_{x}=\left[\begin{array}{ccc} -3 & 0 & 3 \\ -10 & 0 & 10 \\ -3 & 0 & 3 \end{array}\right] \\ G_{y}=\left[\begin{array}{ccc} -3 & -10 & -3 \\ 0 & 0 & 0 \\ 3 & 10 & 3 \end{array}\right] 核算方式同上
3. Laplacian 概念
它核算由联系sr=∂2src∂x2+∂2src∂y2\Delta sr =\frac{\partial^{2}src}{\partial x^{2}}+\frac{\partial^{2} src}{\partial y^{2}}给出的图画的拉普拉斯算子,其间每个导数都是运用 Sobel 导数找到的。 假如 ksize = 1,则运用以下内核进行过滤:kernel=[0101−41010]kernel = \begin{bmatrix} 0 & 1 &0 \\ 1 & -4 &1 \\ 0 &1 &0 \end{bmatrix}
img = cv.imread('chessboard.jpg')
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
laplacian = cv.Laplacian(gray,cv.CV_64F)
sobelx = cv.Sobel(gray,cv.CV_64F,1,0,ksize=5)
sobely = cv.Sobel(gray,cv.CV_64F,0,1,ksize=5)
compare([ sobelx, sobely, laplacian])
4. 一个重要示例
在上一个示例中,输出数据类型为 cv_8U 或 np.uint8。可是有一个小小的问题。是非过渡视为正斜率(具有正值) ,白白过渡视为负斜率(具有负值)。所以当你把数据转换成 np.uint8时,一切的负斜率都变成了零。简略地说,你错过了这个边际。
假如你想检测两个边际,更好的挑选是将输出数据类型保持为更高的方式,如 cv.CV_16S、cv.CV_64F 等,取其绝对值,然后转换回 cv.CV_8U。 下面的代码演示了水平 Sobel 滤波器的此进程和成果差异。
img = cv.imread('round.png',0)
# 导致负值归0
sobelx8u = cv.Sobel(img,cv.CV_8U,1,0,ksize=3)
# Output dtype = cv.CV_64F. Then take its absolute and convert to cv.CV_8U
sobelx64f = cv.Sobel(img,cv.CV_64F,1,0,ksize=3)
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()
5. Sobel算子的绝对值转化示例
首要咱们直接运用Sobel算子核算梯度时,因为sobel算子的核算方向是左减右,下减上。故有时候会形成负值现象,当传入的ddepth为 cv.CV_64F时,会对图画进行切断,导致负数显现不出来,所以咱们进行绝对值转换。
# 核算梯度和阈值处理时,最好输入灰度图画
img = cv.imread('round.png',0)
sobel_x = cv.Sobel(img, cv.CV_64F, 1,0 ,ksize = 3)
sobel_X = cv.convertScaleAbs(sobel_x)
compare([img, sobel_x, sobel_X])
# 同理咱们能够得到y方向的绝对值
sobel_y = cv.Sobel(img, cv.CV_64F, 0, 1,ksize = 3)
sobel_Y = cv.convertScaleAbs(sobel_y)
compare([img, sobel_y, sobel_Y])
# 假如直接出入 dx =1 ,dy =1 则核算出来的成果不如别离核算后相加来得明了
Sobel_xy = cv.convertScaleAbs(cv.Sobel(img, cv.CV_64F, 1, 1 , ksize = 3))
Sobel_XY = cv.addWeighted(sobel_X, 0.5, sobel_Y, 0.5, 0)
compare([Sobel_xy, Sobel_XY])
显着,别离核算后再相加所得的边际没有重影且愈加显着