Canny算法介绍

Canny边缘检测算法是一种多级边缘检测算法,John F. Canny于 1986 年开发出来。在许多方面的应用都有着它的身影。因此,不管是为了学习或者工作,学习Canny算法的原理和实现都是非常有必要的。废话不多说了,下面我将带领大家从原理和代码方面为您讲解Canny算法的基本思想。在本文的最后,会有一个基于Canny算法比较酷炫的简单项目,有兴趣的可以试试,比较有意思。

Canny算法原理

Canny边缘检测主要分为四步来实现的,每一步都有着其主要意义,基于这四个步骤,我将一一讲解其实现的细节。

第一步:高斯模糊

对于原图像我们需要对其进行高斯模糊,高斯模糊在之前我们已经实现过了,或者你可以使用Opencv自带的函数也行。对图像进行模糊能够去除图像的细节部分,图像噪声可大大减少,而图像的边缘信息不会损失什么,这样对后续检测的效果会更好。

/* 获取高斯分布数组               (核大小, sigma值) */
double **getGaussianArray(int arr_size, double sigma)
{
	int i, j;
	// [1] 初始化权值数组
	double **array = new double*[arr_size];
	for (i = 0; i < arr_size; i++) {
		array[i] = new double[arr_size];
	}
	// [2] 高斯分布计算
	int center_i, center_j;
	center_i = center_j = arr_size / 2;
	double pi = 3.141592653589793;
	double sum = 0.0f;
	// [2-1] 高斯函数
	for (i = 0; i < arr_size; i++) {
		for (j = 0; j < arr_size; j++) {
			array[i][j] =
				//后面进行归一化,这部分可以不用
				//0.5f *pi*(sigma*sigma) * 
				exp(-(1.0f)* (((i - center_i)*(i - center_i) + (j - center_j)*(j - center_j)) /
				(2.0f*sigma*sigma)));
			sum += array[i][j];
		}
	}
	// [2-2] 归一化求权值
	for (i = 0; i < arr_size; i++) {
		for (j = 0; j < arr_size; j++) {
			array[i][j] /= sum;
			//printf(" [%.15f] ", array[i][j]);
		}
		// printf("\n");
	}
	return array;
}


// 高斯模糊
Mat gaussianFilter(Mat &src, int size, double sigma)
{
	// 深拷贝
	Mat dst = src.clone();
	double** model = getGaussianArray(size, sigma);
	double sum;
	// 遍历像素
	for (int i = 0; i < src.rows; i++)
		for (int j = 0; j < src.cols; j++)
		{
			// 初始化
			sum = 0;
			// 遍历模板
			for (int n = -size / 2; n <= size / 2; n++)
				for (int m = -size / 2; m <= size / 2; m++)
				{
					// 边缘补零
					if (i + n < 0 || j + m < 0 || i + n >= src.rows || j + m >= src.cols)
						sum += 0;
					else
						sum += model[n + size / 2][m + size / 2] * src.at<uchar>(i + n, j + m);
				}
			dst.at<uchar>(i, j) = pixesDeal(sum);
		}
	// imshow("高斯模糊", dst);
	return dst;
}

// 像素判定
uchar pixesDeal(double n)
{
	if (n < 0)
		return 0;
	else if (n > 255)
		return 255;
	else
		return static_cast<uchar>(n);
}
图像处理——Canny边缘检测算法(原理+代码演示)图像处理——Canny边缘检测算法(原理+代码演示)

第二步:Sobel梯度算子

我们需要对模糊后的图像进行Sobel梯度算子的卷积,但这里的处理和之前我们实现过的Sobel图像锐化稍稍有点不同,之前我们是将x方向的Sobel模板和y方向的Sobel模板进行绝对值的相加(L1范数),但现在我们需要进行平方相加后开平方(L2范数),即幅值。还需要梯度方向的信息为下一步处理提供条件。获取的幅值图像大概描绘了整个图像的轮廓,但是这个边缘存在着很多不必要的信息,例如:边缘描绘过宽和噪声信息。

// sobel梯度计算
Mat sobelFilter(Mat &src)
{
	// 深拷贝
	Mat gradValue = src.clone();
	Mat gradDirect = src.clone();
	double sum1, sum2;
	// x方向边缘的y模板
	double model1[9] = { -1, -2, -1, 0, 0, 0, 1, 2, 1 };
	// y方向边缘的x模板
	double model2[9] = { -1, 0, 1, -2, 0, 2, -1, 0, 1 };
	int p;
	// 遍历像素
	for (int i = 0; i < src.rows; i++)
		for (int j = 0; j < src.cols; j++)
		{
			// 初始化
			p = 0, sum1 = 0, sum2 = 0;
			// 遍历模板
			for (int n = -1; n <= 1; n++)
				for (int m = -1; m <= 1; m++)
				{
					// 边缘补零
					if (i + n < 0 || j + m < 0 || i + n >= src.rows || j + m >= src.cols)
						p++;
					else
					{
						sum1 += src.at<uchar>(i + n, j + m) * model1[p];
						sum2 += src.at<uchar>(i + n, j + m) * model2[p];
						p++;
					}
				}
			gradValue.at<uchar>(i, j) = pixesDeal(pow(pow(sum1, 2) + pow(sum2, 2), 1.0 / 2));
			// 0,7垂直方向, 1,2斜对角方向\, 3,4水平方向, 5,6斜对角方向/
			gradDirect.at<uchar>(i, j) = static_cast<uchar>(atan(sum1 / (sum2 + 0.001)) * 8 / CV_PI + 4);
		}
	Mat temp[] = { gradValue, gradDirect };
	Mat grad;
	merge(temp, 2, grad);
	// imshow("梯度图像", gradValue);

	return grad;
}
图像处理——Canny边缘检测算法(原理+代码演示)

第三步:非极大值抑制(NMS)

非极大值抑制是一种将宽边缘带细化,只保留边缘峰值的处理。主要的手法是根据上一步获取的梯度方向,将图像像素点沿梯度方向或逆方向的邻域像素点进行比较,如果为最大值则保留,否则抑制,即设置像素点为0。

Mat NMS(Mat &src)
{
	vector<Mat> grad;
	// 梯度通道和方向通道
	split(src, grad);
	// 遍历中梯度值会改变,先拷贝一份
	Mat gradValue = grad[0].clone();
	// 遍历像素
	for (int i = 0; i < gradValue.rows; i++)
		for (int j = 0; j < gradValue.cols; j++)
		{
			if (grad[1].at<uchar>(i, j) == 0 || grad[1].at<uchar>(i, j) == 7)
			{
				if (i < gradValue.rows - 1)
					if (gradValue.at<uchar>(i, j) <= gradValue.at<uchar>(i + 1, j))
					{
						grad[0].at<uchar>(i, j) = 0;
						continue;
					}
				if (i > 0)
					if (gradValue.at<uchar>(i, j) <= gradValue.at<uchar>(i - 1, j))
					{
						grad[0].at<uchar>(i, j) = 0;
						continue;
					}
			}
			if (grad[1].at<uchar>(i, j) == 1 || grad[1].at<uchar>(i, j) == 2)
			{
				if (i < gradValue.rows - 1 && j < gradValue.cols - 1)
					if (gradValue.at<uchar>(i, j) <= gradValue.at<uchar>(i + 1, j + 1))
					{
						grad[0].at<uchar>(i, j) = 0;
						continue;
					}
				if (i > 0 && j > 0)
					if (gradValue.at<uchar>(i, j) <= gradValue.at<uchar>(i - 1, j - 1))
					{
						grad[0].at<uchar>(i, j) = 0;
						continue;
					}
			}
			if (grad[1].at<uchar>(i, j) == 3 || grad[1].at<uchar>(i, j) == 4)
			{
				if (j < gradValue.cols - 1)
					if (gradValue.at<uchar>(i, j) <= gradValue.at<uchar>(i, j + 1))
					{
						grad[0].at<uchar>(i, j) = 0;
						continue;
					}
				if (j > 0)
					if (gradValue.at<uchar>(i, j) <= gradValue.at<uchar>(i, j - 1))
					{
						grad[0].at<uchar>(i, j) = 0;
						continue;
					}
			}
			if (grad[1].at<uchar>(i, j) == 5 || grad[1].at<uchar>(i, j) == 6)
			{
				if (i < gradValue.rows - 1 && j > 0)
					if (gradValue.at<uchar>(i, j) <= gradValue.at<uchar>(i + 1, j - 1))
					{
						grad[0].at<uchar>(i, j) = 0;
						continue;
					}
				if (i > 0 && j < gradValue.cols - 1)
					if (gradValue.at<uchar>(i, j) <= gradValue.at<uchar>(i - 1, j + 1))
					{
						grad[0].at<uchar>(i, j) = 0;
						continue;
					}
			}
		}
	//imshow("非极大值处理", grad[0]);
	merge(grad, src);
	return src;
}
图像处理——Canny边缘检测算法(原理+代码演示)

第四步:双阈值连接

设置两个阈值,一个高阈值和一个低阈值。图像像素点如果高于高阈值则表示是强边缘点,是真实边缘点,高于低阈值但小于高阈值表示是弱边缘点,其它不是边缘,抑制。之后需要对弱边缘点进行处理,若其邻域存在强边缘点则表示是真实边缘点,否则不是。这样设置两个阈值可以滤除图像中噪声,改善图像质量。弱边缘的处理原则是因为真实边缘的弱边缘点都存在强边缘点。

Mat doubleThrCon(Mat &src, uchar high, uchar low)
{
	vector<Mat> grad;
	// 梯度通道和方向通道
	split(src, grad);
	for (int i = 0; i < grad[0].rows; i++)
		for (int j = 0; j < grad[0].cols; j++)
		{
			if (grad[0].at<uchar>(i, j) > high)
			{
				grad[0].at<uchar>(i, j) = 255;
				// 标记强边缘点的位置
				grad[1].at<uchar>(i, j) = 2;
			}
			else if (grad[0].at<uchar>(i, j) > low)
			{
				grad[0].at<uchar>(i, j) = 0;
				// 标记弱边缘点的位置
				grad[1].at<uchar>(i, j) = 1;
			}
			else
			{
				grad[0].at<uchar>(i, j) = 0;
				grad[1].at<uchar>(i, j) = 0;
			}
		}
	// 真实的边缘会在弱边缘点的邻域内存在强边缘点
	for (int i = 0; i < grad[0].rows; i++)
		for (int j = 0; j < grad[0].cols; j++)
		{
			if (grad[1].at<uchar>(i, j) == 1)
			{
				for (int n = -1; n <= 1; n++)
					for (int m = -1; m <= 1; m++)
					{
						if (i + n >= 0 && j + m >= 0 && i + n < src.rows && j + m < src.cols && grad[1].at<uchar>(i + n, j + m) == 2)
							grad[0].at<uchar>(i, j) = 255;
					}
			}
		}

	return grad[0];

}
图像处理——Canny边缘检测算法(原理+代码演示)

视频流Canny边缘检测(阈值可调)

应用我们自己编写的Canny算法速度比较慢,因此我们选择系统提供的Canny算法效果会流畅。可以减少对摄像头的帧数处理来加快速度。

// 主函数
int main()
{
	Mat frameImage;
	VideoCapture capture1(0 + CAP_DSHOW);
	// 我用的是手机摄像头,用电脑摄像头的不用填open()里面的参数
	capture1.open("http://admin:[email protected]:8081");
	if (!capture1.isOpened())
		return 0;

	int n = 0;
	int highThreshold=70, lowThreshold=70;
	//创建窗口
	namedWindow("out", 1);

	//创建轨迹条
	createTrackbar("high threshold", "out", &highThreshold, 255);
	createTrackbar("low threshold", "out", &lowThreshold, 255);

	while (true)
	{
		capture1 >> frameImage;
		if (n % 3 == 0)
		{
			cvtColor(frameImage, frameImage, COLOR_BGR2GRAY);
			Canny(frameImage, frameImage, lowThreshold, highThreshold);
			//frameImage = myCanny(frameImage);
			imshow("out", frameImage);
		}
		if (char(waitKey(1)) == 'q') break;
		n++;
	}
	return 0;
}

结果视频我就不发了,有兴趣的可以自己运行一下~

相关文章:

  • 2021-09-18
  • 2022-12-23
  • 2021-09-12
  • 2022-12-23
  • 2021-12-07
  • 2021-12-14
  • 2022-12-23
  • 2021-06-04
猜你喜欢
  • 2021-12-09
  • 2022-12-23
  • 2022-12-23
  • 2022-01-16
  • 2021-09-09
  • 2021-07-04
  • 2021-05-23
相关资源
相似解决方案