图像梯度计算的是图像变化的速度。对于图像的边缘部分,其灰度值变化较大,梯度值也较大;相反,对于图像中比较平滑的部分,其灰度值变化较小,相应的梯度值也较小。一般情况下,图像梯度计算的是图像的边缘信息。 严格来讲,图像梯度计算需要求导数,但是图像梯度一般通过计算像素值的差来得到梯度的近似值(近似导数值)。将上述运算关系进一步优化,可以得到更复杂的边缘信息。
Sobel 算子是一种离散的微分算子,该算子结合了高斯平滑和微分求导运算。该算子利用局部差分寻找边缘,计算所得的是一个梯度的近似值。
Sobel算子如图所示:
假定有原始图像src,下面对Sobel算子的计算进行讨论。
计算水平方向偏导数的近似值:将Sobel算子与原始图像src进行卷积计算,可以计算水平方向上的像素值变化情况。 例如,当Sobel算子的大小为3×3时,水平方向偏导数Gx的计算方式为:
计算垂直方向偏导数的近似值同理,将Sobel算子换为垂直方向的算子。
在OpenCV内,使用函数cv2.Sobel()实现Sobel算子运算,其语法形式为:
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代表边界样式。
在函数cv2.Sobel()的语法中规定,可以将函数cv2.Sobel()内ddepth参数的值设置为-1,让处理结果与原始图像保持一致。但是,如果直接将参数ddepth的值设置为-1,在计算时得到的结果可能是错误的。 在实际操作中,计算梯度值可能会出现负数。如果处理的图像是8位图类型,则在 ddepth的参数值为-1时,意味着指定运算结果也是8位图类型,那么所有负数会自动截断为0,发生信息丢失。为了避免信息丢失,在计算时要先使用更高的数据类型 cv2.CV_64F,再通过取绝对值将其映射为cv2.CV_8U(8位图)类型。所以,通常要将函数cv2.Sobel()内参数ddepth的值设置为“cv2.CV_64F”。。
在OpenCV中,使用函数cv2.convertScaleAbs()对参数取绝对值,该函数的语法格式为: dst=cv2.convertScaleAbs(src[,alpha[,beta]])
上式中:
● dst代表处理结果。
● src代表原始图像。
● alpha代表调节系数,该值是可选值,默认为1。
● beta代表调节亮度值,该值是默认值,默认为0。
在函数cv2.Sobel()中,参数dx表示x轴方向的求导阶数,参数dy表示y轴方向的求导阶数。参数dx和dy通常的值为0或者1,最大值为2。如果是0,表示在该方向上没有求导。 当然,参数dx和参数dy的值不能同时为0。 参数dx和参数dy可以有多种形式的组合,主要包含:
● 计算x方向边缘(梯度):dx=1,dy=0。
● 计算y方向边缘(梯度):dx=0,dy=1。
● 参数dx与参数dy的值均为1:dx=1,dy=1。
● 计算x方向和y方向的边缘叠加:通过组合方式实现。
import cv2 as cv
def cv_show(name, img):
cv.imshow(name, img)
cv.waitKey(0)
cv.destroyAllWindows()
img = cv.imread('D:\\qipan.jpg')
if img is None:
print("Failed to read the image")
# 沿x方向的边缘检测
img1 = cv.Sobel(img, cv.CV_64F, 1, 0)
sobelx = cv.convertScaleAbs(img1)
# 展示未进行取绝对值的图片
cv_show('img1', img1)
cv_show('sobelx', sobelx)
# 沿y方向的边缘检测
img1 = cv.Sobel(img, cv.CV_64F, 0, 1)
sobely = cv.convertScaleAbs(img1)
cv_show('sobely', sobely)
# 沿x,y方向同时检测,效果不是特别好
img1 = cv.Sobel(img, cv.CV_64F, 1, 1)
sobelxy = cv.convertScaleAbs(img1)
cv_show('sobelxy', sobelxy)
# 一般在x,y方向分别检测,在进行与运算
# sobelxy1 = cv.bitwise_and(sobelx, sobely)
# cv_show('sobelxy1', sobelxy1)
# 这种方法也行
sobelxy1 = cv.addWeighted(sobelx, 0.5, sobely, 0.5, 0)
cv_show('sobelxy1', sobelxy1)
原图如下:
沿x方向未进行取绝对值操作:
沿x方向的边缘检测:
可以观察到,未进行取绝对值的图片有许多干扰,不利于图像分析。
沿y方向的边缘检测:
沿x,y方向同时检测:
x,y方向叠加效果:
在本例中,分别使用两种不同的方式获取边缘信息:
● 方式1:将参数dx和dy的值设为“dx=1,dy=1”,获取图像在两个方向的梯度。
● 方式2:分别使用“dx=1,dy=0”和“dx=0,dy=1”计算图像在水平方向和垂直方向的边缘信息,然后将二者相加,构成两个方向的边缘信息。
可以看出,方式2的边缘信息提取的更好,一般我们是分别计算 x 方向的边缘、y 方向的边缘,接下来使用函 数cv2.addWeighted()对两个方向的边缘进行叠加来提取图像边缘信息。