图像梯度计算的是图像变化的速度。对于图像的边缘部分,其灰度值变化较大,梯度值也较大;相反,对于图像中比较平滑的部分,其灰度值变化较小,相应的梯度值也较小。一般情况下,图像梯度计算的是图像的边缘信息。
Canny 边缘检测是一种使用多级边缘检测算法检测边缘的方法。1986 年,John F. Canny 发表了著名的论文A Computational Approach to Edge Detection,在该论文中详述了如何进行边缘检测。OpenCV提供了函数cv2.Canny()实现Canny边缘检测。
《OpenCV轻松入门:面向Python》学习笔记(七)
一. 图像梯度
1.1 Sobel理论基础
Sobel 算子是一种离散的微分算子,该算子结合了高斯平滑和微分求导运算。该算子利用局部差分寻找边缘,计算所得的是一个梯度的近似值。
1.1.1 水平方向偏导数近似值计算
如果要计算像素点P5 的水平方向偏导数P5x,则需要利用Sobel 算子及P5邻域点,所使用的公式为:
P5x = (P3-P1) + 2·(P6-P4) + (P9-P7)
1.1.2 垂直方向偏导数近似值计算
如果要计算像素点P5 的垂直方向偏导数P5y,则需要利用Sobel 算子及P5 邻域点,所使用的公式为:
P5y = (P7-P1) + 2·(P8-P2) + (P9-P3)
1.2 Sobel算子及函数应用
dst = cv2.Sobel( src, ddepth, dx, dy[,ksize[, scale[, delta[, borderType]]]] )
- dst 代表目标图像。
- src 代表原始图像。
- ddepth 代表输出图像的深度。其具体对应关系如表所示。
- dx 代表x方向上的求导阶数。
- dy 代表y方向上的求导阶数。
- ksize 代表Sobel核的大小。该值为-1时,则会使用Scharr算子进行运算。
- scale 代表计算导数值时所采用的缩放因子,默认情况下该值是1,是没有缩放的。
- delta 代表加在目标图像dst 的值,该值是可选的,默认为0。
- borderType 代表边界样式。
1.2.1 参数ddepth
在实际操作中,计算梯度值可能会出现负数。通常处理的图像是8 位图类型,如果结果也是该类型,那么所有负数会自动截断为0,发生信息丢失。所以,为了避免信息丢失,我们在计算时使用更高的数据类型cv2.CV_64F,再通过取绝对值将其映射为cv2.CV_8U(8位图)类型。
在 OpenCV 中,使用函数cv2.convertScaleAbs()对参数取绝对值,该函数的语法格式为:
dst = cv2.convertScaleAbs( src [, alpha[, beta]] )
- dst 代表处理结果。
- src 代表原始图像。
- alpha 代表调节系数,该值是可选值,默认为1。
- beta 代表调节亮度值,该值是默认值,默认为0。
1.2.2 方向
参数 dx和参数dy可以有多种形式的组合,主要包含:
-
计算x方向边缘(梯度):dx=1, dy=0。
-
计算y方向边缘(梯度):dx=0, dy=1。
-
参数dx与参数dy的值均为1:dx=1, dy=1。
-
计算x方向和y方向的边缘叠加
dx= cv2.Sobel( src , ddepth , 1 , 0 )
dy= cv2.Sobel( src , ddepth , 0 , 1 )
dst=cv2.addWeighted( src1 , alpha , src2 , beta , gamma )
1.3 Scharr 算子及函数使用
OpenCV 提供了Scharr 算子,该算子具有和Sobel 算子同样的速度,且精度更高。可以将Scharr 算子看作对Sobel 算子的改进。
dst = cv2.Scharr( src, ddepth, dx, dy[, scale[, delta[, borderType]]] )
- 参数及使用方法与Sobel算子相同。
- 需要注意的是,参数dx和dy的值不能都为1。
1.4 Sobel算子和Scharr算子的比较
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()
1.5 Laplacian算子及函数使用
Laplacian(拉普拉斯)算子是一种二阶导数算子,其具有旋转不变性,可以满足不同方向的图像边缘锐化(边缘检测)的要求。通常情况下,其算子的系数之和需要为零。
计算像素点P5 的近似导数值,如下:
P5lap = (P2 + P4 + P6 + P8) - 4·P5
需要注意,在上述图像中,计算结果的值可能为正数,也可能为负数。所以,需要对计算结果取绝对值,以保证后续运算和显示都是正确的。
dst = cv2.Laplacian( src, ddepth[, ksize[, scale[, delta[, borderType]]]] )
- dst 代表目标图像。
- src 代表原始图像。
- ddepth 代表目标图像的深度。
- ksize 代表用于计算二阶导数的核尺寸大小。该值必须是正的奇数。
- scale 代表计算Laplacian值的缩放比例因子,该参数是可选的。默认情况下,该值为1,表示不进行缩放。
- delta 代表加到目标图像上的可选值,默认为0。
- borderType 代表边界样式。
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()
二. 边缘检测
2.1 边缘检测基础
Canny 边缘检测分为如下几个步骤。
- 去噪。噪声会影响边缘检测的准确性,因此首先要将噪声过滤掉。
由于图像边缘非常容易受到噪声的干扰,因此为了避免检测到错误的边缘信息,通常需要对图像进行滤波以去除噪声。滤波的目的是平滑一些纹理较弱的非边缘区域,以便得到更准确的边缘。在实际处理过程中,通常采用高斯滤波去除图像中的噪声。
- 计算梯度的幅度与方向。
前面我们介绍了如何计算图像梯度的幅度。在这里,我们关注梯度的方向,梯度的方向与边缘的方向是垂直的,通常就近取值为水平(左、右)、垂直(上、下)、对角线(右上、左上、左下、右下)等8 个不同的方向。因此,在计算梯度时,我们会得到梯度的幅度和角度(代表梯度的方向)两个值。
- 非极大值抑制,即适当地让边缘“变瘦”。
在获得了梯度的幅度和方向后,遍历图像中的像素点,去除所有非边缘的点。在具体实现时,逐一遍历像素点,判断当前像素点是否是周围像素点中具有相同梯度方向的最大值,并根据判断结果决定是否抑制该点。通过以上描述可知,该步骤是边缘细化的过程。针对每一个像素点:
(1) 如果该点是正/负梯度方向上的局部最大值,则保留该点。
(2) 如果不是,则抑制该点(归零)。
经过比较判断可知,A 点具有最大的局部值,所以保留A 点(称为边缘),其余两点(B和C)被抑制(归零)。
经过上述处理后,对于同一个方向的若干个边缘点,基本上仅保留了一个,因此实现了边缘细化的目的。
- 确定边缘。使用双阈值算法确定最终的边缘信息。
设置两个阈值,其中一个为高阈值maxVal,另一个为低阈值minVal。根据当前边缘像素的梯度值(指的是梯度幅度,下同)与这两个阈值之间的关系,判断边缘的属性。具体步骤为:
(1)如果当前边缘像素的梯度值大于或等于maxVal,则将当前边缘像素标记为强边缘。
(2)如果当前边缘像素的梯度值介于maxVal 与minVal 之间,则将当前边缘像素标记为虚
边缘(需要保留)。
(3)如果当前边缘像素的梯度值小于或等于minVal,则抑制当前边缘像素。
在上述过程中,我们得到了虚边缘,需要对其做进一步处理。一般通过判断虚边缘与强边
缘是否连接,来确定虚边缘到底属于哪种情况。通常情况下,如果一个虚边缘:
(1)与强边缘连接,则将该边缘处理为边缘。
(2)与强边缘无连接,则该边缘为弱边缘,将其抑制。
2.2 Canny函数及使用
edges = cv.Canny( image, threshold1, threshold2[, apertureSize[, L2gradient]])
- edges 为计算得到的边缘图像。
- image 为8位输入图像。
- threshold1 表示处理过程中的第一个阈值。
- threshold2 表示处理过程中的第二个阈值。
- apertureSize 表示Sobel算子的孔径大小。
- L2gradient 为计算图像梯度幅度(gradient magnitude)的标识。其默认值为False。如果为True,则使用更精确的L2 范数进行计算(即两个方向的导数的平方和再开方),否则使用L1 范数(直接将两个方向导数的绝对值相加)。
import cv2
o=cv2.imread("lena.bmp",cv2.IMREAD_GRAYSCALE)
r1=cv2.Canny(o,128,200)
r2=cv2.Canny(o,32,128)
cv2.imshow("original",o)
cv2.imshow("result1",r1)
cv2.imshow("result2",r2)
cv2.waitKey()
cv2.destroyAllWindows()
从程序运行结果可知,当函数cv2.Canny()的参数threshold1和threshold2的值较小时,能够捕获更多的边缘信息。