文章目录
一、认识什么是图像梯度和Sobel算子
图像的梯度是指图像亮度变化的空间导数,它描述了图像在不同方向上的强度变化。在图像处理和计算机视觉中,梯度通常用来检测边缘,因为边缘往往是亮度变化最显著的地方。图像梯度可以用多种方式来计算,常见的方法包括:一阶导数(Sobel算子)、二阶导数(Laplacian算子)、Prewitt算子、Canny算子。
Sobel算子是一种在图像处理和计算机视觉领域广泛使用的边缘检测算子。Sobel算子使用两个3x3的核(kernel)来分别计算图像在水平和垂直方向上的梯度。这两个核通常被称为Sobel核。水平方向(Gx)的Sobel核与垂直方向(Gy)的Sobel核的具体如下:
dst = cv2.Sobel(src, ddepth, dx, dy, ksize)
是 OpenCV 库中用于计算图像的 Sobel 导数的函数。这个函数实现了 Sobel 算子,用于边缘检测。其中:
src:输入图像,可以是灰度图像或彩色图像。
ddepth:输出图像的深度。通常使用 cv2.CV_64F 表示64位浮点数,这可以避免计算过程中的溢出。
dx:指定求导的阶数,用于x方向。如果为1,则计算一阶导数;如果为2,则计算二阶导数。
dy:指定求导的阶数,用于y方向。如果为1,则计算一阶导数;如果为2,则计算二阶导数。
ksize:Sobel 核的大小。默认值为3,但也可以使用5或7。
二、Sobel算子的具体使用
- x轴方向
在水平方向上,Sobel算子一般都是右边减去左边得到梯度值,并且如果求得的梯度值是负数的话,会进行截取为0的操作,也可以通过绝对值来进行修正参数。
拿下面这个圆来做例子,计算边缘的梯度(就例如红点位置的梯度):
bash
# 导入OpenCV库,用于图像处理
import cv2
# 导入matplotlib的pyplot模块,用于图像显示
import matplotlib.pyplot as plt
# 导入numpy库,用于数值计算
import numpy as np
# 使用cv2.imread()函数读取位于指定路径的图像文件
img = cv2.imread("E:\\XUEXI\\Python_learn\\tupian\\4.jpg")
# 定义一个函数cv_show,用于显示图像
def cv_show(name, img):
# 使用cv2.imshow()函数显示图像,名称为name
cv2.imshow(name, img)
# 使用cv2.waitKey(0)函数等待,直到用户按下任意键
cv2.waitKey(0)
# 使用cv2.destroyAllWindows()函数关闭所有OpenCV创建的窗口
cv2.destroyAllWindows()
# 使用cv2.Sobel()函数计算图像的Sobel算子
# cv2.CV_64F指定输出图像的深度为64位浮点数
# 第三个参数1表示对x方向求导,第四个参数0表示对y方向不求导
# ksize=3指定Sobel算子的大小为3x3
sobelx = cv2.Sobel(img, cv2.CV_64F, 1, 0, ksize=3)
# 调用cv_show函数显示Sobel算子处理后的图像
cv_show("sobelx", sobelx)
运行结果:
可以看到上面只是显示了半圆的值。这是因为右减去左的原因,当白减去黑为正数,所以显示出了边界值。当右边是黑,左边是白的时候,右减去左的结果为负数,截取为0,所以显示黑色。(具体参考下图)
通过sobelx = cv2.convertScaleAbs(sobelx)
可计算绝对值。cv2.convertScaleAbs:这个函数用于将输入数组的元素类型转换为 8 位无符号整数(uint8)。它首先将输入数组的每个元素乘以一个比例因子(默认为 1),然后加上一个偏移量(默认为 0)。最后,它计算结果的绝对值,并将其转换为 8 位无符号整数。
bash
import cv2
import matplotlib.pyplot as plt
import numpy as np
img = cv2.imread("E:\\XUEXI\\Python_learn\\tupian\\4.jpg")
def cv_show(name,img):
cv2.imshow(name, img)
cv2.waitKey(0)
cv2.destroyAllWindows()
sobelx = cv2.Sobel(img, cv2.CV_64F, 1, 0, ksize=3)
sobelx = cv2.convertScaleAbs(sobelx)
cv_show("sobelx", sobelx)
运行结果:
- y轴方向
在竖直方向上,是用下面减去上面来得到所求值。
bash
import cv2
import matplotlib.pyplot as plt
import numpy as np
img = cv2.imread("E:\\XUEXI\\Python_learn\\tupian\\4.jpg")
def cv_show(name,img):
cv2.imshow(name, img)
cv2.waitKey(0)
cv2.destroyAllWindows()
sobely = cv2.Sobel(img, cv2.CV_64F, 0, 1, ksize=3)
sobely = cv2.convertScaleAbs(sobely)
cv_show("sobely", sobely)
运行结果:
通常突出图片的边缘检测,一般使用分别计算x和y的sobel算子,再通过sobelxy = cv2.addWeighted(sobelx, 0.5, sobely, 0.5, 0)
cv2.addWeighted 函数将两个图像 sobelx 和 sobely 按照指定的权重进行加权融合。
bash
import cv2
import matplotlib.pyplot as plt
import numpy as np
img = cv2.imread("E:\\XUEXI\\Python_learn\\tupian\\4.jpg")
def cv_show(name,img):
cv2.imshow(name, img)
cv2.waitKey(0)
cv2.destroyAllWindows()
sobelx = cv2.Sobel(img, cv2.CV_64F, 1, 0, ksize=3)
sobelx = cv2.convertScaleAbs(sobelx)
sobely = cv2.Sobel(img, cv2.CV_64F, 0, 1, ksize=3)
sobely = cv2.convertScaleAbs(sobely)
sobelxy = cv2.addWeighted(sobelx, 0.5, sobely, 0.5, 0)
cv_show("sobelxy", sobelxy)
运行结果:
可以看到边边上缺失的地方有一条浅浅的线条连在一起。
一般不建议直接计算sobelxy = cv2.Sobel(img,cv2.CV_64F, 1, 1,ksize=3)
,这种方法确实可以得到图像的梯度信息,但在某些应用中,分别计算水平和垂直梯度然后再组合它们可能会更有利。例如分别计算梯度可以帮助你确定边缘的方向。这对于某些应用,如形状分析或纹理分类,可能是有用的。
直接计算:
bash
import cv2
import matplotlib.pyplot as plt
import numpy as np
img = cv2.imread("E:\\XUEXI\\Python_learn\\tupian\\4.jpg")
def cv_show(name,img):
cv2.imshow(name, img)
cv2.waitKey(0)
cv2.destroyAllWindows()
sobelxy = cv2.Sobel(img,cv2.CV_64F, 1, 1,ksize=3)
sobelxy = cv2.convertScaleAbs(sobelxy)
cv_show("sobelxy", sobelxy)
运行结果:
由上图和x,y分别计算再组合在一起的对比,也可以效果没有分开计算的好。
用下图的照片来更直观观察分开计算和合起来计算的区别:
合起来计算:
bash
import cv2
import matplotlib.pyplot as plt
import numpy as np
img = cv2.imread("E:\\XUEXI\\Python_learn\\tupian\\5.jpg")
def cv_show(name,img):
cv2.imshow(name, img)
cv2.waitKey(0)
cv2.destroyAllWindows()
sobelxy = cv2.Sobel(img,cv2.CV_64F, 1, 1,ksize=3)
sobelxy = cv2.convertScaleAbs(sobelxy)
cv_show("sobelxy", sobelxy)
运行结果:
分别计算x,y方向的sobel算子再组合在一起:
bash
import cv2
import matplotlib.pyplot as plt
import numpy as np
img = cv2.imread("E:\\XUEXI\\Python_learn\\tupian\\5.jpg")
def cv_show(name, img):
cv2.imshow(name, img)
cv2.waitKey(0)
cv2.destroyAllWindows()
sobelx = cv2.Sobel(img, cv2.CV_64F, 1, 0, ksize=3)
sobelx = cv2.convertScaleAbs(sobelx)
sobely = cv2.Sobel(img, cv2.CV_64F, 0, 1, ksize=3)
sobely = cv2.convertScaleAbs(sobely)
sobelxy = cv2.addWeighted(sobelx, 0.5, sobely, 0.5, 0)
cv_show("sobelxy", sobelxy)
运行结果:
通过两种方法对比,明显分开计算x,y的边缘检测效果要更加的好!
三、scharrx算子与lapkacian(拉普拉斯)算子
scharrx算子
Scharr算子是一种用于边缘检测的图像处理算子,它是Sobel算子的改进版本。Scharr算子在计算图像梯度时,提供了更高的精度,尤其是在使用3x3的核时。这使得Scharr算子在检测图像边缘时更为敏感,能够捕捉到更细小的边缘特征。Scharr算子的卷积核与Sobel算子不同,它在平滑部分给予了中心元素更大的权重,相当于使用了标准差更小的高斯函数,从而使得算子更加敏感。
Scharr算子能更加细致地描绘出细节,能够注意到更多细节。
Scharr算子的卷积核如下:
lapkacian(拉普拉斯)算子
拉普拉斯算子(Laplacian),在图像处理中通常称为Laplacian算子,是一种用于边缘检测的二阶导数算子。它用于增强图像中的快速强度变化,这些变化通常对应于图像中的边缘。拉普拉斯算子可以用于检测图像中的边缘和纹理。
拉普拉斯算子对于一些噪音点会更加敏感,该算子显示的细节相对较少,但是拉普拉斯算子一般都是配合其他工具一块使用的。
通过以下照片来更直观观察三种算子之间的差距:
bash
#不同算子的差异
import cv2
import matplotlib.pyplot as plt
import numpy as np
img = cv2.imread("E:\\XUEXI\\Python_learn\\tupian\\5.jpg")
def cv_show(name, img):
cv2.imshow(name, img)
cv2.waitKey(0)
cv2.destroyAllWindows()
sobelx = cv2.Sobel(img, cv2.CV_64F, 1, 0, ksize=3)
sobelx = cv2.convertScaleAbs(sobelx)
sobely = cv2.Sobel(img, cv2.CV_64F, 0, 1, ksize=3)
sobely = cv2.convertScaleAbs(sobely)
sobelxy = cv2.addWeighted(sobelx, 0.5, sobely, 0.5, 0)
scharrx = cv2.Scharr(img,cv2.CV_64F, 1, 0)
scharry = cv2.Scharr(img,cv2.CV_64F, 0, 1)
scharrx = cv2.convertScaleAbs(scharrx)
scharry = cv2.convertScaleAbs(scharry)
scharrxy = cv2.addWeighted(scharrx , 0.5,scharry,0.5,0)
laplacian = cv2.Laplacian(img,cv2.CV_64F)
laplacian = cv2.convertScaleAbs(laplacian)
res = np.hstack((sobelxy,scharrxy,laplacian))
cv_show("res", res)
运行结果:
其中第一幅是sobel算子,第二幅是scharrx算子,第三幅是lapkacian算子。
可以看到scharrx算子能够更加细腻的展示出照片的细节,是sobel算子的升级版。而lapkacian算子描绘的细节就更加的少,一般lapkacian算子是搭配其他工具一块使用的。