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。我们将逐一见到它们。 为什么对图像进行求导是重要的呢? 假设我们需要检测图像中的边缘,如下图: 你可以看到在边缘,相素值显著的改变了。表示这一改变的一个方法是使用导数 。梯度值的大变预示着图像中内容的显著变化。 从上例中我们可以推论检测边缘可以通过定位梯度值大于邻域的相素的方法找到(或者推广到大于一个阈值).
2.1 Sobel算子
Sobel 算子是一个离散微分算子 (discrete differentiation operator)。 它用来计算图像灰度函数的近似梯度。Sobel算子是一种联合高斯平滑加微分运算,对噪声有较强的抵抗能力。
可以指定求导的方向,垂直的或水平的(分别由参数yorder和xorder指定)。还可以通过参数ksize指定内核的大小。如果ksize = -1,则使用3x3的Scharr滤波器,其效果优于3x3的Sobel滤波器。
2.1.1 Sobel运算
假设被作用图像为 A:
Sobel算子可以在在两个方向求导:
水平变化: 将 IA与一个奇数大小的内核 G_{x} 进行卷积。比如,当内核大小为3时, G_{x} 的计算结果为: 垂直变化: 将A与一个奇数大小的内核 G_{y} 进行卷积。比如,当内核大小为3时, G_{y} 的计算结果为:
2.在图像的每一点,结合以上两个结果求出近似 梯度: 有时也用下面更简单公式代替: ![在这里插入图片描述](https://img-blog.csdnimg.cn/20210317234244503.png#pic_center)
2.2 Scharr算子
当内核大小为 3 时, 以上Sobel内核可能产生比较明显的误差(毕竟,Sobel算子只是求取了导数的近似值)。为解决这一问题,OpenCV提供了 Scharr 函数,但该函数仅作用于大小为3的内核。
2.2.1 Scharr运算
该函数的运算与Sobel函数一样快,但结果却更加精确,其内核为: ![在这里插入图片描述](https://img-blog.csdnimg.cn/20210317234632388.png)
2.3 拉普拉斯算子
它计算由关系式给出的图像的拉普拉斯算子 每个导数都是用Sobel导数来求的。如果ksize = 1,则使用以下kernel进行滤波: ![在这里插入图片描述](https://img-blog.csdnimg.cn/20210317232857410.png#pic_center)
三、图像梯度运算
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的阶数ksizeSobel内核的大小; 它必须是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_64FCV_16U/CV_16S-1/CV_32F/CV_64FCV_32F-1/CV_32F/CV_64FCV_64F-1/CV_64F
BorderTypes
BorderTypes参数说明cv2.BORDER_CONSTANTiiiiii-abcdefgh-iiiiiii with some specified icv2.BORDER_REPLICATEaaaaaa-abcdefgh-hhhhhhhcv2.BORDER_REFLECTfedcba-abcdefgh-hgfedcbcv2.BORDER_WRAPcdefgh-abcdefgh-abcdefgcv2.BORDER_REFLECT_101gfedcb-abcdefgh-gfedcbacv2.BORDER_TRANSPARENTuvwxyz-abcdefgh-ijklmnocv2.BORDER_REFLECT101same as BORDER_REFLECT_101cv2.BORDER_DEFAULTsame as BORDER_REFLECT_101cv2.BORDER_ISOLATEDdo 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矩阵为: 求x阶导数,或者求y阶导数的转置。
该函数通过将图像与适当的核卷积来计算图像的导数: Sobel算子结合了高斯平滑和微分,对噪声有一定的抵抗能力。 最常见的情况是,使用(xorder = 1, yorder = 0, ksize = 3)或(xorder = 0, yorder = 1, ksize = 3)调用函数来计算x或y图像的第一个导数。 第一种情况对应于内核: 第二种情况对应于内核: ![在这里插入图片描述](https://img-blog.csdnimg.cn/20210318000311441.png#pic_center)
3.1.1 举例演示
原图展示: 这个图案中间有一个白色的圆形,图案外面框由白边环绕。 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')
运行结果: 我们发现,Sobel算子再计算水平方向导数时,仅计算了图案的左侧,右侧的却消失了。 这是因为白到黑的导数为正数,黑到白的导数为负数,负数会被自动置为0(黑色),所以只能看到左侧的边缘。 所以要记住使用梯度计算边缘后,一定要再取绝对值,否则会丢失特征数据。
2. 对梯度取绝对值:
sobelx = cv2.convertScaleAbs(sobelx) #取绝对值
cv_show(sobelx,'sobelx')
运行结果: 这样水平方向的梯度信息就显示全了。
3. 下面计算竖直方向梯度:
#计算竖直方向梯度
sobely = cv2.Sobel(img,cv2.CV_64F,0,1,ksize=3)
sobely = cv2.convertScaleAbs(sobely) #取绝对值
cv_show(sobely,'sobely')
运行结果: 3. 再将x方向和y方向的梯度加和:
#x和y方向导数求和
sobelxy = cv2.addWeighted(sobelx,0.5,sobely,0.5,0)
cv_show(sobelxy,'sobelxy')
运行结果: 可能细心的你会产生疑问,为什么不直接在一个函数上同时计算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([])
运行结果: 从图中我们可以很明显的看出,同时计算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空间图像的导数。 等价于 两种函数都可以调用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目标图像所需的深度ksizeLaplacian核大小。用于计算二阶导数滤波器。详情请参阅getDerivKernels。大小必须为正奇数scale可选的计算拉普拉斯值的比例因子。默认情况下,不应用伸缩。详情请参阅getDerivKernelsdelta可选增量值,在将结果存储到dst之前添加到结果中的增量值borderType像素外推方法,见BorderTypes
计算图像的拉普拉斯函数。
该函数通过将使用Sobel算子计算的第二个x和y导数相加来计算源图像的拉普拉斯函数: 这是当ksize > 1。当ksize == 1时,用3×3大小的矩阵对图像进行滤波,计算拉普拉斯函数: ![在这里插入图片描述](https://img-blog.csdnimg.cn/20210318002319892.png#pic_center)
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()
运行结果: 主要参考于OpenCV官方网站:http://www.opencv.org.cn/
目前博主已更新OpenCV平滑处理函数、形态学操作函数的详细介绍,链接如下: 【OpenCV-图像处理】图像平滑处理函数
【OpenCV-图像处理】形态学变换函数
【OpenCV-图像处理】图像阈值处理
如有疑问,欢迎批评指正^^
|