OpenCV_輪廓的查找、表達、繪製、特性及匹配

雖然Canny之類的邊緣檢測算法可以根據像素間的差異檢測出輪廓邊界的像素,但是它並沒有將輪廓作爲一個整體。下一步是要將這些邊緣像素組裝成輪廓。

輪廓是構成任何一個形狀的邊界或外形線。直方圖對比和模板匹配根據色彩及色彩的分佈來進行匹配,以下包括:輪廓的查找、表達方式、組織方式、繪製、特性、匹配。

首先回憶下幾個結構體:

首先是圖像本身的結構體:
typedef struct CvMat
{
int type; /* CvMat 標識 (CV_MAT_MAGIC_VAL), 元素類型和標記 */
int step; /* 以字節爲單位的行數據長度*/
int* refcount; /* 數據引用計數 */
union
{
uchar* ptr;
short* s;
int* i;
float* fl;
double* db;
} data;
union
{
int rows;
int height;
};
union
{
int cols;
int width;
};
這個結構體是最基礎的矩陣,而圖像本身就是一個複雜的矩陣,所以圖像是對這個結構體的繼承:
typedef struct _IplImage
{
int nSize; /* IplImage大小 */
int ID; /* 版本 (=0)*/
int nChannels; /* 大多數OPENCV函數支持1,2,3 或 4 個通道 */
int alphaChannel; /* 被OpenCV忽略 */
int depth; /* 像素的位深度: IPL_DEPTH_8U, IPL_DEPTH_8S, IPL_DEPTH_16U,
IPL_DEPTH_16S, IPL_DEPTH_32S, IPL_DEPTH_32F and IPL_DEPTH_64F 可支持 */
char colorModel[4]; /* 被OpenCV忽略 */
char channelSeq[4]; /* 同上 */
int dataOrder; /* 0 - 交叉存取顏色通道, 1 - 分開的顏色通道.
cvCreateImage只能創建交叉存取圖像 */
int origin; /* 0 - 頂—左結構,1 - 底—左結構 (Windows bitmaps 風格) */
int align; /* 圖像行排列 (4 or 8). OpenCV 忽略它,使用 widthStep 代替 */
int width; /* 圖像寬像素數 */
int height; /* 圖像高像素數*/
struct _IplROI *roi;/* 圖像感興趣區域. 當該值非空只對該區域進行處理 */
struct _IplImage *maskROI; /* 在 OpenCV中必須置NULL */
void *imageId; /* 同上*/
struct _IplTileInfo *tileInfo; /*同上*/
int imageSize; /* 圖像數據大小(在交叉存取格式下imageSize=image->height*image->widthStep),單位字節*/
char *imageData; /* 指向排列的圖像數據 */
int widthStep; /* 排列的圖像行大小,以字節爲單位 */
int BorderMode[4]; /* 邊際結束模式, 被OpenCV忽略 */
int BorderConst[4]; /* 同上 */
char *imageDataOrigin; /* 指針指向一個不同的圖像數據結構(不是必須排列的),是爲了糾正圖像內存分配準備的 */
}IplImage;
值得注意的地方:首先是origin這個,當有些圖像複製或者視頻播放時候,由於原點座標位置未定,很容造成圖片倒置。這時就得用void cvFlip( const CvArr* src, CvArr* dst=NULL, int flip_mode=0)函數或者直接設定origin來改變座標原點;widthstep就是CvMat的step;
構造方法:IplImage* cvCreateImage( CvSize size, int depth, int channels );
直方圖結構:
typedef struct CvHistogram
{
int type;
CvArr* bins;
float thresh[CV_MAX_DIM][2]; /* 對於標準直方圖,bins的值有左邊界+右邊界=2 */
float** thresh2; /* 對於非標準直方圖 */
CvMatND mat; /* embedded matrix header for array histograms */
}CvHistogram;

因此,由於直方圖的複雜性,得到一個圖片的直方圖的步驟就不是一個函數完成的:
1,分割圖片通道
2,求出bins數量及範圍
3,CvHistogram* cvCreateHist( int dims, int* sizes, int type,float** ranges=NULL, int uniform=1 );
創建直方圖
4,void cvCalcHist( IplImage** image, CvHistogram* hist,int accumulate=0, const CvArr* mask=NULL );
計算直方圖

下面開始輪廓的學習。

查找輪廓
    首先是如何在圖像中找到輪廓,可以利用OpenCV提供的方法cvFindContours()可以很方便的查找輪廓。

cvFindContours()方法從二值圖像中尋找輪廓。因此此方法處理的圖像可以是從cvCanny()函數得到的有邊緣像素的圖像,或者從cvThreshold()及cvAdaptiveThreshold()得到的圖像,這時的邊緣是正和負區域之間的邊界。

既然在查找之前,我們需要將彩色圖像轉換成灰度圖像,然後再將灰度圖像轉換成二值圖像。代碼如下所示:

1  CvSeq *contours = 0;
2  cvCvtColor(src,dst,CV_BGR2GRAY);//將源圖像進行灰度化
3  cvThreshold(dst,dst,f_thresh,255,CV_THRESH_BINARY);//二值化閾值 雖然第一個參數是const,但仍可以更改dst
4  cvFindContours(dst,f_storage,&contours); //查找輪廓
5  cvZero(dst);

 

輪廓的表達方式
    使用上面的代碼可以得到圖像的默認輪廓,但是輪廓在電腦中是如何表達的呢?在OpenCv中提供了兩類表達輪廓的方式:頂點的序列、Freeman鏈碼。

 

首先介紹下內存存儲器的概念,這是OpenCV在創建動態對象時存取內存的技術。

 

CvMemStorage* cvCreateMemStorage( int block_size=0 );//創建默認值大小的內存空間
void cvReleaseMemStorage( CvMemStorage** storage );//釋放內存空間
void cvClearMemStorage( CvMemStorage* storage );//清空內存塊,可以用於重複使用,將內存返還給存儲器,而不是返回給系統
void *cvMemStorageAlloc(CvMemStorage *storage,size_t size);//開闢內存空間

 

序列

 

序列是內存存儲器中可以存儲的一種對象。序列是某種結構的鏈表。序列在內存中被實現爲一個雙端隊列,因此序列可以實習快速的隨機訪問,以及快速刪除頂端的元素,但是從中間刪除元素則稍慢些。

 

序列結構:
CvSeq
可動態增長元素序列(OpenCV_1.0已發生改變,詳見cxtypes.h) Growable sequence of elements

 

#define CV_SEQUENCE_FIELDS() /
int flags; /* micsellaneous flags */ /
int header_size; /* size of sequence header */ /
struct CvSeq* h_prev; /* previous sequence */ /
struct CvSeq* h_next; /* next sequence */ /
struct CvSeq* v_prev; /* 2nd previous sequence */ /
struct CvSeq* v_next; /* 2nd next sequence */ /
int total; /* total number of elements */ /
int elem_size;/* size of sequence element in bytes */ /
char* block_max;/* maximal bound of the last block */ /
char* ptr; /* current write pointer */ /
int delta_elems; /* how many elements allocated when the sequence grows (sequence granularity) */ /
CvMemStorage* storage; /* where the seq is stored */ /
CvSeqBlock* free_blocks; /* free blocks list */ /
CvSeqBlock* first; /* pointer to the first sequence block */
typedef struct CvSeq
{
CV_SEQUENCE_FIELDS()
} CvSeq;
相關操作就不重複列出(排序,查找,逆序,拆分,複製,讀取,寫入切片的複製,移除,插入,),可以查找相關文檔。

 

1.頂點的序列
    用多個頂點(或各點間的線段)來表達輪廓。假設要表達一個從(0,0)到(2,2)的矩形,
(1)如果用點來表示,那麼依次存儲的可能是:(0,0),(1,0),(2,0),(2,1),(2,2),(1,2),(0,2),(0,1);
(2)如果用點間的線段來表達輪廓,那麼依次存儲的可能是:(0,0),(2,0),(2,2),(0,2)。
 2.Freeman鏈碼
    Freeman鏈碼需要一個起點,以及從起點出發的一系列位移。每個位移有8個方向,從0~7分別指向從正北開始的8個方向。假設要用Freeman鏈碼錶達從(0,0)到(2,2)的矩形,可能的表示方法是:起點(0,0),方向鏈2,2,4,4,6,6,0,0。
輪廓之間的組織方式
    在查找到輪廓之後,不同輪廓是怎麼組織的呢?根據不同的選擇,它們可能是:(1)列表;(2)雙層結構;(3)樹型結構。
    從縱向上來看,列表只有一層,雙層結構有一或者兩層,樹型結構可能有一層或者多層。
    如果要遍歷所有的輪廓,可以使用遞歸的方式。

輪廓的繪製
    輪廓的繪製比較簡單,用上面提到的方法取得輪廓的所有點,然後把這些點連接成一個多邊形即可。

輪廓的一個例子爲:OpenCV_輪廓例子

上例中檢測出輸入圖像的輪廓,然後逐個繪製每個輪廓。下個例子爲:

在輸入圖像上尋找並繪製輪廓

具體代碼爲:

1 #include "stdafx.h"
2 #include <iostream>
3 using namespace std;
4
5
6 #ifdef _CH_
7 #pragma package <opencv>
8 #endif
9
10 #include "cv.h"
11 #include "highgui.h"
12
13 using namespace std;
14
15 int _tmain(int argc, _TCHAR* argv[])
16 {
17     cvNamedWindow("flower",1);
18
19     IplImage* img_8uc1 = cvLoadImage("flower.jpg",CV_LOAD_IMAGE_GRAYSCALE);
20     IplImage* img_edge = cvCreateImage(cvGetSize(img_8uc1),8,1);
21     IplImage* img_8uc3 = cvCreateImage(cvGetSize(img_8uc1),8,3);
22
23     cvThreshold(img_8uc1,img_edge,128,255,CV_THRESH_BINARY);
24
25     CvMemStorage* storage = cvCreateMemStorage();
26     CvSeq* first_contour = NULL;
27
28     int Nc = cvFindContours(
29         img_edge,
30         storage,
31         &first_contour,
32         sizeof(CvContour),
33         CV_RETR_LIST
34         );
35
36     int n=0;
37     printf("Total contours detected %d \n",Nc);
38
39     for (CvSeq* c=first_contour;c!=NULL;c=c->h_next)
40     {
41         cvCvtColor(img_8uc1,img_8uc3,CV_GRAY2BGR);
42
43         cvDrawContours(
44             img_8uc3,
45             c,
46             CV_RGB(0,0,255),
47             CV_RGB(0,255,0),
48             0,
49             2,
50             8
51             );
52
53         printf("Contour # %d\n",n);
54         cvShowImage("flower",img_8uc3);
55         printf("%d elements:\n",c->total);
56
57         for (int i=0;i<c->total;++i)
58         {
59             CvPoint* p = CV_GET_SEQ_ELEM(CvPoint,c,i);
60             printf("(%d,%d)\n",p->x,p->y);
61         }
62         cvWaitKey(0);
63         n++;
64     }
65
66     printf("Finished all contours.\n");
67
68     cvCvtColor(img_8uc1,img_8uc3,CV_GRAY2BGR);
69     cvShowImage("flower",img_8uc3);
70
71     cvWaitKey(0);
72
73     cvDestroyWindow("flower");
74     cvReleaseImage(&img_8uc3);
75     cvReleaseImage(&img_8uc1);
76     cvReleaseImage(&img_edge);
77     return0;
78 }

輪廓的特性
    輪廓的特性有很多,下面一一介紹。

1.輪廓的多邊形逼近
    輪廓的多邊形逼近指的是:使用多邊形來近似表示一個輪廓。
    多邊形逼近的目的是爲了減少輪廓的頂點數目。
    多邊形逼近的結果依然是一個輪廓,只是這個輪廓相對要粗曠一些。
   可以使用方法cvApproxPoly()

2.輪廓的關鍵點
    輪廓的關鍵點是:輪廓上包含曲線信息比較多的點。關鍵點是輪廓頂點的子集。
    可以使用cvFindDominantPoints函數來獲取輪廓上的關鍵點,該函數返回的結果一個包含 關鍵點在輪廓頂點中索引 的序列。再次強調:是索引,不是具體的點。如果要得到關鍵點的具體座標,可以用索引到輪廓上去找。
3.輪廓的周長和麪積
    輪廓的周長可以用cvContourPerimeter或者cvArcLength函數來獲取。
    輪廓的面積可以用cvContourArea函數來獲取。

4.輪廓的邊界框
    有三種常見的邊界框:矩形、圓形、橢圓。
    (1)矩形:在圖像處理系統中提供了一種叫Rectangle的矩形,不過它只能表達邊垂直或水平的特例;OpenCv中還有一種叫Box的矩形,它跟數學上的矩形一致,只要4個角是直角即可。
    如果要獲取輪廓的Rectangle,可以使用cvBoundingRect函數。
    如果要獲取輪廓的Box,可以使用cvMinAreaRect2函數。
    (2)圓形
    如果要獲取輪廓的圓形邊界框,可以使用cvMinEnclosingCircle函數。
    (3)橢圓
    如果要獲取輪廓的橢圓邊界框,可以使用cvFitEllipse2函數。

5.輪廓的矩

    矩是通過對輪廓上所有點進行積分運算(或者認爲是求和運算)而得到的一個粗略特徵。

在連續情況下,圖像函數爲 f(x,y),那麼圖像的p+q階幾何矩(標準矩)定義爲:

p ,q = 0,1,2…… 

p+q階中心距定義爲:

 p,q = 0,1,2……

 

 

其中代表圖像的重心,

 

,

 

對於離散的數字圖像,採用求和號代替積分:

 

,,p,q = 0,1,2 ……

 

N和M分別是圖像的高度和寬度;

歸一化的中心距定義爲:;其中

在公式中,p對應x維度上的矩,q對應y維度上的矩,階數表示對應的部分的指數。該計算是對輪廓界上所有像素(數目爲n)進行求和。如果p和q全部爲0,那麼m00實際上對應輪廓邊界上點的數目。

雖然可以直接計算出輪廓的矩,但是經常會用到歸一化的矩(因此不同大小但是形狀相同的物體會有相同的值)。同樣,簡單的矩依賴於所選座標系,這意味着物體旋轉後就無法正確匹配。

於是就產生了Hu矩以及其他歸一化矩的函數。

Hu矩是歸一化中心矩的線性組合。之所以這樣做是爲了能夠獲取代表圖像某個特徵的矩函數。這些矩函數對縮放,旋轉和鏡像映射出了(h1)具有不變性。

Hu矩是從中心矩中計算得到。即七個由歸一化中心矩組合成的矩:  

 其中中心矩和歸一化中心矩的定義爲:

 

 

   我們可以使用cvContoursMoments函數、cvMoments函數方便的得到輪廓的矩集,然後再相應的方法或函數獲取各種矩。
    特定的矩:cvGetSpatialMoment函數
    中心矩:cvGetCentralMoment函數
    歸一化中心矩:cvGetNormalizedCentralMoment函數
    Hu矩:cvGetHuMoments函數
.輪廓的輪廓樹
    輪廓樹用來描述某個特定輪廓的內部特徵。注意:輪廓樹跟輪廓是一一對應的關係;輪廓樹不用於描述多個輪廓之間的層次關係。

    輪廓樹的創建過程:

    從一個輪廓創建一個輪廓樹是從底端(葉子節點)到頂端(根節點)的。首先搜索三角形突出或者凹陷的形狀的周邊(輪廓上的每一個點都不是完全和它的相鄰點共線的)每個這樣的三角形被一條線段代替,這條線段通過連接非相鄰點的兩點得到;因此實際上三角形或者被削平或者被填滿。每個這樣的替換都把輪廓的頂點減少,並且給輪廓樹創建一個新節點。如果這樣的一個三角形的兩側有原始邊,那麼她就是得到的輪廓樹的葉子;如果一側已是一個三角形,那麼它就是那個三角形的父節點。這個過程的迭代最終把物體的外形簡稱一個四邊形,這個四邊形也被剖開;得到的兩個三角形是根節點的兩個子節點。

結果的二分樹最終將原始輪廓的形狀性比編碼。每個節點被它所對應的三角形的信息所註釋。

這樣建立的輪廓樹並不太魯棒,因爲輪廓上小的改變也可能會徹底改變結果的樹,同時最初的三角形是任意選取的。爲了得到較好的描述需要首先使用函數cvApproxPoly()之後將輪廓排列(運用循環移動)成最初的三角形不怎麼收到旋轉影響的狀態。
    可以用函數cvCreateContourTree來構造輪廓樹。

 7.輪廓的凸包和凸缺陷
    輪廓的凸包和凸缺陷用於描述物體的外形。凸包和凸缺陷很容易獲得,不過我目前不知道它們到底怎麼使用。
    如果要判斷輪廓是否是凸的,可以用cvCheckContourConvexity函數。
    如果要獲取輪廓的凸包,可以用cvConvexHull2函數,返回的是包含頂點的序列。
    如果要獲取輪廓的凸缺陷,可以用cvConvexityDefects函數。
 8.輪廓的成對幾何直方圖
    成對幾何直方圖(pairwise geometrical histogram PGH)是鏈碼編碼直方圖(chain code histogram CCH)的一個擴展或者延伸。CCH是一種直方圖,用來統計一個輪廓的Freeman鏈碼編碼每一種走法的數字。這種直方圖的一個優良性質爲當物體旋轉45度,那麼新直方圖是老直方圖的循環平移。這樣就可以不受旋轉影響。

    (1)輪廓保存的是一系列的頂點,輪廓是由一系列線段組成的多邊形。對於看起來光滑的輪廓(例如圓),只是線段條數比較多,線段長度比較短而已。實際上,電腦中顯示的任何曲線都由線段組成。
    (2)每兩條線段之間都有一定的關係,包括它們(或者它們的延長線)之間的夾角,兩條線段的夾角範圍是:(0,180)。
    (3)每兩條線段上的點之間還有距離關係,包括最短(小)距離、最遠(大)距離,以及平均距離。最大距離我用了一個偷懶的計算方法,我把輪廓外界矩形的對角線長度看作了最大距離。
    (4)成對幾何直方圖所用的統計數據包括了夾角和距離。

輪廓的匹配
    如果要比較兩個物體,可供選擇的特徵很多。如果要判斷某個人的性別,可以根據他(她)頭髮的長短來判斷,這很直觀,在長髮男稀有的年代準確率也很高。也可以根據這個人尿尿的射程來判斷,如果射程大於0.50米,則是男性。總之,方法很多,不一而足。
    我們在上文中得到了輪廓的這麼多特徵,它們也可以用於進行匹配。典型的輪廓匹配方法有:Hu矩匹配、輪廓樹匹配、成對幾何直方圖匹配。
1.Hu矩匹配
    輪廓的Hu矩對包括縮放、旋轉和鏡像映射在內的變化具有不變性。cvMatchShapes函數可以很方便的實現對2個輪廓間的匹配。
2.輪廓樹匹配
    用樹的形式比較兩個輪廓。cvMatchContourTrees函數實現了輪廓樹的對比。
3.成對幾何直方圖匹配
    在得到輪廓的成對幾何直方圖之後,可以使用直方圖對比的方法來進行匹配。

輪廓匹配源碼1
複製代碼
/************************Hu矩匹配********************************************/    
//    IplImage* img_8uc1 = cvLoadImage("flower.jpg",CV_LOAD_IMAGE_GRAYSCALE);
//    IplImage* img_edge1 = cvCreateImage(cvGetSize(img_8uc1),8,1);
////    IplImage* img_8uc3 = cvCreateImage(cvGetSize(img_8uc1),8,3);
//
//    cvThreshold(img_8uc1,img_edge1,128,255,CV_THRESH_BINARY);
//
//
//    CvMemStorage* storage1 = cvCreateMemStorage();
//    CvSeq* first_contour1 = NULL;
//
//    int Nc = cvFindContours(
//        img_edge1,
//        storage1,
//        &first_contour1,
//        sizeof(CvContour),
//        CV_RETR_LIST
//        );
//
//    IplImage* img_8uc12 = cvLoadImage("flower1.jpg",CV_LOAD_IMAGE_GRAYSCALE);
//    IplImage* img_edge12 = cvCreateImage(cvGetSize(img_8uc12),8,1);
////    IplImage* img_8uc3 = cvCreateImage(cvGetSize(img_8uc1),8,3);
//
//    cvThreshold(img_8uc12,img_edge12,128,255,CV_THRESH_BINARY);
//
//
//    CvMemStorage* storage2 = cvCreateMemStorage();
//    CvSeq* first_contour2 = NULL;
//
//    int Nc2 = cvFindContours(
//        img_edge12,
//        storage2,
//        &first_contour2,
//        sizeof(CvContour),
//        CV_RETR_LIST
//        );
//
//    double n = cvMatchShapes(first_contour1,first_contour2,CV_CONTOURS_MATCH_I1,0);
//
//    printf("%d",n);
//
//    cvWaitKey();

/***************************輪廓樹匹配***********************************************/
    //    IplImage* img_8uc1 = cvLoadImage("flower.jpg",CV_LOAD_IMAGE_GRAYSCALE);
    //    IplImage* img_edge1 = cvCreateImage(cvGetSize(img_8uc1),8,1);
    ////    IplImage* img_8uc3 = cvCreateImage(cvGetSize(img_8uc1),8,3);
    //
    //    cvThreshold(img_8uc1,img_edge1,128,255,CV_THRESH_BINARY);
    //
    //
    //    CvMemStorage* storage1 = cvCreateMemStorage();
    //    CvSeq* first_contour1 = NULL;
    //
    //    int Nc = cvFindContours(
    //        img_edge1,
    //        storage1,
    //        &first_contour1,
    //        sizeof(CvContour),
    //        CV_RETR_LIST
    //        );

    //    CvContourTree* tree1 = cvCreateContourTree(
    //        first_contour1,
    //        storage1,
    //        200
    //        );
    //
    //    IplImage* img_8uc12 = cvLoadImage("flower1.jpg",CV_LOAD_IMAGE_GRAYSCALE);
    //    IplImage* img_edge12 = cvCreateImage(cvGetSize(img_8uc12),8,1);
    ////    IplImage* img_8uc3 = cvCreateImage(cvGetSize(img_8uc1),8,3);
    //
    //    cvThreshold(img_8uc12,img_edge12,128,255,CV_THRESH_BINARY);
    //
    //
    //    CvMemStorage* storage2 = cvCreateMemStorage();
    //    CvSeq* first_contour2 = NULL;
    //
    //    int Nc2 = cvFindContours(
    //        img_edge12,
    //        storage2,
    //        &first_contour2,
    //        sizeof(CvContour),
    //        CV_RETR_LIST
    //        );
    //    CvContourTree* tree2 = cvCreateContourTree(
    //        first_contour2,
    //        storage2,
    //        200
    //        );
    //    double n = cvMatchContourTrees(tree1,tree1,CV_CONTOURS_MATCH_I1,200);
    //
    //    printf("%d",n);
    //
    //    cvWaitKey();

下面爲成對幾何直方圖匹配方法

輪廓的匹配源碼
複製代碼
#include "gesrec.h"
#include <stdio.h>//////////////////////////////////////////

#define PI 3.14159f

//輪廓面積比較函數
static int gesContourCompFunc(const void* _a, const void* _b, void* userdata)
{
    int retval;
    double s1, s2;
    CvContour* a = (CvContour*)_a;
    CvContour* b = (CvContour*)_b;
    
    s1 = fabs(cvContourArea(a));
    s2 = fabs(cvContourArea(b));
    //s1 = a->rect.height * a->rect.width;
    //s2 = b->rect.height * b->rect.width;

    if(s1 < s2)
    {
        retval = 1;
    }
    else if(s1 == s2)
    {
        retval = 0;
    }
    else
    {
        retval = -1;
    }

    return retval;
}

//src:BGR dst:
void gesFindContours(IplImage* src, IplImage* dst, CvSeq** templateContour, CvMemStorage* templateStorage, int flag)
{
    int count;//輪廓數
    IplImage* gray;
    CvMemStorage* first_sto;
    CvMemStorage* all_sto;
    CvSeq* first_cont;
    CvSeq* all_cont;
    CvSeq* cur_cont;
    
    //初始化動態內存
    first_sto = cvCreateMemStorage(0);
    first_cont = cvCreateSeq(CV_SEQ_ELTYPE_POINT, sizeof(CvSeq), sizeof(CvPoint), first_sto);
    all_sto = cvCreateMemStorage(0);
    all_cont = cvCreateSeq(0, sizeof(CvSeq), sizeof(CvSeq), all_sto);

    //創建源圖像對應的灰度圖像
    gray = cvCreateImage(cvGetSize(src), IPL_DEPTH_8U, 1);
    cvCvtColor(src, gray, CV_BGR2GRAY);
    
    //得到圖像的外層輪廓
    count = cvFindContours(gray, first_sto, &first_cont, sizeof(CvContour), CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE);

    //如果沒有檢測到輪廓則返回
    if(first_sto == NULL)
    {
        return;
    }

    //將所有的輪廓都放到first_cont中
    for(;first_cont != 0;first_cont = first_cont->h_next)
    {
        if(((CvContour* )first_cont)->rect.height * ((CvContour* )first_cont)->rect.width >= 625)
            cvSeqPush(all_cont, first_cont);
    }

    //對輪廓按照面積進行排序
    cvSeqSort(all_cont, gesContourCompFunc, 0);

    //在dst中畫出輪廓
    cvZero(dst);
    for(int i = 0;i < min(all_cont->total, 3);i++)///////////////////////次數待改
    {
        cur_cont = (CvSeq* )cvGetSeqElem(all_cont, i);
        if(flag != 0 && i == 0)
        {
            *templateContour = cvCloneSeq(cur_cont, templateStorage);
        }

        CvScalar color = CV_RGB(rand()&255, rand()&255, rand()&255);
        cvDrawContours(dst, (CvSeq* )cur_cont, color, color, -1, 1, 8);
    }
    
    //判斷原點位置以確定是否需要反轉圖像
    if(src->origin == 1)
    {
        cvFlip(dst);
    }

    //釋放內存
    cvReleaseMemStorage(&first_sto);
    cvReleaseMemStorage(&all_sto);
    cvReleaseImage(&gray);
}

void gesMatchContoursTemplate(IplImage* src, IplImage* dst, CvSeq** templateContour)
{
    CvSeq* contour;
    CvMemStorage* storage;

    //初始化動態內存
    storage = cvCreateMemStorage(0);
    contour = cvCreateSeq(CV_SEQ_ELTYPE_POINT, sizeof(CvSeq), sizeof(CvPoint), storage);

    //得到輪廓並進行匹配
    gesFindContours(src, dst, &contour, storage, 1);
    if(contour->total != 0)//如果得到的輪廓不爲空
    {
        double result = cvMatchShapes((CvContour* )contour, (CvContour* )(*templateContour), CV_CONTOURS_MATCH_I3);
        printf("%.2f\n", result);/////////////////////////////////////////////
    }

    //釋放內存
    cvReleaseMemStorage(&storage);
}

//模版匹配法的完整實現
int gesMatchContoursTemplate2(IplImage* src, IplImage* dst, CvSeq* templateContour)
{
    CvSeq* contour;
    CvSeq* cur_cont;
    CvMemStorage* storage;
    double minValue, tempValue;
    int i, minIndex;

    //初始化動態內存
    storage = cvCreateMemStorage(0);
    contour = cvCreateSeq(CV_SEQ_ELTYPE_POINT, sizeof(CvSeq), sizeof(CvPoint), storage);

    //得到輪廓並進行匹配
    minIndex = -1;
    gesFindContours(src, dst, &contour, storage, 1);
    if(contour->total != 0)//如果得到的輪廓不爲空
    {
        if(templateContour->total != 0)
        {
            cur_cont = (CvSeq* )cvGetSeqElem(templateContour, 0);
            minValue = cvMatchShapes((CvContour* )contour, (CvContour* )cur_cont, CV_CONTOURS_MATCH_I3);
            minIndex = 0;
            printf("0:%.2f\n", minValue);
        }
        
        for(i = 1;i < templateContour->total;i++)
        {
            cur_cont = (CvSeq* )cvGetSeqElem(templateContour, i);
            tempValue = cvMatchShapes((CvContour* )contour, (CvContour* )cur_cont, CV_CONTOURS_MATCH_I3);
            if(tempValue < minValue)
            {
                minValue = tempValue;
                minIndex = i;
            }
            printf("%d:%.2f\n", i, tempValue);
        }
        
        if(minValue >= 0.3)
        {
            minIndex = -1;
        }
    }

    //打印匹配結果
    printf("the result is %d\n", minIndex);

    //釋放內存
    cvReleaseMemStorage(&storage);

    return minIndex;
}

//找出輪廓最大的5個極大值點
void gesFindContourMaxs(CvSeq* contour)
{
    int i;
    CvScalar center;//重心位置
    CvPoint* p;
    CvMat max;//存儲5個極大值的數組
    double initMax[] = {-1, -1, -1, -1, -1};//初始極大值設置爲-1
    double minValue, maxValue;//5個極大值中的最大值與最小值
    CvPoint minLoc;//最小值的位置
    double preDistance = 0;
    bool isCandidate = false;//是否是候選的極大值點

    //初始化重心位置
    center = cvScalarAll(0);

    //初始化極大值矩陣
    max = cvMat(1, 5, CV_64FC1, initMax);

    //首先求出輪廓的重心
    for(i = 0;i < contour->total;i++)
    {
        p = (CvPoint* )cvGetSeqElem(contour, i);
        center.val[0] += p->x;
        center.val[1] += p->y;
    }
    center.val[0] /= contour->total;
    center.val[1] /= contour->total;

    //遍歷輪廓,找出所有的極大值點
    for(i = 0;i < contour->total;i++)
    {
        p = (CvPoint* )cvGetSeqElem(contour, i);
        double distance = sqrt(pow(center.val[0] - p->x, 2) + pow(center.val[1] - p->y, 2));

        if(distance > preDistance)
        {
            isCandidate = true;
        }
        else if(distance < preDistance && isCandidate == true)
        {
            cvMinMaxLoc(&max, &minValue, &maxValue, &minLoc);

            if(distance > minValue)
            {
                cvmSet(&max, minLoc.y, minLoc.x, distance);
            }
            isCandidate = false;
        }
        else
        {
            isCandidate = false;
        }

        preDistance = distance;
    }

    //打印5個極大值
    printf("%.2f %.2f %.2f %.2f %.2f\n", cvmGet(&max, 0, 0), cvmGet(&max, 0, 1), cvmGet(&max, 0, 2), cvmGet(&max, 0, 3), cvmGet(&max, 0, 4));
}

//計算輪廓的pair-wise幾何直方圖
CvHistogram* gesCalcContoursPGH(CvSeq* contour)
{
    CvHistogram* hist;//成對幾何直方圖
    CvContour* tempCont;
    
    //得到成對幾何直方圖第二個維度上的範圍
    tempCont = (CvContour* )contour;
    cvBoundingRect(tempCont, 1);

    int sizes[2] = {60, 200};
    float ranges[2][2] = {{0,PI}, {0,200}};
    float** rangesPtr = new float* [2];
    rangesPtr[0] = ranges[0];
    rangesPtr[1] = ranges[1];

    //初始化幾何直方圖
    hist = cvCreateHist(2, sizes, CV_HIST_ARRAY, rangesPtr, 1);

    //計算輪廓的成對幾何直方圖
    cvCalcPGH(contour, hist);

    return hist;
}

//對輪廓的pair-wise幾何直方圖進行匹配
void gesMatchContoursPGH(CvSeq* contour, CvHistogram* templateHist)
{
    CvHistogram* hist;

    //得到輪廓的成對幾何直方圖
    hist = gesCalcContoursPGH(contour);

    //歸一化直方圖
    cvNormalizeHist(templateHist, 1);
    cvNormalizeHist(hist, 1);

    //直方圖匹配
    double result = cvCompareHist(hist, templateHist, CV_COMP_INTERSECT);
    printf("result:%.2f\n", result);

    //釋放內存
    cvReleaseHist(&hist);
}
發佈了2 篇原創文章 · 獲贊 11 · 訪問量 7萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章