原理
关于这一个知识点,有一篇非常好的博客在此推荐:
图像梯度——Sobel ,Scharr和Laplacian详解
梯度简单来说就是求导。
OpenCV 提供了三种不同的梯度滤波器,或者说高通滤波器:Sobel,Scharr 和 Laplacian。
Sobel,Scharr 其实就是求一阶或二阶导数。Scharr 是对 Sobel(使用小的卷积核求解求解梯度角度时)的优化。Laplacian 是求二阶导数。
1 Sobel 算子和 Scharr 算子
Sobel 算子是高斯平滑与微分操作的结合体,所以它的抗噪声能力很好。你可以设定求导的方向(xorder 或 yorder)。还可以设定使用的卷积核的大小(ksize)。如果 ksize=-1,会使用 3x3 的 Scharr 滤波器,它的的效果要比 3x3 的 Sobel 滤波器好(而且速度相同,所以在使用 3x3 滤波器时应该尽量使用 Scharr 滤波器)。
3x3 的 Sobel 滤波器卷积核如下:
在水平方向上,梯度 = 右边 × 权重 - 左边 × 权重
在竖直方向上,梯度 = 下边 × 权重 - 上边 × 权重
dst = cv2.Sobel(src, ddepth, dx, dy, ksize)
ddepth:图像的深度
dx和dy分别表示水平和竖直方向
ksize是Sobel算子的大小
例如:
import cv2
import numpy as np
def cv_show(img,name):
cv2.imshow(name,img)
cv2.waitKey()
cv2.destroyAllWindows()
img = cv2.imread('C:/Users/www12/Desktop/pie.png',cv2.IMREAD_GRAYSCALE)
cv_show(img, 'img')
sobelx = cv2.Sobel(img,cv2.CV_64F,1,0,ksize=3)
cv_show(sobelx,'sobelx')
sobely = cv2.Sobel(img,cv2.CV_64F,0,1,ksize=3)
cv_show(sobely,'sobely')
原图:
使用了Sobel算子处理水平方向之后的图:
使用了Sobel算子处理竖直方向之后的图:
要是想同时处理水平和竖直方向,有两种方法:
第一种方法:分别计算x和y,再求和
sobelxy = cv2.addWeighted(sobelx,0.5,sobely,0.5,0)
cv_show(sobelxy,'sobelxy')
第二种方法:直接计算
第二种方法效果明显不太好,所以尽量不要直接计算!!
一个重要的事!
在查看上面这个例子的注释时不知道你有没有注意到:当我们可以通过参数 -1 来设定输出图像的深度(数据类型)与原图像保持一致,但是我们在代码中使用的却是 cv2.CV_64F。这是为什么呢?想象一下一个从黑到白的边界的导数是整数,而一个从白到黑的边界点导数却是负数。如果原图像的深度是np.int8 时,所有的负值都会被截断变成 0,换句话说就是把把边界丢失掉。所以如果这两种边界你都想检测到,最好的的办法就是将输出的数据类型设置的更高,比如 cv2.CV_16S,cv2.CV_64F 等。取绝对值然后再把它转回到 cv2.CV_8U。
3x3 的 Scharr 滤波器卷积核如下:
Scharr 滤波器形式和Sobel滤波器形式差不多,唯一的不同就是Scharr滤波器更精确,更敏感!
2 Laplacian 算子
拉普拉斯算子可以使用二阶导数的形式定义,可假设其离散实现类似于二阶 Sobel 导数,事实上,OpenCV 在计算拉普拉斯算子时直接调用 Sobel 算子。计算公式如下:
拉普拉斯滤波器使用的卷积核:
拉普拉斯滤波器就不仅仅是简单的对水平或者竖直方向的处理了,其对噪声敏感,会产生双边效果。不能检测出边的方向。通常不直接用于边的检测,只起辅助的角色,检测一个像素是在边的亮的一边还是暗的一边利用零跨越,确定边的位置。
下面的代码分别使用以上三种滤波器对同一幅图进行操作。
import cv2
import numpy as np
#不同算子的差异
img = cv2.imread('C:/Users/www12/Desktop/lena.jpg',cv2.IMREAD_GRAYSCALE)
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)
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 = cv2.Laplacian(img,cv2.CV_64F)
laplacian = cv2.convertScaleAbs(laplacian)
res = np.hstack((img, sobelxy,scharrxy,laplacian))
cv_show(res,'res')
可以看出,Scharr对边缘检测的效果比Sobel更好,因为其对梯度的变化更为敏感,而Laplacian不太适合边缘检测,其用于去除噪音更合适。