SIFT特征提取与匹配

opencv下SIFT特征点的提取与匹配

SIFT:尺度不变特征转换,是一种电脑视觉的算法用来侦测与描述影像中的局部特征。SIFT是基于图像外观的兴趣点而与图像的大小旋转无关,对于噪声、光线、微观的视角容忍度也极高。

SIFT介绍

Lowe将SIFT算法分解为四步:

  1. 尺度空间极值检测:搜索所有尺度上的图像位置。通过高斯微分函数识别潜在的对于尺度旋转不变的兴趣点。
  2. 关键点定位:每个候选位置上,通过一个拟合精细的模型来确定位置和尺度。关键点的选择依据与他们的稳定程度。
  3. 方向确定:基于图像的局部梯度方向,分配给每个关键点位置一个或多个方向。
  4. 关键点描述:在每个关键点周围的邻域内,在选定的尺度上测量图像的局部梯度。

特征点的描述

对于求出的每个特征点,拥有三个信息描述:位置,尺度及方向。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;
}
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值