OpenCV-Python教程(9、使用霍夫變換檢測直線)
相比C++而言,Python適合做原型。本系列的文章介紹如何在Python中用OpenCV圖形庫,以及與C++調用相應OpenCV函數的不同之處。這篇文章介紹在Python中使用OpenCV的霍夫變換檢測直線。
提示:
- 轉載請詳細注明原作者及出處,謝謝!
- 本文介紹在OpenCV-Python中使用霍夫變換檢測直線的方法。
- 本文不介詳細的理論知識,讀者可從其他資料中獲取相應的背景知識。筆者推薦清華大學出版社的《圖像處理與計算機視覺算法及應用(第2版)?》。
霍夫變換
Hough變換是經典的檢測直線的算法。其最初用來檢測圖像中的直線,同時也可以將其擴展,以用來檢測圖像中簡單的結構。
OpenCV提供了兩種用于直線檢測的Hough變換形式。其中基本的版本是cv2.HoughLines。其輸入一幅含有點集的二值圖(由非0像素表示),其中一些點互相聯系組成直線。通常這是通過如Canny算子獲得的一幅邊緣圖像。cv2.HoughLines函數輸出的是[float, float]形式的ndarray,其中每個值表示檢測到的線(ρ , θ)中浮點點值的參數。下面的例子首先使用Canny算子獲得圖像邊緣,然后使用Hough變換檢測直線。其中HoughLines函數的參數3和4對應直線搜索的步長。在本例中,函數將通過步長為1的半徑和步長為π/180的角來搜索所有可能的直線。最后一個參數是經過某一點曲線的數量的閾值,超過這個閾值,就表示這個交點所代表的參數對(rho, theta)在原圖像中為一條直線。具體理論可參考這篇文章。
[python]?view plaincopy
?? import?cv2?? import?numpy?as?np???? ?? img?=?cv2.imread("/home/sunny/workspace/images/road.jpg",?0)?? ?? img?=?cv2.GaussianBlur(img,(3,3),0)?? edges?=?cv2.Canny(img,?50,?150,?apertureSize?=?3)?? lines?=?cv2.HoughLines(edges,1,np.pi/180,118)??? result?=?img.copy()?? for?line?in?lines[0]:?? ????rho?=?line[0]??? ????theta=?line[1]??? ????print?rho?? ????print?theta?? ????if??(theta?<?(np.pi/4.?))?or?(theta?>?(3.*np.pi/4.0)):??? ?????????????????? ????????pt1?=?(int(rho/np.cos(theta)),0)?? ?????????? ????????pt2?=?(int((rho-result.shape[0]*np.sin(theta))/np.cos(theta)),result.shape[0])?? ?????????? ????????cv2.line(?result,?pt1,?pt2,?(255))?? ????else:??? ?????????? ????????pt1?=?(0,int(rho/np.sin(theta)))?? ?????????? ????????pt2?=?(result.shape[1],?int((rho-result.shape[1]*np.cos(theta))/np.sin(theta)))?? ?????????? ????????cv2.line(result,?pt1,?pt2,?(255),?1)?? ?? cv2.imshow('Canny',?edges?)?? cv2.imshow('Result',?result)?? cv2.waitKey(0)?? cv2.destroyAllWindows()??
結果如下:
注意:
在C++中,HoughLines函數得到的結果是一個向量lines,其中的元素是由兩個元素組成的子向量(rho, theta),所以lines的訪問方式類似二維數組。因此,可以以類似:
[cpp]?view plaincopy
std::vector<cv::Vec2f>::const_iterator?it=?lines.begin();?? float?rho=?(*it)[0];?? float?theta=?(*it)[1];??
這樣的方式訪問rho和theta。
而在Python中,返回的是一個三維的np.ndarray!。可通過檢驗HoughLines返回的lines的ndim屬性得到。如:
[python]?view plaincopy
lines?=?cv2.HoughLines(edges,1,np.pi/180,118)?? print?lines.ndim?? ??
至于為什么是三維的,這和NumPy中ndarray的屬性有關(關于NumPy的相關內容,請移步至
NumPy簡明教程
),如果將HoughLines檢測到的的結果輸出,就一目了然了:
[python]?view plaincopy
?? ?? 3??? (1,?5,?2)??? ?? ?? [[??4.20000000e+01???2.14675498e+00]?? ?[??4.50000000e+01???2.14675498e+00]?? ?[??3.50000000e+01???2.16420817e+00]?? ?[??1.49000000e+02???1.60570288e+00]?? ?[??2.24000000e+02???1.74532920e-01]]?? ===============?? ?? [[[??4.20000000e+01???2.14675498e+00]?? ??[??4.50000000e+01???2.14675498e+00]?? ??[??3.50000000e+01???2.16420817e+00]?? ??[??1.49000000e+02???1.60570288e+00]?? ??[??2.24000000e+02???1.74532920e-01]]]??
概率霍夫變換
觀察前面的例子得到的結果圖片,其中Hough變換看起來就像在圖像中查找對齊的邊界像素點集合。但這樣會在一些情況下導致虛假檢測,如像素偶然對齊或多條直線穿過同樣的對齊像素造成的多重檢測。
要避免這樣的問題,并檢測圖像中分段的直線(而不是貫穿整個圖像的直線),就誕生了Hough變化的改進版,即概率Hough變換(Probabilistic Hough)。在OpenCV中用函數cv::HoughLinesP 實現。如下:
[python]?view plaincopy
?? import?cv2?? import?numpy?as?np???? ?? img?=?cv2.imread("/home/sunny/workspace/images/road.jpg")?? ?? img?=?cv2.GaussianBlur(img,(3,3),0)?? edges?=?cv2.Canny(img,?50,?150,?apertureSize?=?3)?? lines?=?cv2.HoughLines(edges,1,np.pi/180,118)?? result?=?img.copy()?? ?? ?? minLineLength?=?200?? maxLineGap?=?15?? lines?=?cv2.HoughLinesP(edges,1,np.pi/180,80,minLineLength,maxLineGap)?? for?x1,y1,x2,y2?in?lines[0]:?? ????cv2.line(img,(x1,y1),(x2,y2),(0,255,0),2)?? ?? cv2.imshow('Result',?img)?? cv2.waitKey(0)?? cv2.destroyAllWindows()??
結果如下:
未完待續。。。
參考資料:
1、《Opencv2 Computer Vision Application Programming Cookbook》
2、《OpenCV References Manule》
OpenCV-Python教程(10、直方圖均衡化)
相比C++而言,Python適合做原型。本系列的文章介紹如何在Python中用OpenCV圖形庫,以及與C++調用相應OpenCV函數的不同之處。這篇文章介紹在Python中使用OpenCV和NumPy對直方圖進行均衡化處理。
提示:
- 轉載請詳細注明原作者及出處,謝謝!
- 本文不介詳細的理論知識,讀者可從其他資料中獲取相應的背景知識。筆者推薦清華大學出版社的《圖像處理與計算機視覺算法及應用(第2版)?》,對于本節的內容,建議直接參考維基百科直方圖均衡化,只需看下頁面最后的兩幅圖就能懂了。
本文內容:
- 使用查找表拉伸直方圖
- 使用OpenCV和NumPy的函數以不同的方式進行直方圖均衡化
在某些情況下,一副圖像中大部分像素的強度都集中在某一區域,而質量較高的圖像中,像素的強度應該均衡的分布。為此,可將表示像素強度的直方圖進行拉伸,將其平坦化。如下:
圖來自維基百科
實驗數據
本節的實驗數據來自維基百科,原圖如下:
其直方圖為:
使用查找表來拉伸直方圖
在圖像處理中,直方圖均衡化一般用來均衡圖像的強度,或增加圖像的對比度。在介紹使用直方圖均衡化來拉伸圖像的直方圖之前,先介紹使用查詢表的方法。
觀察上圖中原始圖像的直方圖,很容易發現大部分強度值范圍都沒有用到。因此先檢測圖像非0的最低(imin)強度值和最高(imax)強度值。將最低值imin設為0,最高值imax設為255。中間的按255.0*(i-imin)/(imax-imin)+0.5)的形式設置。
實現的任務主要集中在查詢表的創建中,代碼如下:
[python]?view plaincopy
minBinNo,?maxBinNo?=?0,?255?? ?? ?? for?binNo,?binValue?in?enumerate(hist):?? ????if?binValue?!=?0:?? ????????minBinNo?=?binNo?? ????????break?? ?? for?binNo,?binValue?in?enumerate(reversed(hist)):?? ????if?binValue?!=?0:?? ????????maxBinNo?=?255-binNo?? ????????break?? print?minBinNo,?maxBinNo?? ?? ?? for?i,v?in?enumerate(lut):?? ????print?i?? ????if?i?<?minBinNo:?? ????????lut[i]?=?0?? ????elif?i?>?maxBinNo:?? ????????lut[i]?=?255?? ????else:?? ????????lut[i]?=?int(255.0*(i-minBinNo)/(maxBinNo-minBinNo)+0.5)??
查詢表創建完成后,就直接調用相應的OpenCV函數即可,這里調用的是cv2.LUT函數:
[python]?view plaincopy
?? result?=?cv2.LUT(image,?lut)??
cv2.LUT函數只有兩個參數,分別為輸入圖像和查找表,其返回處理的結果,完整代碼如下:
[python]?view plaincopy
?? import?cv2?? import?numpy?as?np?? ?? image?=?cv2.imread("D:/test/unequ.jpg",?0)?? lut?=?np.zeros(256,?dtype?=?image.dtype?)?? hist=?cv2.calcHist([image],??? ????[0],??? ????None,??? ????[256],??? ????[0.0,255.0])?? ?????? minBinNo,?maxBinNo?=?0,?255?? ?? ?? for?binNo,?binValue?in?enumerate(hist):?? ????if?binValue?!=?0:?? ????????minBinNo?=?binNo?? ????????break?? ?? for?binNo,?binValue?in?enumerate(reversed(hist)):?? ????if?binValue?!=?0:?? ????????maxBinNo?=?255-binNo?? ????????break?? print?minBinNo,?maxBinNo?? ?? ?? for?i,v?in?enumerate(lut):?? ????print?i?? ????if?i?<?minBinNo:?? ????????lut[i]?=?0?? ????elif?i?>?maxBinNo:?? ????????lut[i]?=?255?? ????else:?? ????????lut[i]?=?int(255.0*(i-minBinNo)/(maxBinNo-minBinNo)+0.5)?? ?? ?? result?=?cv2.LUT(image,?lut)?? cv2.imshow("Result",?result)?? cv2.imwrite("LutImage.jpg",?result)?? cv2.waitKey(0)?? cv2.destroyAllWindows()??
直方圖結果如下,可以看到原來占的區域很小的直方圖尖峰被移動了:
處理結果為:
關于直方圖的繪制,請參考這篇文章。
直方圖均衡化
介紹
有時圖像的視覺上的缺陷并不在強度值集中在很窄的范圍內。而是某些強度值的使用頻率很大。比如第一幅圖中,灰度圖中間值的占了很大的比例。
在完美均衡的直方圖中,每個柱的值都應該相等。即50%的像素值應該小于128,25%的像素值應該小于64。總結出的經驗可定義為:在標準的直方圖中p%的像素擁有的強度值一定小于或等于255×p%。將該規律用于均衡直方圖中:強度i的灰度值應該在對應的像素強度值低于i的百分比的強度中。因此,所需的查詢表可以由下面的式子建立:
[python]?view plaincopy
lut[i]?=?int(255.0?*p[i])???
p[i]即直方圖累積值,這是包含小于給點強度值的像素的直方圖,以代替包含指定強度值像素的數目。比如第一幅圖像的累計直方圖如下圖中的藍線:
而完美均衡的直方圖,其累積直方圖應為一條斜線,如上圖中均衡化之后的紅線。
更專業一點,這種累積直方圖應稱為累積分布(cumulative distribition)。在NumPy中有一個專門的函數來計算。這在NumPy實現直方圖均衡化一節中介紹。
通過上面的介紹,應該可以明白,直方圖均衡化就是對圖像使用一種特殊的查詢表。在第三個例子中可以看到使用查詢表來獲得直方圖均衡化的效果。通常來說,直方圖均衡化大大增加了圖像的表象。但根據圖像可視內容的不同,不同圖像的直方圖均衡化產生的效果不盡相同。
直方圖均衡化之OpenCV函數實現
用OpenCV實現直方圖均衡化很簡單,只需調用一個函數即可:
[python]?view plaincopy
img?=?cv2.imread('圖像路徑',0)?? equ?=?cv2.equalizeHist(img)?? cv2.imshow('equ',equ)??
這樣圖像就均衡化了。可以通過
直方圖的計算與顯示
這篇文章中介紹的方法將結果繪制出來。
直方圖均衡化之NumPy函數實現
通過前面的介紹,可以明白直方圖均衡化就是用一種特殊的查找表來實現的。所以這里用NumPy函數,以查找表的方式手動實現圖像直方圖的均衡化:
[python]?view plaincopy
?? import?cv2?? import?numpy?as?np?? ?? image?=?cv2.imread("D:/test/unequ.jpg",?0)?? ?? lut?=?np.zeros(256,?dtype?=?image.dtype?)?? ?? hist,bins?=?np.histogram(image.flatten(),256,[0,256])??? cdf?=?hist.cumsum()??? cdf_m?=?np.ma.masked_equal(cdf,0)??? cdf_m?=?(cdf_m?-?cdf_m.min())*255/(cdf_m.max()-cdf_m.min())?? cdf?=?np.ma.filled(cdf_m,0).astype('uint8')??? ?? ?? result2?=?cdf[image]?? result?=?cv2.LUT(image,?cdf)?? ?? cv2.imshow("OpenCVLUT",?result)?? cv2.imshow("NumPyLUT",?result2)?? cv2.waitKey(0)?? cv2.destroyAllWindows()??
最終結果
驗證
比較查找表和OpenCV直方圖均衡化生成的直方圖:
可以看出,總體上來看是吻合的,但OpenCV中函數的實現
可能
還有一些細微的差別(有空去翻下源碼,不過今天就先到這里了)。
參考資料:
1、《Opencv2 Computer Vision Application Programming Cookbook》
2、《OpenCV References Manule》
3、http://opencvpython.blogspot.com/2013/03/histograms-2-histogram-equalization.html
OpenCV-Python教程(11、輪廓檢測)
相比C++而言,Python適合做原型。本系列的文章介紹如何在Python中用OpenCV圖形庫,以及與C++調用相應OpenCV函數的不同之處。這篇文章介紹在Python中使用OpenCV檢測并繪制輪廓。
提示:
- 轉載請詳細注明原作者及出處,謝謝!
- 本文介紹在OpenCV-Python中檢測并繪制輪廓的方法。
- 本文不介詳細的理論知識,讀者可從其他資料中獲取相應的背景知識。筆者推薦清華大學出版社的《圖像處理與計算機視覺算法及應用(第2版)?》。
輪廓檢測
輪廓檢測也是圖像處理中經常用到的。OpenCV-Python接口中使用cv2.findContours()函數來查找檢測物體的輪廓。
實現
使用方式如下:
[python]?view plaincopy
import?cv2?? ?? img?=?cv2.imread('D:\\test\\contour.jpg')?? gray?=?cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)?? ret,?binary?=?cv2.threshold(gray,127,255,cv2.THRESH_BINARY)?? ?? contours,?hierarchy?=?cv2.findContours(binary,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)?? cv2.drawContours(img,contours,-1,(0,0,255),3)?? ?? cv2.imshow("img",?img)?? cv2.waitKey(0)??
需要注意的是cv2.findContours()函數接受的參數為二值圖,即黑白的(不是灰度圖),所以讀取的圖像要先轉成灰度的,再轉成二值圖,參見4、5兩行。第六行是檢測輪廓,第七行是繪制輪廓。
結果
原圖如下:
檢測結果如下:
注意,findcontours函數會“原地”修改輸入的圖像。這一點可通過下面的語句驗證:
[python]?view plaincopy
cv2.imshow("binary",?binary)?? contours,?hierarchy?=?cv2.findContours(binary,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)?? cv2.imshow("binary2",?binary)??
執行這些語句后會發現原圖被修改了。
cv2.findContours()函數
函數的原型為
[python]?view plaincopy
cv2.findContours(image,?mode,?method[,?contours[,?hierarchy[,?offset?]]])??
返回兩個值:contours:hierarchy。
參數
第一個參數是尋找輪廓的圖像;
第二個參數表示輪廓的檢索模式,有四種(本文介紹的都是新的cv2接口):
??? cv2.RETR_EXTERNAL表示只檢測外輪廓
??? cv2.RETR_LIST檢測的輪廓不建立等級關系
??? cv2.RETR_CCOMP建立兩個等級的輪廓,上面的一層為外邊界,里面的一層為內孔的邊界信息。如果內孔內還有一個連通物體,這個物體的邊界也在頂層。
??? cv2.RETR_TREE建立一個等級樹結構的輪廓。
第三個參數method為輪廓的近似辦法
??? cv2.CHAIN_APPROX_NONE存儲所有的輪廓點,相鄰的兩個點的像素位置差不超過1,即max(abs(x1-x2),abs(y2-y1))==1
??? cv2.CHAIN_APPROX_SIMPLE壓縮水平方向,垂直方向,對角線方向的元素,只保留該方向的終點坐標,例如一個矩形輪廓只需4個點來保存輪廓信息
??? cv2.CHAIN_APPROX_TC89_L1,CV_CHAIN_APPROX_TC89_KCOS使用teh-Chinl chain 近似算法
返回值
cv2.findContours()函數返回兩個值,一個是輪廓本身,還有一個是每條輪廓對應的屬性。
contour返回值
cv2.findContours()函數首先返回一個list,list中每個元素都是圖像中的一個輪廓,用numpy中的ndarray表示。這個概念非常重要。在下面drawContours中會看見。通過
[python]?view plaincopy
print?(type(contours))?? print?(type(contours[0]))?? print?(len(contours))??
可以驗證上述信息。會看到本例中有兩條輪廓,一個是五角星的,一個是矩形的。每個輪廓是一個ndarray,每個ndarray是輪廓上的點的集合。
由于我們知道返回的輪廓有兩個,因此可通過
[python]?view plaincopy
cv2.drawContours(img,contours,0,(0,0,255),3)??
和
[python]?view plaincopy
cv2.drawContours(img,contours,1,(0,255,0),3)??
分別繪制兩個輪廓,關于該參數可參見下面一節的內容。同時通過
[python]?view plaincopy
print?(len(contours[0]))?? print?(len(contours[1]))??
輸出兩個輪廓中存儲的點的個數,可以看到,第一個輪廓中只有4個元素,這是因為輪廓中并不是存儲輪廓上所有的點,而是只存儲可以用直線描述輪廓的點的個數,比如一個“正立”的矩形,只需4個頂點就能描述輪廓了。
hierarchy返回值
此外,該函數還可返回一個可選的hiararchy結果,這是一個ndarray,其中的元素個數和輪廓個數相同,每個輪廓contours[i]對應4個hierarchy元素hierarchy[i][0] ~hierarchy[i][3],分別表示后一個輪廓、前一個輪廓、父輪廓、內嵌輪廓的索引編號,如果沒有對應項,則該值為負數。
通過
[python]?view plaincopy
print?(type(hierarchy))?? print?(hierarchy.ndim)?? print?(hierarchy[0].ndim)?? print?(hierarchy.shape)??
得到
[python]?view plaincopy
3?? 2?? (1,?2,?4)??
可以看出,hierarchy本身包含兩個ndarray,每個ndarray對應一個輪廓,每個輪廓有四個屬性。
輪廓的繪制
OpenCV中通過cv2.drawContours在圖像上繪制輪廓。??
cv2.drawContours()函數
[python]?view plaincopy
cv2.drawContours(image,?contours,?contourIdx,?color[,?thickness[,?lineType[,?hierarchy[,?maxLevel[,?offset?]]]]])??
- 第一個參數是指明在哪幅圖像上繪制輪廓;
- 第二個參數是輪廓本身,在Python中是一個list。
- 第三個參數指定繪制輪廓list中的哪條輪廓,如果是-1,則繪制其中的所有輪廓。后面的參數很簡單。其中thickness表明輪廓線的寬度,如果是-1(cv2.FILLED),則為填充模式。繪制參數將在以后獨立詳細介紹。
補充:
寫著寫著發現一篇文章介紹不完,所以這里先作為入門的。更多關于輪廓的信息有機會再開一篇文章介紹。
但有朋友提出計算輪廓的極值點。可用下面的方式計算得到,如下
[python]?view plaincopy
pentagram?=?contours[1]??? ?? leftmost?=?tuple(pentagram[:,0][pentagram[:,:,0].argmin()])?? rightmost?=?tuple(pentagram[:,0][pentagram[:,:,0].argmin()])?? ?? cv2.circle(img,?leftmost,?2,?(0,255,0),3)??? cv2.circle(img,?rightmost,?2,?(0,0,255),3)???
注意!假設輪廓有100個點,OpenCV返回的ndarray的維數是(100, 1, 2)!!!而不是我們認為的(100, 2)。切記!!!人民郵電出版社出版了一本《NumPy攻略:Python科學計算與數據分析》,推薦去看一下。
更新:關于pentagram[:,0]的意思
在numpy的數組中,用逗號分隔的是軸的索引。舉個例子,假設有如下的數組:
[python]?view plaincopy
a?=?np.array([[[3,4]],?[[1,2]],[[5,7]],[[3,7]],[[1,8]]])??
其shape是(5, 1, 2)。與我們的輪廓是相同的。那么a[:,0]的結果就是:
[python]?view plaincopy
[3,4],?[1,2],?[5,7],?[3,7],?[1,8]??
這里a[:,0]的意思就是a[0:5,0],也就是a[0:5,0:0:2]
,這三者是等價的
。
回頭看一下,a的shape是(5,1,2),表明是三個軸的。在numpy的數組中,軸的索引是通過逗號分隔的。同時冒號索引“:”表示的是該軸的所有元素。因此a[:, 0]表示的是第一個軸的所有元素和第二個軸的第一個元素。在這里既等價于a[0:5, 0]。
再者,若給出的索引數少于數組中總索引數,則將已給出的索引樹默認按順序指派到軸上。比如a[0:5,0]只給出了兩個軸的索引,則第一個索引就是第一個軸的,第二個索引是第二個軸的,而第三個索引沒有,則默認為[:],即該軸的所有內容。因此a[0:5,0]也等價于a[0:5,0:0:2]。
再詳細一點,a的全體內容為:[[[3,4]], [[1,2]],[[5,7]],[[3,7]],[[1,8]]]。去掉第一層方括號,其中有五個元素,每個元素為[[3,4]]這樣的,所以第一個索引的范圍為[0:5]。注意OpenCV函數返回的多維數組和常見的numpy數組的不同之處!
觀察[[3,4]],我們發現其中只有一個元素,即[3, 4],第二個索引為[0:1]。
再去掉一層方括號,我們面對的是[3,4],有兩個元素,所以第三個索引的范圍為[0:2]。
再次強調一下OpenCVPython接口函數返回的NumPy數組和普通的NumPy數組在組織上的不同之處。
PS:OpenCV-Python討論群——219962286,歡迎大家加入互相探討學習。
得到的結果為如下:
參考資料:
1、《Opencv2 Computer Vision Application Programming Cookbook》
2、《OpenCV References Manule》
3、OpenCV官方文檔Contour部分
from: http://blog.csdn.net/sunny2038/article/category/904451
總結
以上是生活随笔為你收集整理的OpenCV-Python教程(9)(10)(11): 使用霍夫变换检测直线 直方图均衡化 轮廓检测的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。