1. 程式人生 > >Python下opencv使用筆記之hough變換

Python下opencv使用筆記之hough變換

在數字影象中,往往存在著一些特殊形狀的幾何圖形,像檢測馬路邊一條直線,檢測人眼的圓形等等,有時我們需要把這些特定圖形檢測出來,hough變換就是這樣一種檢測的工具。

Hough變換的原理是將特定圖形上的點變換到一組引數空間上,根據引數空間點的累計結果找到一個極大值對應的解,那麼這個解就對應著要尋找的幾何形狀的引數(比如說直線,那麼就會得到直線的斜率k與常熟b,圓就會得到圓心與半徑等等)。

關於hough變換,核心以及難點就是關於就是有原始空間到引數空間的變換上。以直線檢測為例,假設有一條直線L,原點到該直線的垂直距離為p,垂線與x軸夾角為

θθ在空間座標上都是對應一條直線的。好了,同時經過的這一個點有什麼含義呢?它表示在空間座標系下,有一條直線可以經過點1,經過點2,經過點3,這是什麼意思?說明這三個點在一條直線上吧。反過來再來看這個極座標系下的曲線,那麼我們只需要找到交點最多的點,把它返回到空間域就是這個需要找的直線了。為什麼是找相交最多的點,因為上面這只是三個點的曲線,當空間上很多點都畫出來的時候,那麼相交的點可能就不知上述看到的一個點了,可能有多個曲線相交點,但是有一點,勢必是一條直線上的所有點匯成的交點是曲線相交次數最多的。

再來分析這個演算法。可以看到hough變換就是引數對映變換。對每一個點都進行對映,並且每一個對映還不止一次,

(ρ,θ)(ρ,θ)引數(此時角度的步長是10度了,10度,已經非常大的一個概念了),那麼總共需要對映360000次,在考慮每次對映計算的時間吧。可想而知,hough是多麼耗時耗力。所以必須對其進行改進。首先就是對影象進行改進,100*100的影象,10000個點,是不是每個點都要計算?大可不必,我們只需要在開始把影象進行一個輪廓提取,一般使用canny運算元就可以,生成黑白二值影象,白的是輪廓,那麼在對映的時候,只需要把輪廓上的點進行引數空間變換,為什麼提輪廓?想想無論檢測影象中存在的直線呀圓呀,它們都是輪廓鮮明的。那麼需要變換的點可能就從10000個點降到可能1000個點了,這也就是為什麼看到許多hough變換提取形狀時為什麼要把影象提取輪廓,變成二值影象了。

繼續演算法,分析這麼多,可想而知那麼一個hough變換在演算法設計上就可以如下步驟:
(1)將引數空間(ρ,θ)(ρ,θ)和點(x,y)計算出來這個直線就ok了。

說了這麼多,這就是原理上hough變換的最底層原理,事實上完全可以自己寫程式去實現這些,然而,也說過,hough變換是一個耗時耗力的演算法,自己寫迴圈實現通常很慢,曾經用matlab寫過這個,也有實際的hough變換例子可以看看:

虹膜識別(三):Hough變換檢測內圓邊緣

那麼我們在實際中大可不必自己寫,opencv已經集成了hough變換的函式,呼叫它的函式效率高,也很簡單。
Opencv中檢測直線的函式有cv2.HoughLines(),cv2.HoughLinesP()
函式cv2.HoughLines()返回值有三個(opencv 3.0),實際是個二維矩陣,表述的就是上述的(ρ,θ)(ρ,θ)的精確度,可以理解為步長。第四個引數為閾值T,認為當累加器中的值高於T是才認為是一條直線。自己畫了個圖實驗一下:

import cv2
import numpy as np
import matplotlib.pyplot as plt

img = cv2.imread('line.jpg') 
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)#灰度影象 
#open to see how to use: cv2.Canny
#http://blog.csdn.net/on2way/article/details/46851451 
edges = cv2.Canny(gray,50,200)
plt.subplot(121),plt.imshow(edges,'gray')
plt.xticks([]),plt.yticks([])
#hough transform
lines = cv2.HoughLines(edges,1,np.pi/180,160)
lines1 = lines[:,0,:]#提取為為二維
for rho,theta in lines1[:]: 
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),(255,0,0),1)

plt.subplot(122),plt.imshow(img,)
plt.xticks([]),plt.yticks([])
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

這裡寫圖片描述

測試一個新的圖,不停的改變 cv2.HoughLines最後一個閾值引數到合理的時候如下:
這裡寫圖片描述
可以看到檢測的還可以的。

函式cv2.HoughLinesP()是一種概率直線檢測,我們知道,原理上講hough變換是一個耗時耗力的演算法,尤其是每一個點計算,即使經過了canny轉換了有的時候點的個數依然是龐大的,這個時候我們採取一種概率挑選機制,不是所有的點都計算,而是隨機的選取一些個點來計算,相當於降取樣了。這樣的話我們的閾值設定上也要降低一些。在引數輸入輸出上,輸入不過多了兩個引數:minLineLengh(線的最短長度,比這個短的都被忽略)和MaxLineCap(兩條直線之間的最大間隔,小於此值,認為是一條直線)。輸出上也變了,不再是直線引數的,這個函式輸出的直接就是直線點的座標位置,這樣可以省去一系列for迴圈中的由引數空間到影象的實際座標點的轉換。

import cv2
import numpy as np
import matplotlib.pyplot as plt

img = cv2.imread('room.jpg') 
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)#灰度影象 
#open to see how to use: cv2.Canny
#http://blog.csdn.net/on2way/article/details/46851451 
edges = cv2.Canny(gray,50,200)
plt.subplot(121),plt.imshow(edges,'gray')
plt.xticks([]),plt.yticks([])
#hough transform
lines = cv2.HoughLinesP(edges,1,np.pi/180,30,minLineLength=60,maxLineGap=10)
lines1 = lines[:,0,:]#提取為二維
for x1,y1,x2,y2 in lines1[:]: 
    cv2.line(img,(x1,y1),(x2,y2),(255,0,0),1)

plt.subplot(122),plt.imshow(img,)
plt.xticks([]),plt.yticks([])
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

這裡寫圖片描述
可以看到這個方法似乎更好些,速度還快,調引數得到較好的效果就ok了。

Ok說完了直線的檢測,再來說說圓環的檢測,我們知道圓的數學表示為:

(xxcenter)2+(yycenter)2=r2(x−xcenter)2+(y−ycenter)2=r2
從而一個圓的確定需要三個引數,那麼就需要三層迴圈來實現(比直線的多一層),從容把影象上的所有點對映到三維引數空間上。其他的就一樣了,尋找引數空間累加器的最大(或者大於某一閾值)的值。那麼理論上圓的檢測將比直線更耗時,然而opencv對其進行了優化,用了一種霍夫梯度法,感興趣去研究吧,我們只要知道它能優化演算法的效率就可以了。關於函式引數輸入輸出:
cv2.HoughCircles(image, method, dp, minDist, circles, param1, param2, minRadius, maxRadius)
這個時候輸入為灰度影象,同時最好規定檢測的圓的最大最小半徑,不能盲目的檢測,否側浪費時間空間。輸出就是三個引數空間矩陣。
來個實際點的人眼影象,把minRadius和maxRadius調到大圓範圍檢測如下:

import cv2
import numpy as np
import matplotlib.pyplot as plt

img = cv2.imread('eye.jpg')
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)#灰度影象 

plt.subplot(121),plt.imshow(gray,'gray')
plt.xticks([]),plt.yticks([])
#hough transform
circles1 = cv2.HoughCircles(gray,cv2.HOUGH_GRADIENT,1,
100,param1=100,param2=30,minRadius=200,maxRadius=300)
circles = circles1[0,:,:]#提取為二維
circles = np.uint16(np.around(circles))#四捨五入,取整
for i in circles[:]: 
    cv2.circle(img,(i[0],i[1]),i[2],(255,0,0),5)#畫圓
    cv2.circle(img,(i[0],i[1]),2,(255,0,255),10)#畫圓心

plt.subplot(122),plt.imshow(img)
plt.xticks([]),plt.yticks([])
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

這裡寫圖片描述

把半徑範圍調小點,檢測內圓:

這裡寫圖片描述
至此圓的檢測就是這樣。

在數字影象中,往往存在著一些特殊形狀的幾何圖形,像檢測馬路邊一條直線,檢測人眼的圓形等等,有時我們需要把這些特定圖形檢測出來,hough變換就是這樣一種檢測的工具。