【OpenCV-图像处理】如何计算图像梯度,以及如何使用梯度来检测边缘——OpenCV官方教程翻译(全网最详细)

一、目标

在本章中,我们将学习:

  • 寻找图像的梯度,边缘等
  • 我们将学习以下函数: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:

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

在这里插入图片描述
2.在图像的每一点,结合以上两个结果求出近似 梯度:
在这里插入图片描述
有时也用下面更简单公式代替:
在这里插入图片描述

2.2 Scharr算子

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

2.2.1 Scharr运算

该函数的运算与Sobel函数一样快,但结果却更加精确,其内核为:
在这里插入图片描述

2.3 拉普拉斯算子

它计算由关系式给出的图像的拉普拉斯算子
在这里插入图片描述
每个导数都是用Sobel导数来求的。如果ksize = 1,则使用以下kernel进行滤波:
在这里插入图片描述

三、图像梯度运算

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_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_CONSTANTiiiiii-abcdefgh-iiiiiii with some specified i
cv2.BORDER_REPLICATEaaaaaa-abcdefgh-hhhhhhh
cv2.BORDER_REFLECTfedcba-abcdefgh-hgfedcb
cv2.BORDER_WRAPcdefgh-abcdefgh-abcdefg
cv2.BORDER_REFLECT_101gfedcb-abcdefgh-gfedcba
cv2.BORDER_TRANSPARENTuvwxyz-abcdefgh-ijklmno
cv2.BORDER_REFLECT101same as BORDER_REFLECT_101
cv2.BORDER_DEFAULTsame as BORDER_REFLECT_101
cv2.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图像的第一个导数。
第一种情况对应于内核:
在这里插入图片描述
第二种情况对应于内核:
在这里插入图片描述

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可选的计算拉普拉斯值的比例因子。默认情况下,不应用伸缩。详情请参阅getDerivKernels
delta可选增量值,在将结果存储到dst之前添加到结果中的增量值
borderType像素外推方法,见BorderTypes

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

该函数通过将使用Sobel算子计算的第二个x和y导数相加来计算源图像的拉普拉斯函数:
在这里插入图片描述
这是当ksize > 1。当ksize == 1时,用3×3大小的矩阵对图像进行滤波,计算拉普拉斯函数:
在这里插入图片描述

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-图像处理】图像阈值处理

<后续还会继续翻译和整理【OpenCV-图像处理】相关内容,如果需要,可持续关注我哦~>

<翻译和整理不易,留个赞或评论支持一下我吧^^>

如有疑问,欢迎批评指正^^

  • 16
    点赞
  • 42
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

路遥_.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值