【OpenCV图像处理】二十二、图像边缘检测(上)

→图像边缘检测的目的是检测邻域内灰度明显变化的像素,常用一阶差分和二阶差分来进行边缘检测

→数字图像中的边缘是由邻域内灰度值明显变化的像素构成,边缘检测主要是图像灰度的度量检测和定位

→图像的边缘有方向幅值两个属性,沿边缘方向像素灰度值变化平缓或不发生变化,而垂直于边缘方向像素灰度值变化剧烈

→需要理解的是,边缘是灰度值变化的产物,可以利用差分来检测这种不连续性,边缘检测方法大致可以分为两类:

(1)基于一阶差分的方法

(2)基于二阶差分的方法

→一阶差分算子通过寻找一阶差分中的最大值来检测边缘,将边缘定位在一阶差分最大的方向

→二阶差分算子通过寻找二阶差分过零点来定位边缘,常用的有Laplace过零点等


在实际图像中,图像的边缘部分通常具有一定的斜坡面,斜坡部分与边缘模糊程度成比例

→边缘的宽度取决于其是灰度值到终止灰度值的斜面长度,而斜面长度取决于模糊程度


灰度不连续检测(间断检测)是最普遍的边缘检测方法:

在边缘检测中,梯度算子是常用的一阶差分算子,用于检测图像中边缘的存在和强度,Laplace算子是常用的二阶差分算子,二阶差分在边缘亮的一边符号为负,在边缘暗的一边符号为正。而在边缘处,二阶差分穿过零点


应该注意的是,差分操作对噪声十分敏感

→对于有噪边缘,利用差分检测图像边缘将放大噪声的影响,因此在利用差分进行边缘检测时,应该谨慎的考虑噪声的影响,通常在进行边缘检测之前对图像进行去噪或降噪处理

→利用一阶差分模板检测图像边缘实际上是利用一种局部处理的方法,当某一像素邻域内的灰度值的一阶差分大于阈设值时,则判定该像素为边缘像素。

→利用一阶差分模板提取的边缘图像是由许多不连续的边缘组成,这些边缘像素勾画出各个物体的轮廓,但是不能形成图像分割所需的闭合且连通的边界。

边缘连接:

是指将邻近的边缘像素连接起来,从而产生一条闭合且连通边缘的过程。根据事先定义的链接准则,对这样的边缘像素进行边缘连接处理,天不由于噪声和阴影造成的间断。

→利用二阶差分模板检测图像边缘实际上是寻找二阶差分过零点,二阶差分模板产生连续的边缘,但是不能保证检测的边缘是准确的,只能检测边缘的大致形状。


下面首先介绍一阶差分算子,大致可以分为两类,分别是梯度算子方向算子

(1)梯度算子

梯度算子定义在二维一阶导数的基础上,在数字图像中,由于像素是离散的,因此常用差分来近似偏导数

→对于一幅数字图像f(x,y),在像素(x,y)处梯度定义为:

向量


其中,Gx(x, y)和Gy(x, y)分别表示x(垂直)和y(水平)方向上的一阶差分

在边缘检测中,一个重要的量是梯度的幅度,用公式可以表示为


上式中,mag{·}表示求幅度的函数,但是为了降低复杂度,避免进行平方和开放运算,所以求幅度的公式通常写为如下形式:


使用上面这种绝对和计算简单而且保持了灰度的相对变化

→但是,也导致了梯度算子不具备各向同性→也就是不具备旋转不变性,梯度的方向指向像素值f(x, y)在(x,y)处增加最快的方向。

梯度关于x轴的角度为:


上式表示的是像素(x,y)处梯度关于x轴的方向角,这一点的梯度方向和该点边缘方向垂直


→梯度算子检测灰度值变化的两个属性是灰度值的变化率和方向,分别用幅度和方向来表示

→梯度的计算需要在每一个像素位置计算两个方向的一阶差分,常用的一阶差分算子有Robberts,Prewitt和Sobel算子等

在介绍各个算子之前,先给出一个3x3像素领域的定义,如下图所示:



首先简单介绍一下Robberts交叉算子,这是最简单的梯度算子,

在中心像素x(垂直)方向上,一阶差分计算式为

Gx = z9 - z5

在中心像素y(水平)方向上,一阶差分计算式为

Gy = z8 - z6

需要说明的是,Robbert交叉算子没有固定的中心点,因此不能使用2x2的模板卷积来实现

对于原始图像f(x,y),Roberts边缘检测输出图像为g(x,y),图像的Roberts边缘检测可以用下式来表示:


具体实现程序如下所示:

//实现Roberts边缘检测
#include <iostream>
#include <opencv2\core\core.hpp>
#include <opencv2\highgui\highgui.hpp>
#include <opencv2\imgproc\imgproc.hpp>

using namespace cv;
using namespace std;

//函数:Roberts算子实现
Mat Roberts(Mat srcImage);

int main()
{
	Mat srcImage = imread("2345.jpg",0);
	if (!srcImage.data)
	{
		cout << "读入图片错误!" << endl;
		system("pause");
		return -1;
	}
	imshow("原图像", srcImage);
	Mat dstImage = Roberts(srcImage);
	imshow("Roberts边缘检测图像", dstImage);
	waitKey();
	return 0;
}

//函数:Roberts算子实现
Mat Roberts(Mat srcImage)
{
	Mat dstImage = srcImage.clone();
	int rowsNum = srcImage.rows;
	int colsNum = srcImage.cols;
	for (int i = 0; i < rowsNum - 1; i++)
	{
		for (int j = 0; j < colsNum - 1; j++)
		{
			//根据公式进行计算
			int t1 = (srcImage.at<uchar>(i, j) -
				srcImage.at<uchar>(i + 1, j + 1))*
				(srcImage.at<uchar>(i, j) -
				srcImage.at<uchar>(i + 1, j + 1));
			int t2 = (srcImage.at<uchar>(i + 1, j) -
				srcImage.at<uchar>(i, j + 1))*
				(srcImage.at<uchar>(i + 1, j) -
				srcImage.at<uchar>(i, j + 1));
			//计算对角线像素差
			dstImage.at<uchar>(i, j) = (uchar)sqrt(t1 + t2);
		}
	}
	return dstImage;
}

程序执行后的结果如下所示:



→在实际使用中,常常使用Prewitt算子和Sobel算子来当做梯度算子。


下面首先介绍利用图像差分运算进行边缘检测,然后再分别介绍各种边缘检测算子,包括一阶算子和二阶算子


(1)图像差分运算:

对于原函数f(u),积分运算使计算f(u)映射到 f(u+a) - f(u+b)的值,差分运算分为前向差分和后向差分,一阶前向差分是指Δf = f(u+1) - f(u),一阶逆向差分是指Δf = f(u) - f(u-1)

二维离散图像f(x,y)在x方向的一阶差分定义为Δfx = f(x+1, y) - f(x, y),y方向的一阶差分定义为Δfy = f(x, y+1) - f(x, y)

差分运算通过求图像灰度变化剧烈处的一阶微分算子的极值来检测奇异点,通过奇异点的值进一步设定阈值就可以得到边缘二值化图像。

差分边缘检测中差分的水平或垂直方向都与边缘方向正交,因此在实际应用场景中,常常将边缘检测分为水平边缘,垂直边缘和对角线边缘,差分边缘检测定义方向模板如下所示:

                

垂直边缘 水平边缘 对角线边缘


利用OpenCV实现差分边缘检测实例如下所示:

//实现差分边缘检测
#include <iostream>
#include <opencv2\core\core.hpp>
#include <opencv2\highgui\highgui.hpp>
#include <opencv2\imgproc\imgproc.hpp>

using namespace cv;
using namespace std;

//图像差分函数
void diffOperation(const Mat srcImage, Mat &edgeXImage, Mat &edgeYImage);

int main()
{
	Mat srcImage = imread("2345.jpg", 0);
	if (!srcImage.data)
	{
		cout << "读入图片错误!" << endl;
		system("pause");
		return -1;
	}
	imshow("原图像", srcImage);
	Mat edgeXImage(srcImage.size(), srcImage.type());
	Mat edgeYImage(srcImage.size(), srcImage.type());

	//计算差分图像
	diffOperation(srcImage, edgeXImage, edgeYImage);
	imshow("垂直方向差分图像", edgeXImage);
	imshow("水平方向差分图像", edgeYImage);
	Mat edgeImage(srcImage.size(), srcImage.type());
	//将水平与垂直边缘进行叠加
	addWeighted(edgeXImage, 0.5, edgeYImage, 0.5, 0.0, edgeImage);
	imshow("水平和垂直方向边缘", edgeImage);
	waitKey();
	return 0;
}

void diffOperation(const Mat srcImage, Mat &edgeXImage, Mat &edgeYImage)
{ 
	Mat tempImage = srcImage.clone();
	int rowsNum = tempImage.rows;
	int colsNum = tempImage.cols;
	for (int i = 0; i < rowsNum - 1; i++)
	{
		for (int j = 0; j < colsNum - 1; j++)
		{
			//计算垂直边缘
			edgeXImage.at<uchar>(i, j) =
				abs(tempImage.at<uchar>(i + 1, j) - tempImage.at<uchar>(i, j));
			//计算水平边缘
			edgeYImage.at<uchar>(i, j) =
				abs(tempImage.at<uchar>(i, j + 1) - tempImage.at<uchar>(i, j));
		}
	}
}

执行程序后,效果如下图所示:




(2)非极大值抑制

在介绍其余的边缘检测算子之前,首先简单介绍一下图像中的非极大值抑制。

图像梯度矩阵中的元素值越大,说明图像中该点的梯度值越大,但是这并不能将它判断为该点处的边缘。

非极大值抑制操作可以提出为伪边缘信息,被广泛应用于图像边缘检测中,其原理是通过像素邻域的局部最优值,将非极大值点所对应的灰度值设置为背景像素点,如像素邻域区域满足梯度值局部最优值则判断为该像素的边缘,对其余非极大值的相关信息进行抑制,利用这个准则可以剔除大部分非边缘点。


(3)Sobel边缘检测算子

Sobel算子是应用广泛的离散微分算子之一,常常用于图像处理中的边缘检测,计算图像灰度函数的近似梯度。利用图像像素点Sobel算子计算出相应的地图向量及向量的范数,基于图像卷积来实现在水平方向与垂直方向检测对应方向的边缘。对于原图像与奇数Sobel水平核Gx,垂直核Gy进行卷积可计算水平与垂直变换,当内核大小为3x3时,Gx与Gy为下式:


对图像中没一点结合卷积后的结果求出近似梯度幅度G:

G= √(Gx^2 + Gy^2)

Sobel算子在进行边缘检测时效率较高,当对精度要求不是很高时,是一种比较常用的边缘检测方法。Sobel算子对于沿着x轴和y轴的排列的边缘表示的较好,但是对与其他角度的表示取不够精确,这时候可以使用Scharr滤波器。这种滤波器的水平与垂直的核因子如下:


首先给出使用OpenCV中的自带函数库实现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 );

第一个和第二个参数分别为源图像和输出图像

第三个参数为int类型的ddepth,为输出图像的深度,支持src.depth()和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

第四个参数为int类型的dx,为x方向上的差分阶数

第五个参数为int类型的dy,为y方向上的差分阶数

第六个参数为int类型的ksize,有默认值3,表示Sobel核的大小,需要注意的是,Sobel算子的内核大小必须取 1,3,5,7。

第七个参数是double类型的scale,计算导数值是可选的缩放因子,默认值为1,表示默认的情况下不进行放缩操作。可以查阅getDeriveKernels查看相关内容

第八个参数是double类型的delta,表示在结果存入目标图(dst)之前可选的delta值,有默认值0

第九个参数是int类型的bordertype,有默认值BORDER_DEFAULT,在之前的文章中有过这个参数的相关介绍,在这里不再赘述。



具体程序如下:

//使用OpenCV自带函数实现Sobel边缘检测
#include <iostream>
#include <opencv2\core\core.hpp>
#include <opencv2\highgui\highgui.hpp>
#include <opencv2\imgproc\imgproc.hpp>

using namespace cv;
using namespace std;

int main()
{
	Mat srcImage = imread("2345.jpg");
	if (!srcImage.data)
	{
		cout << "读入图片错误!" << endl;
		system("pause");
		return -1;
	}
	Mat srcGray;
	cvtColor(srcImage, srcGray, CV_BGR2GRAY);
	imshow("原图像灰度图", srcGray);
	//定义边缘图,水平和垂直
	Mat edgeImage, edgeImageX, edgeImageY;
	//求x方向Sobel边缘
	Sobel(srcGray, edgeImageX, CV_16S, 1, 0, 3, 1, 0);
	//求y方向的Sobel算子
	Sobel(srcGray, edgeImageY, CV_16S, 0, 1, 3, 1);
	//线性变换,转换输入数组元素为8位无符号整型
	convertScaleAbs(edgeImageX, edgeImageX);
	convertScaleAbs(edgeImageY, edgeImageY);
	//x与y方向进行叠加
	addWeighted(edgeImageX, 0.5, edgeImageY, 0.5, 0, edgeImage);
	imshow("Sobel边缘图像", edgeImage);

	//定义scharr边缘图像
	Mat edgeXImageS, edgeYImageS, edgeImageS;
	//计算X方向的Scharr边缘
	Scharr(srcGray, edgeXImageS, CV_16S, 1, 0, 1, 0);
	convertScaleAbs(edgeXImageS, edgeXImageS);
	//计算Y方向的Scharr边缘
	Scharr(srcGray, edgeYImageS, CV_16S, 0, 1, 1, 0);
	convertScaleAbs(edgeYImageS, edgeYImageS);
	//x与y方向进行边缘叠加
	addWeighted(edgeXImageS, 0.5, edgeYImageS, 0.5,0,edgeImageS);
	imshow("Scharr边缘图像", edgeImageS);
	waitKey();
	return 0;
}

执行后的结果如下所示:

可以看出,Scharr能够保持更多更好的图像边缘部分。

                                                                                                                                                                

下面介绍不使用OpenCV中的函数库实现Sobel边缘检测

(a.非极大值抑制Sobel边缘检测

→非极大值抑制Sobel边缘检测实现步骤主要如下:

(①.将图像转换为32位浮点型数据,定义水平或垂直方向的Sobel算子

(②.利用filter2D完成图像与算子的卷积操作,计算卷积结果的梯度幅值

(③.自适应计算出梯度幅度阈值,阈值设置不大于梯度幅值的均值乘以4,根据与之对水平或垂直的邻域区域梯度进行比较。

(④.判断当前邻域梯度是否大于水平或垂直邻域梯度,自适应完成边缘检测处二值图像的操作。

对上面这个过程进行程序实现如下所示:

//图像非极大值抑制实现Sobel竖直细化边缘
bool SobelVerEdge(Mat srcImage, Mat &dstImage)
{
	CV_Assert(srcImage.channels() == 1);
	srcImage.convertTo(srcImage, CV_32FC1);
	//水平方向的Sobel算子
	Mat sobelX = (Mat_<float>(3, 3) << -0.125, 0, 0.125,
		-0.25, 0, 0.25,
		-0.125, 0, 0.125);
	Mat ConResMat;
	//卷积运算
	filter2D(srcImage, ConResMat, srcImage.type(), sobelX);
	//计算梯度的幅度
	Mat gradMagMat;
	multiply(ConResMat, ConResMat, gradMagMat);
	//根据梯度幅度及参数设置阈值
	int scaleVal = 4;
	double thresh = scaleVal*mean(gradMagMat).val[0];
	Mat resultTempMat = Mat::zeros(gradMagMat.size(), gradMagMat.type());
	float *pDataMag = (float*)gradMagMat.data;
	float *pDataRes = (float*)resultTempMat.data;
	const int rowsNum = ConResMat.rows;
	const int colsNum = ConResMat.cols;
	for (int i = 1; i != rowsNum - 1; ++i)
	{
		for (int j = 1; j != colsNum - 1; ++j)
		{
			//计算这一点的梯度与水平或垂直梯度值的大小并比较结果
			bool b1 = (pDataMag[i*colsNum + j] > pDataMag[i*colsNum + j - 1]);
			bool b2 = (pDataMag[i*colsNum + j] > pDataMag[i*colsNum + j + 1]);
			bool b3 = (pDataMag[i*colsNum + j] > pDataMag[(i - 1)*colsNum + j]);
			bool b4 = (pDataMag[i*colsNum + j] > pDataMag[(i + 1)*colsNum + j]);
			//判断邻域梯度是否满足大于水平或垂直梯度的条件
			//并根据自适应阈值参数进行二值化
			pDataRes[i*colsNum + j] = 255 * ((pDataMag[i*colsNum + j] > thresh)
				&& ((b1 && b2) || (b3 && b4)));
		}
	}
	resultTempMat.convertTo(resultTempMat, CV_8UC1);
	dstImage = resultTempMat.clone();
	return true;
}
(b.图像直接卷积实现Sobel

图像直接卷积Sobel边缘检测实现比较简单,首先定义水平或垂直方向的Sobel核因子,直接对源图像进行窗遍历,计算窗口内的邻域梯度幅值;然后根据梯度模长进行二值化操作,完成图像水平或垂直方向的边缘检测

图像直接卷积Sobel边缘实现代码如下所示:

//图像直接卷积实现Sobel
bool SobelEdge(const Mat &srcImage, Mat &dstImage, uchar threshold)
{
	CV_Assert(srcImage.channels() == 1);
	//初始化水平核因子
	Mat sobelX = (Mat_<double>(3, 3) << 1, 0, -1,
		2, 0, -2,
		1, 0, -1);
	//初始化垂直核因子
	Mat sobelY = (Mat_<double>(3, 3) << 1, 2, 1,
		0, 0, 0,
		-1, -2, -1);
	dstImage = Mat::zeros(srcImage.rows - 2, srcImage.cols - 2, srcImage.type());
	double edgeX = 0;
	double edgeY = 0;
	double graMag = 0;
	for (int k = 1; k < srcImage.rows - 1; ++k)
	{
		for (int n = 1; n < srcImage.cols - 1; ++n)
		{
			edgeX = 0;
			edgeY = 0;
			//遍历计算水平与垂直梯度
			for (int i = -1; i <= 1; i++)
			{
				for (int j = -1; j <= 1; j++)
				{
					edgeX += srcImage.at<uchar>(k + i, n + j)*
						sobelX.at<double>(1 + i, 1 + j);
					edgeY += srcImage.at<uchar>(k + i, n + j)*
						sobelY.at<double>(1 + i, 1 + j);
				}
			}
			//计算梯度模长
			graMag = sqrt(pow(edgeY, 2) + pow(edgeX, 2));
			//进行二值化
			dstImage.at<uchar>(k - 1, n - 1) =
				((graMag > threshold) ? 255 : 0);
		}
	}
	return true;
}


(c.图像卷积下非极大值抑制Sobel

图像卷积下非极大值抑制Sobel边缘检测的实现过程与之前的a部分比较类似。但是需要说明的是,非极大值抑制虽然能够较好的剔除虚假边缘点,但是对于某些特定场景下的边缘检测并不起作用,例如无损文本字符识别

相关的实现代码如下所示:


//图像卷积实现Sobel非极大值抑制
bool sobelOptaEdge(const Mat &srcImage, Mat &dstImage, int flag)
{
	CV_Assert(srcImage.channels() == 1);
	//初始化Sobel水平核因子
	Mat sobelX = (Mat_<double>(3, 3) << 1, 0, -1,
		2, 0, -2,
		1, 0, -1);
	//初始化Sobel垂直核因子
	Mat sobelY = (Mat_<double>(3, 3) << 1, 2, 1,
		0, 0, 0,
		-1, -2, -1);
	//计算水平与垂直卷积
	Mat edgeX, edgeY;
	filter2D(srcImage, edgeX, CV_32F, sobelX);
	filter2D(srcImage, edgeY, CV_32F, sobelY);
	//根据传入的参数确定计算水平或垂直方向的边缘
	int paraX = 0;
	int paraY = 0;
	switch (flag)
	{
	case 0:	
		paraX = 1;
		paraY = 0;
		break;
	case 1:
		paraX = 0;
		paraY = 1;
		break;
	case 2:
		paraX = 1;
		paraY = 1;
		break;
	default:
		break;
	}
	edgeX = abs(edgeX);
	edgeY = abs(edgeY);
	Mat graMagMat = paraX*edgeX.mul(edgeX) +
		paraY*edgeY.mul(edgeY);
	//计算阈值
	int scaleVal = 4;
	double thresh = scaleVal*mean(graMagMat).val[0];
	dstImage = Mat::zeros(srcImage.size(), srcImage.type());
	for (int i = 1; i < srcImage.rows - 1; i++)
	{
		float *pDataEdgeX = edgeX.ptr<float>(i);
		float *pDataEdgeY = edgeY.ptr<float>(i);
		float *pDataGraMag = graMagMat.ptr<float>(i);
		//阈值化和极大值抑制
		for (int j = 1; j < srcImage.cols - 1; j++)
		{
			//判断当前邻域梯度是否大于阈值与大于水平或垂直梯度
			if (pDataGraMag[j]>thresh &&
				(pDataEdgeX[j]>paraX*pDataEdgeY[j] &&
				pDataGraMag[j] > pDataGraMag[j - 1] &&
				pDataGraMag[j] > pDataGraMag[j + 1] ||
				(pDataEdgeY[j] > paraY*pDataEdgeX[j] &&
				pDataGraMag[j] > pDataGraMag[j - 1] &&
				pDataGraMag[j] > pDataGraMag[j + 1])))
				dstImage.at<uchar> = 255;
		}
	}
	return true;
}

#图像梯度 (注意都需要cv.convertScaleAbs将得到的有些负值取绝对值得到正数,并将数据转化到0-255之间,且sobel与Scarr算法中的数据位数都是32位浮点型的) import cv2 as cv import numpy as np def sobel_demo(image): #注意是32位float数据 grad_x = cv.Scharr(image, cv.CV_32F, 1, 0) grad_y = cv.Scharr(image, cv.CV_32F, 0, 1) #当用sobel算子不能很好的得到边缘的时候,就可以用Scharr算子,这是加强版的sobel算子,就可以得到 #原图像不是很明显的边缘了 # grad_x =cv.Sobel(image,cv.CV_32F,1,0) # grad_y =cv.Sobel(image,cv.CV_32F,0,1) gradx =cv.convertScaleAbs(grad_x) grady = cv.convertScaleAbs(grad_y) #cv.imshow("gradx",gradx) #cv.imshow("grady",grady) dst = cv.addWeighted(gradx,0.5,grady,0.5,0) cv.imshow("sobel_demo",dst) def lapalace_demo(image): #dst =cv.Laplacian(image,cv.CV_32F) #dst =cv.convertScaleAbs(dst) 会把dst变成单通道的8位的0-255的图像 #也可以用filter2D来拉普拉斯算子 kernel = np.array([[0,-1,0],[-1,4,-1],[0,-1,0]]) dst = cv.filter2D(image,cv.CV_32F,kernel) dst = cv.convertScaleAbs(dst) cv.imshow("lapalace",dst) src = cv.imread("E:/opencv/picture/step.jpg") cv.imshow("inital_window",src) #sobel_demo(src) lapalace_demo(src) cv.waitKey(0) cv.destroyAllWindows() 分析: 图像梯度可以把图像看成二维离散函数,图像梯度其实就是这个二维离散函数的求导。 一、 Sobel算子是普通一阶差分,是基于寻找梯度强度。拉普拉斯算子(二阶差分)是基于过零点检测。通过计算梯度,设置阀值,得到边缘图像。 def sobel_demo(image): #注意是32位float数据 grad_x = cv.Scharr(image, cv.CV_32F, 1, 0) grad_y = cv.Scharr(image, cv.CV_32F, 0, 1) #当用sobel算子不能很好的得到边缘的时候,就可以用Scharr算子,这是加强版的sobel算子,就可以得到 #原图像不是很明显的边缘了 # grad_x =cv.Sobel(image,cv.CV_32F,1,0) # grad_y =cv.Sobel(image,cv.CV_32F,0,1) gradx = cv.convertScaleAbs(grad_x) grady = cv.convertScaleAbs(grad_y) #cv.imshow("gradx",gradx) #cv.imshow("grady",grady) dst = cv.addWeighted(gradx,0.5,grady,0.5,0) cv.imshow("sobel_demo",dst) 1.Sobel算子用来计算图像灰度函数的近似梯度。Sobel算子根据像素点上下、左右邻点灰度加权差,在边缘处达到极值这一现象检测边缘。对噪声具有平滑作用,提供较为精确的边缘方向信息,边缘定位精度不够高。当对精度要求不是很高时,是一种较为常用的边缘检测方法。 2.Sobel具有平滑和微分的功效。即:Sobel算子先将图像横向或纵向平滑,然后再纵向或横向差分,得到的结果是平滑后的差分结果。 OpenCV的Sobel函数原型为:Sobel(src, ddepth, dx, dy[, dst[, ksize[, scale[, delta[, borderType]]]]]) -> dst 注:一般就设置src,ddepth = cv.CV_32F,dx,dy(对x方求梯度就是1,0对y方向求梯度就是0,1) src参数表示输入需要处理的图像。 ddepth参数表示输出图像深度,针对不同的输入图像,输出目标图像有不同的深度。   具体组合如下:   src.depth() = CV_8U, 取ddepth =-1/CV_16S/CV_32F/CV_64F (一般源图像都为CV_8U,为了避免溢出,一般ddepth参数选择CV_32F)   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时,代表输出图像与输入图像相同的深度。 dx参数表示x方向上的差分阶数,1或0 。 dy参数表示y 方向上的差分阶数,1或0 。 dst参数表示输出与src相同大小和相同通道数的图像。 ksize参数表示Sobel算子的大小,必须为1、3、5、7。 scale参数表示缩放导数的比例常数,默认情况下没有伸缩系数。 delta参数表示一个可选的增量,将会加到最终的dst中,同样,默认情况下没有额外的值加到dst中。 borderType表示判断图像边界的模式。这个参数默认值为cv2.BORDER_DEFAULT。 2.OpenCV的convertScaleAbs函数使用线性变换转换输入数组元素成8位无符号整型。函数原型:convertScaleAbs(src[, dst[, alpha[, beta]]]) -> dst src参数表示原数组。 dst参数表示输出数组 (深度为 8u)。 alpha参数表示比例因子。 beta参数表示原数组元素按比例缩放后添加的值。 3.OpenCV的addWeighted函数是计算两个数组的加权和。函数原型:addWeighted(src1, alpha, src2, beta, gamma[, dst[, dtype]]) -> dst 用于将x,y方向的梯度合成。 二、Scharr算子 当用sobel算子发现得到的边缘信息不明显的时候,就可以用Scharr算子了。该算子是sobel算子的加强版,用法也和sobel算子一样,效果更加突出。 Scharr算子也是计算x或y方向上的图像差分OpenCV的Scharr函数原型为:Scharr(src, ddepth, dx, dy[, dst[, scale[, delta[, borderType]]]]) -> dst 参数和Sobel算子的几乎差不多,意思也一样,只是没有ksize大小。 三、拉普拉斯算子 1.拉普拉斯算子(Laplace Operator)是n维欧几里德空间中的一个二阶微分算子,定义为梯度(▽f)的散度(▽•f)。 2.OpenCV的Laplacian函数原型为:Laplacian(src, ddepth[, dst[, ksize[, scale[, delta[, borderType]]]]]) -> dst src参数表示输入需要处理的图像。 ddepth参数表示输出图像深度,针对不同的输入图像,输出目标图像有不同的深度。   具体组合如下:   src.depth() = CV_8U, 取ddepth =-1/CV_16S/CV_32F/CV_64F (一般源图像都为CV_8U,为了避免溢出,一般ddepth参数选择CV_32F) 注:当然我们也可以cv.filter2D命令来make一个拉普拉斯算子: kernel = np.array([[0,-1,0],[-1,4,-1],[0,-1,0]]) dst = cv.filter2D(image,cv.CV_32F,kernel) dst = cv.convertScaleAbs(dst) cv.imshow("lapalace",dst) Canny算法 canny 的目标是找到一个最优的边缘检测算法,最优边缘检测的含义是: 好的检测- 算法能够尽可能多地标识出图像中的实际边缘。 好的定位- 标识出的边缘要尽可能与实际图像中的实际边缘尽可能接近。 最小响应- 图像中的边缘只能标识一次,并且可能存在的图像噪声不应标识为边缘。 3.算法步骤:   ①高斯模糊 - GaussianBlur   ②灰度转换 - cvtColor   ③计算梯度 – Sobel/Scharr   ④非最大信号抑制   ⑤高低阈值输出二值图像 #canny算法常用步骤: #高斯模糊:因为canny对噪声很敏感,注意核size别太大 #源图像灰度化 #canny算法: 像素值小于低阈值的舍弃,高于高阈值的保留作为边缘信息,大于低阈值且该像素仅仅在连接到一个高于高阈值的像素时被保留。一般高阈值:低阈值在2:1到3:1之间 def canny_demo(image): image = cv.GaussianBlur(image,(3,3),0) gray = cv.cvtColor(image,cv.COLOR_BGR2GRAY) dst = cv.Canny(image,50,150) cv.imshow("Canny_demo",dst)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值