Canny边缘算法总结(基于C语言)

引入

本人在学习Canny边缘算法的过程中,发现网上对Canny算法的介绍和开源还是挺多的,但大多基于Python,面向嵌入式、基于C语言的Canny算法的介绍有但不多。

本篇文章主要通过代码实现外加部分注释的形式,来分享一下我对Canny边缘算法的认识和总结。

声明:

本篇文章分享的代码均为伪代码,如果想获取完整的算法工程文件,可下载与本文章绑定的资源。

代码实现

简单介绍

常用的Canny边缘算法里包含了四个部分:高斯模糊、Sobel(Scharr)边缘算子、非极大值抑制(NMS)、双阈值法+边缘链接。

高斯模糊

int *Gaussain_Blur(int *image) {
    static int Blur_image[MT9V03X_H][MT9V03X_W];         // 高斯模糊图像
	int *map;        
	float Gaussain_weight_matrix[9] = { 0.0947416, 0.118318, 0.0947416, 0.118318, 0.147761, 0.118318, 0.0947416, 0.118318, 0.0947416 };        
    int temp_fix[9];

	map = image;	// 获取原灰度图
	for (int j = 0; j < MT9V03X_W ; ++j) {	// 第一行不做处理
		Blur_image[0][j] = *(map);
		map++;
	}
	for (int i = 1; i < MT9V03X_H - 1; ++i) {
		Blur_image[i][0] = *(map);	// 第一列不做处理
		map++;
		//高斯模糊处理
		for (int j = 1; j < MT9V03X_W  - 1; ++j) {
			temp_fix[0] = *(map - MT9V03X_W  - 1);
			temp_fix[1] = *(map - MT9V03X_W );
			temp_fix[2] = *(map - MT9V03X_W  + 1);
			temp_fix[3] = *(map - 1);
			temp_fix[4] = *(map);
			temp_fix[5] = *(map + 1);
			temp_fix[6] = *(map + MT9V03X_W  - 1);
			temp_fix[7] = *(map + MT9V03X_W );
			temp_fix[8] = *(map + MT9V03X_W  + 1);
			float fix_sum = 0;
			for (int k = 0; k < 9; ++k) {//权重分配
				fix_sum += (float)temp_fix[k] * Gaussain_weight_matrix[k];
			}
			Blur_image[i][j] = (int)fix_sum;//直接灰度值赋值,要用一个int(强制类型转化)来表示0-255之间的整数值
			map++;
		}
		Blur_image[i][MT9V03X_W  - 1] = *(map); // 最后一列不做处理
		map++;
	}
	for (int j = 0; j < MT9V03X_W ; ++j) { // 最后一行不做处理
		Blur_image[MT9V03X_H - 1][j] = *(map);
		map++;
	}
  return *Blur_image;
}

Gaussain_weight_matrix对应的是高斯模糊算法的权重矩阵,当前这个是常用的权重矩阵,网上也有其他的权重矩阵,读者可自行查找使用。代码中的MT9V03X_H和MT9V03X_W分别表示的是摄像头拍摄到图像的高和宽,读者使用时改成自己摄像头所拍摄图像对应的高和宽就行,之后不再重复解释。

Sobel算子

int *Sobel_edge(int *image1){
  static int Sobel_image[MT9V03X_H][MT9V03X_W];        //sobel边沿提取图像
  int Gx,Gy,G=0;
  int sobel_fix[9];
  int *map;
  map = image1;
  for(int j=0;j<MT9V03X_W;j++){   //第一行不处理
    Sobel_image[0][j]=*(map);
    map++;
  }
  for(int i=1;i<MT9V03X_H-1;i++){
    Sobel_image[i][0]=*(map);  //第一列不处理
    map++;
    for(int j=1;j<MT9V03X_W-1;j++){
      	sobel_fix[0] = *(map - MT9V03X_W - 1);
	    sobel_fix[1] = *(map - MT9V03X_W);
	    sobel_fix[2] = *(map - MT9V03X_W + 1);
	    sobel_fix[3] = *(map - 1);
	    sobel_fix[4] = *(map);
	    sobel_fix[5] = *(map + 1);
	    sobel_fix[6] = *(map + MT9V03X_W - 1);
	    sobel_fix[7] = *(map + MT9V03X_W);
	    sobel_fix[8] = *(map + MT9V03X_W + 1);
        Gx = abs(sobel_fix[2]-sobel_fix[0]+2*sobel_fix[5]-2*sobel_fix[3]+sobel_fix[8]-sobel_fix[6]);
        Gx = Gx>255?255:Gx;//限幅
        Gy = abs(sobel_fix[0]-sobel_fix[6]+2*sobel_fix[1]-2*sobel_fix[7]+sobel_fix[2]-sobel_fix[8]);
        Gy = Gy>255?255:Gy;//限幅
        G = (int)InvSqrt(Gx*Gx+Gy*Gy);//用到了卡马尔开方InvSqrt
        Sobel_image[i][j]=G;
        map++;
    }
    Sobel_image[i][MT9V03X_W-1]=*(map);  //最后一列不处理
    map++;
  }
  for(int j=0;j<MT9V03X_W;j++){
    Sobel_image[MT9V03X_H-1][j]=*(map);
    map++;
  }
  return *Sobel_image;
}

代码中Gx表示图像九宫格中X方向上的梯度,Gy表示图像九宫格中Y方向上的梯度,G表示X方向和Y方向梯度的融合。Gx和Gy的求值运算要对应Sobel算子的权重矩阵,G的求值运用到了快速高效算法卡马尔开方,读者也可以直接用math.h库里的sqrt函数,但据说这个函数算起来比较慢。

想深入了解Sobel算子可以参考这篇文章:Sobel算子及C++实现

Scharr算子可以看做是Sobel算子的改进版,算法逻辑基本是一样的,不同的就是在求Gx和Gy时用到了权重算子不一样,具体可以参考一下这篇文章:scharr算子函数及其使用

非极大值抑制(NMS)

在进行非极大值抑制时,需要先求出每一个像素点的方向梯度,及Gy/Gx,然后规定4个方向,分别为竖直方向、水平方向、左上斜向右下方向、左下斜向右上方向。确定好每个像素点的方向梯度之后,就可以对图像进行非极大值抑制操作。

void NMS(){
  int loss=0;//非极大值抑制补偿,根据实际效果来确定用不用,可手动调,初始设置为0;

  for(int i=1;i<MT9V03X_H-1;i++){
    for(int j=1;j<MT9V03X_W-1;j++){
      switch(dir[i][j])
      {
      case 0://水平方向
        {
          if( (Scharr_image[i][j]>Scharr_image[i][j+1]-loss) && (Scharr_image[i][j]>Scharr_image[i][j-1]-loss) )
          nms[i][j]=Scharr_image[i][j];       //这里默认在scharr图像的基础上进行非极大值抑制,如果要修改的话切记这行以及下面的scharr都要修改
          else  nms[i][j]=0;
          
          break;
        }
      case 1://左下右上
        {
          if( (Scharr_image[i][j]>Scharr_image[i+1][j-1]-loss) && (Scharr_image[i][j]>Scharr_image[i-1][j+1]-loss) )
          nms[i][j]=Scharr_image[i][j];
          else  nms[i][j]=0;
          
          break;
        }
      case 2://左上右下
        {
          if( (Scharr_image[i][j]>Scharr_image[i-1][j-1]-loss) && (Scharr_image[i][j]>Scharr_image[i+1][j+1]-loss) )
          nms[i][j]=Scharr_image[i][j];
          else  nms[i][j]=0;
          
          break;
        }
      case 3://竖直方向
        {
          if( (Scharr_image[i][j]>Scharr_image[i+1][j]-loss) && (Scharr_image[i][j]>Scharr_image[i-1][j]-loss) )
          nms[i][j]=Scharr_image[i][j];
          else  nms[i][j]=0;
          
          break;
        }
      default:
        break;
      }
    }
  }
}

双阈值法+边缘链接

void TwoThreshold(){
  int Link_fix[8];//当前点不算,只读取八邻域的值
  static uint16 lowThr,highThr;

  lowThr=LOW_THRESHOLD;//低阈值设定
  highThr=HIGH_THRESHOLD;//高阈值设定

  for(int i=1;i<MT9V03X_H-1;i++){
    for(int j=1;j<MT9V03X_W-1;j++){
      if(nms[i][j]<lowThr)  link[i][j]=0;
      else if(nms[i][j]>highThr)  link[i][j]=255;
      else{
        Link_fix[0]=nms[i-1][j-1];
        Link_fix[1]=nms[i-1][j];
        Link_fix[2]=nms[i-1][j+1];
        Link_fix[3]=nms[i][j-1];
        Link_fix[4]=nms[i][j+1];
        Link_fix[5]=nms[i+1][j-1];
        Link_fix[6]=nms[i+1][j];
        Link_fix[7]=nms[i+1][j+1];
        quick_sort(Link_fix,0,8);//快速排序算法,对八领域的灰度值进行排序;7最大,0最小
        if(Link_fix[4]>highThr)  link[i][j]=255;//满足条件,链接
        else  link[i][j]=0;//不满足条件,灰度值置为0(黑)       
      }
    }
  }
}

在这里,双阈值的两个阈值是我根据实际情况来设定的,调试起来不会特别麻烦,当然这里可以改成自适应的双阈值,这个就由读者根据实际情况自行修改了。在边缘链接算法中,我运用了快速排序算法quick_sort,读者也可以自己写个冒泡排序算法或者其他排序算法。

写在最后

以上代码主要是给读者写Canny算法时提供一些思路,如果读者想直接复制粘贴就能用的话肯定是不行的。另外,推荐读者在写比较模块化的函数时,能像高斯模糊或者Sobel算子函数那样,尽量不引入全局变量,最好要有输入参数有返回参数,这样代码的可移植性会好很多,尽量不要像上面NMS函数和边缘链接函数那样,不太好移植(个人建议)。不过我这里主要是提供思路,所以就不做另外的改进了,还望读者谅解。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值