图像梯度计算的是图像变化的速度。对于图像的边缘部分,其灰度值变化较大,梯度值也较大;相反,对于图像中比较平滑的部分,其灰度值变化较小,相应的梯度值也较小。一般情况下,图像梯度计算的是图像的边缘信息。
Canny 边缘检测是一种使用多级边缘检测算法检测边缘的方法。1986 年,John F. Canny 发表了著名的论文A Computational Approach to Edge Detection,在该论文中详述了如何进行边缘检测。OpenCV提供了函数cv2.Canny()实现Canny边缘检测。
Sobel 算子是一种离散的微分算子,该算子结合了高斯平滑和微分求导运算。该算子利用局部差分寻找边缘,计算所得的是一个梯度的近似值。
如果要计算像素点P5 的水平方向偏导数P5x,则需要利用Sobel 算子及P5邻域点,所使用的公式为:
P5x = (P3-P1) + 2·(P6-P4) + (P9-P7)
如果要计算像素点P5 的垂直方向偏导数P5y,则需要利用Sobel 算子及P5 邻域点,所使用的公式为:
P5y = (P7-P1) + 2·(P8-P2) + (P9-P3)
dst = cv2.Sobel( src, ddepth, dx, dy[,ksize[, scale[, delta[, borderType]]]] )
在实际操作中,计算梯度值可能会出现负数。通常处理的图像是8 位图类型,如果结果也是该类型,那么所有负数会自动截断为0,发生信息丢失。所以,为了避免信息丢失,我们在计算时使用更高的数据类型cv2.CV_64F,再通过取绝对值将其映射为cv2.CV_8U(8位图)类型。
在 OpenCV 中,使用函数cv2.convertScaleAbs()对参数取绝对值,该函数的语法格式为:
dst = cv2.convertScaleAbs( src [, alpha[, beta]] )
参数 dx和参数dy可以有多种形式的组合,主要包含:
dx= cv2.Sobel( src , ddepth , 1 , 0 )
dy= cv2.Sobel( src , ddepth , 0 , 1 )
dst=cv2.addWeighted( src1 , alpha , src2 , beta , gamma )
OpenCV 提供了Scharr 算子,该算子具有和Sobel 算子同样的速度,且精度更高。可以将Scharr 算子看作对Sobel 算子的改进。
dst = cv2.Scharr( src, ddepth, dx, dy[, scale[, delta[, borderType]]] )
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()
Laplacian(拉普拉斯)算子是一种二阶导数算子,其具有旋转不变性,可以满足不同方向的图像边缘锐化(边缘检测)的要求。通常情况下,其算子的系数之和需要为零。
计算像素点P5 的近似导数值,如下:
P5lap = (P2 + P4 + P6 + P8) - 4·P5
需要注意,在上述图像中,计算结果的值可能为正数,也可能为负数。所以,需要对计算结果取绝对值,以保证后续运算和显示都是正确的。
dst = cv2.Laplacian( src, ddepth[, ksize[, scale[, delta[, 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()
Canny 边缘检测分为如下几个步骤。
由于图像边缘非常容易受到噪声的干扰,因此为了避免检测到错误的边缘信息,通常需要对图像进行滤波以去除噪声。滤波的目的是平滑一些纹理较弱的非边缘区域,以便得到更准确的边缘。在实际处理过程中,通常采用高斯滤波去除图像中的噪声。
前面我们介绍了如何计算图像梯度的幅度。在这里,我们关注梯度的方向,梯度的方向与边缘的方向是垂直的,通常就近取值为水平(左、右)、垂直(上、下)、对角线(右上、左上、左下、右下)等8 个不同的方向。因此,在计算梯度时,我们会得到梯度的幅度和角度(代表梯度的方向)两个值。
在获得了梯度的幅度和方向后,遍历图像中的像素点,去除所有非边缘的点。在具体实现时,逐一遍历像素点,判断当前像素点是否是周围像素点中具有相同梯度方向的最大值,并根据判断结果决定是否抑制该点。通过以上描述可知,该步骤是边缘细化的过程。针对每一个像素点:
(1) 如果该点是正/负梯度方向上的局部最大值,则保留该点。
(2) 如果不是,则抑制该点(归零)。
经过比较判断可知,A 点具有最大的局部值,所以保留A 点(称为边缘),其余两点(B和C)被抑制(归零)。
经过上述处理后,对于同一个方向的若干个边缘点,基本上仅保留了一个,因此实现了边缘细化的目的。
设置两个阈值,其中一个为高阈值maxVal,另一个为低阈值minVal。根据当前边缘像素的梯度值(指的是梯度幅度,下同)与这两个阈值之间的关系,判断边缘的属性。具体步骤为:
(1)如果当前边缘像素的梯度值大于或等于maxVal,则将当前边缘像素标记为强边缘。
(2)如果当前边缘像素的梯度值介于maxVal 与minVal 之间,则将当前边缘像素标记为虚
边缘(需要保留)。
(3)如果当前边缘像素的梯度值小于或等于minVal,则抑制当前边缘像素。
在上述过程中,我们得到了虚边缘,需要对其做进一步处理。一般通过判断虚边缘与强边
缘是否连接,来确定虚边缘到底属于哪种情况。通常情况下,如果一个虚边缘:
(1)与强边缘连接,则将该边缘处理为边缘。
(2)与强边缘无连接,则该边缘为弱边缘,将其抑制。
edges = cv.Canny( image, threshold1, threshold2[, apertureSize[, L2gradient]])
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的值较小时,能够捕获更多的边缘信息。