zoukankan      html  css  js  c++  java
  • 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、源码分析

  • 相关阅读:
    POJ 3253 Fence Repair
    POJ 2431 Expedition
    NYOJ 269 VF
    NYOJ 456 邮票分你一半
    划分数问题 DP
    HDU 1253 胜利大逃亡
    NYOJ 294 Bot Trust
    NYOJ 36 最长公共子序列
    HDU 1555 How many days?
    01背包 (大数据)
  • 原文地址:https://www.cnblogs.com/polly333/p/5421050.html
Copyright © 2011-2022 走看看