計算機視覺 OpenCV Android | 基本特徵檢測之 霍夫直線檢測 詳析
-
在取得
影象邊緣
的基礎上,對一些
特定的幾何形狀邊緣
,如直線、圓
,通過影象霍夫變換
把影象從平面座標空間
變換到霍夫座標空間
,就可以通過求取
霍夫空間
的區域性極大值方法(其實就是霍夫空間中的曲線交集點)
,得到
極座標空間
對應引數方程
中直線的兩個引數(r,θ)
,從而計算得到
邊緣影象中
的所有直線
(基於平面座標
)的數目與位置
。
假設有一條直線如下圖:

(紅色部分是計算過程,遞等到右下角的結果,待會兒要用)
在 笛卡兒平面座標系
統中的斜率引數與截距引數為 (k,b)
;
若變換到 極座標空間
則變成求取 另外兩個引數(r,θ)
, r 和 θ
之間的關係可以表示為:
(公式的來源運算過程見上圖)

對於 每個平面空間的畫素點座標(x,y)
,
隨著 角度θ
的取值不同,都會 得到r值
,
(要點.b)而對於 任意一條直線
來說,在 極座標空間
它的 (r,θ)
都是 固定不變的
,
則對於 邊緣影象
的 每個平面空間座標點
可繪製 極座標的曲線
如圖所示:

- 截圖中,
左側是一個
平面空間的畫素點
,
基於公式r = x * cosθ + y * sinθ
,
通過給定不同的θ
值,得到唯一對應r
值,
無數個(r,θ)
數對構成的一道極座標曲線
;
右側是三個
平面空間的畫素點
,
基於公式r = x * cosθ + y * sinθ
,
通過給定不同的θ
值,得到唯一對應r
值,
無數個(r,θ)
數對構成的三道極座標曲線
; - 無論截圖的左側還是右側,都是所謂
霍夫空間的一部分
,所謂霍夫空間
,如下圖:
霍夫空間
,
就是一個基於(r,θ)兩個引數座標軸的資料空間,
數量級規模
是可以是一個邊緣影象
的畫素點數量
;
並且這個空間包括了這樣的一系列曲線 :
一個邊緣影象
的所有(all & each,假設為 N 個)畫素點(x,y)
,
基於公式r = x * cosθ + y * sinθ
,
通過給定不同的θ
值,得到唯一對應r
值,
無數個(r,θ)
數對構成的N 道 極座標曲線(霍夫空間的曲線)
;
由在平面空間同屬於一條直線的畫素點繪製出來的曲線必然會相交於一點(如圖5-4b所示的曲線),而這個點正是直線在極座標空間中的引數方程的引數,這樣就在極座標空間找到了直線的引數方程,反變換回到平面座標空間就可以求得直線的兩個引數(k,b),得到直線位置,而它們在極座標的交點就是直線在霍夫空間的表達,直線越長,其在霍夫空間這個點的累積值就越高,相對的灰度值也就越(亮)。OpenCV關於霍夫直線變換提供了兩個相關API函式,一個是在霍夫空間求取直線兩個極座標的引數,需要開發者自己轉換到平面座標空間計算直線,另外一個則會直接返回平面空間直線/線段的兩個點座標資訊。返回極座標引數的API函式及其引數的解釋如下:
-
HoughLines(Mat image, Mat lines, double rho, double theta, int threshold)
image
:表示輸入影象,8位單通道影象,一般為二值影象。lines
:表示輸出的每個直線的極座標引數方程的兩個引數。rho
:表示極座標空間r值每次的步長,一般設定為1。theta
:表示角度θ,每次移動1°即可。threshold
:表示極座標中該點的累積數,該累積數越大,則得到的直線可能就越長,取值範圍通常為30~50,單位是畫素,假設為30的話,則表示大於30個畫素長度的線段才會被檢測到。使用該API實現直線檢測的程式碼如下:
private void houghLinesDemo(Mat src, Mat dst) { Mat edges = new Mat(); Imgproc.Canny(src, edges, 50, 150, 3, true); Mat lines = new Mat(); Imgproc.HoughLines(edges, lines, 1,Math.PI/180.0, 200); Mat out = Mat.zeros(src.size(), src.type()); float[] data = new float[2]; for(int i=0; i<lines.rows(); i++) { lines.get(i, 0, data); float rho = data[0], theta = data[1]; double a = Math.cos(theta), b = Math.sin(theta); double x0 = a*rho, y0 = b*rho; Point pt1 = new Point(); Point pt2 = new Point(); pt1.x = Math.round(x0 + 1000*(-b)); pt1.y = Math.round(y0 + 1000*(a)); pt2.x = Math.round(x0 - 1000*(-b)); pt2.y = Math.round(y0 - 1000*(a)); Imgproc.line(out, pt1, pt2, new Scalar(0,0,255), 3, Imgproc.LINE_AA, 0); } out.copyTo(dst); out.release(); edges.release(); }
需要對得到的每個極座標引數方程做計算,使其變換到平面空間,然後繪製直線。
另外一個API函式則比較簡單,它省去了開發者自己把極座標變換為直線座標的過程,直接返回每個線段/直線對應的兩個點座標,其API函式與引數的解釋具體如下:
-
HoughLinesP(Mat image, Mat lines, double rho, double theta, int threshold, double minLineLength, double maxLineGap)
image
:表示輸入影象,8位單通道影象,一般為二值影象。lines
:表示輸出的每個直線的極座標引數方程的兩個引數。rho
:表示極座標空間r值每次的步長,一般設定為1。theta
:表示角度θ,每次移動1°即可。threshold
:表示極座標中該點的累積數,該累積數越大,則得到的直線可能就越長,取值範圍通常為30~50,單位是畫素,假設取值為30,則表示大於30個畫素長度的線段才會被檢測到。minLineLength
:表示可以檢測的最小線段長度,根據實際需要進行設定。maxLineGap
:表示線段之間的最大間隔畫素,假設5表示小於5個畫素的兩個相鄰線段可以連線起來。使用該API實現影象直線檢測的程式碼演示如下:
private void houghLinePDemo(Mat src, Mat dst) { Mat edges = new Mat(); Imgproc.Canny(src, edges, 50, 150, 3, true); Mat lines = new Mat(); Imgproc.HoughLinesP(edges, lines, 1, Math.PI/180.0, 100, 50, 10); Mat out = Mat.zeros(src.size(), src.type()); for(int i=0; i<lines.rows(); i++) { int[] oneline = new int[4]; lines.get(i, 0, oneline); Imgproc.line(out, new Point(oneline[0], oneline[1]), new Point(oneline[2], oneline[3]), new Scalar(0, 0, 255), 2, 8, 0); } out.copyTo(dst); // 釋放記憶體 out.release(); edges.release(); }
這裡需要注意的是,影象二值化與邊緣檢測演算法輸出結果的質量在很大程度上影響霍夫直線變換的結果,同時在使用HoughLinesP的時候,最後兩個引數的設定也會影響霍夫直線檢測的結果。