第9章:图像梯度

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

​ 严格的来讲,图像梯度计算是需要求导数的,但是我们一般可以通过计算像素的差值来得到图像梯度的近似值(近似导数)

例如,下面两幅图中分别描述了图像的水平边界和图像的垂直边界。

image-20211031192829874

针对左图 ,通过垂直方向的线条A和线条B的位置,可以计算图像水平方向的边界。

  • 对于线条A和线条B,其右侧像素值与左侧像素值的差值不为0,因此是边界。
  • 对于其他列,其右侧像素值与左侧像素值的差值均为0,因此不是边界。

针对右图,通过水平方向的线条A和线条B的位置,可以计算图像垂直方向的边界

  • 对于线条A和线条B,其下侧像素值与上侧像素值的差值不为0,因此是边界。
  • 对于其他行,其下侧的像素值与上侧像素值均为零,因此不是边界。

通过将上述关系进行进一步优化,可以得到更复杂的边缘信息。

one. Sobel理论基础

Sobel算子是一种离散的微分算子,该算子结合了高斯平滑和微分求导运算。该算子利用局部差分来寻找边缘,计算所得的是一个梯度的近似值。

image-20211031193107135

名词介绍:

  • 滤波器:指的是由一幅图像根据像素点(x, y)及临近的区域计算得到的另一幅新图像的算法。
    • 滤波器由邻域及预定义的操作构成。滤波器规定了滤波时所采用的形状以及该区域内像素值的组成规律。
    • 滤波器也被称为掩模、核、模板、窗口、算子
    • 一般在信号领域称为滤波器,在数学领域称为核。
    • 基于线性滤波器的滤波就是我们所熟悉的卷积,滤波的目标像素点的值等于原始像素值及其周围像素值的加权和。
    • 在图像梯度中我们统称其为算子。例如,Sobel 算子通常是指 Sobel滤波器。

1. 计算水平方向偏导数的近似值

​ 通过将Sobel算子与原始图像src进行卷积计算,来计算水平方向上的像素值变化的情况。例如,当Sobel算子的大小为3×3时,水平方向偏导数Gx的计算方式为:

image-20211031194036741

src是原始图像,假设其中有9个像素点

image-20211031194146747

如果要计算像素点P5的水平方向上的偏导数P5x,则需要利用Sobel算子及P5邻域点,所使用的公式为:P5x = (P3-P1) + 2 · (P6-P4) + (P9-P7)

​ 即用像素点P5右侧像素点的像素值减去其左侧的像素值。其中,中间像素点(P4和P6)距离P5较近,其像素值的差值权重为2,其余的权重为1。

2. 计算垂直方向偏导数的近似值

​ 通过将Sobel算子与原始图像src进行卷积计算,来计算垂直方向上的像素值变化的情况。例如,当Sobel算子的大小为3×3时,水平方向偏导数Gx的计算方式为:

image-20211031195839763

src是原始图像,假设其中有9个像素点

image-20211031195921468

如果要计算像素点P5的水平方向上的偏导数P5y,则需要利用Sobel算子及P5邻域点,所使用的公式为:P5y = (P7-P1) + 2 · (P8-P2) + (P9-P3)

​ 即用像素点P5下一行像素点的像素值减去其上一行的像素值。其中,中间像素点(P2和P8)距离P5较近,其像素值的差值权重为2,其余的权重为1。

two. Sobel算子及函数的使用:

1. 函数语法:

在OpenCV中,使用函数cv2.Sobel()实现Sobel算子运算,语法为:

dst = cv2.Sobel(src, ddepth, dx, dy [, ksize [, scale [, delta [, borderType] ] ] ] )

  • dst :代表目标结果图像

  • src:原始图像

  • ddepth:代表输入图像的深度。如下图所示

    image-20211031200210578

  • dx:代表x方向上的求导阶数

  • dy:代表y方向上的求导阶数

  • ksize:代表Sobel核的导向。该值为-1时,会使用Scharr算子进行运算。

  • scale:代表就是那个导数值时所采用的的缩放因子,默认情况下该值是1,是没有缩放的。

  • delta:代表加在目标图像dst上的值,该值是可选的,默认为0。

  • borderType:代表边界样式。

    image-20211031200244514

2. 对像素取绝对值:

特别注意:

​ 在函数cv2.Sobel()的语法中,同样是可以将ddepth参数的值是设置-1的,让处理的结果图像与原始图像图像深度保持一致。但是,如果直接将参数ddepth的值设置为-1,在计算时得到的结果可能是错误的。

​ 在实际的操作中,计算梯度是可能出现负数的。如果处理的图像是8位图类型,则在ddepth的参数为-1时,意味着指定运算结果也是8位图类型,那么所有的负数会自动截断为0,发生信息丢失。为了避免信息丢失,在计算时要先使用更高的数据类型cv2.CV_64F,再通过取绝对值将其映射为cv2.CV_8U(8位图)类型。所以,通常要将函数cv2.Sobel()内参数ddepth的值设置为"cv2.CV_64F"。

例如:

image-20211101001156090

图的原始图像(左图)是一幅二值图像,图中黑色部分的像素值为0,白色部分的像素值为1。在计算A线条所在位置和B线条所在位置的近似偏导数时:

  • 针对A线条所在列,右侧像素值减去左侧像素值所得近似偏导数的值为-1。
  • 针对B线条所在列,右侧像素值减去左侧像素值所得近似偏导数的值为1。

针对上述偏导数结果进行不同方式的处理,可能会得到不同的结果,例如:

  • 直接计算。此时,A线条位置的值为负数,B线条位置的值为正数。在显示时,由于上述负值不在8位图范围内,因此要做额外处理。将A线条处的负数偏导数处理为0,B线条处的正数偏导数保持不变。在这种情况下,显示结果如图中间的图所示。
  • 计算绝对值。此时,A线条处的负数偏导数被处理正数,B线条处的正数偏导数保持不变。在显示时,由于上述值在8位图的表示范围内,因此不再对上述值进行处理,此时,显示结果如图中的右图所示。

上述问题在计算垂直方向的近似偏导数时同样存在。

​ 经过以上分析,我们得知:在实际操作中,计算梯度值可能会出现负数。通常处理的图像是8位图类型,如果结果也是该类型,那么所有负数会自动截断为0,发生信息丢失。所以,为了避免信息丢失,我们在计算时使用更高的数据类型 cv2.CV_64F,再通过取绝对值将其映射为cv2.CV_8U(8位图)类型。

在OpenCV中,通过使用函数cv2.convertScaleAbs()可以对参数取绝对值,该函数的语法是:

dst = cv2.convertScaleAbs( src, [, alpha[, beta ] ] )

  • dst:代表目标处理结果
  • src:代表原始图像
  • alpha:代表调节系数,该值是可选值,默认为1。
  • beta:代表调节亮度值,该值是默认值,默认值为0。

示例:

import cv2
import numpy as np

img = np.random.randint(-256, 256, size=[4, 5], dtype=np.int16)
rst = cv2.convertScaleAbs(img)
print('img=\n', img)
print('rst=\n', rst)

# 输出结果
img=
 [[-101 -212 -241  185 -176]
 [ 133  -27 -188 -144  -83]
 [-188 -244 -136  162  218]
 [-231 -112 -138  228 -157]]
rst=
 [[101 212 241 185 176]
 [133  27 188 144  83]
 [188 244 136 162 218]
 [231 112 138 228 157]]

3. 方向:

​ 在函数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
  • 计算x方向和y方向的边缘叠加:通过组合方式实现
  • 参数dx和dy均为1:dx=1, dy=1

(1) 计算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)

示例:

import cv2
import numpy as np

img = cv2.imread('../edge.bmp')
rst = cv2.Sobel(img, cv2.CV_64F, 1, 0)
rst = cv2.convertScaleAbs(rst)
cv2.imshow('img', img)
cv2.imshow('rst', rst)
cv2.waitKey()
cv2.destroyAllWindows()

image-20211101145704668

(2) 计算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)

示例:

import cv2

img = cv2.imread('../edge.bmp')
rst = cv2.Sobel(img, cv2.CV_64F, 0, 1)
rst = cv2.convertScaleAbs(rst)
cv2.imshow('img', img)
cv2.imshow('rst', rst)
cv2.waitKey()
cv2.destroyAllWindows()

image-20211101152856040

(3) 参数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) 只是是以小白点的形式展现的。

import cv2

img = cv2.imread('../edge.bmp')
rst = cv2.Sobel(img, cv2.CV_64F, 1, 1)
rst = cv2.convertScaleAbs(rst)
cv2.imshow('img', img)
cv2.imshow('rst', rst)
cv2.waitKey()
cv2.destroyAllWindows()

image-20211101154121973

(4) 计算x方向和y方向的边缘叠加:

​ 如果想获取x方向和y方向的边缘叠加,需要分别获取水平方向、垂直方向两个方向的边缘图,然后将二者相加。此时的语法格式为:

dx = cv2.Sobel( src, ddepth, 1, 0)

dy = cv2.Sobel( src, ddepth, 0, 1)

dst = cv2.addWeighted(src1, alpha, src2, brta, gamma)

import cv2

img = cv2.imread('../edge.bmp')
sobelx = cv2.Sobel(img, cv2.CV_64F, 1, 0)
sobely = cv2.Sobel(img, cv2.CV_64F, 0, 1)
sobelx = cv2.convertScaleAbs(sobelx)
sobely = cv2.convertScaleAbs(sobely)
rst = cv2.addWeighted(sobelx, 0.5, sobely, 0.5, 0)
cv2.imshow('img', img)
cv2.imshow('rst', rst)
cv2.waitKey()
cv2.destroyAllWindows()

image-20211101154911958

three. Scharr 算子及函数使用:

​ 在离散的空间上,有很多方法可以用来计算近似导数,在使用3×3的Sobel算子时,可能计算结果并不太精准。OpenCV提供了Scharr算子,该算子具有和Sobel算子同样的速度,且精度更高。可以将Scharr算子看作对Sobel算子的改进,其核通常为:

image-20211102104617726

1. 函数语法:

OpenCV 中提供了函数cv2.Scharr()来计算Scharr算子,语法格式如下:

rst = cv2.Scharr( src, ddepth, dx, dy[, scale[, delta [, borderType] ] ] )

  • dst:代表输出图像
  • src:代表原始图像
  • ddepth:代表输出图像深度。该值与函数cv2.Sobel()中的参数ddepth的含义相同。
  • dx:代表x方向上的求导阶数
  • dy:代表y方向上的求导阶数
  • scale:代表就是那个导数值时所采用的的缩放因子,默认情况下该值是1,是没有缩放的。
  • delta:代表加在目标图像dst上的值,该值是可选的,默认为0。
  • borderType:代表边界样式。

注意:

  • 在函数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 = cv2.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方向的边缘叠加:通过组合方式实现

2. 实例:

(1) 计算x方向边缘(梯度):dx = 1, dy = 0

import cv2

img = cv2.imread('../edge.bmp')
rst = cv2.Scharr(img, cv2.CV_64F, 1, 0)
rst = cv2.convertScaleAbs(rst)
cv2.imshow('img', img)
cv2.imshow('rst', rst)
cv2.waitKey()
cv2.destroyAllWindows()

image-20211102110037525

(2) 计算y方向边缘(梯度):dx = 0, dy = 1

import cv2

img = cv2.imread('../edge.bmp')
rst = cv2.Scharr(img, cv2.CV_64F, 0, 1)
rst = cv2.convertScaleAbs(rst)
cv2.imshow('img', img)
cv2.imshow('rst', rst)
cv2.waitKey()
cv2.destroyAllWindows()

image-20211102110220323

(3) 计算x方向和y方向的边缘叠加:通过组合方式实现

import cv2

img = cv2.imread('../edge.bmp')
rst_x = cv2.Scharr(img, cv2.CV_64F, 1, 0)
rst_y = cv2.Scharr(img, cv2.CV_64F, 0, 1)
rst_x = cv2.convertScaleAbs(rst_x)
rst_y = cv2.convertScaleAbs(rst_y)
rst = cv2.addWeighted(rst_x, 0.5, rst_y, 0.5, 0)
cv2.imshow('img', img)
cv2.imshow('rst', rst)
cv2.waitKey()
cv2.destroyAllWindows()

image-20211102110708485

four. Sobel算子和Scharr算子的比较:

Sobel算子的缺点是,当其核结构较小时,精确度不高,而Scharr算子具有更高的精度。Sobel算子和Scharr算子的核结构如图所示

image-20211102140850839

示例:

import cv2

img = cv2.imread('../lena.bmp')
soble_x = cv2.Sobel(img, cv2.CV_64F, 1, 0, ksize=3)
soble_y = cv2.Sobel(img, cv2.CV_64F, 0, 1, ksize=3)
soble_x = cv2.convertScaleAbs(soble_x)
soble_y = cv2.convertScaleAbs(soble_y)

scharr_x = cv2.Scharr(img, cv2.CV_64F, 1, 0)
scharr_y = cv2.Scharr(img, cv2.CV_64F, 0, 1)
scharr_x = cv2.convertScaleAbs(scharr_x)
scharr_y = cv2.convertScaleAbs(scharr_y)

soble_rst = cv2.addWeighted(soble_x, 0.5, soble_y, 0.5, 0)
scharr_rst = cv2.addWeighted(scharr_x, 0.5, scharr_y, 0.5, 0)

cv2.imshow('img', img)
cv2.imshow('soble_rst', soble_rst)
cv2.imshow('scharr_rst', scharr_rst)
cv2.waitKey()
cv2.destroyAllWindows()

在这里插入图片描述

five. Laplacian 算子:

​ Laplacian(拉普拉斯)算子是一种二阶导数算子,其具有旋转不变性,可以满足不同方向的图形边缘锐化(边缘检测)的要求。通常情况下,其算子的系数之和需要为零。例如,一个3×3大小的Laplacian算子如图所示:

image-20211102142611373

Laplacian算子类似于二阶Sobel导数,需要计算两个方向的梯度值。例如:在下图中

  • 左图是Laplacian算子
  • 右图是一个简单的图像,其中有9个像素点

image-20211102143641409

计算像素点P5的近似导数值,如下:P5lap = (p2 + p4 + p6 + p8) - 4 · p5

image-20211102143902370

  • 在左图中,像素点P5与周围像素点的值相差较小,得到的计算结果值较小,边缘不明显
  • 在中间图中,像素点P5与周围像素点的值相差较大,得到的计算结果值较大,边缘较明显
  • 在右图中,像素点P5与周围像素点的值相差较大,得到的计算结果值较大,边缘较明显

需要注意,在上述图像中,计算结果的值可能是正数,也可能是负数。所以,需要对计算的结果取绝对值,以保证后续的运算和显示都是正确的。

1. 函数语法:

在OpenCV中,使用cv2.Laplacian()实现对Laplacian算子的计算,该函数的语法格式为:

src = cv2.Laplacian( src, ddepth, [, ksize [, scale [ , delta [, borderType] ] ] ] )

  • dst :代表目标图像
  • src:代表原始图像
  • ddepth:代表目标图像的深度
  • ksize:代表用于计算的二阶导数的核的尺寸大小。该值必须是正奇数。
  • scale:代表计算Laplacian值的缩放比例因子,该参数是可选的。默认情况下,该值为1,表示不进行缩放。
  • delta:代表加到目标图像上的可选值,默认为0
  • borderType:代表边界处理方式

该函数分别对x,y方向进行二次求导,具体为:

image-20211102144147291

上式是当ksize的值大于1的时候的情况,当ksize的值为1时,Laplacian算子计算时采用的3×3的核如下:

image-20211102144234783

2. 实例:

示例:

import cv2

img = cv2.imread('../edge.bmp')
laplaction = cv2.Laplacian(img, cv2.CV_64F)
laplaction = cv2.convertScaleAbs(laplaction)
cv2.imshow('img', img)
cv2.imshow('laplaction', laplaction)
cv2.waitKey()
cv2.destroyAllWindows()

image-20211102144649552

six. 算子总结:

Sobel算子、Scharr算子、Laplacian算子都可以用作边缘检测,他们的核如下:

image-20211102145039173

Sobel算子和Scharr算子计算的都是一阶近似导数的值。通常情况线下,可以表示为:

  • Sobel算子 = |左 - 右|/ |下 - 上|
  • Scharr算子 = |左 - 右|/ |下 - 上|

左侧像素值减去右侧像素值的绝对值或者下方像素值减去上方像素值的绝对值。

Laplacian算子计算的是二阶近似导数的值,可以将它表示为:

Laplacian算子 = |左 - 右| + |左 - 右| + |下 - 上| + |下 - 上|

通过公式可以发现,Sobel算子和Scharr算子各计算了一次"|左 - 右|“和”|下 - 上|“的值,而Laplacian算子分别计算了两次”|左 - 右|“和”|下 - 上|"的值

猜你喜欢

转载自blog.csdn.net/weixin_57440207/article/details/122647002
今日推荐