图像的梯度计算的是图像变化的速度,对于边缘部分呢灰度值变换大,梯度值也大,相反则灰度值变化小,梯度值小
图像梯度值严格说应该需要求导数,但是图像梯度一般通过计算像素值的差,来得到梯度的近似值
以下介绍三种算子的使用Sobel算子、Scharr算子和Laplacian算子
Sobel算子是一种离散的微分算子,该算子结合了高斯平滑处理和微分求导运算。 该算子利用局部差寻找边缘
Sobel算子如下所示
-1 | 0 | 1 |
-2 | 0 | 2 |
-1 | 0 | 1 |
图一
-1 | -2 | -1 |
0 | 0 | 0 |
1 | 2 | 1 |
图二
将Sobel算子图一和原始图像卷积可以得到水平方向的像素值变化,与图二卷积的到垂直方向的像素值变化
P1 | P2 | P3 |
P4 | P5 | P6 |
P7 | P8 | P9 |
如果要计算P5的水平方向的偏导数,则需要Sobel算子及P5邻域点
公式为:P5x = (P3 - P1 ) + 2 * ( P6 - P4 ) + ( P9 - P7 )
用P5右侧的像素点减左侧的像素点,因为P4和P6离P5较近,所以权值为2,其他为1
垂直方向类似,垂直是下减上
函数形式
dst = cv2.Sobel( src , ddepth, dx , dy [, ksize [, scale [, delta [, borderType]]]])
dst目标图像
src原始图像
ddepth 输出图像的深度,
dx x方向上的求导阶数
dy y方向上的求导阶数
ksize 代表Sobel核的大小,该值为-1时,会使用Scharr算子进行计算
scale计算导数值时采用的缩放因子,默认为1,没有缩放
delta加在目标图像dst上的值,默认为0
borderType 边界样式 (上篇博客提到过不再重复)
可以将ddepth设置为-1,在计算时可能得到的结果时错误的,
如果处理的图像是8位图类型,且ddepth为-1 意味着指定运算结果也是8位图类型,所有负数都自动处理为0
为了避免信息丢失在计算是要先使用更高的数据类型嗯 cv2.CV_64F,在通过取绝对值将其映射位8位图类型
所以通常要将ddepth设置位cv2.CV_64F
如下代码所示
1 import cv2 2 o = cv2.imread("example.bmp" , cv2.IMREAD_GRAYSCALE) 3 Sobelx = cv2.Sobel(o , -1 , 1 , 0) 4 cv2.imshow("original" , o) 5 cv2.imshow("x" , Sobelx) 6 cv2.waitKey() 7 cv2.destroyAllWindows()
原图
效果图
如上图所示在8位灰度图中因为黑色的像素值位0,而白色为255,当使用Sobel算子后,对其水平方向计算近似偏导数
右侧减左侧,右侧边缘得到的是正数所以可以正常显示,而左侧的边缘得到的是负数被处理为0后便显示黑色不能得到准确的结果
将ddepth设置为cv2.CV_64F后 还要取绝对值才能获得所需的图像,则需要绝对值函数 cv2.convertScaleAbs()
dx = 1 , dy = 0
1 import cv2 2 o = cv2.imread("example.bmp" , cv2.IMREAD_GRAYSCALE) 3 Sobelx = cv2.Sobel(o , cv2.CV_64F , 1 ,0) 4 Sobelx = cv2.convertScaleAbs(Sobelx) 5 cv2.imshow("original" , o) 6 cv2.imshow("x" , Sobelx) 7 cv2.waitKey() 8 cv2.destroyAllWindows()
如图 可以获得完整边缘信息
垂直方向的边缘信息 dx = 0 . dy = 1
1 import cv2 2 o = cv2.imread("example.bmp" , cv2.IMREAD_UNCHANGED) 3 Sobely = cv2.Sobel(o , cv2.CV_64F , 0 , 1) 4 Sobely = cv2.convertScaleAbs(Sobely) 5 cv2.imshow("original" , o) 6 cv2.imshow("y" , Sobely) 7 cv2.waitKey() 8 cv2.destroyAllWindows()
如图
当dx =1 , dy = 1
1 import cv2 2 o = cv2.imread("example.bmp" , cv2.IMREAD_GRAYSCALE) 3 Sobelxy = cv2.Sobel(o , cv2.CV_64F , 1 , 1) 4 sobelxy = cv2.convertScaleAbs(Sobelxy) 5 cv2.imshow("original" , o) 6 cv2.imshow("xy" , Sobelxy) 7 cv2.waitKey() 8 cv2.destroyAllWindows()
没有达到我们想要的结果,而是只留下几个点
若想要x,y方向都显示边缘需要两个方向分别处理,然后向叠加
import cv2 o = cv2.imread("example.bmp" , cv2.IMREAD_GRAYSCALE ) Sobelx = cv2.Sobel(o , cv2.CV_64F , 1 , 0) Sobely = cv2.Sobel(o , cv2.CV_64F , 0 , 1) Sobelx = cv2.convertScaleAbs(Sobelx) Sobely = cv2.convertScaleAbs(Sobely) Sobelxy = cv2.addWeighted(Sobelx , 0.5 , Sobely , 0.5 , 0) cv2.imshow("original" , o) cv2.imshow("xy" , Sobelxy) cv2.waitKey() cv2.destroyAllWindows()
Scharr算子
在使用3x3的Sobel算子是精度可能不高,Scharr速度与Sobel算子一样 ,但精度更高
Scharr算子通常为
-3 | 0 | 3 |
-10 | 0 | 10 |
-3 | 0 | 3 |
-3 | -10 | -3 |
0 | 0 | 0 |
3 | 10 | 3 |
函数形式为
dst = cv2.Scharr( src , ddepth , dx , dy [ , scale [ ,delta [, borderType]]])
与Sobel相似少了Ksize参数, 即当Sobel中ksize = -1 时会使用Scharr算子计算
函数cv2.Scharr()与cv2.Sobel()相似
但有一些约束条件
dx >= 0 && dy >=0 && dx + dy == 1
x方向和y方向的边缘叠加
1 import cv2 2 o = cv2.imread("example.bmp" , cv2.IMREAD_GRAYSCALE) 3 Scharrx = cv2.Scharr(o , cv2.CV_64F , 1 , 0) 4 Scharry = cv2.Scharr(o , cv2.CV_64F , 0 , 1) 5 Scharrx = cv2.convertScaleAbs(Scharrx) 6 Scharry = cv2.convertScaleAbs(Scharry) 7 Scharrxy = cv2.addWeighted(Scharrx , 0.5 , Scharry , 0.5 , 0) 8 cv2.imshow("original" , o) 9 cv2.imshow("xy" , Scharrxy) 10 cv2.waitKey() 11 cv2.destroyAllWindows()
Sobel和Scharr的比较
原图
Sobel
Scharr
Laplacian算子
该算子是一个二阶导数算子,具有旋转不变性,可以满足不同方向的图像边缘锐化(边缘检测)的要求,
通常情况下算子的系数和要为0
Laplacian算子
0 | 1 | 0 |
1 | -4 | 1 |
0 | 1 | 0 |
p1 | p2 | p3 |
p4 | p5 | p6 |
p7 | p8 | p9 |
P5点的近似导数值
P5lap =( P2 + P4 + P6 + P8 ) - 4* P5
该结果可能是正数也可能是负数,需要对计算结果取绝对值
函数形式
dst = cv2.Laplacian(src , ddepth , [,ksize [ ,scale [, delta [.borderType]]]])
dst 目标图像
src原始图像
ddepth目标时图像的深度
ksize二阶导数的核尺寸大小
scale 计算Laplacian值的缩放比例因子
delta 夹道目标图像上的可选值
borderType 边界样式
1 import cv2 2 o = cv2.imread("example.bmp" , cv2.IMREAD_GRAYSCALE) 3 Laplacian = cv2.Laplacian(o , cv2.CV_64F) 4 Laplacian = cv2.convertScaleAbs(Laplacian) 5 cv2.imshow("original" , o) 6 cv2.imshow("Laplacina" , Laplacian) 7 cv2.waitKey() 8 cv2.destroyAllWindows()