ORB、SIFT、SURF特征提取与匹配

  • 1、ORB特征提取与匹配

ORB算法分为两部分,分别是特征点提取和特征点描述。特征提取是由FAST(Features from  Accelerated Segment Test)算法发展来的,特征点描述是根据BRIEF(Binary Robust IndependentElementary Features)特征描述算法改进的。ORB特征是将FAST特征点的检测方法与BRIEF特征描述子结合起来,并在它们原来的基础上做了改进与优化。

对于FAST角点特征的提取主要分为:粗提取,机器学习的方法筛选最优特征点,非极大值抑制去除局部较密集特征点,特征点的尺度不变形,特征点的旋转不变性5个步骤。

BRIEF 是 Binary Robust Independent Elementary Features 的简称,它的作用是根据一组关键点创建二元特征向量。它是在一个特征点的邻域内,选择n对像素点pi、qi(i=1,2,…,n)。然后比较每个点对的灰度值的大小。如果I(pi)> I(qi),则生成二进制串中的1,否则为0。所有的点对都进行比较,则生成长度为n的二进制串。一般n取128、256或512,opencv默认为256。

具体的详细细节参见:

https://blog.csdn.net/qq_20791919/article/details/80176643

https://www.cnblogs.com/alexme/p/11345701.html

在ORB-SLAM算法中对ORB特征提取与匹配进行了改进,使得图像中提取的特征点分布更加均匀。主要思想是:设定提取的ORB特征点数量为1000个。将图像进行网格划分,设置FAST角点的最大阈值为12,最小为5。然后使用最大阈值对每一个网格图像都进行FAST特征点提取,如果没有提取到特征点,则减小阈值再次提取,如若到了最小阈值还没有提取到特征点,那么跳过该网格。在匹配过程中,与传统的暴力匹配不同,搜索对应网格区域内的ORB特征点作为匹配点,提高匹配的正确性。

在ubuntu系统上使用OpenCV3.3.1,对OpenCV中的ORB特征提取与匹配算法进行测试,,测试代码(SLAM14讲代码略做修改)为:

#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/features2d/features2d.hpp>
#include <opencv2/highgui/highgui.hpp>

using namespace std;
using namespace cv;

int main ( int argc, char** argv )
{
    if ( argc != 3 )
    {
        cout<<"usage: feature_extraction img1 img2"<<endl;
        return 1;
    }
    //-- 读取图像
    Mat img_1 = imread ( argv[1], CV_LOAD_IMAGE_COLOR );
    Mat img_2 = imread ( argv[2], CV_LOAD_IMAGE_COLOR );

    //-- 初始化,提取1000个ORB特征点
    std::vector<KeyPoint> keypoints_1, keypoints_2;
    Mat descriptors_1, descriptors_2;
    Ptr<FeatureDetector> detector = ORB::create(1000);
    Ptr<DescriptorExtractor> descriptor = ORB::create(1000);
    // Ptr<FeatureDetector> detector = FeatureDetector::create(detector_name);
    // Ptr<DescriptorExtractor> descriptor = DescriptorExtractor::create(descriptor_name);
    Ptr<DescriptorMatcher> matcher  = DescriptorMatcher::create ( "BruteForce-Hamming" );

    //-- 第一步:检测 Oriented FAST 角点位置
    detector->detect ( img_1,keypoints_1 );
    detector->detect ( img_2,keypoints_2 );

    //-- 第二步:根据角点位置计算 BRIEF 描述子
    descriptor->compute ( img_1, keypoints_1, descriptors_1 );
    descriptor->compute ( img_2, keypoints_2, descriptors_2 );

    Mat outimg1, outimg2;
    drawKeypoints( img_1, keypoints_1, outimg1, Scalar(0, 255, 0), DrawMatchesFlags::DEFAULT );
    drawKeypoints( img_2, keypoints_2, outimg2, Scalar(0, 255, 0), DrawMatchesFlags::DEFAULT );

    imwrite("./result/orb_feature.png", outimg1);

    //-- 第三步:对两幅图像中的BRIEF描述子进行匹配,使用 Hamming 距离
    vector<DMatch> matches;
    //BFMatcher matcher ( NORM_HAMMING );
    matcher->match ( descriptors_1, descriptors_2, matches );

    //-- 第四步:匹配点对筛选
    double min_dist=10000, max_dist=0;

    //找出所有匹配之间的最小距离和最大距离, 即是最相似的和最不相似的两组点之间的距离
    for ( int i = 0; i < descriptors_1.rows; i++ )
    {
        double dist = matches[i].distance;
        if ( dist < min_dist ) min_dist = dist;
        if ( dist > max_dist ) max_dist = dist;
    }
    
    // 仅供娱乐的写法
    min_dist = min_element( matches.begin(), matches.end(), [](const DMatch& m1, const DMatch& m2) {return m1.distance<m2.distance;} )->distance;
    max_dist = max_element( matches.begin(), matches.end(), [](const DMatch& m1, const DMatch& m2) {return m1.distance<m2.distance;} )->distance;

    printf ( "-- Max dist : %f \n", max_dist );
    printf ( "-- Min dist : %f \n", min_dist );

    //当描述子之间的距离大于两倍的最小距离时,即认为匹配有误.但有时候最小距离会非常小,设置一个经验值30作为下限.
    std::vector< DMatch > good_matches;
    for ( int i = 0; i < descriptors_1.rows; i++ )
    {
        if ( matches[i].distance <= max ( 2*min_dist, 30.0 ) )
        {
            good_matches.push_back ( matches[i] );
        }
    }

    //-- 第五步:绘制匹配结果
    Mat img_match;
    Mat img_goodmatch;
    drawMatches ( outimg1, keypoints_1, outimg2, keypoints_2, matches, img_match, Scalar(0, 255, 255), Scalar(0, 255, 0));
    drawMatches ( outimg1, keypoints_1, outimg2, keypoints_2, good_matches, img_goodmatch, Scalar(0, 255, 255), Scalar(0, 255, 0));

    imwrite("./result/orb_match1.png", img_match);
    imwrite("./result/orb_match2.png", img_goodmatch);

    return 0;
}

CMakeLists可以简单的写为:

cmake_minimum_required( VERSION 2.8 )
project( orb )

set( CMAKE_BUILD_TYPE "Release" )
set( CMAKE_CXX_FLAGS "-std=c++11 -O3" )

find_package( OpenCV REQUIRED )

include_directories( ${OpenCV_INCLUDE_DIRS} )

add_executable( feature_extraction feature_extraction.cpp  )
target_link_libraries( feature_extraction ${OpenCV_LIBS} )

使用时需要读入图片,并对保存结果的路径进行修改。若在Windows上使用,需要对输入的参数进行修改。以上代码的测试结果为:             

   

可以看出:特征点的分布较为集中,特和那个匹配筛选前误匹配较多,筛选后结果相对较好。对于均匀分布ORB特征提取与匹配的测试代码为:https://github.com/zwl2017/ORB_Feature

测试结果为:

    

2、SIFT和SURF特征提取与匹配

尺度不变特征转换(Scale-invariant feature transform或SIFT)是一种电脑视觉的算法用来侦测与描述影像中的局部性特征,它在空间尺度中寻找极值点,并提取出其位置、尺度、旋转不变量,此算法由 David Lowe在1999年所发表,2004年完善总结。Lowe将SIFT算法分解为如下四步:

尺度空间极值检测:搜索所有尺度上的图像位置。通过高斯微分函数来识别潜在的对于尺度和旋转不变的兴趣点。

关键点定位:在每个候选的位置上,通过一个拟合精细的模型来确定位置和尺度。关键点的选择依据于它们的稳定程度。

方向确定:基于图像局部的梯度方向,分配给每个关键点位置一个或多个方向。所有后面的对图像数据的操作都相对于关键点的方向、尺度和位置进行变换,从而提供对于这些变换的不变性。

关键点描述:在每个关键点周围的邻域内,在选定的尺度上测量图像局部的梯度。这些梯度被变换成一种表示,这种表示允许比较大的局部形状的变形和光照变化

其具体的详细细节可以参考:https://blog.csdn.net/zddblog/article/details/7521424

可以通过C++代码自己实现SIFT算法:https://blog.csdn.net/maweifei/article/details/58227605

由于OpenCV3中的SIFT和SURF算法被移植到contrib模块中,因此为了简单起见,使用OpenCV2进行实验的测试,SIFT代码如下:

///SIFT特征点匹配  
#include "opencv2/opencv.hpp"  
#include "opencv2/nonfree/nonfree.hpp"//SIFT相关  
#include "opencv2/legacy/legacy.hpp"//匹配器相关  
#include <iostream>  

using namespace cv;
using namespace std;

int main()
{
	//1.SURF特征点提取——detect()方法    
	Mat srcImg1 = imread("nn_left.jpg", CV_LOAD_IMAGE_COLOR);
	Mat srcImg2 = imread("nn_right.jpg", CV_LOAD_IMAGE_COLOR);

	double t = getTickCount();//当前滴答数
	Mat dstImg1, dstImg2;
	//定义SIFT特征检测类对象    
	SiftFeatureDetector siftDetector;//SiftFeatureDetector是SIFT类的别名    
	//定义KeyPoint变量    
	vector<KeyPoint> keyPoints1;
	vector<KeyPoint> keyPoints2;
	//特征点检测    
	siftDetector.detect(srcImg1, keyPoints1);
	siftDetector.detect(srcImg2, keyPoints2);
	//绘制特征点(关键点)    
	drawKeypoints(srcImg1, keyPoints1, dstImg1);
	drawKeypoints(srcImg2, keyPoints2, dstImg2);
	//显示结果
	resize(dstImg1, dstImg1, Size(dstImg1.cols*0.5, dstImg1.rows*0.5));
	imshow("dstImg1", dstImg1);
	imshow("dstImg2", dstImg2);
	//2.特征点描述符(特征向量)提取——compute()方法    
	SiftFeatureDetector descriptor;//siftDescriptorExtractor是SIFT类的别名     
	Mat description1;
	Mat description2;
	descriptor.compute(srcImg1, keyPoints1, description1);
	descriptor.compute(srcImg2, keyPoints2, description2);
	//3.使用Flann匹配器进行匹配——FlannBasedMatcher类的match()方法    
	FlannBasedMatcher matcher;//实例化Flann匹配器  
	vector<DMatch> matches;
	matcher.match(description1, description2, matches);
	//4.对匹配结果进行筛选(依据DMatch结构体中的float类型变量distance进行筛选)    
	float minDistance = 100;
	float maxDistance = 0;
	for (int i = 0; i < matches.size(); i++)
	{
		if (matches[i].distance < minDistance)
			minDistance = matches[i].distance;
		if (matches[i].distance > maxDistance)
			maxDistance = matches[i].distance;
	}
	cout << "minDistance: " << minDistance << endl;
	cout << "maxDistance: " << maxDistance << endl;
	vector<DMatch> goodMatches;
	for (int i = 0; i < matches.size(); i++)
	{
		if (matches[i].distance < 2 * minDistance)
		{
			goodMatches.push_back(matches[i]);
		}
	}
	//5.绘制匹配结果——drawMatches()    
	Mat dstImg3;
	Mat dstImg4;
	drawMatches(srcImg1, keyPoints1, srcImg2, keyPoints2, goodMatches, dstImg3);
	resize(dstImg3, dstImg4, Size(dstImg3.cols*0.5, dstImg3.rows*0.5));

	t = ((double)getTickCount() - t) / getTickFrequency();
	cout << "算法用时:" << t << "秒" << endl;
	imshow("dstImg4", dstImg4);
	waitKey(0);
	return 0;
}

SURF的代码与SIFT类似,仅仅是实例化的类不一样,代码如下:

///SURF特征点匹配  
#include "opencv2/opencv.hpp"  
#include "opencv2/nonfree/nonfree.hpp"//SURF相关  
#include "opencv2/legacy/legacy.hpp"//匹配器相关  
#include <iostream>  

using namespace cv;
using namespace std;

int main()
{
	//1.SURF特征点提取——detect()方法    
	Mat srcImg1 = imread("nn_left.jpg", CV_LOAD_IMAGE_COLOR);
	Mat srcImg2 = imread("nn_right.jpg", CV_LOAD_IMAGE_COLOR);

	double t = getTickCount();//当前滴答数 
	Mat dstImg1, dstImg2;
	//定义SURF特征检测类对象    
	SurfFeatureDetector surfDetector;//SurfFeatureDetector是SURF类的别名    
	//定义KeyPoint变量    
	vector<KeyPoint> keyPoints1;
	vector<KeyPoint> keyPoints2;
	//特征点检测    
	surfDetector.detect(srcImg1, keyPoints1);
	surfDetector.detect(srcImg2, keyPoints2);
	//绘制特征点(关键点)    
	drawKeypoints(srcImg1, keyPoints1, dstImg1);
	drawKeypoints(srcImg2, keyPoints2, dstImg2);
	//显示结果    
	resize(dstImg1, dstImg1, Size(dstImg1.cols*0.5, dstImg1.rows*0.5));
	imshow("dstImg1", dstImg1);
	imshow("dstImg2", dstImg2);
	//2.特征点描述符(特征向量)提取——compute()方法    
	SurfDescriptorExtractor descriptor;//SurfDescriptorExtractor是SURF类的别名     
	Mat description1;
	Mat description2;
	descriptor.compute(srcImg1, keyPoints1, description1);
	descriptor.compute(srcImg2, keyPoints2, description2);
	//3.使用Flann匹配器进行匹配——FlannBasedMatcher类的match()方法    
	FlannBasedMatcher matcher;//实例化Flann匹配器  
	vector<DMatch> matches;
	matcher.match(description1, description2, matches);
	//4.对匹配结果进行筛选(依据DMatch结构体中的float类型变量distance进行筛选)    
	float minDistance = 100;
	float maxDistance = 0;
	for (int i = 0; i < matches.size(); i++)
	{
		if (matches[i].distance < minDistance)
			minDistance = matches[i].distance;
		if (matches[i].distance > maxDistance)
			maxDistance = matches[i].distance;
	}
	cout << "minDistance: " << minDistance << endl;
	cout << "maxDistance: " << maxDistance << endl;
	vector<DMatch> goodMatches;
	for (int i = 0; i < matches.size(); i++)
	{
		if (matches[i].distance < 2 * minDistance)
		{
			goodMatches.push_back(matches[i]);
		}
	}
	//5.绘制匹配结果——drawMatches()    
	Mat dstImg3;
	drawMatches(srcImg1, keyPoints1, srcImg2, keyPoints2, goodMatches, dstImg3);
	resize(dstImg3, dstImg3, Size(dstImg3.cols*0.5, dstImg3.rows*0.5));

	t = ((double)getTickCount() - t) / getTickFrequency();
	cout << "算法用时:" << t << "秒" << endl;

	imshow("dstImg3", dstImg3);
	waitKey(0);
	return 0;
}

 

  • 4
    点赞
  • 37
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值