OpenCV4学习笔记(17)——常用导数算子

本次笔记要整理记录一些常用的导数算子,包括一阶导数算子和二阶导数算子等,这些导数算子种类比较繁杂,统一记录起来会比较容易回顾。

- 常见一阶导数算子
对于图像求一阶导数,就是求图像灰度曲线的导数,所以在灰度曲线中变化较大的区域,在导数曲线中体现出绝对值比较大的值,而灰度曲线中变化平坦的区域,在导数曲线中体现出接近零的值,通过求一阶导数的处理,能突出图像中对象的边缘,并且还具有方向性。
常用的一阶导数算子主要有:Robert算子、prewitt算子、sobel算子。
下面分别给出这三个算子的代码实现:

  1. Robert算子
	Mat robertx_image;
	Mat robert_x = (Mat_<int>(2, 2) << 1, 0, 0, -1);				//robert_x
	filter2D(image, robertx_image, CV_32F, robert_x);						
	convertScaleAbs(robertx_image, robertx_image);								//将输入数组均转化为uchar类型

	Mat roberty_image;
	Mat robert_y = (Mat_<int>(2, 2) << 0, -1, 1, 0);			//robert_y
	filter2D(image, roberty_image, CV_32F, robert_y);
	convertScaleAbs(roberty_image, roberty_image);

	Mat robert_image;
	add(robertx_image, roberty_image, robert_image, Mat(), CV_16S);
	convertScaleAbs(robert_image, robert_image);
	imshow("robert_image", robert_image);

其中,定义了robert算子的x方向和y方向,分别是Mat robert_x = (Mat_(2, 2) << 1, 0, 0, -1)Mat robert_y = (Mat_(2, 2) << 0, -1, 1, 0),然后通过filter2D这个API对图像和算子分别卷积,要注意图像求梯度后存在浮点数,需要使用浮点(如CV_32F)类型,最后将x和y方向的两个结果进行相加,并且转换到CV_8UC类型,就可以显示出输出图像了。
效果如下(分别是x、y方向的梯度,以及完整图像梯度):


2.prewitt算子

	//prewitt算子
	Mat prewitt_x_image, prewitt_y_image, prewitt_image;
	Mat prewitt_x = (Mat_<int>(3, 3) << -1, 0, 1, -1, 0, 1, -1, 0, 1);
	filter2D(image, prewitt_x_image, CV_32F, prewitt_x);
	convertScaleAbs(prewitt_x_image, prewitt_x_image);

	Mat prewitt_y = (Mat_<int>(3, 3) << -1, -1, -1, 0, 0, 0, 1, 1, 1);
	filter2D(image, prewitt_y_image, CV_32F, prewitt_y);
	convertScaleAbs(prewitt_y_image, prewitt_y_image);

	add(prewitt_x_image, prewitt_y_image, prewitt_image, Mat(), CV_16S);
	convertScaleAbs(prewitt_image, prewitt_image);
	imshow("prewitt_image", prewitt_image);

其中同样分别定义了prewitt算子的x、y方向:Mat prewitt_x = (Mat_(3, 3) << -1, 0, 1, -1, 0, 1, -1, 0, 1)Mat prewitt_y = (Mat_(3, 3) << -1, -1, -1, 0, 0, 0, 1, 1, 1),分别与图像计算卷积后再进行相加,转换数据类型后显示输出图像。
效果如下:



3.sobel算子

	Mat x_grad, y_grad, image_grad;
	//Sobel算子求梯度后的值为浮点型且超过[0,  255]
	Sobel(image, x_grad, CV_32F, 1, 0, 3, 1, 0, 4);
	Sobel(image, y_grad, CV_32F, 0, 1, 3, 1, 0, 4);

	convertScaleAbs(x_grad, x_grad);			//转化至CV_8UC
	imshow("x_grad", x_grad);
	convertScaleAbs(y_grad, y_grad);			//转化至CV_8UC
	imshow("y_grad", y_grad);
	
	add(x_grad, y_grad, image_grad, Mat(), CV_16S);		//两个[0,255]范围的值相加,输出深度为CV_16S(16位有符号整型)
	convertScaleAbs(image_grad, image_grad);
	imshow("image_grad", image_grad);

Sobel算子的优势是,相比prewitt算子能更好的抑制噪声。而且不需要我们自定义卷积核,而是OpenCV提供了一个专门的APISobel(image, x_grad, CV_32F, 1, 0, 3, 1, 0, 4)
其第一个参数是要计算梯度的输入图像;
第二个参数是处理完的输出图像;
第三个参数是输出图像的深度;
第四个参数dx,为1时表示计算x方向梯度,为0表示不计算x方向梯度;
第五个参数dy,为1时表示计算y方向梯度,为0表示不计算y方向梯度;
第六个参数ksize是计算梯度的卷积核大小;
后面几个参数分别是缩放量(输出图像相比输入图像的缩放)、增加常量(输出图像相比输入图像增加的常量)、边缘填充方式,如果没有特殊需求都选用默认值即可。
效果如下:

- 常见二阶导数算子
二阶导数算子是对图像灰度曲线的导数求导,也就是在一阶导数的基础上再次求导,对灰度值的剧烈变化敏感,能突出图像的纹理结构,并且不具有方向性。
常见的二阶导数算子主要有拉普拉斯算子和canny边缘检测算子。

1.拉普拉斯算子

	//
	//拉普拉斯算子
	Mat gaussianBlur_image, laplacian_image_4, laplacian_image_8;
	GaussianBlur(image, gaussianBlur_image, Size(), 1, 1);

	Laplacian(gaussianBlur_image, laplacian_image_4, CV_32F, 1, 1, 127);			
	convertScaleAbs(laplacian_image_4, laplacian_image_4);
	Laplacian(gaussianBlur_image, laplacian_image_8, CV_32F, 3, 1, 127);			
	convertScaleAbs(laplacian_image_8, laplacian_image_8);

	imshow("laplacian_image_4", laplacian_image_4);
	imshow("laplacian_image_8", laplacian_image_8);

拉普拉斯算子对于噪声非常敏感,所以需要先对原图进行高斯模糊后再提取梯度,在某些场合下会将高斯模糊和拉普拉斯边缘提取合并起来,即为LOG算子。
OpenCV中提供一个API实现拉普拉斯算子的运算:Laplacian(gaussianBlur_image, laplacian_image_4, CV_32F, 1, 1, 127)
第一个参数是经过高斯模糊后的图像;
第二个参数是输出图像;
第三个参数是输出图像的深度;
第四个参数是ksize,当ksize = 1 使用4邻域的拉普拉斯算子;当ksize > 1 使用8邻域的拉普拉斯算子;
第五个参数是缩放值;
第六个参数是输出图像加上的常量,因为二阶导数求得值很小,为了便于观察可以加上一个delta值来提高显示亮度。
效果如下:

左边是四邻域的拉普拉斯算子,右边是八邻域的拉普拉斯算子提取,可见使用八邻域的拉普拉斯算子的纹理提取效果比四邻域要好得多,能看得到更多的细节,轮廓也更加清晰。

2.Canny边缘检测
Canny边缘检测的主要步骤:
1、 高斯模糊 – 抑制噪声;
2、梯度提取得到候选边缘;
3、角度计算与非最大信号抑制;
4、高低阈值链接、获取完整边缘;
5. 输出边缘;
其中,第一、二步骤可以采用LOG算子实现,或使用高斯模糊加Sobel算子实现;第三步计算每个像素点的边缘幅值和角度,并进行非最大信号抑制,主要目的是实现边缘细化,使得边缘像素进一步减少(个人对非最大信号抑制的理解:对已经提取出的离散的边缘像素,与其邻域内的像素进行比较,若不是邻域的最大值则将其从边缘像素中剔除,若为最大值则保留为边缘像素);第四步是在边缘像素点集中进行筛选,将小于低阈值的像素点除去,将大于高阈值的像素点保留,对介于高低阈值区间的像素点则进行判断:该像素点是否与位于高阈值及以上的像素点相连通,若有连通域则在边缘像素点集中保留该像素点,若没有连通域则剔除该像素点;最后第五步以二值图像输出边缘。
当然了在OpenCV中已经封装好了这些功能,不需要我们自己实现,我们可以调用相关API即可,下面是代码:

Mat image_canny;	
	Canny(image, image_canny, 200, 500, 5, false);
	imshow("image_canny", image_canny);
	Mat image_and, image_add;
	bitwise_and(image, image, image_and, image_canny);		
	imshow("image_and", image_and);
	addWeighted(image, 1, image_and, 0.2, 0, image_add, CV_16S);	
	convertScaleAbs(image_add, image_add);
	imshow("image_add", image_add);			

主要APICanny(image, image_canny, 200, 500, 5, false)
第一个参数是输入图像;
第二个参数是输出图像;
第三和第四个参数:参数threshold1表示低阈值,参数threshold2表示高阈值(上限尽量不超过500);经验值是设置threshold1:threshold2 = 1:2到1:3之间效果较好
第五个参数apertureSize:进行梯度运算(Sobel算子)的窗口大小,默认值为3
第六个参数L2gradient:布尔类型,表示是否使用L2计算梯度,即使用矢量和计算;默认为false,即默认使用平方和计算梯度。
上述代码中,将canny边缘检测提取出的边缘与原图像进行求与(and)运算,得到有色彩的边缘,再将边缘和原图像按权重相加,最后的输出结果增强了原图像细节纹理,但视觉感受上存在撕裂感。效果如下:

最左边是进行canny提取后的边缘图像,中间是和原图像求与(and)运算后的彩色边缘,最右边是彩色和原图像按权重相加的结果,可见虽然纹理部分变得明显了,但是却显得很不自然。

本次关于图像导数算子的整理就到此结束啦,谢谢~

PS:本人的注释比较杂,既有自己的心得体会也有网上查阅资料时摘抄下的知识内容,所以如有雷同,纯属我向前辈学习的致敬,如果有前辈觉得我的笔记内容侵犯了您的知识产权,请和我联系,我会将涉及到的博文内容删除,谢谢!

你可能感兴趣的:(学习笔记)