FREAK算法介绍

本文为转载,原博客地址:http://blog.csdn.net/lhanchao/article/details/52744514

FREAK算法是ICCV 2012上的一篇关于特征点检测与匹配的论文《FREAK: Fast Retina Keypoint》上提出的,从文章标题中可以看出来该算法的一个特点是快速,另外一个特点就是该算法是被人眼识别物体的原理上得到启发提出的。 
看过我之前博文的可能知道,我到现在已经把SIFT算法、ORB算法、BRIEF算法和BRISK算法都进行了介绍。可以看出BRIEF、ORB和BRISK都是特征点周围的邻域像素点对之间的比较形成的二进制串作为特征点的描述符,这种做法有着快速和占用内存低的优点,在如今的移动计算中有很大的优势,但是也遗留了一些问题。比如,如何确定特征点邻域中哪些像素点对进行比较,如何匹配它们呢?作者提出,这种优化的趋势与自然界中通过简单的规则解决复杂的问题是相符的。作者提出的FREAK算法就是通过模仿人眼视觉系统而完成的,下面我们来介绍一下FREAK算法。

一、特征点检测

特征点检测是特征点匹配的第一个阶段,FAST算法是一种可以快速检测特征点的算法,而且有AGAST算法对FAST算法的加速版本。本文中特征点检测方法与BRISK中特征点检测方法相同,都是建立多尺度的图像,在不同图像中分别使用FAST算法检测特征点,具体的做法见我的博文特征点检测——BRISK算法介绍,这里就不在详细的说明了。

二、二进制串特征描述符

既然FREAK算法是通过人眼的视觉系统得到启发提出的算法,那么我们首先来看看人眼的视觉系统。

2.1 Human retina

作者提出,在人眼的视网膜区域中,感受光线的细胞的密度是不相同的。人眼的视网膜根据感光细胞的密度分成了四个区域:foveola、fovea、parafoveal和perifoveal,如下图所示: 
这里写图片描述 
这里面fovea区域是负责接收高分辨率的图像,而低分辨率的图像是perfoveal区域中形成的。

2.2 采样模式

在我以前博文中提到的BRIEF、ORB算法中特征点邻域中的采样点对是随机生成的,而BRISK算法则是采用平均采样的方式生成的这些采样点。FREAK算法中采样模式与BRISK算法中采样模式类似,但是它的模式与人眼视觉系统中的模式更为接近,如下图所示: 


这里写图片描述 

上图中每一个黑点代表一个采样点,每个圆圈代表一块感受野,具体在处理时时对该部分图像进行高斯模糊处理,以降低噪声的影响,而且每个圆圈的半径表示了高斯模糊的标准差。这种采样模式与BRISK算法的不同之处在于,每个感受野与感受野之间有重叠的部分。与ORB和BRIEF算法的不同之处在于,ORB和BRIEF算法中的高斯模糊半径都是相同的,而这里采用了这种不同大小的高斯模糊的核函数。作者提出,正是这些不同之处,导致最后的结果更加优秀。通过重叠的感受野,可以获得更多的信息,这些信息可以使最终的描述符更具有独特性。而不同大小的感受野在人体的视网膜中也有这样类似的结构。 
最终FREAK算法的采样结构为6、6、6、6、6、6、6、1,这里的6代表每层中有6个采样点并且这6个采样点在一个同心圆中,一共有7个同心圆,最后的1代表的是特征点。

2.3 Coarse-to-fine descriptor

如上所述,FREAK算法也是用二进制串对特征点进行描述,这里用FF进行表示,则有

F=0a<N2aT(Pa)F=∑0≤a<N2aT(Pa)
这里的 T(Pa)T(Pa) 是一个函数, PaPa 是一个采样点对,如果 I(Pr1a)>I(Pr2a)I(Par1)>I(Par2) ,那么 T(Pa)=1T(Pa)=1 ,否则, T(Pa)=0T(Pa)=0 I(Pr1a)I(Par1) 表示的采样点经过高斯模糊后得到的灰度值。 
由于一个特征点中有数十个采样点,那么就有上千个候选的采样点对,然后有些采样点对对于特征描述并没有什么用处,因此需要对特征点对进行筛选,作者采用了一种与ORB算法类似的算法对特征点对进行筛选: 
(1)作者利用50000个特征点建立了一个矩阵 DD ,矩阵中的每一行表示一个特征点,这一行中使用的是上面提到的特征点建立的所有可能的特征点对的比较结果。如我们对每个特征点提取了43个采样点,那么43个采样点可能构建43*42 = 903个采样点对,那么矩阵 DD 中一行有903列; 
(2)计算矩阵 DD 中每一列的平均值,为了得到独特性好的特征,方差应该较大,这就要求该列的平均值应该接近于0.5; 
(3)根据各列中的方差由大到小进行排序; 
(4)取该矩阵的前 kk 列,在论文中取了前512列,因为作者发现前512列是最相关的,且再增加列数对于结果提升也不是很大。 
作者把得到的这512个采样点对分成了4组,每128个为一组,把这些采样点对进行连线,得到的效果如下: 


这里写图片描述 

作者发现这四组的连线中第一组主要在外围,之后的每一组连线逐渐内缩,最后一组的连线主要在中央部分,这与人眼视觉系统很相似。人眼视觉系统中也是首先通过perifoveal区域对感兴趣的物体的位置进行估计,然后通过感光细胞更加密集的fovea区域进行验证,最终确定物体的位置。

2.4 Saccadic search

人眼的fovea区域由于有比较密集的感光细胞,因此可以捕捉更加高分辨率的图像,因此fovea区域在识别和匹配的过程中起着关键的作用。perifoveal区域的感光细胞则没有那么密集,因此它只能捕捉到模糊的图像,因此首先用他们来进行物体位置的估计。这就是人眼识别和匹配的大体流程,而作者就模仿这种流程对特征点进行匹配。 
首先使用前128个匹配点对,作为粗略信息,如果距离小于某个阈值,再使用剩余的匹配点对(精密信息)进行匹配。这种级联的操作在很大程度上加速了匹配的速度,大约有超过90%的候选点可以通过前128个匹配点对进行排除。这种级联的操作的图示如下图所示: 
这里写图片描述

2.5 Orientation

为了保证算法具有方向不变性,需要为每个特征点增加方向信息,由于BRISK算法与FREAK算法对特征点邻域的采样点格式相近,因此FREAK算法特征点方向的计算与BRISK算法也比较相近。BRISK算法是通过计算具有长距离的采样点对的梯度来表示特征点的方向(具体描述见我之前介绍BRISK算法的博文),FREAK算法则采用其中45个距离长的、对称的采样点计算其梯度,如下图所示: 
这里写图片描述 
计算公式为

O=1MPoG(I(Pr1o)I(Pr2o))Pr1oPr2o||Pr1oPr2o||O=1M∑Po∈G(I(Por1)−I(Por2))Por1−Por2||Por1−Por2||

三、Opencv代码实现

下面是调用Opencv的FREAK算法进行特征点匹配的代码示例,使用了双向验证(即图1中第i个点匹配图2中第j个点,同时要求图二中第j个点也要匹配图1中的第i个点)以及RANSAN去除错配点。

#include <iostream>
#include <opencv2\core\core.hpp>
#include <opencv2\highgui\highgui.hpp>
#include <opencv2\features2d\features2d.hpp>
#include <opencv2\legacy\legacy.hpp>
#include <time.h>
using namespace std;
using namespace cv;

void detectFREAK(Mat& img1, Mat& img2, vector<KeyPoint>& kp1,
    vector<KeyPoint>& kp2, Mat& des1, Mat& des2)
{
    //使用BRISK算法中的特征点检测算法检测特征点,存入kp1和kp2中
    BRISK brisk(80,4);      //40表示阈值,4表示有4个octave
    brisk(img1, Mat(), kp1);
    brisk(img2, Mat(), kp2);
    //FREAK算法,得到特征点描述符,存入des1和des2中
    FREAK freak;
    freak.compute(img1, kp1, des1);
    freak.compute(img2, kp2, des2);
}

void runRANSAC(vector<DMatch>& good_matches, const vector<KeyPoint>& kp1, const vector<KeyPoint>& kp2)
{
    Mat image1Points(good_matches.size(), 2, CV_32F);//存放第一张图像的匹配点的坐标
    Mat image2Points(good_matches.size(), 2, CV_32F);
    for (size_t i = 0; i < good_matches.size(); i++)
    {
        int sub_des1 = good_matches[i].queryIdx;
        int sub_des2 = good_matches[i].trainIdx;
        float *ptr1 = image1Points.ptr<float>(i);
        float *ptr2 = image2Points.ptr<float>(i);
        ptr1[0] = kp1[sub_des1].pt.x;   ptr1[1] = kp1[sub_des1].pt.y;
        ptr2[0] = kp2[sub_des2].pt.x;   ptr2[1] = kp2[sub_des2].pt.y;
    }

    Mat fundamental;//基础矩阵
    vector<uchar> RANSAC_state;//RANSAC过滤后各匹配点对的状态
    //计算基础矩阵,并记录各个匹配点对的状态,0为匹配不正确,1为正确
    fundamental = cv::findFundamentalMat(image1Points, image2Points, RANSAC_state, cv::FM_RANSAC);

    vector<DMatch>::iterator iter;
    int i = 0;
    for (iter = good_matches.begin(); iter != good_matches.end();)
    {
        if (RANSAC_state[i] == 0)
        {
            iter = good_matches.erase(iter);
        }
        else
            iter++;
        i++;
    }
}

void matchFREAK(Mat& img1, Mat& img2, vector<KeyPoint>& kp1, vector<KeyPoint>& kp2,
    Mat& des1, Mat& des2, string &str)
{
    BruteForceMatcher<HammingLUT> matcher;
    vector<DMatch> matches1to2;
    vector<DMatch> matches2to1;
    matcher.match(des1, des2, matches1to2);
    matcher.match(des2, des1, matches2to1);
    int* flag = new int[des2.rows];
    for (int i = 0; i < des2.rows; i++)
    {
        flag[i] = matches2to1[i].trainIdx;
    }

    double max_dist = 0; double min_dist = 100;
    for (int i = 0; i < des1.rows; i++)
    {
        double dist = matches1to2[i].distance;
        if (dist < min_dist) min_dist = dist;
        if (dist > max_dist) max_dist = dist;
    }
    cout << str << " " << max_dist << " " << min_dist << endl;
    std::vector< DMatch > good_matches;
    for (int i = 0; i < des1.rows; i++)
    {
        if (matches1to2[i].distance < max_dist)
        {
            if (matches2to1[matches1to2[i].trainIdx].trainIdx == i)
                good_matches.push_back(matches1to2[i]);
        }
    }
    for (size_t i = 0; i < kp1.size(); i++)
    {
        circle(img1, Point(kp1[i].pt.x, kp1[i].pt.y), 2, Scalar(0, 0, 255), 2);
    }
    for (size_t i = 0; i < kp2.size(); i++)
    {
        circle(img2, Point(kp2[i].pt.x, kp2[i].pt.y), 2, Scalar(0, 0, 255), 2);
    }
    runRANSAC(good_matches, kp1, kp2);

    Mat img_matches;
    drawMatches(img1, kp1, img2, kp2,
        good_matches, img_matches, Scalar(0, 255, 0), Scalar(0, 0, 255),
        vector<char>(), DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS);
    imwrite(str, img_matches);
}

int main()
{
    Mat img1 = imread("1.jpg");
    Mat img2 = imread("2.jpg");
    vector<KeyPoint> kp1, kp2;
    Mat des1, des2;
    detectFREAK(img1, img2, kp1, kp2, des1, des2);
    matchFREAK(img1, img2, kp1, kp2, des1, des2, string("matchImage.jpg"));
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113

FREAK匹配的结果并不好,结果如下图所示: 
这里写图片描述


  • 1
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值