13 图像梯度处理
13.1 图像梯度
边缘提取是图像处理中的一个重要任务,其目的是检测图像中灰度值发生显著变化的区域,这些区域通常对应于图像中的物体边界、纹理变化或深度变化等。边缘提取的原理可以分为以下几个关键步骤:
1. 边缘的定义和灰度变化
边缘可以定义为图像中灰度值发生显著变化的区域。这些变化通常是由于物体的边界、阴影、纹理或其他视觉特征引起的。边缘提取的目标是找到这些灰度变化的区域。
2. 梯度计算
边缘提取的核心是计算图像的梯度,梯度反映了灰度值的变化率。梯度通常使用梯度算子来计算,这些算子通过卷积操作与图像进行计算。
常见的梯度算子包括:
Sobel 算子:用于计算 x 和 y 方向的梯度,能够突出水平和垂直方向的边缘。
Prewitt 算子:类似于 Sobel 算子,但使用不同的权重。
Roberts 算子:用于计算对角线方向的梯度。
Scharr 算子:在某些情况下提供更精确的梯度计算结果。
13.2 垂直边缘提取
Sobel 算子 :Sobel 算子通过计算图像亮度的空间梯度来突出边缘,它使用两个 3x3 的卷积核,其中一个用于检测水平方向边缘(即垂直边缘),其卷积核为:
import cv2 as cv
import numpy as np
img = cv.imread('images/shudu.png')
dst_x = cv.Sobel(img, -1,dx= 1,dy= 0,ksize=3) #x方向的边缘检测
dst_y = cv.Sobel(img, -1, 0, 1,3) #y方向的边缘检测
dst = cv.Sobel(img, -1, 1, 1,3) #x和y方向的边缘检测
cv.imshow('1', dst_x)
cv.imshow('2', dst_y)
cv.imshow('3', dst)
cv.waitKey(0)
cv.destroyAllWindows()
ksize
表示 Sobel 算子的大小。它是一个奇数(1、3、5、7 等),较大的算子会在一定程度上增强图像的模糊效果,但也能检测到更粗糙的边缘特征。你设置为 3,是一个常用的较小的算子大小,在检测边缘时能在一定程度上平衡细节保留和计算效率。
在进行卷积运算时,该算子在图像的每个 3×3 区域内,将卷积核的每个元素与对应位置的像素值相乘后求和,得到该区域在水平方向的梯度近似值,即垂直边缘的信息。例如,对于一个 3×3 的像素块,使用上述卷积核计算得到的梯度值若较大,则说明该区域存在垂直边缘。
Laplacian算子:
拉普拉斯算子是一种基于二阶导数的边缘检测方法,通过计算图像中灰度值的二阶变化来定位边缘。
拉普拉斯算子边缘提取步骤
图像预处理
将图像转换为灰度图,因为边缘检测通常在灰度图像上进行。
对图像进行去噪处理,如高斯模糊,以减少噪声对边缘检测的影响。
应用拉普拉斯算子
使用拉普拉斯算子对图像进行卷积操作,计算每个像素点的二阶导数。
结果处理
由于拉普拉斯算子的结果可能包含负值,需要对结果进行取绝对值操作。
可以根据需要对结果进行阈值处理,以突出边缘信息。
# 读取图像并转换为灰度图
image = cv.imread('images/shudu.png', cv.IMREAD_GRAYSCALE)# 应用高斯模糊去噪
blurred = cv.GaussianBlur(image, (5, 5), 1.0)# 应用拉普拉斯算子,二阶导的典型代表,利用二阶导的思想,变化率的变化率
laplacian = cv.Laplacian(image, -1)# 显示结果
cv.imshow('Original Image', image)
cv.imshow('Laplacian Edge Detection', laplacian)
cv.waitKey(0)
cv.destroyAllWindows()
Prewitt 算子 :Prewitt 算子也采用两个 3x3 的卷积模板来检测水平和垂直方向的边缘,其垂直边缘检测的卷积核为:
与 Sobel 算子类似,通过在图像上滑动该卷积核进行运算,可得到图像中垂直边缘的响应。Prewitt 算子对图像中的噪声具有一定的平滑作用,对灰度和噪声较多的图像处理效果较好。
Roberts 算子 :Roberts 算子是一种用于图像边缘检测的算子,其通过计算图像上相邻像素点之间的差异来检测边缘。用于检测垂直边缘的卷积核为:
该算子计算简单快速,但对噪声非常敏感,且产生的边缘响应较弱,除非边缘非常锐利。
Scharr 算子 :Scharr 算子在计算梯度时具有更好的旋转对称性,并且在计算垂直边缘时,其卷积核为:
相较于 Sobel 算子,Scharr 算子在某些情况下可以提供更精确的梯度计算结果,能够更准确地提取垂直边缘。
基于梯度计算后的处理
13.5 代码
img = cv.imread('images/shudu.png')
kernel =np.array([[-1, 0, 1],[-2, 0, 2],[-1, 0, 1]],np.float32)
#垂直边缘提取
img1 = cv.filter2D(img, -1, kernel)
kernel2 = kernel.T
#水平边缘提取
img2 = cv.filter2D(img, -1, kernel2)
cv.imshow('1', img1)
cv.imshow('2', img2)
cv.waitKey(0)
cv.destroyAllWindows()
img2 = cv.filter2D(img, -1, kernel2)
函数用于对图像应用二维滤波操作
img
: 输入图像,这里是之前读取的图像数据。
-1
: 表示输出图像的深度与输入图像相同。在 OpenCV 中,-1
通常表示输出图像的深度与输入图像一致,这里输入图像是彩色图像(假设使用cv.imread
读取),因此输出图像的深度保持不变。
kernel2
: 卷积核,这是一个二维的浮点数数组,用于定义滤波器的权重。
举个直观粒子
import numpy as np
import cv2 as cvimg = np.array([[100, 102, 109, 110, 98, 20, 20, 18, 21],[110, 104, 105, 100, 104, 23, 20, 18, 20],[98, 100, 104, 104, 100, 17, 19, 22, 21],[110, 104, 105, 100, 104, 23, 20, 18, 20],[98, 100, 104, 104, 100, 17, 19, 22, 21],[100, 102, 109, 110, 98, 20, 19, 18, 21]
], dtype=np.float32)kernel =np.array([[-1, 0, 1],[-1, 0, 2],[-1, 0, 1]],np.float32)
img2 = cv.filter2D(img, -1, kernel)
print(img2)
输出
[[ 102. 108. 110. 85. -224. -226. 6. 22. 18.]
[ 104. 115. 108. 88. -231. -223. 16. 23. 18.]
[ 100. 100. 100. 94. -224. -230. 17. 23. 22.]
[ 104. 112. 104. 95. -228. -226. 23. 24. 18.]
[ 100. 114. 112. 84. -237. -225. 20. 25. 22.]
[ 102. 130. 126. 79. -244. -222. 26. 27. 18.]]
边缘检测的关键点
显著变化的值:边缘通常对应于绝对值较大的正数或负数,因为这些值表示灰度值的急剧变化。
正负变化的边界:边缘可能出现在正值和负值之间的过渡区域。
具体分析
在输出数组中,以下位置的值绝对值较大,可能是边缘的候选位置:
(0,4): -224
(0,5): -226
(1,4): -231
(1,5): -223
(2,4): -224
(2,5): -230
(3,4): -228
(3,5): -226
(4,4): -237
(4,5): -225
(5,4): -244
(5,5): -222
这些位置的值显著偏离周围的像素值,表明在这些区域灰度值发生了较大的变化,可能是垂直边缘所在的位置。
14 图像边缘检测
14.1 高斯滤波
就是去除噪点!
边缘检测本身属于锐化操作,对噪点比较敏感,所以需要进行平滑处理。这里使用的是一个5*5的高斯核对图像进行消除噪声。上一个实验中已经介绍了高斯滤波的具体过程,这里就不再过多叙述,只展示一下用到的5*5的高斯核:
14.2 计算图像的梯度与方向
在使用上述算子计算出梯度分量后,通常需要计算梯度的幅值和方向,以确定边缘的位置和强度。梯度的幅值表示边缘的强度,方向表示边缘的方向。对于垂直边缘,其梯度方向近似为 0 度或 180 度。计算公式如下:
首先使用sobel算子计算中心像素点的两个方向上的梯度Gx和Gy,然后就能够得到其具体的梯度值:
也可以使用G=|Gx+Gy|来代替。在OpenCV中,默认使用G=|Gx+Gy|来计算梯度值。
然后我们根据如下公式可以得到一个角度值
这个角度值其实是当前边缘的梯度的方向。通过这个公式我们就可以计算出图片中所有的像素点的梯度值与梯度方向,然后根据梯度方向获取边缘的方向。
a). 并且如果梯度方向不是0°、45°、90°、135°这种特定角度,那么就要用到插值算法来计算当前像素点在其方向上进行插值的结果了,然后进行比较并判断是否保留该像素点。这里使用的是单线性插值,通过A1和A2两个像素点获得dTmp1与dTmp2处的插值,然后与中心点C进行比较(非极大值抑制)。具体的插值算法请参考图像旋转实验。
b). 得到\theta的值之后,就可以对边缘方向进行分类,为了简化计算过程,一般将其归为四个方向:水平方向、垂直方向、45°方向、135°方向。并且:
当\theta值为-22.5°~22.5°,或-157.5°~157.5°,则认为边缘为水平边缘;
当法线方向为22.5°~67.5°,或-112.5°~-157.5°,则认为边缘为45°边缘;
当法线方向为67.5°~112.5°,或-67.5°~-112.5°,则认为边缘为垂直边缘;
当法线方向为112.5°~157.5°,或-22.5°~-67.5°,则认为边缘为135°边缘;
14.3 非极大值抑制
该步骤用于消除边缘检测中产生的伪边缘,保留真正的边缘。在确定梯度幅值和方向后,对于垂直边缘检测,将每个像素点与其在垂直方向上的两个邻点进行比较,若该像素点的梯度幅值不是这三个点中的最大值,则抑制该点的梯度幅值,将其设为 0,从而保留沿垂直方向的局部最大值,得到更精细的垂直边缘。
14.4 双阈值筛选
通过设定两个阈值,如高阈值和低阈值,对梯度幅值进行筛选。大于高阈值的像素点被确定为边缘点,介于低阈值和高阈值之间的像素点则根据与确定边缘点的连接关系来判断是否为边缘点,小于低阈值的像素点则被排除。这一步骤有助于连接断断续续的边缘,形成完整的垂直边缘轮廓。
14.5 API和使用
edges = cv2.Canny(image, threshold1, threshold2),即使读到的是彩色图也可以进行处理。
image
:输入的灰度/二值化图像数据。
threshold1
:低阈值,用于决定可能的边缘点。
threshold2
:高阈值,用于决定强边缘点。
image = cv.imread('images/shudu.png', cv.IMREAD_GRAYSCALE)
ret,img_binary = cv.threshold(image, 127, 255, cv.THRESH_BINARY)
dst_img = cv.Canny(img_binary, 30, 70) #边缘检测
cv.imshow('1', dst_img)
cv.waitKey(0)
cv.destroyAllWindows()
14.6 总结
边缘提取的原理主要包括以下几个步骤:
-
梯度计算:使用梯度算子计算图像的梯度。
-
梯度幅度和方向:计算梯度的幅度和方向,确定边缘的强度和方向。
-
非极大值抑制:保留沿边缘方向的局部最大值,消除伪边缘。
-
双阈值检测和边缘连接:通过设定两个阈值,筛选出真正的边缘点并连接形成完整的边缘轮廓。
这些步骤共同作用,实现了从图像中提取边缘的目标。通过这些方法,可以有效地检测出图像中的物体边界和其他重要特征。