第16章:霍夫变换
第16章:霍夫變換
- 一、霍夫直線變換:
- 1. 霍夫直線變換原理:
- 2. HoughLines函數:
- 3. HoughLinesP函數:
- 2. 霍夫圓環變換:
- 霍夫變換是一種在圖像中尋找直線、圓形以及其他簡單形狀的方法。
- 霍夫變換采用類似于投票的方式來獲取當前圖像內的形狀集合,該變換由Paul Hough(霍夫)于1962年首次提出。
最初的霍夫變換只能用于檢測直線,經過發展后,霍夫變換不僅能夠識別直線,還能識別其他簡單的圖形結構,常見的有圓、橢圓等。實際上,只要是能夠用一個參數方程表示的對象,都適合用霍夫變換來檢測。
下面主要介紹霍夫直線變換和霍夫圓變換。
- 霍夫直線變換用來在圖像內尋找直線。在 OpenCV 中,可以用函數 cv2.HoughLines()和函數cv2.HoughLinesP()實現。
- 霍夫圓變換用來在圖像內尋找圓。以用函數cv2.HoughCircles()實現。
一、霍夫直線變換:
OpenCV 提供了函數 cv2.HoughLines()和函數 cv2.HoughLinesP()用來實現霍夫直線變換。下面首先介紹霍夫變換的基本原理,然后分別介紹這兩個函數的基本使用方法。
1. 霍夫直線變換原理:
? 為了方便說明問題,先以我們熟悉的笛卡兒坐標系(即平面直角坐標系,與笛卡兒空間對應)為例來說明霍夫變換的基本原理。與笛卡兒坐標系對應,我們構造一個霍夫坐標系(對應于霍夫空間)。在霍夫坐標系中,橫坐標采用笛卡兒坐標系中直線的斜率k,縱坐標使用笛卡兒坐標系中直線的截距b。
? 首先,我們觀察笛卡兒空間中的一條直線在霍夫空間內的映射情況。例如下圖中,左圖是笛卡兒x-y坐標系(笛卡兒空間),右圖是霍夫k-b坐標系(霍夫空間)。在笛卡兒空間中,存在著一條直線y=k0x+b0,該直線的截距k0是已知的常量,截距b0也是已知的常量。將該直線映射到霍夫空間內,找到已知的點(k0,b0),即完成映射。
? 從上述分析中可知,笛卡兒空間內的一條直線,其斜率為k,截距為b,映射到霍夫空間內成為一個點(k,b)?;蛘?#xff0c;可以這樣理解,霍夫空間內的一個點(k0,b0),映射到笛卡兒空間,就是一條直線y=k0x+b0。
? 這里,我們用“映射”這個詞表達不同的空間(坐標系)之間的對應關系,也可以表述為“確定”。例如,上述關系可以表述為:
- 笛卡兒空間內的一條直線確定了霍夫空間內的一個點。
- 霍夫空間內的一個點確定了笛卡兒空間內的一條直線。
? 接下來,觀察笛卡兒空間中的一個點在霍夫空間內的映射情況。如下圖所示,在笛卡兒空間內存在一個點(x0,y0),通過該點的直線可以表示為y0=kx0+b。其中,(x0,y0)是已知的常量,(k,b)是變量。
? 對于表達式y0=kx0+b,通過算術運算的左右移項,可以表示為b=?x0k+y0。將點(x0,y0)映射到霍夫空間時,可以認為對應的直線斜率為?x0,截距為y0,即b=?x0k+y0,如下圖中右圖的直線所示。
從上述分析可知:
- 笛卡兒空間內的點(x0,y0)映射到霍夫空間,就是直線b=?x0k+y0。
- 霍夫空間內的直線b=?x0k+y0映射到笛卡兒空間,就是點(x0,y0)。
下面我們看看笛卡兒空間中的兩個點映射到霍夫空間的情況。例如,在下圖中,左圖的笛卡兒空間中存在著兩個點(x0,y0)、(x1,y1),分析這兩個點映射到霍夫空間的情況。
為了方便理解,我們從不同的角度分析笛卡兒空間中這兩個點到霍夫空間的映射情況。
-
角度1:笛卡兒空間的一個點會映射為霍夫空間的一條線。
在笛卡兒空間內,存在著任意兩個點(x0,y0)、(x1,y1)。在霍夫空間中,這兩個點對應著兩條不同的直線。當然,通過分析可知,一條直線是b=?x0k+y0,另外一條直線是b=?x1k+y1。
-
角度2:笛卡兒空間的一條線會映射為霍夫空間的一個點
在笛卡兒空間內,存在著任意兩個點(x0,y0)、(x1,y1)。這兩個點一定能夠用一條直線連接,將連接它們的直線標記為y=k1x+b1,則該直線的截距和斜率是(k1,b1)。也就是說,該直線在霍夫空間內映射為點(k1,b1)。
從上述分析可知:
- 笛卡兒空間內的兩個點會映射為霍夫空間內兩條相交于(k1,b1)的直線。
- 這兩個點對應的直線會映射為霍夫空間內的點(k1,b1)。
換句話說,角度1決定了線條的數量,角度2決定了兩條線相交的點。
這說明,如果在笛卡兒空間內有兩個點A、B,它們能夠連成一條直線y=k1x+b1,那么在霍夫空間中的點(k1,b1)上會有兩條直線,分別對應著笛卡兒空間內的兩個點A、B。
下面我們看看笛卡兒空間中的三個點映射到霍夫空間的情況。在下圖中,左圖是笛卡兒空間,其中存在(0,1)、(1,2)、(2,3)三個點。
下面從不同的角度分析笛卡兒空間中這三個點映射到霍夫空間的情況。
-
角度1:笛卡兒空間內的一個點會映射為霍夫空間的一條線。
例如,笛卡兒空間中的(0,1)、(1,2)、(2,3)三個點映射到霍夫空間時,每個點對應著一條直線,對應關系如表所示。
根據對應關系可知:
- 笛卡兒空間內的點(0,1),對應著霍夫空間內的直線b=1。
- 笛卡兒空間內的點(1,2),對應著霍夫空間內的直線b=-k+2。
- 笛卡兒空間內的點(2,3),對應著霍夫空間內的直線b=-2k+3。
從上述分析可知,笛卡兒空間內的三個點映射為霍夫空間內的三條直線。
-
角度2:笛卡兒空間內的一條線會映射為霍夫空間的一個點。
例如,笛卡兒空間中的(0,1)、(1,2)、(2,3)三個點對應著直線y=x+1,斜率k為1,截距b為1。該直線y=x+1 映射到霍夫空間內的點(1,1)。
從上述角度1和角度2的分析可知:
- 笛卡兒空間中的(0,1)、(1,2)、(2,3)三個點會映射為霍夫空間內相交于點(1,1)的三條直線。
- 笛卡兒空間中的(0,1)、(1,2)、(2,3)三個點所連成(確定)的直線映射為霍夫空間內的點(1,1)
? 這說明,如果在笛卡兒空間內有三個點,并且它們能夠連成一條y=k1x+b1的直線,那么在霍夫空間中,對應的點(k1,b1)上會有三條直線,分別對應著笛卡兒空間內的三個點。
? 到此,我們已經發現,如果在笛卡兒空間內,有N個點能夠連成一條直線y=k1x+b1,那么在霍夫空間內就會有N條直線穿過對應的點(k1,b1)?;蛘叻催^來說,如果在霍夫空間中,有越多的直線穿過點(k1,b1),就說明在笛卡兒空間內有越多的點位于斜率為k1,截距為b1的直線y=k1x+b1上。
? 現在,我們看一個在笛卡兒空間內更多個點映射到霍夫空間的例子,也驗證一下上述觀點。在下圖中,左圖所示的是笛卡兒空間,其中有6個點,下面從不同的角度看下這6個點在右圖霍夫空間的映射情況。
- 角度1:笛卡兒空間的一點會映射為霍夫空間的一條線。
笛卡兒空間中的6個點:(0,1)、(1,2)、(2,3)、(3,4)、(3,2)、(1,4),映射到霍夫空間時,每個點對應著一條直線,對應關系如表所示
根據對應關系可知:
- 笛卡兒空間內的點(0,1),對應著霍夫空間內的直線b=1。
- 笛卡兒空間內的點(1,2),對應著霍夫空間內的直線b=-k+2。
- 笛卡兒空間內的點(2,3),對應著霍夫空間內的直線b=-2*k+3。
- 笛卡兒空間內的點(3,4),對應著霍夫空間內的直線b=-3*k+4。
- 笛卡兒空間內的點(3,2),對應著霍夫空間內的直線b=-3*k+2。
- 笛卡兒空間內的點(1,4),對應著霍夫空間內的直線b=-1*k+4。
從上述分析可知,笛卡兒空間內的6個點映射為霍夫空間內的6條直線
-
角度2:笛卡兒空間的一條線會映射為霍夫空間的一個點。
這里為了觀察方便,將笛卡兒空間內連接了較多點的線繪制出來:連接點(0,1)、(1,2)、(2,3)、(3,4)的線LineA,連接點(2,3)、(3,2)、(1,4)的線LineB,連接點(0,1)、(3,2)的線LineC
需要注意,在笛卡兒空間內,各個點之間存在多條直線。例如在點(1,2)、(3,2)之間,點(3,2)、(3,4)之間,點(1,4)、(3,4)之間都存在著直線,這里做了簡化,沒有將上述直線都繪制出來。
下面分析笛卡兒空間內的三條直線LineA、LineB、LineC在霍夫空間內的映射情況。
- 直線LineA經過了4個點,表達式為y=1 * x+1,斜率k為1,截距b為1,在霍夫空間內對應于點A(1,1)。
- 直線LineB經過了3個點,表達式為y=-1 * x+5,斜率k為-1,截距b為5,在霍夫空間內對應于點B(-1,5)。
- 直線LineC經過了2個點,表達式為y=-1/3 * x+1,斜率k為-1/3,截距b為1,在霍夫空間內對應于點C(-1/3,1)。
在上圖中可以看到,右圖所示的霍夫空間內點A有4條直線穿過,點B有3條直線穿過,點C有2條直線穿過。分析上述關系:
- 霍夫空間內有4條直線穿過點A。點A確定了笛卡兒空間內的一條直線,同時該直線穿過4個點,即霍夫空間內的點A確定了笛卡兒空間內的LineA,該直線上包含(0,1)、(1,2)、(2,3)、(3,4)共4個點。
- 霍夫空間內有3條直線穿過點B。點B確定了笛卡兒空間內的一條直線,同時該直線穿過3個點,即霍夫空間內的點B確定了笛卡兒空間內的LineB,該直線上包含(2,3)、(3,2)、(1,4)共3個點。
- 霍夫空間內有2條直線穿過點C。點C確定了笛卡兒空間內的一條直線,同時該直線穿過2個點,即霍夫空間內的點C確定了笛卡兒空間內的LineC,該直線上包含(0,1)、(2,3)共2個點。
? 綜上所述,在霍夫空間內,經過一個點的直線越多,說明其在笛卡兒空間內映射的直線,是由越多的點所構成(穿過)的。我們知道,兩個點就能構成一條直線。但是,如果有一個點是因為計算錯誤而產生的,那么它和另外一個點,也會構成一條直線,此時就會憑空構造出一條實際上并不存在的直線。這種情況是要極力避免的。
? 因此,在計算中,我們希望用更多的點構造一條直線,以提高直線的可靠性。也就是說,如果一條直線是由越多點所構成的,那么它實際存在的可能性就越大,它的可靠性也就越高。
? 因此,霍夫變換選擇直線的基本思路是:選擇有盡可能多直線交匯的點。
上面都是以我們熟悉的笛卡兒空間為例說明的。在笛卡兒空間中,可能存在諸如x=x0的垂線LineA的形式
此時,斜率k為無窮大,截距b無法取值。因此,上圖中的垂線LineA無法映射到霍夫空間內。為了解決上述問題,可以考慮將笛卡兒坐標系映射到極坐標系上,如圖所示。
在笛卡兒坐標系內使用的是斜率k和截距b,即用(k,b)表示一條直線。在極坐標系內,采用極徑r(有時也用ρ表示)和極角θ來表示,即(r,θ)來表示。極坐標系中的直線可以表示為:
- r=xcosθ+ysinθ
? 例如,上圖中的直線LineA,可以使用極坐標的極徑r和極角θ來表示。其中,r是直線LineA與圖像原點O之間的距離,參數θ是直線LineA的垂線LineB與x軸的角度。在這種表示方法中,圖像中的直線有一個(0~π)的角θ,而r的最大值是圖像對角線的長度。用這種表示方法,可以很方便地表示上圖中的3個點所構成的直線。
與笛卡兒空間和霍夫空間的映射關系類似:
- 極坐標系內的一個點映射為霍夫坐標系(霍夫空間)內的一條線(曲線)。
- 極坐標系內的一條線映射為霍夫坐標系內的一個點。
? 一般來說,在極坐標系內的一條直線能夠通過在霍夫坐標系內相交于一點的線的數量來評估。在霍夫坐標系內,經過一個點的線越多,說明其映射在極坐標系內的直線,是由越多的點所構成(穿過)的。因此,霍夫變換選擇直線的基本思路是:選擇由盡可能多條線匯成的點。
? 通常情況下,設置一個閾值,當霍夫坐標系內交于某點的曲線達到了閾值,就認為在對應的極坐標系內存在(檢測到)一條直線。
? 上述內容是霍夫變換的原理,即使完全不理解上述原理,也不影響我們使用OpenCV提供的霍夫變換函數來進行霍夫變換。OpenCV本身是一個黑盒子,它給我們提供了接口(參數、返回值),我們只需要掌握接口的正確使用方法,就可以正確地處理圖像問題,無須掌握其內部工作原理。
? 在某種情況下,OpenCV庫和Photoshop等圖像處理軟件是類似的,只要掌握了它們的使用方法,就能夠得到正確的處理結果。在進行圖像處理時,并不需要我們關注其實現原理等技術細節。但是,如果我們進一步了解其工作原理,對我們的工作也是有大有裨益的。
2. HoughLines函數:
OpenCV 提供了函數 cv2.HoughLines()用來實現霍夫直線變換,該函數要求所操作的源圖像是一個二值圖像,所以在進行霍夫變換之前要先將源圖像進行二值化,或者進行 Canny 邊緣檢測。
函數cv2.HoughLines()的語法格式為:
- lines=cv2.HoughLines(image,rho,theta,threshold)
- image:是輸入圖像,即源圖像,必須是8位的單通道二值圖像。如果是其他類型的圖像,在進行霍夫變換之前,需要將其修改為指定格式。
- rho:為以像素為單位的距離r的精度。一般情況下,使用的精度是1。
- theta:為角度θ的精度。一般情況下,使用的精度是π/180,表示要搜索所有可能的角度。
- threshold:是閾值。該值越小,判定出的直線就越多。通過上一節的分析可知,識別直線時,要判定有多少個點位于該直線上。在判定直線是否存在時,對直線所穿過的點的數量進行評估,如果直線所穿過的點的數量小于閾值,則認為這些點恰好(偶然)在算法上構成直線,但是在源圖像中該直線并不存在;如果大于閾值,則認為直線存在。所以,如果閾值較小,就會得到較多的直線;閾值較大,就會得到較少的直線。
- lines:返回值 ,每個元素都是一對浮點數,表示檢測到的直線的參數,即(r,θ),是numpy.ndarray類型。
注意:
? 使用函數 cv2.HoughLines()檢測到的是圖像中的直線而不是線段,因此檢測到的直線是沒有端點的。所以,我們在進行霍夫直線變換時所繪制的直線都是穿過整幅圖像的。
? 繪制直線的方法是,對于垂直方向的直線(不是指垂線,是指垂直方向上的各種角度的直線),計算它與圖像水平邊界(即圖像中的第一行和最后一行)的交叉點,然后在這兩個交叉點之間畫線。對于水平方向上的直線,采用類似的方式完成,只不過用到的是圖像的第一列和最后一列。
? 在繪制線時,所使用的函數是cv2.line()。該函數方便的地方在于,即使點的坐標超出了圖像的范圍,它也能正確地畫出線來,因此沒有必要檢查交叉點是否位于圖像內部。遍歷函數cv2.HoughLines()的返回值lines
import cv2 import numpy as np import matplotlib.pyplot as pltimg = cv2.imread('./buliding.jpg') gray_img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) edges = cv2.Canny(gray_img, 50, 150, apertureSize=3)rgb_img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) new_img = rgb_img.copy() print(lines) for line in lines:rho, theta = line[0]a = np.cos(theta)b = np.sin(theta)x0 = a * rhoy0 = b * rhox1 = int(x0 + 1000 * (-b))y1 = int(y0 + 1000 * (a))x2 = int(x0 - 1000 * (-b))y2 = int(y0 - 1000 * (a))cv2.line(new_img, (x1, y1), (x2, y2), (0, 0, 255), 2)plt.subplot(131) plt.imshow(rgb_img) plt.axis('off')plt.subplot(132) plt.imshow(edges) plt.axis('off')plt.subplot(133) plt.imshow(new_img) plt.axis('off')plt.show()補充:OpenCV中HoughLines兩個點(x1, y1),(x2, y2)是如何求出的。
上圖顯示怎么放大線段到1000即當r=1000時的計算方法。可以是300也可是500、800。
3. HoughLinesP函數:
概率霍夫變換對基本霍夫變換算法進行了一些修正,是霍夫變換算法的優化。它沒有考慮所有的點。相反,它只需要一個足以進行線檢測的隨機點子集即可。
為了更好地判斷直線(線段),概率霍夫變換算法還對選取直線的方法作了兩點改進:
- **所接受直線的最小長度。**如果有超過閾值個數的像素點構成了一條直線,但是這條直線很短,那么就不會接受該直線作為判斷結果,而認為這條直線僅僅是圖像中的若干個像素點恰好隨機構成了一種算法上的直線關系而已,實際上原圖中并不存在這條直線。
- **接受直線時允許的最大像素點間距。**如果有超過閾值個數的像素點構成了一條直線,但是這組像素點之間的距離都很遠,就不會接受該直線作為判斷結果,而認為這條直線僅僅是圖像中的若干個像素點恰好隨機構成了一種算法上的直線關系而已,實際上原始圖像中并不存在這條直線。
在OpenCV中通過cv2.HoughLinesP()函數實現概率霍夫變換:
- lines = cv2.HoughLiesP(image, rho, theta, threshold, minLineLength, maxLineGap)
- image:是輸入圖像,即原圖像,必須為8位的單通道二值圖像。對于其他類型的圖像,在進行霍夫變換之前,需要將其修改為這個指定的格式。
- rho:為以像素為單位的距離r的精度。一般情況下,使用的精度是1。
- theta:是角度θ的精度。一般情況下,使用的精度是np.pi/180,表示要搜索可能的角度。
- threshold:是閾值。該值越小,判定出的直線越多;值越大,判定出的直線就越少。
- minLineLength:用來控制“接受直線的最小長度”的值,默認值為0。
- maxLineGap:用來控制接受共線線段之間的最小間隔,即在一條線中兩點的最大間隔。
如果兩點間的間隔超過了參數maxLineGap的值,就認為這兩點不在一條線上。默認值為0。 - lines:返回值。是由numpy.ndarray類型的元素構成的,其中每個元素都是一對浮點數,表示檢測到的直線的參數,即(x1, y1)、(x2, y2)。
2. 霍夫圓環變換:
? 霍夫變換除了用來檢測直線外,也能用來檢測其他幾何對象。實際上,只要是能夠用一個參數方程表示的對象,都適合用霍夫變換來檢測。
? 用霍夫圓變換來檢測圖像中的圓,與使用霍夫直線變換檢測直線的原理類似。在霍夫圓變換中,需要考慮圓半徑和圓心(x坐標、y坐標)共3個參數。在OpenCV中,采用的策略是兩輪篩選。第1輪篩選找出可能存在圓的位置(圓心);第2輪再根據第1輪的結果篩選出半徑大小。
? 與用來決定是否接受直線的兩個參數“接受直線的最小長度(minLineLength)”和“接受直線時允許的最大像素點間距(MaxLineGap)”類似,霍夫圓變換也有幾個用于決定是否接受圓的參數:圓心間的最小距離、圓的最小半徑、圓的最大半徑。
在OpenCV中,通過函數cv2.HoughCircles()實現霍夫圓變換,**該函數將Canny邊緣檢測和霍夫變換結合。**其語法格式為:
- circles = cv2.HoughCircles(image, method, dp, minDist, param1, param2, minRadius, maxradius)
- image:輸入圖像,即源圖像,類型為8位的單通道灰度圖像。
- method:檢測方法。截止到OpenCV 4.0.0-pre版本,HOUGH_GRADIENT是唯一可用的參數值。該參數代表的是霍夫圓檢測中兩輪檢測所使用的方法。
- dp:累計器分辨率,它是一個分割比率,用來指定圖像分辨率與圓心累加器分辨率的比例。例如,如果dp=1,則輸入圖像和累加器具有相同的分辨率。
- minDist:圓心間的最小間距。該值被作為閾值使用,如果存在圓心間距離小于該值的多個圓,則僅有一個會被檢測出來。因此,如果該值太小,則會有多個臨近的圓被檢測出來;如果該值太大,則可能會在檢測時漏掉一些圓。
- param1:該參數是缺省的,在缺省時默認值為100。它對應的是Canny邊緣檢測器的高閾值(低閾值是高閾值的二分之一)。
- param2:圓心位置必須收到的投票數。只有在第1輪篩選過程中,投票數超過該值的圓,才有資格進入第2輪的篩選。因此,該值越大,檢測到的圓越少;該值越小,檢測到的圓越多。這個參數是缺省的,在缺省時具有默認值100。
- minRadius:圓半徑的最小值,小于該值的圓不會被檢測出來。該參數是缺省的,在缺省時具有默認值0,此時該參數不起作用。
- maxRadius:圓半徑的最大值,大于該值的圓不會被檢測出來。該參數是缺省的,在缺省時具有默認值0,此時該參數不起作用。
- circles:返回值,由圓心坐標和半徑構成的numpy.ndarray。
需要特別注意,在調用函數 cv2.HoughLinesCircles()之前,要對源圖像進行平滑操作,以減少圖像中的噪聲,避免發生誤判。
import cv2 import numpy as np import matplotlib.pyplot as pltimg = cv2.imread('xiangqi.jpg') gray_img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) rgb_img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) new_img = rgb_img.copy()circles = cv2.HoughCircles(gray_img, cv2.HOUGH_GRADIENT, 1, 100, param1=100, param2=30, minRadius=50, maxRadius=100) print(circles) circles = np.uint16(np.around(circles)) print(circles) for i in circles[0, :]:cv2.circle(new_img, (i[0], i[1]), i[2], (255, 0, 0), 10)# 圓心cv2.circle(new_img, (i[0], i[1]), 2, (255, 0, 0), 10)plt.subplot(121) plt.imshow(rgb_img) plt.title('img') plt.axis('off')plt.subplot(122) plt.imshow(new_img) plt.title('rst') plt.axis('off') plt.show()總結
- 上一篇: 【预测模型】基于天牛须算法优化ELman
- 下一篇: 【好程序员笔记分享】C语言之break和