Python 和 C++ 使用 OpenCV 计算基本矩阵的结果不同。

4
我是一个有用的助手,可以翻译文本。

我正在使用OpenCV在Python和C++中计算视频里程计的基本矩阵。我尽量保持两种实现中的代码相同。然而,两者得到了不同的结果。在Python中,它可以正常工作,而在C++中则显示完全不正确的结果。以下是它们的代码和输出的部分示例(第一个是Python版本的,第二个是C++版本的)

Python版本的代码:

import os
import sys
import cv2
import numpy as np
import math

# Main Function
if __name__ == '__main__':
    K = np.matrix([[522.4825, 0,        300.9989], 
                   [0,        522.5723, 258.1389], 
                   [0.0,      0.0,      1.0]])
img1 = cv2.imread(sys.argv[1] + ".jpg")
img2 = cv2.imread(sys.argv[2] + ".jpg")

# sift = cv2.SURF()

detector = cv2.FeatureDetector_create("SURF")    # SURF, FAST, SIFT
descriptor = cv2.DescriptorExtractor_create("SURF") # SURF, SIFT

# kp1, des1 = sift.detectAndCompute(img1,None)
# kp2, des2 = sift.detectAndCompute(img2,None)

kp1 = detector.detect(img1)
kp2 = detector.detect(img2) 

k1, des1 = descriptor.compute(img1,kp1)
k2, des2 = descriptor.compute(img2,kp2)

# BFMatcher with default params
bf = cv2.BFMatcher()
matches = bf.knnMatch(des1,des2, k=2)

good = []

# Apply ratio test
for m,n in matches:
    if m.distance < 0.7*n.distance:
            good.append(m)

MIN_MATCH_COUNT = 10
if len(good)>MIN_MATCH_COUNT:
    src_pts = np.float32([ kp1[m.queryIdx].pt for m in good ]).reshape(-1,1,2)
    dst_pts = np.float32([ kp2[m.trainIdx].pt for m in good ]).reshape(-1,1,2)
    F, mask = cv2.findFundamentalMat(src_pts, dst_pts, cv2.RANSAC, 5.0)
    matchesMask = mask.ravel().tolist()
else:
    print "Not enough matches are found - %d/%d" % (len(good),MIN_MATCH_COUNT)
    matchesMask = None

print F

"并且它的输出:"
[[ -3.22706105e-07   1.12585581e-04  -2.86938406e-02]
[ -1.16307090e-04  -5.04244159e-07   5.60714444e-02]
[  2.98839742e-02  -5.99974406e-02   1.00000000e+00]]

这里是C++版本:

#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/nonfree/features2d.hpp>
#include <opencv2/legacy/legacy.hpp>

using namespace std;

int main(int argc,char *argv[]) {
    //Define intrinsic matrix
    cv::Mat intrinsic = (cv::Mat_<double>(3,3) << 522.4825, 0, 300.9989,
            0, 522.5723, 258.1389,
            0, 0, 1);

    // Read input images
    string jpg1 = argv[1];
    jpg1.append(".jpg");
    string jpg2 = argv[2];
    jpg2.append(".jpg");
    cv::Mat image1 = cv::imread(jpg1,0);
    cv::Mat image2 = cv::imread(jpg2,0);
    if (!image1.data || !image2.data)
        return 0;

    // Display the images
    // cv::namedWindow("Image 1");
    // cv::imshow("Image 1",image1);
    // cv::namedWindow("Image 2");
    // cv::imshow("Image 2",image2);

    // pointer to the feature point detector object
    cv::Ptr<cv::FeatureDetector> detector = new cv::SurfFeatureDetector();
    // pointer to the feature descriptor extractor object
    cv::Ptr<cv::DescriptorExtractor> extractor = new cv::SurfDescriptorExtractor();

    // Detection of the SURF features
    vector<cv::KeyPoint> keypoints1, keypoints2;
    detector->detect(image1,keypoints1);
    detector->detect(image2,keypoints2);

    // Extraction of the SURF descriptors
    cv::Mat descriptors1, descriptors2;
    extractor->compute(image1,keypoints1,descriptors1);
    extractor->compute(image2,keypoints2,descriptors2);

    // Construction of the matcher
    cv::BruteForceMatcher<cv::L2<float> > matcher;

    vector<vector<cv::DMatch> > matches;
    vector<cv::DMatch> good_matches;
    matcher.knnMatch(descriptors1, descriptors2, matches, 2);

    for (vector<vector<cv::DMatch> >::iterator matchIterator= matches.begin();
         matchIterator!= matches.end(); ++matchIterator) {
        if ((*matchIterator)[0].distance < 0.7f * (*matchIterator)[1].distance) {
            good_matches.push_back((*matchIterator)[0]);
        }
    }

    // Convert keypoints into Point2f
    vector<cv::Point2f> src_pts, dst_pts;
    for (vector<cv::DMatch>::iterator it= good_matches.begin();
         it!= good_matches.end(); ++it)
    {
        // Get the position of left keypoints
        float x= keypoints1[it->queryIdx].pt.x;
        float y= keypoints1[it->queryIdx].pt.y;
        src_pts.push_back(cv::Point2f(x,y));
        // Get the position of right keypoints
        x= keypoints2[it->trainIdx].pt.x;
        y= keypoints2[it->trainIdx].pt.y;
        dst_pts.push_back(cv::Point2f(x,y));
    }
    // Compute F matrix using RANSAC
    cv::Mat fundemental = cv::findFundamentalMat(
            cv::Mat(src_pts),cv::Mat(dst_pts), // matching points
            CV_FM_RANSAC,  // RANSAC method
            5.0); // distance
    cout <<  fundemental << endl;

    return 0;
}

它的输出:
[-4.310057787788129e-06, 0.0002459670522815174, -0.0413520716270485;
-0.0002531048911221476, -8.423657757958228e-08, 0.0974897887347238;
0.04566865455090797, -0.1062956485414729, 1]

这是两张测试图片: 图片1 图片2 我找不到原因。有人能告诉我为什么吗?

这可能与这个有关,但我不确定。 - Brandon White
1个回答

1

由于没有人回答,我来分享一下我的想法。你只检查F中的数字吗,还是会对它进行某种应用并观察其不正确的结果?正如@brandon-white已经注意到的那样,浮点精度可能是其中一个原因。但实际上情况更加复杂。

首先想到的是,据我所知,在C++ OpenCV中,矩阵和其他数学运算使用了自己的例程,而在Python中则使用了Numpy。也许在底层它们使用了类似的算法/实现,但仍然可能会得到数值上不同的结果,特别是在处理模糊性(特征向量分解、SVD等)的情况下。

此外,您正在使用 RANSAC 来估计 F。为了处理(理论上)任何数量的离群值,RANSAC 从所有关键点中随机抽取一个小样本,并尝试找到满足某些约束条件的成对关键点。它会多次执行此操作,然后在之后选择最佳样本来计算最终模型。因此,如果您适当地设置伪随机生成例程的种子,则每次运行时都会得到不同的点来估计 F。但是,通常情况下,单应性和基础矩阵估计器使用更智能的方法,在找到最符合约束条件的样本后,将使用满足此模型的所有点再次计算矩阵。这样,您应该可以获得更一致的结果,如果 RANSAC 参数正确,理想情况下应该相同。我不确定它是否在 OpenCV 中使用,但我猜测会使用。

最后,存在退化情况,其中 F 无法完全估计-平面运动的情况,当所有关键点都位于一个平面上(在三维世界中),以及纯旋转摄像机运动的情况。由于您说您的代码在 Python 中工作,因此这可能不是问题,但仍需考虑。

如果您还没有这样做,请尝试检查一些数据上获得的F矩阵,以确保您获得的结果确实不同。在这种情况下,应该会有某处错误(我承认我还没有仔细检查过您的代码)。

此外,显示用于F计算的匹配可能对调试有用,因为它缩小了代码可能表现不同的范围。


谢谢你的回答。今天我将矩阵F应用于计算两个图像之间在x-y平面上的旋转,并发现它有效。C++和Python中的旋转结果几乎相同,尽管昨晚似乎没有起作用。也许我的后续代码有些问题,而这个页面上的代码是正确的。 - Yusu Pan
很高兴你解决了它 :) 如果您更新答案并提供当前可用的代码,以帮助其他可能遇到相同问题的人,那将是非常好的。或者您使用了不同的图像作为输入? - alexisrozhkov

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接