Sobel算子是一种用来计算图像梯度的算子,众所周知图像的梯度所衡量的就是图像像素值变化的情况,某点周围像素值变化越剧烈,该点的梯度值就越大。在数学里二维函数沿x,y方向梯度的定义是:
∂ f ( x , y ) ∂ x = lim Δ x → 0 f ( x + Δ x , y ) − f ( x , y ) Δ x {{\partial f(x,y)}\over{\partial x}}=\lim\limits_{\varDelta x\rightarrow0}{{f(x+\varDelta x,y)-f(x,y)}\over{\varDelta x}} ∂x∂f(x,y)=Δx→0limΔxf(x+Δx,y)−f(x,y)
∂ f ( x , y ) ∂ y = lim Δ y → 0 f ( x , y + Δ y ) − f ( x , y ) Δ y {{\partial f(x,y)}\over{\partial y}}=\lim\limits_{\varDelta y\rightarrow0}{{f(x,y+\varDelta y)-f(x,y)}\over{\varDelta y}} ∂y∂f(x,y)=Δy→0limΔyf(x,y+Δy)−f(x,y)
将其离散化之后就变成了:
∂ f ( x , y ) ∂ x = f ( x + 1 , y ) − f ( x , y ) {{\partial f(x,y)}\over{\partial x}}=f(x+1,y)-f(x,y) ∂x∂f(x,y)=f(x+1,y)−f(x,y)
∂ f ( x , y ) ∂ y = f ( x , y + 1 ) − f ( x , y ) {{\partial f(x,y)}\over{\partial y}}=f(x,y+1)-f(x,y) ∂y∂f(x,y)=f(x,y+1)−f(x,y)
于是可以看出衡量图像上某点梯度值的标准其实就是它与周围像素的差值,对于某一个点来说,它周围被多个点所环绕,像素差值并不只局限 f ( x + 1 , y ) − f ( x , y ) f(x+1,y)-f(x,y) f(x+1,y)−f(x,y)、 f ( x , y + 1 ) − f ( x , y ) f(x,y+1)-f(x,y) f(x,y+1)−f(x,y),还有 f ( x , y ) − f ( x − 1 , y ) f(x,y)-f(x-1,y) f(x,y)−f(x−1,y)等等,于是度量其梯度的方式就很多了。
Sobel算子就是其中一种,它分为水平方向和竖直方向上两个部分,结合了某点邻域范围内X方向上的差值和Y方向上的差值,同时考虑了距离和权重的关系。3×3的Sobel算子如下
可以看出此时其实就是分别在待求点X轴两端、Y轴两端计算像素差异,由于十字方向较角点方向距离更近,权重更大,如果不考虑这一因素全设为1的话就是prewitt算子了。查阅资料说Sobel算子结合了高斯平滑,没有弄懂这是什么意思,暂且认为是因为不止在十字方向进行了运算,而是整个邻域都介入到达一种平均的效果。至于其他尺寸的Sobel算子,也没能查到更为翔实的资料,stackoverflow、researchgate查询到的不尽相同,至于opencv源码中则能力不够没能看懂,暂且搁置。
stackoverflow查询结果:
researchgate结果:
由Sobel算子计算出X、Y方向上的梯度后,可由下列公式合成出总的梯度:
G = G x 2 + G y 2 G=\sqrt{G_x^2+G_y^2} G=Gx2+Gy2
当然由于根号计算不方便,我们也会用绝对值求和来近似:
G = ∣ G x ∣ + ∣ G y ∣ G=|G_x|+|G_y| G=∣Gx∣+∣Gy∣
由Sobel算子计算出图像的梯度,也就相当与近似获得了图像的边缘,因为边缘和梯度是相互垂直的,水平方向梯度结果就是竖直边缘,竖直方向梯度结果就是水平边缘,这样就可用于边缘检测了,opencv中已给出封装好的sobel函数如下:
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 );
第一个参数为输入图像,可单通道可多通道,多通道独立处理;
第二个参数为输出梯度值,与原图同样的大小和类型;
第三个参数为输出图像的深度,随着输入图像深度的变化应该取不同值,通常设为-1,
第四个参数为X方向上差分阶数,不知道这个高阶阶差分是如何计算的,已证实不是单纯的对一阶差分再差分,估计是改变卷积核;
第五个参数为Y方向上的差分阶数;
第六个参数为sobel算子大小,应该为奇数且不超过31,默认值为3,如果为负数的话就是Scharr滤波器了;
第七个参数为缩放系数,很单纯的对卷积核内的值扩大或者缩小,即得到梯度图灰度值×scale,相当于调整对比度,默认为1;
第八个元素为灰度附加值,即得到梯度图灰度值+delta,相当于调整亮度,默认为0;
第九个元素为边界填充类型;
使用示例如下:
int main()
{
Mat src = imread("E:\\material\\assassin.jpeg");
if (src.empty())
{
cout << "未找到该图片";
return -1;
}
else
cout << "图片的深度为" << src.depth();
Mat grad_x, grad_y, gray; //x,y方向梯度,灰度
Mat abs_grad_x, abs_grad_y; //绝对值并映射到8bit的x,y梯度
Mat dst; //合梯度
cvtColor(src, gray, COLOR_BGR2GRAY);
Sobel(gray, grad_x, -1, 1, 0, 3); //第三个参数为输出图像的深度,四五参数为,x,y方向上的差分阶数,六参数为内核大小
//剩余参数没找到确切概念,估计是放大系数及偏移系数
convertScaleAbs(grad_x, abs_grad_x); //实际作用为abs(grad_x*a+b),默认a=1,b==0;
imshow("x方向上sobel", abs_grad_x);
Sobel(gray, grad_y, -1, 0, 1, 3);
convertScaleAbs(grad_y, abs_grad_y);
imshow("y方向上sobel", abs_grad_y);
addWeighted(abs_grad_x, 0.5, abs_grad_y, 0.5, 0, dst);//直接x,y方向各一半,是方和根的近似
imshow("梯度", dst);
while ('q' != (char)waitKey(0));
}
Scharr滤波器和Sobel算子可以说是区别不大,Scharr滤波器是3阶Sobel算子的进阶版,Scharr滤波器增大了临近像素的权重,对细节更为敏感,得到的边缘相应就更多。
opencv中也包含了该函数,除了ksize默认为3无法选择之外,其他参数和Sobel算子一模一样:
void Scharr( InputArray src, OutputArray dst, int ddepth,
int dx, int dy, double scale = 1, double delta = 0,
int borderType = BORDER_DEFAULT );
拉普拉斯算子也是用于检测梯度的,但它是二阶算子,Sobel算子计算的是X,Y方向的一阶微分,用 G x ∣ + ∣ G y ∣ G_x|+|G_y| Gx∣+∣Gy∣来定义图像的梯度,而拉普拉斯算子求的是图像的二阶微分和,其数学公式如下:
∇ 2 G = ∂ 2 f ( x , y ) ∂ x 2 + ∂ 2 f ( x , y ) ∂ y 2 \nabla^2 G={{\partial^2 f(x,y)}\over{\partial x^2}}+{{\partial^2 f(x,y)}\over{\partial y^2}} ∇2G=∂x2∂2f(x,y)+∂y2∂2f(x,y)
离散化之后:
∇ 2 G = ∂ ( f ( x , y ) − f ( x − 1 , y ) ∂ x + ∂ ( f ( x , y ) − f ( x − 1 , y − 1 ) ∂ x = f ( x + 1 , y ) − f ( x , y ) − f ( x , y ) + f ( x − 1 , y ) + f ( x , y + 1 ) − f ( x , y ) − f ( x , y ) + f ( x , y − 1 ) = f ( x + 1 , y ) + f ( x − 1 , y ) + f ( x , y + 1 ) + f ( x , y − 1 ) − 4 f ( x , y ) \nabla^2 G={{\partial (f(x,y)-f(x-1,y)}\over{\partial x}}+{{\partial (f(x,y)-f(x-1,y-1)}\over{\partial x}}\\ =f(x+1,y)-f(x,y)-f(x,y)+f(x-1,y)+f(x,y+1)-f(x,y)-f(x,y)+f(x,y-1)\\ =f(x+1,y)+f(x-1,y)+f(x,y+1)+f(x,y-1)-4f(x,y) ∇2G=∂x∂(f(x,y)−f(x−1,y)+∂x∂(f(x,y)−f(x−1,y−1)=f(x+1,y)−f(x,y)−f(x,y)+f(x−1,y)+f(x,y+1)−f(x,y)−f(x,y)+f(x,y−1)=f(x+1,y)+f(x−1,y)+f(x,y+1)+f(x,y−1)−4f(x,y)
由此可以得到最常规的3×3拉普拉斯算子:
还有其他形式的拉普拉斯算子,大体上也是符合了二阶微分的特征。即用关于待测点某个方向(水平、竖直、对角)对称的两个值减去待测点。
既然已经有了一阶微分算子像sobel算子、prewitt算子,那么为什么还要引入二阶微分算子,它和一阶微分算子又有什么不同呢?
通过laplacian()函数我们可以获得图像的梯度值,也就获得了图像的边缘,同时用原图像减去(中心系数为负)或者加上(中心系数为正)梯度图就可以获得锐化之后的图像了,opencv中laplacian()函数如下
void Laplacian( InputArray src, OutputArray dst, int ddepth,
int ksize = 1, double scale = 1, double delta = 0,
int borderType = BORDER_DEFAULT );
第一个参数为输入图像,可单通道可多通道,多通道独立处理;
第二个参数为输出梯度值,与原图同样的大小和类型;
第三个参数为输出图像的深度,随着输入图像深度的变化应该取不同值,通常设为-1,
第四个元素为算子的大小,选填1或者3的话就是沿用之前的3×3拉普拉斯算子,否则选用其他奇数的话就是使用了X、Y方向的二阶sobel算子再相加;
第五个参数为缩放系数,很单纯的对卷积核内的值扩大或者缩小,即得到梯度图灰度值×scale,相当于调整对比度,默认为1;
第六个元素为灰度附加值,即得到梯度图灰度值+delta,相当于调整亮度,默认为0;
第七个元素为边界填充类型;
Canny边缘检测分为四个步骤:
如果梯度值大于高阈值,认为是强边缘,保留边缘;
如果梯度值小于低阈值,认为是若边缘,舍去边缘;
如果梯度值介于高阈值和低阈值之间,认为待观察,需要考察该点的连接性,即判断该点8邻域范围内有没有点被判定为了强边缘,有的话,则该点保留,否则舍去。
opencv函数原型:
void Canny( InputArray image, OutputArray edges,
double threshold1, double threshold2,
int apertureSize = 3, bool L2gradient = false );
第一个参数为输入图像,经实验无论是单通道还多通道都可以运行,但输出梯度图像均为单通道,并且多通道结果和灰度图结果或者是任意单一通道结果都不一样,不知道其原理,建议使用灰度图;
第二个参数为输出梯度图,和原图有同样的大小和类型;
第三、四个参数为两阈值,其中较大的作为高阈值,较小的作为低阈值,顺序无关,通常高低阈值比为3:1或者2:1;
第六个参数为计算梯度的Sobel算子大小,默认为3×3;
第七个参数为计算梯度的方法,默认为false,即不选用L2范数而是用L1范数用X,Y方向梯度绝对值之和作为梯度;如果选择True的话,则使用L2范数,用X,Y方向梯度值方和根作为梯度;
示例代码:
int main()
{
//载入原图
Mat srcImage = imread("E:\\material\\assassin.jpeg");
imshow("【原始图】Canny边缘检测", srcImage);
Mat dstImage, edge, grayImage;
//创建与src同类型和大小的矩阵(dst)
dstImage.create(srcImage.size(), srcImage.type());
//将原图转换为灰度图像
cvtColor(srcImage, grayImage, COLOR_BGR2GRAY);
//使用5*5内核降噪
GaussianBlur(grayImage, edge, Size(5, 5),0,0);
//运行Canny算子
Canny(edge, edge,150 ,100, 3);
imshow("边缘", edge);
dstImage = Scalar::all(0);
srcImage.copyTo(dstImage, edge); //以检测出的边缘图为掩模复制到dst,输出彩图的边缘
imshow("【效果图】", dstImage);
waitKey(0);
return 0;
}
参考文献
Canny边缘检测算法的原理与实现
图像梯度算子的本质
图像梯度的基本原理
Larger Sobel Keneral
5×5 Sobel
机器学习–拉普拉斯算子(Laplace Operator)学习整理
OpenCV学习笔记(九)——Sobel边缘检测
为什么 空间二阶导(拉普拉斯算子)这么重要?
图像一阶倒数和二阶导数的区别与联系
边缘检测二 二阶差分算子[拉普拉斯算子(Laplace)、高斯拉普拉斯算子(LOG)、Canny]
差分近似图像导数算子之Laplace算子