图像梯度
图像梯度计算的是图像变化的速度。对于图像的边缘部分,其灰度值变化较大,梯度值也较大;相反,对于图像中比较平滑的部分,其灰度值变化较小,相应的梯度值也较小。一般情况下,图像梯度计算的是图像的边缘信息。
严格来讲,图像梯度计算需要求导数,但是图像梯度一般通过计算像素值的差来得到梯度的近似值(我们把这种方法称为求图像梯度)。
例如,图1中的左右两幅图分别描述了图像的水平边界和垂直边界。
针对左图,通过垂直方向的线条A和线条B的位置,可以计算图像水平方向的边界:
- 对于线条A和线条B,其右侧像素值与左侧像素值的差值不为零,因此是边界。
- 对于其余列,其右侧像素值与左侧像素值的差值均为零,因此不是边界。
对于其余列,其右侧像素值与左侧像素值的差值均为零,因此不是边界。
- 对于线条A和线条B,其下侧像素值与上侧像素值的差值不为零,因此是边界。
- 对于其余行,其下侧像素值与上侧像素值的差值均为零,因此不是边界。
将上述运算关系进一步优化,可以得到更复杂的边缘信息。本章将关注Sobel算子、Scharr算子和Laplacian算子的使用。
Sobel理论基础
Sobel算子是一种离散的微分算子,该算子结合了高斯平滑和微分求导运算。该算子利用局部差分寻找边缘,计算所得的是一个梯度的近似值。
Sobel算子如图2所示。
需要说明的是,滤波器通常是指由一幅图像根据像素点 ( x , y ) (x, y) (x,y)临近的区域计算得到另外一幅新图像的算法。因此,滤波器是由邻域及预定义的操作构成的。滤波器规定了滤波时所采用的形状以及该区域内像素值的组成规律。滤波器也被称为“掩模”、“核”、“模板”、“窗口”、“算子”等。一般信号领域将其称为“滤波器”,数学领域将其称为“核”。本章中出现的滤波器多数为“线性滤波器”,也就是说,滤波的目标像素点的值等于原始像素值及其周围像素值的加权和。这种基于线性核的滤波,就是我们所熟悉的卷积。在本章中,为了方便说明,直接使用“算子”来表示各种算子所使用的滤波器。例如,本章中所说的“Sobel算子”通常是指Sobel滤波器。
假定有原始图像src,下面对Sobel算子的计算进行讨论。
计算水平方向偏导数的近似值
将Sobel算子与原始图像src进行卷积计算,可以计算水平方向上的像素值变化情况。例如,当Sobel算子的大小为3×3时,水平方向偏导数Gx的计算方式为:
上式中,src是原始图像,假设其中有9个像素点,如图3所示:
如果要计算像素点P5的水平方向偏导数 P 5 x P5_x P5x,则需要利用Sobel算子及 P 5 P5 P5邻域点,所使用的公式为:
P 5 x = ( P 3 − P 1 ) + 2 ⋅ ( P 6 − P 4 ) + ( P 9 − P 7 ) P5_x=(P3-P1) + 2·(P6-P4) + (P9-P7) P5x=(P3−P1)+2⋅(P6−P4)+(P9−P7)
即用像素点 P 5 P5 P5右侧像素点的像素值减去其左侧像素点的像素值。其中,中间像素点 ( P 4 和 P 6 ) (P4和P6) (P4和P6)距离像素点 P 5 P5 P5较近,其像素值差值的权重为2;其余差值的权重为1。
计算垂直方向偏导数的近似值
将Sobel算子与原始图像src进行卷积计算,可以计算垂直方向上的变化情况。例如,当Sobel算子的大小为3×3时,垂直方向偏导数 G y G_y Gy的计算方式为:
上式中,src是原始图像,假设其中有9个像素点,如图4所示。
如果要计算像素点P5的垂直方向偏导数 P 5 y P5_y P5y,则需要利用Sobel算子及 P 5 P5 P5邻域点,所使用的公式为:
P 5 y = ( P 7 − P 1 ) + 2 ⋅ ( P 8 − P 2 ) + ( P 9 − P 3 ) P5_y=(P7-P1) + 2·(P8-P2) + (P9-P3) P5y=(P7−P1)+2⋅(P8−P2)+(P9−P3)
式中,使用像素点P5下一行像素点的像素值减去上一行像素点的像素值。其中,中间像素点( P 2 P2 P2和 P 8 P8 P8)距离像素点 P 5 P5 P5较近,其像素值差值的权重为2;其余差值的权重为1。
Sobel算子及函数使用
在OpenCV内,使用函数cv2.Sobel()实现Sobel算子运算,其语法形式为:
dst = cv2.Sobel( src, ddepth, dx, dy[, ksize[, scale[, delta[, borderType]]]] )
式中:
- dst代表目标图像。
- src代表原始图像。
- ddepth代表输出图像的深度。其具体对应关系如表1所示。
- dx代表x方向上的求导阶数。
- dy代表y方向上的求导阶数。
- ksize代表Sobel核的大小。该值为-1时,则会使用Scharr算子进行运算。
- scale代表计算导数值时所采用的缩放因子,默认情况下该值是1,是没有缩放的。
- delta代表加在目标图像dst上的值,该值是可选的,默认为0。
- borderType代表边界样式。该参数的具体类型及值如表2所示。
参数ddepth
在函数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”。
下面对参数ddepth值的设定做一个简要的说明。
例如,图5中的原始图像(左图)是一幅二值图像,图中黑色部分的像素值为0,白色部分的像素值为1。在计算A线条所在位置和B线条所在位置的近似偏导数时:
- 针对A线条所在列,右侧像素值减去左侧像素值所得近似偏导数的值为-1。
- 针对B线条所在列,右侧像素值减去左侧像素值所得近似偏导数的值为1。
针对上述偏导数结果进行不同方式的处理,可能会得到不同的结果,例如:
-
直接计算。此时,A线条位置的值为负数,B线条位置的值为正数。在显示时,由于上述负值不在8位图范围内,因此要做额外处理。将A线条处的负数偏导数处理为0, B线条处的正数偏导数保持不变。在这种情况下,显示结果如图5中间的图所示。
-
计算绝对值。此时,A线条处的负数偏导数被处理正数,B线条处的正数偏导数保持不变。在显示时,由于上述值在8位图的表示范围内,因此不再对上述值进行处理,此时,显示结果如图5中的右图所示。
上述问题在计算垂直方向的近似偏导数时同样存在。例如,图6中的原始图像(左图)是一幅二值图像,图中黑色部分的像素值为0,白色部分的像素值为1。计算A线条所在位置和B线条所在位置的近似偏导数时:
- 针对A线条所在行,下方像素值减去上方像素值所得近似偏导数为-1。
- 针对B线条所在行,下方像素值减去上方像素值所得近似偏导数为1。
针对上述偏导数结果进行不同方式的处理,可能会得到不同的结果,例如:
-
直接计算。此时,A线条位置的值为负数,B线条位置的值为正数。在显示时,上述值不在8位图的表示范围内,因此需要进行额外处理。将A线条处的负数偏导数处理为0, B线条处的正数偏导数保持不变。此时,显示结果如图6中间的图所示。
-
计算绝对值。此时,A线条处的负数偏导数被处理正数,B线条处的正数偏导数保持不变。在显示时,由于上述值在8位图的表示范围内,因此不再对上述值进行处理,此时,显示结果如图6中的右图所示。
经过上述分析可知,为了让偏导数正确地显示出来,需要将值为负数的近似偏导数转换为正数。即,要将偏导数取绝对值,以保证偏导数总能正确地显示出来。
例如,图7描述了如何计算偏导数。
为了得到结果为正数的偏导数,需要对图7中计算的偏导数取绝对值,如下:
∣ P 5 x ∣ = ∣ ( P 3 − P 1 ) + 2 ⋅ ( P 6 − P 4 ) + ( P 9 − P 7 ) ∣ |P5x| = |(P3-P1) + 2·(P6-P4) + (P9-P7)| ∣P5x∣=∣(P3−P1)+2⋅(P6−P4)+(P9−P7)∣
∣ P 5 y ∣ = ∣ ( P 7 − P 1 ) + 2 ⋅ ( P 8 − P 2 ) + ( P 9 − P 3 ) ∣ |P5y| = |(P7-P1) + 2·(P8-P2) + (P9-P3)| ∣P5y∣=∣(P7−P1)+2⋅(P8−P2)+(P9−P3)∣
当然,在需要时,还可以进行如下处理:
P 5 S o b e l = ∣ P 5 x ∣ + ∣ P 5 y ∣ = ∣ ( P 3 − P 1 ) + 2 ⋅ ( P 6 − P 4 ) + ( P 9 − P 7 ) ∣ + ∣ ( P 7 − P 1 ) + 2 ⋅ ( P 8 − P 2 ) + ( P 9 − P 3 ) ∣ P5Sobel = |P5x| + |P5y| = |(P3-P1) +2·(P6-P4) + (P9-P7)| + |(P7-P1) + 2·(P8-P2) + (P9-P3)| P5Sobel=∣P5x∣+∣P5y∣=∣(P3−P1)+2⋅(P6−P4)+(P9−P7)∣+∣(P7−P1)+2⋅(P8−P2)+(P9−P3)∣
经过以上分析,我们得知:在实际操作中,计算梯度值可能会出现负数。通常处理的图像是8位图类型,如果结果也是该类型,那么所有负数会自动截断为0,发生信息丢失。所以,为了避免信息丢失,我们在计算时使用更高的数据类型cv2.CV_64F,再通过取绝对值将其映射为cv2.CV_8U(8位图)类型。
在OpenCV中,使用函数cv2.convertScaleAbs()对参数取绝对值,该函数的语法格式为:
dst = cv2.convertScaleAbs( src [, alpha[, beta]] )
上式中:
-
dst代表处理结果。
-
src代表原始图像。
-
alpha代表调节系数,该值是可选值,默认为1。
-
beta代表调节亮度值,该值是默认值,默认为0。
这里,该函数的作用是将原始图像src转换为256色位图,其可以表示为:
dst=saturate(src*alpha+beta)
式中,saturate()表示计算结果的最大值是饱和值,例如当src*alpha+beta
的值超过255时,其取值为255。
方向
在函数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方向的边缘叠加:通过组合方式实现。
下面分别对上述情况进行简要说明。
计算x方向边缘(梯度):dx=1, dy=0
如果想只计算x方向(水平方向)的边缘,需要将函数cv2.Sobel()的参数dx和dy的值设置为“dx=1, dy=0”。当然,也可以设置为“dx=2,dy=0”。此时,会仅仅获取水平方向的边缘信息,此时的语法格式为:
dst = cv2.Sobel( src , ddepth , 1 , 0 )
使用该语句获取边缘图的示例如图8所示,其中左图为原始图像,右图为获取的边缘图。
计算y方向边缘(梯度):dx=0, dy=1
如果想只计算y方向(垂直方向)的边缘,需要将函数cv2.Sobel()的参数dx和dy的值设置为“dx=0, dy=1”。当然,也可以设置为“dx=0,dy=2”。此时,会仅仅获取垂直方向的边缘信息,此时的语法格式为:
dst = cv2.Sobel( src , ddepth , 0 , 1 )
使用该语句获取边缘图的示例如图9所示,其中左图为原始图像,右图为获取的边缘图。
参数dx与参数dy的值均为1:dx=1, dy=1
可以将函数cv2.Sobel()的参数dx和dy的值设置为“dx=1, dy=1”,也可以设置为“dx=2, dy=2”,或者两个参数都不为零的其他情况。此时,会获取两个方向的边缘信息,此时的语法格式为:
dst = cv2.Sobel( src , ddepth , 1 , 1 )
使用该语句获取边缘图的示例如图10所示,其中左图为原始图像,右图为获取的边缘图,仔细观察可以看到图中仅有若干个微小白点,每个点的大小为一个像素。
计算x方向和y方向的边缘叠加
如果想获取x方向和y方向的边缘叠加,需要分别获取水平方向、垂直方向两个方向的边缘图,然后将二者相加。此时的语法格式为:
dx= cv2.Sobel( src , ddepth , 1 , 0 )
dy= cv2.Sobel( src , ddepth , 0 , 1 )
dst=cv2.addWeighted( src1 , alpha , src2 , beta , gamma )
使用上述语句获取边缘图的示意图如图11所示,其中左图为原始图像,右图为获取的边缘图。
实例
本节将通过实例来介绍如何使用函数cv2.Sobel()获取图像边缘信息。
【例2】使用函数cv2.Sobel()获取图像水平方向的边缘信息。
在本例中,将参数ddepth的值设置为-1,参数dx和dy的值设置为“dx=1, dy=0”。
根据题目要求及分析,设计程序如下:
import cv2
o = cv2.imread('Sobel4.bmp', cv2.IMREAD_GRAYSCALE)
Sobelx = cv2.Sobel(o, -1,1,0)
cv2.imshow("original", o)
cv2.imshow("x", Sobelx)
cv2.waitKey()
cv2.destroyAllWindows()
运行程序,结果如图12所示。
【分析】
从程序可以看出,当参数ddepth的值为-1时,只得到了图中黑色框的右边界。这是因为,左边界在运算时得到了负值,其在显示时被调整为0,所以没有显示出来。要想获取左边界的值(将其显示出来),必须将参数ddepth的值设置为更大范围的数据结构类型,并将其映射到8位图像内。
【例3】使用函数cv2.Sobel()获取图像水平方向的完整边缘信息。在本例中,将参数ddepth的值设置为cv2.CV_64F,并使用函数cv2.convertScaleAbs()对cv2.Sobel()的计算结果取绝对值。
根据题目要求及分析,设计程序如下:
import cv2
o = cv2.imread('Sobel4.bmp', cv2.IMREAD_GRAYSCALE)
Sobelx = cv2.Sobel(o, cv2.CV_64F,1,0)
Sobelx = cv2.convertScaleAbs(Sobelx)
cv2.imshow("original", o)
cv2.imshow("x", Sobelx)
cv2.waitKey()
cv2.destroyAllWindows()
运行程序,结果如图13所示。
【分析】
从程序可以看出,将函数cv2.Sobel()内参数ddepth的值设置为“cv2.CV_64F”,参数dx和dy的值设置为“dx=1, dy=0”后,执行该函数,再对该函数的结果计算绝对值,可以获取图像在水平方向的完整边缘信息。
【例4】
使用函数cv2.Sobel()获取图像垂直方向的边缘信息。
在本例中,将参数ddepth的值设置为cv2.CV_64F,并使用函数cv2.convertScaleAbs()对cv2.Sobel()的计算结果取绝对值。
根据题目要求及分析,设计程序如下:
import cv2
o = cv2.imread('Sobel4.bmp', cv2.IMREAD_GRAYSCALE)
Sobely = cv2.Sobel(o, cv2.CV_64F,0,1)
Sobely = cv2.convertScaleAbs(Sobely)
cv2.imshow("original", o)
cv2.imshow("y", Sobely)
cv2.waitKey()
cv2.destroyAllWindows()
运行程序,结果如图14所示。
【分析】
从程序可以看出,将参数ddepth的值设置为“cv2.CV_64F”,参数dx和dy的值设置为“dx=0, dy=1”的情况下,使用函数cv2.convertScaleAbs()对函数cv2.Sobel()的计算结果取绝对值,可以获取图像在垂直方向的完整边缘信息。
根据题目要求,设计程序如下:
import cv2
o = cv2.imread('Sobel4.bmp', cv2.IMREAD_GRAYSCALE)
Sobelxy=cv2.Sobel(o, cv2.CV_64F,1,1)
Sobelxy=cv2.convertScaleAbs(Sobelxy)
cv2.imshow("original", o)
cv2.imshow("xy", Sobelxy)
cv2.waitKey()
cv2.destroyAllWindows()
运行程序,结果如图15所示。
【例6】
计算函数cv2.Sobel()在水平、垂直两个方向叠加的边缘信息。
根据题目要求,设计程序如下:
import cv2
o = cv2.imread('Sobel4.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()
运行程序,结果如图16所示。
【分析】
从程序可以看出,本例中首先分别计算x方向的边缘、y方向的边缘,接下来使用函数cv2.addWeighted()对两个方向的边缘进行叠加。在最终的叠加边缘结果中,同时显示两个方向的边缘信息。
【例7】
使用不同方式处理图像在两个方向的边缘信息。在本例中,分别使用两种不同的方式获取边缘信息。
- 方式1:分别使用“dx=1, dy=0”和“dx=0, dy=1”计算图像在水平方向和垂直方向的边缘信息,然后将二者相加,构成两个方向的边缘信息。
- 方式2:将参数dx和dy的值设为“dx=1, dy=1”,获取图像在两个方向的梯度。
根据题目要求,设计程序如下:
import cv2
o = cv2.imread('lena.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)
Sobelxy11=cv2.Sobel(o, cv2.CV_64F,1,1)
Sobelxy11=cv2.convertScaleAbs(Sobelxy11)
cv2.imshow("original", o)
cv2.imshow("xy", Sobelxy)
cv2.imshow("xy11", Sobelxy11)
cv2.waitKey()
cv2.destroyAllWindows()
运行程序,结果如图17所示,其中左图为原始图像,中间的图为方式1对应的图像,右图为方式2对应的图像。
Scharr算子及函数使用
在离散的空间上,有很多方法可以用来计算近似导数,在使用3×3的Sobel算子时,可能计算结果并不太精准。OpenCV提供了Scharr算子,该算子具有和Sobel算子同样的速度,且精度更高。可以将Scharr算子看作对Sobel算子的改进,其核通常为:
OpenCV提供了函数cv2.Scharr()来计算Scharr算子,其语法格式如下:
dst = cv2.Scharr( src, ddepth, dx, dy[, scale[, delta[, borderType]]] )
式中:
- dst代表输出图像。
- src代表原始图像。
- ddepth代表输出图像深度。该值与函数cv2.Sobel()中的参数ddepth的含义相同,具体可以参考表1。
- dx代表x方向上的导数阶数。
- dy代表y方向上的导数阶数。
- scale代表计算导数值时的缩放因子,该项是可选项,默认值是1,表示没有缩放。
- delta代表加到目标图像上的亮度值,该项是可选项,默认值为0。
- borderType代表边界样式。具体可以参考表2。
在函数cv2.Sobel()中介绍过,如果ksize=-1,则会使用Scharr滤波器。因此,如下语句:
dst=cv2.Scharr(src, ddepth, dx, dy)
和
dst=cv2.Sobel(src, ddepth, dx, dy, -1)
是等价的。函数cv2.Scharr()和函数cv2.Sobel()的使用方式基本一致。首先,需要注意的是,参数ddepth的值应该设置为“cv2.CV_64F”,并对函数cv2.Scharr()的计算结果取绝对值,才能保证得到正确的处理结果。具体语句为:
dst=Scharr(src, cv2.CV_64F, dx, dy)
dst= cv2.convertScaleAbs(dst)
另外,需要注意的是,在函数cv2.Scharr()中,要求参数dx和dy满足条件:
dx >= 0 && dy >= 0 && dx+dy == 1
因此,参数dx和参数dy的组合形式有:
- 计算x方向边缘(梯度):dx=1, dy=0。
- 计算y方向边缘(梯度): dx=0, dy=1。
- 计算x方向与y方向的边缘叠加:通过组合方式实现。
下面分别对上述情况进行简要说明。
计算x方向边缘(梯度):dx=1, dy=0
此时,使用的语句是:
dst=Scharr(src, ddpeth, dx=1, dy=0)
计算y方向边缘(梯度):dx=0, dy=1
此时,使用的语句是:
dst=Scharr(src, ddpeth, dx=0, dy=1)
计算x方向与y方向的边缘叠加
将两个方向的边缘相加,使用的语句是:
dx=Scharr(src, ddpeth, dx=1, dy=0)
dy=Scharr(src, ddpeth, dx=0, dy=1)
Scharrxy=cv2.addWeighted(dx,0.5, dy,0.5,0)
需要注意的是,参数dx和dy的值不能都为1。例如,如下语句是错误的:
dst=Scharr(src, ddpeth, dx=1, dy=1)
【例8】使用函数cv2.Scharr()获取图像水平方向的边缘信息。
根据题目要求,设计程序如下:
import cv2
o = cv2.imread('Scharr.bmp', cv2.IMREAD_GRAYSCALE)
Scharrx = cv2.Scharr(o, cv2.CV_64F,1,0)
Scharrx = cv2.convertScaleAbs(Scharrx)
cv2.imshow("original", o)
cv2.imshow("x", Scharrx)
cv2.waitKey()
cv2.destroyAllWindows()
【例9】使用函数cv2.Scharr()获取图像垂直方向的边缘信息。
根据题目要求,设计程序如下:
import cv2
o = cv2.imread('Scharr.bmp', cv2.IMREAD_GRAYSCALE)
Scharry = cv2.Scharr(o, cv2.CV_64F,0,1)
Scharry = cv2.convertScaleAbs(Scharry)
cv2.imshow("original", o)
cv2.imshow("y", Scharry)
cv2.waitKey()
cv2.destroyAllWindows()
运行程序,结果如图18所示。
【例10】使用函数cv2.Scharr()实现水平方向和垂直方向边缘叠加的效果。
根据题目要求,设计程序如下
import cv2
o = cv2.imread('scharr.bmp', cv2.IMREAD_GRAYSCALE)
scharrx = cv2.Scharr(o, cv2.CV_64F,1,0)
scharry = cv2.Scharr(o, cv2.CV_64F,0,1)
scharrx = cv2.convertScaleAbs(scharrx)
scharry = cv2.convertScaleAbs(scharry)
scharrxy = cv2.addWeighted(scharrx,0.5, scharry,0.5,0)
cv2.imshow("original", o)
cv2.imshow("xy", scharrxy)
cv2.waitKey()
cv2.destroyAllWindows()
运行程序,结果如图19所示。
【例11】观察将函数cv2.Scharr()的参数dx、dy同时设置为1时,程序的运行情况。
根据题目要求,设计程序如下:
import cv2
import numpy as np
o = cv2.imread('image\\Scharr.bmp', cv2.IMREAD_GRAYSCALE)
Scharrxy11=cv2.Scharr(o, cv2.CV_64F,1,1)
cv2.imshow("original", o)
cv2.imshow("xy11", Scharrxy11)
cv2.waitKey()
cv2.destroyAllWindows()
运行程序,报错如下:
【分析】
需要注意,不允许将函数cv2.Scharr()的参数dx和dy的值同时设置为1。因此,本例中将这两个参数的值都设置为1后,程序会报错。
【例12】
使用函数cv2.Sobel()完成Scharr算子的运算。当函数cv2.Sobel()中ksize的参数值为-1时,就会使用Scharr算子进行运算。因此,
dst=cv2.Sobel(src, ddpeth, dx, dy, -1)
等价于
dst=cv2.Scharr(src, ddpeth, dx, dy)
根据题目要求及分析,设计程序如下:
import cv2
o = cv2.imread('Sobel4.bmp', cv2.IMREAD_GRAYSCALE)
Scharrx = cv2.Sobel(o, cv2.CV_64F,1,0, -1)
Scharry = cv2.Sobel(o, cv2.CV_64F,0,1, -1)
Scharrx = cv2.convertScaleAbs(Scharrx)
Scharry = cv2.convertScaleAbs(Scharry)
cv2.imshow("original", o)
cv2.imshow("x", Scharrx)
cv2.imshow("y", Scharry)
cv2.waitKey()
cv2.destroyAllWindows()
运行程序,结果如图20所示,其中左图为原始图像,中间的图为水平边缘图像,右图为垂直边缘图像。
Sobel算子和Scharr算子的比较
Sobel算子的缺点是,当其核结构较小时,精确度不高,而Scharr算子具有更高的精度。Sobel算子和Scharr算子的核结构如图21所示。
【例13】分别使用Sobel算子和Scharr算子计算一幅图像的水平边缘和垂直边缘的叠加信息。根据题目要求,设计程序如下:
import cv2
o = cv2.imread('lena.bmp', cv2.IMREAD_GRAYSCALE)
Sobelx = cv2.Sobel(o, cv2.CV_64F,1,0, ksize=3)
Sobely = cv2.Sobel(o, 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(o, cv2.CV_64F,1,0)
Scharry = cv2.Scharr(o, cv2.CV_64F,0,1)
Scharrx = cv2.convertScaleAbs(Scharrx)
Scharry = cv2.convertScaleAbs(Scharry)
Scharrxy = cv2.addWeighted(Scharrx,0.5, Scharry,0.5,0)
cv2.imshow("original", o)
cv2.imshow("Sobelxy", Sobelxy)
cv2.imshow("Scharrxy", Scharrxy)
cv2.waitKey()
cv2.destroyAllWindows()
运行程序,结果如图22所示。
Laplacian算子及函数使用
Laplacian(拉普拉斯)算子是一种二阶导数算子,其具有旋转不变性,可以满足不同方向的图像边缘锐化(边缘检测)的要求。通常情况下,其算子的系数之和需要为零。例如,一个3×3大小的Laplacian算子如图23所示。
Laplacian算子类似二阶Sobel导数,需要计算两个方向的梯度值。例如,在图24中:
- 左图是Laplacian算子。
- 右图是一个简单图像,其中有9个像素点。
计算像素点 P 5 P5 P5的近似导数值,如下:
P 5 l a p = ( P 2 + P 4 + P 6 + P 8 ) − 4 ⋅ P 5 P5_{lap}=(P2 + P4 + P6 + P8) -4·P5 P5lap=(P2+P4+P6+P8)−4⋅P5
图25展示了像素点与周围点的一些实例,其中:
- 在左图中,像素点P5与周围像素点的值相差较小,得到的计算结果值较小,边缘不明显。
- 在中间的图中,像素点P5与周围像素点的值相差较大,得到的计算结果值较大,边缘较明显。
- 在右图中,像素点P5与周围像素点的值相差较大,得到的计算结果值较大,边缘较明显。
需要注意,在上述图像中,计算结果的值可能为正数,也可能为负数。所以,需要对计算结果取绝对值,以保证后续运算和显示都是正确的。在OpenCV内使用函数cv2.Laplacian()实现Laplacian算子的计算,该函数的语法格式为:
dst = cv2.Laplacian( src, ddepth[, ksize[, scale[, delta[, borderType]]]] )
式中:
- dst代表目标图像。
- src代表原始图像。
- ddepth代表目标图像的深度。
- ksize代表用于计算二阶导数的核尺寸大小。该值必须是正的奇数。
- scale代表计算Laplacian值的缩放比例因子,该参数是可选的。默认情况下,该值为1,表示不进行缩放。
- delta代表加到目标图像上的可选值,默认为0。
- borderType代表边界样式。
该函数分别对x、y方向进行二次求导,具体为:
上式是当ksize的值大于1时的情况。当ksize的值为1时,Laplacian算子计算时采用的3×3的核如下:
通过从图像内减去它的Laplacian图像,可以增强图像的对比度,此时其算子如图26所示。
【例14】使用函数cv2.Laplacian()计算图像的边缘信息。根据题目要求,设计程序如下:
import cv2
o = cv2.imread('Laplacian.bmp', cv2.IMREAD_GRAYSCALE)
Laplacian = cv2.Laplacian(o, cv2.CV_64F)
Laplacian = cv2.convertScaleAbs(Laplacian)
cv2.imshow("original", o)
cv2.imshow("Laplacian", Laplacian)
cv2.waitKey()
cv2.destroyAllWindows()
运行程序,结果如图27所示,其中左图为原始图像,右图为得到的边缘信息。
算子总结
Sobel算子、Scharr算子、Laplacian算子都可以用作边缘检测,它们的核如图28所示。
Sobel算子和Scharr算子计算的都是一阶近似导数的值。通常情况下,可以将它们表示为:
S o b e l 算子 = ∣ 左 − 右 ∣ / ∣ 下 − 上 ∣ Sobel算子=|左-右| / |下-上| Sobel算子=∣左−右∣/∣下−上∣
S c h a r r 算子 = ∣ 左 − 右 ∣ / ∣ 下 − 上 ∣ Scharr算子=|左-右| / |下-上| Scharr算子=∣左−右∣/∣下−上∣
式中|左-右|
表示左侧像素值减右侧像素值的结果的绝对值,|下-上|
表示下方像素值减上方像素值的结果的绝对值。
Laplacian算子计算的是二阶近似导数值,可以将它表示为:
L a p l a c i a n 算子 = ∣ 左 − 右 ∣ + ∣ 左 − 右 ∣ + ∣ 下 − 上 ∣ + ∣ 下 − 上 ∣ Laplacian算子=|左-右| + |左-右| + |下-上| + |下-上| Laplacian算子=∣左−右∣+∣左−右∣+∣下−上∣+∣下−上∣
通过公式可以发现,Sobel算子和Scharr算子各计算了一次|左-右|
和|下-上|
的值,而Laplacian算子分别计算了两次|左-右|
和|下-上|
的值。