最近再次用到了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(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);
}