Canny边缘检测

Canny边缘检测

1、概述

Canny边缘检测是多阶段优化的方法,该方法严格遵循以下三个准则:

1.1、高信噪比。

这一准则说明好的检测效果要具有高的信噪比,对于图像的边缘要能够实实在在地检测到,对于图像中非边缘的部分,不能够被检测成边缘。

1.2、控制对边缘检测的精度。

指算法所检测的边缘要尽可能的逼近实际的边缘,这是边缘检测算法的基本要求,检测精度直接反映了一个算法的好坏。

1.3、降低对同一边缘的相应次数。

对于检测的图像边缘,应该准确检测出边缘点,避免出现伪边缘。

2、步骤:

Canny边缘检测算法的具体的实现步骤为:

2.1、对图像进行高斯平滑

由于图像中的边缘信息对噪声比较敏感,因此使用滤波器来对噪声进行处理,这里使用的高斯平滑用来削弱图像中的噪声对边缘信息提取的影响,对于图像 ,有
这里写图片描述
其中,⊗表示卷积运算, 这里写图片描述这里写图片描述,σ表示高斯卷积的平滑因子。σ越大图像越平滑,对于噪声越不敏感,σ越小图像平缓程度越小,对噪声越敏感。

2.2、求梯度的方向和幅值

对经过高斯平滑后的图像这里写图片描述再求其偏导,得到图像的偏导数这里写图片描述这里写图片描述,对其求方向和幅值
这里写图片描述

2.3、非极大值抑制

图像中像素点导数的幅值大,但是不能够认定该点是边缘点。非极大值抑制就是指在图像中的局部最大值里,将非极大值点进行剔除,不对这些点进行进一步的检测,这样可以将一些由噪声引起的、出现局部梯度较大而可能会成为伪边缘的点剔除掉。实现起来就是将点这里写图片描述与邻域的8个点按导数减小的方向进行幅值比较,若点这里写图片描述小于该方向的两个邻域的点的值,那么该点不是极大值,在此处将该点梯度的幅值置0。

2.4、图像二值化

双阈值检测指的是首先使用高阈值进行对图像边缘的检测,检测到高于阈值的点就将置设为255,低于阈值的点就置为0。这时得到的图像边缘虽然比较真实,噪声也很少,但是图像边缘信息丢失比较严重,这时使用低阈值在高阈值的邻域进行二次检测,由于图像边缘一般都是连续的,所以检测到的新边缘可以填充高阈值检测到的图像边缘的缝隙中,完成边缘的提取。
阈值的取值关系到检测结果,阈值的取值对于图像边沿的提取至关重要,为了使选取的阈值能够适应不同场景的图像,选择使用图像直方图确定高阈值,统计梯度幅值大小为 的点的数量,得到函数
这里写图片描述
这里写图片描述表示幅值大小为i的点的数量,则每个幅值大小相同的点在图像中的概率为
这里写图片描述
其中N表示图像中像素点的总数量,根据统计的梯度幅值大小的信息设置双阈值的高阈值,然后取高阈值的一半作为低阈值。
实验验证:
σ取0.3,高阈值取0.6
原图:
这里写图片描述
检测结果:
这里写图片描述

3、C语言实现:

/*/
函数名:int CannyEdge()
结果:double Canny[IMGROWS][IMGCOLS];
*//
int CannyEdge()
{
	int i,j,k,l;
	int tempSize=0;
	double m,n;
	char name[20];
	double sigma=0.3;//0.4;   
	double number;//中间变量
	double gaussConall;
	double conBuff=0.0;
    double conBuffSum=0.0;
	double g1=0, g2=0, g3=0, g4=0;     //中间变量            
	double dTmp1=0.0, dTmp2=0.0;    //中间变量          
	double dWeight=0.0;             //插值权重
	int grayHist[50000]={0};	//只能用于灰度图像
	double maxMag=0;          //最大梯度值
	int notEdgeNum=0;
	double  RatHigh = 0.6;  //直方图的比例
	double  ThrHigh;  
	double  ThrLow;  
	double  RatLow = 0.6;  
	int HighCount=0;
	FILE *fpTarget;
	
	高斯滤波  使用一维x2///
	tempSize=(int)(sigma*6+1.9);//保证模板尺寸
	printf("\nsigma: %f\n",sigma);
	if(!(tempSize&1))
	{
		tempSize++;
	}
	gaussConall=0;
	number=sqrt(2.0*Pi)*sigma;
	for(i=0;i<tempSize;i++)
	{	
		m=i-tempSize/2;
		gaussCon1[i]=(exp(-(m*m)/(2.0*sigma*sigma)))/number;
		gaussConall+=gaussCon1[i];
		//printf("gaussCon1   %d : %f\n",i,gaussCon1[i]);
	}
	for(i=0;i<tempSize;i++)//归一化
	{
		gaussCon1[i]/=gaussConall;
		//printf("gaussCon1   %d : %f\n",i,gaussCon1[i]);
	}
	//printf("tempSize: %d\n",tempSize);
	for(i=0;i<IMGROWS;++i)//横向
	{
		for(j=0;j<IMGCOLS;++j)
		{		
			conBuff=0;
			conBuffSum=0;
			for(k=0;k<tempSize;++k)
			{
				if(j-(tempSize-1)/2+k>=0&&j-(tempSize-1)/2+k<IMGCOLS)
				{
					conBuff+=(gaussCon1[k]*(double)BMPHEAD.grayMatrix[i][j-(tempSize-1)/2+k]);
					conBuffSum+=gaussCon1[k];
				}
			}
			imageMid[i][j]=conBuff/conBuffSum;		
		}
	}/*for(i=0;i<IMGROWS;++i)*/
	for(j=0;j<IMGCOLS;++j)//纵向
	{
		for(i=0;i<IMGROWS;++i)
		{		
			conBuff=0;
			conBuffSum=0;
			for(k=0;k<tempSize;++k)
			{
				if(i-(tempSize-1)/2+k>=0&&i-(tempSize-1)/2+k<IMGCOLS)
				{
					conBuff+=(gaussCon1[k]*imageMid[i-(tempSize-1)/2+k][j]);
					conBuffSum+=gaussCon1[k];
				}
			}
			image1[i][j]=conBuff/conBuffSum;		
		}
	}/*for(i=0;i<IMGROWS;++i)*/



	for(i=0;i<BMPHEAD.rows;i++)
	{
		for(j=0;j<BMPHEAD.cols;j++)
		{
			imageShow[i][j]=(unsigned char)image1[i][j];//(gradientMag[i][j]*100);
	//		image1[i][j]=BMPHEAD.grayMatrix[i][j];
		}
	}

	matrix2bmp8("gauss.bmp");


	for(i=0; i<(IMGROWS-1); i++)//求偏导数
	{  
        for(j=0; j<(IMGCOLS-1); j++)  
        {  
              dx[i][j] = (image1[i][j+1]-image1[i][j]+image1[i+1][j+1]-image1[i][j]+image1[i-1][j+1]-image1[i][j]+image1[i][j]-image1[i][j-1]+image1[i][j]-image1[i-1][j-1]+image1[i][j]-image1[i+1][j-1])/6.0;//+(image1[i+1][j+1]-image1[i][j]+image1[i+1][j-1]-image1[i][j]+image1[i-1][j+1]-image1[i][j]+image1[i-1][j-1]-image1[i][j])/8;  
              dy[i][j] = (image1[i+1][j]-image1[i][j]+image1[i+1][j+1]-image1[i][j]+image1[i+1][j-1]-image1[i][j]+image1[i][j]-image1[i-1][j]+image1[i][j]-image1[i-1][j-1]+image1[i][j]-image1[i-1][j+1])/6.0;//+(image1[i+1][j+1]-image1[i][j]+image1[i+1][j-1]-image1[i][j]+image1[i-1][j+1]-image1[i][j]+image1[i-1][j-1]-image1[i][j])/8;  
		}  
	}  
	for(i=0; i<IMGROWS; i++)//梯度幅值和方向
	{  
        for(j=0; j<IMGCOLS; j++)  
        {  
              gradientMag[i][j] = sqrt(dx[i][j]*dx[i][j] + dy[i][j]*dy[i][j]); 
			 
              gradientDir[i][j] = atan2(dy[i][j], dx[i][j]) * 180/Pi;  
              if(gradientDir[i][j] < 0)  
                    gradientDir[i][j] += 360;              //0--360
		}  
	//	printf("%f\n",gradientDir[i][50]);
	}  
	极大值抑制//
	for(i=1; i<(IMGROWS-1); i++)  
	{  
		for(j=1; j<(IMGCOLS-1); j++)  
		{  
			if(gradientMag[i][j] == 0)  
			{
			    notGreat[i][j] = 0;         //如果当前梯度幅值为0,则不是局部最大,对该点赋为0  
			}
			else  
			{  
			首先判断属于那种情况,然后根据情况插值///  
			第一种情况///  
			/       g1  g2                  /  
			/           C                   /  
			/           g3  g4              /  
			/  
				if( ((gradientDir[i][j]>=90)&&(gradientDir[i][j]<135)) ||   
					((gradientDir[i][j]>=270)&&(gradientDir[i][j]<315)))  
				{  
					//根据斜率和四个中间值进行插值求解  
					g1 = gradientMag[i-1][j-1];  
					g2 = gradientMag[i-1][j];  
					g3 = gradientMag[i+1][j];  
					g4 = gradientMag[i+1][j+1];  
					dWeight = fabs(dx[i][j])/fabs(dy[i][j]);   //反正切  
					dTmp1 = g1*dWeight+g2*(1.0-dWeight);  
					dTmp2 = g4*dWeight+g3*(1.0-dWeight);  
				//	printf("90");
				}  
			第二种情况///  
			/       g1                      /  
			/       g2  C   g3              /  
			/               g4              /  
			/  
				else if( ((gradientDir[i][j]>=135)&&(gradientDir[i][j]<180)) ||   
					((gradientDir[i][j]>=315)&&(gradientDir[i][j]<360)))  
				{  
					g1 = gradientMag[i-1][j-1];  
					g2 = gradientMag[i][j-1];  
					g3 = gradientMag[i][j+1];  
					g4 = gradientMag[i+1][j+1];  
					dWeight = fabs(dy[i][j])/fabs(dx[i][j]);   //正切  
					dTmp1 = g2*dWeight+g1*(1.0-dWeight);  
					dTmp2 = g4*dWeight+g3*(1.0-dWeight);  
				//	printf("180");
				}  
			第三种情况///  
			/           g1  g2              /  
			/           C                   /  
			/       g4  g3                  /  
			/  
				else if( ((gradientDir[i][j]>=45)&&(gradientDir[i][j]<90)) ||   
					((gradientDir[i][j]>=225)&&(gradientDir[i][j]<270)))  
				{  
					g1 = gradientMag[i-1][j];  
					g2 = gradientMag[i-1][j+1];  
					g3 = gradientMag[i+1][j];  
					g4 = gradientMag[i+1][j-1];  
					dWeight = fabs(dx[i][j])/fabs(dy[i][j]);   //反正切  
					dTmp1 = g2*dWeight+g1*(1.0-dWeight);  
					dTmp2 = g3*dWeight+g4*(1.0-dWeight);  
				//	printf("270");
				}  
				第四种情况///  
				/               g1              /  
				/       g4  C   g2              /  
				/       g3                      /  
				/  
				else if( ((gradientDir[i][j]>=0)&&(gradientDir[i][j]<45)) ||   
					((gradientDir[i][j]>=180)&&(gradientDir[i][j]<225)))  
				{  
					g1 = gradientMag[i-1][j+1];  
					g2 = gradientMag[i][j+1];  
					g3 = gradientMag[i+1][j-1];  
					g4 = gradientMag[i][j-1];  
					dWeight = fabs(dy[i][j])/fabs(dx[i][j]);   //正切  
					dTmp1 = g1*dWeight+g2*(1.0-dWeight);  
					dTmp2 = g3*dWeight+g4*(1.0-dWeight); 
					//printf("225");
				}  
			  }         
        //进行局部最大值判断,并写入检测结果  
			if((gradientMag[i][j]>=dTmp1) && (gradientMag[i][j]>=dTmp2))  
			{
				notGreat[i][j] = 128;  
				//printf("1");
			}
			else  
			{
				notGreat[i][j] = 0;  
				//printf("\n");
			}
        }  
	} 
	test=0;
	for(i=1; i<(IMGROWS-1); i++)  
	{
		for(j=1; j<(IMGCOLS-1); j++)  
		{
			if(gradientMag[i][j]>test)
			{
				test=gradientMag[i][j];
			}
		}	
	}
	//双阈值检测
	for(i=1; i<(IMGROWS-1); i++)  
	{
		for(j=1; j<(IMGCOLS-1); j++)  
		{
			if(notGreat[i][j] == 128)
			grayHist[(int)(gradientMag[i][j]*255.0/test)]++;
		}
	}
/*	test=10000;
	for(i=1; i<(IMGROWS-1); i++)  
	{
		for(j=1; j<(IMGCOLS-1); j++)  
		{
			if(gradientMag[i][j]<test)
			{
				test=gradientMag[i][j];
			}
		}	
	}
	printf("%f\n",test);*/
	notEdgeNum=0;
	for(i=1; i<50000; i++)       //统计有多少像素  
	{
		if(grayHist[i] != 0)
		{
			maxMag = (double)(i)*test/255.0;  
		}
		notEdgeNum += grayHist[i]; 
	}
	HighCount = (int)(RatHigh * notEdgeNum + 0.5);
	printf("HighCount  :  %d\n",HighCount);
	for(i=0;i<50000;++i)
	{
		HighCount-=grayHist[i];
		printf("HighCount  :  %d\n",HighCount);
		if(HighCount<0)
		{
			ThrHigh=(double)(i)*test/255.0;
		//	printf("i  :  %d\n",i);
		//	
			break;

		}
	}                            
	ThrLow = ThrHigh*RatLow;   
	printf("ThrHigh  :  %f\n",ThrHigh);
	printf("ThrLow  :  %f\n",ThrLow);
	for(i=1; i<(IMGROWS-1); i++)  
	{
		for(j=1; j<(IMGCOLS-1); j++)  
		{
			if((notGreat[i][j]==128) && (gradientMag[i][j] >= ThrHigh)) //强边界 
			{  
				Canny[i][j] = 255;
			    //notGreat[i][j] = 255;  
			}  
		}
	}
	for(i=2; i<(IMGROWS-2); i++)  
	{
		for(j=2; j<(IMGCOLS-2); j++)  
		{
			if((Canny[i][j] != 255)&& (notGreat[i][j]==128) && (gradientMag[i][j]>= ThrLow) && (255==Canny[i+1][j]||255==Canny[i-1][j]||255==Canny[i][j+1]||255==Canny[i][j-1]||255==Canny[i+1][j+1]||255==Canny[i+1][j-1]||255==Canny[i-1][j+1]||255==Canny[i-1][j-1])) //弱边界 
				{  
					Canny[i][j] = 255;
		//			printf("%d\n",j);
				}
		}
	//	printf("弱边界\n");
	}
	return 1;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值