OpenCV圖像的輪廓
查找輪廓
輪廓到底是什麽?一個輪廓一般對應一系列的點,也就是圖像中的一條曲線.表示的方法可能根據不同情況而有所不同.有多重方法可以表示曲線.在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()函數可區分內部和外部邊界.
包含的概念在很多應用中都非常重要.因此.OpenCV允許得到的輪廓被聚合成一個輪廓樹,從而把包含關系編碼到樹結構中.這個測試圖的輪廓樹在根節點的輪廓叫c0,孔h00和h01是它的字子節點.這些輪廓中直接包含輪廓稱為他們的子節點,以此類推.
現在來看cvFindContours()函數
int cvFindContours( CvArr* image, CvMemStorage* storage, CvSeq** first_contour,
int header_size CV_DEFAULT(sizeof(CvContour)),
int mode CV_DEFAULT(CV_RETR_LIST),
int method CV_DEFAULT(CV_CHAIN_APPROX_SIMPLE) ,
CvPoint offset CV_DEFAULT(cvPoint(0,0)));
第一個參數 image是輸入圖像,圖像必須是8位單通道圖像,並且應該被轉化成二值的(例如,所有非0像素的值都是一個定值).cvFindContours()運行的時候,這個圖像會被直接塗改,因此如果是將來還有用的圖像,應該復制之後再傳給cvFindContours().
storage 是內存存儲器,cvFindContours()找到的輪廓記錄在此內存裏.正如之前所說,這個存儲器的空間應該由cvCreateMemStorage()分配.
first_contour 是指向CvSeq*的一個指針firstContour.無需動手,cvFindContours()會自動分配該指針.實際上,只要在這裏傳一個指針就可以了函數會自動設置.不需要分配和釋放(new/delete或者malloc/free).就是這個指針(例如,*firstContour)指向輪廓樹的首地址(head).cvFindContours()返回值是,找到的所有輪廓的個數
cvSeq* firstContout = NULL;
cvFindContours(..., &firstContour, ...);
headerSize告訴cvFindContours()更多有關對象分配的信息,它可以被設定為sizeof(CvContour)或者sizeof(CvChain)(當近似方法參數method被設定為CV_ChAIN_CODE時使用後者).最後是mode和method參數,他們分別指定計算方法和如何計算.
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參數.
序列中保存一系列的點,這些點構成輪廓,輪廓是本章的重點.輪廓只是序列所能表示物體的一種.輪廓的點的序列,可以用來表示圖像空間中的曲線.這種點的序列很常用,所有需要有專門的函數來幫助我們對他進行處理.下面是一組這樣的處理函數.
int cvFindContours( CvArr* image, CvMemStorage* storage, CvSeq** first_contour,
int header_size CV_DEFAULT(sizeof(CvContour)),
int mode CV_DEFAULT(CV_RETR_LIST),
int method CV_DEFAULT(CV_CHAIN_APPROX_SIMPLE),
CvPoint offset CV_DEFAULT(cvPoint(0,0)));
CvContourScanner cvStartFindContours( CvArr* image, CvMemStorage* storage,
int header_size CV_DEFAULT(sizeof(CvContour)),
int mode CV_DEFAULT(CV_RETR_LIST),
int method CV_DEFAULT(CV_CHAIN_APPROX_SIMPLE),
CvPoint offset CV_DEFAULT(cvPoint(0,0)));
CvSeq* cvFindNextContour( CvContourScanner scanner );
void cvSubstituteContour( CvContourScanner scanner, CvSeq* new_contour );
/* Releases contour scanner and returns pointer to the first outer contour */
CvSeq* cvEndFindContours( CvContourScanner* scanner );
/* Approximates a single Freeman chain or a tree of chains to polygonal curves */
CvSeq* cvApproxChains( CvSeq* src_seq, CvMemStorage* storage,
int method CV_DEFAULT(CV_CHAIN_APPROX_SIMPLE),
double parameter CV_DEFAULT(0),
int minimal_perimeter CV_DEFAULT(0),
int recursive CV_DEFAULT(0));
第一函數是cvFindContours(),在前面已經提到.接著是cvStartFindContiors()函數,它和cvFin的Contours()功能類似.但是cvStartFindContours()每次放回一個輪廓,而不像cvFinContours()那樣一次查找所有輪廓然後統一返回.調用cvStartFindContours()函數後,返回一個CvSequenceScanner結構.CvSequenceScanner結構中包含一些狀態信息,這些信息不可讀.你可以通過在cvSequenceScabber結構上依次調用cvFinNextContour()來查找剩余的輪廓.當全部輪廓都被找完之後,cvFindNextContour()將放回NULL
cvSubstituteContour()函數用於替換scanner指向的輪廓.該函數的一個特性是,如果參數 new_contour為NULL,那麽當前的輪廓將被從Scanner指定的樹或鏈表中刪除(受影響的序列會作適當更新,來保證不會有指針指向不存在的物體).
函數cvEndFindContour()結束輪廓查找,並且將scanner設置為結束狀態.註意,scanner並沒有被刪除,實際上該函數返回的是指針所指序列的第一個元素.
最後一個函數cvApproxChains()函數.該函數將Freeman鏈轉換為多邊形表示(精確轉換或者近似擬合).
Freeman鏈碼
一般情況下,通過cvFindCountours獲取的輪廓是一系列頂點的序列.另一種不同的表達是設置method參數為CV_CHAIN_CODE,然後生成輪廓.當選者CV_CHAIN_CODE標誌的時候,檢測的輪廓通過Freemain鏈碼[Freeman67](圖8-4)的方式返回.在Freeman鏈碼中,多邊形被表示為一系列的位移,每一個位移有8個方向,這8個方向使用整數0到7表示.Freeman鏈碼對於識別一些形狀的物體很有幫助.如果得到的是Freeman鏈碼,可以通過以下兩個函數讀出每個點
void cvStartReadChainPoints( CvChain* chain, CvChainPtReader* reader );
CvPoint cvReadChainPoint( CvChainPtReader* reader );
第一個函數用來初始化Freeman鏈CvChainPtReader結構,第二個函數通過CvChainptReader來讀每個點,CvChainPtReader對應當前狀態.結構CvChain從CvSeq擴展得來.和CvContourScanner從多個輪廓間叠代一樣,CvChainPtReader用於叠代一個使用Freemain鏈碼表示輪廓中的每個點.CvChainPtReader和CvSeqReader的用法類似.如您所期望,當所有點都讀完後,返回CvChainPtReader值為NULL.
繪制輪廓
一個經常使用的功能是在屏幕上繪制檢測到的輪廓.繪制可以用cvDrawContours函數完成
/* Draws contour outlines or filled interiors on the image */
void cvDrawContours( CvArr *img, CvSeq* contour,
CvScalar external_color, CvScalar hole_color,
int max_level, int thickness CV_DEFAULT(1),
int line_type CV_DEFAULT(8),
CvPoint offset CV_DEFAULT(cvPoint(0,0)));
第一個參數為要繪制輪廓的圖像.第二個參數是要繪制的輪廓,他不像乍看上去那麽簡單,他是輪廓樹的根節點.其他的參數(主要是max_level)將會控制如何繪制輪廓樹.下一個參數很容易理解,是繪制輪廓所用的顏色.但是hole_color那?請回憶輪廓的分類,有外輪廓,也有"洞"(圖8-2中的虛劃線和點線).無論繪制單個輪廓還是輪廓樹中的所有輪廓,標記為"洞"的輪廓都會使用hole_color指定的顏色繪制.
通過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)用於設置閾值,然後對采二值化後的圖像提取輪廓並繪制輪廓.當控制參數的滑動條變換時,圖像被更新.
#include "stdafx.h"
#include <cv.h>
#include <highgui.h>
IplImage* g_image = NULL;
IplImage* g_gray = NULL;
int g_thresh = 100;
CvMemStorage* g_storage = nullptr;
void on_trackbar(int)
{
if (g_storage == nullptr)
{
g_gray = cvCreateImage(cvGetSize(g_image),8,1);
g_storage = cvCreateMemStorage(0);
}
else
{
cvClearMemStorage(g_storage);
}
CvSeq* contours = NULL;
cvCvtColor(g_image,g_gray,CV_BGR2GRAY);
cvThreshold(g_gray,g_gray,g_thresh,255,CV_THRESH_BINARY);
cvFindContours(g_gray,g_storage,&contours);
cvZero(g_gray);
if (contours)
{
cvDrawContours(g_gray,contours,cvScalarAll(255),cvScalarAll(255),100);
}
cvShowImage("Contours",g_gray);
}
int _tmain(int argc, _TCHAR* argv[])
{
g_image = cvLoadImage("C:\\Users\\chenchao\\Desktop\\細胞圖象\\正常的紅細胞\\5.bmp");
cvNamedWindow("Contours",1);
cvCreateTrackbar("Threshold","Contours",&g_thresh,300,on_trackbar);
on_trackbar(0);
cvWaitKey(0);
printf("HELLO");
return 0;
}
如果全局參數g_storage為NULL的話,則用cvCreateMemSotrage(0)創建一個內存存儲器.g_gray被初始化為和g_image同樣大小的黑色圖像,但是為單通道圖像.如果g_storage非空的話,則先用cvClearMemStorage清空內存存儲器的中間,這樣以便重復利用內存存儲器中的資源.然後創建一個CvSeq*指針,該指針用來保存cvFindCountours()檢測到的輪廓.
然後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 在輸入圖像上尋找並繪制輪廓
int _tmain(int argc, _TCHAR* argv[])
{
cvNamedWindow("src");
IplImage* img_8uc1 = cvLoadImage("C:\\Users\\chenchao\\Desktop\\細胞圖象\\正常的紅細胞\\5.bmp",0);
IplImage* img_edge = cvCreateImage(cvGetSize(img_8uc1),8,1);
IplImage* img_8uc3 = cvCreateImage(cvGetSize(img_8uc1),8,3);
cvThreshold(img_8uc1,img_edge,128,255,CV_THRESH_BINARY);
CvMemStorage* storage = cvCreateMemStorage(0);
CvSeq* first_contour = nullptr;
int Nc = cvFindContours(img_edge,storage,&first_contour,sizeof(CvContour),CV_RETR_LIST);
int n = 0;
printf("Total Contours Detected : %d \n",Nc);
for (CvSeq* c = first_contour; c!= NULL; c= c->h_next)
{
cvCvtColor(img_edge,img_8uc3,CV_GRAY2BGR);
cvDrawContours(img_8uc3,c,cvScalar(0,255,0),cvScalar(0,0,255),0,2,8);
printf("contours #%d\n",n);
cvShowImage("src",img_8uc3);
printf(" %d elements: \n",c->total);
for (int i=0 ; i<c->total; ++i)
{
CvPoint* p = CV_GET_SEQ_ELEM(CvPoint,c,i);
printf(" (%d,%d) \n",p->x,p->y);
}
cvWaitKey(0);
n++;
}
printf("Finished all contours.\n");
cvCvtColor(img_8uc1,img_8uc3,CV_GRAY2BGR);
cvShowImage("src",img_8uc3);
cvWaitKey(0);
cvDestroyWindow("src");
cvReleaseImage(&img_8uc1);
cvReleaseImage(&img_8uc3);
cvReleaseImage(&img_edge);
return 0;
}
深入分析輪廓
多邊形逼近
當我們繪制一個多邊形或者進行形狀分析的時候,通常需要使用多邊形畢竟一個輪廓,使頂點數目變少.有多種方法可以實現這個功能,OpenCV實現了其中的一種逼近算法.函數cvApproxPoly是該算法的一種實現,可以處理輪廓的序列.
(CvSeq*) cvApproxPoly( const void* src_seq,
int header_size, CvMemStorage* storage,
int method, double eps,
int recursive CV_DEFAULT(0));
我們可以傳遞一個列表或者數狀序列給cvApproxPoly,然後對其表示的輪廓進行處理.函數返回值對應第一個輪廓,同樣我們可用通過h_next(以及v_next)來訪問返回其他的輪廓.
因為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()的宏.
CVAPI(double) cvArcLength( const void* curve,
CvSlice slice CV_DEFAULT(CV_WHOLE_SEQ),
int is_closed CV_DEFAULT(-1));
CV_INLINE double cvContourPerimeter( const void* contour )
{
return cvArcLength( contour, CV_WHOLE_SEQ, 1 );
}
cvArcLength()的第一參數是輪廓,其形式可以是點的序列(CvContour*或CvSeq*)或任一n×2的點的數組.後邊的參數是slice,以及表明是否將輪廓視為閉合的一個布爾類型(例如,是否將輪廓的最後一個點視為和第一個點有連接).slice可以讓我們只選擇曲線上的點的部分集合.
一個和cvArcLength()有緊密關系的函數是cvContourArea(),如其名稱所示,這個函數同於計算輪廓的面積.函數的參數contour和slice和cvArcLength()一樣.
CVAPI(double) cvContourArea( const CvArr* contour,
CvSlice slice CV_DEFAULT(CV_WHOLE_SEQ),
int oriented CV_DEFAULT(0));
邊界框
當然長度和面積只是輪廓的簡單特性,更復雜一些的特性描述應該是矩形邊界框,圓形邊界框或橢圓形邊界框.有兩種方法可以得到矩形邊界框,圓形與橢圓形編輯框各只有一種方法.
CVAPI(CvRect) cvBoundingRect( CvArr* points, int update CV_DEFAULT(0) );
CVAPI(CvBox2D) cvMinAreaRect2(const CvArr* points,CvMemStorage* storage CV_DEFAULT(NULL));
最簡單的方法是調用函數cvBoundingRect();它將放回一個包圍輪廓的CvRect.第一個參數points可以是由點組成的序列,一個輪廓(CvContour*)或者一個n×1雙通道的矩陣(CvMat*).為了理解第二個參數update,我們需要想想前面的描述,當時說CvContour並不完全等於CvSeq;CvSeq能實現的CvContour都可以實現,CvContour甚至能做的更多一點.其中一個附加功能就是CvRect成員可以記載輪廓自己的邊界框.如果調用函數cvBoundingRect()時參數update設置為0,便可以直接從CvCoutour的成員中獲取邊界框;如果將uodate設置為1,邊界框便會被計算出(CvContour成員的內容也會被更新).
cvBoundingRect()得到的長方形的一個問題是,cvRect只能表現一個四邊水平和豎直的長方形.然而函數cvMinAreaRect2()可以返回一個包圍輪廓最小的長方形,這個長方形可能是傾斜的;請看圖8-7,該函數的參數和cvBoundingRect()的相似.opencv的數據類型CvBox2D就是用來表述這樣的長方形狀的.
typedef struct CvBox2D
{
CvPoint2D32f center; /* Center of the box. */
CvSize2D32f size; /* Box width and length. */
float angle; /* Angle between the horizontal axis */
/* and the first side (i.e. length) in degrees */
}
CvBox2D;
圓形和橢圓形邊界
接著我們來看函數cvMinEnclosingCircle().該函數和矩形邊界框的作用基本相同,輸入同樣很靈活,可以是點的序列,也可以是二維點的數組.
CVAPI(int) cvMinEnclosingCircle(const CvArr* points,CvPoint2D32f* center, float* radius);
OpenCV裏沒有專門用來表示圓的結構,因此需要給函數cvMinEnclosingCircle()傳遞中心和浮點型半徑的兩個指針來獲取計算結果.
與最小包圍圓一樣,OpenCV提供一函數來擬合一組點,以獲取最佳擬合橢圓
CVAPI(CvBox2D) cvFitEllipse2( const CvArr* points );
cvMinEnclosingCircle()和cvFitEllipse2()的細微差別在於,前者只簡單計算完全包圍已有輪廓的最小圓,而後者使用擬合函數返回一個與輪廓最相近似的橢圓.這意味著並不是輪廓中所有的點都會被包在cvFitEllipse2()返回的橢圓中.該擬合由最小二乘擬合方法算出.
橢圓的擬合結果由CvBox2D結構體返回,給出的矩形正好完全包圍橢圓,如圖8-8所示.
幾何
在處理CvBox2D或多邊形邊界的時候,經常需要進行多邊形以及邊界框的重疊判斷.OpenCV提供了一組方便的小函數用於此類測試.
CVAPI(CvRect) cvMaxRect( const CvRect* rect1, const CvRect* rect2 );
CVAPI(void) cvBoxPoints( CvBox2D box, CvPoint2D32f pt[4] );
/* Initializes sequence header for a matrix (column or row vector) of points -
a wrapper for cvMakeSeqHeaderForArray (it does not initialize bounding rectangle!!!) */
CVAPI(CvSeq*) cvPointSeqFromMat( int seq_kind, const CvArr* mat,
CvContour* contour_header,
CvSeqBlock* block );
/* Checks whether the point is inside polygon, outside, on an edge (at a vertex).
Returns positive, negative or zero value, correspondingly.
Optionally, measures a signed distance between
the point and the nearest polygon edge (measure_dist=1) */
CVAPI(double) cvPointPolygonTest( const CvArr* contour,
CvPoint2D32f pt, int measure_dist );
第一個函數cvMaxRect()根據輸入的2個矩形計算,他們的最小外包矩形.
下一個使用函數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通道矩陣向量.
再分享一下我老師大神的人工智能教程吧。零基礎!通俗易懂!風趣幽默!還帶黃段子!希望你也加入到我們人工智能的隊伍中來!https://www.cnblogs.com/captainbed
OpenCV圖像的輪廓