python Canny邊緣檢測(cè)算法的實(shí)現(xiàn)
圖像邊緣信息主要集中在高頻段,通常說(shuō)圖像銳化或檢測(cè)邊緣,實(shí)質(zhì)就是高頻濾波。我們知道微分運(yùn)算是求信號(hào)的變化率,具有加強(qiáng)高頻分量的作用。在空域運(yùn)算中來(lái)說(shuō),對(duì)圖像的銳化就是計(jì)算微分。對(duì)于數(shù)字圖像的離散信號(hào),微分運(yùn)算就變成計(jì)算差分或梯度。圖像處理中有多種邊緣檢測(cè)(梯度)算子,常用的包括普通一階差分,Robert算子(交叉差分),Sobel算子等等,是基于尋找梯度強(qiáng)度。拉普拉斯算子(二階差分)是基于過(guò)零點(diǎn)檢測(cè)。通過(guò)計(jì)算梯度,設(shè)置閥值,得到邊緣圖像。
Canny邊緣檢測(cè)算子是一種多級(jí)檢測(cè)算法。1986年由John F. Canny提出,同時(shí)提出了邊緣檢測(cè)的三大準(zhǔn)則:
- 低錯(cuò)誤率的邊緣檢測(cè):檢測(cè)算法應(yīng)該精確地找到圖像中的盡可能多的邊緣,盡可能的減少漏檢和誤檢。
- 最優(yōu)定位:檢測(cè)的邊緣點(diǎn)應(yīng)該精確地定位于邊緣的中心。
- 圖像中的任意邊緣應(yīng)該只被標(biāo)記一次,同時(shí)圖像噪聲不應(yīng)產(chǎn)生偽邊緣。
Canny算法出現(xiàn)以后一直是作為一種標(biāo)準(zhǔn)的邊緣檢測(cè)算法,此后也出現(xiàn)了各種基于Canny算法的改進(jìn)算法。時(shí)至今日,Canny算法及其各種變種依舊是一種優(yōu)秀的邊緣檢測(cè)算法。而且除非前提條件很適合,你很難找到一種邊緣檢測(cè)算子能顯著地比Canny算子做的更好。
關(guān)于各種差分算子,還有Canny算子的簡(jiǎn)單介紹,這里就不羅嗦了,網(wǎng)上都可以找得到。直接進(jìn)入Canny算法的實(shí)現(xiàn)。Canny算法分為以下幾步。
1. 高斯模糊。
這一步很簡(jiǎn)單,類似于LoG算子(Laplacian of Gaussian)作高斯模糊一樣,主要作用就是去除噪聲。因?yàn)樵肼曇布杏诟哳l信號(hào),很容易被識(shí)別為偽邊緣。應(yīng)用高斯模糊去除噪聲,降低偽邊緣的識(shí)別。但是由于圖像邊緣信息也是高頻信號(hào),高斯模糊的半徑選擇很重要,過(guò)大的半徑很容易讓一些弱邊緣檢測(cè)不到。
2. 計(jì)算梯度幅值和方向。
圖像的邊緣可以指向不同方向,因此經(jīng)典Canny算法用了四個(gè)梯度算子來(lái)分別計(jì)算水平,垂直和對(duì)角線方向的梯度。但是通常都不用四個(gè)梯度算子來(lái)分別計(jì)算四個(gè)方向。常用的邊緣差分算子(如Rober,Prewitt,Sobel)計(jì)算水平和垂直方向的差分Gx和Gy。這樣就可以如下計(jì)算梯度模和方向:
梯度角度 θ 范圍從弧度 -π 到 π,然后把它近似到四個(gè)方向,分別代表水平,垂直和兩個(gè)對(duì)角線方向(0°,45°,90°,135°)??梢砸浴纈π/8(i=1,3,5,7)分割,落在每個(gè)區(qū)域的梯度角給一個(gè)特定值,代表四個(gè)方向之一。
這里我選擇Sobel算子計(jì)算梯度。Sobel算法很簡(jiǎn)單,到處都可以找到,就不列出代碼來(lái)了。相對(duì)于其他邊緣算子,Sobel算子得出來(lái)的邊緣粗大明亮。
下圖是對(duì)上面半徑2的高斯模糊圖像L通道(HSL)應(yīng)用Sobel算子的梯度模圖,沒(méi)有施加任何閥值。
Sobel算子,無(wú)閥值
3. 非最大值抑制。
非最大值抑制是一種邊緣細(xì)化方法。通常得出來(lái)的梯度邊緣不止一個(gè)像素寬,而是多個(gè)像素寬。就像我們所說(shuō)Sobel算子得出來(lái)的邊緣粗大而明亮,從上面Lena圖的Sobel結(jié)果可以看得出來(lái)。因此這樣的梯度圖還是很“模糊”。而準(zhǔn)則3要求,邊緣只有一個(gè)精確的點(diǎn)寬度。非最大值抑制能幫助保留局部最大梯度而抑制所有其他梯度值。這意味著只保留了梯度變化中最銳利的位置。算法如下:
- 比較當(dāng)前點(diǎn)的梯度強(qiáng)度和正負(fù)梯度方向點(diǎn)的梯度強(qiáng)度。
- 如果當(dāng)前點(diǎn)的梯度強(qiáng)度和同方向的其他點(diǎn)的梯度強(qiáng)度相比較是最大,保留其值,否則抑制,即設(shè)為0。比如當(dāng)前點(diǎn)的方向指向正上方90°方向,那它需要和垂直方向,它的正上方和正下方的像素比較。
注意,方向的正負(fù)是不起作用的,比如東南方向和西北方向是一樣的,都認(rèn)為是對(duì)角線的一個(gè)方向。前面我們把梯度方向近似到水平,垂直和兩個(gè)對(duì)角線四個(gè)方向,所以每個(gè)像素根據(jù)自身方向在這四個(gè)方向之一進(jìn)行比較,決定是否保留。這一部分的代碼也很簡(jiǎn)單,列出如下。pModule,pDirection分別記錄了上一步梯度模值和梯度方向。
pmoddrow = pModule + Width + 1; pdirdrow = pDirection + Width + 1; pstrongdrow = pStrong + Width + 1; for (i = 1; i < Hend - 1; i++) { pstrongd = pstrongdrow; pmodd = pmoddrow; pdird = pdirdrow; for (j = 1; j < Wend - 1; j++) { switch (*pdird) { case 0: // x direction case 4: if (*pmodd > *(pmodd - 1) && *pmodd > *(pmodd + 1)) *pstrongd = 255; break; case 1: // northeast-southwest direction. Notice the data order on y direction of bmp data case 5: if (*pmodd > *(pmodd + Width + 1) && *pmodd > *(pmodd - Width - 1)) *pstrongd = 255; break; case 2: // y direction case 6: if (*pmodd > *(pmodd - Width) && *pmodd > *(pmodd + Width)) *pstrongd = 255; break; case 3: // northwest-southeast direction. Notice the data order on y direction of bmp data case 7: if (*pmodd > *(pmodd + Width - 1) && *pmodd > *(pmodd - Width + 1)) *pstrongd = 255; break; default: ASSERT(0); break; } pstrongd++; pmodd++; pdird++; } pstrongdrow += Width; pmoddrow += Width; pdirdrow += Width; }
下圖是非最大值抑制的結(jié)果??梢?jiàn)邊緣寬度已經(jīng)大大減小。但是這個(gè)圖像中因?yàn)闆](méi)有應(yīng)用任何閥值,還含有大量小梯度模值的點(diǎn),也就是圖中很暗的地方。下面,閥值要上場(chǎng)了。
非最大值抑制結(jié)果
4. 雙閥值。
一般的邊緣檢測(cè)算法用一個(gè)閥值來(lái)濾除噪聲或顏色變化引起的小的梯度值,而保留大的梯度值。Canny算法應(yīng)用雙閥值,即一個(gè)高閥值和一個(gè)低閥值來(lái)區(qū)分邊緣像素。如果邊緣像素點(diǎn)梯度值大于高閥值,則被認(rèn)為是強(qiáng)邊緣點(diǎn)。如果邊緣梯度值小于高閥值,大于低閥值,則標(biāo)記為弱邊緣點(diǎn)。小于低閥值的點(diǎn)則被抑制掉。這一步算法很簡(jiǎn)單。
5. 滯后邊界跟蹤。
至此,強(qiáng)邊緣點(diǎn)可以認(rèn)為是真的邊緣。弱邊緣點(diǎn)則可能是真的邊緣,也可能是噪聲或顏色變化引起的。為得到精確的結(jié)果,后者引起的弱邊緣點(diǎn)應(yīng)該去掉。通常認(rèn)為真實(shí)邊緣引起的弱邊緣點(diǎn)和強(qiáng)邊緣點(diǎn)是連通的,而由噪聲引起的弱邊緣點(diǎn)則不會(huì)。所謂的滯后邊界跟蹤算法檢查一個(gè)弱邊緣點(diǎn)的8連通領(lǐng)域像素,只要有強(qiáng)邊緣點(diǎn)存在,那么這個(gè)弱邊緣點(diǎn)被認(rèn)為是真的邊緣保留下來(lái)。
這個(gè)算法搜索所有連通的弱邊緣,如果一條連通的弱邊緣的任何一個(gè)點(diǎn)和強(qiáng)邊緣點(diǎn)連通,則保留這條弱邊緣,否則抑制這條弱邊緣。搜索時(shí)可以用廣度優(yōu)先或者深度優(yōu)先算法,我在這里實(shí)現(xiàn)了應(yīng)該是最容易的深度優(yōu)先算法。一次連通一條邊緣的深度優(yōu)先算法如下:
- 準(zhǔn)備一個(gè)棧s,一個(gè)隊(duì)列q,設(shè)連通指示變量connected為假。從圖像的第一個(gè)點(diǎn)開始,進(jìn)入2。
- 如果這個(gè)點(diǎn)是弱邊界點(diǎn)并且沒(méi)有被標(biāo)記,把它標(biāo)記,并把它作為第一個(gè)元素放入棧s中,同時(shí)把它放入記錄連通曲線的隊(duì)列q,進(jìn)入3。如果這個(gè)點(diǎn)不是弱邊界或者已經(jīng)被標(biāo)記過(guò),到圖像的下一個(gè)點(diǎn),重復(fù)2。
- 從棧s中取出一個(gè)元素,查找它的8像素領(lǐng)域。如果一個(gè)領(lǐng)域像素是弱邊界并且沒(méi)有被標(biāo)記過(guò),把這個(gè)領(lǐng)域像素標(biāo)記,并加入棧s中,同時(shí)加入隊(duì)列q。同時(shí)查找領(lǐng)域?qū)?yīng)的強(qiáng)邊界圖,如果有一個(gè)像素是強(qiáng)邊界,表示這條弱邊界曲線和強(qiáng)邊界聯(lián)通,設(shè)置connected為真。重復(fù)3直到棧中沒(méi)有元素了。如果connected為假,則依次從隊(duì)列q中取出每個(gè)元素,清空標(biāo)記。如果connected為真,保留標(biāo)記。
- 清空隊(duì)列q,設(shè)置connected為假,移動(dòng)到圖像的下一個(gè)點(diǎn),回到2。
pmoddrow = pModule + Width + 1; pdirdrow = pDirection + Width + 1; pstrongdrow = pStrong + Width + 1; for (i = 1; i < Hend - 1; i++) { pstrongd = pstrongdrow; pmodd = pmoddrow; pdird = pdirdrow; for (j = 1; j < Wend - 1; j++) { switch (*pdird) { case 0: // x direction case 4: if (*pmodd > *(pmodd - 1) && *pmodd > *(pmodd + 1)) *pstrongd = 255; break; case 1: // northeast-southwest direction. Notice the data order on y direction of bmp data case 5: if (*pmodd > *(pmodd + Width + 1) && *pmodd > *(pmodd - Width - 1)) *pstrongd = 255; break; case 2: // y direction case 6: if (*pmodd > *(pmodd - Width) && *pmodd > *(pmodd + Width)) *pstrongd = 255; break; case 3: // northwest-southeast direction. Notice the data order on y direction of bmp data case 7: if (*pmodd > *(pmodd + Width - 1) && *pmodd > *(pmodd - Width + 1)) *pstrongd = 255; break; default: ASSERT(0); break; } pstrongd++; pmodd++; pdird++; } pstrongdrow += Width; pmoddrow += Width; pdirdrow += Width; }
下面是對(duì)Lena圖計(jì)算Canny邊緣檢測(cè)的梯度模圖和二值化圖,高斯半徑2,高閥值100,低閥值50。
Canny檢測(cè)梯度模圖 Canny檢測(cè)梯度二值圖
作為對(duì)比,下面是用一階差分和Sobel算子對(duì)原圖計(jì)算的結(jié)果,閥值100。由于一階差分的梯度值相對(duì)較小,我對(duì)一階差分的梯度值放大了一定倍數(shù),使得它和Sobel的梯度值保持同樣的水平。
一階差分梯度模圖 一階差分梯度二值圖
Sobel梯度模圖 Sobel梯度二值圖
很明顯,Canny邊緣檢測(cè)的效果是很顯著的。相比普通的梯度算法大大抑制了噪聲引起的偽邊緣,而且是細(xì)化過(guò)的邊緣,易于后續(xù)處理。對(duì)于對(duì)比度較低的圖像,通過(guò)調(diào)節(jié)參數(shù),Canny算法也能有很好的效果。
到此這篇關(guān)于python Canny邊緣檢測(cè)算法的實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)Canny邊緣檢測(cè)算法內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
python腳本實(shí)現(xiàn)分析dns日志并對(duì)受訪域名排行
這篇文章主要介紹了python腳本實(shí)現(xiàn)分析dns日志并對(duì)受訪域名排行,本文是在Windows服務(wù)器環(huán)境中實(shí)現(xiàn),需要的朋友可以參考下2014-09-09Scrapy框架基本命令與settings.py設(shè)置
這篇文章主要介紹了Scrapy框架基本命令與settings.py設(shè)置,結(jié)合實(shí)例形式分析了創(chuàng)建爬蟲項(xiàng)目、創(chuàng)建爬蟲文件、存儲(chǔ)、打開網(wǎng)頁(yè)及settings.py設(shè)置等相關(guān)操作技巧,需要的朋友可以參考下2020-02-02python3.8 微信發(fā)送服務(wù)器監(jiān)控報(bào)警消息代碼實(shí)現(xiàn)
這篇文章主要介紹了python3.8 微信發(fā)送服務(wù)器監(jiān)控報(bào)警消息代碼實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-11-11Python Django給admin添加Action的方法實(shí)例詳解
這篇文章主要介紹了Django給admin添加Action的方法,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-04-04在Python的Django框架上部署ORM庫(kù)的教程
這篇文章主要介紹了在Python的Django框架上部署ORM庫(kù)的教程,文中還給出了幾個(gè)ORM庫(kù)之間的對(duì)比,需要的朋友可以參考下2015-04-04influx+grafana自定義python采集數(shù)據(jù)和一些坑的總結(jié)
一些數(shù)據(jù)的類型不正確會(huì)導(dǎo)致no datapoint的錯(cuò)誤,真是令人抓狂,本文就是總結(jié)一下采集數(shù)據(jù)種的一些坑,希望大家可以從中獲益2018-09-09