FREAK算法是ICCV 2012上的一篇关于特征点检测与匹配的论文《FREAK: Fast Retina Keypoint》上提出的,从文章标题中可以看出来该算法的一个特点是快速,另外一个特点就是该算法是被人眼识别物体的原理上得到启发提出的。
看过我之前博文的可能知道,我到现在已经把SIFT算法、ORB算法、BRIEF算法和BRISK算法都进行了介绍。可以看出BRIEF、ORB和BRISK都是特征点周围的邻域像素点对之间的比较形成的二进制串作为特征点的描述符,这种做法有着快速和占用内存低的优点,在如今的移动计算中有很大的优势,但是也遗留了一些问题。比如,如何确定特征点邻域中哪些像素点对进行比较,如何匹配它们呢?作者提出,这种优化的趋势与自然界中通过简单的规则解决复杂的问题是相符的。作者提出的FREAK算法就是通过模仿人眼视觉系统而完成的,下面我们来介绍一下FREAK算法。
特征点检测是特征点匹配的第一个阶段,FAST算法是一种可以快速检测特征点的算法,而且有AGAST算法对FAST算法的加速版本。本文中特征点检测方法与BRISK中特征点检测方法相同,都是建立多尺度的图像,在不同图像中分别使用FAST算法检测特征点,具体的做法见我的博文特征点检测——BRISK算法介绍,这里就不在详细的说明了。
既然FREAK算法是通过人眼的视觉系统得到启发提出的算法,那么我们首先来看看人眼的视觉系统。
作者提出,在人眼的视网膜区域中,感受光线的细胞的密度是不相同的。人眼的视网膜根据感光细胞的密度分成了四个区域:foveola、fovea、parafoveal和perifoveal,如下图所示:
这里面fovea区域是负责接收高分辨率的图像,而低分辨率的图像是perfoveal区域中形成的。
在我以前博文中提到的BRIEF、ORB算法中特征点邻域中的采样点对是随机生成的,而BRISK算法则是采用平均采样的方式生成的这些采样点。FREAK算法中采样模式与BRISK算法中采样模式类似,但是它的模式与人眼视觉系统中的模式更为接近,如下图所示:
如上所述,FREAK算法也是用二进制串对特征点进行描述,这里用 F 进行表示,则有
人眼的fovea区域由于有比较密集的感光细胞,因此可以捕捉更加高分辨率的图像,因此fovea区域在识别和匹配的过程中起着关键的作用。perifoveal区域的感光细胞则没有那么密集,因此它只能捕捉到模糊的图像,因此首先用他们来进行物体位置的估计。这就是人眼识别和匹配的大体流程,而作者就模仿这种流程对特征点进行匹配。
首先使用前128个匹配点对,作为粗略信息,如果距离小于某个阈值,再使用剩余的匹配点对(精密信息)进行匹配。这种级联的操作在很大程度上加速了匹配的速度,大约有超过90%的候选点可以通过前128个匹配点对进行排除。这种级联的操作的图示如下图所示:
为了保证算法具有方向不变性,需要为每个特征点增加方向信息,由于BRISK算法与FREAK算法对特征点邻域的采样点格式相近,因此FREAK算法特征点方向的计算与BRISK算法也比较相近。BRISK算法是通过计算具有长距离的采样点对的梯度来表示特征点的方向(具体描述见我之前介绍BRISK算法的博文),FREAK算法则采用其中45个距离长的、对称的采样点计算其梯度,如下图所示:
计算公式为
下面是调用Opencv的FREAK算法进行特征点匹配的代码示例,使用了双向验证(即图1中第i个点匹配图2中第j个点,同时要求图二中第j个点也要匹配图1中的第i个点)以及RANSAN去除错配点。
#include
#include
#include
#include
#include
#include
using namespace std;
using namespace cv;
void detectFREAK(Mat& img1, Mat& img2, vector & kp1,
vector & 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 & good_matches, const vector & kp1, const vector & 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 RANSAC_state;//RANSAC过滤后各匹配点对的状态
//计算基础矩阵,并记录各个匹配点对的状态,0为匹配不正确,1为正确
fundamental = cv::findFundamentalMat(image1Points, image2Points, RANSAC_state, cv::FM_RANSAC);
vector ::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 & kp1, vector & kp2,
Mat& des1, Mat& des2, string &str)
{
BruteForceMatcher matcher;
vector matches1to2;
vector 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 kp1, kp2;
Mat des1, des2;
detectFREAK(img1, img2, kp1, kp2, des1, des2);
matchFREAK(img1, img2, kp1, kp2, des1, des2, string("matchImage.jpg"));
return 0;
}