Brief描述子


一、Brief算法

1、基本原理

BRIEF是2010年的一篇名為《BRIEF:Binary Robust Independent Elementary Features》的文章中提出,Brief為特征描述子,對已檢測到的特征點進行描述,是一種二進制編碼描述子,摒棄了區域灰度直方圖描述特征點的傳統方法,加快特征描述子建立速度,降低特征匹配時間。因為需要事先得到特征點的位置,可以利用Fast特征點檢測算法或Harris角點檢測算法或者SIFT、Surf等算法檢測特征點的位置,接下來利用特征點鄰域BRIEF算法建立特征描述符。

在特征點附近隨機的選取若干點對,將這些點對的灰度值大小組合成一個長為256的二進制字串,並將這個二進制字串作為該特征點的特征描述子。

構造BRIEF特征只有兩個關鍵步驟:

1)如何對圖像對平滑處理;算法是對特征點對進行描述子生成,所以對特征點要求較高,因此在進行生成前進行平滑操作。盒狀濾波器的處理方法來代替高斯平滑處理方法。由於可以采用積分圖像的方法,所以盒狀濾波器比高斯濾波器更快,而且兩者的准確性幾乎相同。

2)如果選擇[x,y]對。由於tao測試是根據單像素的亮度進行判別的,非常被噪聲影響,做圖像平滑可以消除噪聲影響。匹配的難度越大,圖像的平滑也越重要。

關於特征點的附近區域選擇,作者提供五種方法:

其中方法(2)比較好。

技術分享

由於其描述子利用二進制(“0”和“1”)編碼,因此在特征匹配時只需計算2個特征點描述子的Hamming距離。大量實驗表明,不匹配特征點的描述子的Hamming距離在128左右,匹配點對描述子的Hamming距離則遠小於128。由於BRIEF的匹配速度遠高於SURF和SIFT,因此應用較為廣泛。

1、兩個特征編碼對應bit位上相同元素的個數小於128的,一定不是配對的。

2、一幅圖上特征點與另一幅圖上特征編碼對應bit位上相同元素的個數最多的特征點配成一對。

 

2、算法流程

 

1、為減少噪聲干擾,先對圖像進行高斯濾波(方差為2,高斯窗口為9x9)。

2、以特征點為中心,取SxS的鄰域大窗口。在大窗口中隨機選取一對(兩個)5x5的子窗口,比較子窗口內的像素和(可用積分圖像完成),進行二進制賦值。(一般S=31)

技術分享

其中,p(x),p(y)分別隨機點x=(u1,v1),y=(u2,v2)所在5x5子窗口的像素和。

3、在大窗口中隨機選取N對子窗口,重復步驟2的二進制賦值,形成一個二進制編碼,這個編碼就是對特征點的描述,即特征描述子。(一般N=256)

構造一個512個bit的BRIEF,就需要512對[x,y],且需要注意,它們是有序的,每次計算位置都相同,否則影響最終結果。也就說說,一旦選定了512對[x,y],那么,無論是提取特征,還是匹配特征,都要按照這512對進行計算。512/8=64就是存儲BRIEF所需的字節數,論文將512個bit的BRIEF又稱作BRIEF-64。

在opencn2.4.9中,該區域的大小為48×48。再在該區域內,以某種特定的方式選擇nd個像素點對。然后比較像素點對的灰度值:  I(pi)和I(qi)分別表示第i個像素點對的兩個像素piqi的灰度值。最后把補丁區域內所有點對的比較結果串成一個二值位字符串的形式,從而形成了該特征點的描述符。

B = b0b1bibnd (2)

通過實驗對比可知,nd = 128,256和512時,在運算速度,空間占用和准確性上可以達到最佳的效果。如果用字節型來表示描述符的話,那么

k =nd / 8 (3)

k就表示為描述符的字節數。

源碼

1、程序源碼

對於這算法,在opencv中已經有很好的源碼實現,算法亮點有:

a、構造函數

BRIEF描述符創建的類是BriefDescriptorExtractor,它的構造函數為:

typedef void(*PixelTestFn)(InputArray, const std::vector<KeyPoint>&, OutputArray, bool use_orientation ); //函數指針,分別針對不同類型調用不同函數,三個函數區別在於所需要的像素點對的數量就不同 PixelTestFn test_fn_; BriefDescriptorExtractorImpl::BriefDescriptorExtractorImpl(int bytes, bool use_orientation) : bytes_(bytes), test_fn_(NULL) { use_orientation_ = use_orientation; switch (bytes) { case 16 : test_fn_ = pixelTests16; break ; case 32 : test_fn_ = pixelTests32; break ; case 64 : test_fn_ = pixelTests64; break ; default : CV_Error(Error::StsBadArg, "bytes must be 16, 32, or 64"
); } }

 

b、接口函數

void
BriefDescriptorExtractorImpl::compute(InputArray image, std::vector<KeyPoint>& keypoints, OutputArray descriptors) { // Construct integral image for fast smoothing (box filter) Mat sum; Mat grayImage = image.getMat(); //計算時為灰度圖 if( image.type() != CV_8U ) cvtColor( image, grayImage, COLOR_BGR2GRAY ); ///TODO allow the user to pass in a precomputed integral image // if(image.type() == CV_32S) // sum = image; // else //創建積分圖像 integral( grayImage, sum, CV_32S); // Remove keypoints very close to the border // PATCH_SIZE = 48;表示補丁區域的邊長,KERNEL_SIZE = 9;表示盒狀濾波器的邊長 //根據補丁區域和盒狀濾波器的尺寸大小,去掉那些過於靠近圖像邊界的特征點 KeyPointsFilter::runByImageBorder(keypoints, image.size(), PATCH_SIZE/2 + KERNEL_SIZE/2 ); //描述符矩陣變量清零 descriptors.create((int )keypoints.size(), bytes_, CV_8U); descriptors.setTo(Scalar::all(0 )); //調用test_fn_指向的函數,創建BRIEF描述符
 test_fn_(sum, keypoints, descriptors, use_orientation_); }

3、Brief描述子產生

static void pixelTests16(InputArray _sum, const std::vector<KeyPoint>& keypoints, OutputArray _descriptors, bool use_orientation ) { Matx21f R; Mat sum = _sum.getMat(), descriptors = _descriptors.getMat(); //遍歷特征點 for (size_t i = 0; i < keypoints.size(); ++ i) { //描述子首地址 uchar* desc = descriptors.ptr(static_cast<int> (i)); //獲取特征點 const KeyPoint& pt = keypoints[i]; //如果有旋轉 if ( use_orientation ) { float angle = pt.angle; angle *= (float)(CV_PI/180 .f); R(0,0) = sin(angle); R(1,0) = cos(angle); } //開始平滑產生描述子 #include "generated_16.i"

 } }

4、smoothedSum函數

對點進行盒裝高斯平滑作用,函數是根據像素點位置,返回此區域的差分和

//sum為積分圖像,pt為特征點變量,x和y表示點對中某一個像素相對於特征點的坐標,函數返回濾波的結果 inline int smoothedSum(const Mat& sum, const KeyPoint& pt, int y, int x) { //盒狀濾波器邊長的一半 static const int HALF_KERNEL = BriefDescriptorExtractor::KERNEL_SIZE / 2 ; //計算點對中某一個像素的絕對坐標 int img_y = (int)(pt.pt.y + 0.5) + y; int img_x = (int)(pt.pt.x + 0.5) + x; //計算以該像素為中心,以KERNEL_SIZE為邊長的正方形內所有像素灰度值之和,本質上是均值濾波 return sum.at<int>(img_y + HALF_KERNEL + 1, img_x + HALF_KERNEL + 1 ) - sum.at<int>(img_y + HALF_KERNEL + 1, img_x - HALF_KERNEL) - sum.at<int>(img_y - HALF_KERNEL, img_x + HALF_KERNEL + 1 ) + sum.at<int>(img_y - HALF_KERNEL, img_x -
 HALF_KERNEL); }

5、根據每個點差分和結合公式形成描述子,其中是16字節表示一個特征點(描述子),每個字節通過比較積分圖像灰度值得到(8位)

//定義宏SMOOTHED,作用就是調用smoothedSum函數,SMOOTHED中的參數y和x表示相對於特征點的坐標 #define SMOOTHED(y,x) smoothedSum(sum, pt, y, x) //該描述符需要16個字節型變量,所以從desc[0]到desc[15] desc[0] = (uchar)( // 每個字節型變量由8位組成 //比較平滑處理以后的坐標為(-2, -1)和(7, -1)的兩個像素的灰度值,如公式1,並把結果移位到第7位上 ((SMOOTHED(-2, -1) < SMOOTHED(7, -1)) << 7) + ((SMOOTHED(-14, -1) < SMOOTHED(-3, 3)) << 6) + //第6位 ((SMOOTHED(1, -2) < SMOOTHED(11, 2)) << 5) + //第5位 ((SMOOTHED(1, 6) < SMOOTHED(-10, -7)) << 4) + //第4位 ((SMOOTHED(13, 2) < SMOOTHED(-1, 0)) << 3) + //第3位 ((SMOOTHED(-14, 5) < SMOOTHED(5, -3)) << 2) + //第2位 ((SMOOTHED(-2, 8) < SMOOTHED(2, 4)) << 1) + //第1位 ((SMOOTHED(-11, 8) < SMOOTHED(-15, 5)) << 0)); // 第0位 //以下省略 desc[1] = …… …… desc[15] = ……

上述源碼相對比較簡單大體流程分為:轉化為灰度圖,求取積分圖,去掉邊界干擾點,遍歷特征點根據特征點獲取盒裝區域內每個小塊差分值,由此生成描述子。在實際應用中,雖然點對都是按一定規則隨機選擇的,但在確定了補丁區域大小S的情況下,點對的坐標位置一旦隨機選定,就不再更改,自始自終都用這些確定下來的點對坐標位置。也就是說這些點對的坐標位置其實是已知的,在編寫程序的時候,這些坐標事先存儲在系統中,在創建描述符時,只要調用這些坐標即可。

 

2、opencv調用函數

class CV_EXPORTS BriefDescriptorExtractor : public DescriptorExtractor { public : static const int PATCH_SIZE = 48; //S為48即窗口為48x48 static const int KERNEL_SIZE = 9; // 高斯濾波器窗口為9x9 // bytes is a length of descriptor in bytes. It can be equal 16, 32 or 64 bytes. BriefDescriptorExtractor( int bytes = 32 );//保存特征的空間為32字節,即32x8=256bit
 ... ... ... }
#include <opencv2/core/core.hpp> #include <opencv2/highgui/highgui.hpp> #include <opencv2/imgproc/imgproc.hpp> #include <opencv2/features2d/features2d.hpp> using namespace cv; int main(int argc, char** argv) { Mat img_1 = imread("img1.png" ); Mat img_2 = imread("img2.png" ); // -- Step 1: Detect the keypoints using STAR Detector std::vector<KeyPoint> keypoints_1,keypoints_2; StarDetector detector; detector.detect(img_1, keypoints_1); detector.detect(img_2, keypoints_2); // -- Stpe 2: Calculate descriptors (feature vectors) BriefDescriptorExtractor brief; Mat descriptors_1, descriptors_2; brief.compute(img_1, keypoints_1, descriptors_1); brief.compute(img_2, keypoints_2, descriptors_2); //-- Step 3: Matching descriptor vectors with a brute force matcher BFMatcher matcher(NORM_HAMMING); std::vector<DMatch> mathces; matcher.match(descriptors_1, descriptors_2, mathces); // -- dwaw matches Mat img_mathes; drawMatches(img_1, keypoints_1, img_2, keypoints_2, mathces, img_mathes); // -- show imshow("Mathces" , img_mathes); waitKey(0 ); return 0
; }

3、實驗結果分析

下面的內容是參考http://blog.csdn.net/hujingshuang/article/details/46910259,中,其中講解特征描述的特點,通過編譯器看到描述子。在img1中檢測到14個特征點,img2中檢測到76個:

以img1為例,14個特征點,每個特征點用一個32字節(256bit)的二進制編碼描述,則有:

進一步查看上圖中第二個紅框的信息,這表明特征描述子存放的位置,起始地址是:0x01c08030,結束地址為:0x01c081f0。

可以看到,這就是最終提取到的特征點描述編碼了,一行32個字節,共14行,剛好對應img1的14個特征點。同理可知img2中得到了76個特征編碼。假如以img1中的特征為准,在img2中尋找與其匹配的特征點,結果如下:

可以看到,img1中的14個特征點依次與img2中的第74、47、... 、29特征點配成一對,最終匹配效果見上面的實驗結果圖。

四、總結

Brief是一種對特征點產生描述子的算法,用二進制表示,其占用內存空間小,計算快。

由於在計算中,是對一副圖像進行測試、計算,不具備尺度不變形以及旋轉不變性,同時對噪聲魯棒性較差

五、參考文獻

1、BRIEF特征點描述算法

2、特征描述子總結

3、源碼分析

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM