图像拼接之特征点匹配--距离筛选,余弦角筛选,相似度和ransac筛选

我们在做图像拼接时,最重要的一步就是关于特征点匹配的问题,我们常用到的特征点检测方法有SIFT,SURF以及ORB等,对于特征点匹配我们有Bruteforce方法,Flannbesed方法,这些方法处理完之后,或许特征点匹配效果还是达不到我们想要的结果,这个时候我们需要对我们匹配的特征点进行筛选。下面我来说一说常见的特征点筛选的方法

一丶特征点匹配
这里我们用SIFT方法做例子

Mat img1 = imread("D1.jpg");
Mat img2 = imread("D2.jpg");

//第一步,用SIFT算子检测关键点;
SiftFeatureDetector detector;
std::vector<KeyPoint> m_LeftKey, m_RightKey;//构造2个专门由点组成的点向量用来存储特征点
detector.detect(img1, m_LeftKey);//将img1图像中检测到的特征点存储起来放在m_LeftKey中
detector.detect(img2, m_RightKey);//同理

//计算特征向量
SiftDescriptorExtractor extractor;//定义描述子对象
cv::Mat descriptors1, descriptors2;//存放特征向量的矩阵
extractor.compute(srcImage1, m_LeftKey, descriptors1);
extractor.compute(srcImage2, m_RightKey, descriptors2);

//进行匹配
BruteForceMatcher<L2<float>>matcher;//定义一个burte force matcher对象
vector<DMatch> matches;//定义数据类型为matches的vector容器
matcher.match(descriptors1, descriptors2, matches);//匹配两个图像的特征矩阵  

结果如下、
原图:
这里写图片描述
这里写图片描述
匹配结果图:
这里写图片描述

效果不好,有很多错误点,下面就是要消除错误的匹配点

二丶特征点匹配原理
我们知道特征点匹配主要是将每一个相对应的描述子相减,计算出他们的欧式距离。
下面是我自己写的匹配代码,计算速度较慢,不过有助于理解匹配原理。
1)计算向量之间的欧式距离的函数

//每一个位置的描述子相减
double minkowsky(const vector<float>& v1, const vector<float>& v2, double m)
{
    assert(v1.size() == v2.size());//描述子应该都是128维
    double ret = 0.0;
    for (vector<double>::size_type i = 0; i != v1.size(); ++i)
    {
        ret += pow(abs(v1[i] - v2[i]), m);//累加每一个向量元素的相减平方值
    }
    return pow(ret, 1.0 / m);
}
//计算欧式距离
double euclidean(const vector<float>& v1, const vector<float>& v2)
{
    assert(v1.size() == v2.size());
    return minkowsky(v1, v2, 2.0);
}

2)找出距离最近的点
左图中一个点p1与右图中所有特征点计算欧式距离,找出其距离最近距离的点p2。找出p2点之后,将p2点与左图中所有点计算欧式距离,若距离最近的点为p1,则该两点为匹配点对


    //计算距离
    Mat m_left_row(1, 128, CV_32S);//创建一个1*128的矩阵 用来保存每一次读取的矩阵
    Mat m_right_row(1, 128, CV_32S);
    Mat m_rightL_row(1, 128, CV_32S);
    Mat m_leftR_row(1, 128, CV_32S);
    vector<float>left_des, right_des,rightL_des,leftR_des;
    float dist,dist2;
    vector<float>distance1,distance2;
    int index_l, index_r;
    int Count = 0;
    vector<KeyPoint>left_keypoint, right_keypoint;
    vector<DMatch> matches;
    DMatch matches1;
    for (int i = 0; i < keypoints1.size(); i++)
    {
        //将左图第i个特征点描述子矩阵读取出来
        MatRow(descriptor1, m_left_row, left_des, i);
        for (int j = 0; j < keypoints2.size(); j++)
        {
            MatRow(descriptor2, m_right_row, right_des, j);
            dist=euclidean(left_des, right_des);
        //  cout << "距离:"<<i<<","<<j <<":   "<< dist << endl;
            distance1.push_back(dist);//将与每一个距离保存下来
            right_des.clear();
        }
        left_des.clear();

        auto smallest_l = min_element(begin(distance1), end(distance1));
         index_r =distance(begin(distance1), smallest_l);
         //cout << "左边最小距离的位置是:" << index_r << endl;
         distance1.clear();


 //反过来再利用右边的最近距离特征点与左边的特征点求距离
        MatRow(descriptor2, m_rightL_row, rightL_des, index_r);
        for (int m = 0; m < keypoints1.size(); m++)
        {
            MatRow(descriptor1, m_leftR_row, leftR_des, m);
            dist2 = euclidean(rightL_des, leftR_des);
            distance2.push_back(dist2);
            leftR_des.clear();
        }
        rightL_des.clear();

        auto smallest_r = min_element(begin(distance2), end(distance2));
        index_l = distance(begin(distance2), smallest_r);
    //  cout << "左图索引:" << i << endl;
        //cout << "右边最小距离的位置是:" << index_l << endl;
        //cout<< endl;

        distance2.clear();

        if (index_l == i)
        {
            left_keypoint.push_back(keypoints1[i]);
            right_keypoint.push_back(keypoints2[index_r]);
            matches1 = { i,index_r,*smallest_l };
            matches.push_back(matches1);
        }   
    }

注:这个在opencv中BruteForce函数中有写好的

class CV_EXPORTS BruteForceMatcher : public BFMatcher
{
public:
    BruteForceMatcher( Distance d = Distance() ) : BFMatcher(Distance::normType,true) {(void)d;}
    virtual ~BruteForceMatcher() {}
};

一般情况下BFMatcher中的第二个参数为false,这是什么意思呢?意思就是左图p1在右图找到最近点p2之后就认为p1和p2是匹配点对了。
我们把这个参数改为true,这样的话p1在右图找到p2之后,还要再判断p2在左图中的最近点是否为p1,如果是,才认为是匹配点对。这样也能过滤一部分错误点。

三丶特征点筛选
上面那种改BFMatcher参数的方法可以减少一些错误的匹配点,下面我总结了一下几种筛选匹配点的方法。
1)距离筛选法

//计算匹配结果中距离的最大和最小值  
    //距离是指两个特征向量间的欧式距离,表明两个特征的差异,值越小表明两个特征点越接近  
    double max_dist = 0;
    double min_dist = 100;
    for (int i = 0; i<matches.size(); i++)
    {
        double dist = matches[i].distance;
        if (dist < min_dist) min_dist = dist;
        if (dist > max_dist) max_dist = dist;
    }
    cout << "最大距离:" << max_dist << endl;
    cout << "最小距离:" << min_dist << endl;

    //筛选出较好的匹配点  
    vector<DMatch> goodMatches;
    for (int i = 0; i<matches.size(); i++)
    {
        if (matches[i].distance < 0.5*(max_dist+min_dist))
        {
            goodMatches.push_back(matches[i]);
        }
    }

结果如下:
这里写图片描述
效果好了一些

2)余弦值筛选法
计算每一个匹配点对的余弦值,求出平均余弦值,然后将每一对余弦值与平均余弦值做对比,小于平均余弦值则提出,余弦值为1时相似度最高。

vector<float> vec_all1;//矩阵第一行的向量
    vector<float> vec_all2;
    vector<float>vec_all3;//用于清空vec_all1和vec_all2的向量
    double cosine_sum = 0;
    double cosine_average;
    double value;
    vector<double>value_all;
    for (int i = 0; i < goodMatches.size(); i++)
    {
        int *data1 = descriptors1.ptr<int>(goodMatches[i].queryIdx);
        int *data2 = descriptors2.ptr<int>(goodMatches[i].trainIdx);
        for (int j = 0; j < 128; j++)
        {
            int vec1 = data1[j];//读取描述子矩阵的一行的128个描述子,也就是某个特征点的描述子
            int vec2 = data2[j];
            vec_all1.push_back(vec1);//把描述子加入到一个向量中
            vec_all2.push_back(vec2);
        }
        value = cosine(vec_all1, vec_all2);//计算两个特征点的余弦值
        value_all.push_back(value);
        cosine_sum = cosine_sum + value;//将余弦值加总
        vec_all1.swap(vec_all3);
        vec_all2.swap(vec_all3);
    }
    cosine_average = cosine_sum / goodMatches.size();//求取平均余弦值
    cout << "余弦平均值为:" << cosine_average << endl;
    vector<DMatch>angle_matches;
    for (int n = 0; n < goodMatches.size(); n++)
    {
        if (value_all[n] > cosine_average) //与平均余弦值作比较 大于平均余弦值的加入到匹配中
        {
            angle_matches.push_back(goodMatches[n]);
        }
    }
    cout << "角度筛选之后匹配点数" << angle_matches.size() << endl;
    cout << "角度筛选筛选掉匹配点个数" << goodMatches.size() - angle_matches.size() << endl;

注:上述余弦值筛选法所依赖的函数如下

//计算夹角
double dotProduct(const vector<float>& v1, const vector<float>& v2)
{assert(v1.size() == v2.size());
double ret = 0.0;
for (vector<float>::size_type i = 0; i != v1.size(); ++i)
{
    ret += v1[i] * v2[i];
}
return ret;
}
//向量模
double module(const vector<float>& v)
{
    double ret = 0.0;
    for (vector<float>::size_type i = 0; i != v.size(); ++i)
    {
        ret += v[i] * v[i];
    }
    return sqrt(ret);
}

//计算余弦值
double cosine(const vector<float>& v1, const vector<float>& v2)
{
    assert(v1.size() == v2.size());
    return dotProduct(v1, v2) / (module(v1) * module(v2));
}

3)相似度筛选
这个方法我感觉不是特别好,也不是特别常用,不过也可以筛选掉一部分特征点,原理就是:距离相似度=1/(1+距离),利用距离相似度的平均值方法进行筛选,大于平均值则为好的匹配点对

vector<DMatch> goodMatches;//保存筛选完之后的特征点
    double similarity;
    double similarity_sum = 0;
    double similarity_average;//相似度平均值
    vector<double>similarity_all;
    for (int i = 0; i<matches.size(); i++)
    {
        similarity = 1 / (1 + matches[i].distance); //距离相似度 = 1 / (1 + 距离)
        similarity_sum += similarity;
        similarity_all.push_back(similarity);
    }
    similarity_average = similarity_sum / matches.size();

    for (int i = 0; i < matches.size(); i++)
    {
        if (similarity_all[i] >similarity_average)
        {
            goodMatches.push_back(matches[i]);
        }
    }

4)ransac筛选法
这个关于ransac的介绍很多,大家可以找来看一看,我给出以下我的ransac过程的代码

//RANSAC匹配过程
    vector<DMatch> m_Matches = angle_matches;

    cout<<"m_Matches="<<m_Matches.size()<<endl;
    // 分配空间
    int ptCount = (int)m_Matches.size();
    //cout<<"m_Matches="<<ptCount<<endl;
    Mat p1(ptCount, 2, CV_32F);
    Mat p2(ptCount, 2, CV_32F);
    //cout<<"p1="<<p1<<endl;
    // 把Keypoint转换为Mat
    Point2f pt;
    for (int i = 0; i<ptCount; i++)
    {
        pt = m_LeftKey[m_Matches[i].queryIdx].pt;
        p1.at<float>(i, 0) = pt.x;
        p1.at<float>(i, 1) = pt.y;
        pt = m_RightKey[m_Matches[i].trainIdx].pt;
        p2.at<float>(i, 0) = pt.x;
        p2.at<float>(i, 1) = pt.y;
    }
    //cout<<"p1="<<p1<<endl;//图1的匹配点坐标
    //cout<<"p2="<<p2<<endl;//图2的匹配点坐标
    // 用RANSAC方法计算F(基础矩阵)
    Mat m_Fundamental;
    vector<uchar> m_RANSACStatus;       // 这个变量用于存储RANSAC后每个点的状态
    findFundamentalMat(p1, p2, m_RANSACStatus, FM_RANSAC);
    // 计算内点个数
    int OutlinerCount = 0;
    for (int i = 0; i<ptCount; i++)
    {
        if (m_RANSACStatus[i] == 0)    // 状态为0表示外点
        {
            OutlinerCount++;
        }
    }
    int InlinerCount = ptCount - OutlinerCount;   // 计算内点   
    cout << "内点数为:" << InlinerCount << endl;
    cout << "外点数为:" << OutlinerCount << endl;
    // 这三个变量用于保存内点和匹配关系
    vector<Point2f> m_LeftInlier;
    vector<Point2f> m_RightInlier;
    vector<DMatch> m_InlierMatches;
    m_InlierMatches.resize(InlinerCount);
    m_LeftInlier.resize(InlinerCount);
    m_RightInlier.resize(InlinerCount);
    InlinerCount = 0;
    float inlier_minRx = img1.cols;        //用于存储内点中右图最小横坐标,以便后续融合
                                           //cout<<"inlier="<<inlier_minRx<<endl;
    for (int i = 0; i<ptCount; i++)
    {
        if (m_RANSACStatus[i] != 0)
        {
            m_LeftInlier[InlinerCount].x = p1.at<float>(i, 0);
            m_LeftInlier[InlinerCount].y = p1.at<float>(i, 1);
            m_RightInlier[InlinerCount].x = p2.at<float>(i, 0);
            m_RightInlier[InlinerCount].y = p2.at<float>(i, 1);
            m_InlierMatches[InlinerCount].queryIdx = InlinerCount;
            m_InlierMatches[InlinerCount].trainIdx = InlinerCount;

            if (m_RightInlier[InlinerCount].x<inlier_minRx) inlier_minRx = m_RightInlier[InlinerCount].x;   //存储内点中右图最小横坐标
            InlinerCount++;
        }
    }
    //cout<<"inlier="<<inlier_minRx<<endl;
    // 把内点转换为drawMatches可以使用的格式
    vector<KeyPoint> key1(InlinerCount);
    vector<KeyPoint> key2(InlinerCount);
    KeyPoint::convert(m_LeftInlier, key1);
    KeyPoint::convert(m_RightInlier, key2);
    // 显示计算F过后的内点匹配
    Mat OutImage;
    drawMatches(img1, key1, img2, key2, m_InlierMatches, OutImage, CV_RGB(255, 0, 255), CV_RGB(0, 255, 0));
    imshow("RANSAC match features", OutImage);
    imwrite("RansacMatch.tif", OutImage);

这里写图片描述
5)最近邻比次近邻方法
最近邻比次近邻法效果比较好,一般用过这个方法之后可以直接进行ransac一般也可以达到很好的效果,这个方法在我的另一篇博客中写了,下面是链接:
http://blog.csdn.net/weixin_38285131/article/details/78487782

这几种方法可以互相混着用,如果效果好的话也可以只用一种方法,总之只要达到我们想要的效果就可以啦。有问题可以跟我交流,仅供参考吧

  • 15
    点赞
  • 98
    收藏
    觉得还不错? 一键收藏
  • 7
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值