Sobel算子是一种常用的边缘检测算法,是一种离散性差分算子,用差分近似代替梯度。对x求1阶差分用来检测竖直边缘,同样的对y求1阶差分用来检测水平边缘。
sobel算子对垂直和水平方向上的排列表达的较好,但对于其他角度的表达往往不够准确。
Sobel算子根据像素点上下、左右邻点灰度加权差,在边缘处达到极值这一现象检测边缘。对噪声具有平滑作用,提供较为精确的边缘方向信息,边缘定位精度不够高。当对精度要求不是很高时,是一种较为常用的边缘检测方法。
图像在某一点A的梯度,只需要计算其相邻两个像素点的差值即可得到X方向Y方向上的梯度;但为了更加合理的判断某一点的梯度大小,需要借助算子为这个以点A为中心的ksize×ksize区域(也即内核,一般是ksize取3或5或者其他奇数)添加权重,来扩大差异,增强边缘检测效果。离中心点越近的像素权重越大,越重要。
因为计算一点的梯度需要借助周边区域的多个点的灰度值,因而对图像中的噪点比较敏感,因此时常在调用sobel方法进行计算之前,需要先借助高斯滤波器或者其他滤波器对原图像进行平滑\模糊处理以降噪。所以完整的Sobel梯度计算过程是:
高斯滤波器对原图像平滑降噪GaussianBlur;转灰度图cvtColor;求X和Y方向的梯度Sobel。当内核为3*3时,横向和纵向方向上的卷积因子分别为: s o b e l x = [ − 1 0 1 − 2 0 2 − 1 0 1 ] sobel_x=\left[ \begin{matrix} -1 & 0 & 1 \\ -2 & 0 & 2 \\ -1 & 0 & 1 \end{matrix} \right] sobelx=⎣⎡−1−2−1000121⎦⎤ s o b e l y = [ − 1 2 − 1 0 0 0 1 2 1 ] sobel_y=\left[ \begin{matrix} -1 & 2 & -1 \\ 0 & 0 & 0 \\ 1 & 2 & 1 \end{matrix} \right] sobely=⎣⎡−101202−101⎦⎤ 假设原图像为 A = [ f ( x − 1 , y − 1 ) f ( x , y − 1 ) f ( x + 1 , y − 1 ) f ( x − 1 , y ) f ( x , y ) f ( x + 1 , y ) f ( x − 1 , y + 1 ) f ( x , y + 1 ) f ( x + 1 , y + 1 ) ] A=\left[ \begin{matrix} f(x-1,y-1) & f(x,y-1) & f(x+1,y-1) \\ f(x-1,y) & f(x,y) & f(x+1,y) \\ f(x-1,y+1) & f(x,y+1) & f(x+1,y+1) \end{matrix} \right] A=⎣⎡f(x−1,y−1)f(x−1,y)f(x−1,y+1)f(x,y−1)f(x,y)f(x,y+1)f(x+1,y−1)f(x+1,y)f(x+1,y+1)⎦⎤,分别做卷积可以得到: G x = s o b e l x ∗ A = [ − 1 0 1 − 2 0 2 − 1 0 1 ] ∗ A G_x=sobel_x*A=\left[ \begin{matrix} -1 & 0 & 1 \\ -2 & 0 & 2 \\ -1 & 0 & 1 \end{matrix} \right]*A Gx=sobelx∗A=⎣⎡−1−2−1000121⎦⎤∗A G y = s o b e l y ∗ A = [ − 1 2 − 1 0 0 0 1 2 1 ] ∗ A G_y=sobel_y*A=\left[ \begin{matrix} -1 & 2 & -1 \\ 0 & 0 & 0 \\ 1 & 2 & 1 \end{matrix} \right]*A Gy=sobely∗A=⎣⎡−101202−101⎦⎤∗A 注意:
这里的乘积是将两矩阵相应位置处的值相乘,然后把所有值相加,不是矩阵的乘法;opencv对图像坐标的定义为原点在左上角角位置,x轴是从原点水平向右正向增大,y轴是从原点竖直向下正向增大。一个像素点的坐标为(x,y),那么(x+1,y+1)在该点的右下角;sobel算子一般是,右减左,下减上,得到图像中某一点的梯度。由上述公式计算得到Gy和Gx后,可以计算得到G的值: 或者采用简便算法: 一个点的G的代表该点的梯度,如果大于某一设定范围则认为该点是边缘点。
如果为一幅图像有竖直边缘,该竖直边缘的水平两侧灰度将存在差异,同理如果存在水平边缘,该边缘的垂直两侧灰度将存在差异,sobel算子利用这种差异实现竖直边缘和水平边缘的检测。
下面左图表示用大于一定阈值的Gx来识别边缘点的结果,中图为用大于一定阈值的Gy来识别边缘点的结果,右图为用大于一定阈值的G来识别边缘点的结果。
在C++中,opencv的Sobel函数如下:
CV_EXPORTS_W void Sobel( InputArray src, OutputArray dst, int ddepth, int dx, int dy, int ksize=3, double scale=1, double delta=0, int borderType=BORDER_DEFAULT );在python中,通过cv2.Sobel来调用Sobel函数如下:
dst = cv2.Sobel(src, ddepth, dx, dy[, dst[, ksize[, scale[, delta[, borderType]]]]])参数具体含义:
src:输入图像,Mat类型。dst:目标图像,Mat类型。ddepth:int类型的ddepth,目标图像的深度,-1表示采用的是与原图像相同的深度。目标图像的深度必须大于等于原图像的深度。因为进行减法操作后不一定得到的是正值,也可能是负值,也可能大于255,但是uint8(即8位无符号数)不允许这些数的存在。因此要指定ddepth = cv2.CV_64F 以包容负值的存在(eg:cv2.Sobel(img,cv2.CV_64F,1,0,ksize=3)),此时进行图像的展示,会先将数组中的负值视为0。但是随后会调用其他方法(cv2.convertScaleAbs)以进行绝对值化的操作,转回原来的uint8形式。此时进行展示时,会使得负值转化为正值,得到我们完整的特征图。 支持如下src.depth()和ddepth的组合: (1) 若src.depth() = CV_8U, 取ddepth =-1/CV_16S/CV_32F/CV_64F; (2) 若src.depth() = CV_16U/CV_16S, 取ddepth =-1/CV_32F/CV_64F; (3) 若src.depth() = CV_32F, 取ddepth =-1/CV_32F/CV_64F; (4) 若src.depth() = CV_64F, 取ddepth = -1/CV_64F。dx:int类型,表示对x差分的阶数,0表示这个方向上没有求导,一般为0、1、2。dy:int类型,表示对x差分的阶数,0表示这个方向上没有求导,一般为0、1、2。ksize:int类型,表示Sobel核的大小,默认值3,必须取1,3,5或7。scale:double类型,计算导数值时可选的缩放因子,默认值是1。delta:double类型,表示在结果存入目标图(第二个参数dst)之前可选的delta值,默认值0。borderType:int类型,边界模式,默认值为BORDER_DEFAULT。具体基于python和C++的边缘检测代码参考sobel算子原理与实现。
