Canny边缘检测的c++&&OpenCV实现

Canny边缘检测算法是一种比较常见且效果较好的边缘检测算法,其优点在于得到的边缘为单像素边缘,但是速度较Sobel等边缘检测算法较慢。网上Canny算法的算法介绍很多,此处不仔细写了,主要分为五步,分别是:高斯去噪,梯度计算、梯度方向计算、非极大值抑制和双阈值边缘抑制。其中,非极大值抑制保证了边缘检测结果为单像素边缘,双阈值边缘抑制的作用为去掉孤立的低阈值边缘。以下代码为Canny算子的简单实现,其中包含了Sobel算子的实现和高斯去噪的简单版本。

高斯去噪:

cv::Mat GaussBlur(__in__ const cv::Mat& src) {
	int row = src.rows;
	int col = src.cols;
	cv::Mat Gauss_x(row, col, CV_8UC1);
	cv::Mat des(row, col, CV_8UC1);

	for (int i = 0; i < row; ++i) {
		for (int j = 1; j < col - 1; ++j) {
			int temp = ((int)src.data[i * col + j] << 1) + src.data[i * col + j - 1] + src.data[i * col + j + 1];
			Gauss_x.data[i * col + j] = (temp >> 2);
		}
	}
	for (int i = 1; i < row - 1; ++i) {
		for (int j = 1; j < col - 1; ++j) {
			int temp = ((int)Gauss_x.data[i * col + j] << 1) + Gauss_x.data[i * col + j - col] + Gauss_x.data[i * col + j + col];
			des.data[i * col + j] = (temp >> 2);
		}
	}

	return des;
}

 

Sobel算子:

cv::Mat SobelOperator(__in__ const cv::Mat& src, __out__ cv::Mat& Gx, __out__ cv::Mat& Gy) {

	int row = src.rows;
	int col = src.cols;

	cv::Mat des(row, col, CV_16UC1);
	Gx = cv::Mat(row, col, CV_16SC1);
	Gy = cv::Mat(row, col, CV_16SC1);

	for (int i = 1; i < row - 1; ++i) {
		for (int j = 1; j < col - 1; ++j) {
			int temp = 0;
			auto pdata = &src.data[i * col + j];

			temp = *(pdata - col - 1) + (((int)*(pdata - col)) << 1) + *(pdata - col + 1);
			temp -= (*(pdata + col - 1) + (((int)*(pdata + col)) << 1) + *(pdata + col + 1));
			Gy.at<short>(i, j) = temp;
			auto gy = temp;

			temp = *(pdata - col - 1) + (((int)*(pdata - 1)) << 1) + *(pdata + col - 1);
			temp -= *(pdata - col + 1) + (((int)*(pdata + 1)) << 1) + *(pdata + col + 1);
			Gx.at<short>(i, j) = temp;
			auto gx = temp;

			des.at<ushort>(i, j) = (ushort)sqrt(gy * gy + gx * gx);
		}
	}

	return des;
}

Canny算子:


cv::Mat CannyEdgeDetector(__in__ const cv::Mat& src, __in__ const float up, __in__ const float down) {
	
	const int row = src.rows;
	const int col = src.cols;
	const double A2R = 1.0 * 180.0 / 3.1415926535;
	//Step 1: Gaussian filtering to remove noise
	cv::Mat Gauss = GaussBlur(src);

	//Step 2: Sobel Operator
	cv::Mat Gx;//CV_16UC1
	cv::Mat Gy;//CV_16UC1
	cv::Mat G = SobelOperator(Gauss, Gx, Gy);//CV_16SC1

	//Step 3: Finding Gradient angle theta and tracing the edge in the image using theta
	cv::Mat theta = cv::Mat(row, col, CV_32FC1);
	for (int i = 1; i < row; ++i) {
		for (int j = 1; j < col; ++j) {
			 theta.at<float>(i, j) = (float)atan2(Gy.at<short>(i, j), Gx.at<short>(i, j) );
		}
	}

	uint max_pixel_value = 0;
	cv::Mat non_max = G.clone();
	//Step 4: Non-maximum suppression
	for (int i = 1; i < row - 1; ++i) {
		for (int j = 1; j < col - 1; ++j) {
			ushort* pdata = &G.at<ushort>(i, j);
			ushort* pnew = &non_max.at<ushort>(i, j);
			float th = theta.at<float>(i, j);
			if (th < 0)th += CV_PI;
			float angle = th * A2R;
			if (angle < 22.5 || angle >= 157.5) {
				if (*pdata < *(pdata + 1) || *pdata < *(pdata - 1))*pnew = 0;
			}
			else if (angle >= 22.5 && angle < 67.5){
				if (*pdata < *(pdata + col - 1) || *pdata < *(pdata - col + 1))*pnew = 0;
			}
			else if (angle >= 67.5 && angle < 112.5) {
				if (*pdata < *(pdata + col) || *pdata < *(pdata - col))*pnew = 0;
			}
			else if (angle >= 112.5 && angle < 157.5) {
				if (*pdata < *(pdata + 1 + col) || *pdata < *(pdata - 1 - col))*pnew = 0;
			}
			else *pnew = *pdata;
			
			if (max_pixel_value < *pdata)max_pixel_value = *pdata;
		}
	}

	cv::Mat result(row, col, CV_8UC1, cv::Scalar(0));
	//Step 5: Hysteresis Thresholding using two thresholds
	uint* account = new uint[max_pixel_value + 1]();
	for (int i = 1; i < row - 1; ++i) {
		for (int j = 1; j < col - 1; ++j) {
			account[non_max.at<ushort>(i, j)]++;
		}
	}
	//chack the thresholds' legality
	if (up < down) {
		cout << "up is bigger than down\n";
		throw param_exception("up is bigger than down");
	}
	if (up > 1) {
		cout << "up is bigger than 1\n";
		throw param_exception("up is bigger than 1");
	}
	if (down < 0) {
		cout << "down is lower than 0\n";
		throw param_exception("down is lower than 0");
	}
	
	int all = (row - 2) * (col - 2) - account[0];
	const int up_account = all * up;
	const int down_account = all * down;
	int up_num, down_num;
	while (all > up_account) {
		all -= account[max_pixel_value--];
	}
	up_num = ++max_pixel_value;
	while (all > down_account) {
		all -= account[max_pixel_value--];
	}
	down_num = ++max_pixel_value;

	for (int i = 1; i < row - 1; ++i) {
		for (int j = 1; j < col - 1; ++j) {
			if (non_max.at<ushort>(i, j) >= up_num)result.data[i * col + j] = 255;
			else if(non_max.at<ushort>(i, j) <= down_num)result.data[i * col + j] = 0;
			else result.data[i * col + j] = 127;
		}
	}
	for (int i = 1; i < row - 1; ++i) {
		for (int j = 1; j < col - 1; ++j) {
			if (127 == result.data[i * col + j]) {
				uchar* pdata = &result.data[i * col + j];
				if (*(pdata - 1) || *(pdata + 1) || *(pdata - 1 - col) || *(pdata - 1 + col) || *(pdata + 1 - col) || *(pdata + 1 + col) || *(pdata - col) || *(pdata + col))*pdata = 255;
			}
		}
	}

	return result;
}

使用这段代码需要#include <opencv.hpp>, 其中涉及了一段异常抛出,各位看官可以直接删除掉那段代码,或者直接用c++的exception。

需要注意几个坑:

第一:Sobel算子的计算结果并非CV_8UC1

第二:注意atan2的计算,不要把Gx和Gy搞反

第三,非极大值抑制的时候不要在G的原图上改,否则画出来的边缘和素描一样,并非单边缘

第四,openCV里面的双阈值为像素具体值,而本程序里面用的是百分比,测试中使用的值up为0.93, down为0.9,效果还可以。

此外,非极大值抑制部分有另一个版本的形式,下面为另一个版本,对比来看,前一个版本应该更好用一点

//Step 4: Non-maximum suppression
	for (int i = 1; i < row - 1; ++i) {
		for (int j = 1; j < col - 1; ++j) {
			ushort* pdata = &G.at<ushort>(i, j);
			ushort* pnew = &non_max.at<ushort>(i, j);
			float th = theta.at<float>(i, j);
			if (th < 0)th += CV_PI;
			float angle = th * A2R;
			double tanth = tan(th);
			double inv_tanth = 1. / tanth;

			if (th <= 45.0) {
				double data1 = *(pdata - col + 1) * tanth + (1.0 - tanth) * (*(pdata + 1));
				double data2 = *(pdata + col - 1) * tanth + (1.0 - tanth) * (*(pdata - 1));
				if (*pdata < data1 || *pdata < data2)*pnew = 0;
			}
			else if (th <= 90.0) {
				double data1 = *(pdata - col + 1) * inv_tanth + (1.0 - inv_tanth) * (*(pdata - col));
				double data2 = *(pdata + col - 1) * inv_tanth + (1.0 - inv_tanth) * (*(pdata + col));
				if (*pdata < data1 || *pdata < data2)*pnew = 0;
			}
			else if (th <= 135.0) {
				double data1 = *(pdata - col - 1) * (-inv_tanth) + (1.0 + inv_tanth) * (*(pdata - col));
				double data2 = *(pdata + col + 1) * (-inv_tanth) + (1.0 + inv_tanth) * (*(pdata + col));
				if (*pdata < data1 || *pdata < data2)*pnew = 0;
			}
			else {
				double data1 = *(pdata - col - 1) * (-tanth) + (1.0 + tanth) * (*(pdata - 1));
				double data2 = *(pdata + col + 1) * (-tanth) + (1.0 + tanth) * (*(pdata + 1));
				if (*pdata < data1 || *pdata < data2)*pnew = 0;
			}
			
			if (max_pixel_value < *pdata)max_pixel_value = *pdata;
		}
	}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值