图像梯度计算是图像变化的幅度。对于图像的边缘部分,其灰度值变化较大,梯度值变化也较大;相反,对于图像中比较平滑的部分,其灰度值变化较小,相应的梯度值变化也较小。
一般情况下,图像梯度计算的是图像的边缘信息。
OpenCV中提供Sobel、Scharr和Laplcaian算子实现图像梯度计算。
1、Sobel算子
Sobel算子是一种离散的微分算子,该算子结合了高斯平滑和微分求导运算。该算子利用局部差分来寻找边缘,计算所得的一个梯度的近似值。
Sobel与梯度密不可分,它目的是图像边缘检测。Soble算子是图像一种图像边缘检测方法,本质是梯度运算。
1.1 什么情况下会产生梯度
在卷积核范围内,图像数值差越大,梯度则越大;梯度越大图像的边缘则越明显,如图1.1所示。
图1.1 图像梯度
在图1.1中,情况1和情况2中不会产生梯度,因为情况1和情况2中的数值都一致,计算后不会发生变化,所有没有梯度产生;只有情况3时才会产生梯度,对于情况3中,卷积核内有黑、有白,数值不一样,这样就会产生较大的梯度,梯度越大,图像边缘效果就越明显。
1.2 计算水平方向偏导数的近似值
通过将Sobel算子与原始图像进行卷积计算,来计算水平方向上的像素值变化情况。
经过卷积核对原图像的每个像素进行计算后,取其核心中的值为水平方向偏导数值(Gx),如图1.2。
图1.2 卷积核
其中,边缘周围一圈无法计算,一般情况取同一边缘的平均值,或者是边缘向外扩1个像素,新扩的像素值为0。卷积核一般为3x3,也可以使用5x5,越靠近核心(中心)的权重值越高。
例如,当Sobel算子的大小为3x3时,水平方向偏导数(Gx)的计算方式为。
图1.3 水平方向偏导数近似值公式
Gx即为P5x,当目标(P5x点)左右两列差别特别大的时候,目标点的值会很大,说明该点为边界。
水平方向偏导数据近似值计算由右减左(例:P3-P1),然后加上各行的值。其中2(P6-P4)中的2是权重,越靠近中心点的权重越高。
1.3 计算垂直方向偏导数的近似值
通过将Sobel算子与原始图像进行卷积计算,来计算垂直方向上的像素值变化情况。
经过卷积核对原图像的每个像素进行计算后,取其核心中的值为垂直方向偏导数值(Gy)。
例如,当Sobel算子的大小为3x3时,垂直方向偏导数(Gy)的计算方式为。
图1.4 垂直方向偏导数近似值公式
垂直方向偏导数据近似值计算由下减上(例:P7-P1),然后加上各列的值。其中2(P8-P2)中的2是权重,越靠近中心点的权重越高。
1.4 总梯度
Gx和Gy公式计算后会出现一个问题,目标像素点求得的值有小于0,也有大于255的情况,而像素取值范围在0-255之间。
为了解决像素超出范围的问题,Sobel算子采取以下方式来解决影响。
1)对于小于0的近似值,取该值的绝对值。
2)对于大于255的近似值,取值255,因为255已经是像素的极值。
总梯度计算公式如图1.5所示。
图1.5 总梯度计算公式
1.5 Soble算子案例
1)Soble语法格式
OpenCV中提供Soble算子的直接应用,使用函数vc2.Sobe()实现Sobel算子运算,语法格式如下。
dst = cv2.Sobel(src, ddepth, dx, dy, ksize)
dst:目标结果图像
src:原始图像
ddepth:用于设置图像的深度,-1表示8位(cv2.CV_8U),该输入图像后会导致像素近似值截断(即小于0为0,大于255为255),设置64位则不会导致截断(ddepth=cv2.CV_64F)。
dx:x方向上的求导阶数。
dy:y方向上的求导阶数。
ksize:卷积核。该值为-1时,会使用Scharr算子进行运算。
2)对像素取绝对值
在函数cv2.Sobel()语法中,设置ddepth参数值为-1,这样即可以让处理结果图像与原始图像深度保持一致,但是,这样计算的结果可能是错误的。
在实际的操作中,计算梯度是可能出现负数的,如果设置ddepth为-1时,处理图像是8位类型,意味着指定运算结果也是8位图类型,那么所有的负数会自动截断为0,发生信息丢失。为了避免信息丢失,在计算时要先使用更高的数据类型cv2.CV_64F,再通过取绝对值将其映射为cv2.CV_8U类型。
在OpenCV中,通过使用函数cv2.convertScaleAbs()可以对参数取绝对值,该函数语法格式如下。
dst = cv2.convertScaleAbs(src)
dst:目标结果图像
src:代表原始图像。
3)直接使用Sobel计算(不推荐)
直接使用Sobel算子进行计算,通过设置cv2.Soble()算子中dx=1、dy=1计算x方向和y方向的导阶数,该方式计算结果要差些。
4)分步方式的Sobel计算(推荐)
分步方式Sobel算子的计算,即是单独对x方向的求导阶数,再对y方向求导阶数,然后在求x方向和y方向导阶数的绝对值,最后通过cv2.addWeighted()函数按权重将两个图像相加,语法格式如下。
cv2.addWeighted(src1, alpha, src2, beta, gamma)
src1:第1个图像矩阵。
alpha:第1个图像的权重。
src2:第2个图像矩阵。
beta:第2个图像的权重。
gamma:两个图像相加后的亮度(0-255)。
综合案例如下。
import cv2
img = cv2.imread("E:/tmp/img/cat.png", cv2.IMREAD_GRAYSCALE)
cv2.imshow("test", img) # 原图结果
# 1、直接计算(不建议)
# ddepth:用于设置图像的深度,-1表示8位,该输入图像后会导致像素近似值截断(即小于0为0,大于255为255),设置64位则不会导致截断(ddepth=cv2.CV_64F)
sobel = cv2.Sobel(src=img, ddepth=cv2.CV_64F, dx=1, dy=1, ksize=3)
sobel = cv2.convertScaleAbs(sobel) # 求图像绝对值
cv2.imshow("sobel", sobel) # 直接计算结果
# 2、xy方向分步计算
# 2.1 分别获取x、y方向的图像像素近似值(导阶数)
sobel_x = cv2.Sobel(src=img, ddepth=cv2.CV_64F, dx=1, dy=0, ksize=3)
sobel_y = cv2.Sobel(src=img, ddepth=cv2.CV_64F, dx=0, dy=1, ksize=3)
# 2.2 分别获取x、y方向的图像像素近似值(导阶数)的绝对值
sobel_x = cv2.convertScaleAbs(sobel_x)
sobel_y = cv2.convertScaleAbs(sobel_y)
# 2.3 根据权重计算图像的梯度
# gamma:添加结果后的亮度(0-255)
sobel_xy = cv2.addWeighted(src1=sobel_x, alpha=0.5, src2=sobel_y, beta=0.5, gamma=0)
cv2.imshow("sobel_xy", sobel_xy) # 分步计算结果
cv2.waitKey(0)
cv2.destroyAllWindows()
运行结果如图1.6所示。
图1.6 Sobel算子运行结果
2、Scharr算子
在离散的空间上,有很多方法可以用来计算所似导数,在使用3x3的Sobel算子时,可能计算结果并不太精准。OpenCV提供了Scharr算子,该算子具有和Sobel算子同样的速度,具精度更高,可以将Scharr算子看作对Sobel算子的改进。
Scharr算子与Sobel是区别是卷积核不一样。以3x3的卷积核为例,Sobel算子核结构较小,精确度不高,而Scharr算子核结构较大,具有更高的精度。
Sobel算子和Scharr算子的核结构如图2.1所示。
图2.1 Sobe算子与Scharr算子卷积核
OpenCV中提供Scharr算子的直接应用,使用函数vc2.Scharr()实现Scharr算子运算,语法格式与Sobel算子类似,但是不用设置卷积核数(即ksize)。
Sobel算子与Scharr算子对比案例。
import cv2
img = cv2.imread("E:/tmp/img/cat.png", cv2.IMREAD_GRAYSCALE)
cv2.imshow("test", img) # 原图
# 1、Sobel算子
sobel_x = cv2.Sobel(src=img, ddepth=cv2.CV_64F, dx=1, dy=0, ksize=3)
sobel_y = cv2.Sobel(src=img, ddepth=cv2.CV_64F, dx=0, dy=1, ksize=3)
abs_sobel_x = cv2.convertScaleAbs(sobel_x)
abs_sobel_y = cv2.convertScaleAbs(sobel_y)
sobel = cv2.addWeighted(abs_sobel_x, 0.5, abs_sobel_y, 0.5, 0)
cv2.imshow("Sobel", sobel)
# 2、Scharr算子
scharr_x = cv2.Scharr(src=img, ddepth=cv2.CV_64F, dx=1, dy=0)
scharr_y = cv2.Scharr(src=img, ddepth=cv2.CV_64F, dx=0, dy=1)
abs_scharr_x = cv2.convertScaleAbs(scharr_x)
abs_scharr_y = cv2.convertScaleAbs(scharr_y)
scharr = cv2.addWeighted(abs_scharr_x, 0.5, abs_scharr_y, 0.5, 0)
cv2.imshow("Scharr", scharr)
cv2.waitKey(0)
cv2.destroyAllWindows()
运行结果。
图2.2 Sobel算子与Scharr算子对比
通过图2.2运行结果可以看出,Scharr算子对细节的把控更加精准。
3、Laplcaian算子
Laplacian(拉普拉斯)算子是一种二阶导数算子,其具有旋转不变性,可以满足不同方向的图形边缘检测的要求。通常情况下,其算子的系数之和需要为零。
例如,一个3x3大小的Laplacian算子如图3.1所示。
图3.1 Laplcaian算子卷积核
Laplacian算子类似于二阶Sobel导数,需要计算两个方向的梯度值,例如图3.2。
图3.2 Laplacian算子卷积核
计算像素点P5的近似导数值:P5=(P2 + P4 +P6 + P8) - 4xP5