欢迎来到军工软件开发人才培养基地——学到牛牛

梯度算子

时间:2024-05-06 07:01:10 来源:学到牛牛

图像梯度计算是图像变化的幅度。对于图像的边缘部分,其灰度值变化较大,梯度值变化也较大;相反,对于图像中比较平滑的部分,其灰度值变化较小,相应的梯度值变化也较小。

一般情况下,图像梯度计算的是图像的边缘信息。

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