雖然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.成對幾何直方圖匹配
在得到輪廓的成對幾何直方圖之後,可以使用直方圖對比的方法來進行匹配。
/************************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); }