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

OpenCV-Python系列之霍夫線變換

本次我們來看OpenCV中的霍夫線變換,它可以用於檢測影象中的直線進而標註出來。

基本原理

一條直線可由兩個點A=(X1,Y1)和B=(X2,Y2)確定(笛卡爾座標):

另一方面,也可以寫成關於(k,q)的函式表示式(霍夫空間):

對應的變換可以通過圖形直觀表示:

變換後的空間成為霍夫空間。即:笛卡爾座標系中一條直線,對應霍夫空間的一個點

反過來同樣成立(霍夫空間的一條直線,對應笛卡爾座標系的一個點):

再來看看A、B兩個點,對應霍夫空間的情形:

一步步來,再看一下三個點共線的情況:

可以看出如果笛卡爾座標系的點共線,這些點在霍夫空間對應的直線交於一點:這也是必然,共線只有一種取值可能。

如果不止一條直線呢?再看看多個點的情況(有兩條直線):

其實(3,2)與(4,1)也可以組成直線,只不過它有兩個點確定,而圖中A、B兩點是由三條直線匯成,這也是霍夫變換的後處理的基本方式選擇由儘可能多直線匯成的點

看看,霍夫空間:選擇由三條交匯直線確定的點(中間圖),對應的笛卡爾座標系的直線(右圖)。

到這裡問題似乎解決了,已經完成了霍夫變換的求解,但是如果像下圖這種情況呢?

k=∞是不方便表示的,而且q怎麼取值呢,這樣不是辦法。因此考慮將笛卡爾座標系換為:極座標表示

在極座標系下,其實是一樣的:極座標的點→霍夫空間的直線,只不過霍夫空間不再是[k,q]的引數,而是的引數,給出對比圖:

我們來看霍夫變換的演算法步驟:

OpenCV中的霍夫變換

在OpenCV中,我們可以使用相應的函式API,先來看函式原型:
lines=cv.HoughLines(image, rho, theta, threshold[, lines[, srn[, stn[, min_theta[, max_theta]]]]])

第一個引數,輸入影象應該是一個二值影象,因此在應用hough變換之前應用閾值或使用Canny邊緣檢測.

第二和第三個引數分別是ρ和θ的精度.

第四個引數是閾值,這意味著它應該被視為一條直線.

記住,線條的數量取決於直線上的點的數量,所以它表示應該檢測到的最小長度。

我們來看程式碼:

def Hough(img):
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    edges = cv2.Canny(gray, 50, 150, apertureSize=3)

    lines = cv2.HoughLines(edges, 1, np.pi / 180, 90)
    for line in lines:
        rho, theta = line[0]
        a = np.cos(theta)
        b = np.sin(theta)
        x0 = a * rho
        y0 = b * rho
        x1 = int(x0 + 1000 * (-b))
        y1 = int(y0 + 1000 * (a))
        x2 = int(x0 - 1000 * (-b))
        y2 = int(y0 - 1000 * (a))

        cv2.line(img, (x1, y1), (x2, y2), (0, 0, 255), 1)

    cv2.imshow('show', img)
    cv2.waitKey()

可以看到,線條被檢測出來,不過精度並不好,接下來我們介紹另一種直線檢測。

概率霍夫線變換

上面介紹的標準霍夫變換,其本質上就是把影象中的邊緣畫素對映到它的霍夫空間,比如一共有M個邊緣畫素,則所有的邊緣畫素都需要進行對映,其運算量和所需記憶體都會很大。

如果只處理影象的m(m<M)個邊緣畫素點,則這m個邊緣畫素點的選取是具有一定概率的,因此該方法就是概率霍夫變換。該方法有一個重要的特點就是能夠檢測出線段,即能夠檢測出影象中直線的兩個端點,從而定點陣圖像中的直線。

下面是概率霍夫變換的簡易步驟:

1. 隨機抽取影象中的一個邊緣畫素點,如果已經被標定為是某一條直線上的點,則繼續在剩下的邊緣點中隨機抽取一個邊緣點,直到所有邊緣點都抽取完為止;

2. 對該點進行霍夫變換,並進行累加計算;

3. 選取在霍夫空間內累加值最大的點,如果該點的值大於閾值,則進行步驟4,否則回到步驟1;

4. 對於累加值大於閾值的點,從該點出發,沿著影象中的直線的方向位移,從而找到直線的兩個端點;

5. 計算直線的長度,如果大於某個閾值,則被認為是直線並輸出。

OpenCV提供了函式API:

lines=cv.HoughLinesP(image, rho, theta, threshold[, lines[, minLineLength[, maxLineGap]]])

其引數跟上面的一樣,這裡不再過多贅述,函式cv2.HoughLinesP()是一種概率直線檢測,我們知道,原理上講hough變換是一個耗時耗力的演算法,尤其是每一個點計算,即使經過了canny轉換了有的時候點的個數依然是龐大的,這個時候我們採取一種概率挑選機制,不是所有的點都計算,而是隨機的選取一些個點來計算,相當於降取樣了。這樣的話我們的閾值設定上也要降低一些。在引數輸入輸出上,輸入不過多了兩個引數:minLineLengh(線的最短長度,比這個短的都被忽略)和MaxLineCap(兩條直線之間的最大間隔,小於此值,認為是一條直線)。輸出上也變了,不再是直線引數的,這個函式輸出的直接就是直線點的座標位置,這樣可以省去一系列for迴圈中的由引數空間到影象的實際座標點的轉換。

我們來看程式碼:

def HoughP(img):
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    edges = cv2.Canny(gray, 50, 150, apertureSize=3)
    lines = cv2.HoughLinesP(edges, 1, np.pi / 180, 100, minLineLength=90, maxLineGap=10)
    for line in lines:
        x1, y1, x2, y2 = line[0]
        cv2.line(img, (x1, y1), (x2, y2), (0, 255, 0), 1)

    cv2.imshow('show', img)
    cv2.waitKey()

可以看到,這個直線檢測是比之前的要好很多的,我們將在下一次中介紹霍夫圓變換,用途更為廣泛。

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