Freak特征描述+BruteForceMatcher匹配+RANSAC剔除误匹配
时间一晃飞快,前几日的广州之行让人难忘,置身炎热的户外仿佛蒸桑拿一样。这一段时间一直在研究一种从一幅图片中找到给定目标的方法,而基于局部特征提取并且进行特征的匹配可以实现。
目前局部特征描述子有以下几种,
- 2004年发展成熟的SIFT
- 后来改进的SURF,它们的描述符是高维度整型的数字。
- 2010年出现的BRIEF是一种二进制的描述符,这种描述符在提高计算速度方面给出了不错的思路,但是不具备旋转不变,尺度不变,对噪声也比较敏感(虽然它采用了高斯平滑来抗噪声)
- 2011年出现的ORB算法同样是二进制,但是解决了旋转不变和噪声敏感的问题,但是不具备尺度不变性。
- 2011年同时出现了BRISK算法,解决旋转不变,尺度不变和抗噪声问题,网上有视频,实时性也不错,先有个直观认识,爽死。链接[(http://v.youku.com/v_show/id_XMTI5MzI3Mzk0OA==.html)]
- 2012年提出FREAK算法,其实是在BRISK上的改进。具备BRISK的优点。这篇论文中作者表示FREAK算法可以outperform以前所有的描述子。(注意:这里是特征描述子,因为论文中没有给出特征点检测方法,只有特征点的描述子)
看完了综述,我决定用FREAK来实现。足足耗了我15天,今天我这一段所得写下,勉励自己,服务他人。
先贴上代码
/************************************************************************
* Copyright(c) 2016 唯疯
* All rights reserved.
*
* Brief: FAST特征点提取以及FREAK描述子的图像匹配,基于OpenCV2.4.8
* Version: 1.0
* Author: 唯疯
* Date: 2016/07/21
* Address: 广州&北京
************************************************************************/
#include <opencv2/core/core.hpp>
#include <opencv2/features2d/features2d.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/nonfree/features2d.hpp>
#include <opencv2/legacy/legacy.hpp>
#include <iostream>
#include <vector>
using namespace std;
using namespace cv;
int main()
{
Mat img1_src = imread("im5.jpg",0);
Mat img2_src = imread("im6.jpg",0);
//FastFeatureDetector fast(40);
SurfFeatureDetector fast(2000,4);
FREAK extractor;
vector<KeyPoint> keypoints1,keypoints2;
Mat descriptor1,descriptor2;
vector<DMatch> final_matches;
vector<DMatch> matches;
double t = (double)getTickCount();
fast.detect(img1_src,keypoints1);
fast.detect(img2_src,keypoints2);
//drawKeypoints(img1_src,keypoints1,img1_src,Scalar(0,255,0));
//drawKeypoints(img2_src,keypoints2,img2_src,Scalar(0,255,0));//问题在这里!!!醉了,这里的问题,浪费了我5天,欧耶,就是整整5天,由于我想在这里看一看检测出来的特征点是啥样的,就跟父母想第一眼就立刻看到刚出生的婴儿是一个心情的。结果,后来特征匹配就几乎没有什么正确匹配,害我还以为是FREAK这个描述子有问题呢。于是就各种找问题,看论文,翻墙逛论坛。最后一个不经意间发现了。问题就是:这俩个语句是把特征点又原封不动的画到了img1_src中,也就是原图像里面,而后来我进行特征点描述的时候,就直接在画满了特征点的图片下进行描述,而不是原图!不是原图啊!是充满了特征点的图片!所以后期再进行匹配的时候,显然,各种乱匹配,就跟隔壁家小狗似的,见了猫都想干坏事。于是乎,我直接注释掉了这两句!
extractor.compute(img1_src,keypoints1,descriptor1);
extractor.compute(img2_src,keypoints2,descriptor2);
BFMatcher matcher(NORM_HAMMING,true);//暴力匹配,并且进行crosscheck,就是说第二个参数选择true。
matcher.match(descriptor1,descriptor2,matches);
final_matches=matches;
cout<<"number of total_matches : "<<final_matches.size()<<endl;
//接下来是RANSAC剔除误匹配
vector<Point2f> querymatches, trainmatches;
vector<KeyPoint> p1,p2;
for(int i=0;i<final_matches.size();i++)
{
p1.push_back(keypoints1[final_matches[i].queryIdx]);
p2.push_back(keypoints2[final_matches[i].trainIdx]);
}
for(int i=0;i<p1.size();i++)
{
querymatches.push_back(p1[i].pt);
trainmatches.push_back(p2[i].pt);
}
cout<<querymatches[1]<<" and "<<trainmatches[1]<<endl;
vector<uchar> status;
Mat h = findHomography(querymatches,trainmatches,status,CV_FM_RANSAC,10);
int index=0;
vector<DMatch> super_final_matches;
for (int i=0;i<final_matches.size();i++)
{
cout<<status[i];
if (status[i] != 0)
{
super_final_matches.push_back(final_matches[i]);
index++;
}
}
cout<<"number of inlier_matches : "<<index<<endl;
Mat imgMatch;
drawMatches(img1_src,keypoints1,img2_src,keypoints2,super_final_matches,imgMatch);
imshow("imgMatch",imgMatch);
t = ((double)getTickCount()-t)/getTickFrequency();
cout<<" total time [s] : "<<t<<endl;
waitKey(0);
return 0;
}
一:
首先说说特征点检测,为什么我用surf,而不用速度很快的fast或者Brisk,我自己做了测试,发现用fast和brisk的话,再用freak描述,最终匹配效果并不好,比sift要差…我其实不太明白为什么,因为有一些后期的论文对比效果说freak综合效果最好,然而我却得不到这样的结果。要继续钻研了,大家有想法欢迎交流,哦不,欢迎指点!
二:
RANSAC的这个算法,请用opencv库中的cvFindHomography,不要用cvFindFundamentalMat,因为这两个是不一样的,字面上简单看一个是找到单应性矩阵,一个是找到基础矩阵,这两个到底有神马区别???
《计算机视觉中的多视图几何》中这样描述:
- 2D单应:一幅2D图像中点集到另一幅图像中点集的射影变换。
- 基本矩阵:它使一个队所有的点都是的x`Fx=0成立的奇异矩阵F。这是一个3*3的奇异矩阵,而且秩为2,不可逆。它是2维图像上的点通过对极线束约束的映射,是2维到1维的映射。
具体的大家可以看书或者百度,或者等以后我有空了,再具体描述。其实我也没有太懂哈哈哈!还要再看看!慎言慎言!
之前,我曾经自己编写过一个RANSAC的算法啊,但是效果不是特别理想,由于RANSAC算法本身也比较耗时,各路大神们也都提出了很多改进方法,不知道opencv跟进这些改进算法没有?推荐综述型文章《A Universal Framework for Random
Sample Consensus》,里面提到的prosac,lo-ransac速度提升很多!大家自行汲取,我想以后再写一些关于RANSAC的博文。
三:
代码中用到BFMatcher这个类,对象的参数有两个BFMatcher::BFMatcher(int normType=NORM_L2, bool crossCheck=false )。第一个指的是距离,如果是sift或者surf描述的话,就选择NORM_L2和NORM_L2;ORB,BRISK,FREAK这种二进制的描述子就用汉明距离,也就是NORM_HAMMING;NORM_HAMMING2是用作ORB的。第二个参数是是否crossCheck,最好选择true,这个也经过我实验验证,选择true相当于knn匹配中将k置1,也就是只返回最好的那一组匹配,而不是多个近邻。(注意:如果要用knnmatch的话,这个crossCheck的参数就选择false)。这种只返回最好的一组匹配的方式可以作为ratio test的一种替代选择。实验证明,如果选择false,那么将会返回一对多匹配,和多对一的匹配,后期用ransac的效果可能非常不理想。crossCheck就是交叉匹配,剔除不好的匹配。有论文支撑,大家感兴趣可以去搜。