之前的博文大部分都寫的很是詳細,有不少分析過程,不過寫起來確實很累人,通常一篇好的文章要整理個三四天,可是,時間愈來愈緊張,後續的一些算法可能就以隨記的方式,把實現過程的一些比較容易出錯和有價值的細節部分加以描述,而且可能須要對算法自己有必定了解的朋友才能明白我所描述的一些過程了。html
那這個系列的開篇,咱們以Canny邊緣檢測算法爲頭吧。算法
相關參考資料:函數
一、Canny邊緣檢測算法的實現。 post
二、OpenCV(五)——超細節的Canny原理及算法實現 測試
三、OpenCV 之 邊緣檢測 優化
四、Opencv 3.0\opencv\sources\modules\imgproc\src\canny.cppurl
Canny邊緣檢測是邊緣檢測算法領域裏最爲著名的算法,其標準實現過程就是下面五個步驟:spa
一、高斯模糊。.net
二、 計算梯度幅值和方向。3d
三、非最大值抑制。
四、 雙閥值。
五、 滯後邊界跟蹤
我的認爲,第一步能夠不做爲標準流程,在OpenCV的實現裏,也沒有他,有些文章裏也說了能夠用保邊濾波來代替標準的高斯模糊的過程,其實,用高斯模糊,在其餘參數相同的狀況下,模糊後的結果線條會更少,這是由於模糊後邊緣部分的細節有所丟失,這樣在後續的非最大值抑制步驟裏強邊緣和若邊緣的數據量會有所減小。
不過這個步驟帶來的另一個好處就是,算法的計算時間會減小,這主要是因爲邊緣信息的減小讓最後一步的滯後邊界跟蹤計算量大爲減小。
在計算梯度幅值和方向時,最開始的Canny算法使用的時2領域,這個計算量要少,但能表達的邊緣信息仍是不夠強烈,因此如今通常都採用Sobel算子的方式,計算處X和Y方向的梯度和幅值,在幅值計算過程當中,可使用L1範數(絕對值之和),也可使用L2範數(歐式距離),二者在結果上仍是稍有差別的,整體來講,L2範數的結果稍微好一點。
計算這個的過程,咱們能夠藉助SSE圖像算法優化系列九:靈活運用SIMD指令16倍提高Sobel邊緣檢測的速度(4000*3000的24位圖像時間由480ms下降到30ms) 一文的相關技巧來加速,當咱們須要使用L2範數時,這個結果是個浮點數,爲了提升後續的處理速度,咱們可採用 LinePM[X] = sqrtf((float)((Gx * Gx + Gy * Gy) << 12));這樣的過程把他定點化,保存到unsigned short類型裏,便可以節省內存,有能有利於後續的處理,並且在本例中計算的精度也可以保證了。
非最大值抑制過程,也就是根據當前點的梯度的方向,比較當前幅值及其周邊2個位置的幅值,若是當前幅值是他們的最大值,則該點需保留,不然,該點捨棄。那麼這裏的實現過程能夠和後面的雙閾值處理放置在同一個過程當中,這樣能夠多方面提升算法速度,由於,若是當前的幅值小於小的閾值,哪怕他是局部最大值,咱們也要捨棄他,因此就根本不用計算他,而在肯定某個點是改保留時,經過進一步判斷其和大的閾值之間的關係,能夠同時肯定他是否爲強邊緣,或者弱邊緣。
在這個過程當中,咱們能夠像OpenCV那樣採用簡易的判斷,把梯度分爲[0,22.5],[22.5 67.5],[67.5 90]三個範圍,在小於22.5度時,只需比較當前左右兩個點的幅值,當大於67.5度時,比較上下兩個點的幅值,不然,比較對角線上兩個點的幅值。
另一種方式就是以下圖所示,進行插值比較:
這種方式理論上講更爲精確一些,可是帶來主要時計算量的增長。
在採用OpenCV方法實現時,前面的幅值計算部分,咱們能夠考慮不用開平方,由於咱們只須要比較大小,平方值大,一樣也就表明原始值大,固然這個時候咱們須要對用戶輸入的LowThreshold和HighThreshold進行同步的平方處理。
在滯後邊界跟蹤方面,我以爲有一篇文章講的比較好,我直接引用他的說法:
那麼問題來了,弱邊緣究竟是邊緣,仍是因爲噪點致使的梯度突變。
斷定依據有多種。有的人是斷定弱邊緣點的8鄰域中是否存在強邊緣,若是有則將弱邊緣設置成強的。沒有就認爲是假邊緣。
另外一種方案是用搜索算法,經過強邊緣點,搜索8領域是否存在弱邊緣,若是有,以弱邊緣點爲中心繼續搜索,直到搜索不到弱邊緣截止。
我看大部分人用的都是第二種方案,第二種方案也有二種實現方式,一個時遞歸法,一個是普通的循環法,遞歸容易致使堆棧溢出,仍是使用循環法,速度快,並且效果穩定。
整個實現過程種,除了計算梯度和幅值時,可使用SSE加速外,其餘的過程因爲有太多的判斷和使用不連續位置的內存等緣由,是不太可能用SSE進行加速的。
在內存佔用方面,只須要梯度和幅值方面的數據(分別用signed short和unsigned short類型來保存),大約用6倍大小的圖像內存,另外,爲了能處理邊緣像素,還須要一點點額外的小內存(注意在非最大值抑制時也可使用Sobel邊緣檢測算法那個文章裏的那種技巧來處理邊緣)。
針對這個過程,咱們編制了一個UI界面,比較測試各個算法的結果:
下面進行一些簡單的測試:
原圖 無預處理,L1Gradient, OpenCV版抑制,低閾值50,高閾值150
無預處理,L2Gradient, OpenCV版抑制,低閾值50,高閾值150 無預處理,L1Gradient, 精確版抑制,低閾值50,高閾值150
無預處理,L2Gradient, 精確版抑制,低閾值50,高閾值150 高斯模糊0.5,L2Gradient, 精確版抑制,低閾值50,高閾值150
高斯模糊1,L2Gradient, 精確版抑制,低閾值50,高閾值150 保邊濾波,L2Gradient, 精確版抑制,低閾值50,高閾值150
看得出,不一樣的配置仍是有不一樣的效果的。可是硬要說誰號誰壞,可能仍是難以確定。
另外,測試中發現使用精確版本的抑制在線條拐角的地方可能連續性會比OpenCV版本要好一些,不會出現斷裂。
在耗時方面,其實這個函數的效果和不少參數仍是有關的,正如前面所說,若是進行了預處理,耗時會短一些,若是低閾值越大,耗時也越短,選用OpenCV的抑制算法速度也能有所提升。測試2500*2000的灰度圖,大概須要60ms.
測試Demo:http://files.cnblogs.com/files/Imageshop/SSE_Optimization_Demo.rar。