(轉)opencv輪廓高級應用(輪廓匹配,幾何直方圖)

轉自:http://blog.sina.com.cn/s/blog_662c785901011i7z.html


OpenCv輪廓高級應用(輪廓匹配,幾何直方圖)

    最近再次用到了opencv輪廓,在這裏結合作者冰山一角的博客(http://www.cnblogs.com/slysky/)以及自己的體會在此稍加說明。其程序主要參見冰山一角的Blog,遺憾的是代碼是OpenCV1.0寫的,等有時間再用2.4.2改寫一篇。

    對於輪廓的相關數據結構表示和幾本操作(查找輪廓,畫輪廓),可參見前面兩片關於輪廓的例程,在這裏不多講。

   對於查找輪廓我們一般要對圖像Canny檢測。但是對於很特殊的場合其實我們還可以直接對二值化的圖像進行輪廓的提取,找出的輪廓其實就是Blob(這個可能就是爲什麼OpenCV高版本里面把blob分析拋棄的原因吧,我猜的話),畫上外截矩形就是一個ROI,是不是覺得很有用?下面介紹羅闊的高級應用。

   輪廓的特性:

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函數
6.輪廓的輪廓樹
    輪廓樹用來描述某個特定輪廓的內部特徵。注意:輪廓樹跟輪廓是一一對應的關係;輪廓樹不用於描述多個輪廓之間的層次關係。

    輪廓樹的創建過程:

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

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

這樣建立的輪廓樹並不太魯棒,因爲輪廓上小的改變也可能會徹底改變結果的樹,同時最初的三角形是任意選取的。爲了得到較好的描述需要首先使用函數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:

 

輪廓匹配源碼1

    
    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(0sizeof(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, -118); 
	
	//判斷原點位置以確定是否需要反轉圖像 
	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(15, 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, 00), cvmGet(&max, 01), cvmGet(&max, 02), cvmGet(&max, 03), cvmGet(&max, 04)); 
//計算輪廓的pair-wise幾何直方圖 
CvHistogram* gesCalcContoursPGH(CvSeq* contour)
		CvHistogram* hist;//成對幾何直方圖 
		CvContour* tempCont; //得到成對幾何直方圖第二個維度上的範圍 
		tempCont = (CvContour* )contour; 
		cvBoundingRect(tempCont, 1); 
	int sizes[2] = {60200}; 
	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); 
}

發佈了19 篇原創文章 · 獲贊 3 · 訪問量 6萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章