輪廓究竟是什麼?一個輪廓通常對應一系列的點,也就是圖像中的一條曲線.表示的方法可能根據不一樣狀況而有所不一樣.有多重方法能夠表示曲線.在openCV中通常用序列來存儲輪廓信息.序列中的每個元素是曲線中一個點的位置.關於序列表示的輪廓細節將在後面討論,如今只要簡單把輪廓想象爲使用CvSeq表示的一系列的點就能夠了.算法
函數cvFindContours()從二值圖像中尋找輪廓.cvFindContours()處理的圖像能夠是從cvCanny()函數獲得的有邊緣像素的圖像,或者是從cvThreshold()及cvAdaptiveThreshold()獲得的圖像,這時的邊緣是正和負區域之間的邊界.數組
圖8-2描述了cvFindContours的函數功能,圖像的上半部分是神色背景和白色區域(被從A到E標記)的測試圖像.下半部分是使用cvFindCountours()函數後會獲得的輪廓的說明.這些輪廓被標記爲cX或hx,"c"表示"輪廓(contour)","h"表示"孔(hole)","X表述數字".其中一些輪廓用虛劃線表示;代表他們是白色區域的外部邊界(例如,非0區域).孔(hole)的外部邊界(例如,非0區域)即白色區域的內部邊界.在圖中是用電線表示外部邊界的.OpenCV的cvFindContours()函數可區份內部和外部邊界.app
包含的概念在不少應用中都很是重要.所以.OpenCV容許獲得的輪廓被聚合成一個輪廓樹,從而把包含關係編碼到樹結構中.這個測試圖的輪廓樹在根節點的輪廓叫c0,孔h00和h01是它的字子節點.這些輪廓中直接包含輪廓稱爲他們的子節點,以此類推.ide
如今來看cvFindContours()函數函數
storage 是內存存儲器,cvFindContours()找到的輪廓記錄在此內存裏.正如以前所說,這個存儲器的空間應該由cvCreateMemStorage()分配.測試
first_contour 是指向CvSeq*的一個指針firstContour.無需動手,cvFindContours()會自動分配該指針.實際上,只要在這裏傳一個指針就能夠了函數會自動設置.不須要分配和釋放(new/delete或者malloc/free).就是這個指針(例如,*firstContour)指向輪廓樹的首地址(head).cvFindContours()返回值是,找到的全部輪廓的個數ui
cvSeq* firstContout = NULL;編碼
cvFindContours(..., &firstContour, ...);spa
headerSize告訴cvFindContours()更多有關對象分配的信息,它能夠被設定爲sizeof(CvContour)或者sizeof(CvChain)(當近似方法參數method被設定爲CV_ChAIN_CODE時使用後者).最後是mode和method參數,他們分別指定計算方法和如何計算..net
mode變量能夠被設置爲如下四個選項之一: CV_RETR_ExTERNAL, CV_RETR_LIST, CV_RETR_CCOMP或CV_RETR_TREE.mode的值向cvFindeContours()說明須要的輪廓類型,和但願的放回值形式.具體說來,mode的值決定把找到的輪廓如何掛到輪廓樹節點變量(h_prev,h_next,v_prev和v_next)上,圖8-3展現了四種可能的mode值所獲得的結果的拓撲結構.
每中狀況下,結構均可以當作是被"橫向"鏈接(h_next和h_prev)聯繫和被"縱向"鏈接(v_next和v_prev)不一樣的"層次".
CV_RETR_EXTERNAL 只檢測出最外的輪廓.圖8-2中,只有一個最外輪廓,所以圖8-3中第一個輪廓指向最外的序列,除此以外沒有別的鏈接
CV_RETR_LIST 檢測全部的輪廓並將他們保存到表(list)中.圖8-3描繪了從圖8-2樣圖中獲得的表.在這個例子中,有8條輪廓被找到,他們相互之間有h_prev和h_next鏈接(這裏並無使用v_prev和v_next)
CV_RETR_CCOMP 檢出全部的輪廓並將他們組織成雙層結構(two-level hierarchy),頂層邊界是全部成份的外界邊界,第二層邊界是空的邊界.圖8-3中,咱們能看到5個外部邊界,其中3個包含孔.孔被v_next和v_prev能夠只包括一個值,此節點能夠只有一個子節點.c0中有兩個孔,由於v_next能夠值包括一個值,次節點能夠只有一個子節點.c0以內的全部孔相互間有h_prev和h_next指針鏈接.
CV_RETR_TREE 檢出全部輪廓而且從新創建網狀的輪廓結構.在咱們給出的例子中(圖8-2和8-3中),這意味着根節點是最外的輪廓c0.c0之下是空h00,在同一層次中與另外一個孔h01相鏈接.同理,每一個孔都有子節點(相對應的是c000和c010),這些子節點與父節點被垂直鏈接起來.這個步驟一直持續到圖像最內層的輪廓,這些輪廓會成爲樹葉節點.
如下的五個值與方法相關(例如輪廓會如何被近似).
CV_CHAIN_CODE 用freeman鏈碼輸出輪廓,其餘方法輸出多邊形(頂點的序列)
CV_CHAIN_APPROX_NONE 將鏈碼編碼中的全部點轉換爲點
CV_CHAIN_APPROX_SIMPLE 壓縮水平,垂直或斜的部分,只保存最後一個點
CV_CHAIN_APPROX_TC89_L1或CV_CHAIN_APPROX_TC89_KCOS使用Ten-Chin鏈逼近算法中的一個
CV_LINK_RUNS 與上述算法徹底不一樣的算法,鏈接全部水平層次的輪廓.此方法只可與Cv_RETR_LIST搭配使用.
使用序列表示輪廓
當調用cvFindContours函數的時候,返回多個序列.序列的類型依賴與調用cvFindContours時 所傳遞的參數.默認狀況下使用CV_RETR_LIST和CV_CHAIN_APPROX_SIMPLE參數.
序列中保存一系列的點,這些點構成輪廓,輪廓是本章的重點.輪廓只是序列所能表示物體的一種.輪廓的點的序列,能夠用來表示圖像空間中的曲線.這種點的序列很經常使用,全部須要有專門的函數來幫助咱們對他進行處理.下面是一組這樣的處理函數.
cvSubstituteContour()函數用於替換scanner指向的輪廓.該函數的一個特性是,若是參數 new_contour爲NULL,那麼當前的輪廓將被從Scanner指定的樹或鏈表中刪除(受影響的序列會做適當更新,來保證不會有指針指向不存在的物體).
函數cvEndFindContour()結束輪廓查找,而且將scanner設置爲結束狀態.注意,scanner並無被刪除,實際上該函數返回的是指針所指序列的第一個元素.
最後一個函數cvApproxChains()函數.該函數將Freeman鏈轉換爲多邊形表示(精確轉換或者近似擬合).
通常狀況下,經過cvFindCountours獲取的輪廓是一系列頂點的序列.另外一種不一樣的表達是設置method參數爲CV_CHAIN_CODE,而後生成輪廓.當選者CV_CHAIN_CODE標誌的時候,檢測的輪廓經過Freemain鏈碼[Freeman67](圖8-4)的方式返回.在Freeman鏈碼中,多邊形被表示爲一系列的位移,每個位移有8個方向,這8個方向使用整數0到7表示.Freeman鏈碼對於識別一些形狀的物體頗有幫助.若是獲得的是Freeman鏈碼,能夠經過如下兩個函數讀出每一個點
第一個函數用來初始化Freeman鏈CvChainPtReader結構,第二個函數經過CvChainptReader來讀每一個點,CvChainPtReader對應當前狀態.結構CvChain從CvSeq擴展得來.和CvContourScanner從多個輪廓間迭代同樣,CvChainPtReader用於迭代一個使用Freemain鏈碼錶示輪廓中的每一個點.CvChainPtReader和CvSeqReader的用法相似.如您所指望,當全部點都讀完後,返回CvChainPtReader值爲NULL.
一個常用的功能是在屏幕上繪製檢測到的輪廓.繪製能夠用cvDrawContours函數完成
經過max_level變量能夠告訴cvDrawConturs() 如何處理經過節點樹變量連結到一個輪廓上的其餘任何輪廓.此變量能夠被設置爲遍歷輪廓的最大深度.所以max_level = 0表示與輸入輪廓屬於贊成等級的全部輪廓(更具體的說,輸入輪廓和與其相鄰的輪廓被畫出),max_level = 1表示與輸入輪廓屬於同一登記的全部輪廓與其子節點被畫出,以此類推.若是項要畫的輪廓是由cvFindContous()的CV_RETR_CCOMP或CV_RETR_TREE模式獲得的話,max_level的負值也是被支持的.在這種狀況下,max_level=-1表示只有輸入輪廓被畫出,以此類推,max_level = -2 表示輸入輪廓與其直系(僅直接相連的)子節點會被畫出,以此類推.
參數thickness和line_type就如其字面含義所示.最後,咱們能夠給繪圖程序一個偏移量,這樣輪廓能夠被畫在指定的精確座標上.當輪廓座標被轉換成質心座標或其餘局部座標系的時候,這個特性很是有用.
若是在圖像上的不一樣感興趣的區域屢次執行cvFindContour(),而後又想將全部結果在原來大圖像上顯示出來,便宜量offset也頗有用.相反,能夠先從大圖提取出一個輪廓,而後在用offset和填充,在小圖像上造成和輪廓對應的蒙板(mask);
首先建立一個窗口用於顯示圖像,滑動條(trackbar)用於設置閾值,而後對採二值化後的圖像提取輪廓並繪製輪廓.當控制參數的滑動條變換時,圖像被更新.
而後g_image被轉換爲灰度圖像,接着用g_thresh爲參數進行二值化處理,獲得的二值圖像保存在g_gray中.cvFindContours從二值圖像g_gray查找輪廓,而後將獲得的輪廓用cvDrawContours()函數繪製爲白色到灰度圖像.最終圖像在窗口中顯示出來,並將在回調函數開始處申請的結構釋放.
在上例中,咱們檢測出輸入圖像的輪廓,而後逐個繪製沒格輪廓.從這個例子中,咱們能夠了解到輪廓檢測方法(如代碼中是CV_RETR_LIST)以及max_depth(代碼中是0)等參數的細節.若是設置的max_depth是一個比較大的值,你能夠發現cvFindCountours()返回的輪廓是經過h_next鏈接被遍歷.對於其餘一些拓撲結構(CV_RETR_TREE,CV_REER_CCOMP等),你會發現有些輪廓被畫過不僅一次
例8-3 在輸入圖像上尋找並繪製輪廓
當咱們繪製一個多邊形或者進行形狀分析的時候,一般須要使用多邊形畢竟一個輪廓,使頂點數目變少.有多種方法能夠實現這個功能,OpenCV實現了其中的一種逼近算法.函數cvApproxPoly是該算法的一種實現,能夠處理輪廓的序列.
由於cvApproxPoly在返回結果的時候須要建立新的對象,所以 須要指定一個內存存儲器以及頭結構大小.(通常爲sizeof(CvContour)).
逼急算法目前只可以使用CV_POLY_APPROx_DP.另外兩個參數爲逼近算法參數(目前只用到第一個).eps參數指定逼近的精度.若是想了解這個參數如何起做用的的必須仔細瞭解具體的算法.最後一個參數指定是否針對所有的輪廓(經過h_next和v_next可達的)進行逼近
若是爲0,則表示只處理src_seq指向輪廓.
下面簡要介紹一下算法的工做原理.參考圖8-5,算法先從輪廓(圖b)選擇2個最遠的點,而後將2個連成一個線段(圖c),而後再查找輪廓上到線段距離最遠的點,添加到逼近後的心輪廓(圖d).算法反覆迭代,不斷將最遠點的添加到結果中.直到全部點的點到多邊形的最短距離小於eps參數指定的精度(圖f).從這裏能夠看出,精度和輪廓的周長,或者外包矩形周長的幾分之一比較合適.
曲線逼近的過程和尋找關掉點的過程密切相關。跟曲線上的其餘點相比,關鍵點是那些包含曲線信息比較多的點。關鍵點在逼近算法以及其餘應用中都會涉及。函數cvFindDominantPoints()實現了被稱爲IPAN*[Chetvreikov99]的算法.
CvSeq cvFindDominantPoints(CvSeq* contour,CvMemStorage* storage,int metod = CV_DOMINANT_IPAN,double parameter1 = 0,double parameter2 = 0,double parameter3 = 0,double parameter4 = 0);
本質上,IPAN算法經過掃描輪廓上並在曲線內部使用可能頂點構造三角形來實現.對於三角形的大小和張角有特殊要求.在此某一特定的全局閾值和它的相鄰的張角小的狀況下,具備大張角的點被保留.
函數cvFindDominantPoints()按照慣例使用參數CvSeq* 和CvMemStorage* .而且要求指定一個方法,和cvApproxPoly()相同,目前可供選擇的方法只有一個,就是CV_DOMINANT_IPAN.
接下來四個參數是:最短距離dmin,最長距離dmax,相鄰距離dn和最大角度θmax.如圖8-6所示,算法首先把全部兩邊距離rpa和rpb在dmin和dmax之間,θab < θmax的三角形找出來.而後保留對於距離dn(dn的大小不得超過dmax)有最小夾角θab的全部點p.dmin,dmax,dn和θmax典型值能夠是7,9,9,150(最後一個參數是以度數爲單位的角大小).
輪廓處理中常常遇到的另外一個任務是計算一些輪廓變化的歸納特性.這可能包括長度或者其餘一些反映輪廓總體大小的度量.另外一個有用的特性是輪廓的輪廓矩(contourmoment),能夠用來歸納輪廓的總形狀特性
函數cvContourPerimeter()做用於一個輪廓並返回其長度.事實上,此函數是一個調用函數cvArcLength()的宏.
一個和cvArcLength()有緊密關係的函數是cvContourArea(),如其名稱所示,這個函數同於計算輪廓的面積.函數的參數contour和slice和cvArcLength()同樣.
固然長度和麪積只是輪廓的簡單特性,更復雜一些的特性描述應該是矩形邊界框,圓形邊界框或橢圓形邊界框.有兩種方法能夠獲得矩形邊界框,圓形與橢圓形編輯框各只有一種方法.
cvBoundingRect()獲得的長方形的一個問題是,cvRect只能表現一個四邊水平和豎直的長方形.然而函數cvMinAreaRect2()能夠返回一個包圍輪廓最小的長方形,這個長方形多是傾斜的;請看圖8-7,該函數的參數和cvBoundingRect()的類似.opencv的數據類型CvBox2D就是用來表述這樣的長方形狀的.
接着咱們來看函數cvMinEnclosingCircle().該函數和矩形邊界框的做用基本相同,輸入一樣很靈活,能夠是點的序列,也能夠是二維點的數組.
與最小包圍圓同樣,OpenCV提供一函數來擬合一組點,以獲取最佳擬合橢圓
橢圓的擬合結果由CvBox2D結構體返回,給出的矩形正好徹底包圍橢圓,如圖8-8所示.
在處理CvBox2D或多邊形邊界的時候,常常須要進行多邊形以及邊界框的重疊判斷.OpenCV提供了一組方便的小函數用於此類測試.
下一個使用函數cvBoxPoints()用於計算CvBox2D結構表示矩形的4個頂點.固然你也能夠本身經過三角函數計算,不過這使人頭大,而簡單調用一下這個函數則可求出.
第三實用函數cvPointSeqFromMat()從mat中初始化序列.這在你須要使用輪廓相關的函數,可是函數又不支持矩陣參數的時候使用.第一個參數用於指定點序列類型,seq_kind能夠爲如下類型:點集爲0;曲線爲CV_SEQ_KIND_CURVE;封閉曲線爲CV_SEQ_KIND_CURVE|Cv_SEQ_FLAG_CLOSED.第二個參數是輸入的矩陣,該參數是連續的1維向量.矩陣類型必須爲cv_32C2或CV_32FC2.
下面的兩個參數是指針,指針指向的內容經過該函數填充.contour_header參數對應輪廓結構,通常要事先建立,不過由該函數負責初始化.block參數一樣如此,也是由該函數複雜初始化.最後,該函數放回一個類型爲CvSeq*的序列指針,指向你輸入的序列頭*contour_header.返回值跟輸入參數相同只是爲了使用該函數時更方便,由於這樣你就能夠將該函數看成某個輪廓函數的參數使用,代碼寫入同一行.
最後一個平面幾個相關的函數是cvPointPolygonTest(),用於測試一個點是否在多邊形的內部.若是參數measure_dist非零,函數返回值是點到多邊形最近距離.若是measure_dist爲0,函數返回+1,-1,0,分別表示在內部,外部,在多邊形邊上.參數contour能夠是序列,也能夠是2通道矩陣向量.