ORB + OPENCV
一、介紹
假如有兩張人物圖片,我們的目標(biāo)是要確認(rèn)這兩張圖片中的人物是否是同一個(gè)人。如果人來判斷,這太簡單了。但是讓計(jì)算機(jī)來完成這個(gè)功能就困難重重。一種可行的方法是:
?
- 分別找出兩張圖片中的特征點(diǎn)
- 描述這些特征點(diǎn)的屬性,
- 比較這兩張圖片的特征點(diǎn)的屬性。如果有足夠多的特征點(diǎn)具有相同的屬性,那么就可以認(rèn)為兩張圖片中的人物就是同一個(gè)人。
?
ORB(Oriented FAST and Rotated BRIEF)就是一種特征提取并描述的方法。ORB是由Ethan Rublee, Vincent Rabaud, Kurt Konolige以及Gary R.Bradski在2011年提出,論文名稱為"ORB:An Efficient Alternative to SIFTor SURF",(http://www.willowgarage.com/sites/default/files/orb_final.pdf)。
ORB分兩部分,即特征點(diǎn)提取和特征點(diǎn)描述。特征提取是由FAST(Features from Accelerated Segment Test)算法發(fā)展來的,特征點(diǎn)描述是根據(jù)BRIEF(Binary Robust Independent Elementary Features)特征描述算法改進(jìn)的。ORB特征是將FAST特征點(diǎn)的檢測(cè)方法與BRIEF特征描述子結(jié)合起來,并在它們?cè)瓉淼幕A(chǔ)上做了改進(jìn)與優(yōu)化。據(jù)說ORB算法的速度是sift的100倍,是surf的10倍。
二、Oriented FAST(oFast)特征點(diǎn)的提取
oFast就是在使用FAST提取特征點(diǎn)之后,給其定義一個(gè)該特征點(diǎn)的放向,并以此來實(shí)現(xiàn)該特征點(diǎn)的旋轉(zhuǎn)不變形。
2.1、粗提取
圖像的特征點(diǎn)可以簡單的理解為圖像中比較顯著顯著的點(diǎn),如輪廓點(diǎn),較暗區(qū)域中的亮點(diǎn),較亮區(qū)域中的暗點(diǎn)等。
FAST的核心思想是找出那些卓爾不群的點(diǎn)。即拿一個(gè)點(diǎn)跟它周圍的點(diǎn)比較,如果它和其中大部分的點(diǎn)都不一樣,就人物它是一個(gè)特征點(diǎn)。
如上圖,假設(shè)圖像中的一點(diǎn)P,及其一個(gè)鄰域。右半拉是放大的圖,每個(gè)小方格代表一個(gè)像素,方格內(nèi)的顏色只是為了便于區(qū)分,不代表該像素點(diǎn)的顏色。判斷該點(diǎn)是不是特征點(diǎn)的方法是,以P為圓心畫一個(gè)半徑為3pixel的圓(周長為16pixel)。圓周上如果有連續(xù)n個(gè)像素點(diǎn)的灰度值比P點(diǎn)的灰度值大或者小(需事先設(shè)定一個(gè)閾值T),則認(rèn)為P為特征點(diǎn)。一般n設(shè)置為12。
為了加快特征點(diǎn)的提取,快速排除非特征點(diǎn),首先檢測(cè)1、9、5、13位置上的灰度值,如果P是特征點(diǎn),那么這四個(gè)位置上有3個(gè)或3個(gè)以上的的像素值都大于或者小于P點(diǎn)的灰度值。如果不滿足,則直接排除此點(diǎn)。
2.2、使用ID3決策樹,將特征點(diǎn)圓周上的16個(gè)像素輸入決策樹中,以此來篩選出最優(yōu)的FAST特征點(diǎn)。
2.3、使用非極大值抑制算法去除臨近位置多個(gè)特征點(diǎn)的。具體:為每一個(gè)特征點(diǎn)計(jì)算出其響應(yīng)大小(特征點(diǎn)P和其周圍16個(gè)特征點(diǎn)偏差的絕對(duì)值和)。在比較臨近的特征點(diǎn)中,保留響應(yīng)值較大的特征點(diǎn),刪除其余的特征點(diǎn)。
2.4、特征點(diǎn)的尺度不變性
建立金字塔,來實(shí)現(xiàn)特征點(diǎn)的多尺度不變性。設(shè)置一個(gè)比例因子scaleFactor(opencv默認(rèn)為1.2)和金字塔的層數(shù)nlevels(pencv默認(rèn)為8)。將原圖像按比例因子縮小成nlevels幅圖像??s放后的圖像為:I’= I/scaleFactork(k=1,2,…, nlevels)。nlevels幅不同比例的圖像提取特征點(diǎn)總和作為這幅圖像的oFAST特征點(diǎn)。
2.5、特征點(diǎn)的旋轉(zhuǎn)不變形
oFast用矩(moment)法來確定FAST特征點(diǎn)的方向。即計(jì)算特征點(diǎn)以r為半徑范圍內(nèi)的質(zhì)心,特征點(diǎn)坐標(biāo)到質(zhì)心形成一個(gè)向量作為該特征點(diǎn)的方向。矩定義如下:
三、Rotated BRIEF(rBRIEF)特征點(diǎn)的描述
3.1、BRIEF算法
BRIEF算法計(jì)算出來的是一個(gè)二進(jìn)制串的特征描述符。它是在一個(gè)特征點(diǎn)的鄰域內(nèi),選擇n對(duì)像素點(diǎn)pi、qi(i=1,2,…,n)。然后比較每個(gè)點(diǎn)對(duì)的灰度值的大小。如果I(pi)> I(qi),則生成二進(jìn)制串中的1,否則為0。所有的點(diǎn)對(duì)都進(jìn)行比較,則生成長度為n的二進(jìn)制串。一般n取128、256或512(opencv默認(rèn)為256)。
另外,為了增加特征描述符的抗噪性,算法需要先對(duì)圖像進(jìn)行高斯平滑處理。在ORB算法中,在這個(gè)地方進(jìn)行了改進(jìn),在使用高斯函數(shù)進(jìn)行平滑后又用了其他操作,使其更加的具有抗噪性。具體方法下面將會(huì)描述。
在特征點(diǎn)SxS的區(qū)域內(nèi)選取點(diǎn)對(duì)的方法,BRIEF論文中測(cè)試了5種方法:
- 在圖像塊內(nèi)平均采樣;
- p和q都符合(0,S2/25)的高斯分布;
- p符合(0,S2/25)的高斯分布,而q符合(0,S2/100)的高斯分布;
- 在空間量化極坐標(biāo)下的離散位置隨機(jī)采樣;
- 把p固定為(0,0),q在周圍平均采樣。
?
3.2、rBRIEF算法
3.2.1、steered BRIEF(旋轉(zhuǎn)不變性改進(jìn)):
在使用oFast算法計(jì)算出的特征點(diǎn)中包括了特征點(diǎn)的方向角度。假設(shè)原始的BRIEF算法在特征點(diǎn)SxS(一般S取31)鄰域內(nèi)選取n對(duì)點(diǎn)集。
經(jīng)過旋轉(zhuǎn)角度θ旋轉(zhuǎn),得到新的點(diǎn)對(duì):
在新的點(diǎn)集位置上比較點(diǎn)對(duì)的大小形成二進(jìn)制串的描述符。這里需要注意的是,在使用oFast算法是在不同的尺度上提取的特征點(diǎn)。因此,在使用BRIEF特征描述時(shí),要將圖像轉(zhuǎn)換到相應(yīng)的尺度圖像上,然后在尺度圖像上的特征點(diǎn)處取SxS鄰域,然后選擇點(diǎn)對(duì)并旋轉(zhuǎn),得到二進(jìn)制串描述符。
3.2.2、rBRIEF-改進(jìn)特征點(diǎn)描述子的相關(guān)性
使用steeredBRIEF方法得到的特征描述子具有旋轉(zhuǎn)不變性,但是卻在另外一個(gè)性質(zhì)上不如原始的BRIEF算法,即描述符的可區(qū)分性(相關(guān)性)。為了解決描述子的可區(qū)分性和相關(guān)性的問題,ORB論文中沒有使用原始BRIEF算法中選取點(diǎn)對(duì)時(shí)的5種方法中的任意一種,而是使用統(tǒng)計(jì)學(xué)習(xí)的方法來重新選擇點(diǎn)對(duì)集合。
對(duì)每個(gè)特征點(diǎn)選取31x31領(lǐng)域,每個(gè)領(lǐng)域選擇5x5的平均灰度值代替原來單個(gè)像素值進(jìn)行比對(duì),因此可以得到N=(31-5+1)x(31-5+1) = 729個(gè)可以比對(duì)的子窗口(patch),可以使用積分圖像加快求取5x5鄰域灰度平均值的速度。一共有M = 1+2+3+...+N = 265356種點(diǎn)對(duì)組合,也就是一個(gè)長度為M的01字符串。顯然M遠(yuǎn)大于256,我們得篩選。
篩選方法如下:
?
- 重組所有點(diǎn)以及對(duì)應(yīng)的初始二值串得到矩陣O,行數(shù)為提取得到的點(diǎn)數(shù),每行是每個(gè)點(diǎn)對(duì)應(yīng)的初始二值描述子
- 對(duì)重組后的矩陣?O,按照每列均值與0.5的絕對(duì)差從小到大排序,得到矩陣T
- 貪心選擇:把T中第一列放進(jìn)矩陣R(一開始為空)中,并從T中移除依次選擇T的每列,與R中所有的列進(jìn)行比較,如果相似度超過一定閾值,忽略,進(jìn)行下一列,否則放進(jìn)R中,并從T中移除重復(fù)以上過程直到選擇?256個(gè)列,這樣每個(gè)特征點(diǎn)就有256個(gè)0,1組成的描述子。如果不足256個(gè),則降低閾值直到滿足256就可,R即為最終特征描述矩陣。
?
三、特征點(diǎn)匹配
這部分是另外一個(gè)話題了。ORB算法最大的特點(diǎn)就是計(jì)算速度快 。這得益于使用FAST檢測(cè)特征點(diǎn),FAST的檢測(cè)速度正如它的名字一樣是出了名的快。再就是是使用BRIEF算法計(jì)算描述子,該描述子特有的2進(jìn)制串的表現(xiàn)形式不僅節(jié)約了存儲(chǔ)空間,而且大大縮短了匹配的時(shí)間。
例如特征點(diǎn)A、B的描述子如下。
A:10101011
B:10101010
設(shè)定一個(gè)閾值,比如80%。當(dāng)A和B的描述子的相似度大于90%時(shí),我們判斷A,B是相同的特征點(diǎn),即這2個(gè)點(diǎn)匹配成功。在這個(gè)例子中A,B只有最后一位不同,相似度為87.5%,大于80%。則A和B是匹配的。
將A和B進(jìn)行異或操作就可以輕松計(jì)算出A和B的相似度。而異或操作可以借助硬件完成,具有很高的效率,加快了匹配的速度。
四、OpenCV實(shí)驗(yàn)(OpenCV3.0以上版本,包含contrib模塊)
?
[cpp]?view plain?copy
- #include?<iostream>??
- #include?<stdio.h>??
- #include?<unistd.h>??
- #include?<stdlib.h>??
- #include?<string.h>??
- #include?<string>??
- #include?<dirent.h>??
- #include?<unistd.h>??
- #include?<vector>??
- #include?<sstream>??
- #include?<fstream>??
- #include?<sys/io.h>??
- #include?<sys/times.h>??
- #include?<iomanip>??
- #include?<tuple>??
- #include?<cstdlib>??
- using?namespace?std;??
- ??
- #include?"opencv2/imgproc.hpp"??
- #include?"opencv2/imgcodecs.hpp"??
- #include?"opencv2/highgui.hpp"??
- #include?"opencv2/stitching.hpp"??
- #include?"opencv2/xfeatures2d/nonfree.hpp"??
- using?namespace?cv;??
- ??
- #define?ENABLE_LOG??
- ??
- bool?PreapreImg(vector<Mat>?&imgs);??
- bool?Match(vector<cv::detail::MatchesInfo>?&pairwise_matches,???
- ???????????const?vector<cv::detail::ImageFeatures>?&features,??
- ???????????const?cv::String?matcher_type?=?"homography",???
- ???????????const?int?range_width?=?-1,??
- ???????????const?bool?try_cuda?=?false,???
- ???????????const?double?match_conf?=?0.3f);??
- void?demo();??
- ??
- int?main(int?argc,?char**?argv)??
- {??
- ????cout?<<?"#?STA?##############################"?<<?endl;??
- ????cout?<<?"\n"?<<?endl;??
- ????int64?app_start_time?=?getTickCount();??
- ??????
- ????demo();??
- ??????
- ????cout?<<?"\n"?<<?endl;??
- ????cout?<<?"#?END?##############################?Time:?"???
- ?????????<<?((getTickCount()?-?app_start_time)?/?getTickFrequency())???
- ?????????<<?"?sec"?<<?endl;??
- ????return?0;??
- }??
- ??
- void?demo()??
- {??
- ????vector<Mat>?imgs;???
- ????PreapreImg(imgs);??
- ??????
- ????//?define?feature?finder??
- ????Ptr<cv::detail::FeaturesFinder>?finder?=???
- ????cv::makePtr<cv::detail::OrbFeaturesFinder>();??
- ??????
- ????//?detect?features??
- ????int?num_images?=?static_cast<int>(imgs.size());??
- ????vector<cv::detail::ImageFeatures>?features(num_images);??
- ????for?(int?i?=?0;?i?<?num_images;?i++)?{??
- ????????(*finder)(imgs[i],?features[i]);??
- ????????features[i].img_idx?=?i;??
- #ifdef?ENABLE_LOG??
- ????????cout?<<?">>?features?number:?"?<<?setw(4)?<<?features[i].img_idx??
- ?????????????<<?setw(5)?<<?static_cast<int>(features[i].keypoints.size())??
- ?????????????<<?endl;??
- ????????Mat?tmp;??
- ????????cv::drawKeypoints(imgs[i],?features[i].keypoints,?tmp);??
- ????????stringstream?ss;??
- ????????ss?<<?i;??
- ????????cv::imwrite(("./img"?+?string(ss.str())?+?"_keypoints.jpg").c_str(),?tmp);??
- #endif??
- ????}??
- ????//?Frees?unused?memory?allocated?before?if?there?is?any??
- ????finder->collectGarbage();??
- ??????
- ????//?Pairwise?matching???
- ????vector<cv::detail::MatchesInfo>?pairwise_matches;??
- ????Match(pairwise_matches,?features);??
- #ifdef?ENABLE_LOG??
- ????????cout?<<?">>?pairwise?matches:?"???
- ?????????????<<?setw(5)?<<?static_cast<int>(pairwise_matches.size())??
- ?????????????<<?endl;??
- ????????cout?<<?">>?Saving?matches?graph..."?<<?endl;??
- ????????ofstream?f("./matchGraph.txt");??
- ????????vector<cv::String>?img_names;??
- ????????for?(int?i?=?0;?i?<?num_images;?i++)?{??
- ????????????stringstream?ss;?ss?<<?i;??
- ????????????img_names.push_back(ss.str());??
- ????????}??
- ????????f?<<?matchesGraphAsString(img_names,?pairwise_matches,?1.0f);??
- ????????cout?<<?">>?Saving?matches?graph?OK.?Position:?./matchGraph.txt"?<<?endl;??
- ??
- ????????Mat?tmp;??
- ????????cv::drawMatches(imgs[0],?features[0].keypoints,???
- ????????????????????????imgs[1],?features[1].keypoints,??
- ????????????????????????pairwise_matches[1].matches,??
- ????????????????????????tmp);??
- ????????cv::imwrite("./matches0_1.jpg",?tmp);??
- #endif??
- }??
- ??
- bool?PreapreImg(vector<Mat>?&imgs)??
- {??
- ????Mat?image0?=?imread("./0.jpg",?IMREAD_GRAYSCALE);??
- ????Mat?image1?=?imread("./1.jpg",?IMREAD_GRAYSCALE);??
- ????imgs.push_back(image0);??
- ????imgs.push_back(image1);??
- ??????
- ????//?Check?if?have?enough?images??
- ????int?num_images?=?static_cast<int>(imgs.size());??
- ????if?(num_images?<?2)??
- ????{??
- ????????cout?<<?">>?error.?num_images?<?2"?<<?endl;??
- ????????return?false;??
- ????}??
- ??????
- #ifdef?ENABLE_LOG??
- ????for?(int?i?=?0;?i?<?num_images;?i++)?{??
- ????????cout?<<?">>?image?"?<<?setw(2)?<<?i?<<?":?"??
- ?????????????<<?setw(5)?<<?imgs[i].rows??
- ?????????????<<?setw(5)?<<?imgs[i].cols??
- ?????????????<<?setw(5)?<<?imgs[i].channels()??
- ?????????????<<?endl;??
- ????}??
- #endif??
- ??
- ????return?true;??
- }??
- ??
- /************************************************?
- *?There?are?3?kinds?of?feature?matchers?offered?by?"matchers.hpp"?
- */??
- bool?Match(vector<cv::detail::MatchesInfo>?&pairwise_matches,???
- ???????????const?vector<cv::detail::ImageFeatures>?&features,??
- ???????????const?cv::String?matcher_type?=?"homography",???
- ???????????const?int?range_width?=?-1,??
- ???????????const?bool?try_cuda?=?false,???
- ???????????const?double?match_conf?=?0.3f)??
- {??
- ????Ptr<cv::detail::FeaturesMatcher>?matcher;??
- ????if?(matcher_type?==?"affine")???
- ????{??
- ????????bool?full_affine?=?false;??
- ????????int?num_matches_thresh1?=?6;??
- ????????matcher?=?makePtr<cv::detail::AffineBestOf2NearestMatcher>(??
- ????????full_affine,?try_cuda,?match_conf,?num_matches_thresh1);??
- ????}??
- ????else?if?(matcher_type?==?"homography")???
- ????{??
- ????????int?num_matches_thresh1?=?6;??
- ????????int?num_matches_thresh2?=?6;??
- ????????if?(range_width?==?-1)??
- ????????????matcher?=?makePtr<cv::detail::BestOf2NearestMatcher>(??
- ????????????try_cuda,?match_conf,?num_matches_thresh1,?num_matches_thresh2);??
- ????????else??
- ????????????matcher?=?makePtr<cv::detail::BestOf2NearestRangeMatcher>(??
- ????????????range_width,?try_cuda,?match_conf,?num_matches_thresh1,?num_matches_thresh2);??
- ????}??
- ??????
- ????(*matcher)(features,?pairwise_matches);??
- ????matcher->collectGarbage();??
- ??????
- ????return?true;??
- }??
實(shí)驗(yàn)代碼:https://code.csdn.net/guoyunfei20/orb.git
實(shí)驗(yàn)結(jié)果:
輸入圖像1:
輸入圖像2:
圖像1的ORB特征點(diǎn)位置:
圖像2的ORB特征點(diǎn)位置:
利用cv::detail::BestOf2NearestMatcher匹配算法得到的能匹配上的特征點(diǎn)(圖像0 -> 圖像1):
?
?
?
?
總結(jié)
以上是生活随笔為你收集整理的ORB + OPENCV的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 客户端与服务器的数据交互
- 下一篇: 明星都是怎么谈恋爱的???