目录
背景介绍及应用
学习目标
原理
Sobel算子和Scharr算子
Laplacian 算子
代码示例
重要提醒
背景介绍及应用
图像的梯度是指图像中每个像素点的强度变化情况。计算图像的梯度可以帮助我们了解图像中物体的边界和纹理等信息。梯度在计算机视觉和图像处理领域有着广泛的应用,以下是一些常见的应用场景:
- 边缘检测:梯度可以帮助我们找到图像中物体之间的边界。通过计算图像的梯度,我们可以检测出物体的边缘,从而实现目标检测、物体识别和图像分割等任务。
- 图像增强:梯度可以帮助我们增强图像中的细节和纹理。通过计算图像的梯度,我们可以强调图像中的边缘和纹理,从而提高图像的清晰度和质量。
- 光照估计:梯度可以帮助我们估计图像中的光照条件。通过计算图像的梯度,我们可以分析图像中的亮度变化,从而了解图像的光照情况,进而进行光照校正和图像增强等操作。
- 特征提取:梯度可以帮助我们提取图像中的特征。通过计算图像的梯度,我们可以得到图像中的边缘和纹理等特征,从而用于图像分类、目标识别和图像检索等任务。
学习目标
- 掌握图像梯度、图像边界等重要术语
- 使用到的函数有cv2.Sobel()、cv2.Schar()、cv2.Laplacian() 等
原理
梯度简单来说就是求导。OpenCV 提供了三种不同的梯度滤波器,或者说是高通滤波器,分别是Sobel,Scharr 和Laplacian。Sobel,Scharr 其实就是求一阶或二阶导数。Scharr 是对Sobel(使用小的卷积核求解梯度角度梯度角度时的优化)。Laplacian 是求二阶导数。
Sobel算子和Scharr算子
Sobel算子和Scharr算子都是常用的图像梯度计算算子,用于边缘检测和图像增强等任务。它们可以计算图像在水平和垂直方向上的梯度,并且可以根据梯度的大小来判断图像中的边缘。
Sobel算子是一种离散的差分算子,可以用于计算图像的一阶导数。它分为水平和垂直两个方向的算子,分别表示为Sobel_x和Sobel_y。这两个算子可以通过卷积运算来计算图像在水平和垂直方向上的梯度。Sobel算子的计算公式如下:
Sobel_x = [[-1, 0, 1], [-2, 0, 2], [-1, 0, 1]]
Sobel_y = [[-1, -2, -1], [0, 0, 0], [1, 2, 1]]
Scharr算子是Sobel算子的一种改进版本,它使用了一种更加精确的差分算法,可以提供更好的梯度近似。Scharr算子也分为水平和垂直两个方向的算子,分别表示为Scharr_x和Scharr_y。Scharr算子的计算公式如下:
Scharr_x = [[-3, 0, 3], [-10, 0, 10], [-3, 0, 3]]
Scharr_y = [[-3, -10, -3], [0, 0, 0], [3, 10, 3]]
使用Sobel算子和Scharr算子可以通过卷积运算来计算图像的梯度。具体步骤如下:
- 将图像转换为灰度图像,如果图像已经是灰度图像则可以省略此步骤。
- 对图像进行平滑处理,可以使用高斯滤波器来进行平滑。
- 使用Sobel算子或Scharr算子进行卷积运算,计算图像在水平和垂直方向上的梯度。
- 根据梯度的大小来判断图像中的边缘,可以使用阈值来过滤掉低强度的边缘。
- 可选的,可以对图像进行非极大值抑制来细化边缘。
- 可选的,可以使用双阈值法来进一步筛选边缘,选择具有足够强度的边缘。
3x3 的Scharr 滤波器卷积核如下:
Laplacian 算子
Laplacian算子是一种常用的图像二阶导数算子,用于图像增强、边缘检测和特征提取等任务。它可以通过计算图像的二阶导数来检测图像中的边缘和纹理特征。
Laplacian算子的计算公式如下:
Laplacian = [[0, 1, 0], [1, -4, 1], [0, 1, 0]]
使用Laplacian算子可以通过卷积运算来计算图像的二阶导数。具体步骤如下:
- 将图像转换为灰度图像,如果图像已经是灰度图像则可以省略此步骤。
- 对图像进行平滑处理,可以使用高斯滤波器来进行平滑。
- 使用Laplacian算子进行卷积运算,计算图像的二阶导数。
- 根据导数的大小来判断图像中的边缘和纹理特征,可以使用阈值来过滤掉低强度的特征。
- 可选的,可以对图像进行非极大值抑制来细化边缘。
- 可选的,可以使用双阈值法来进一步筛选特征,选择具有足够强度的特征。
代码示例
下面的代码分别使用以上三种滤波器对同一幅图像操作。使用的卷积核是5x5 的。
# -*- coding: utf-8 -*-
import cv2
import numpy as np
from matplotlib import pyplot as plt
img=cv2.imread('dave.jpg',0)
#cv2.CV_64F 输出图像的深度(数据类型)可以使用-1, 与原图像保持一致np.uint8
laplacian=cv2.Laplacian(img,cv2.CV_64F)
# 参数1,0 为只在x 方向求一阶导数最大可以求2阶导数。
sobelx=cv2.Sobel(img,cv2.CV_64F,1,0,ksize=5)
# 参数0,1 为只在y 方向求一阶导数最大可以求2阶导数。
sobely=cv2.Sobel(img,cv2.CV_64F,0,1,ksize=5)
plt.subplot(2,2,1),plt.imshow(img,cmap = 'gray')
plt.title('Original'), plt.xticks([]), plt.yticks([])
plt.subplot(2,2,2),plt.imshow(laplacian,cmap = 'gray')
plt.title('Laplacian'), plt.xticks([]), plt.yticks([])
plt.subplot(2,2,3),plt.imshow(sobelx,cmap = 'gray')
plt.title('Sobel X'), plt.xticks([]), plt.yticks([])
plt.subplot(2,2,4),plt.imshow(sobely,cmap = 'gray')
plt.title('Sobel Y'), plt.xticks([]), plt.yticks([])
plt.show()
效果如下:
重要提醒
在查看上面这个例子的注释时不知到你有没有注意到,我们可以使用参数-1 来设定输出图像的深度(数据类型)与原图像保持一致,但是我们在代码中使用的却是cv2.CV_64F。这是为什么呢?想想一下一个从黑到白的边界的导数是正数,而一个从白到黑的边界点导数却是负数。如果原图像的深度是np.int8 时,所有的负值会被截断变成0,换句话说就就是把边界信息丢失掉所以如果这两种边界你都想检测到,最好的的办法就是将输出的数据类型设置的更高,比cv2.CV_16S,cv2.CV_64F 等。取绝对值然后再把它转回到cv2.CV_8U。下面的示例演示了输出图片的深度不同造成的不同效果。
import cv2
import numpy as np
from matplotlib import pyplot as plt
img = cv2.imread('boxs.png',0)
# Output dtype = cv2.CV_8U
sobelx8u = cv2.Sobel(img,cv2.CV_8U,1,0,ksize=5)
# 也可以将参数为-1
#sobelx8u = cv2.Sobel(img,-1,1,0,ksize=5)
# Output dtype = cv2.CV_64F. Then take its absolute and convert to cv2.CV_8U
sobelx64f = cv2.Sobel(img,cv2.CV_64F,1,0,ksize=5)
abs_sobel64f = np.absolute(sobelx64f)
sobel_8u = np.uint8(abs_sobel64f)
plt.subplot(1,3,1),plt.imshow(img,cmap = 'gray')
plt.title('Original'), plt.xticks([]), plt.yticks([])
plt.subplot(1,3,2),plt.imshow(sobelx8u,cmap = 'gray')
plt.title('Sobel CV_8U'), plt.xticks([]), plt.yticks([])
plt.subplot(1,3,3),plt.imshow(sobel_8u,cmap = 'gray')
plt.title('Sobel abs(CV_64F)'), plt.xticks([]), plt.yticks([])
plt.show()