在數(shù)字圖像中,往往存在著一些特殊形狀的幾何圖形,像檢測(cè)馬路邊一條直線,檢測(cè)人眼的圓形等等,有時(shí)我們需要把這些特定圖形檢測(cè)出來(lái),hough變換就是這樣一種檢測(cè)的工具。
Hough變換的原理是將特定圖形上的點(diǎn)變換到一組參數(shù)空間上,根據(jù)參數(shù)空間點(diǎn)的累計(jì)結(jié)果找到一個(gè)極大值對(duì)應(yīng)的解,那么這個(gè)解就對(duì)應(yīng)著要尋找的幾何形狀的參數(shù)(比如說(shuō)直線,那么就會(huì)得到直線的斜率k與常熟b,圓就會(huì)得到圓心與半徑等等)。
關(guān)于hough變換,核心以及難點(diǎn)就是關(guān)于就是有原始空間到參數(shù)空間的變換上。以直線檢測(cè)為例,假設(shè)有一條直線L,原點(diǎn)到該直線的垂直距離為p,垂線與x軸夾角為 θ ,那么這條直線是唯一的,且直線的方程為 ρ=xcosθ+ysinθ , 如下圖所示:
可以看到的是這條直線在極坐標(biāo)系下只有一個(gè) (ρ,θ) 與之對(duì)應(yīng),隨便改變其中一個(gè)參數(shù)的大小,變換到空間域上的這個(gè)直線將會(huì)改變。好了,再回來(lái)看看這個(gè)空間域上的這條直線上的所有點(diǎn)吧,你會(huì)發(fā)現(xiàn),這條直線上的所有點(diǎn)都可以是在極坐標(biāo)為 (ρ,θ) 所表示的直線上的,為什么說(shuō)是都可以在,因?yàn)槠渲须S便的一個(gè)點(diǎn)也可以在其他的 (ρ,θ) 所表示的直線上,就比如上述的(x,y)吧,它可以再很多直線上,準(zhǔn)確的說(shuō),在經(jīng)過(guò)這個(gè)點(diǎn)的直線上,隨便畫兩條如下:
可以看到,光是空間上的一個(gè)點(diǎn)在極坐標(biāo)系下就可能在很多極坐標(biāo)對(duì)所對(duì)應(yīng)的直線上,具體有多少個(gè)極坐標(biāo)對(duì)呢?那得看你的 θ 的步長(zhǎng)了,我們可以看到 θ 無(wú)非是從0-360度( 0−2π )變化,假設(shè)我們沒(méi)10度一走取一個(gè)直線(這個(gè)點(diǎn)在這個(gè)直線上),那么我們走一圈是不是取了36條直線,也就對(duì)應(yīng)36個(gè)極坐標(biāo)對(duì)沒(méi)錯(cuò)吧,那么這個(gè)極坐標(biāo)對(duì),畫在坐標(biāo)軸上是什么樣子的呢?因?yàn)?θ 是從 0−2π ,并且一個(gè)點(diǎn)定了,如果一個(gè) θ 也定了,你想想它對(duì)應(yīng)的直線的 ρ 會(huì)怎么樣,自然也是唯一的。那么這個(gè)點(diǎn)在極坐標(biāo)下對(duì)應(yīng)的 (ρ,θ) 畫出來(lái)一個(gè)周期可能就是這樣的,以 θ 為x軸的話:
ok前面說(shuō)的是單單這一個(gè)點(diǎn)對(duì)應(yīng)的極坐標(biāo)系下的參數(shù)對(duì),那么如果每個(gè)點(diǎn)都這么找一圈呢?也就是每個(gè)點(diǎn)在參數(shù)空間上都對(duì)應(yīng)一系列參數(shù)對(duì)吧,現(xiàn)在把它們?nèi)A仔同一個(gè)坐標(biāo)系下會(huì)怎么樣呢?為了方便,假設(shè)在這個(gè)直線上取3個(gè)點(diǎn)畫一下:
那么可以看到,首先對(duì)于每一個(gè)點(diǎn),在極坐標(biāo)下,會(huì)存在一個(gè)周期的曲線來(lái)表示通過(guò)這個(gè)點(diǎn),其次,這三個(gè)極坐標(biāo)曲線同時(shí)經(jīng)過(guò)一個(gè)點(diǎn),要搞清楚的是,極坐標(biāo)上每一個(gè)點(diǎn)對(duì) (ρ,θ) 在空間坐標(biāo)上都是對(duì)應(yīng)一條直線的。好了,同時(shí)經(jīng)過(guò)的這一個(gè)點(diǎn)有什么含義呢?它表示在空間坐標(biāo)系下,有一條直線可以經(jīng)過(guò)點(diǎn)1,經(jīng)過(guò)點(diǎn)2,經(jīng)過(guò)點(diǎn)3,這是什么意思?說(shuō)明這三個(gè)點(diǎn)在一條直線上吧。反過(guò)來(lái)再來(lái)看這個(gè)極坐標(biāo)系下的曲線,那么我們只需要找到交點(diǎn)最多的點(diǎn),把它返回到空間域就是這個(gè)需要找的直線了。為什么是找相交最多的點(diǎn),因?yàn)樯厦孢@只是三個(gè)點(diǎn)的曲線,當(dāng)空間上很多點(diǎn)都畫出來(lái)的時(shí)候,那么相交的點(diǎn)可能就不知上述看到的一個(gè)點(diǎn)了,可能有多個(gè)曲線相交點(diǎn),但是有一點(diǎn),勢(shì)必是一條直線上的所有點(diǎn)匯成的交點(diǎn)是曲線相交次數(shù)最多的。
再來(lái)分析這個(gè)算法。可以看到hough變換就是參數(shù)映射變換。對(duì)每一個(gè)點(diǎn)都進(jìn)行映射,并且每一個(gè)映射還不止一次, (ρ,θ) 都是存在步長(zhǎng)的,像一個(gè)點(diǎn)映射成一個(gè) (ρ,θ) ,以 θ 取步長(zhǎng)為例,當(dāng) θ 取得步長(zhǎng)大的時(shí)候,映射的 (ρ,θ) 對(duì)少些,反之則多,但是我們有看到,映射后的點(diǎn)對(duì)是需要求交點(diǎn)的,上述畫出來(lái)的曲線是連續(xù)的,然而實(shí)際上因?yàn)?θ 步長(zhǎng)的存在,他不可能是連續(xù)的,像上上個(gè)圖一樣,是離散的。那么當(dāng) θ 步長(zhǎng)取得比較大的時(shí)候,你還想有很多交點(diǎn)是不可能的,因?yàn)檫@個(gè)時(shí)候是離散的曲線然后再去相交,所以說(shuō) θ 步長(zhǎng)不能太大,理論上是越小效果越好,因?yàn)樵叫。浇咏谶B續(xù)曲線,也就越容易相交,但是越小帶來(lái)的問(wèn)題就是需要非常多的內(nèi)存,計(jì)算機(jī)不會(huì)有那么多內(nèi)存給你的,并且越小,計(jì)算量越大,想想一個(gè)點(diǎn)就需要映射那么多次,每次映射是需要計(jì)算的,耗時(shí)的。那么再想想對(duì)于一副圖像所有點(diǎn)都進(jìn)行映射,隨便假設(shè)一副100*100的圖像(很小吧),就有10000個(gè)點(diǎn),對(duì)每個(gè)點(diǎn)假設(shè)就映射36組 (ρ,θ) 參數(shù)(此時(shí)角度的步長(zhǎng)是10度了,10度,已經(jīng)非常大的一個(gè)概念了),那么總共需要映射360000次,在考慮每次映射計(jì)算的時(shí)間吧??上攵琱ough是多么耗時(shí)耗力。所以必須對(duì)其進(jìn)行改進(jìn)。首先就是對(duì)圖像進(jìn)行改進(jìn),100*100的圖像,10000個(gè)點(diǎn),是不是每個(gè)點(diǎn)都要計(jì)算?大可不必,我們只需要在開始把圖像進(jìn)行一個(gè)輪廓提取,一般使用canny算子就可以,生成黑白二值圖像,白的是輪廓,那么在映射的時(shí)候,只需要把輪廓上的點(diǎn)進(jìn)行參數(shù)空間變換,為什么提輪廓?想想無(wú)論檢測(cè)圖像中存在的直線呀圓呀,它們都是輪廓鮮明的。那么需要變換的點(diǎn)可能就從10000個(gè)點(diǎn)降到可能1000個(gè)點(diǎn)了,這也就是為什么看到許多hough變換提取形狀時(shí)為什么要把圖像提取輪廓,變成二值圖像了。
繼續(xù)算法,分析這么多,可想而知那么一個(gè)hough變換在算法設(shè)計(jì)上就可以如下步驟:
(1)將參數(shù)空間 (ρ,θ) 量化,賦初值一個(gè)二維矩陣M, M(ρ,θ) 就是一個(gè)累加器了。
(2)然后對(duì)圖像邊界上的每一個(gè)點(diǎn)進(jìn)行變換,變換到屬于哪一組 (ρ,θ) ,就把該組 (ρ,θ) 對(duì)應(yīng)的累加器數(shù)加1,這里的需要變換的點(diǎn)就是上面說(shuō)的經(jīng)過(guò)邊緣提取以后的圖像了。
(3)當(dāng)所有點(diǎn)處理完成后,就來(lái)分析得到的 M(ρ,θ) ,設(shè)置一個(gè)閾值T,認(rèn)為當(dāng) M(ρ,θ)>T ,就認(rèn)為存在一條有意義的直線存在。而對(duì)應(yīng)的 M(ρ,θ) 就是這組直線的參數(shù),至于T是多少,自己去式,試的比較合適為止。
(4)有了 M(ρ,θ) 和點(diǎn)(x,y)計(jì)算出來(lái)這個(gè)直線就ok了。
說(shuō)了這么多,這就是原理上hough變換的最底層原理,事實(shí)上完全可以自己寫程序去實(shí)現(xiàn)這些,然而,也說(shuō)過(guò),hough變換是一個(gè)耗時(shí)耗力的算法,自己寫循環(huán)實(shí)現(xiàn)通常很慢,曾經(jīng)用matlab寫過(guò)這個(gè),也有實(shí)際的hough變換例子可以看看:
function mean_circle = hough_circle(BW,step_r,step_angle,r_min,r_max,p)
%------------------------------算法概述-----------------------------
% 該算法通過(guò)a = x-r*cos(angle),b = y-r*sin(angle)將圓圖像中的邊緣點(diǎn)
% 映射到參數(shù)空間(a,b,r)中,由于是數(shù)字圖像且采取極坐標(biāo),angle和r都取
% 一定的范圍和步長(zhǎng),這樣通過(guò)兩重循環(huán)(angle循環(huán)和r循環(huán))即可將原圖像
% 空間的點(diǎn)映射到參數(shù)空間中,再在參數(shù)空間(即一個(gè)由許多小立方體組成的
% 大立方體)中尋找圓心,然后求出半徑坐標(biāo)。
%-------------------------------------------------------------------
%------------------------------輸入?yún)?shù)-----------------------------
% BW:二值圖像;
% step_r:檢測(cè)的圓半徑步長(zhǎng)
% step_angle:角度步長(zhǎng),單位為弧度 :各度計(jì)算 1° = 0.0174
% 2° = 0.035
% 3° = 0.0524
% 4° = 0.0698
% 5° = 0.0872
% r_min:最小圓半徑
% r_max:最大圓半徑
% p:以p*hough_space的最大值為閾值,p取0,1之間的數(shù)
%-------------------------------------------------------------------
% --------對(duì)半徑的大小范圍規(guī)定問(wèn)題--------
% ------ 實(shí)驗(yàn)中發(fā)現(xiàn):外輪廓的半徑范圍在220~260之間
% 內(nèi)輪廓的半徑范圍 60~80之間
% Note:: 當(dāng)圖像改變時(shí)半徑范圍需要改變
% question: 半徑的范圍差超過(guò)50將會(huì)顯示內(nèi)存不足,注意方案辦法
%------------------------------輸出參數(shù)-----------------------------
% hough_space:參數(shù)空間,h(a,b,r)表示圓心在(a,b)半徑為r的圓上的點(diǎn)數(shù)
% hough_circl:二值圖像,檢測(cè)到的圓
% para:檢測(cè)到的所有圓的圓心、半徑
% mean_circle : 返回檢測(cè)到的圓的平均位置及大小
%-------------------------------------------------------------------
[m,n] = size(BW); %取大小
size_r = round((r_max-r_min)/step_r)+1; %半徑增加,循環(huán)次數(shù)
size_angle = round(2*pi/step_angle); %角度增加,循環(huán)次數(shù)
hough_space = zeros(m,n,size_r); %hough空間
[rows,cols] = find(BW);%把要檢測(cè)的點(diǎn)存起來(lái),只有白色(邊緣)點(diǎn)需要變換
ecount = size(rows); %檢測(cè)的點(diǎn)的個(gè)數(shù)
tic %%%% 計(jì)時(shí)開始位置
% Hough變換
% 將圖像空間(x,y)對(duì)應(yīng)到參數(shù)空間(a,b,r)
% a = x-r*cos(angle)
% b = y-r*sin(angle)
for i=1:ecount %點(diǎn)個(gè)數(shù)循環(huán)
for r=1:size_r %單個(gè)點(diǎn)在所有半徑空間內(nèi)檢測(cè)
for k=1:size_angle %單個(gè)點(diǎn)在半徑一定的所在圓內(nèi)檢測(cè)
a = round(rows(i)-(r_min+(r-1)*step_r)*cos(k*step_angle));
b = round(cols(i)-(r_min+(r-1)*step_r)*sin(k*step_angle));
if(a>0a=mb>0b=n) %對(duì)應(yīng)到某個(gè)圓上,記錄之
hough_space(a,b,r) = hough_space(a,b,r)+1;
end
end
end
end
% 搜索超過(guò)閾值的聚集點(diǎn)
max_para = max(max(max(hough_space)));%找到最大值所在圓參數(shù)
index = find(hough_space>=max_para*p);%索引在一定范圍內(nèi)的圓參數(shù)
length = size(index);
toc %%%% 計(jì)時(shí)結(jié)束位置,通過(guò)計(jì)時(shí)觀察運(yùn)行效率,hough變換的一大缺點(diǎn)就是耗時(shí)
% 將索引結(jié)果轉(zhuǎn)換為對(duì)應(yīng)的行列(圓心)和半徑大小
% 理解三維矩陣在內(nèi)存中的存儲(chǔ)方式可以理解公式的原理
for k=1:length
par3 = floor(index(k)/(m*n))+1;
par2 = floor((index(k)-(par3-1)*(m*n))/m)+1;%轉(zhuǎn)換為圓心的y值
par1 = index(k)-(par3-1)*(m*n)-(par2-1)*m;%轉(zhuǎn)換為圓心的x值
par3 = r_min+(par3-1)*step_r; %轉(zhuǎn)化為圓的半徑
%儲(chǔ)存在一起
para(:,k) = [par1,par2,par3]';
end
% 為提高準(zhǔn)確性,求取一個(gè)大致的平均位置(而不是直接采用的最大值)
mean_circle = round(mean(para')');
那么我們?cè)趯?shí)際中大可不必自己寫,opencv已經(jīng)集成了hough變換的函數(shù),調(diào)用它的函數(shù)效率高,也很簡(jiǎn)單。
Opencv中檢測(cè)直線的函數(shù)有cv2.HoughLines(),cv2.HoughLinesP()
函數(shù)cv2.HoughLines()返回值有三個(gè)(opencv 3.0),實(shí)際是個(gè)二維矩陣,表述的就是上述的 (ρ,θ) ,其中 ρ 的單位是像素長(zhǎng)度(也就是直線到圖像原點(diǎn)(0,0)點(diǎn)的距離),而 θ 的單位是弧度。這個(gè)函數(shù)有四個(gè)輸入,第一個(gè)是二值圖像,上述的canny變換后的圖像,二三參數(shù)分別是 ρ 和 θ 的精確度,可以理解為步長(zhǎng)。第四個(gè)參數(shù)為閾值T,認(rèn)為當(dāng)累加器中的值高于T是才認(rèn)為是一條直線。自己畫了個(gè)圖實(shí)驗(yàn)一下:
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([])
測(cè)試一個(gè)新的圖,不停的改變 cv2.HoughLines最后一個(gè)閾值參數(shù)到合理的時(shí)候如下:
可以看到檢測(cè)的還可以的。
函數(shù)cv2.HoughLinesP()是一種概率直線檢測(cè),我們知道,原理上講hough變換是一個(gè)耗時(shí)耗力的算法,尤其是每一個(gè)點(diǎn)計(jì)算,即使經(jīng)過(guò)了canny轉(zhuǎn)換了有的時(shí)候點(diǎn)的個(gè)數(shù)依然是龐大的,這個(gè)時(shí)候我們采取一種概率挑選機(jī)制,不是所有的點(diǎn)都計(jì)算,而是隨機(jī)的選取一些個(gè)點(diǎn)來(lái)計(jì)算,相當(dāng)于降采樣了。這樣的話我們的閾值設(shè)置上也要降低一些。在參數(shù)輸入輸出上,輸入不過(guò)多了兩個(gè)參數(shù):minLineLengh(線的最短長(zhǎng)度,比這個(gè)短的都被忽略)和MaxLineCap(兩條直線之間的最大間隔,小于此值,認(rèn)為是一條直線)。輸出上也變了,不再是直線參數(shù)的,這個(gè)函數(shù)輸出的直接就是直線點(diǎn)的坐標(biāo)位置,這樣可以省去一系列for循環(huán)中的由參數(shù)空間到圖像的實(shí)際坐標(biāo)點(diǎn)的轉(zhuǎn)換。
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([])
可以看到這個(gè)方法似乎更好些,速度還快,調(diào)參數(shù)得到較好的效果就ok了。
Ok說(shuō)完了直線的檢測(cè),再來(lái)說(shuō)說(shuō)圓環(huán)的檢測(cè),我們知道圓的數(shù)學(xué)表示為:
從而一個(gè)圓的確定需要三個(gè)參數(shù),那么就需要三層循環(huán)來(lái)實(shí)現(xiàn)(比直線的多一層),從容把圖像上的所有點(diǎn)映射到三維參數(shù)空間上。其他的就一樣了,尋找參數(shù)空間累加器的最大(或者大于某一閾值)的值。那么理論上圓的檢測(cè)將比直線更耗時(shí),然而opencv對(duì)其進(jìn)行了優(yōu)化,用了一種霍夫梯度法,感興趣去研究吧,我們只要知道它能優(yōu)化算法的效率就可以了。關(guān)于函數(shù)參數(shù)輸入輸出:
cv2.HoughCircles(image, method, dp, minDist, circles, param1, param2, minRadius, maxRadius)
這個(gè)時(shí)候輸入為灰度圖像,同時(shí)最好規(guī)定檢測(cè)的圓的最大最小半徑,不能盲目的檢測(cè),否側(cè)浪費(fèi)時(shí)間空間。輸出就是三個(gè)參數(shù)空間矩陣。
來(lái)個(gè)實(shí)際點(diǎn)的人眼圖像,把minRadius和maxRadius調(diào)到大圓范圍檢測(cè)如下:
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([])
把半徑范圍調(diào)小點(diǎn),檢測(cè)內(nèi)圓:
至此圓的檢測(cè)就是這樣。
到此這篇關(guān)于Python下opencv使用hough變換檢測(cè)直線與圓的文章就介紹到這了,更多相關(guān)opencv hough變換檢測(cè)直線與圓內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
您可能感興趣的文章:- python opencv檢測(cè)直線 cv2.HoughLinesP的實(shí)現(xiàn)
- Opencv2.4.9函數(shù)HoughLinesP分析
- OpenCV霍夫變換(Hough Transform)直線檢測(cè)詳解
- Opencv Hough算法實(shí)現(xiàn)圖片中直線檢測(cè)
- 利用Opencv中Houghline方法實(shí)現(xiàn)直線檢測(cè)
- Java+opencv3.2.0實(shí)現(xiàn)hough直線檢測(cè)
- Java+opencv3.2.0實(shí)現(xiàn)hough圓檢測(cè)功能