Opencv学习笔记(九)边缘检测

大纲

  • 一、Sobel算子
    • 1.Sobel算子的导出
    • 2.Opencv中的sobel()函数
  • 二、Scharr滤波器
  • 三、Laplacian算子
    • 1.拉普拉斯算子的导出
    • 2.拉普拉斯算子的目的
    • 3.opencv中的laplacian()函数
  • 四、Canny边缘检测
    • 1.Canny边缘检测原理
    • 2.opencv中应用

一、Sobel算子

1.Sobel算子的导出

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}} xf(x,y)=Δx0limΔ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}} yf(x,y)=Δy0limΔ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) xf(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) yf(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(x1,y)等等,于是度量其梯度的方式就很多了。
Sobel算子就是其中一种,它分为水平方向和竖直方向上两个部分,结合了某点邻域范围内X方向上的差值和Y方向上的差值,同时考虑了距离和权重的关系。3×3的Sobel算子如下
Opencv学习笔记(九)边缘检测_第1张图片
可以看出此时其实就是分别在待求点X轴两端、Y轴两端计算像素差异,由于十字方向较角点方向距离更近,权重更大,如果不考虑这一因素全设为1的话就是prewitt算子了。查阅资料说Sobel算子结合了高斯平滑,没有弄懂这是什么意思,暂且认为是因为不止在十字方向进行了运算,而是整个邻域都介入到达一种平均的效果。至于其他尺寸的Sobel算子,也没能查到更为翔实的资料,stackoverflow、researchgate查询到的不尽相同,至于opencv源码中则能力不够没能看懂,暂且搁置。
stackoverflow查询结果:
Opencv学习笔记(九)边缘检测_第2张图片
researchgate结果:
Opencv学习笔记(九)边缘检测_第3张图片
由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

2.Opencv中的sobel()函数

由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滤波器

Scharr滤波器和Sobel算子可以说是区别不大,Scharr滤波器是3阶Sobel算子的进阶版,Scharr滤波器增大了临近像素的权重,对细节更为敏感,得到的边缘相应就更多。
Opencv学习笔记(九)边缘检测_第4张图片
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 );

三、Laplacian算子

1.拉普拉斯算子的导出

拉普拉斯算子也是用于检测梯度的,但它是二阶算子,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=x22f(x,y)+y22f(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(x1,y)+x(f(x,y)f(x1,y1)=f(x+1,y)f(x,y)f(x,y)+f(x1,y)+f(x,y+1)f(x,y)f(x,y)+f(x,y1)=f(x+1,y)+f(x1,y)+f(x,y+1)+f(x,y1)4f(x,y)
由此可以得到最常规的3×3拉普拉斯算子:
Opencv学习笔记(九)边缘检测_第5张图片
还有其他形式的拉普拉斯算子,大体上也是符合了二阶微分的特征。即用关于待测点某个方向(水平、竖直、对角)对称的两个值减去待测点。
Opencv学习笔记(九)边缘检测_第6张图片

2.拉普拉斯算子的目的

既然已经有了一阶微分算子像sobel算子、prewitt算子,那么为什么还要引入二阶微分算子,它和一阶微分算子又有什么不同呢?

  1. 二阶微分算子更加的突出了灰度值急剧变化的区域而不强调灰度值缓慢变化的区域,对边缘的定位能力更强,并且能够突出图像中的孤立线和孤立点(具体见参考文献);
  2. 二阶微分算子具有各项同性,即旋转不变性,初步理解是由于拉普拉斯算子是完全对称的,无论图像怎么旋转得到的梯度值都一样;

3.opencv中的laplacian()函数

通过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边缘检测

1.Canny边缘检测原理

Canny边缘检测分为四个步骤:

  1. 去噪
    边缘检测都需要用到一阶或者二阶微分算子来计算图像的梯度,而无论是一阶还是二阶算子都会放大图像中的孤立点,也就对噪声很敏感,所以我们要先对图片进行高斯平滑;
  2. 计算图像的梯度大小和方向
    在Canny边缘检测中,我们使用Sobel算子计算图像的梯度,得到X,Y方向梯度 G x , G y G_x,G_y Gx,Gy后使用下列公式得到梯度大小和方向:
    G = ∣ G x ∣ + ∣ G y ∣ G=|G_x|+|G_y| G=Gx+Gy;
    θ = a r c t a n ( G y G x ) \theta=arctan({{G_y}\over{G_x}}) θ=arctan(GxGy)
    由于图像中梯度方向也是离散的,只有0、45、90、135四种选择,所以我们通常用分区域的方法判断梯度方向,像是梯度方向为0的判断方式就是:
    f x ∗ t a n ( − 22.5 ° ) < f y < f x ∗ t a n ( 22.5 ° ) fx∗tan(−22.5°)fxtan(22.5°)<fy<fxtan(22.5°)
  3. 非极大值抑制
    非极大值指的是判断在8邻域范围内,待求点灰度值是否大于其梯度方向上两点的灰度值,若梯度方向为0,则要求 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+1,y) f(x,y)>f(x1,y)&&f(x,y)>f(x+1,y)
    这一步是为了排除掉非边缘像素,对粗边缘进行”瘦身“,保留较细的线条;
  4. 双阈值处理和连接性分析
    通常来说这一步和上一步是同时进行的,这一步要求我们排除掉由噪声等因素引起的假边缘,假边缘的特征就是梯度值不是很剧烈,所以我们通过双阈值的方法来排除假边缘:

如果梯度值大于高阈值,认为是强边缘,保留边缘;
如果梯度值小于低阈值,认为是若边缘,舍去边缘;
如果梯度值介于高阈值和低阈值之间,认为待观察,需要考察该点的连接性,即判断该点8邻域范围内有没有点被判定为了强边缘,有的话,则该点保留,否则舍去。

2.opencv中应用

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算子

你可能感兴趣的:(opencv,计算机视觉,opencv,边缘检测)