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()
可以看到,這個直線檢測是比之前的要好很多的,我們將在下一次中介紹霍夫圓變換,用途更為廣泛。
天道酬勤 循序漸進 技壓群雄