1. 程式人生 > 其它 >OpenCV-Python系列之霍夫圓變換

OpenCV-Python系列之霍夫圓變換

上個教程我們討論了霍夫線變換,這次我們來看看霍夫圓變換。

原理

霍夫圓變換和霍夫線變換的原理類似。霍夫線變換是兩個引數(r,θ),霍夫圓需要三個引數,圓心的x,y座標和圓的半徑.如下對應的三個引數c1,c2,c3:

例如:

其形狀和:

類似,該函式是由z=x沿z軸旋轉而成的圓錐曲面。

對於xy平面的一個點x0,y0(上述對應的點為(1,1)),則對應的由c1,c2,c3組成三維空間的空間曲面。對於c1,c2,c3平面的一個點,則對應的在xy平面它是一個圓。

對於在x,y平面上的三個點(x0,y0),(x1,y1),(x2,y2),在c1,c2,c3三維空間是對應的三個空間曲面(此時c1,c2,c3相當於常量)。

求解這三個方程,我們可以得到c1,c2,c3的值。這說明(x0,y0),(x1,y1),(x2,y2)這三個點在由c1,c2,c3所確定的圓上(即c1,c2,c3分別表示圓的圓心x座標、圓心y座標以及圓的半徑),且三個點對應由c1,c2,c3確定的空間的三個空間曲面。進一步說明,在xy平面,三個點在同一個圓上,則它們對應的空間曲面相交於一點(即點(c1,c2,c3))。

故我們如果知道一個邊界上的點的數目,足夠多,且這些點與之對應的空間曲面相交於一點。則這些點構成的邊界,就接近一個圓形。

上述描述的是標準霍夫圓變換的原理,由於三維空間的計算量大大增大的原因, 標準霍夫圓變化很難被應用到實際中。

OpenCV實現的是一個比標準霍夫圓變換更為靈活的檢測方法: 霍夫梯度法, 也叫2-1霍夫變換(21HT),

它的原理依據是圓心一定是在圓上的每個點的模向量上, 這些圓上點模向量的交點就是圓心, 霍夫梯度法的第一步就是找到這些圓心, (圓心包含了圓心處的x和y座標)這樣三維的累加平面就又轉化為二維累加平面. 第二步根據所有候選中心的邊緣非0畫素對其的支援程度來確定半徑。

OpenCV中的cv2.HoughCircles()函式實現了圓形檢測,它使用的演算法也是改進的霍夫變換——2-1霍夫變換(21HT)。也就是把霍夫變換分為兩個階段,從而減小了霍夫空間的維數。第一階段用於檢測圓心,第二階段從圓心推匯出圓半徑。檢測圓心的原理是圓心是它所在圓周所有法線的交匯處,因此只要找到這個交點,即可確定圓心,該方法所用的霍夫空間與影象空間的性質相同,因此它僅僅是二維空間。檢測圓半徑的方法是從圓心到圓周上的任意一點的距離(即半徑)是相同,只要確定一個閾值,只要相同距離的數量大於該閾值,我們就認為該距離就是該圓心所對應的圓半徑,該方法只需要計算半徑直方圖,不使用霍夫空間。圓心和圓半徑都得到了,那麼通過公式1一個圓形就得到了。從上面的分析可以看出,2-1霍夫變換把標準霍夫變換的三維霍夫空間縮小為二維霍夫空間,因此無論在記憶體的使用上還是在執行效率上,2-1霍夫變換都遠遠優於標準霍夫變換。但該演算法有一個不足之處就是由於圓半徑的檢測完全取決於圓心的檢測,因此如果圓心檢測出現偏差,那麼圓半徑的檢測肯定也是錯誤的。

2-1霍夫變換的具體步驟為:

1)首先對影象進行邊緣檢測,呼叫OpenCV自帶的Canny()函式,將影象二值化,得到邊緣影象。

2)對邊緣影象上的每一個非零點。採用Sobel()函式,計算x方向導數和y方向的導數,從而得到梯度。從邊緣點,沿著梯度和梯度的反方向,對引數指定的min_radius到max_radium的每一個畫素,在累加器中被累加。同時記下邊緣影象中每一個非0點的位置。

3)從(二維)累加器中這些點中選擇候選中心。這些中心都大於給定的閾值和其相鄰的四個鄰域點的累加值。

4)對於這些候選中心按照累加值降序排序,以便於最支援的畫素的中心首次出現。

5)對於每一箇中心,考慮到所有的非0畫素(非0,梯度不為0),這些畫素按照與其中心的距離排序,從最大支援的中心的最小距離算起,選擇非零畫素最支援的一條半徑。

6)如果一箇中心受到邊緣影象非0畫素的充分支援,並且到前期被選擇的中心有足夠的距離。則將圓心和半徑壓入到序列中,得以保留。

我們先來看OpenCV中的函式API:

circles=cv.HoughCircles(image, method, dp, minDist[, circles[, param1[, param2[, minRadius[, maxRadius]]]]])

image:輸入影象 (灰度圖);

method:指定檢測方法. 現在OpenCV中只有霍夫梯度法;

dp:累加器影象的反比分辨=1即可預設;

minDist = src_gray.rows/8: 檢測到圓心之間的最小距離,這是一個經驗值。這個大了,那麼多個圓就是被認為一個圓;

param_1 = 200: Canny邊緣函式的高閾值;

param_2 = 100: 圓心檢測閾值.根據你的影象中的圓大小設定,當這張圖片中的圓越小,那麼此值就設定應該被設定越小。當設定的越小,那麼檢測出的圓越多,在檢測較大的圓時則會產生很多噪聲。所以要根據檢測圓的大小變化;

min_radius = 0: 能檢測到的最小圓半徑, 預設為0;

max_radius = 0: 能檢測到的最大圓半徑, 預設為0。

我們使用影象做實驗:

影象中的雜亂資訊太多,我們必須先進行濾波,如果不進行濾波的情況下,我們可以看程式碼:

def Houghcircle(img):
    cimg = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)
    circles = cv2.HoughCircles(img, cv2.HOUGH_GRADIENT, 1, 20,
                              param1=50, param2=30, minRadius=0, maxRadius=0)
    circles = np.uint16(np.around(circles))
    for i in circles[0, :]:
        # draw the outer circle
        cv2.circle(cimg, (i[0], i[1]), i[2], (0, 255, 0), 2)
        # draw the center of the circle
        cv2.circle(cimg, (i[0], i[1]), 2, (0, 0, 255), 3)
    cv2.imshow('detected circles', cimg)
    cv2.waitKey(0)

結果讓人感到亂七八糟的,現在我先採用中值濾波,繼而採用高斯濾波,我們來看程式碼:

def Houghcircle(img):
    img = cv2.medianBlur(img, 3)
    img = cv2.GaussianBlur(img,(17,19),0)
    cimg = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)
    circles = cv2.HoughCircles(img, cv2.HOUGH_GRADIENT, 1, 20,
                              param1=50, param2=30, minRadius=0, maxRadius=0)
    circles = np.uint16(np.around(circles))
    for i in circles[0, :]:
        # draw the outer circle
        cv2.circle(cimg, (i[0], i[1]), i[2], (0, 255, 0), 2)
        # draw the center of the circle
        cv2.circle(cimg, (i[0], i[1]), 2, (0, 0, 255), 3)
    cv2.imshow('detected circles', cimg)
    cv2.waitKey(0)

可以看到,效果良好,霍夫變換通常情況下受圖片的噪聲資訊干擾非常大,所以通常情況下我們需要對影象進行預處理操作。

天道酬勤 循序漸進 技壓群雄