opencv下SIFT特征点的提取与匹配
SIFT:尺度不变特征转换,是一种电脑视觉的算法用来侦测与描述影像中的局部特征。SIFT是基于图像外观的兴趣点而与图像的大小旋转无关,对于噪声、光线、微观的视角容忍度也极高。
SIFT介绍
Lowe将SIFT算法分解为四步:
- 尺度空间极值检测:搜索所有尺度上的图像位置。通过高斯微分函数识别潜在的对于尺度旋转不变的兴趣点。
- 关键点定位:每个候选位置上,通过一个拟合精细的模型来确定位置和尺度。关键点的选择依据与他们的稳定程度。
- 方向确定:基于图像的局部梯度方向,分配给每个关键点位置一个或多个方向。
- 关键点描述:在每个关键点周围的邻域内,在选定的尺度上测量图像的局部梯度。
特征点的描述
对于求出的每个特征点,拥有三个信息描述:位置,尺度及方向。Lowe建议描述子使用在关键点尺度空间内4x4的窗口中计算8个方向的梯度信息,共计4x4x8=128维向量表征。
opencv中特征点的提取匹配
在OpenCV中,实现了很多种特征,如SIFT,FAST等,这些特征的实现各不相同,但是都是从一个公共抽象基类派生出来的。下面进行详细说明:
FeatureDetector是关键点检测类的抽象基类,其已经实现的具体类有:
- class FastFeatureDetector
- class GoodFeaturesToTrackDetector
- class MserFeatureDetector
- class StarFeatureDetector
- class SiftFeatureDetector
- class SurfFeatureDetector
要使用某一种检测器,可以直接调用FeatureDetector的工厂来创建,该工厂是一个静态方法,也可以像我的示例代码中那样显式的创建,如下:
FeatureDetector *pDetector = new SurfFeatureDetector;
类似的提取关键点的描述向量类抽象基类DescriptorExtractor
- class SiftDescriptorExtractor
- class SurfDescriptorExtractor
- class CalonderDescriptorExtractor
- class BriefDescriptorExtractor
- class OpponentColorDescriptorExtractor
匹配器的抽象基类DescriptorMatcher
- class BruteForceMatcher
- class FlannBasedMatcher
这里我们主要来介绍提取SIFT特征点
1、提取单个图片的SIFT坐标
SiftFeatureDetector : Opencv中Sift算子的特征提取是在SiftFeatureDetector类中的detect方法实现的,提取的是特征点的坐标。
SiftFeatureDetector siftDetector;
vector<KeyPoint> keyPoint1; // 这里keypoint是一种数据结构,用来存储产生的每个特征点的位置信息
siftDetector.detect(image1, keyPoint1);
2、计算SIFT特征向量
SiftDescriptorExtractor : 特征点描述是在SiftDescriptorExtractor类中的compute方法实现的,计算出来的是每个SIFT点的128维SIFT特征描述向量。
SiftDescriptorExtractor siftDescriptor;
Mat imageDesc1, imageDesc2;
siftDescriptor.compute(image1, keyPoint1, imageDesc1);
siftDescriptor.compute(image2, keyPoint2, imageDesc2);
3、进行SIFT匹配
DescriptorMatcher是匹配特征向量的抽象类,在OpenCV2中的特征匹配方法都继承自该类。
BruteForceMatcher:暴力匹配法(FlannBasedMatcher选择性匹配法,与暴力匹配法相比匹配点数少,但质量高,剔除了一些误匹配的点,用法一样)
BruteForceMatcher<L2<float>> matcher; // 对应float类型的匹配方式
vector<DMatch> matchePoints; // 存储匹配点的向量
matcher.match(descriptors1, descriptors2, matchePoints, Mat());
Opencv中的DMatch:是一种数据结构用来存储匹配点的信息。
匹配过程中很可能发生错误的匹配,错误的匹配主要有两种:匹配的特征点事错误的,图像上的特征点无法匹配。常用的删除错误的匹有:
1. 交叉过滤
如果第一幅图像的一个特征点和第二幅图像的一个特征点相匹配,则进行一个相反的检查,即将第二幅图像上的特征点与第一幅图像上相应特征点进行匹配,如果匹配成功,则认为这对匹配是正确的。
OpenCV中的BFMatcher已经包含了这种过滤 BFMatcher matcher(NORM_L2,true),在构造BFMatcher是将第二个参数设置为true。
2. 比率测试。
knnMatch,可设置K = 2 ,即对每个匹配返回两个最近邻描述符,仅当第一个匹配与第二个匹配之间的距离足够小时,才认为这是一个匹配。
BruteForceMatcher<cv::L2<float>> matcher;
matcher.knnMatch(descriptors1,descriptors2, matches1, 2); // 保存和参考特征点最接近的两个匹配点
附上参考代码
#include <opencv2/opencv.hpp>
#include <highgui/highgui.hpp>
#include <features2d/features2d.hpp>
#include <nonfree/nonfree.hpp>
#include <vector>
#include <fstream>
#include <nonfree/features2d.hpp>
#include<opencv2/legacy/legacy.hpp>
using namespace cv;
using namespace std;
int main()
{
Mat input1 = imread("F:/test/03.jpg", 1);
Mat input2 = imread("F:/test/04.jpg", 1);
if (input1.empty() || input2.empty())
{
cout << "不能正常加载图片" << endl;
system("pause");
return -1;
}
/************************************************************************/
/*下面进行提取特征点*/
/************************************************************************/
SiftFeatureDetector feature;
vector<KeyPoint> keypoints1;
feature.detect(input1, keypoints1);
Mat output1;
drawKeypoints(input1, keypoints1, output1);
vector<KeyPoint> keypoints2;
feature.detect(input2, keypoints2);
Mat output2;
drawKeypoints(input2, keypoints2, output2);
imshow("提取特征点后的03.jpg", output1);
imshow("提取特征点后的04.jpg", output2);
imwrite("提取特征点后的03.jpg", output1);
imwrite("提取特征点后的04.jpg", output2);
cout << "03提取的特征点数为:" << keypoints1.size() << endl;
cout << "04的特征点数为:" << keypoints2.size() << endl;
/************************************************************************/
/* 下面进行特征向量提取 */
/************************************************************************/
SiftDescriptorExtractor descript;
Mat description1;
descript.compute(input1, keypoints1, description1);
Mat description2;
descript.compute(input2, keypoints2, description2);
ofstream file1("./03img.feature");//提取到的特征保存在这个文件中,128维,整数
file1 << endl << description1 << endl;
ofstream file2("./04img.feature");//提取到的特征保存在这个文件中,128维,整数
file2 << endl << description2 << endl;
/************************************************************************/
/* 下面进行特征向量临近匹配 */
/************************************************************************/
vector<DMatch> matches;
BruteForceMatcher< L2<float> > matcher;
Mat image_match;
matcher.match(description1, description2, matches);
/************************************************************************/
/* 下面计算向量距离的最大值与最小值 */
/************************************************************************/
double max_dist = 0, min_dist = 100;
for (int i = 0; i < description1.rows; i++)
{
if (matches.at(i).distance>max_dist)
{
max_dist = matches[i].distance;
}
if (matches[i].distance<min_dist)
{
min_dist = matches[i].distance;
}
}
cout << "最小距离为" << min_dist << endl;
cout << "最大距离为" << max_dist << endl;
/************************************************************************/
/* 得到原始的匹配点 */
/************************************************************************/
drawMatches(input1, keypoints1, input2, keypoints2, matches, image_match);
imshow("原始匹配后的图片", image_match);
imwrite("原始匹配后的图片.jpg", image_match);
cout << "原始匹配的特征点数为:" << matches.size() << endl;
/************************************************************************/
/* 得到最近比次近距离<0.65的匹配点 */
/************************************************************************/
vector<vector<DMatch>> ratio_matches;
vector<DMatch> good_matches2;
int remove = 0; // 记录移除的数据个数
matcher.knnMatch(description1, description2, ratio_matches, 2);
for (int i = 0; i < ratio_matches.size(); i++)
{
if (ratio_matches[i].size()>1)
{
// 在第二幅图上找到两个匹配点
if (ratio_matches[i][0].distance / ratio_matches[i][1].distance > 0.65)
{
ratio_matches[i].clear(); // 删除匹配数据
remove++;
}
else
{
good_matches2.push_back(ratio_matches[i][0]);
}
}
else
{
// 第二幅图上没有找到两个匹配点
ratio_matches[i].clear();
remove++;
}
}
Mat image_match2;
drawMatches(input1, keypoints1, input2, keypoints2, good_matches2, image_match2);
imshow("最近比次近匹配后的图片", image_match2);
imwrite("最近比次近匹配后的图片.jpg", image_match2);
cout << "最近比次近匹配的特征点数为:" << good_matches2.size() << endl;
/************************************************************************/
/* 基础矩阵F过滤SIFT特征点 */
/************************************************************************/
vector<KeyPoint> alignedKps1, alignedKps2;
for (size_t i = 0; i < matches.size(); i++) {
alignedKps1.push_back(keypoints1[matches[i].queryIdx]);
alignedKps2.push_back(keypoints2[matches[i].trainIdx]);
}
//Keypoints to points
vector<Point2f> ps1, ps2;
for (unsigned i = 0; i < alignedKps1.size(); i++)
ps1.push_back(alignedKps1[i].pt);
for (unsigned i = 0; i < alignedKps2.size(); i++)
ps2.push_back(alignedKps2[i].pt);
Mat m_Fundamental;
vector<uchar> m_RANSACStatus;
m_Fundamental = findFundamentalMat(ps1, ps2, m_RANSACStatus, FM_RANSAC);
// 计算野点个数
int OutlinerCount = 0;
int ptCount = matches.size();
for (int i = 0; i<matches.size(); i++)
{
if (m_RANSACStatus[i] == 0) // 状态为0表示野点
{
OutlinerCount++;
}
}
// 计算内点
vector<KeyPoint> leftInlier;
vector<KeyPoint> rightInlier;
vector<DMatch> inlierMatch;
// 内点个数
int inlinerCount = 0;
for (unsigned i = 0; i < matches.size(); i++) {
if (m_RANSACStatus[i] != 0){
leftInlier.push_back(alignedKps1[i]);
rightInlier.push_back(alignedKps2[i]);
matches[i].trainIdx = inlinerCount;
matches[i].queryIdx = inlinerCount;
inlierMatch.push_back(matches[i]);
inlinerCount++;
}
}
Mat image_match3;
drawMatches(input1, keypoints1, input2, keypoints2, inlierMatch, image_match3);
imshow("F矩阵优化后匹配后的图片", image_match3);
imwrite("F矩阵优化后匹配后的图片.jpg", image_match3);
cout << "F矩阵优化后匹配的特征点数为:" << inlierMatch.size() << endl;
/************************************************************************/
/* H矩阵过滤SIFT特征点 */
/************************************************************************/
//find homography matrix and get inliers mask
vector<uchar> inliersMask(ps1.size());
Mat homography;
double reprojectionThreshold = 3;
homography = findHomography(ps1, ps2, CV_FM_RANSAC, reprojectionThreshold, inliersMask);
vector<DMatch> inliers;
for (size_t i = 0; i < inliersMask.size(); i++){
if (inliersMask[i])
inliers.push_back(matches[i]);
}
Mat image_match4;
drawMatches(input1, keypoints1, input2, keypoints2, inliers, image_match4);
imshow("H矩阵优化后H匹配后的图片", image_match4);
imwrite("H矩阵优化后H匹配后的图片.jpg", image_match4);
cout << "H矩阵优化后H匹配的特征点数为:" << inliers.size() << endl;
cout << "H = " << endl << " " << homography << endl << endl;
waitKey(0);
return 0;
}