1. 程式人生 > >【算法隨記】Canny邊緣檢測算法實現和優化分析。

【算法隨記】Canny邊緣檢測算法實現和優化分析。

輸入 放置 位圖 code 計算 並且 比較 lan 開篇

  以前的博文大部分都寫的非常詳細,有很多分析過程,不過寫起來確實很累人,一般一篇好的文章要整理個三四天,但是,時間越來越緊張,後續的一些算法可能就以隨記的方式,把實現過程的一些比較容易出錯和有價值的細節部分加以描述,並且可能需要對算法本身有一定了解的朋友才能明白我所描述的一些過程了。

  那這個系列的開篇,我們以Canny邊緣檢測算法為頭吧。

  相關參考資料:

  1、Canny邊緣檢測算法的實現。

  2、OpenCV(五)——超細節的Canny原理及算法實現

  3、OpenCV 之 邊緣檢測

  4、Opencv 3.0\opencv\sources\modules\imgproc\src\canny.cpp

Canny邊緣檢測是邊緣檢測算法領域裏最為著名的算法,其標準實現過程就是下面五個步驟:

  1、高斯模糊。

  2、 計算梯度幅值和方向。

  3、非最大值抑制。

  4、 雙閥值。

  5、 滯後邊界跟蹤

  個人認為,第一步可以不作為標準流程,在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。

技術分享圖片

【算法隨記】Canny邊緣檢測算法實現和優化分析。