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 plain copy
 
  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 plain copy
 
  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 plain copy
 
  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>  

      最后,效果圖如下:

原圖:

灰度圖:

邊緣圖:


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM