OpenCV:SURF算法浅析
引子: 課題需要SURF特征提取算法,在運(yùn)動(dòng)中提取攝像頭圖像中的特征點(diǎn),并進(jìn)行跟蹤匹配,以此估計(jì)運(yùn)動(dòng)狀態(tài)。開始找到了SIFT算法,SIFT特征提取具有極強(qiáng)的適應(yīng)能力,但運(yùn)算量稍大,后來就有了SURF特征提取算法,簡(jiǎn)化了計(jì)算量,保持了較高的性能,是性價(jià)比很不錯(cuò)的算法。開始并不知道OpenCV的存在,后來的后來發(fā)現(xiàn)OpenCV中已經(jīng)有了SURF算法,感嘆于技術(shù)發(fā)展之快(要知道SIFT是Low在2004年系統(tǒng)的提出的,SURF是在2006年才被Bay等提出的),感謝Low, Bay, et al.?感謝Internet、感謝Google、感謝Intel、感謝OpenCV、感謝Liu Liu(OpenCV中實(shí)現(xiàn)SURF算法的作者)、最重要的感謝祖國(guó)、感謝全世界最大的局域網(wǎng)。
在庸長(zhǎng)的開場(chǎng)白中SURF登臺(tái)了!!!令各位看官失望的是,引言很長(zhǎng),然內(nèi)容不多,為什么?我是菜鳥!我怕誰!菜鳥看不懂當(dāng)然寫不多了。
正文開始了,在OpenCV(據(jù)說是1.1以后的版本)中包含了SURF算法,并且還有一個(gè)使用SURF的例子,這里使用的是OpenCV2.1。在OpenCV的安裝目錄下/samples/c 文件夾中一個(gè)叫 find_obj.cpp 的文件,這是個(gè)應(yīng)用SURF算法尋找一本書的例子。同目錄下還有一對(duì)于的可執(zhí)行文件 find_obj.exe,可以先運(yùn)行一下看看。來看find_obj.cpp
1、這個(gè)程序的框架
從入口 main() 開始慢慢道來
const char* object_filename = argc == 3 ? argv[1] : "box.png";
const char* scene_filename = argc == 3 ? argv[2] : "box_in_scene.png";
main函數(shù)前兩行,判斷是否有圖片文件名的參數(shù)傳入,沒有則使用同目錄下的"box.png"和"box_in_scene.png"
緊接著創(chuàng)建內(nèi)存塊“CvMemStorage* storage = cvCreateMemStorage(0);”坦白地承認(rèn):具體的不懂
cvNamedWindow("Object", 1); //建立兩個(gè)窗口:物體和對(duì)比,第一個(gè)參數(shù)是窗口名稱,第二個(gè)參數(shù)是Flag。
cvNamedWindow("Object Correspond", 1); //據(jù)說Flag只支持一個(gè)參數(shù) CV_WINDOW_AUTOSIZE,可能CV_WINDOW_AUTOSIZE =?1 吧。
static CvScalar colors[] =? // 建立類似調(diào)色板的東西,colors[0],表示紅色,colors[8],表示白色
{???????????????????????????????????????????? // 下面的程序也只用到了這兩種顏色,紅色的圈兒和白色的線兒。
??? {{0,0,255}},????????????????????? // 不知道為什么這里弄了九個(gè),嚇人嗎?
??? {{0,128,255}},
??? {{0,255,255}},
??? {{0,255,0}},
??? {{255,128,0}},
??? {{255,255,0}},
??? {{255,0,0}},
??? {{255,0,255}},
??? {{255,255,255}}
};
IplImage* object = cvLoadImage( object_filename, CV_LOAD_IMAGE_GRAYSCALE );?????
IplImage* image = cvLoadImage( scene_filename, CV_LOAD_IMAGE_GRAYSCALE );
//載入圖像,如果為彩色轉(zhuǎn)換為灰度圖像,或者說以灰度的模式載入圖像。???
//后面的if()就是判斷是否正確載入了圖像,過!
IplImage* object_color = cvCreateImage(cvGetSize(object), 8, 3);
cvCvtColor( object, object_color, CV_GRAY2BGR );
//上面兩行依據(jù)灰度圖像建立彩色圖像,雖然目前實(shí)際上的數(shù)據(jù)是黑白的,但是后面可以添加紅色圓圈
CvSeq *objectKeypoints = 0, *objectDescriptors = 0;// 表示指向特征點(diǎn)及其描述符的結(jié)構(gòu)體的指針
CvSeq *imageKeypoints = 0, *imageDescriptors = 0;// CvSeq 為可動(dòng)態(tài)增長(zhǎng)元素序列,是所有OpenCV動(dòng)態(tài)數(shù)據(jù)結(jié)構(gòu)的基礎(chǔ)
CvSURFParams params = cvSURFParams(500, 1);//SURF參數(shù)設(shè)置:閾值500,生成128維描述符
// cvSURFParams 函數(shù)原型如下:
CvSURFParams cvSURFParams(double threshold, int extended)
{
??? CvSURFParams params;
??? params.hessianThreshold = threshold; // 特征點(diǎn)選取的 hessian 閾值
??? params.extended = extended; // 是否擴(kuò)展,1 - 生成128維描述符,0 - 64維描述符
??? params.nOctaves = 4;?
??? params.nOctaveLayers = 2;
??? return params;
}
往下
double tt = (double)cvGetTickCount(); //計(jì)時(shí)
cvExtractSURF( object, 0, &objectKeypoints, &objectDescriptors, storage, params );
cvExtractSURF( image, 0, &imageKeypoints, &imageDescriptors, storage, params );
//提取圖像中的特征點(diǎn),函數(shù)原型:
CVAPI(void) cvExtractSURF( const CvArr* img, const CvArr* mask,
?????????????????? CvSeq** keypoints, CvSeq** descriptors,
???????????????????CvMemStorage* storage, CvSURFParams params, int useProvidedKeyPts CV_DEFAULT(0)?);
第3、4個(gè)參數(shù)返回結(jié)果:特征點(diǎn)和特征點(diǎn)描述符,數(shù)據(jù)類型是指針的指針,真麻煩,但是后面的例程中有如何將這些數(shù)據(jù)從指針的指針中提取出來,所以這里就不管指針的指針是什么東西了。關(guān)于對(duì)特征點(diǎn)的描述見第二小節(jié),關(guān)于特征點(diǎn)描述符見第三小節(jié)(三,中國(guó)傳統(tǒng)文化對(duì)三情有獨(dú)鐘,怎么如隔三秋、三日不絕、三月不知菜味<本人菜鳥嗎,喜是食白菜的鳥人>,新時(shí)代新氣象也給“三”賦予新的含義,為發(fā)揚(yáng)傳統(tǒng)文化,故本菜鳥喜歡什么都分割為1、2、3)
//后面的因?yàn)椴惶P(guān)心沒仔細(xì)看,大概就是生成匹配對(duì)比的圖像即:"correspond"
//然后,根據(jù)是否定義了 "USE_FLANN" 采用不同的匹配方法,flann 和 非flann
//最后,在object圖像中使用紅色圓圈畫出特征點(diǎn)位置及所處尺度的大小
//在correspond 圖像中顯示匹配結(jié)果,用白色線段相連。
2、特征點(diǎn)的描述,例如 objectKeypoints
定義:CvSeq *objectKeypoints = 0;
對(duì)于描述特征點(diǎn)和特征點(diǎn)的描述是基于一種 結(jié)構(gòu)體CvSeq,結(jié)構(gòu)CvSeq是所有OpenCV動(dòng)態(tài)數(shù)據(jù)結(jié)構(gòu)的基礎(chǔ),內(nèi)部結(jié)構(gòu):
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 */ \ // 總共有多少個(gè)元素,objectKeypoints->total 表有多少個(gè)特征點(diǎn)
int elem_size;/* size of sequence element in bytes */ \ // 每個(gè)元素的大小,字節(jié)表示
char* block_max;/* maximal bound of the last block */ \ //不懂
char* ptr; /* current write pointer */ \ // 當(dāng)時(shí)寫指針的位置,可能就是指向?qū)嶋H數(shù)據(jù)的指針吧
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 */ //同上
其實(shí)這些弄不弄明白對(duì)于應(yīng)用來說沒影響,因?yàn)橐唤M特征點(diǎn)被提取出來了,通常會(huì)關(guān)心:
整體的
特征點(diǎn)的個(gè)數(shù): objectKeypoints->total
第i個(gè)特征點(diǎn)
CvSURFPoint *r = (CvSURFPoint*)cvGetSeqElem(objectKeypoints,i);
第i個(gè)特征點(diǎn)的:
X坐標(biāo)位置(單位像素): r->pt.x
Y坐標(biāo)位置(單位像素): r->pt.y
特征點(diǎn)所處的尺度大小 : r->size
特征點(diǎn)主方位角 : r->dir (0-360 表示,如何定義未知)
特征點(diǎn)的Hessian值 : r->hessian
還有一個(gè)參數(shù): r->laplacian 取值為1 或 -1 不知道是什么意思,什么拉普拉斯,有木有高人給指點(diǎn)一下,望指導(dǎo)
3、特征點(diǎn)描述符,例如:objectDescriptors
定義:CvSeq *objectDescriptors = 0;
特征點(diǎn)的個(gè)數(shù): objectDescriptors->total
對(duì)于某個(gè)特征點(diǎn)描述符,就沒有特征點(diǎn)那么簡(jiǎn)單了,至少這個(gè)例程中沒有涉及僅使用一個(gè)函數(shù)和對(duì)應(yīng)的結(jié)構(gòu)體就可以提取出所需數(shù)據(jù)的簡(jiǎn)單的方法。
通過觀查特征點(diǎn)匹配的代碼,菜鳥以為得到特征點(diǎn)描述符的方法為:
CvSetReader reader;
cvStartReadSeq(objectDescriptors, &reader, 0);
for(int i = 0; i < objectDescriptors->total; i++)
{
??????? const float* descriptor = (const float*)reader.ptr; // descriptor 指向第i 特征描述符(float數(shù)組)的指針
??????? //?數(shù)組長(zhǎng)度為:reader.seq->elem_size/sizeof(float) or objectDescriptors->elem_size/sizeof(float)?
???????CV_NEXT_SEQ_ELEM(reader.seq->elem_size, reader); //讀取下一個(gè)特征點(diǎn)
}
// 同樣對(duì)于 特征點(diǎn) objectKeypoints 也有
CvSetReader kreader;
cvStartReadSeq(objectKeypoints , &kreader, 0);
for(int i = 0; i < objectKeypoints ->total; i++)
{
??????? const CvSURFPoint*?kp = (const CvSURFPoint*)kreader.ptr;?
????????CV_NEXT_SEQ_ELEM(reader.seq->elem_size, reader); //讀取下一個(gè)特征點(diǎn)
????????//?上面等價(jià)于?CvSURFPoint *kp = (CvSURFPoint*)cvGetSeqElem(objectKeypoints,i);(第二小節(jié))
}
順便提一下特征點(diǎn)的匹配,前面第一小節(jié)涉及到了兩種特征匹配:flann 和 非flann。
flann沒看明白,非flann就是計(jì)算描述符向量的距離,即每個(gè)對(duì)應(yīng)元素差的平方和,對(duì)于每個(gè)需要匹配的特征點(diǎn)同對(duì)應(yīng)的圖片中的各個(gè)特征點(diǎn)進(jìn)行距離的計(jì)算并保存最短的兩個(gè)距離,當(dāng)最短距離小于次短距離的0.6時(shí)認(rèn)為匹配成功。函數(shù)定義如下:
int naiveNearestNeighbor( const float* vec, int laplacian,
??????????????????????????????????????????????? ?const CvSeq* model_keypoints,
???????????????????????????????????????????????? const CvSeq* model_descriptors )
vec ---- 待匹配的描述符
laplacian ---- 第二小節(jié)中的神秘變量在這里再一次出現(xiàn)了,當(dāng)兩個(gè)特征點(diǎn)的 laplacian 值不同時(shí)直接pass 不準(zhǔn)配對(duì)(只有同性才能配對(duì),OMG 太變態(tài)了太變態(tài)了EMTF怎么存在這么惡心的變量)不匹配也不計(jì)算距離
model_keypoints --------- 指向匹配候選特征點(diǎn)們的結(jié)構(gòu)體指針
model_descriptors ------ 指向匹配候選特征點(diǎn)描述符們的結(jié)構(gòu)體指針
如果匹配成功返回 特征點(diǎn)在 model_keypoints 中的索引值
計(jì)算兩個(gè)描述符距離,函數(shù)定義
double compareSURFDescriptors( const float* d1, const float* d2, double best, int length )
d1 ---------?描述符1數(shù)組
d2 ---------?描述符2數(shù)組
best ------ 相當(dāng)閾值,當(dāng)計(jì)算的過程中距離大于 beat ?的值時(shí)即刻返回當(dāng)前計(jì)算的距離值,不用再計(jì)算了,木有意義,通常將目前的次近距離傳遞給 best,以便節(jié)省匹配時(shí)間(其實(shí)提取時(shí)間遠(yuǎn)遠(yuǎn)大于匹配時(shí)間)
length? --- 描述符的維數(shù),即描述符數(shù)組長(zhǎng)度
返回計(jì)算的距離。
總結(jié)
以上是生活随笔為你收集整理的OpenCV:SURF算法浅析的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: editplus使用php,EditPl
- 下一篇: qq音乐下载|qq音乐播放器下载