OpenCV数字图像处理基于C++:Canny边缘检测

Canny边缘检测

1、概述

Canny边缘检测是从不同视觉对象中提取有用的结构信息并大大减少要处理的数据量的一种技术,目前已广泛应用于各种计算机视觉系统。Canny发现,在不同视觉系统上对边缘检测的要求较为类似,因此,可以实现一种具有广泛应用意义的边缘检测技术。

Canny算法也被许多人称为最佳探测器,旨在满足三个主要标准:
(1)低错误率:这意味着只能很好地检测存在的边缘。
(2)良好的本地化:必须将检测到的边缘像素与实际边缘像素之间的距离降至最低。
(3)最小响应:每条边距只有一个检测器响应。

Canny算子边缘检测有以下步骤:
(1)用高斯滤波器,平滑图像,过滤噪音
(2)用Sobel等梯度算子计算梯度幅值和方向
	SobelX、SobleY核在水平方向和垂直方向对平滑后的图像进行滤波,找到每个像素的边缘梯度和方向
(3)对梯度幅值进行非极大值抑制(细化边缘)
	在得到梯度大小和方向后,对图像进行全扫描,去除任何不需要的像素,这些像素可能不构成边缘。检查像素是否在其梯度方向的邻域中是局部最大值。否则,将被抑制(归零)
(4)用双阈值算法检测和连接边缘(确定真实的和潜在的边缘)
	如果像素渐变高于阈值上限,则该像素被接受为边缘
	如果像素渐变值低于下限阈值,则会拒绝该值。
	如果像素渐变介于两个阈值之间,则仅当它连接到高于阈值上限的像素时,才会接受它。

2、具体步骤实现

2.1高斯滤波

为了尽可能减少噪声对边缘检测结果的影响,所以必须滤除噪声以防止由噪声引起的错误检测。为了平滑图像,使用高斯滤波器与图像进行卷积,该步骤将平滑图像,以减少边缘检测器上明显的噪声影响。大小为img的高斯滤波器核的生成方程式由下式给出:

img

下面是一个sigma = 1.4,尺寸为3x3的高斯卷积核的例子(需要注意归一化):

1182370-20180910085156352-703442321

若图像中一个3x3的窗口为A,要滤波的像素点为e,则经过高斯滤波之后,像素点e的亮度值为:

1182370-20180910085156427-2130430232-removebg-preview

其中*为卷积符号,sum表示矩阵中所有元素相加求和。

重要的是需要理解,高斯卷积核大小的选择将影响Canny检测器的性能。尺寸越大,检测器对噪声的敏感度越低,但是边缘检测的定位误差也将略有增加。

//1 高斯滤波
void Gaussfilter_ly(Mat input_image, Mat& output_image, int Gauss_size, double Sigma)
{
	//保证高斯核大小为大于等于3的奇数
	if (Gauss_size < 3) Gauss_size = 3;
	else Gauss_size = (int)(Gauss_size / 2) * 2 + 1;

	//1. 生成高斯卷积核
	double** Gausskernel = new double* [Gauss_size];
	for (int i = 0; i < Gauss_size; i++)
	{
		Gausskernel[i] = new double[Gauss_size];
	}
	int center = Gauss_size / 2;
	double sum = 0;

	for (int i = 0; i < Gauss_size; i++)
	{
		for (int j = 0; j < Gauss_size; j++)
		{
			Gausskernel[i][j] = exp(-((i - center) * (i - center) + (j - center) * (j - center)) / (2 * Sigma * Sigma));
			sum += Gausskernel[i][j];
		}
	}
	//2. 高斯卷积核归一化
	double sum1 = 1 / sum;
	for (int i = 0; i < Gauss_size; i++)
	{
		for (int j = 0; j < Gauss_size; j++)
		{
			Gausskernel[i][j] *= sum1;
		}
	}

	//3. 高斯滤波
	Mat tem_image = input_image.clone();
	int rows = input_image.rows - center;
	int cols = input_image.cols - center;
	for (int i = center; i < rows; i++)
	{
		for (int j = center; j < cols; j++)
		{
			double sum = 0;
			for (int m = -center; m <= center; m++)
			{
				for (int n = -center; n <= center; n++)
				{
					sum += Gausskernel[center + m][center + n] * input_image.at<uchar>(i + m, j + n);
				}
			}
			// 防越界
			tem_image.at<uchar>(i, j) = static_cast<uchar>(sum);
		}
	}
	output_image = tem_image;

	//释放内存
	for (int i = 0; i < Gauss_size; i++) delete[] Gausskernel[i];
	delete[] Gausskernel;
}

image-20221008191454071

2.2 计算梯度强度和方向

图像中的边缘可以指向各个方向,因此Canny算法使用四个算子来检测图像中的水平、垂直和对角边缘。边缘检测的算子(如Roberts,Prewitt,Sobel等)返回水平Gx和垂直Gy方向的一阶导数值,由此便可以确定像素点的梯度G和方向theta 。

img

其中G为梯度强度, theta表示梯度方向,arctan为反正切函数。下面以Sobel算子为例讲述如何计算梯度强度和方向。

x和y方向的Sobel算子分别为:

2

其中Sx表示x方向的Sobel算子,用于检测y方向的边缘; Sy表示y方向的Sobel算子,用于检测x方向的边缘(边缘方向和梯度方向垂直)。在直角坐标系中,Sobel算子的方向如下图所示。

3

若图像中一个3x3的窗口为A,要计算梯度的像素点为e,则和Sobel算子进行卷积之后,像素点e在x和y方向的梯度值分别为:

4

其中*为卷积符号,sum表示矩阵中所有元素相加求和。根据公式计算出像素点e的梯度和方向。

//2 计算梯度幅值图像,方向图像和边缘图像
void Grad_dire_ly(Mat input, Mat& Gradimage, Mat& Direimage)
{
	Mat tempGrad = Mat(input.size(), CV_16U, Scalar(0));
	Mat tempDire = Mat(input.size(), CV_8U, Scalar(0));

	int width = input.cols;
	int height = input.rows;

	for (int i = 1; i < height - 1; i++)
	{
		for (int j = 1; j < width - 1; j++)
		{
			//计算梯度及梯度幅值
			int gx = static_cast<uchar>(input.at<uchar>(i - 1, j + 1) + 2 * input.at<uchar>(i, j + 1) + input.at<uchar>(i + 1, j + 1)
				- input.at<uchar>(i - 1, j - 1) - 2 * input.at<uchar>(i - 1, j) - input.at<uchar>(i - 1, j + 1));
			int gy = static_cast<uchar>(input.at<uchar>(i + 1, j - 1) + 2 * input.at<uchar>(i + 1, j) + input.at<uchar>(i + 1, j + 1)
				- input.at<uchar>(i - 1, j - 1) - 2 * input.at<uchar>(i, j - 1) - input.at<uchar>(i + 1, j - 1));
			int sum = static_cast<uchar>(gx + gy);

			//梯度幅值图像
			tempGrad.at<ushort>(i, j) = abs(sum);

			//方向图像,图像中的坐标轴
			double dire = atan2(gy, gx) * 180 / 3.1415926;
			if (dire <= -67.5 || dire >= 67.5) 
				tempDire.at<uchar>(i, j) = 1; //1:水平
			else if (dire > -67.5 && dire < -22.5) 
				tempDire.at<uchar>(i, j) = 2; //2:45
			else if (dire > -22.5 && dire < 22.5) 
				tempDire.at<uchar>(i, j) = 3; //3:垂直
			else 
				tempDire.at<uchar>(i, j) = 4; //4:-45
		}
	}
	Gradimage = tempGrad;
	Direimage = tempDire;
}

image-20221008193356433

2.3 非极大值抑制

非极大值抑制是一种边缘稀疏技术,非极大值抑制的作用在于“瘦”边。对图像进行梯度计算后,仅仅基于梯度值提取的边缘仍然很模糊。对于标准3,对边缘有且应当只有一个准确的响应。而非极大值抑制则可以帮助将局部最大值之外的所有梯度值抑制为0,对梯度图像中每个像素进行非极大值抑制的算法是:

   将当前像素的梯度强度与沿正负梯度方向上的两个像素进行比较。
   如果当前像素的梯度强度与另外两个像素相比最大,则该像素点保留为边缘点,否则该像素点将被抑制。

在这里插入图片描述

通常为了更加精确的计算,在跨越梯度方向的两个相邻像素之间使用线性插值来得到要比较的像素梯度,现举例如下:

1

将梯度分为8个方向,分别为E、NE、N、NW、W、SW、S、SE,其中0代表0°45°,1代表45°90°,2代表-90°-45°,3代表-45°0°。像素点P的梯度方向为theta,则像素点P1和P2的梯度线性插值为:

1-removebg-preview

因此非极大值抑制的伪代码描写如下:

2

需要注意的是,如何标志方向并不重要,重要的是梯度方向的计算要和梯度算子的选取保持一致。

//3 非极大值抑制图像
void Nonmax_suppression_ly(Mat Gradimage, Mat Direimage, Mat& Suppimage)
{
	Mat tempSupp = Mat(Gradimage.size(), Gradimage.type(), Scalar(0));

	int width = Gradimage.cols;
	int height = Gradimage.rows;

	for (int i = 1; i < height - 1; i++)
	{
		for (int j = 1; j < width - 1; j++)
		{
			switch (Direimage.at<uchar>(i, j))
			{
			case 1:
				if (Gradimage.at<ushort>(i, j) >= Gradimage.at<ushort>(i, j - 1) && Gradimage.at<ushort>(i, j) >= Gradimage.at<ushort>(i, j + 1))
					tempSupp.at<ushort>(i, j) = Gradimage.at<ushort>(i, j);
				else
					tempSupp.at<ushort>(i, j) = 0;
				break;
			case 2:
				if (Gradimage.at<ushort>(i, j) >= Gradimage.at<ushort>(i + 1, j - 1) && Gradimage.at<ushort>(i, j) >= Gradimage.at<ushort>(i - 1, j + 1))
					tempSupp.at<ushort>(i, j) = Gradimage.at<ushort>(i, j);
				else
					tempSupp.at<ushort>(i, j) = 0;
				break;
			case 3:
				if (Gradimage.at<ushort>(i, j) >= Gradimage.at<ushort>(i - 1, j) && Gradimage.at<ushort>(i, j) >= Gradimage.at<ushort>(i + 1, j))
					tempSupp.at<ushort>(i, j) = Gradimage.at<ushort>(i, j);
				else
					tempSupp.at<ushort>(i, j) = 0;
				break;
			case 4:
				if (Gradimage.at<ushort>(i, j) >= Gradimage.at<ushort>(i - 1, j - 1) && Gradimage.at<ushort>(i, j) >= Gradimage.at<ushort>(i + 1, j + 1))
					tempSupp.at<ushort>(i, j) = Gradimage.at<ushort>(i, j);
				else
					tempSupp.at<ushort>(i, j) = 0;
				break;
			default:

				break;
			}
		}
	}
	Suppimage = tempSupp;
}

image-20221008194932824

2.4 双阈值检测

在施加非极大值抑制之后,剩余的像素可以更准确地表示图像中的实际边缘。然而,仍然存在由于噪声和颜色变化引起的一些边缘像素。为了解决这些杂散响应,必须用弱梯度值过滤边缘像素,并保留具有高梯度值的边缘像素,可以通过选择高低阈值来实现。如果边缘像素的梯度值高于高阈值,则将其标记为强边缘像素;如果边缘像素的梯度值小于高阈值并且大于低阈值,则将其标记为弱边缘像素;如果边缘像素的梯度值小于低阈值,则会被抑制。阈值的选择取决于给定输入图像的内容。

在这里插入图片描述

双阈值检测的伪代码描写如下:
在这里插入图片描述

到目前为止,被划分为强边缘的像素点已经被确定为边缘,因为它们是从图像中的真实边缘中提取出来的。然而,对于弱边缘像素,将会有一些争论,因为这些像素可以从真实边缘提取也可以是因噪声或颜色变化引起的。为了获得准确的结果,应该抑制由后者引起的弱边缘。通常,由真实边缘引起的弱边缘像素将连接到强边缘像素,而噪声响应未连接。为了跟踪边缘连接,通过查看弱边缘像素及其8个邻域像素,只要其中一个为强边缘像素,则该弱边缘点就可以保留为真实的边缘。

//4 滞后阈值处理(双阈值)
void doubleThread_ly(Mat Suppimage, Mat& Edgeimage, int th_high, int th_low)
{
	int temp;
	if (th_high < th_low)
	{
		temp = th_high;
		th_high = th_low;
		th_low = temp;
	}

	Mat bw_h = Mat(Suppimage.size(), CV_8UC1, Scalar(0));
	Mat bw_l = Mat(Suppimage.size(), CV_8UC1, Scalar(0));

	int width = Suppimage.cols;
	int height = Suppimage.rows;

	for (int i = 0; i < height; i++)
	{
		for (int j = 0; j < width; j++)
		{
			if (Suppimage.at<ushort>(i, j) >= th_high)
				bw_h.at<uchar>(i, j) = 255;
			else
				bw_h.at<uchar>(i, j) = 0;
			if (Suppimage.at<ushort>(i, j) >= th_low && Suppimage.at<ushort>(i, j) < th_high)
				bw_l.at<uchar>(i, j) = 255;
			else
				bw_l.at<uchar>(i, j) = 0;
		}
	}

	Mat bw = bw_h.clone();
	for (int i = 1; i < height - 1; i++)
	{
		for (int j = 1; j < width - 1; j++)
		{
			if (bw_h.at<uchar>(i, j) == 255)
			{
				if (bw_l.at<uchar>(i - 1, j - 1) == 255)
					bw.at<uchar>(i - 1, j - 1) = 255;
				if (bw_l.at<uchar>(i - 1, j) == 255)
					bw.at<uchar>(i - 1, j) = 255;
				if (bw_l.at<uchar>(i - 1, j + 1) == 255)
					bw.at<uchar>(i - 1, j + 1) = 255;
				if (bw_l.at<uchar>(i, j - 1) == 255)
					bw.at<uchar>(i, j - 1) = 255;
				if (bw_l.at<uchar>(i, j + 1) == 255)
					bw.at<uchar>(i, j + 1) = 255;
				if (bw_l.at<uchar>(i + 1, j - 1) == 255)
					bw.at<uchar>(i + 1, j - 1) = 255;
				if (bw_l.at<uchar>(i + 1, j) == 255)
					bw.at<uchar>(i + 1, j) = 255;
				if (bw_l.at<uchar>(i + 1, j + 1) == 255)
					bw.at<uchar>(i + 1, j + 1) = 255;
			}
		}
	}

	Edgeimage = bw;
}

image-20221008195827766

3、完整实现Canny 算子边缘检测

#include<opencv2/opencv.hpp>

using namespace std;
using namespace cv;

//1 高斯滤波
void Gaussfilter_ly(Mat input_image, Mat& output_image, int Gauss_size, double Sigma)
{
	//保证高斯核大小为大于等于3的奇数
	if (Gauss_size < 3) Gauss_size = 3;
	else Gauss_size = (int)(Gauss_size / 2) * 2 + 1;

	//生成高斯卷积核
	double** Gausskernel = new double* [Gauss_size];
	for (int i = 0; i < Gauss_size; i++)
	{
		Gausskernel[i] = new double[Gauss_size];
	}
	int center = Gauss_size / 2;
	double sum = 0;

	for (int i = 0; i < Gauss_size; i++)
	{
		for (int j = 0; j < Gauss_size; j++)
		{
			Gausskernel[i][j] = exp(-((i - center) * (i - center) + (j - center) * (j - center)) / (2 * Sigma * Sigma));
			sum += Gausskernel[i][j];
		}
	}
	//高斯卷积核归一化
	double sum1 = 1 / sum;
	for (int i = 0; i < Gauss_size; i++)
	{
		for (int j = 0; j < Gauss_size; j++)
		{
			Gausskernel[i][j] *= sum1;
		}
	}

	//滤波
	Mat tem_image = input_image.clone();
	int rows = input_image.rows - center;
	int cols = input_image.cols - center;
	for (int i = center; i < rows; i++)
	{
		for (int j = center; j < cols; j++)
		{
			double sum = 0;
			for (int m = -center; m <= center; m++)
			{
				for (int n = -center; n <= center; n++)
				{
					sum += Gausskernel[center + m][center + n] * input_image.at<uchar>(i + m, j + n);
				}
			}
			tem_image.at<uchar>(i, j) = static_cast<uchar>(sum);
		}
	}
	output_image = tem_image;

	//释放内存
	for (int i = 0; i < Gauss_size; i++) delete[] Gausskernel[i];
	delete[] Gausskernel;
}


//2 计算梯度幅值图像,方向图像和边缘图像
void Grad_dire_ly(Mat input, Mat& Gradimage, Mat& Direimage)
{
	Mat tempGrad = Mat(input.size(), CV_16U, Scalar(0));
	Mat tempDire = Mat(input.size(), CV_8U, Scalar(0));

	int width = input.cols;
	int height = input.rows;

	for (int i = 1; i < height - 1; i++)
	{
		for (int j = 1; j < width - 1; j++)
		{
			//计算梯度及梯度幅值
			int gx = input.at<uchar>(i + 1, j - 1) + input.at<uchar>(i + 1, j) + input.at<uchar>(i + 1, j + 1)
				- input.at<uchar>(i - 1, j - 1) - input.at<uchar>(i - 1, j) - input.at<uchar>(i - 1, j + 1);
			int gy = input.at<uchar>(i - 1, j + 1) + input.at<uchar>(i, j + 1) + input.at<uchar>(i + 1, j + 1)
				- input.at<uchar>(i - 1, j - 1) - input.at<uchar>(i, j - 1) - input.at<uchar>(i + 1, j - 1);
			int sum = gx + gy;

			//梯度幅值图像
			tempGrad.at<ushort>(i, j) = abs(sum);

			//方向图像,图像中的坐标轴
			double dire = atan2(gy, gx) * 180 / 3.1415926;
			if (dire <= -67.5 || dire >= 67.5) tempDire.at<uchar>(i, j) = 1; //1:水平
			else if (dire > -67.5 && dire < -22.5) tempDire.at<uchar>(i, j) = 2; //2:45
			else if (dire > -22.5 && dire < 22.5) tempDire.at<uchar>(i, j) = 3; //3:垂直
			else tempDire.at<uchar>(i, j) = 4; //4:-45
		}
	}
	Gradimage = tempGrad;
	Direimage = tempDire;
}

//3 非极大值抑制图像
void Nonmax_suppression_ly(Mat Gradimage, Mat Direimage, Mat& Suppimage)
{
	Mat tempSupp = Mat(Gradimage.size(), Gradimage.type(), Scalar(0));

	int width = Gradimage.cols;
	int height = Gradimage.rows;

	for (int i = 1; i < height - 1; i++)
	{
		for (int j = 1; j < width - 1; j++)
		{
			switch (Direimage.at<uchar>(i, j))
			{
			case 1:
				if (Gradimage.at<ushort>(i, j) >= Gradimage.at<ushort>(i, j - 1) && Gradimage.at<ushort>(i, j) >= Gradimage.at<ushort>(i, j + 1))
					tempSupp.at<ushort>(i, j) = Gradimage.at<ushort>(i, j);
				else
					tempSupp.at<ushort>(i, j) = 0;
				break;
			case 2:
				if (Gradimage.at<ushort>(i, j) >= Gradimage.at<ushort>(i + 1, j - 1) && Gradimage.at<ushort>(i, j) >= Gradimage.at<ushort>(i - 1, j + 1))
					tempSupp.at<ushort>(i, j) = Gradimage.at<ushort>(i, j);
				else
					tempSupp.at<ushort>(i, j) = 0;
				break;
			case 3:
				if (Gradimage.at<ushort>(i, j) >= Gradimage.at<ushort>(i - 1, j) && Gradimage.at<ushort>(i, j) >= Gradimage.at<ushort>(i + 1, j))
					tempSupp.at<ushort>(i, j) = Gradimage.at<ushort>(i, j);
				else
					tempSupp.at<ushort>(i, j) = 0;
				break;
			case 4:
				if (Gradimage.at<ushort>(i, j) >= Gradimage.at<ushort>(i - 1, j - 1) && Gradimage.at<ushort>(i, j) >= Gradimage.at<ushort>(i + 1, j + 1))
					tempSupp.at<ushort>(i, j) = Gradimage.at<ushort>(i, j);
				else
					tempSupp.at<ushort>(i, j) = 0;
				break;
			default:

				break;
			}
		}
	}
	Suppimage = tempSupp;
}

//4 滞后阈值处理(双阈值)
void doubleThread_ly(Mat Suppimage, Mat& Edgeimage, int th_high, int th_low)
{
	int temp;
	if (th_high < th_low)
	{
		temp = th_high;
		th_high = th_low;
		th_low = temp;
	}

	Mat bw_h = Mat(Suppimage.size(), CV_8UC1, Scalar(0));
	Mat bw_l = Mat(Suppimage.size(), CV_8UC1, Scalar(0));

	int width = Suppimage.cols;
	int height = Suppimage.rows;

	for (int i = 0; i < height; i++)
	{
		for (int j = 0; j < width; j++)
		{
			if (Suppimage.at<ushort>(i, j) >= th_high)
				bw_h.at<uchar>(i, j) = 255;
			else
				bw_h.at<uchar>(i, j) = 0;
			if (Suppimage.at<ushort>(i, j) >= th_low && Suppimage.at<ushort>(i, j) < th_high)
				bw_l.at<uchar>(i, j) = 255;
			else
				bw_l.at<uchar>(i, j) = 0;
		}
	}

	Mat bw = bw_h.clone();
	for (int i = 1; i < height - 1; i++)
	{
		for (int j = 1; j < width - 1; j++)
		{
			if (bw_h.at<uchar>(i, j) == 255)
			{
				if (bw_l.at<uchar>(i - 1, j - 1) == 255)
					bw.at<uchar>(i - 1, j - 1) = 255;
				if (bw_l.at<uchar>(i - 1, j) == 255)
					bw.at<uchar>(i - 1, j) = 255;
				if (bw_l.at<uchar>(i - 1, j + 1) == 255)
					bw.at<uchar>(i - 1, j + 1) = 255;
				if (bw_l.at<uchar>(i, j - 1) == 255)
					bw.at<uchar>(i, j - 1) = 255;
				if (bw_l.at<uchar>(i, j + 1) == 255)
					bw.at<uchar>(i, j + 1) = 255;
				if (bw_l.at<uchar>(i + 1, j - 1) == 255)
					bw.at<uchar>(i + 1, j - 1) = 255;
				if (bw_l.at<uchar>(i + 1, j) == 255)
					bw.at<uchar>(i + 1, j) = 255;
				if (bw_l.at<uchar>(i + 1, j + 1) == 255)
					bw.at<uchar>(i + 1, j + 1) = 255;
			}
		}
	}

	Edgeimage = bw;
}

//5 canny函数
void canny_ly(Mat input_image, Mat& output_image, int th_high, int th_low, int Gauss_size, double sigmma)
{
	Mat Gaussimage, Gradimage, Direimage, Suppimage, Edgeimage;
	//1 高斯滤波函数
	Gaussfilter_ly(input_image, Gaussimage, Gauss_size, sigmma);
	//2 计算梯度幅值图像和方向图像
	Grad_dire_ly(Gaussimage, Gradimage, Direimage);
	//3 非极大值抑制图像
	Nonmax_suppression_ly(Gradimage, Direimage, Suppimage);
	//4 滞后阈值处理(双阈值)
	doubleThread_ly(Suppimage, Edgeimage, th_high, th_low);

	output_image = Edgeimage;
}

int main()
{
	Mat src = imread("E:\\la.jpg", 1);//读取灰度图像
	if (src.empty())
	{
		cout << "读取错误" << endl;
		return -1;
	}
	imshow("原图", src);

	Mat dst;
	//转灰度图像
	cvtColor(src, dst, COLOR_BGRA2GRAY);
	imshow("灰度", dst);

	Mat img2;
	canny_ly(dst, img2, 50, 20, 3, 1);
	imshow("Canny", img2);
	waitKey();

	return 0;
}

image-20221008151922700

4、函数实现Canny 算子边缘检测

int main()
{
	Mat src = imread("E:\\la.jpg", 1);//读取灰度图像
	if (src.empty())
	{
		cout << "读取错误" << endl;
		return -1;
	}
	imshow("原图", src);

	Mat dst;
	//转灰度图像
	cvtColor(src, dst, COLOR_BGRA2GRAY);
	imshow("灰度", dst);
	//均值滤波过滤
	blur(dst, dst, Size(3, 3));
	imshow("高斯滤波", dst);
	//opencv自带canny检测函数
	Canny(src, dst, 50, 150);
	imshow("Canny", dst);
	waitKey(0);

	return 0;
}

image-20221008150536078

Canny(
InputArray src, // 8-bit的输入图像,也就是单通道图像
OutputArray edges,// 输出边缘图像, 一般都是二值图像,背景是黑色
double threshold1,// 低阈值,常取高阈值的1/2或者1/3
double threshold2,// 高阈值
int aptertureSize,// Soble算子的size,通常3x3,取值3
bool L2gradient // 选择 true表示是L2来归一化,否则用L1归一化,一般我们选择L1,性能更好
)

部分参考来源数字图像处理(c++ opencv):图像分割-基本边缘检测–canny边缘检测 - 知乎 (zhihu.com)边缘检测之Canny - 走看看 (zoukankan.com)

  • 7
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值