---邊緣檢測概念理解---
邊緣檢測的理解可以結合前面的內核,說到內核在圖像中的應用還真是多,到現在為止學的對圖像的操作都是核的操作,下面還有更神奇的!
想把邊緣檢測出來,從圖像像素的角度去想,那就是像素值差別很大,比如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
