原理
Soble边缘检测算法比较简,实际应用中效率比canny边缘检测效率要高,但是边缘不如Canny检测的准确,但是很多实际应用的场合,sobel边缘却是首选,尤其是对效率要求较高,而对细纹理不太关心的时候。Soble边缘检测通常带有方向性,可以只检测竖直边缘或垂直边缘或都检测。
算子使用两个3*3的矩阵(图1)算子使用两个3*3的矩阵(图1)去和原始图片作卷积,分别得到横向G(x)和纵向G(y)的梯度值,如果梯度值大于某一个阈值,则认为该点为边缘点。
Sobel卷积因子
sobel算子的可分离性
我们先来看一个3阶的Sobel边缘检测算子,如下所示:
显然,3*3的Sobel算子是可分离的,它是Sobel算子的标准形式,可以利用二项式展开式的系数构建窗口更大的Sobel算子,如5*5、7*7等,但是有一点必须要注意,窗口大小要为奇数。
该算子包含两组3x3的矩阵,分别为横向及纵向,将之与图像作平面卷积,即可分别得出横向及纵向的亮度差分近似值。如果以A代表原始图像,Gx及Gy分别代表经横向及纵向边缘检测的图像灰度值,其公式如下:
图像的每一个像素的横向及纵向梯度近似值可用以下的公式结合,来计算梯度的大小。
可用以下公式计算梯度方向。
在以上例子中,如果以上的角度Θ等于零,即代表图像该处拥有纵向边缘,左方较右方暗。
除此之外:由于基础核具有关于0,0,0所在的中轴正负对称,所以通过对基础核的旋转,和图像做卷积,可以获得灰度图的边缘图,同时消去旋转角方向+180°上的边缘,迭代多个方向即可消去多个方向的边缘,但是为消去的边缘会加倍。
基础核 0° (顺时针为正)旋转 45° 90° 135° 180° 225° 270°
当内核大小为 时, 以上Sobel内核可能产生比较明显的误差(毕竟,Sobel算子只是求取了导数的近似值)。 为解决这一问题,OpenCV提供了 Scharr 函数,但该函数仅作用于大小为3的内核。该函数的运算与Sobel函数一样快,但结果却更加精确,其内核为:
C++ API
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 );
@param src 为输入图像,填Mat类型即可。
@param dst 即目标图像,函数的输出参数,需要和源图片有一样的尺寸和类型。
@param ddepth 输出图像的深度。
若src.depth() = CV_8U, 取ddepth =-1/CV_16S/CV_32F/CV_64F
若src.depth() = CV_16U/CV_16S, 取ddepth =-1/CV_32F/CV_64F
若src.depth() = CV_32F, 取ddepth =-1/CV_32F/CV_64F
若src.depth() = CV_64F, 取ddepth = -1/CV_64F
@param dx x 方向上的差分阶数。
@param dy y方向上的差分阶数。
@param ksize 有默认值3,表示Sobel核的大小;必须取1,3,5或7。
@param scale 计算导数值时可选的缩放因子,默认值是1,表示默认情况下是没有应用缩放的。我们可以在文档中查阅getDerivKernels的相关介绍,来得到这个参数的更多信息。
@param delta 表示在结果存入目标图(第二个参数dst)之前可选的delta值,有默认值0。
@param borderType 边界模式,默认值为BORDER_DEFAULT。这个参数可以在官方文档中borderInterpolate处得到更详细的信息。
梯度计算
写了一段代码验证一下,首先创建一个5*7的图像,并且创建一个滤波器,使用filter2D进行卷积,dst为输出图像。
Mat A = (Mat_(5, 8) <<
20, 30, 15, 3, 16, 9, 56, 8,
21, 33, 66, 15, 3, 9, 75, 12,
11, 0, 3, 2, 6, 12, 35, 54,
15, 26, 35, 15, 12, 3, 33, 87,
21, 14, 12, 95, 13, 2, 15, 46);
Mat kernel = (Mat_(3, 3) << -1, 0, 1, -2, 0, 2, -1, 0, 1);
Mat dst;
//copyMakeBorder(A, B, 1, 1, 1, 1, BORDER_CONSTANT,0);
filter2D(A, dst, CV_16S, kernel);
convertScaleAbs(dst, dst);
dst = dst(Rect(1, 1, dst.cols - 2, dst.rows - 2));
waitKey();
测试代码
Mat src = imread("test.png", IMREAD_GRAYSCALE);
imshow("src", src);
//使用高斯滤波消除噪声
GaussianBlur(src, src, Size(3, 3), 0.1);
Mat sobelX;
Sobel(src, sobelX, CV_16S, 1, 0); //求X方向梯度
convertScaleAbs(q_matsobelX, q_matsobelX);
imshow("效果图窗口X", sobelX);
Mat sobelY;
Sobel(src, sobelY, CV_16S, 0, 1);//求Y方向梯度
convertScaleAbs(sobelY, sobelY);
imshow("效果图窗口Y", sobelY);
//合并梯度
Mat dst;
addWeighted(sobelX, 0.5, sobelY, 0.5, 0, dst);
imshow("合并效果图窗口", dst);
waitKey();
参考:
https://blog.csdn.net/yanmy2012/article/details/8110316
https://blog.csdn.net/weixin_41695564/article/details/79965676