什么是霍夫變換
霍夫變換是一種在圖像中尋找直線瘦赫,圓形以及其他簡單形狀的方法。霍夫變換采用類似于投票的方式來獲取當(dāng)前圖像內(nèi)的形狀集合刃永,該變換由Paul Hough(霍夫)于1962年首次提出。
最初的霍夫變換只能用于檢測直線羊精,經(jīng)過發(fā)展后斯够,霍夫變換不僅能夠識別直線,還能識別其他簡單的圖形結(jié)構(gòu)喧锦,常見的有圓形读规,橢圓等。
HoughLines函數(shù)
在OpenCV中燃少,它給我們提供了cv2.HoughLines()函數(shù)來實現(xiàn)霍夫直線變換束亏,該函數(shù)要求所有操作的原圖是一個二值圖像,所以在進行霍夫變換之前供汛,需要將圖像進行二值化處理枪汪。或者進行Canny邊緣檢測怔昨。
其完整定義如下:
def HoughLines(image, rho, theta, threshold, lines=None, srn=None, stn=None, min_theta=None, max_theta=None):
image:原始圖形雀久,必須是8位單通道的二值圖像
rho:以像素為單位的距離r的精度。一般情況下趁舀,使用的精度是1
theta:為角度θ的精度赖捌。一般情況下,使用的精度是Π/180矮烹,表示要搜索所有可能的角度
threshold:閾值越庇。該值越小,判定出直線就越多奉狈。識別直線時卤唉,要判定多少個點位于該直線上。在判定直線是否存在時仁期,對直線所穿過的點的數(shù)量進行評估桑驱,如果直線所穿過的點的數(shù)量小于閾值,則認(rèn)為這些點恰好在算法構(gòu)成直線跛蛋,但是在原始圖像中該直線并不存在熬的;如果大于閾值,則認(rèn)為直線存在赊级。所以押框,如果閾值越小,就會得到較多的直線理逊;閾值越大橡伞,就會得到較少的直線
lines:返回值盒揉,它的每個元素都是一對浮點數(shù),表示檢測到的直線的參數(shù)兑徘,即(r,θ)预烙。是numpy.ndarray類型。
HoughLines實戰(zhàn)
了解了函數(shù)常用的參數(shù)之后道媚。下面扁掸,我們通過一個棋盤來進行霍夫變換。代碼如下:
import cv2
import numpy as np
import matplotlib.pyplot as plt
img = cv2.imread("35.jpg")
plt.subplot(121)
plt.imshow(img, cmap="gray")
plt.axis('off')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
edges = cv2.Canny(gray, 50, 150, apertureSize=3)
lines = cv2.HoughLines(edges, 1, np.pi / 180, 140)
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, 255, 0), 2)
plt.subplot(122)
plt.imshow(img, cmap="gray")
plt.axis('off')
plt.show()
運行之后最域,效果如下:
HoughLinesP實戰(zhàn)
使用HoughLines雖然可以完成霍夫變換谴分,但其本身存在非常嚴(yán)重的誤檢測。為了解決這個問題镀脂,OpenCV加入了概率霍夫變換函數(shù)cv2.HoughLinesP()函數(shù)牺蹄。
其完整定義如下:
def HoughLinesP(image, rho, theta, threshold, lines=None, minLineLength=None, maxLineGap=None):
image:原始圖像,比如為8位單通道二值圖像
rho:同上
theta:同上
threshold:同上
lines:同上
minLineLength:用來控制”接受直線的最小長度“的值薄翅。默認(rèn)值為0
maxLineGap:用來控制接受共線線段之間的最小間隔沙兰,即在一條直線中兩點的最大間隔。如果兩點間的間隔超過了參數(shù)maxLineGap的值翘魄,就認(rèn)為這兩點不在一條直線上鼎天。默認(rèn)值為0
將上面的例子,使用HoughLinesP改進以下:
import cv2
import numpy as np
import matplotlib.pyplot as plt
img = cv2.imread("35.jpg")
plt.subplot(121)
plt.imshow(img, cmap="gray")
plt.axis('off')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
edges = cv2.Canny(gray, 50, 150, apertureSize=3)
lines = cv2.HoughLinesP(edges, 1, np.pi / 180, 10, 10)
for line in lines:
x1, y1, x2, y2 = line[0]
cv2.line(img, (x1, y1), (x2, y2), (255, 0, 0), 5)
plt.subplot(122)
plt.imshow(img, cmap="gray")
plt.axis('off')
plt.show()
運行之后暑竟,效果如下:
可以看到斋射,這里我們通過函數(shù)HoughLinesP,將棋盤線完整的全標(biāo)記出來了但荤。
HoughCircles實戰(zhàn)
霍夫變換出來用來檢測直線外罗岖,我們還可以用來檢測其他的幾何對象。實際上腹躁,只要是能用一個方程式表示的對象桑包,都適合用霍夫變換來檢測。
其中纺非,我們就可以使用霍夫圓變換來檢測圖像中的圓哑了。這里我們只需要考慮圓心坐標(biāo)(x,y)與半徑r共3個參數(shù)铐炫。
在OpenCV中要經(jīng)過2個步驟:
- 找出可能存在圓的位置(圓心)
- 根據(jù)1計算半徑
在OpenCV中垒手,它給我們提供的霍夫圓變換函數(shù)為cv2.HoughCircle()蒜焊。該函數(shù)也是將Canny邊緣檢測與霍夫變換結(jié)合倒信,唯一的區(qū)別是,不要我們進行Canny邊緣檢測泳梆,該函數(shù)自動先進行Canny邊緣檢測鳖悠。
其完整定義如下:
def HoughCircles(image, method, dp, minDist, circles=None, param1=None, param2=None, minRadius=None, maxRadius=None):
image:原始圖像榜掌,8位單通道灰度圖像
method:檢測方法,HOUGH_GRADIENT是唯一可用的參數(shù)值乘综。該參數(shù)代表霍夫圓檢測中兩輪檢測所使用的方法
dp:累計器分辨率憎账,它是一個分割比例,用來指定圖像分辨率與圓心累加器分辨率的比例卡辰。例如胞皱,如果dp=1,則輸入圖像和累加器具有相同的分辨率
minDist:圓心間最小間距九妈。該值被作為閾值來使用反砌,如果存在圓心間距小于該值的多個圓,則僅有一個會被檢測出來萌朱。因此宴树,如果該值太小,則會有很多臨近的圓被檢測出來晶疼;如果該值很大酒贬,則可能會在檢測時漏掉很多圓
circles:返回值,有圓心坐標(biāo)和半徑構(gòu)成的numpy.ndarray類型翠霍。
param1:該參數(shù)缺省锭吨,默認(rèn)100。它對應(yīng)的是Canny邊緣檢測器的高閾值(低閾值是高閾值的二分之一)
param2:圓心位置必須受到的投票數(shù)寒匙。只有在第1論篩選的過程中耐齐,投票數(shù)超過該值的圓,才有資格進入第2輪的篩選蒋情。因此埠况,該值越大,檢測到的圓越少棵癣;該值越小辕翰,檢測到的圓越多。也是缺省值狈谊,默認(rèn)100
minRadius:圓半徑最小值喜命,小于該值的圓不會被檢測出來。也是缺省值河劝,默認(rèn)0壁榕,不起作用
maxRadius:圓半徑的最大值,大于該值的圓不會被檢測出來赎瞎。也是缺省值牌里,默認(rèn)0,不起作用
下面,我們來用一個奧運五環(huán)的照片牡辽,進行霍夫圓變換喳篇。代碼如下:
import cv2
import numpy as np
import matplotlib.pyplot as plt
img = cv2.imread("35_1.jpg")
plt.subplot(121)
plt.imshow(img, cmap="gray")
plt.axis('off')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
circles = cv2.HoughCircles(gray, cv2.HOUGH_GRADIENT, 1, 300, param1=50, param2=20,minRadius=0,maxRadius=0)
circles = np.uint16(np.around(circles))
for i in circles[0, :]:
cv2.circle(img, (i[0], i[1]), i[2], (255, 0, 0), 12)
cv2.circle(img, (i[0], i[1]), 2, (255, 0, 0), 12)
plt.subplot(122)
plt.imshow(img, cmap="gray")
plt.axis('off')
plt.show()
運行之后,效果如下: