OpenCV-边缘检测算子Canny实现

最优边缘检测方法所需的特性,给出了评价边缘检测性能优劣的3个指标:

1.低错误率,即将非边缘点判定为边缘点的概率要低,将边缘点判为非边缘点的概率要低;

2.高定位性,即检测出的边缘点要尽可能在实际边缘的中心;

3.对单一边缘仅有唯一响应,即单个边缘产生多个响应的概率要低,并且虚假响应边缘应该得到最大抑制;

Canny算法就是基于满足这3个指标的最优解实现的,在对图像中物体边缘敏感性的同时,也可以抑制或消除噪声的影响。


Canny算子边缘检测的具体步骤如下:

1.用高斯滤波器平滑图像

2.用Sobel等梯度算子计算梯度幅值和方向

3.对梯度幅值进行非极大值抑制,排除非边缘像素, 仅仅保留了一些细线条(候选边缘)

4.滞后阈值算法,需要两个阈值(低阈值和高阈值,推荐的高低阈值比在2:1到3:1之间),如果某一像素位置的幅值超过高阈值, 该像素被保留为边缘像素。如果某一像素位置的幅值小于低阈值, 该像素被排除。如果某一像素位置的幅值在两个阈值之间, 该像素仅仅在连接到一个高于高阈值的像素时被保留。


代码实现:

// 非极大值抑制  
void nonMaximumSuppression(cv::Mat &magnitudeimage, cv::Mat &directionimage)
{
	cv::Mat checkimage = cv::Mat(magnitudeimage.rows, magnitudeimage.cols, CV_8U);
	//迭代器初始化  
	cv::MatIterator_<float> it1 = magnitudeimage.begin<float>();
	cv::MatIterator_<float> it2 = directionimage.begin <float>();
	cv::MatIterator_<uchar> it3 = checkimage.begin<uchar>();
	cv::MatIterator_<float> it4 = magnitudeimage.end<float>();
	//计算对应方向  
	for (; it1 != it4; it1++, it2++, it3++)
	{
		//将方向进行划分,对每个方向进行幅值判断
		//pos()返回当前迭代器的位置
		const cv::Point pos = it3.pos();
		float currentDirection = atan(*it2) * (180 / CV_PI);
		while (currentDirection < 0) currentDirection += 180;
		*it2 = currentDirection;
		//边界限定,对相应方向进行判断  
		if (currentDirection > 22.5 && currentDirection <= 67.5)
		{
			//邻域位置极值判断
			if (pos.y > 0 && pos.x > 0 && *it1 <= magnitudeimage.at<float>(pos.y - 1, pos.x - 1))
				magnitudeimage.at<float>(pos.y, pos.x) = 0;
			if (pos.y < magnitudeimage.rows - 1 && pos.x < magnitudeimage.cols - 1 && *it1 <= magnitudeimage.at<float>(pos.y + 1, pos.x + 1))
				magnitudeimage.at<float>(pos.y, pos.x) = 0;
		}
		else if (currentDirection>67.5 && currentDirection <= 112.5)
		{
			//邻域位置极值判断  
			if (pos.y > 0 && *it1 <= magnitudeimage.at<float>(pos.y - 1, pos.x))
				magnitudeimage.at<float>(pos.y, pos.x) = 0;
			if (pos.y < magnitudeimage.rows - 1 && *it1 <= magnitudeimage.at<float>(pos.y + 1, pos.x))
				magnitudeimage.at<float>(pos.y, pos.x) = 0;
		}
		else if (currentDirection > 112.5 && currentDirection <= 157.5)
		{
			//邻域位置极值判断  
			if (pos.y > 0 && pos.x < magnitudeimage.cols - 1 && *it1 <= magnitudeimage.at<float>(pos.y - 1, pos.x + 1))
				magnitudeimage.at<float>(pos.y, pos.x) = 0;
			if (pos.y < magnitudeimage.rows - 1 && pos.x > 0 && *it1 <= magnitudeimage.at<float>(pos.y + 1, pos.x - 1))
				magnitudeimage.at<float>(pos.y, pos.x) = 0;
		}
		else
		{
			// 邻域位置极值判断  
			if (pos.x > 0 && *it1 <= magnitudeimage.at<float>(pos.y, pos.x - 1))
				magnitudeimage.at<float>(pos.y, pos.x) = 0;
			if (pos.x < magnitudeimage.cols - 1 && *it1 <= magnitudeimage.at<float>(pos.y, pos.x + 1))
				magnitudeimage.at<float>(pos.y, pos.x) = 0;
		}
	}
}

// 边缘连接  
void followEdges(int x, int y, cv::Mat &magnitude, int lowthresh, int highthresh, cv::Mat &result)
{
	result.at<float>(y, x) = 255;
	for (int i = -1; i < 2; i++)
	{
		for (int j = -1; j < 2; j++)
		{
			// 边界限制
			if (i == 0 && j == 0) continue;
			if ((x + i >= 0) && (y + j >= 0) && (x + i < magnitude.cols) && (y + j < magnitude.rows))
			{
				// 梯度幅值边缘判断及连接  
				if ((magnitude.at<float>(y + j, x + i) > lowthresh) && (result.at<float>(y + j, x + i) != 255))
					followEdges(x + i, y + j, magnitude, lowthresh, highthresh, result);
			}
		}
	}
}

// 边缘检测  
void edgeDetect(cv::Mat &magnitude, int lowthresh, int highthresh, cv::Mat &result)
{
	int rows = magnitude.rows, cols = magnitude.cols;
	result = cv::Mat(magnitude.size(), CV_32F, 0.0);
	for (int i = 0; i < rows; i++)
	{
		for (int j = 0; j < cols; j++)
		{
			// 梯度幅值判断  
			if (magnitude.at<float>(i, j) >= highthresh)
				followEdges(j, i, magnitude, lowthresh, highthresh, result);
		}
	}
}

void myCanny(cv::Mat src, cv::Mat &result, int lowthresh, int highthresh)
{
	cv::Mat image = src.clone();
	//高斯滤波
	cv::GaussianBlur(src, image, cv::Size(3, 3), 1.5);
	// 使用sobel计算相应的梯度幅值及方向  
	cv::Mat magX = cv::Mat(src.rows, src.cols, CV_32F);
	cv::Mat magY = cv::Mat(src.rows, src.cols, CV_32F);
	cv::Sobel(image, magX, CV_32F, 1, 0, 3);
	cv::Sobel(image, magY, CV_32F, 0, 1, 3);
	// 计算斜率  
	cv::Mat slopes = cv::Mat(image.rows, image.cols, CV_32F);
	cv::divide(magY, magX, slopes);
	// 计算每个点的梯度  
	cv::Mat sum = cv::Mat(image.rows, image.cols, CV_64F);
	cv::Mat prodX = cv::Mat(image.rows, image.cols, CV_64F);
	cv::Mat prodY = cv::Mat(image.rows, image.cols, CV_64F);
	multiply(magX, magX, prodX);
	multiply(magY, magY, prodY);
	sum = prodX + prodY;
	cv::sqrt(sum, sum);
	cv::Mat magnitude = sum.clone();
	//非极大值抑制  
	nonMaximumSuppression(magnitude, slopes);
	//边缘检测 
	edgeDetect(magnitude, lowthresh, highthresh, result);
}

运行结果:




OpenCV提供的Canny函数利用Canny算法来进行图像的边缘检测。


C++: void Canny(InputArray image,OutputArray edges, double threshold1, double threshold2, int apertureSize=3,bool L2gradient=false )  

image:输入图像
edges:输出的边缘图,需要和源图片有一样的尺寸和类型。
threshold1:第一个滞后性阈值。
threshold2:第二个滞后性阈值。
apertureSize:表示应用Sobel算子的模板大小,其有默认值3。
L2gradient:一个计算图像梯度幅值的标识,有默认值false。


代码实现:

int main()
{
	cv::Mat image = cv::imread("1.jpg", 0);
	if (image.data == NULL) return -1;
	cv::imshow("image", image);
	cv::Mat result1;
	int thresh = 50;
	cv::Canny(image, result1, thresh, thresh * 3, 3);
	cv::imshow("result1", result1);
	cv::waitKey(0);
	return 0;
}

运行结果:


  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值