說明
在做項目中,需要識別圖片中的表格,了解到opencv中的哈夫變換,相關(guān)文檔只有英文版,未找到中文版,自行翻譯了一遍(能力有限渣翻譯),其中拓展閱讀內(nèi)容引自:Python OpenCV -- 霍夫線變換(十二),可幫助理解,僅供參考。
目標(biāo)
在本章中,我們可以:
了解哈夫變換的概念
如何使用哈夫變換識別圖片中的直線
了解以下函數(shù):cv2.HoughLines(), cv2.HoughLinesP()
理論
哈夫變換(Hough Transform)是一種流行的圖形識別技術(shù),如果數(shù)學(xué)模型中的圖形可以被肉眼識別,那么即使圖形損壞或者有一點扭曲,哈夫變換也可以識別圖形,接下來介紹它的工作原理。
直線可以通過類似 y = mx + c 或 極坐標(biāo) ρ = x cosθ + y sinθ(其中ρ表示原點到直線的垂直距離,θ是垂直線與水平軸之間的逆時針方向夾角,方向主要取決于如何放置坐標(biāo)系)來表示。如下圖:

如果線經(jīng)過原點下方,那就有小于180°的正[RHO][1]和夾角。如果線經(jīng)過原點上方,相較于使用大于180°的夾角,opencv會選擇使用小于180°的夾角,并采用負(fù)的RHO。垂直線是0°,水平線是90°。
來看看哈夫變換如何處理線條。任何線條都可以由(ρ,θ)2個參數(shù)形容,首先創(chuàng)建一個二維數(shù)組或者累加器(存儲2個參數(shù)的值),初始化為0。用行表示ρ,用列表示θ,數(shù)組的大小取決于精確度需求,假設(shè)精確度為1°,則需要180列,ρ最大距離可能是圖片對角線的長度,所以設(shè)置精確度為1像素,那么行數(shù)就是圖片對角線長。
拓展閱讀
1、
用 極坐標(biāo)系 來表示直線,直線的表達式可為:
y = (- \frac{cosθ}{sinθ})x + (\frac{r}{sinθ})
化簡得: r = x cosθ + y sinθ
2、一般來說對于點(x_0,y_0), 我們可以將通過這個點的一族直線統(tǒng)一定義為:
r_\theta = x_0 · cos\theta + y_0 · sin\theta
這就意味著每一對(r_\theta, \theta) 代表一條通過點(x_0,y_0)的直線.
假設(shè)有一個100*100的圖片,有一條水平線在中間,獲取這條線的第一個點,已知它的(x, y)值,然后將θ = 0,1,2,……,180放入線條方程中,獲取ρ值。對于每個(ρ, θ)單元格,累加器中與之相關(guān)的(ρ, θ)單元格每次增加的值為1,所以在累加器中,(50, 90) = 1和一些其他單項相關(guān)聯(lián)。
然后取線條上的第二個點,進行以上相同的動作,增加與之相關(guān)聯(lián)的(ρ, θ)單元格,此時(50, 90) = 2,現(xiàn)在的操作都會增加(ρ, θ)的值。繼續(xù)為線條上的每個點進行相同的操作,在每個點,(50, 90)單元格可以增值或者計數(shù),其他單元格可能不會被計數(shù)。通過這種方式,最后(50, 90)單元格會得到最大的計數(shù)。如果在計數(shù)器中搜索最大計數(shù),就可以得到(50, 90 )的值,表示在距離原點50,90°角的地方有一條線,在以下動圖中顯示。

這就是哈夫變換作用于線條的原理,可以使用numpy來執(zhí)行它。下圖展示了累加模型,圖中高亮的位置代表著圖片中可能是線條的參數(shù)。

拓展閱讀
1、如果對于一個給定點(x_0,y_0) 我們在極坐標(biāo)對極徑極角平面繪出所有通過它的直線, 將得到一條正弦曲線. 例如, 對于給定點 x_0 = 8 和 y_0 = 6我們可以繪出下圖 (在平面 \theta - r):

只繪出滿足下列條件的點r > 0 和 0 < \theta < 2\pi.
2、我們可以對圖像中所有的點進行上述操作. 如果兩個不同點進行上述操作后得到的曲線在平面\theta - r相交, 這就意味著它們通過同一條直線. 例如, 接上面的例子我們繼續(xù)對點: x_1 = 9, y_1 = 4 和點x_2 = 12, y_2 = 3 繪圖, 得到下圖:

這三條曲線在\theta - r平面相交于點(0.925, 9.6), 坐標(biāo)表示的是參數(shù)對(\theta, r) 或者是說點(x_0,y_0), 點(x_1,y_1) 和點(x_2,y_2) 組成的平面內(nèi)的的直線.
3、一條直線能夠通過在平面\theta - r 尋找交于一點的曲線數(shù)量來檢測. 越多曲線交于一點也就意味著這個交點表示的直線由更多的點組成. 一般來說我們可以通過設(shè)置直線上點的 閾值 來定義多少條曲線交于一點我們才認(rèn)為檢測 到了一條直線.
4、這就是霍夫線變換要做的. 它追蹤圖像中每個點對應(yīng)曲線間的交點. 如果交于一點的曲線的數(shù)量超過了閾值, 那么可以認(rèn)為這個交點所代表的參數(shù)對(\theta, r_\theta) 在原圖像中為一條直線.
OpenCV中的哈夫變換
上面講述的是Opencv中的函數(shù),cv2.HoughLines()。它返回(ρ, θ)值的序列,ρ單位像素,θ單位弧度。第一個參數(shù),輸入的圖片是一個二進制圖片,在使用hough變換之前,應(yīng)用閾值或使用canny邊緣檢測。第二和第三個參數(shù)分別是ρ和θ的精度,第4個參數(shù)是閾值,指可以被認(rèn)為是一個線條的最小計數(shù)值。由于計數(shù)值的多少取決于線上的點數(shù),所以這代表了可以被識別為線的最小長度。
import cv2
import numpy as np
img = cv2.imread('dave.jpg')
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
edges = cv2.Canny(gray,50,150,apertureSize = 3)
lines = cv2.HoughLines(edges,1,np.pi/180,200)
for rho,theta in lines[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),2)
cv2.imwrite('houghlines3.jpg',img)</pre>
認(rèn)為第9行代碼存在一定錯誤,應(yīng)為:
for i in range(0,len(lines)):
rho,theta = lines[i][0][0],lines[i][0][1]
結(jié)果如下:

概率哈夫變換
在哈夫變換中,可以通過2個變量來檢測一條線上的點,但花費大量計算。概率哈夫變換是一個優(yōu)化的哈夫變換,它不會計算所有的點,而是隨機的選取一組足以識別直線的點,所以我們需要減少閾值??聪聢D中關(guān)于哈夫變換和概率哈夫變換在哈夫空間的比較:

OpenCV 技術(shù)是基于Matas, J. and Galambos, C. and Kittler, J.V.使用概率哈夫變換進行的線條魯棒檢測。它通過cv2.HoughLinesP()來實現(xiàn),函數(shù)有2個新的參數(shù)。
minLineLength - 線段的最小長度. Line segments shorter than this are rejected.
maxLineGap - 使程序識別線段為一條線的線段之間最大的空隙
最好是這樣,函數(shù)直接返回了線條的兩個終點,在前面的實例中,必須找到所有的點,而只獲取了線的參數(shù)。在這個實例中,所有的事都變得簡單直接。
import cv2
import numpy as np
img = cv2.imread('dave.jpg')
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
edges = cv2.Canny(gray,50,150,apertureSize = 3)
minLineLength = 100
maxLineGap = 10
lines = cv2.HoughLinesP(edges,1,np.pi/180,100,minLineLength,maxLineGap)
for x1,y1,x2,y2 in lines[0]:
cv2.line(img,(x1,y1),(x2,y2),(0,255,0),2)
cv2.imwrite('houghlines5.jpg',img)</pre>
結(jié)果如下:

-
曲線飽滿度,RHO 值越小,曲線就越平坦;RHO值越大,曲線就越飽滿,RHO<0.5,曲線為橢圓,RHO=0.5時,曲線為拋物線;RHO>0.5時,曲線為雙曲線! ?