- 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;
}