基于OpenCV的图像匹配----SIFT算法(二)
阅读原文时间:2021年04月21日阅读:1

    SIFT的全称是Scale Invariant Feature Transform,尺度不变特征变换,由加拿大教授David G.Lowe提出的。SIFT特征对旋转、尺度缩放、亮度变化等保持不变性,是一种非常稳定的局部特征。

    有关sift匹配的理论问题可以参考这篇文章:

SIFT特征详解 - Brook_icv - 博客园
https://www.cnblogs.com/wangguchangqing/p/4853263.html

    下面我们直接进行基于opencv的算法实现

头文件

#include <opencv2/opencv.hpp>
#include <opencv2/nonfree/features2d.hpp>
#include <iostream>

载入图像并转为灰度图

Mat templateImage , templateImage_gray;
templateImage = imread("map.png");
cvtColor(templateImage, templateImage_gray, CV_BGR2GRAY);

Mat srcImage, srcImage_gray;
srcImage = imread("test1.png");
cvtColor(srcImage, srcImage_gray, CV_BGR2GRAY);    

调用特征检测子公用接口和特征描述子提取公用接口

SiftFeatureDetector featureDetector;
SiftDescriptorExtractor featureExtractor;

检测SIFT关键点、提取训练图像描述符

vector<KeyPoint> template_keyPoint;    
Mat templateDescription;
featureDetector.detect(templateImage_gray, template_keyPoint);
featureExtractor.compute(templateImage_gray, template_keyPoint, templateDescription);

vector<KeyPoint> src_keyPoint;
Mat srcDescriptor;
featureDetector.detect(srcImage_gray, src_keyPoint);
featureExtractor.compute(srcImage_gray, src_keyPoint, srcDescriptor);

    /*
        在keypoint定义中包含了pt(0,0), size(0), angle(-1), response(0), octave(0), class_id(-1)
        pt:为关键点的坐标
        size:为有意义的关键点邻域的直径
        response:选择最强关键点的响应。可用于进一步分类或再取样
        octave:提取关键点的尺度(金字塔层)
        class_id:对象类(如果关键字需要向它们所属的对象聚集)
    */
    /*
        void FeatureDetector::detect(const Mat& image, vector& keypoints, const Mat& mask) const (C++ function, in 特征检测子公用接口)
        image: 为输入图像
        keypoints: 为输出的关键特征点

        void DescriptorExtractor::compute(const Mat& image, vector& keypoints, Mat& descriptors) const (C++ function, in 特征描述子提取公用接口)
        image: 为输入图像
        keypoints:为输入的关键特征点
        descriptors:为输出的特征描述子
    */

使用特征向量临近匹配

FlannBasedMatcher matcher;
vector<DMatch> matches;
matcher.match(templateDescription, srcDescriptor, matches);

    /*
        FlannBasedMatcher为特征向量临近匹配,算法更快但是找到的是最近邻近似匹配
        BFMatcher为暴力匹配法,总是尝试所有可能的匹配,从而使得它总能够找到最佳匹配
    */

关于匹配方法可以参考这篇文章:

sift算法特征点如何匹配? - 知乎
https://www.zhihu.com/question/23371175

计算向量距离的最大值与最小值

float max_dist = 0.0, min_dist = 100.0;
    for (int i = 0; i < templateDescription.rows; i++)
    {
        if (matches[i].distance > max_dist)
        {
            max_dist = matches[i].distance;
        }
        if (matches[i].distance < min_dist)
        {
            min_dist = matches[i].distance;
        }
    }

得到距离小于V倍最小距离的匹配

vector<DMatch> goodMatches;
    for (int i = 0; i < matches.size(); i++)
    {
        if (matches[i].distance <= 2*min_dist)
        {
            goodMatches.push_back(matches[i]);
        }
    }

下面开始绘制匹配信息

/*绘制匹配点并显示窗口*/
    Mat dstImage;
    drawMatches(srcImage, src_keyPoint, templateImage, template_keyPoint, goodMatches, dstImage);

    /*定义两个局部变量*/
    vector<Point2f> obj;
    vector<Point2f> scene;

    /*从匹配成功的匹配对中获取关键点*/
    for (unsigned int i = 0; i < goodMatches.size(); i++)
    {
        obj.push_back(src_keyPoint[goodMatches[i].queryIdx].pt);
        scene.push_back(template_keyPoint[goodMatches[i].trainIdx].pt);
    }
    /*
        queryIdx:是测试图像的特征点描述符(descriptor)的下标,同时也是描述符对应特征点(keypoint)的下标。
        trainIdx:是样本图像的特征点描述符的下标,同样也是相应的特征点的下标。
    */

    /*计算多个二维点对之间的最优单映射变换矩阵 H(3行x3列)*/
    Mat H = findHomography(obj, scene, CV_RANSAC);
    /*
        Mat findHomography( InputArray srcPoints, InputArray dstPoints,int method=0, double ransacReprojThreshold=3, OutputArray mask=noArray());
        srcPoints:源平面中点的坐标矩阵
        dstPoints:目标平面中点的坐标矩阵
        method:0 - 利用所有点的常规方法 RANSAC-基于RANSAC的鲁棒算法 LMEDS - 最小中值鲁棒算法 RHO - PROSAC-基于PROSAC的鲁棒算法
        maxIters    RANSAC算法的最大迭代次数,默认值为2000。
        confidence  可信度值,取值范围为0到1.
    */

    /*从待测图片中获取角点*/
    vector<Point2f> obj_corners(4);
    obj_corners[0] = cvPoint(0, 0);
    obj_corners[1] = cvPoint(srcImage.cols, 0);
    obj_corners[2] = cvPoint(srcImage.cols, srcImage.rows);
    obj_corners[3] = cvPoint(0, srcImage.rows);
    vector<Point2f> scene_corners(4);

    /*进行透视变换*/
    perspectiveTransform(obj_corners, scene_corners, H);
    /*
        void perspectiveTransform(InputArray src, OutputArray dst, InputArray m );
        src: 输入图像
        dst:输出图像
        m:   变换矩阵
    */

    /*输出匹配点坐标*/
    int locx = (scene_corners[0].x + scene_corners[1].x + scene_corners[2].x + scene_corners[3].x) / 4;
    int locy = (scene_corners[0].y + scene_corners[1].y + scene_corners[2].y + scene_corners[3].y) / 4;

    /*画实心点*/
    Point p(locx + srcImage.cols, locy);
    circle(dstImage, p, 5, Scalar(0, 0, 255), -1);

    /*绘制出角点之间的直线*/
    line(dstImage, scene_corners[0] + Point2f(static_cast<float>(srcImage.cols), 0), scene_corners[1] + Point2f(static_cast<float>(srcImage.cols), 0), Scalar(255, 0, 123), 4);
    line(dstImage, scene_corners[1] + Point2f(static_cast<float>(srcImage.cols), 0), scene_corners[2] + Point2f(static_cast<float>(srcImage.cols), 0), Scalar(255, 0, 123), 4);
    line(dstImage, scene_corners[2] + Point2f(static_cast<float>(srcImage.cols), 0), scene_corners[3] + Point2f(static_cast<float>(srcImage.cols), 0), Scalar(255, 0, 123), 4);
    line(dstImage, scene_corners[3] + Point2f(static_cast<float>(srcImage.cols), 0), scene_corners[0] + Point2f(static_cast<float>(srcImage.cols), 0), Scalar(255, 0, 123), 4);

上面的代码我已经打包发在csdn上了

https://download.csdn.net/download/weixin_42521239/11149276

如果没有积分的小伙伴也可以在评论里留下你的邮箱,我发给你。