OpenCV学习十八:sobel 、scharr 边缘检测算子

版权声明:共享知识,欢迎转载 https://blog.csdn.net/kakiebu/article/details/79362576

    Sobel 算子是一个离散微分算子 (discrete differentiation operator)。 它结合了高斯平滑和微分求导,用来计算图像灰度函数的近似梯度。

    图像边缘,相素值会发生显著的变化了。表示这一改变的一个方法是使用 导数 。 梯度值的大变预示着图像中内容的显著变化。用更加形象的图像来解释,假设我们有一张一维图形。下图2中灰度值的”跃升”表示边缘的存在,图3中使用一阶微分求导我们可以更加清晰的看到边缘”跃升”的存在。

图1、lena.jpg                     图2、像素一维图形                       图3、一阶导数

        具体是采用卷积的计算方法实现的。假设被作用的图像为I ,在两个方向上求导:

水平变化求导:将 I 与一个奇数大小的内核 G_{x} 进行卷积。比如,当内核大小为3时, G_{x} 的计算结果为图4a:

垂直变化求导:将 I 与一个奇数大小的内核 G_{y} 进行卷积。比如,当内核大小为3时, G_{y} 的计算结果为图4b:

在图像的每一点,结合以上两个结果求出近似 梯度 ,如图4c:

图4a、                          图4b、                          图4c、

这里补充一个原帖主没有介绍的,最终的合成也可以是 G = |Gx| + |Gy| 形式的简略写法。

因为Sobel算子只是求取了导数的近似值,当内核大小为3时,以上Sobel内核可能产生比较明显的误差。为解决这一问题,OpenCV提供了 Scharr 函数,但该函数仅作用于大小为3的内核,该函数的运算与Sobel函数一样快,但结果却更加精确。

上面摘自:图像处理算法4——Sobel 边缘检测算子

##########################################################################################

函数说明:

void Sobel(  
InputArray src ,   – 输入图像。
OutputArray dst,   – 输出图像,与输入图像同样大小,拥有同样个数的通道。
int ddepth,   –输出图片深度
int dx,   – x方向导数运算参数。
int dy,   – y方向导数运算参数。
int ksize , – Sobel内核的大小,可以是:1,3,5,7。  注意:只可以是小于7 的奇数。
double scale, – 可选的缩放导数的比例常数。
double delta, – 可选的增量常数被叠加到导数中。
intborderType – 用于判断图像边界的模式。
)

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

当 ddepth为-1时, 输出图像将和输入图像有相同的深度。输入8位图像则会截取顶端的导数。

##########################################################################################

自己写的代码,比较简单。但注意推荐思路是 “ 模糊 + 灰度 + x/y方向分别函数处理 + 绝对值处理 + 合成 + 越界处理”,并且sobel等处理函数的输出图深度最好是高一阶。

#include <opencv2/opencv.hpp>  
#include <stdio.h>  
#include <stdlib.h>  

using namespace cv;  
using namespace std;  

char file[] = "1.jpg";
int main(int argc, char** argv)  
{  
	Mat img = imread(file, -1);
	pyrDown(img, img, Size(img.cols/2, img.rows/2));
	imshow("1",img);

	Mat Gimg, gray, xgrad1,ygrad1, xgrad2,ygrad2, out1,out2;

	//高斯模糊
	GaussianBlur(img, Gimg, Size(3,3), 0,0 );
	imshow("2",Gimg);

	//转灰度图像
	cvtColor(Gimg, gray, CV_BGR2GRAY);
	imshow("3",gray);imwrite("gray.jpg",gray);

	//查看数据类型,0表示 CV_8UC1
	printf("type : %d \n", gray.type());

	//sobel 计算
	Sobel(gray, xgrad1, CV_16S, 1, 0, 3);
	Sobel(gray, ygrad1, CV_16S, 0, 1, 3);

	//将负数改为正数,不然图片是全灰色的
	convertScaleAbs(xgrad1, xgrad1);
	convertScaleAbs(ygrad1, ygrad1);

	//显示图片
	imshow("xgrad1",xgrad1);imwrite("xgrad1.jpg",xgrad1);
	imshow("ygrad1",ygrad1);imwrite("ygrad1.jpg",ygrad1);

	//相加融合图片,方法1
	addWeighted(xgrad1, 1, ygrad1, 1, 0, out1);
	imshow("融合", out1);imwrite("out1.jpg",out1);

	//相加融合图片,方法2
	out2 = xgrad1 + ygrad1;		
	imshow("融合2", out2);

	//相加融合图片,方法3
	Mat out3 = Mat(xgrad1.size(), xgrad1.type());
	for (int i=0; i<xgrad1.rows; i++)
	{
		for (int j=0; j<xgrad1.cols; j++)
		{		
			out3.at<uchar>(i,j) = saturate_cast<uchar>( xgrad1.at<uchar>(i,j) + ygrad1.at<uchar>(i,j) );
		}
	}
	imshow("融合3", out3);

	//开方融合图片,方法1
	Mat out4 = Mat(xgrad1.size(), xgrad1.type());
	for (int i=0; i<xgrad1.rows; i++)
	{
		for (int j=0; j<xgrad1.cols; j++)
		{	
			double x = xgrad1.at<uchar>(i,j);
			double y = ygrad1.at<uchar>(i,j);
			double z = sqrt(x*x+y*y);
			out4.at<uchar>(i,j) = saturate_cast<uchar>( z );
						 
		}
	}
	imshow("融合4", out4);

	//scharr运算
	Scharr(gray, xgrad2, CV_16S, 1, 0);
	Scharr(gray, ygrad2, CV_16S, 0, 1);

	//将负数改为正数,不然图片是全灰色的
	convertScaleAbs(xgrad2, xgrad2);
	convertScaleAbs(ygrad2, ygrad2);

	//显示图片
	imshow("xgrad2",xgrad2);imwrite("xgrad2.jpg",xgrad2);
	imshow("ygrad2",ygrad2);imwrite("ygrad2.jpg",ygrad2);

	//相加融合图片
	Mat out5 = xgrad2 + ygrad2;
	imshow("融合5", out5);imwrite("out5.jpg",out5);


	waitKey();
	return 1;
}  

原图

gray

xgrad1

ygrad1

out1

xgrad2

ygrad2

out5

猜你喜欢

转载自blog.csdn.net/kakiebu/article/details/79362576