我的OpenCV学习笔记(18):使用Sobel变化和拉普拉斯变换检测图像边沿

Sobel变换和拉普拉斯变换都是高通滤波器。什么是高通滤波器呢?就是保留图像的高频分量(变化剧烈的部分),抑制图像的低频分量(变化缓慢的部分)。而图像变化剧烈的部分,往往反应的就是图像的边沿信息了。

在OpenCV中,调用sobel函数很简单:

	Mat image = imread("D:/picture/images/boldt.jpg",0);
	if(!image.data)
		return -1;
	imshow("源图像",image);
	
	Mat sobelX;
	//参数为:源图像,结果图像,图像深度,x方向阶数,y方向阶数,核的大小,尺度因子,增加的值
	Sobel(image,sobelX,CV_8U,1,0,3,0.4,128);
	imshow("X方向Sobel结果",sobelX);

	Mat sobelY;
	Sobel(image,sobelY,CV_8U,0,1,3,0.4,128);
	imshow("Y方向Sobel结果",sobelY);


注意到,这里对sobel的结果进行了一些尺度变换来更好的显示。

有一点需要特别注意:

由于是对X方向求导,sobelX保留了很多垂直方向的信息,所以垂直的轮廓“看起来更加清楚”;y方向同理。

按照数学推倒,应该是把两个方向的值平方以后相加在开方(2范数),得到梯度。而实际上,为了简化运算,我们直接把他们的绝对值相加(1范数)得出梯度:

	//合并结果
	Mat sobel;
	Sobel(image,sobelX,CV_32F,1,0);
	Sobel(image,sobelY,CV_32F,0,1);
	//计算1范数
	sobel= abs(sobelX)+abs(sobelY);
	double sobmin,sobmax;
	minMaxLoc(sobel,&sobmin,&sobmax);
	//转换为8比特,进行尺度变换
	Mat sobelImage;
	sobel.convertTo(sobelImage,CV_8U,-255./sobmax,255);
	imshow("结果",sobelImage);
	threshold(sobelImage, sobelImage, 190, 255, cv::THRESH_BINARY);
	imshow("最终结果",sobelImage);


注意:由于运算结果有正有负,所以这里没有使用CV_8U类型,而是CV_32F类型。

如果你想精确的计算梯度,不但有大小,还有方向,你可以这样做:

	Mat norm,dir;
	//计算L2范数和方向
	cartToPolar(sobelX,sobelY,norm,dir);


拉普拉斯变换是对x和y方向求2阶偏导数,然后加起来。

他当在图像边沿作用时(例如,从暗到亮)我们可以观察到灰度值的上升必然意味着从正曲度(强度升高)到负曲度(强度达到瓶颈)的变化。因此,拉普拉斯变换结果从正到负(或者相反)组成了一个图像边沿的很好的指示器。另一种方法表达这个事实是说,边沿出现在拉普拉斯变换的过零点处。

OpenCV中计算拉普拉斯变换也比较容易:

	//直接计算
	Mat laplace;
	//变换的结果*1+128
	Laplacian(image,laplace,CV_32F,7,1,128);
	imshow(" 直接使用的结果",laplace);
	//计算一个小窗口内的拉普拉斯变换的值
	for(int i = 0; i < 12;i++)
	{
		for(int j = 0; j < 12; j++)
		{
			//由于前面的变换中加了128,所以这里要减去128
			cout<(laplace.at(i+135,j+362))-128<<" ";
		}
		cout<

为了更好的说明拉普拉斯变换的作用,我们先定义1个类:

#if !defined LAPLACEZC
#define LAPLACEZC

#include 
#include 
#include 

using namespace cv;

class LaplacianZC
{
private:
	//源图像
	Mat img;
	//拉普拉斯变换的结果,32比特
	Mat laplace;
	//拉普拉斯核的大小
	int aperture;
public:
	//构造函数
	LaplacianZC():aperture(3){}
	//设置核的大小
	void setAperture(int a)
	{
		aperture = a;
	}

	//计算拉普拉斯变换
	Mat computeLaplacian(const Mat &image)
	{
		//计算拉普拉斯变换
		Laplacian(image,laplace,CV_32F,aperture,1,0);
		//保留副本
		img = image.clone();
		return laplace;
	}

	//获取变换后的图像
	Mat getLaplacianImage(double scale = -1.0)
	{
		double lapmin, lapmax;
		if(scale < 0)
		{
			//获取变换的最大值和最小值
			minMaxLoc(laplace,&lapmin,&lapmax);
			scale = 127/ std::max(-lapmin,lapmax);
		}
		Mat laplaceImage;
		laplace.convertTo(laplaceImage,CV_8U,scale,128);
		return laplaceImage;
	}

	//获得过零点的2值图像
	Mat getZeroCrossings(float threshold = 1.0)
	{
		//第二行第一个元素
		Mat_::const_iterator it		= laplace.begin()+laplace.step1();
		//最后一个元素
		Mat_::const_iterator itend	= laplace.end();
		//第一行第一个元素
		Mat_::const_iterator itup	= laplace.begin();

		//2值图像初始化为白色
		Mat binary(laplace.size(),CV_8U,Scalar(255));
		Mat_::iterator itout = binary.begin()+binary.step1();
		//使得门限无效
		threshold *= -1.0;
		for(;it != itend; ++it,++itup,++itout)
		{
			//如果相邻像个像素的积为负,这里的符号就发生了变化
			if(*it * *(it-1) < threshold)
				*itout = 0;
			else if(*it * *(itup) < threshold )
				*itout = 0;
		}
		return binary;
	}
};

#endif


我们选择一个小的区域重点观测:


先看看主函数:

	//使用LaplacianZC类计算拉普拉斯变换
	LaplacianZC laplacian;
	laplacian.setAperture(7);
	//变换后的结果有正有负,小于0的设为0,大于255的设为255,效果很差
	Mat flap = laplacian.computeLaplacian(image);
	double lpmin,lpmax;
	//获取变换后的最大值和最小值
	minMaxLoc(flap,&lpmin,&lpmax);
	cout<<"拉普拉斯变换后的范围为:"<<"["<(flap.at(i+135,j+362))/100 << " ";
		}
		cout<


打印消息中对每个点的拉普拉斯变换除以100仅仅因为计算的结果太大,如果正常显示,无法对齐。但这并不影响过零点(因为他只用考虑正负号)

而过零点是:

这恰恰就是那个小窗口中的塔楼的边沿!

你可能感兴趣的:(我的OpenCV学习笔记(18):使用Sobel变化和拉普拉斯变换检测图像边沿)