Canny算子边缘检测与改进

Canny算子边缘检测与改进 摘要:边缘检测是图像处理中很重要的一环,针对图像的原始特征对算法,提出了一种基于Canny算子边缘检测的算法。采用中值滤波代替高斯滤波去噪工作,因为中值滤波更能消除椒盐噪声。采用33的sobel算子代替了22的Reberts算子来计算梯度幅值和梯度。自适应双阈值的算法能够很好的根据图像选取双阈值,结果很好的检测到图像梯度,有效抑制假边缘和噪声边缘,提高边缘检测的性能。 1.引言 图像边缘检测是图像分析和图像识别的基础。常用的边缘检测微分算子有一阶(Reberts,sobel,Prewitt,Krish),二阶算子(laplacian)。这些算子都是通过原图像素和模板进行卷积来提取边缘,特点计算简单,易于实现,但常常会丢失一些边缘信息。 本文在传统Canny算法上的改进,1.用均值滤波对原图像滤波,2用sobel算子模板的一阶微分提取边缘,计算出梯度和梯度幅值。3.根据计算的梯度,和梯度幅值进行非极大值抑制。4自适应选取双阈值,对图像进行阈值化。5最后根据高低阈值处理后,进行边缘连接。

2传统的Canny算子 传统的Canny边缘检测算子的基本思想是先进行高斯滤波,高斯滤波是通过图像卷积进行的,然后通过一阶微分算子对边缘进行提取。然后通过梯度进行非极大值抑制,在一阶微分算子中只采用了水平,竖直放行而没考虑45°和135°方向来提取边缘。高低阈值需要进行自己设置,或者用outs方法进行双阈值的求取。根据高低阈值处理后的图像进行边缘连接。 3.本文canny算法的实现 滤波处理 滤波的方法。在我们接触过程中有很多种滤波,均值滤波,中值滤波,高斯滤波,和双边滤波。 每种滤波各有优缺点如下: 均值滤波在去噪声的同时会有如下缺点:边界模糊效应明显,细节丢失比较严重。 中值滤波在边界的保存方面优于均值滤波,是经常使用的一种滤波器。但是在模板逐渐变大时,依然会存在一定的边界模糊中值滤波对处理椒盐噪声非常有效,或者称为脉冲噪声。 高斯滤波是基于空间域的一直滤波,离中心越近权重越大,能很好的模糊图像,对边缘保存不是很好。 本文采用双边滤波,这里主要简单介绍一下Bilateral方法(双边滤波),这主要是由于前段时间做了SSAO,需要用bilateral blur 算法进行降噪。Bilateral blur相对于传统的高斯blur来说很重要的一个特性即可可以保持边缘(Edge Perseving),这个特点对于一些图像模糊来说很有用。一般的高斯模糊在进行采样时主要考虑了像素间的空间距离关系,但是却并没有考虑像素值之间的相似程度,因此这样我们得到的模糊结果通常是整张图片一团模糊。Bilateral blur的改进就在于在采样时不仅考虑像素在空间距离上的关系,同时加入了像素间的相似程度考虑,因而可以保持原始图像的大体分块进而保持边缘。在于游戏引擎的post blur算法中,bilateral blur常常被用到,比如对SSAO的降噪。 原理 首先介绍一下传统高斯滤波的原理 根据高斯公式求解出高斯模板,然后对模板进行归一化处理,归一化后和图像像素进行卷积处理。处理后就得到了高斯滤波的图像。 双边滤波原理 根据公式,先计算尺空间域的权重,在算出像素域的权重,算出模板进行归一化,然后卷积处理。 的自适应取值, 取值越小,图像边缘和细节越清楚,越大图像就越模糊边缘损失就会很大 ,所以根据图像的像素来设置取值能得到更好滤波。

梯度大小和方向计算 对像素进一阶求导,或者进行二阶求导。 求导模板的处理 这个模板比传统模板好,因为它更精确,对X,Y,45,135,方向进行了计算。

自适应双阈值处理 我采用的是OTSU最大类间方差的处理,找到双阈值,但是我算出的阈值对有些图像不是太合适,于是采取了一个滚动条,在求取出自适应双阈值的条件下微调,阈值达到最佳的效果。 非极大值抑制处理 图像梯度幅值矩阵中的元素值越大,说明图像中该点的梯度值越大,但这不不能说明该点就是边缘(这仅仅是属于图像增强的过程)。在Canny算法中,非极大值抑制是进行边缘检测的重要步骤,通俗意义上是指寻找像素点局部最大值,将非极大值点所对应的灰度值置为0,这样可以剔除掉一大部分非边缘的点。 C点是一个判断点,判断dTmp1 和dTmp2,进行判断,如果C大于两者就保留,不然就抑制说明不是边缘点。

边缘链接 方法 DFS深度优先搜索。 在大于高阈值的为真实边缘。 小于低阈值就丢弃,在非极大值抑制已经丢弃一部分。 介于高低阈值之间相当于伪边缘。 这部分伪边缘就是要操作的部分。 参考文献

代码如下

// AirTagetDetection.cpp: 定义控制台应用程序的入口点。
//

#include"stdafx.h"
#include<opencv2opencv.hpp>
#include<iostream>
#include<math.h>
#include<algorithm>
using namespace std;
using namespace cv;
Mat pic;
int y5=0,count1=0,maxc=0;
//双边滤波
Mat  BilateralFilter(Mat pic)
{
	Mat c(pic.size(), pic.type());
	double temp[3][3], sum;
	double d = 1, r ;
	for (int i = 1; i < pic.rows - 1; i++)
	{
		for (int j = 1; j < pic.cols - 1; j++)
		{
			int tem[3][3] = {1,-2,1,-2,4,-2,1 - 2,1};
			r = (int)(1.0* fabs(pic.at<uchar>(i - 1, j - 1) * tem[0][0]) + fabs(pic.at<uchar>(i, j - 1) * tem[1][0]) + fabs(pic.at<uchar>(i + 1, j - 1)* tem[2][0]) + fabs(pic.at<uchar>(i - 1, j) * tem[0][1]) + fabs(pic.at<uchar>(i, j) * tem[1][1]) + fabs(pic.at<uchar>(i + 1, j) * tem[2][1]) + fabs(pic.at<uchar>(i - 1, j + 1) * tem[0][2]) +fabs( pic.at<uchar>(i, j + 1) * tem[1][2]) +fabs( pic.at<uchar>(i + 1, j + 1) * tem[2][2]));
			r = 3*sqrt(3.14159*1.0 / 2) * 1 / (6 * (pic.rows - 2)*(pic.cols - 2))*r;
			sum = 0;
			for (int ij = -1, k1 = 0; ij < 2; ij++, k1++)
			{
				for (int ji = -1, k = 0; ji< 2; ji++, k++)
				{

					double t = (fabs(pic.at<uchar>(i, j) - pic.at<uchar>(i+ij,j+ji))*fabs(pic.at<uchar>(i, j) - pic.at<uchar>(i+ij, j+ji)))*1.0/ 2 / r / r;
					double s = (ij*ij + ji*ji)*1.0 / 2 / d / d;
					temp[k1][k] = exp(-s-t);
					sum += temp[k1][k];
				}
			}
			int b = (int)(1.0* pic.at<uchar>(i - 1, j - 1) * temp[0][0] + pic.at<uchar>(i, j - 1) * temp[1][0] + pic.at<uchar>(i + 1, j - 1)* temp[2][0] + pic.at<uchar>(i - 1, j) * temp[0][1] + pic.at<uchar>(i, j) * temp[1][1] + pic.at<uchar>(i + 1, j) * temp[2][1] + pic.at<uchar>(i - 1, j + 1) * temp[0][2] + pic.at<uchar>(i, j + 1) * temp[1][2] + pic.at<uchar>(i + 1, j + 1) * temp[2][2]);
			b = b / sum;
			c.at<uchar>(i, j) = b;
		}
	}
	namedWindow("can", 1);
	imshow("can", c);
	return c;
}
//高斯滤波
Mat gaoshi(Mat pic)
{
	Mat c(pic.size(), pic.type());
	double temp[3][3];
	double x = 0.8;
	for (int ij = -1, k1 = 0; ij < 2; ij++, k1++)
	{
		for (int ji = -1, k = 0; ji< 2; ji++, k++)
		{

			temp[k1][k] = 1.0 / (2 * 3.14159*x*x)*exp(-(ij*ij + ji*ji)*1.0 / 2 / x / x);
		}
	}
	double sum = 0;
	for (int ij = 0; ij < 3; ij++)
	{
		for (int ji = 0; ji < 3; ji++)
		{
			sum += temp[ij][ji];
		}
	}
	for (int ij = 0; ij < 3; ij++)
	{
		for (int ji = 0; ji < 3; ji++)
		{
			temp[ij][ji] /= sum;
			cout << temp[ij][ji] << ends;
		}
		cout << endl;
	}
	for (int i = 1; i < pic.rows - 1; i++)
	{
		for (int j = 1; j < pic.cols - 1; j++)
		{
		  int b = (int)(1.0* pic.at<uchar>(i - 1, j - 1) * temp[0][0] + pic.at<uchar>(i, j - 1) * temp[1][0] + pic.at<uchar>(i + 1, j - 1)* temp[2][0] + pic.at<uchar>(i - 1, j) * temp[0][1] + pic.at<uchar>(i, j) * temp[1][1] + pic.at<uchar>(i + 1, j) * temp[2][1] + pic.at<uchar>(i - 1, j + 1) * temp[0][2] + pic.at<uchar>(i, j + 1) * temp[1][2] + pic.at<uchar>(i + 1, j + 1) * temp[2][2]);
			c.at<uchar>(i, j) = b;
		}
	}
	namedWindow("can", 1);
	imshow("can", c);
	return c;
}
//DFS深度收索
void TraceEdge(int w, int h, int nThrHig, int nThrLow, Mat c1, Mat c2)
{
	if (w > 0 && w < c1.rows - 1 && h>0 && h < c1.cols - 1)
	{
		//对8邻域像素进行查询 
		for (int i = w - 1; i< w + 2; i++)
		{
			for (int j = h - 1; j <h + 2; j++)

				if (c2.at<uchar>(i, j) !=0)
				{
					//该点设为边界点
					c2.at<uchar>(i, j) = 0;
					c1.at<uchar>(i, j) = 255;
					//以该点为中心再进行跟踪  
				    TraceEdge(i, j, nThrHig, nThrLow, c1, c2);
				}
		}
	}
}

//R行,c列
void canny(Mat pic,int& y5,int& count,int& maxc)
{
    Mat c;
	c=BilateralFilter(pic);
	//bilateralFilter(pic,c,0.4,0.4,3);
	//imshow("cscs",c);
	//c = gaoshi(pic);//高斯滤波
	Mat c1(pic.size(), pic.type());
	Mat c2(pic.size(), pic.type());
	int maxM = 0;
	int **M = new int*[c.rows];//储存每个点梯度幅值的数组
	int **N = new int*[c.rows];
	int **jiao = new int*[c.rows]; //储存每个点梯度角度的数组
	double **g1 = new double *[c.rows];//储存每个点X的一阶偏导的数组
	double **g2 = new double *[c.rows];//储存每个点y的一阶偏导的数组
	double **g3 = new double *[c.rows];//储存每个点45的一阶偏导的数组
	double **g4 = new double *[c.rows];//储存每个点135的一阶偏导的数组
	for (int j = 0; j < c.rows; j++)
	{
		M[j] = new int[c.cols];
		N[j] = new int[c.cols];
		jiao[j] = new int[c.cols];
		g1[j] = new double[c.cols];
		g2[j] = new double[c.cols];
		g3[j] = new double[c.cols];
		g4[j] = new double[c.cols];
	}
	for (int i = 0; i < c.rows; i++)
	{
		for (int j = 0; j < c.cols; j++)
		{
			N[i][j] = 0;
			M[i][j] = 0;
		}
	}
	//梯度角度的计算并保存
	for (int i = 1; i < c.rows - 1; i++)
	{
		for (int j = 1; j < c.cols - 1; j++)
		{
			int a1 = c.at<uchar>(i - 1, j - 1);
			int a2 = c.at<uchar>(i - 1, j);
			int a3 = c.at<uchar>(i - 1, j + 1);
			int a4 = c.at<uchar>(i, j - 1);
			int a5 = c.at<uchar>(i, j);
			int a6 = c.at<uchar>(i, j + 1);
			int a7 = c.at<uchar>(i + 1, j - 1);
			int a8 = c.at<uchar>(i + 1, j);
			int a9 = c.at<uchar>(i + 1, j + 1);
			g1[i][j] = a7 + 2 * a8 + a9 - a1 - 2 * a2 - a3;
			g2[i][j] = a3 + 2 * a6 + a9 - a1 - 2 * a4 - a7;
			g3[i][j] = a6 + 2 * a8 + a9 - a1 - 2 * a2 - a4;
	        g4[i][j] = a2 + 2 * a3 + a6 - a4 - 2 * a7 - a8;
			M[i][j] = sqrt(g1[i][j] * g1[i][j] + g2[i][j] * g2[i][j]+ g3[i][j] * g3[i][j]+ g4[i][j] * g4[i][j]);
			double hu = atan2(g2[i][j], g1[i][j]);
			/*g1[i][j] = (a8 + a9 - a5 - a6) / 2;
			g2[i][j] = (a9 + a6 - a5 - a8) / 2;
			M[i][j] = sqrt(g1[i][j] * g1[i][j] + g2[i][j] * g2[i][j]);
			double hu = atan2(g2[i][j], g1[i][j]);*/
			if (M[i][j] > maxM)
			{
				maxM = M[i][j];
			}
			jiao[i][j] = hu * 180 / 3.14159;
				     if (jiao[i][j] < 0)
					{
						jiao[i][j] = 360 + jiao[i][j];
					}
				}
			}
	int y1 = 0;
	int maxt = maxM / 255 + 1;
	maxc = maxM / maxt;
	if (count == 0)
	{	
		//求取双阈值
			double Host[255] = { 0 };
			double p[255] = { 0 };
			double v = 0;
			double w = 0;
			for (int i = 1; i < c.rows - 1; i++)
			{
				for (int j = 1; j < c.cols - 1; j++)
				{
					M[i][j] /= maxt;
					Host[M[i][j]]++;
				}
			}
			for (int i = 0; i <= maxc; i++)
			{
				p[i] = Host[i] / ((c.rows - 2)* (c.cols - 2));
				w += i * p[i];
			}
			double maxv = 0;
			for (int i = 0; i <= maxc; i++)
			{
				double p1 = 0;
				double m1 = 0;
				for (int k = 0; k < i; k++)
				{
					p1 += p[k];
					m1 += k*p[k];
				}
				//m1 = m1*1.0 / p1;
				for (int j = i; j <= maxc; j++)
				{
					double p2 = 0,p3=0,m2=0,m3=0;
					for (int l = i; l < j; l++)
					{
						p2 += p[l];
						m2 += l*p[l];
					}
					//m2 = m2*1.0 / p2;
					p3 = 1 - p1 - p2;
					m3 = w - m1 - m2;
					//m3 = m3*1.0/p3;
					v = p1*(m1 - w)*(m1 - w) + p2*(m2 - w)*(m2 - w)+p3*(m3 - w)*(m3 - w);
					if (v >maxv)
					{
						maxv = v;
						y1 = i;
						y5 = j;
					}
				}
			}
			cout << y1 << " " << y5 << endl;
		  }
			int r1 = y5*0.7, r2 = y5*0.4;
			//对梯度幅值进行非极大值抑制
			for (int i = 1; i < c.rows - 1; i++)
			{
				for (int j = 1; j < c.cols - 1; j++)
				{			
					int y1 = 0, y2 = 0, y3 = 0, y4 = 0;
			        double dTmp1 = 0, dTmp2 = 0, dweight = 0;
					if (M[i][j] == 0)
					{
						N[i][j] = 1;
					}
					else if (jiao[i][j] >= 0 && jiao[i][j]<45 || jiao[i][j] >= 180 && jiao[i][j]<225)
					{
						y1 = M[i - 1][j - 1];
						y2 = M[i - 1][j];
						y3 = M[i + 1][j];
						y4 = M[i + 1][j + 1];
						if (jiao[i][j] < 45) dweight = jiao[i][j] / 45;
						else dweight = (jiao[i][j] - 180) / 45;
						dTmp1 = y2*dweight + y1*(1 - dweight);
						dTmp2 = y3* dweight + y4 * (1 - dweight);
					}
					else if (jiao[i][j] >= 45 && jiao[i][j]<90 || jiao[i][j] >= 225 && jiao[i][j]<270)
					{
						y1 = M[i - 1][j - 1];
						y2 = M[i][j - 1];
						y3 = M[i][j + 1];
						y4 = M[i + 1][j + 1];
						if (jiao[i][j] < 90) dweight = (jiao[i][j]-45) / 45;
						else dweight = (jiao[i][j] - 225) / 45;
						dTmp1 = y1*dweight + y2*(1 - dweight);
						dTmp2 = y4* dweight + y3 * (1 - dweight);
					}
					else if (jiao[i][j] >= 135 && jiao[i][j]<180 || jiao[i][j] >= 315 && jiao[i][j]<360)
					{
						y1 = M[i - 1][j];
						y2 = M[i - 1][j + 1];
						y3 = M[i + 1][j];
						y4 = M[i + 1][j - 1];
						if (jiao[i][j] < 180) dweight = (jiao[i][j] - 135) / 45;
						else dweight = (jiao[i][j] - 315) / 45;
						dTmp1 = y2*dweight + y1*(1 - dweight);
						dTmp2 = y4 * dweight + y3 * (1 - dweight);
					}
					else if (jiao[i][j] >= 90 && jiao[i][j]<135 || jiao[i][j] >= 270 && jiao[i][j]<315)
					{
						y1 = M[i - 1][j + 1];
						y2 = M[i][j + 1];
						y3 = M[i][j -1];
						y4 = M[i+1][j-1];
						if (jiao[i][j] < 135) dweight = (jiao[i][j] - 90) / 45;
						else dweight = (jiao[i][j] - 270) / 45;
						dTmp1 = y2*dweight + y1*(1 - dweight);
						dTmp2 = y3 * dweight + y4 * (1 - dweight);
					}
					if (M[i][j] >= dTmp1&&M[i][j] >= dTmp2);
					else  N[i][j] = 1;
				}
			}
			//int m,t1;
			对梯度幅值进行非极大值抑制
			//for (int i = 1; i < c.rows - 1; i++)
			//{
			//	for (int j = 1; j < c.cols - 1; j++)
			//	{
			//		double s = jiao[i][j];
			//		m = M[i][j];
			//		t1 = s / 22.5;
			//		switch (t1)
			//		{
			//		case 0:
			//		case 7:
			//		case 0+8:
			//		case 7+8:
			//			if (m < M[i + 1][j] || m < M[i - 1][j])
			//			{
			//				N[i][j] = 1;
			//			}
			//			else
			//			{
			//				N[i][j] = 0;
			//			}; break;
			//		case 1:
			//		case 2:
			//		case 1+8:
			//		case 2+8:
			//			if (m < M[i + 1][j + 1] || m < M[i - 1][j - 1])
			//			{
			//				N[i][j] = 1;
			//			}
			//			else
			//			{
			//				N[i][j] = 0;
			//			}; break;
			//		case 3:
			//		case 4:
			//		case 3+8:
			//		case 4+8:
			//			if (m < M[i][j + 1] || m < M[i][j - 1])
			//			{
			//				N[i][j] = 1;
			//			}
			//			else
			//			{
			//				N[i][j] = 0;
			//			}; break;
			//		case 5:
			//		case 6:
			//		case 5+8:
			//		case 6+8:
			//			if (m < M[i - 1][j + 1] || m < M[i + 1][j - 1])
			//			{
			//				N[i][j] = 1;
			//			}
			//			else
			//			{
			//				N[i][j] = 0;
			//			}; break;
			//		}
			//	}
			//}
			//去除非边缘点
			for (int i = 1; i < c.rows - 1; i++)
			{
				for (int j = 1; j < c.cols - 1; j++)
				{
					if (N[i][j] == 1) M[i][j] = 0;
				}
			}
			//利用高低阈值,找出分别处理后的图像
			for (int i = 0; i < c1.rows; i++)
			{
				for (int j = 0; j < c1.cols; j++)
				{
					if (i == 0 || j == 0 || i == c1.rows - 1 || j == c1.cols)
					{
						c1.at<uchar>(i, j) = 0;
					}
					if (M[i][j] >= r1)
					{
						c1.at<uchar>(i, j) = 255;
					}
					else c1.at<uchar>(i, j) = 0;
					if (M[i][j] > r2&&M[i][j] < r1)
					{
						c2.at<uchar>(i, j) = M[i][j];
					}
					else c2.at<uchar>(i, j) = 0;

				}
			}

			// 边缘连接
			for (int i = 0; i < c1.rows; i++)
			{
				for (int j = 0; j < c1.cols; j++)
				{
					if (c1.at<uchar>(i, j) == 255)
					{
						TraceEdge(i, j, r1, r2, c1, c2);
					}
				}
			}

			namedWindow("canny", 1);
			imshow("canny", c1);
}
//回掉函数
void on_Trackbar(int, void*)
{
 canny(pic,y5,count1,maxc);
}

int main()
{
    pic = imread("D:/123.png", 0);
	//resize(pic, pic, Size(500, 500));
	namedWindow("src", 1);
	imshow("src", pic);
	if (pic.empty())
	{
		cout << "read picture wrong";
		return 0;
	}
	copyMakeBorder(pic, pic, 1, 1, 1, 1, BORDER_REPLICATE);//扩充边界
	Mat pdes(pic.size(), pic.type());
	Canny(pic, pdes,70, 30);
	namedWindow("dst", 1);
	imshow("dst", pdes);
	canny(pic, y5, count1,maxc);
	count1++;
	char TrackbarName[50];//窗口中的滑动条控件  
	sprintf(TrackbarName, "高阈值%d",y5);
	createTrackbar(TrackbarName, "canny", &y5,maxc, on_Trackbar);
	waitKey(0);
	return 0;
}
经验分享 程序员 微信小程序 职场和发展