自适应阈值canny边缘检测(功能实现)

学习记录…

概述

canny边缘检测是一种特别常用且性能优秀的边缘检测算法,相比于普通的边缘检测算法,canny获得的边缘较细且具有连续的边缘轮廓,为之后的一系列图像处理带来极大的便利。

canny边缘检测也是基于梯度图像的,通常在其局部最大值附近会包含一些宽脊,为了细化这些宽脊采用的方向就是非极大值抑制——梯度的本意是一个向量(矢量),函数在该点处沿着该方向(此梯度的方向)变化最快,变化率最大(为该梯度的模——即梯度图像像素值),梯度的方向是与边缘的方向垂直的,那么在一个3x3范围内,可以将梯度的方向进行分区:

在这里插入图片描述

梯度方向(角度)在不同的分区可以分别映射为水平方向(垂直边缘)、+45方向、垂直方向(水平边缘)、-45方向。

那么在确定某一点梯度方向所属分区所映射到的方向之后,就将该点梯度幅值与方向上的梯度幅值进行比较,若该点梯度幅值均大于方向上点的梯度幅值则保留,否则令为0。

改进

在canny边缘检测中,还有一个重要的步骤:双阈值的滞后阈值处理,一个高阈值TH和一个低阈值TL,比例在2:1到3:1内,(至于为什么会这样真不明白)这就带来了canny边缘检测的一个很大的缺点,那就是需要输入阈值参数,基于此,很多完全自适应阈值的canny算法诞生,在这里仅提供一种较简单和实用的思路——将经过非极大值抑制后的梯度图像利用Otsu算法算出一个阈值,将其作为一个高阈值TH,高阈值的一半作为低阈值TL

算法步骤小结

  1. 使用一个高斯滤波器平滑输入图像。
  2. 计算梯度幅值图像和角度图像。
  3. 对梯度幅值图像进行非极大值抑制。
  4. 将非极大值抑制获得的图像利用Otsu算法确定双阈值。
  5. 使用双阈值处理和连通域分析来检测与连接边缘。

具体内容可参照冈萨雷斯《数字图像处理》

具体代码如下

//确定一个点的坐标是否在图像内
bool checkInRang(int r, int c, int rows, int cols) {
	if (r >= 0 && r < rows && c >= 0 && c < cols)
		return true;
	else
		return false;
}

//从确定边缘点出发,延长边缘
void EdgePoint_Trace(cv::Mat& edgeMag_noMaxsup, cv::Mat& edge, unsigned TL, int r, int c, int rows, int cols)
{
	//如果边缘图未被标记
	if (edge.at<uchar>(r, c) == 0)
	{
		edge.at<uchar>(r, c) = 255;
		for (int i = -1; i <= 1; ++i)
		{
			for (int j = -1; j <= 1; ++j)
			{
				float mag = edgeMag_noMaxsup.at<float>(r + i, c + j);
				if (checkInRang(r + i, c + j, rows, cols) && mag >= TL)
					EdgePoint_Trace(edgeMag_noMaxsup, edge, TL, r + i, c + j, rows, cols);
			}
		}
	}
}

/********************************mian函数入口***************************************/
int main()
{
	string path = "F:\NoteImage\lena.jpg";

	Mat SrcImage = imread(path);
	if (!SrcImage.data) {
		std::cout << "Could not open or find the image" << std::endl;
		return -1;
	}

	cv::Mat grayImage, cannyImage;
	cvtColor(SrcImage, grayImage, COLOR_BGR2GRAY);
	//使图像连续并可导
	GaussianBlur(grayImage, grayImage, Size(3, 3), 0, 0);

	cv::Mat gx, gy;
	cv::Mat mag, angle;

	Sobel(grayImage, gx, CV_32F, 1, 0, 3);
	Sobel(grayImage, gy, CV_32F, 0, 1, 3);
	//计算梯度幅值和梯度的方向(角度)
	cv::cartToPolar(gx, gy, mag, angle, true);
	//定义全黑非极大值抑制图像
	cv::Mat Non_maxImage = cv::Mat::zeros(grayImage.size(), CV_32FC1);
	int height = grayImage.rows;
	int width = grayImage.cols;
	//获得非极大值抑制图像
	for (int i = 1; i < height - 1; ++i)
	{
		for (int j = 1; j < width - 1; ++j)
		{
			float g_angle = angle.at<float>(i, j);
			float K_mag = mag.at<float>(i, j);
			//梯度方向在垂直方向
			if ((g_angle <= 112.5 && g_angle > 67.5) || (g_angle <= 292.5 && g_angle > 247.5))
			{
				if (K_mag >= mag.at<float>(i - 1, j) && K_mag >= mag.at<float>(i + 1, j))
					Non_maxImage.at<float>(i, j) = K_mag;
			}
			//梯度方向在水平方向
			else if (g_angle <= 22.5 || g_angle > 337.5 || (g_angle <= 202.5 && g_angle > 157.5))
			{
				if (K_mag >= mag.at<float>(i, j - 1) && K_mag >= mag.at<float>(i, j + 1))
					Non_maxImage.at<float>(i, j) = K_mag;
			}
			//梯度方向在+45方向
			else if ((g_angle <= 67.5 && g_angle > 22.5) || (g_angle <= 247.5 && g_angle > 202.5))
			{
				if (K_mag >= mag.at<float>(i - 1, j - 1) && K_mag >= mag.at<float>(i + 1, j + 1))		
					Non_maxImage.at<float>(i, j) = K_mag;
			}
			//梯度方向在-45方向
			else if ((g_angle <= 337.5 && g_angle > 292.5) || (g_angle <= 157.5 && g_angle > 112.5))
			{
				if (K_mag >= mag.at<float>(i + 1, j - 1) && K_mag >= mag.at<float>(i - 1, j + 1))		
					Non_maxImage.at<float>(i, j) = K_mag;
			}
		}
	}
	//双阈值处理--根据Otsu算出的阈值确定为高阈值,取高阈值的一半记为低阈值
	unsigned TH = Otsu_threshold(Non_maxImage);
	unsigned TL = TH * 0.5;
	cv::Mat My_cannyImage = cv::Mat::zeros(grayImage.size(), grayImage.type());

	for (int i = 1; i < height - 1; ++i)
	{
		for (int j = 1; j < width - 1; ++j)
		{
			float K_mag = Non_maxImage.at<float>(i, j);
			//大于高阈值确定为边缘点
			if (K_mag > TH)
				EdgePoint_Trace(Non_maxImage, My_cannyImage, TL, i, j, height, width);
			else if (K_mag < TL)
				My_cannyImage.at<uchar>(i, j) = 0;
		}
	}

	//和OpenCV自带函数做对比
	Canny(grayImage, cannyImage, TH, TL, 3, true);


	imshow("src", My_cannyImage);
	cv::waitKey(0);
	return 0;

双阈值边缘连接处理要点采用了大佬的方法:canny算子边缘检测原理与实现

试验图例:

在这里插入图片描述

差别还是很微小的…

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
分享
二维码
< <上一篇
下一篇>>