zoukankan      html  css  js  c++  java
  • Canny边缘检测原理及C#程序实现

    http://blog.csdn.net/yjz_uestc/article/details/6664937

     Canny边缘检测是被公认的检测效果最好的边缘检测方法,是由John F. Canny于1986年提出,算法目标是找出一个最优的边缘检测的方法,所谓最优即:1.好的检测:算法能够尽可能的标识出图像的边缘;2.好的定位:标识出的边缘要尽可能的与实际边缘相接近;3.最小响应:图像中的边缘只能标识一次,并且不能把噪声标识成边缘。同时我们也要满足3个准则:信噪比准则、定位精度准则、单边缘响应准则。

        Canny边缘检测算法可分为4步:

        高斯滤波器平滑、计算梯度、非极大值抑制、双阈值边缘检测和边缘连接。

        (经典不会随着时间褪色,算法也是一样)

        下面将逐步讲解并给出程序:

        第一步:高斯平滑

        为什么要对图像(灰度图像)进行高斯平滑预处理呢?高斯滤波器对去除服从正态分布的的噪声很有效,我做过实验,随着高斯模板的增大,被识别的边缘会逐渐减少,所以通过选着适合大小的高斯模板平滑,可以比较有效的去除一些伪边缘点。

        第二步:计算梯度

        首先,由一阶导数算子(一般用sobel模板)计算灰度图像每个像素点在水平和竖直方向上的导数Gx、Gy,得出梯度向量(Gx,Gy),计算梯度的值G和方向theta:

            G=sqrt(Gx*Gx+Gy*Gy)  theta=arctan(Gy/Gx)

    然后,将每个像素点的梯度的值和方向分别放入两个数组中,程序如下:

    [csharp] view plaincopy
     
    1. <span style="font-size:16px;">byte[] orients = new byte[width * height];// 梯度方向数组  
    2. float[,] gradients = new float[width, height];// 梯度值数组  
    3. double gx, gy;  
    4. for (int i = 1; i < (height - 1);i++ )  
    5.     {  
    6.         for (int j = 1; j < (width - 1); j++)  
    7.             {  
    8.                 //求水平和竖直导数  
    9.                 gx = bufdata[(i - 1) * width + j] + bufdata[(i + 1) * width + j] - bufdata[(i -1) * width + j - 1] - bufdata[(i + 1) * width + j - 1]+ 2*(bufdata[i * width + j + 1] - bufdata[i * width + j - 1]);  
    10.                 gy = bufdata[(i - 1) * width + j - 1] + bufdata[(i + 1) * width + j + 1] - bufdata[(i + 1) * width + j - 1] - bufdata[(i + 1) * width + j + 1]+ 2*(bufdata[(i - 1) * width + j] - bufdata[(i + 1) * width + j - 1]);  
    11.                 gradients[j, i] = (float)Math.Sqrt(gx * gx + gy * gy);  
    12.                 if (gx == 0)  
    13.                 {  
    14.                     orientation = (gy == 0) ? 0 : 90;  
    15.                 }  
    16.                 else  
    17.                 {  
    18.                     double div = (double)gy / gx;  
    19.   
    20.                     if (div < 0)  
    21.                     {  
    22.                         orientation = 180 - Math.Atan(-div) * toAngle;  
    23.                     }  
    24.                      else  
    25.                     {  
    26.                          orientation = Math.Atan(div) * toAngle;  
    27.                     }  
    28.                      //只保留成4个方向  
    29.                      if (orientation < 22.5)  
    30.                                 orientation = 0;  
    31.                             else if (orientation < 67.5)  
    32.                                 orientation = 45;  
    33.                             else if (orientation < 112.5)  
    34.                                 orientation = 90;  
    35.                             else if (orientation < 157.5)  
    36.                                 orientation = 135;  
    37.                             else orientation = 0;  
    38.                 }  
    39.                 orients[i*width+j] = (byte)orientation;  
    40.             }  
    41.     } </span>  

         第三步:非极大值抑制

         如果直接把梯度作为边缘的话,将得到一个粗边缘的图像,这不满足上面提到的准则,我们希望得到定位准确的单像素的边缘,所以将每个像素点的梯度与其梯度方向上的相邻像素比较,如果不是极大值,将其置0,否则置为某一不大于255的数,程序如下:

    [csharp] view plaincopy
     
    1.  <span style="font-size:16px;"> float leftPixel = 0, rightPixel = 0;  
    2.   for (int y = 1; y <height-1; y++)  
    3.   {  
    4.     for (int x = 1; x < width-1; x++)  
    5.         {  
    6.             //获得相邻两像素梯度值  
    7.             switch (orients[y * width + x])  
    8.                 {  
    9.                     case 0:  
    10.                         leftPixel = gradients[x - 1, y];  
    11.                         rightPixel = gradients[x + 1, y];  
    12.                         break;  
    13.                     case 45:  
    14.                         leftPixel = gradients[x - 1, y + 1];  
    15.                         rightPixel = gradients[x + 1, y - 1];  
    16.                         break;  
    17.                     case 90:  
    18.                         leftPixel = gradients[x, y + 1];  
    19.                         rightPixel = gradients[x, y - 1];  
    20.                         break;  
    21.                     case 135:  
    22.                         leftPixel = gradients[x + 1, y + 1];  
    23.                         rightPixel = gradients[x - 1, y - 1];  
    24.                         break;  
    25.                 }  
    26.                 if ((gradients[x, y] < leftPixel) || (gradients[x, y] < rightPixel))  
    27.                     {  
    28.                         dis[y * disdata.Stride + x] = 0;  
    29.                     }  
    30.                 else  
    31.                     {  
    32.                          dis[y * disdata.Stride + x] = (byte)(gradients[x, y] /maxGradient* 255);//maxGradient是最大梯度  
    33.                     }  
    34.   
    35.         }    
    36. }   </span>  

           第四步:双阈值边缘检测

        由上一步得到的边缘还有很多伪边缘,我们通过设置高低双阈值的方法去除它们,具体思想是:梯度值大于高阈值的像素点认为其一定是边缘,置为255,梯度值小于低阈值的像素点认为其一定不是边缘置为0,介于两阈值之间的点像素点为待定边缘。然后,考察这些待定边缘点,如果其像素点周围8邻域的梯度值都小于高阈值,认为其不是边缘点,置为0;至于,如何设定双阈值大小,我们可以根据实际情况设定,如设成100和20,也可以根据图像梯度值的统计信息设定,一般小阈值是大阈值的0.4倍即可。程序如下:

    [csharp] view plaincopy
     
    1. <span style="font-size:16px;">fmean = fmean / maxGradient * 255;//某统计信息  
    2. highThreshold = (byte)(fmean);//高阈值  
    3. lowThreshold = (byte)(0.4 * highThreshold); //低阈值                                
    4. for (int y = 0; y < height; y++)  
    5.     {  
    6.     for (int x = 0; x < width; x++)  
    7.         {  
    8.              if (dis[y * disdata.Stride + x] < highThreshold)  
    9.                 {  
    10.                     if (dis[y * disdata.Stride + x] < lowThreshold)  
    11.                         {  
    12.                         dis[y * disdata.Stride + x] = 0;  
    13.                         }  
    14.                      else  
    15.                         {  
    16.                             if ((dis[y * disdata.Stride + x - 1] < highThreshold) &&  
    17.                                 (dis[y * disdata.Stride + x + 1] < highThreshold) &&  
    18.                                 (dis[(y - 1) * disdata.Stride + x - 1] < highThreshold) &&  
    19.                                 (dis[(y - 1) * disdata.Stride + x] < highThreshold) &&  
    20.                                 (dis[(y - 1) * disdata.Stride + x + 1] < highThreshold) &&  
    21.                                 (dis[(y + 1) * disdata.Stride + x - 1] < highThreshold) &&  
    22.                                 (dis[(y + 1) * disdata.Stride + x] < highThreshold) &&  
    23.                                 (dis[(y + 1) * disdata.Stride + x + 1] < highThreshold))  
    24.                                {  
    25.                                     dis[y * disdata.Stride + x] = 0;  
    26.                                }  
    27.                         }  
    28.                 }  
    29.         }  
    30.     }</span>  

          最后,效果图如下:

    原图:

    灰度图:

    边缘图:

  • 相关阅读:
    黑马程序员——JAVA基础之System,Runtime,Date,Calendar,Math
    黑马程序员——JAVA基础之JDK1.5新特性高级for循环和可变参数
    黑马程序员——JAVA基础之Collections和Arrays,数组集合的转换
    黑马程序员——JAVA基础之Map集合
    黑马程序员——仅当源级别为 1.5 时已参数化的类型才可用的解决办法
    黑马程序员——JAVA基础之泛型和通配符
    黑马程序员——JAVA基础之Vector集合
    黑马程序员——JAVA基础之set集合
    黑马程序员——JAVA基础之List集合
    Bringing up interface eth0: Error: No suitable device found: no device found for connection 'System eth0'.
  • 原文地址:https://www.cnblogs.com/zkwarrior/p/4954128.html
Copyright © 2011-2022 走看看