OpenCV的imgproc 模块:图像梯度函数cv2.Sobel, cv2.Scharr, cv2.Laplacian ——OpenCV官方教程翻译(全网最详细)

  • 一、目标
  • 二、基本理论
  • 2.1 Sobel算子
  • 2.1.1 Sobel运算
  • 2.2 Scharr算子
  • 2.2.1 Scharr运算
  • 2.3 拉普拉斯算子
  • 三、图像梯度运算
  • 3.1 cv2.Sobel()函数
  • 3.1.1 举例演示
  • 3.2 cv2.Scharr()函数
  • 3.3 cv2.Laplacian()函数
  • 3.4 比较三种算子的差异


一、目标

在本章中,我们将学习:

  • 寻找图像的梯度,边缘等
  • 我们将学习以下函数:cv2.Sobel()cv2.Scharr()cv2.Laplacian()

二、基本理论

OpenCV提供了三种类型的梯度滤波器或高通滤波器,Sobel, Scharr和Laplacian。我们将逐一见到它们。

为什么对图像进行求导是重要的呢? 假设我们需要检测图像中的边缘,如下图:

python 查看算子的梯度 opencv求梯度_边缘检测


你可以看到在边缘,相素值显著的改变了。表示这一改变的一个方法是使用导数 。梯度值的大变预示着图像中内容的显著变化。

从上例中我们可以推论检测边缘可以通过定位梯度值大于邻域的相素的方法找到(或者推广到大于一个阈值).

2.1 Sobel算子

  • Sobel 算子是一个离散微分算子 (discrete differentiation operator)。 它用来计算图像灰度函数的近似梯度。
  • Sobel算子是一种联合高斯平滑加微分运算,对噪声有较强的抵抗能力

可以指定求导的方向,垂直的或水平的(分别由参数yorder和xorder指定)。还可以通过参数ksize指定内核的大小。如果ksize = -1,则使用3x3的Scharr滤波器,其效果优于3x3的Sobel滤波器。

2.1.1 Sobel运算

假设被作用图像为 A:

  1. Sobel算子可以在在两个方向求导:
  • 水平变化: 将 IA与一个奇数大小的内核 G_{x} 进行卷积。比如,当内核大小为3时, G_{x} 的计算结果为:
  • 垂直变化: 将A与一个奇数大小的内核 G_{y} 进行卷积。比如,当内核大小为3时, G_{y} 的计算结果为:

python 查看算子的梯度 opencv求梯度_python 查看算子的梯度_02


2.在图像的每一点,结合以上两个结果求出近似 梯度:

python 查看算子的梯度 opencv求梯度_python_03


有时也用下面更简单公式代替:

python 查看算子的梯度 opencv求梯度_python_04

2.2 Scharr算子

当内核大小为 3 时, 以上Sobel内核可能产生比较明显的误差(毕竟,Sobel算子只是求取了导数的近似值)。为解决这一问题,OpenCV提供了 Scharr 函数,但该函数仅作用于大小为3的内核。

2.2.1 Scharr运算

该函数的运算与Sobel函数一样快,但结果却更加精确,其内核为:

python 查看算子的梯度 opencv求梯度_计算机视觉_05

2.3 拉普拉斯算子

它计算由关系式给出的图像的拉普拉斯算子

python 查看算子的梯度 opencv求梯度_opencv_06


每个导数都是用Sobel导数来求的。如果ksize = 1,则使用以下kernel进行滤波:

python 查看算子的梯度 opencv求梯度_opencv_07

三、图像梯度运算

3.1 cv2.Sobel()函数

dst = cv2.Sobel	(	InputArray 	src,
					int 	ddepth,
					int 	dx,
					int 	dy,
					int 	ksize = 3,
					double 	scale = 1,
					double 	delta = 0,
					int 	borderType = BORDER_DEFAULT 
)

参数

说明

src

输入图像

dst

输出图像的大小和通道数目与src相同

ddepth

输出图像深度,见 combinations;在8位输入图像的情况下,它将导致导数截断。

dx

导数x的阶数

dy

导数y的阶数

ksize

Sobel内核的大小; 它必须是1、3、5或7。

scale

计算得出的导数值的可选比例因子; 默认情况下,不应用任何缩放(有关详细信息,请参见 getDerivKernels)。

delta

可选增量值,在将结果存储到dst之前添加到结果中的增量值。

borderType

像素外推方法,见BorderTypes

Depth combinations参数:

Input depth (src.depth())

Output depth (ddepth)

CV_8U

-1/CV_16S/CV_32F/CV_64F

CV_16U/CV_16S

-1/CV_32F/CV_64F

CV_32F

-1/CV_32F/CV_64F

CV_64F

-1/CV_64F

BorderTypes

BorderTypes参数

说明

cv2.BORDER_CONSTANT

iiiiii-abcdefgh-iiiiiii with some specified i

cv2.BORDER_REPLICATE

aaaaaa-abcdefgh-hhhhhhh

cv2.BORDER_REFLECT

fedcba-abcdefgh-hgfedcb

cv2.BORDER_WRAP

cdefgh-abcdefgh-abcdefg

cv2.BORDER_REFLECT_101

gfedcb-abcdefgh-gfedcba

cv2.BORDER_TRANSPARENT

uvwxyz-abcdefgh-ijklmno

cv2.BORDER_REFLECT101

same as BORDER_REFLECT_101

cv2.BORDER_DEFAULT

same as BORDER_REFLECT_101

cv2.BORDER_ISOLATED

do not look outside of ROI

使用扩展的Sobel运算符计算一阶,二阶,三阶或混合图像导数。

除一种情况外,在所有情况下,均使用ksize×ksize可分离内核来计算导数。 当ksize = 1时,将使用3×1或1×3内核(即,不进行高斯平滑)。 ksize = 1只能用于一阶或二阶x或y导数。

还有一个特殊值ksize = CV_SCHARR(-1)对应于3×3 Scharr滤波器,它可能比3×3 Sobel给出更准确的结果。 Scharr矩阵为:

python 查看算子的梯度 opencv求梯度_opencv_08


求x阶导数,或者求y阶导数的转置。该函数通过将图像与适当的核卷积来计算图像的导数:

python 查看算子的梯度 opencv求梯度_边缘检测_09


Sobel算子结合了高斯平滑和微分,对噪声有一定的抵抗能力。

最常见的情况是,使用(xorder = 1, yorder = 0, ksize = 3)或(xorder = 0, yorder = 1, ksize = 3)调用函数来计算x或y图像的第一个导数。

第一种情况对应于内核:

python 查看算子的梯度 opencv求梯度_python 查看算子的梯度_10


第二种情况对应于内核:

python 查看算子的梯度 opencv求梯度_边缘检测_11

3.1.1 举例演示

原图展示:

python 查看算子的梯度 opencv求梯度_边缘检测_12


这个图案中间有一个白色的圆形,图案外面框由白边环绕。

1. 下面对该图片做梯度求解:

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

def cv_show(img,name):
    cv2.imshow(name,img)
    cv2.waitKey()
    cv2.destroyAllWindows()

img = cv2.imread('circle.png',cv2.IMREAD_GRAYSCALE)

#计算水平方向梯度
sobelx = cv2.Sobel(img,cv2.CV_64F,1,0,ksize=3)      
cv_show(sobelx,'sobelx')

运行结果:

python 查看算子的梯度 opencv求梯度_python 查看算子的梯度_13


我们发现,Sobel算子再计算水平方向导数时,仅计算了图案的左侧,右侧的却消失了。

这是因为白到黑的导数为正数,黑到白的导数为负数,负数会被自动置为0(黑色),所以只能看到左侧的边缘。

所以要记住使用梯度计算边缘后,一定要再取绝对值,否则会丢失特征数据。

2. 对梯度取绝对值:

sobelx = cv2.convertScaleAbs(sobelx)    #取绝对值
cv_show(sobelx,'sobelx')

运行结果:

python 查看算子的梯度 opencv求梯度_计算机视觉_14


这样水平方向的梯度信息就显示全了。

3. 下面计算竖直方向梯度:

#计算竖直方向梯度
sobely = cv2.Sobel(img,cv2.CV_64F,0,1,ksize=3)      
sobely = cv2.convertScaleAbs(sobely)  #取绝对值
cv_show(sobely,'sobely')

运行结果:

python 查看算子的梯度 opencv求梯度_边缘检测_15


3. 再将x方向和y方向的梯度加和:

#x和y方向导数求和
sobelxy = cv2.addWeighted(sobelx,0.5,sobely,0.5,0)  
cv_show(sobelxy,'sobelxy')

运行结果:

python 查看算子的梯度 opencv求梯度_边缘检测_16


可能细心的你会产生疑问,为什么不直接在一个函数上同时计算x方向和y方向的梯度呢?非要分别计算再加和在一起干嘛呢?

4. 下面我们通过实践来看看两者的差别就知道了:

#同时计算x、y方向
img = cv2.imread('lena.jpg',cv2.IMREAD_GRAYSCALE)
sobelxy1 = cv2.Sobel(img,cv2.CV_64F,1,1,ksize=3)        
sobelxy1 = cv2.convertScaleAbs(sobelxy1) 
#分别计算x、y方向
sobelx = cv2.Sobel(img,cv2.CV_64F,1,0,ksize=3)
sobelx = cv2.convertScaleAbs(sobelx)
sobely = cv2.Sobel(img,cv2.CV_64F,0,1,ksize=3)
sobely = cv2.convertScaleAbs(sobely)
sobelxy2 = cv2.addWeighted(sobelx,0.5,sobely,0.5,0)

plt.rcParams['font.sans-serif'] = ['SimHei']        #文字字体为黑体
plt.figure(figsize=(10,5))
plt.subplot(121)
plt.imshow(sobelxy1,cmap='gray')
plt.title('同时计算x、y方向')
plt.xticks([]),plt.yticks([])

plt.subplot(122)
plt.imshow(sobelxy2,cmap='gray')
plt.title('分别计算x、y方向、再加和')
plt.xticks([]),plt.yticks([])

运行结果:

python 查看算子的梯度 opencv求梯度_python 查看算子的梯度_17


从图中我们可以很明显的看出,同时计算x、y方向的边缘效果并不好,而分别计算x、y方向再加和的效果就会好很多。

3.2 cv2.Scharr()函数

dst = cv2.Scharr	(	InputArray 	src,
						int 	ddepth,
						int 	dx,
						int 	dy,
						double 	scale = 1,
						double 	delta = 0,
						int 	borderType = BORDER_DEFAULT 
)

参数

说明

src

输入图像

dst

输出图像的大小和通道数目与src相同

ddepth

输出图像深度,见 combinations;在8位输入图像的情况下,它将导致导数截断。

dx

导数x的阶数

dy

导数y的阶数

scale

计算得出的导数值的可选比例因子; 默认情况下,不应用任何缩放(有关详细信息,请参见 getDerivKernels)。

delta

可选增量值,在将结果存储到dst之前添加到结果中的增量值。

borderType

像素外推方法,见BorderTypes

使用Scharr算子计算第一个x或y图像的导数。

该函数使用Scharr算子计算第一个x或y空间图像的导数。

python 查看算子的梯度 opencv求梯度_计算机视觉_18


等价于

python 查看算子的梯度 opencv求梯度_python 查看算子的梯度_19


两种函数都可以调用Sharr算子来计算梯度。

3.3 cv2.Laplacian()函数

dst = cv2.Laplacian	(	InputArray 	src,
						int 	ddepth,
						int 	ksize = 1,
						double 	scale = 1,
						double 	delta = 0,
						int 	borderType = BORDER_DEFAULT 
)

参数

说明

src

原图像

dst

目标图像的大小和通道数目与src相同

ddepth

目标图像所需的深度

ksize

Laplacian核大小。用于计算二阶导数滤波器。详情请参阅getDerivKernels。大小必须为正奇数

scale

可选的计算拉普拉斯值的比例因子。默认情况下,不应用伸缩。详情请参阅getDerivKernels

delta

可选增量值,在将结果存储到dst之前添加到结果中的增量值

borderType

像素外推方法,见BorderTypes

计算图像的拉普拉斯函数。

该函数通过将使用Sobel算子计算的第二个x和y导数相加来计算源图像的拉普拉斯函数:

python 查看算子的梯度 opencv求梯度_python_20


这是当ksize > 1。当ksize == 1时,用3×3大小的矩阵对图像进行滤波,计算拉普拉斯函数:

python 查看算子的梯度 opencv求梯度_边缘检测_21

3.4 比较三种算子的差异

img = cv2.imread('lena.jpg',cv2.IMREAD_GRAYSCALE)
#Sobel算子
sobelx = cv2.Sobel(img,cv2.CV_64F,1,0,ksize=3)
sobely = cv2.Sobel(img,cv2.CV_64F,0,1,ksize=3)
sobelx = cv2.convertScaleAbs(sobelx)   
sobely = cv2.convertScaleAbs(sobely)  
sobelxy =  cv2.addWeighted(sobelx,0.5,sobely,0.5,0)  
#Scharr算子
scharrx = cv2.Scharr(img,cv2.CV_64F,1,0)
scharry = cv2.Scharr(img,cv2.CV_64F,0,1)
scharrx = cv2.convertScaleAbs(scharrx)   
scharry = cv2.convertScaleAbs(scharry)  
scharrxy =  cv2.addWeighted(scharrx,0.5,scharry,0.5,0) 
#laplacian算子
laplacian = cv2.Laplacian(img,cv2.CV_64F)
laplacian = cv2.convertScaleAbs(laplacian)   

plt.figure(figsize=(10,5))
plt.subplot(131)
plt.imshow(sobelxy,cmap='gray')
plt.title('Sobel')
plt.xticks([]),plt.yticks([])

plt.subplot(132)
plt.imshow(scharrxy,cmap='gray')
plt.title('Scharr')
plt.xticks([]),plt.yticks([])

plt.subplot(133)
plt.imshow(laplacian,cmap='gray')
plt.title('laplacian')
plt.xticks([]),plt.yticks([])

plt.show()

运行结果:

python 查看算子的梯度 opencv求梯度_计算机视觉_22


主要参考于OpenCV官方网站:http://www.opencv.org.cn/