opencv邊緣檢測的入門剖析(第七天)


---邊緣檢測概念理解---

邊緣檢測的理解可以結合前面的內核,說到內核在圖像中的應用還真是多,到現在為止學的對圖像的操作都是核的操作,下面還有更神奇的!

想把邊緣檢測出來,從圖像像素的角度去想,那就是像素值差別很大,比如X1=20和X2=200,這兩個像素差值180,在圖像的顯示就非常明顯,這樣圖像的邊緣不就體現出來了?但是問題來了,一幅圖像給你,如果一個像素一個像素對比,

1.周圍像素差別不大的怎么辦?

2.周圍相差很大,但是很多的怎么辦?

3.怎么樣才能更好地區別圖像的邊緣呢?

比如5-200比較肯定算一個邊緣,但是200-200比較久不是邊緣了。如果全部做對比的話有很多的問題存在,這只是一個簡單的問題。

 

 ---范談各種邊緣檢測---

              從上面的分析可以得到,邊緣檢測->>>>>就是分離那些相差大的像素

如何分離像素,現在腦子里有個映像:利用核去處理圖像!

 

A.  Robert算子檢測邊緣:

 x和y方向的算子

 觀察上訴的算子,可以發現和我們剛開始設想的一個一個比較差不多,比如本來比如X1=20和X2=40,這兩個像素差值20,但是20體現不出來,所以用1,-1來增大這種差值,這其實解決了我們上訴遇到的第一點問題了,但是后面的兩點依然沒有解決。

具體的例子我們可以用opencv自帶的API,addwight進行試驗,把核改一下就行了。。。

 Roberts算子檢測方法對具有陡峭的低噪聲的圖像處理效果較好,但是利用roberts算子提取邊緣的結果是邊緣比較粗,因此邊緣的定位不是很准確。

 

B.  Sobel算子檢測邊緣:

 

X和Y方向

對X\Y兩個方向的梯度進行合並

 

 Sobel算子如果光從核上面去看,根本什么都不知道,我們得去看他的原理->>>>

 

他的原理就是利用導數求解邊緣,我們知道像素差別大的時候那么它的切點越陡峭,那么這個時候就找到了邊緣!具體程序怎么實現的,我還沒弄懂,感覺是利用拉普拉斯變化之后再計算的,最后用一個算子近似代替。。。(個人YY)

Sobel算子檢測方法對灰度漸變和噪聲較多的圖像處理效果較好,sobel算子對邊緣定位不是很准確,圖像的邊緣不止一個像素。

 

C.  Laplacian算子檢測邊緣:

 

拉普拉斯算子

 

 拉普拉斯邊緣檢測是通過二階倒數,從上面的一階倒數的理解就不難發現二階倒數是怎么進行的了。

二階倒數比一階倒數的好處是在與受到周圍的干擾小,其不具有方向性,操作容易,且對於很多方向的圖像處理好。

Laplacian算子法對噪聲比較敏感,所以很少用該算子檢測邊緣,而是用來判斷邊緣像素視為與圖像的明區還是暗區。

 

C.  Scharr算子檢測邊緣:

這個濾波是Sobel的升級版,原理是一樣的,就是實現的近似代替不一樣,說白了就事核改進了。。。

 

D.  Canny算子檢測邊緣:

 

 這是比較新的算法,運用的也是最廣泛的。這個算法是在Sobel算法的基礎上改進的,和Scharr不一樣!

Canny的步驟是:1.給一張圖片,先進行濾波消除干擾,濾波前面博客已經說明。

                      2.計算梯度(進行Sobel算子計算)。

                      3.非極大值抑制。

                      4.滯后閾值。

下面一屆具體介紹->>>>

在opencv2.0的時候,直接調用API就幫你完成全部的工作,包含上面的四部。

現在opencv3.0濾波得自己操作,API完成了后三步操作。

這里在Sobel運行之后的基礎上對圖像的邊緣進行了優化,哪些是優秀的,哪些是差的,在這里會處理。

 

---細談邊緣檢測---

上面講到Canny的非極大值抑制和滯后閾值,其中這兩點是這個算法的核心!

非極大值抑制:

            從字面上的理解就是從一群數據中找到真正的極大值,對於不是極大值的省略或者抑制顯示。

 我們來想一下,Sobel算子計算的值就是邊緣的值嗎?1.算子是固定的,那就有很大的幾率會計算到不是邊緣的數據。

                                                                  2.計算的結果不會省略不好的點,也不會去加強好的點,所以顯示就不明顯。

我們的目的就是改進上面兩個點,對於第一個點,我們得比較那些計算的點進行比較,把不好點舍去--->>>

以前在神經網絡那篇博文里提到過“梯度”的概念,就是數據下降或者上升最快的方向,簡單的說就是求導切線的方向!

試想一下我們在這個方向上找最大和最小值是最快最准確的,這個具體原因神經網絡那篇博文說過了,可以去看看。

通過計算我們得到了θ的值在[-π/2,+π/2]區間,然后我們就可以比較在這個方向上的G和左右G1、G2的大小,當G>G1、G2的時候,那就說明這個G就是局部極大值,從而保留下來:

例如:G0的θ是45度,那么在它的梯度方向來對比它是不是最大值,如果是的話那就說明它是局部極大值->判斷G0和(G3、G6)的大小關系!G0 = G0>G3&&G0>G6? G0:0;

上面的方法是第一代非極大值抑制算法,缺點是當 θ!=0、45、90、180 時,那么旁邊的八個值就不在θ的梯度上,就沒辦法去做比較了,這時候出現第二代算法--->>>

插值法運用在非最大值抑制算法中:

插值法:就是y=kx+b的插值公式,比如:X1和X2中間想插一點X,X = X1 + k(X2-X1)或者X= k*X1 +(1-k)X2 當然插值法還有其它形式,不過兩點的線性插值比較簡單的。這里使用第二者!

上面的圖形是當 |Gy|>|Gx| && Gx*Gy>0 的情況。前者保障靠近y軸,后者保證θ>0. 

注釋:在有的文章上看的和我說的相反,按照數學知識應該是這樣的啊,具體原因我也不知道了。

令 k = |Gy/Gx|

G23 = k*G2 + (1-k)*G3;

G67 = k*G6 + (1-k)*G7; 

G0 = G0>G23 && G0>G67 ? G0:0;或者這里可以突出重點給定G0的值G0 = G0>G23 && G0>G67 ? 200:0;

 opencv的源碼就是使用這種方法的,大家可以參考源碼:

 

  1 void NonMaxSuppress(int*pMag,int* pGradX,int*pGradY,SIZE sz,LPBYTE pNSRst)  
  2 {  
  3     LONG x,y;  
  4     int nPos;  
  5     // the component of the gradient  
  6     int gx,gy;  
  7     // the temp varialbe  
  8     int g1,g2,g3,g4;  
  9     double weight;  
 10     double dTemp,dTemp1,dTemp2;  
 11     //設置圖像邊緣為不可能的分界點  
 12     for(x=0;x<sz.cx;x++)  
 13     {  
 14         pNSRst[x] = 0;  
 15         pNSRst[(sz.cy-1)*sz.cx+x] = 0;  
 16           
 17     }  
 18     for(y=0;y<sz.cy;y++)  
 19     {  
 20         pNSRst[y*sz.cx] = 0;  
 21         pNSRst[y*sz.cx + sz.cx-1] = 0;  
 22     }  
 23       
 24     for (y=1;y<sz.cy-1;y++)  
 25     {  
 26         for (x=1;x<sz.cx-1;x++)  
 27         {  
 28             nPos=y*sz.cx+x;  
 29             // if pMag[nPos]==0, then nPos is not the edge point  
 30             if (pMag[nPos]==0)  
 31             {  
 32                 pNSRst[nPos]=0;  
 33             }  
 34             else  
 35             {  
 36                 // the gradient of current point  
 37                 dTemp=pMag[nPos];  
 38                 // x,y 方向導數  
 39                 gx=pGradX[nPos];  
 40                 gy=pGradY[nPos];  
 41                 //如果方向導數y分量比x分量大,說明導數方向趨向於y分量  
 42                 if (abs(gy)>abs(gx))  
 43                 {  
 44                     // calculate the factor of interplation  
 45                     weight=fabs(gx)/fabs(gy);  
 46                     g2 = pMag[nPos-sz.cx];  // 上一行  
 47                     g4 = pMag[nPos+sz.cx];  // 下一行  
 48                     //如果x,y兩個方向導數的符號相同  
 49                     //C 為當前像素,與g1-g3 的位置關系為:  
 50                     //g1 g2  
 51                     //   C  
 52                     //   g4 g3  
 53                     if(gx*gy>0)  
 54                     {  
 55                         g1 = pMag[nPos-sz.cx-1];  
 56                         g3 = pMag[nPos+sz.cx+1];  
 57                     }                     
 58                     //如果x,y兩個方向的方向導數方向相反  
 59                     //C是當前像素,與g1-g3的關系為:  
 60                     //    g2 g1  
 61                     //    C  
 62                     // g3 g4  
 63                     else  
 64                     {  
 65                         g1 = pMag[nPos-sz.cx+1];  
 66                         g3 = pMag[nPos+sz.cx-1];  
 67                     }  
 68                 }  
 69                 else  
 70                 {  
 71                     //插值比例  
 72                     weight = fabs(gy)/fabs(gx);                   
 73                     g2 = pMag[nPos+1]; //后一列  
 74                     g4 = pMag[nPos-1];  // 前一列                
 75                     //如果x,y兩個方向的方向導數符號相同  
 76                     //當前像素C與 g1-g4的關系為  
 77                     // g3  
 78                     // g4 C g2  
 79                     //       g1  
 80                     if(gx * gy > 0)  
 81                     {  
 82                         g1 = pMag[nPos+sz.cx+1];  
 83                         g3 = pMag[nPos-sz.cx-1];  
 84                     }  
 85                       
 86                     //如果x,y兩個方向導數的方向相反  
 87                     // C與g1-g4的關系為  
 88                     // g1  
 89                     // g4 C g2  
 90                     //      g3  
 91                     else  
 92                     {  
 93                         g1 = pMag[nPos-sz.cx+1];  
 94                         g3 = pMag[nPos+sz.cx-1];  
 95                     }  
 96                 }  
 97                 //--線性插值等價於dTemp1 = g1 + weight*(g2-g1)--//
 98                 dTemp1 = weight*g1 + (1-weight)*g2;  
 99                 dTemp2 = weight*g3 + (1-weight)*g4;               
100                 //當前像素的梯度是局部的最大值  
101                 //該點可能是邊界點  
102                 if(dTemp>=dTemp1 && dTemp>=dTemp2)  
103                 {  
104                     pNSRst[nPos] = 128;  
105                 }  
106                 else  
107                 {  
108                     //不可能是邊界點  
109                     pNSRst[nPos] = 0;  
110                 }             
111             }  
112         }  
113     }  
114 }

 

 在論文中海油一個改進的插值,用二次插值代替一次插值,學過數值分析的都知道,一次插值在直線很好,但是在曲線不好,當然二次插值也不能消除很多誤差,當然海油牛頓插值等等。。。

 這是當Gx和Gy同號的情況,另一種情況自己想一下就行了。

 

 

 

 二次插值相比較一次插值的優點是:不用考慮哪個哪個具體的角度。其實很多人都提到了0、45、90、180的角度划分,我這里沒有提到,原理是一樣的,我感覺直接做就好了,沒必要再去弄個中間變量過度一下,可能為了理解吧。

滯后閾值:

   1. T1, T2為閾值,凡是高於T2的都保留,凡是小於T1都丟棄。

   2.如果介於T1和T2之間的話,判斷是否連接T2,如果沒連接T2那就刪除。

   3.T1和T2比例最好1:2/1:3

這里說明一下第二點:

                       A.我們的目的是找到最大邊緣變化。

                       B.並且保證邊緣顯示效果很好。

對於A來說,我們非最大值抑制已經找到部分最大值,現在用T2再進行一遍,已經很好的達到我們A目的了。

對於B來說,用T1去濾去可能不是最大值的點,現在用第二點來加強顯示,在T2附近的保留,不在的都刪除(意思就是在最小值附近)。

看下面這個例子,T1=2,T2=9 用核3X3去找T2附近的值,那就表示只有6個值可以保留,其他值都將被刪除。

第一步:整個圖像去找T>T2和T<T1的值,刪除或者保留,並且標記記錄。

第二步:在上一步記錄的最大值附近尋找存在的值,直接刪除或者保留。

 

 

 參考:《自適應Canny算法研究及其在圖像邊緣檢測中的應用_金剛》

          http://blog.csdn.net/kezunhai/article/details/11620357

 

 

 

 

 


免責聲明!

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



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