0. 前言

当两台摄像机拍摄同一场景时,它们会在不同视角拍摄到相同的元素。我们已经学习了特征点匹配,在本节中,我们将学习如何利用两个视图之间的对极约束来更可靠地匹配图像特征。
我们将遵循以下原则:当匹配两个图像之间的特征点时,只接受落在相应极线上的匹配。为了能够检查是否满足此条件,必须知道基本矩阵,但我们需要较好的匹配来估计这个矩阵,这似乎成了一个先有鸡还是先有蛋的问题。在本节中,我们将学习如何联合计算基本矩阵和一组良好的匹配。

1. 基于随机样本一致匹配图像

我们的目标是能够计算两个视图之间的基本矩阵和一组良好的匹配,为此,需要使用对极约束验证所有特征点的对应关系。

1.1 计算基本矩阵与匹配集

(1) 创建 RobustMatcher 类封装鲁棒匹配过程:

class RobustMatcher {
    private:
        // 指向特征点检测器对象的指针
        cv::Ptr<cv::FeatureDetector> detector;
        // 指向特征描述符提取器对象的指针
        cv::Ptr<cv::DescriptorExtractor> descriptor;
        int normType;
        float ratio;
        bool refineF;
        bool refineM;
        double distance;
        double confidence;  // 置信度水平
    public:
        RobustMatcher(const cv::Ptr<cv::FeatureDetector>& detector,
                    const cv::Ptr<cv::DescriptorExtractor>& descriptor=cv::Ptr<cv::DescriptorExtractor>()) :
                detector(detector),descriptor(descriptor), normType(cv::NORM_L2),
                ratio(0.8f), refineF(true), refineM(true), confidence(0.98), distance(1.0) {
            if (!this->descriptor) {
                this->descriptor = this->detector;
            }
        }

使用 cv::FeatureDetectorcv::DescriptorExtractor 接口以便用户可以通过名称选择创建方法,也可以使用定义的 setter 方法 setFeatureDetectorsetDescriptorExtractor 指定创建方法。

(2) 类中最重要的是 match 方法,它返回匹配、检测到的关键点和估计的基本矩阵。该方法分为四个步骤:

    // 使用 RANSAC 匹配特征点
    cv::Mat match(cv::Mat& image1, cv::Mat& image2,     // 输入图像
            std::vector<cv::DMatch>& matches,       // 输出匹配和关键点
            std::vector<cv::KeyPoint>& keypoints1,
            std::vector<cv::KeyPoint>& keypoints2,
            int check=CROSSCHECK) {                 // 检查类型
    // 1. 检测特征点
    detector->detect(image1, keypoints1);
    detector->detect(image2, keypoints2);
    std::cout << "Number of feature points (1): " << keypoints1.size() << std::endl;
    std::cout << "Number of feature points (2): " << keypoints2.size() << std::endl;
    // 2. 提取特征描述符
    cv::Mat descriptors1, descriptors2;
    descriptor->compute(image1,keypoints1,descriptors1);
    descriptor->compute(image2,keypoints2,descriptors2);
    std::cout << "descriptor matrix size: " << descriptors1.rows << " by " << descriptors1.cols << std::endl;
    // 3. 匹配两张图像描述符
    cv::BFMatcher matcher(normType,         // 距离度量
                    check==CROSSCHECK);     // 交叉检查标记
    // 匹配向量
    std::vector<std::vector<cv::DMatch> > matches1;
    std::vector<std::vector<cv::DMatch> > matches2;
    std::vector<cv::DMatch> outputMatches;
    // 比率检查时调用 knnMatch
    if (check==RATIOCHECK || check==BOTHCHECK) {
        matcher.knnMatch(descriptors1, descriptors2,
                    matches1,       // 匹配向量
                    2);             // 返回两个最近邻居
        std::cout << "Number of matched points 1->2: " << matches1.size() << std::endl;
        if (check==BOTHCHECK) {
            matcher.knnMatch(descriptors2, descriptors1,
                    matches2,       // 匹配向量
                    2);             // 返回两个最近邻居
            std::cout << "Number of matched points 2->1: " << matches2.size() << std::endl;
        }
    }
    // 选择检查方法
    switch (check) {
        case CROSSCHECK:
            matcher.match(descriptors1,descriptors2,outputMatches);
            std::cout << "Number of matched points 1->2 (after cross-check): " << outputMatches.size() << std::endl;
            break;
        case RATIOCHECK:
            ratioTest(matches1,outputMatches);
            std::cout << "Number of matched points 1->2 (after ratio test): " << outputMatches.size() << std::endl;
            break;
        case BOTHCHECK:
            ratioAndSymmetryTest(matches1,matches2,outputMatches);
            std::cout << "Number of matched points 1->2 (after ratio and cross-check): " << outputMatches.size() << std::endl;
            break;
        case NOCHECK:
        default:
            matcher.match(descriptors1,descriptors2,outputMatches);
            std::cout << "Number of matched points 1->2: " << outputMatches.size() << std::endl;
            break;
    }
    // 4. 使用 RANSAC 验证匹配
    cv::Mat fundamental= ransacTest(outputMatches, keypoints1, keypoints2, matches);
    std::cout << "Number of matched points (after RANSAC): " << matches.size() << std::endl;
    return fundamental;
}

前两步检测特征点并计算它们的描述符;然后使用 cv::BFMatcher 类进行特征匹配,使用 crosscheck 标志来获得更高质量的匹配;第四步是本节引入的新概念,通过一个额外的过滤测试,使用基本矩阵来拒绝不遵守对极约束的匹配。

(3) 随机抽样一致算法 (RANdom SAmple Consensus, RANSAC) 采用迭代的方式从一组包含离群的被观测数据中估算出数学模型的参数。接下来,基于 RANSAC 方法进行过滤测试,即使在匹配集中仍然存在异常值时,也可以计算基本矩阵,编写函数 ransacTest

// 使用 RANSAC 标示较好的匹配
cv::Mat ransacTest(const std::vector<cv::DMatch>& matches,
                std::vector<cv::KeyPoint>& keypoints1,
                std::vector<cv::KeyPoint>& keypoints2,
                std::vector<cv::DMatch>& outMatches) {
    std::vector<cv::Point2f> points1, points2;
    for(std::vector<cv::DMatch>::const_iterator it=matches.begin(); it!=matches.end(); ++it) {
        points1.push_back(keypoints1[it->queryIdx].pt);
        points2.push_back(keypoints2[it->trainIdx].pt);
    }
    // 使用 RANSAC 计算 F 矩阵
    std::vector<uchar> inliers(points1.size(), 0);
    cv::Mat fundamental = cv::findFundamentalMat(
            points1, points2,       // 匹配点
            inliers,                // 匹配状态
            cv::FM_RANSAC,          // RANSAC 方法
            distance,               // 到极线的距离
            confidence);            // 置信度
    // 提取正确匹配
    std::vector<uchar>::const_iterator itIn = inliers.begin();
    std::vector<cv::DMatch>::const_iterator itM = matches.begin();
    for (; itIn!=inliers.end(); ++itIn, ++itM) {
        if (*itIn) {
            outMatches.push_back(*itM);
        }
    }
    return fundamental;
}

(4) F F F 矩阵计算之前需要将 keypoints 转换成 cv::Point2f。使用这个类,可以通过以下调用实现图像对的鲁棒匹配:

// 匹配器
RobustMatcher rmatcher(cv::xfeatures2d::SIFT::create(250));
// 匹配两张图像
std::vector<cv::DMatch> matches;
std::vector<cv::KeyPoint> keypoints1, keypoints2;
cv::Mat fundamental = rmatcher.match(image1, image2, matches, keypoints1, keypoints2);

可以得到 228 个匹配项,如下图所示:

OpenCV实战(21)——基于随机样本一致匹配图像-LMLPHP
这些匹配几乎都是正确的,有少数错误的匹配意外的落在了基本矩阵的相应核线上。

1.2 随机样本一致算法

我们已经知道可以从多个特征点匹配中估计与图像对相关联的基本矩阵。显然,这个匹配集必须只由质量较好的匹配组成。然而,在实际应用场景中,无法保证通过比较检测到的特征点的描述符获得的匹配集是完全准确的。因此引入了基于 随机抽样一致算法 (RANdom SAmple Consensus, RANSAC) 的基本矩阵估计方法。
RANSAC 算法旨在从可能包含较多异常值的数据集中估计给定的数学实体。其核心是从集合中随机选择一些数据点,并仅使用这些数据点进行估计。所选点的数量应为估计数学实体所需的最少点数。在基本矩阵中,8 个匹配对是最小数量(实际上,也可以使用 7 个匹配对,但 8 点线性算法计算速度更快)。从这 8 个随机匹配中估计出基本矩阵后,匹配集中的所有其他匹配都将根据源自该矩阵的对极约束进行测试。识别满足此约束的所有匹配项,即对应特征与其极线相距较短距离的匹配项。这些匹配形成了计算的基本矩阵的支持集。
RANSAC 算法的核心思想是支持集越大,计算出的矩阵是正确矩阵的概率就越高。相反,如果随机选择的一个(或多个)匹配是不正确的匹配,那么计算的基本矩阵也是不正确的,并且其支持集也很小。此过程会重复多次,最后,支持度最大的矩阵将被保留为最可能的矩阵。
因此,我们多次随机选择 8 个匹配项,以便最终能够选择 8 个质量较好的匹配项,并得到一个较大的支持集。根据整个数据集中错误匹配的数量,选择一组 8 个正确匹配的概率有所不同。然而,通过增加选择的次数,我们有信心能够选择出一个较好的匹配集。更准确地说,如果我们假设匹配集中有 w w w 的正确值 (inlier, 即良好匹配),那么我们选择的 8 个匹配均是良好匹配的概率是 w 8 w^8 w8。因此,一个选择包含至少一个错误匹配的概率是 ( 1 − w 8 ) (1-w^8) (1w8) 。如果我们进行 k k k 次选择,则拥有一个包含良好匹配的随机集合的概率仅为 1 − ( 1 − w 8 ) k 1-(1-w^8)^k 1(1w8)k ,将其定义为置信概率 c c c ,我们希望这个概率尽可能高,因为至少需要一组好的匹配才能获得正确的基本矩阵。因此,在运行 RANSAC 算法时,需要确定选择匹配集的数量 k k k,以获得给定的置信度。
使用 CV_FM_RANSAC 标志调用 cv::findFundamentalMat 函数时,需要两个额外的参数。第一个参数是置信度(默认值为 0.99),它决定了要进行的迭代次数;第二个参数是被视为内值的点到极线的最大距离,点与其核线的距离大于指定距离的所有匹配对都被视为异常值。函数返回字符值向量,表明输入集中的相应匹配被识别为异常值 (0) 或正确值 (1)。
在初始匹配集中的匹配越多,使用 RANSAC 算法得到正确基本矩阵的可能性就越大,因此在匹配特征点时应用交叉检查过滤器,也可以使用比率测试以进一步提高最终匹配集的质量。这是为了平衡计算复杂性、最终匹配数以及获得仅包含精确匹配的匹配集,以得到所需的置信水平。
本节中介绍的鲁棒匹配过程的结果是获得具有最大支持度的 8 个选定匹配集,以及利用该支持集中包含的匹配集计算出基本矩阵的估计值。因此可以通过两种方式优化结果。

2. 算法优化

2.1 优化基本矩阵

由于最终我们得到了一个质量较好的匹配集,因此可以使用它们来重新估计基本矩阵。我们使用在上一节中介绍的线性八点算法来估计这个矩阵。因此,可以获得一个超定方程组,该方程组将基于最小二乘法求解基本矩阵。可以将此步骤添加到 ransacTest 函数末尾:

if (refineF) {
    // F 矩阵会重新计算所接受的匹配
    points1.clear();
    points2.clear();
    for (std::vector<cv::DMatch>::const_iterator it=outMatches.begin();
            it!=outMatches.end(); ++it) {
        points1.push_back(keypoints1[it->queryIdx].pt);
        points2.push_back(keypoints2[it->trainIdx].pt);
    }
    // 计算 8 点 F 矩阵
    fundamental = cv::findFundamentalMat(
            points1, points2,       // 匹配点
            cv::FM_8POINT);         // 8 点方法
}

cv::findFundamentalMat 函数通过使用奇异值分解求解线性方程组。

2.2 优化匹配集

我们知道在双目系统中,每个点都必须位于其对应点核线上,这是由基本矩阵表示的对极约束。因此,如果能够很好的估计基本矩阵,则可以使用对极约束通过强制每个点位于它们的核线上来纠正获得的匹配,这可以通过使用 cv::correctMatches 函数完成:

std::vector<cv::Point2f> newPoints1, newPoints2;
correctMatches(fundamental,     // F 矩阵
        points1, points2,       // 原位置
        newPoints1, newPoints2);// 新位置

该函数通过修改每个对应点的位置使其满足对极约束,同时最小化累积(平方)偏差。

3. 完整代码

头文件 (robustMatcher.h) 完整代码如下所示:

#if !defined MATCHER
#define MATCHER

#include <iostream>
#include <vector>
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/features2d/features2d.hpp>
#include <opencv2/calib3d/calib3d.hpp>
#include <opencv2/xfeatures2d.hpp>

#define NOCHECK 0
#define CROSSCHECK 1
#define RATIOCHECK 2
#define BOTHCHECK 3

class RobustMatcher {
    private:
        // 指向特征点检测器对象的指针
        cv::Ptr<cv::FeatureDetector> detector;
        // 指向特征描述符提取器对象的指针
        cv::Ptr<cv::DescriptorExtractor> descriptor;
        int normType;
        float ratio;
        bool refineF;
        bool refineM;
        double distance;
        double confidence;  // 置信度水平
    public:
        RobustMatcher(const cv::Ptr<cv::FeatureDetector>& detector,
                    const cv::Ptr<cv::DescriptorExtractor>& descriptor=cv::Ptr<cv::DescriptorExtractor>()) :
                detector(detector),descriptor(descriptor), normType(cv::NORM_L2),
                ratio(0.8f), refineF(true), refineM(true), confidence(0.98), distance(1.0) {
            if (!this->descriptor) {
                this->descriptor = this->detector;
            }
        }
        // 设置特征检测器
        void setFeatureDetector(const cv::Ptr<cv::FeatureDetector>& detect) {
            this->detector = detector;
        }
        // 设置描述符提取器
        void setDescriptorExtractor(const cv::Ptr<cv::DescriptorExtractor>& desc) {
            this->descriptor = desc;
        }
        // 设置匹配时使用的归一化方法
        void setNormType(int norm) {
            normType = norm;
        }
        // 在 RANSAC 中设置到极线的最小距离 
        void setMinDistanceToEpipolar(double d) {
            distance = d;
        }
        // 设置 RANSAC 中的阈值水平
        void setConfidenceLevel(double c) {
            confidence = c;
        }
        // 设置比例
        void setRatio(float r) {
            ratio = r;
        }
        // 如果要重新计算 F 矩阵
        void refineFundamental(bool flag) {
            refineF = flag;
        }
        // 如果希望使用 F 重新进行匹配
        void refineMatches(bool flag) {
            refineM = flag;
        }
        // 移除比率大于阈值的匹配项
        int ratioTest(const std::vector<std::vector<cv::DMatch> >& inputMatches,
                    std::vector<cv::DMatch>& outputMatches) {
                        int removed = 0;
            for (std::vector<std::vector<cv::DMatch> >::const_iterator matchIterator=inputMatches.begin();
                    matchIterator!=inputMatches.end(); ++matchIterator) {
                if ((matchIterator->size()>1) && (*matchIterator)[0].distance/(*matchIterator)[1].distance<ratio) {
                    outputMatches.push_back((*matchIterator)[0]);
                } else {
                    removed++;
                }
            }
            return removed;
        }
        // 在 symMatches 矢量中插入对称匹配
        void symmetryTest(const std::vector<cv::DMatch>& matches1,
                        const std::vector<cv::DMatch>& matches2,
                        std::vector<cv::DMatch>& symMatches) {
            for (std::vector<cv::DMatch>::const_iterator matchIterator1=matches1.begin();
                        matchIterator1!=matches1.end(); ++matchIterator1) {
                for (std::vector<cv::DMatch>::const_iterator matchIterator2=matches2.begin();
                        matchIterator2!=matches2.end(); ++matchIterator2) {
                    if (matchIterator1->queryIdx == matchIterator2->trainIdx &&
                        matchIterator2->queryIdx == matchIterator1->trainIdx) {
                        symMatches.push_back(*matchIterator1);
                        break;
                    }
                }
            }
        }
        // 应用比率和对称测试
        void ratioAndSymmetryTest(const std::vector<std::vector<cv::DMatch> >& matches1,
                        const std::vector<std::vector<cv::DMatch> >& matches2,
                        std::vector<cv::DMatch>& outputMatches) {
            std::vector<cv::DMatch> ratioMatches1;
            int removed = ratioTest(matches1, ratioMatches1);
            std::cout << "Number of matched points 1->2 (ratio test) : " << ratioMatches1.size() << std::endl;
            std::vector<cv::DMatch> ratioMatches2;
            removed = ratioTest(matches2, ratioMatches2);
            std::cout << "Number of matched points 1->2 (ratio test) : " << ratioMatches2.size() << std::endl;
            symmetryTest(ratioMatches1, ratioMatches2, outputMatches);
            std::cout << "Number of matched points (symmetry test): " << outputMatches.size() << std::endl;
        }
        // 使用 RANSAC 标示较好的匹配
        cv::Mat ransacTest(const std::vector<cv::DMatch>& matches,
                        std::vector<cv::KeyPoint>& keypoints1,
                        std::vector<cv::KeyPoint>& keypoints2,
                        std::vector<cv::DMatch>& outMatches) {
            std::vector<cv::Point2f> points1, points2;
            for(std::vector<cv::DMatch>::const_iterator it=matches.begin(); it!=matches.end(); ++it) {
                points1.push_back(keypoints1[it->queryIdx].pt);
                points2.push_back(keypoints2[it->trainIdx].pt);
            }
            // 使用 RANSAC 计算 F 矩阵
            std::vector<uchar> inliers(points1.size(), 0);
            cv::Mat fundamental = cv::findFundamentalMat(
                    points1, points2,       // 匹配点
                    inliers,                // 匹配状态
                    cv::FM_RANSAC,          // RANSAC 方法
                    distance,               // 到极线的距离
                    confidence);            // 置信度
            // 提取正确匹配
            std::vector<uchar>::const_iterator itIn = inliers.begin();
            std::vector<cv::DMatch>::const_iterator itM = matches.begin();
            for (; itIn!=inliers.end(); ++itIn, ++itM) {
                if (*itIn) {
                    outMatches.push_back(*itM);
                }
            }
            if (refineF || refineM) {
                // F 矩阵会重新计算所接受的匹配
                points1.clear();
                points2.clear();
                for (std::vector<cv::DMatch>::const_iterator it=outMatches.begin();
                        it!=outMatches.end(); ++it) {
                    points1.push_back(keypoints1[it->queryIdx].pt);
                    points2.push_back(keypoints2[it->trainIdx].pt);
                }
                // 计算 8 点 F 矩阵
                fundamental = cv::findFundamentalMat(
                        points1, points2,       // 匹配点
                        cv::FM_8POINT);         // 8 点方法
                if (refineM) {
                    std::vector<cv::Point2f> newPoints1, newPoints2;
                    correctMatches(fundamental,     // F 矩阵
                            points1, points2,       // 原位置
                            newPoints1, newPoints2);// 新位置
                    for (int i=0; i< points1.size(); i++) {
                        std::cout << "(" << keypoints1[outMatches[i].queryIdx].pt.x 
                                << "," << keypoints1[outMatches[i].queryIdx].pt.y 
                                << ") -> ";
                        std::cout << "(" << newPoints1[i].x 
                                << "," << newPoints1[i].y << std::endl;
                        std::cout << "(" << keypoints2[outMatches[i].trainIdx].pt.x 
                                << "," << keypoints2[outMatches[i].trainIdx].pt.y 
                                << ") -> ";
                        std::cout << "(" << newPoints2[i].x 
                                << "," << newPoints2[i].y << std::endl;
                        keypoints1[outMatches[i].queryIdx].pt.x= newPoints1[i].x;
                        keypoints1[outMatches[i].queryIdx].pt.y= newPoints1[i].y;
                        keypoints2[outMatches[i].trainIdx].pt.x= newPoints2[i].x;
                        keypoints2[outMatches[i].trainIdx].pt.y= newPoints2[i].y;
                    }
                }
            }
            return fundamental;
        }
        // 使用 RANSAC 匹配特征点
        cv::Mat match(cv::Mat& image1, cv::Mat& image2,     // 输入图像
                    std::vector<cv::DMatch>& matches,       // 输出匹配和关键点
                    std::vector<cv::KeyPoint>& keypoints1,
                    std::vector<cv::KeyPoint>& keypoints2,
                    int check=CROSSCHECK) {                 // 检查类型
            // 1. 检测特征点
            detector->detect(image1, keypoints1);
            detector->detect(image2, keypoints2);
            std::cout << "Number of feature points (1): " << keypoints1.size() << std::endl;
            std::cout << "Number of feature points (2): " << keypoints2.size() << std::endl;
            // 2. 提取特征描述符
            cv::Mat descriptors1, descriptors2;
            descriptor->compute(image1,keypoints1,descriptors1);
            descriptor->compute(image2,keypoints2,descriptors2);
            std::cout << "descriptor matrix size: " << descriptors1.rows << " by " << descriptors1.cols << std::endl;
            // 3. 匹配两张图像描述符
            cv::BFMatcher matcher(normType,         // 距离度量
                            check==CROSSCHECK);     // 交叉检查标记
            // 匹配向量
            std::vector<std::vector<cv::DMatch> > matches1;
            std::vector<std::vector<cv::DMatch> > matches2;
            std::vector<cv::DMatch> outputMatches;
            // 比率检查时调用 knnMatch
            if (check==RATIOCHECK || check==BOTHCHECK) {
                matcher.knnMatch(descriptors1, descriptors2,
                            matches1,       // 匹配向量
                            2);             // 返回两个最近邻居
                std::cout << "Number of matched points 1->2: " << matches1.size() << std::endl;
                if (check==BOTHCHECK) {
                    matcher.knnMatch(descriptors2, descriptors1,
                            matches2,       // 匹配向量
                            2);             // 返回两个最近邻居
                    std::cout << "Number of matched points 2->1: " << matches2.size() << std::endl;
                }
            }
            // 选择检查方法
            switch (check) {
                case CROSSCHECK:
                    matcher.match(descriptors1,descriptors2,outputMatches);
                    std::cout << "Number of matched points 1->2 (after cross-check): " << outputMatches.size() << std::endl;
                    break;
                case RATIOCHECK:
                    ratioTest(matches1,outputMatches);
                    std::cout << "Number of matched points 1->2 (after ratio test): " << outputMatches.size() << std::endl;
                    break;
                case BOTHCHECK:
                    ratioAndSymmetryTest(matches1,matches2,outputMatches);
                    std::cout << "Number of matched points 1->2 (after ratio and cross-check): " << outputMatches.size() << std::endl;
                    break;
                case NOCHECK:
                default:
                    matcher.match(descriptors1,descriptors2,outputMatches);
                    std::cout << "Number of matched points 1->2: " << outputMatches.size() << std::endl;
                    break;
            }
            // 4. 使用 RANSAC 验证匹配
            cv::Mat fundamental= ransacTest(outputMatches, keypoints1, keypoints2, matches);
            std::cout << "Number of matched points (after RANSAC): " << matches.size() << std::endl;
            return fundamental;
        }
        // 使用 RANSAC 匹配特征点
        cv::Mat matchBook(cv::Mat& image1, cv::Mat& image2, // 输入图像
                    std::vector<cv::DMatch>& matches,       // 输出匹配和关键点
                    std::vector<cv::KeyPoint>& keypoints1,
                    std::vector<cv::KeyPoint>& keypoints2) {
            // 1. 特征点检测
            detector->detect(image1,keypoints1);
            detector->detect(image2,keypoints2);
            // 2. 提取特征描述符
            cv::Mat descriptors1, descriptors2;
            descriptor->compute(image1,keypoints1,descriptors1);
            descriptor->compute(image2,keypoints2,descriptors2);
            // 3. 匹配图像描述符
            cv::BFMatcher matcher(normType, // 距离度量
                                true);      // 交叉检查标记     
            // 匹配描述符
            std::vector<cv::DMatch> outputMatches;
            matcher.match(descriptors1,descriptors2,outputMatches);
            // 4. 使用 RANSAC 验证匹配
            cv::Mat fundamental= ransacTest(outputMatches, keypoints1, keypoints2, matches);
            // 返回 F 矩阵
            return fundamental;
        }
};

#endif

主函数文件 (robustmatching.cpp) 完整代码如下所示:

#include <iostream>
#include <vector>
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/features2d/features2d.hpp>
#include <opencv2/calib3d/calib3d.hpp>
#include <opencv2/xfeatures2d.hpp>
#include "robustMatcher.h"

int main() {
    // 读取输入图像
    cv::Mat image1= cv::imread("01.png",0);
    cv::Mat image2= cv::imread("02.png",0);
    if (!image1.data || !image2.data)
        return 0;
    cv::namedWindow("Right Image");
    cv::imshow("Right Image",image1);
    cv::namedWindow("Left Image");
    cv::imshow("Left Image",image2);
    // 匹配器
    RobustMatcher rmatcher(cv::xfeatures2d::SIFT::create(250));
    // 匹配两张图像
    std::vector<cv::DMatch> matches;
    std::vector<cv::KeyPoint> keypoints1, keypoints2;
    cv::Mat fundamental = rmatcher.match(image1, image2, matches, keypoints1, keypoints2);
    // 绘制匹配
    cv::Mat imageMatches;
	cv::drawMatches(image1,keypoints1,  // 第一张图像及其关键点
                    image2,keypoints2,  // 第二张图像及其关键点
                    matches,			// 匹配
                    imageMatches,		// 结果
                    cv::Scalar(255,255,255),  // 线颜色
                    cv::Scalar(255,255,255),  // 关键点颜色
                    std::vector<char>(),
                    cv::DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS); 
    cv::namedWindow("Matches");
    cv::imshow("Matches",imageMatches);
    // 将关键点转换为 Point2f
    std::vector<cv::Point2f> points1, points2;
    for (std::vector<cv::DMatch>::const_iterator it= matches.begin();
                it!= matches.end(); ++it) {
        // 获取左侧图像关键点的位置 
        float x= keypoints1[it->queryIdx].pt.x;
        float y= keypoints1[it->queryIdx].pt.y;
        points1.push_back(keypoints1[it->queryIdx].pt);
        cv::circle(image1,cv::Point(x,y),3,cv::Scalar(255,255,255),3);
        //  获取右侧关键点的位置 
        x= keypoints2[it->trainIdx].pt.x;
        y= keypoints2[it->trainIdx].pt.y;
        cv::circle(image2,cv::Point(x,y),3,cv::Scalar(255,255,255),3);
        points2.push_back(keypoints2[it->trainIdx].pt);
    }
    // 绘制极线
    std::vector<cv::Vec3f> lines1;
    cv::computeCorrespondEpilines(points1,1,fundamental,lines1);
    for (std::vector<cv::Vec3f>::const_iterator it= lines1.begin();
                it!=lines1.end(); ++it) {
        cv::line(image2,cv::Point(0,-(*it)[2]/(*it)[1]),
                        cv::Point(image2.cols,-((*it)[2]+(*it)[0]*image2.cols)/(*it)[1]),
                        cv::Scalar(255,255,255));
    }
    std::vector<cv::Vec3f> lines2; 
    cv::computeCorrespondEpilines(points2,2,fundamental,lines2);
    for (std::vector<cv::Vec3f>::const_iterator it= lines2.begin();
                it!=lines2.end(); ++it) {
        cv::line(image1,cv::Point(0,-(*it)[2]/(*it)[1]),
                        cv::Point(image1.cols,-((*it)[2]+(*it)[0]*image1.cols)/(*it)[1]),
                        cv::Scalar(255,255,255));
    }
    cv::namedWindow("Right Image Epilines (RANSAC)");
    cv::imshow("Right Image Epilines (RANSAC)",image1);
    cv::namedWindow("Left Image Epilines (RANSAC)");
    cv::imshow("Left Image Epilines (RANSAC)",image2);
    cv::waitKey();
    return 0;
}

小结

在本节中,我们学习了如何利用两个视图之间的对极约束来更可靠地匹配图像特征,基于随机样本一致算法可以同时解决基本矩阵和匹配集的解决问题,并在最后介绍了如何改进计算结果。

系列链接

OpenCV实战(1)——OpenCV与图像处理基础
OpenCV实战(2)——OpenCV核心数据结构
OpenCV实战(3)——图像感兴趣区域
OpenCV实战(4)——像素操作
OpenCV实战(5)——图像运算详解
OpenCV实战(6)——OpenCV策略设计模式
OpenCV实战(7)——OpenCV色彩空间转换
OpenCV实战(8)——直方图详解
OpenCV实战(9)——基于反向投影直方图检测图像内容
OpenCV实战(10)——积分图像详解
OpenCV实战(11)——形态学变换详解
OpenCV实战(12)——图像滤波详解
OpenCV实战(13)——高通滤波器及其应用
OpenCV实战(14)——图像线条提取
OpenCV实战(15)——轮廓检测详解
OpenCV实战(16)——角点检测详解
OpenCV实战(17)——FAST特征点检测
OpenCV实战(18)——特征匹配
OpenCV实战(19)——特征描述符
OpenCV实战(20)——图像投影关系

05-03 15:16