OpenCV——Canny直线检测
目錄
- 前言
- 正文
- 原理
- 高斯濾波過濾
- 計(jì)算像素點(diǎn)的梯度方向(Sobel算子)
- 非極大值抑制
- 用雙閾值算法檢測和連接邊緣
- 通過抑制孤立的弱邊緣最終完成邊緣檢測
- 代碼
- 參考文獻(xiàn)
前言
Canny邊緣檢測是從不同視覺對象中提取有用的結(jié)構(gòu)信息并大大減少要處理的數(shù)據(jù)量的一種技術(shù)。我們這里主要用其來進(jìn)行直線邊緣檢測。
正文
原理
Canny邊緣檢測算法主要分為以下五個(gè)步驟(參考自:Canny邊緣檢測算法)
高斯濾波過濾
為了盡可能減少噪聲對邊緣檢測結(jié)果的影響,所以必須濾除噪聲以防止由噪聲引起的錯(cuò)誤檢測。為了平滑圖像,使用高斯濾波器與圖像進(jìn)行卷積,該步驟將平滑圖像,以減少邊緣檢測器上明顯的噪聲影響。大小為(2k+1)x(2k+1)的高斯濾波器核的生成方程式由下式給出:
下面是一個(gè)sigma = 1.5,尺寸為3x3的高斯卷積核的例子(需要注意歸一化):
這個(gè)卷積核是怎么來的,我們可以算一下。我就直接摘抄網(wǎng)上的一些圖了:
假定中心點(diǎn)的坐標(biāo)是(0,0),那么距離它最近的8個(gè)點(diǎn)的坐標(biāo)如下:
遠(yuǎn)的點(diǎn)以此類推。
為了計(jì)算權(quán)重矩陣,需要設(shè)定σ的值。假定σ=1.5,則模糊半徑為1的權(quán)重矩陣如下:
這個(gè)權(quán)重矩陣,就是你把上面sigma和x,y的值都帶入那個(gè)公式就可以了。
這9個(gè)點(diǎn)的權(quán)重總和等于0.4787147,如果只計(jì)算這9個(gè)點(diǎn)的加權(quán)平均,還必須讓它們的權(quán)重之和等于1,因此上面9個(gè)值還要分別除以0.4787147,得到最終的權(quán)重矩陣。
接下來,計(jì)算高斯模糊
有了權(quán)重矩陣,就可以計(jì)算高斯模糊的值了。
假設(shè)現(xiàn)有9個(gè)像素點(diǎn),灰度值(0-255)如下:
每個(gè)點(diǎn)乘以自己的權(quán)重值:
得到
將這9個(gè)值加起來,就是中心點(diǎn)的高斯模糊的值。
對所有點(diǎn)重復(fù)這個(gè)過程,就得到了高斯模糊后的圖像。如果原圖是彩色圖片,可以對RGB三個(gè)通道分別做高斯模糊。
那么如果一個(gè)點(diǎn)處于邊界,周邊沒有足夠的點(diǎn),怎么辦?
一個(gè)變通方法,就是把已有的點(diǎn)拷貝到另一面的對應(yīng)位置,模擬出完整的矩陣。
就類似于這樣的一個(gè)效果:
你看上面我圈起來的藍(lán)色的地方,就出現(xiàn)了,119-46<225-4。圖像的邊緣這樣就因此便的平滑了。這就是高斯濾波的作用了。
計(jì)算像素點(diǎn)的梯度方向(Sobel算子)
索貝爾算子(Sobeloperator)主要用作邊緣檢測,在技術(shù)上,它是一離散性差分算子,用來運(yùn)算圖像亮度函數(shù)的灰度之近似值。在圖像的任何一點(diǎn)使用此算子,將會(huì)產(chǎn)生對應(yīng)的灰度矢量或是其法矢量。
這里就直接貼圖了:
Sobel算子根據(jù)像素點(diǎn)上下、左右鄰點(diǎn)灰度加權(quán)差,在邊緣處達(dá)到極值這一現(xiàn)象檢測邊緣。對噪聲具有平滑作用,提供較為精確的邊緣方向信息,邊緣定位精度不夠高。當(dāng)對精度要求不是很高時(shí),是一種較為常用的邊緣檢測方法。
類似的結(jié)果圖就是下面這樣:
上面這個(gè)就是算出來的梯度值,但這上面那個(gè)sobel算子的符號好像反了,不用管這個(gè),直接按照最上面的那張圖算即可。
非極大值抑制
指尋找像素點(diǎn)局部最大值,將非極大值點(diǎn)所對應(yīng)的灰度值置為0,這樣可以剔除掉一大部分非邊緣的點(diǎn)。要進(jìn)行非極大值抑制,就首先要確定像素點(diǎn)C的灰度值在其8值鄰域內(nèi)是否為最大。圖1中藍(lán)色的線條方向?yàn)镃點(diǎn)的梯度方向,這樣就可以確定其局部的最大值肯定分布在這條線上,也即出了C點(diǎn)外,梯度方向的交點(diǎn)dTmp1和dTmp2這兩個(gè)點(diǎn)的值也可能會(huì)是局部最大值。因此,判斷C點(diǎn)灰度與這兩個(gè)點(diǎn)灰度大小即可判斷C點(diǎn)是否為其鄰域內(nèi)的局部最大灰度點(diǎn)。如果經(jīng)過判斷,C點(diǎn)灰度值小于這兩個(gè)點(diǎn)中的任一個(gè),那就說明C點(diǎn)不是局部極大值,那么則可以排除C點(diǎn)為邊緣。這就是非極大值抑制的工作原理。
上面的這個(gè)解釋應(yīng)該還是算比較清楚的,特別還要注意的兩個(gè)點(diǎn)是:
1)中非最大抑制是回答這樣一個(gè)問題:“當(dāng)前的梯度值在梯度方向上是一個(gè)局部最大值嗎?” 所以,要把當(dāng)前位置的梯度值與梯度方向上兩側(cè)的梯度值進(jìn)行比較;
2)梯度方向垂直于邊緣方向。
但實(shí)際上,我們只能得到C點(diǎn)鄰域的8個(gè)點(diǎn)的值,而dTmp1和dTmp2并不在其中,要得到這兩個(gè)值就需要對該兩個(gè)點(diǎn)兩端的已知灰度進(jìn)行線性插值,也即根據(jù)圖1中的g1和g2對dTmp1進(jìn)行插值,根據(jù)g3和g4對dTmp2進(jìn)行插值,這要用到其梯度方向,這是上文Canny算法中要求解梯度方向矩陣Thita的原因。
完成非極大值抑制后,會(huì)得到一個(gè)二值圖像,非邊緣的點(diǎn)灰度值均為0,可能為邊緣的局部灰度極大值點(diǎn)可設(shè)置其灰度為128。根據(jù)下文的具體測試圖像可以看出,這樣一個(gè)檢測結(jié)果還是包含了很多由噪聲及其他原因造成的假邊緣。因此還需要進(jìn)一步的處理。
用雙閾值算法檢測和連接邊緣
Canny算法中減少假邊緣數(shù)量的方法是采用雙閾值法。選擇兩個(gè)閾值(關(guān)于閾值的選取方法在擴(kuò)展中進(jìn)行討論),根據(jù)高閾值得到一個(gè)邊緣圖像,這樣一個(gè)圖像含有很少的假邊緣,但是由于閾值較高,產(chǎn)生的圖像邊緣可能不閉合,未解決這樣一個(gè)問題采用了另外一個(gè)低閾值。
在高閾值圖像中把邊緣鏈接成輪廓,當(dāng)?shù)竭_(dá)輪廓的端點(diǎn)時(shí),該算法會(huì)在斷點(diǎn)的8鄰域點(diǎn)中尋找滿足低閾值的點(diǎn),再根據(jù)此點(diǎn)收集新的邊緣,直到整個(gè)圖像邊緣閉合。
雙閾值的玩法參考下面這張圖:
通過抑制孤立的弱邊緣最終完成邊緣檢測
到目前為止,被劃分為強(qiáng)邊緣的像素點(diǎn)已經(jīng)被確定為邊緣,因?yàn)樗鼈兪菑膱D像中的真實(shí)邊緣中提取出來的。然而,對于弱邊緣像素,將會(huì)有一些爭論,因?yàn)檫@些像素可以從真實(shí)邊緣提取也可以是因噪聲或顏色變化引起的。為了獲得準(zhǔn)確的結(jié)果,應(yīng)該抑制由后者引起的弱邊緣。通常,由真實(shí)邊緣引起的弱邊緣像素將連接到強(qiáng)邊緣像素,而噪聲響應(yīng)未連接。為了跟蹤邊緣連接,通過查看弱邊緣像素及其8個(gè)鄰域像素,只要其中一個(gè)為強(qiáng)邊緣像素,則該弱邊緣點(diǎn)就可以保留為真實(shí)的邊緣。
代碼
我這里就直接調(diào)用OpenCV的一些函數(shù)去實(shí)現(xiàn)這樣的一個(gè)效果。
import cv2 as cv import numpy as np# canny運(yùn)算步驟:5步 # 1. 高斯模糊 - GaussianBlur # 2. 灰度轉(zhuǎn)換 - cvtColor # 3. 計(jì)算梯度 - Sobel/Scharr # 4. 非極大值抑制 # 5. 高低閾值輸出二值圖像# 非極大值抑制: # 算法使用一個(gè)3×3鄰域作用在幅值陣列M[i,j]的所有點(diǎn)上; # 每一個(gè)點(diǎn)上,鄰域的中心像素M[i,j]與沿著梯度線的兩個(gè)元素進(jìn)行比較, # 其中梯度線是由鄰域的中心點(diǎn)處的扇區(qū)值ζ[i,j]給出。 # 如果在鄰域中心點(diǎn)處的幅值M[i,j]不比梯度線方向上的兩個(gè)相鄰點(diǎn)幅值大,則M[i,j]賦值為零,否則維持原值; # 此過程可以把M[i,j]寬屋脊帶細(xì)化成只有一個(gè)像素點(diǎn)寬,即保留屋脊的高度值。# 高低閾值連接 # T1,T2為閾值,凡是高于T2的都保留,凡是低于T1的都丟棄 # 從高于T2的像素出發(fā),凡是大于T1而且相互連接的都保留。最終得到一個(gè)輸出二值圖像 # 推薦高低閾值比值為T2:T1 = 3:1/2:1,其中T2高閾值,T1低閾值def edge_demo(image):blurred = cv.GaussianBlur(image,(3,3),0)#高斯模糊gray = cv.cvtColor(blurred,cv.COLOR_BGR2GRAY)# 將其變成灰度圖grad_x = cv.Sobel(gray,cv.CV_16SC1,1,0)#用sobel算子求梯度。最后兩個(gè)參數(shù)就是說是求的x,還是y的梯度grad_y = cv.Sobel(gray,cv.CV_16SC1,0,1)edge_output = cv.Canny(grad_x,grad_y,30,150)# 將兩個(gè)梯度傳入,然后傳入高閾值與低閾值#edge_output = cv.Canny(gray,50,150)cv.imshow("gray",gray)cv.imshow("canny_dmeo",edge_output)src = cv.imread("../images/boss2.jpg") cv.namedWindow("input image",cv.WINDOW_AUTOSIZE) cv.imshow('input image', src) edge_demo(src) cv.waitKey(0) # 等有鍵輸入或者1000ms后自動(dòng)將窗口消除,0表示只用鍵輸入結(jié)束窗口cv.destroyAllWindows()效果圖
參考文獻(xiàn)
總結(jié)
以上是生活随笔為你收集整理的OpenCV——Canny直线检测的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 二十年三万公里,线上跑完中国陆地国境线
- 下一篇: 京瓷6525_京瓷6525复印机报价 京