给定相机矩阵,如何使用 OpenCV 找到点对应关系?

Given camera matrices, how to find point correspondances using OpenCV?

我正在关注 this tutorial,它使用 Features2D + Homography。如果我知道每个图像的相机矩阵,我该如何优化结果?我尝试了一些图像,但效果不佳。

//编辑
看了一些资料,我觉得还是先整两张图吧。但整改并不完美,所以图像1上的垂直线通常对应图像2上的垂直带。有什么好的算法吗?

给出你的照片,你正在尝试做立体匹配。

page 会很有帮助。您可以使用 stereoCalibrate 然后 stereoRectify.

完成您想要的校正

结果(来自文档):

我不确定我是否理解你的问题。您想找到图像之间的对应点,还是想通过使用相机内在函数来提高匹配的正确性?

原则上,为了使用相机几何来寻找匹配项,您需要基本矩阵或基本矩阵,具体取决于您是否了解相机内在函数(即校准相机)。这意味着,您需要估计相机的相对旋转和平移。然后,通过计算与在一张图像中找到的特征相对应的对极线,您需要在第二张图像中沿着这些线进行搜索以找到最佳匹配。但是,我认为简单地依靠自动特征匹配会更好。给定 fundamental/essential 矩阵,您可以使用 correctMatches 试试运气,这将移动对应关系以使重投影误差最小化。

更好匹配的提示

为了增加自动匹配的稳定性和显着性,通常需要

  • 调整特征检测器的参数
  • 尝试不同的检测算法
  • 进行比率测试,过滤掉那些具有非常相似的第二最佳匹配并因此不稳定的关键点。这是这样做的:

    Mat descriptors_1, descriptors_2; // obtained from feature detector
    BFMatcher matcher;
    vector<DMatch> matches;
    matcher = BFMatcher(NORM_L2, false); // norm depends on feature detector
    vector<vector<DMatch>> match_candidates;
    const float ratio = 0.8; // or something
    matcher.knnMatch(descriptors_1, descriptors_2, match_candidates, 2);
    for (int i = 0; i < match_candidates.size(); i++)
    {
       if (match_candidates[i][0].distance < ratio * match_candidates[i][1].distance)
          matches.push_back(match_candidates[i][0]);
    }
    
  • 一种更复杂的过滤方法是计算第一帧中每个关键点的重投影误差。这意味着计算第二张图像中相应的对极线,然后检查其假设的匹配点与该线的距离。丢弃那些距离超过某个阈值的点将删除与 epiploar 几何不兼容的匹配项(我假设这是已知的)。可以这样计算错误(老实说,我不记得我从哪里得到这段代码,我可能对它做了一些修改,而且当代码在列表中时,SO 编辑器也有问题,对于错误的格式感到抱歉):

    double computeReprojectionError(vector& imgpts1, vector& imgpts2, Mat& inlier_mask, const Mat& F)
    {
    double err = 0;
    vector lines[2];
    int npt = sum(inlier_mask)[0]; </p>
    
    <p>// strip outliers so validation is constrained to the correspondences
    // which were used to estimate F
    vector imgpts1_copy(npt), 
      imgpts2_copy(npt);
    int c = 0;
    for (int k = 0; k < inlier_mask.size().height; k++) 
    {
      if (inlier_mask.at(0,k) == 1) 
      {
         imgpts1_copy[c] = imgpts1[k];
         imgpts2_copy[c] = imgpts2[k];
         c++;
      } 
    }</p>
    
    <p>Mat imgpt[2] = { Mat(imgpts1_copy), Mat(imgpts2_copy) };
    computeCorrespondEpilines(imgpt[0], 1, F, lines[0]);
    computeCorrespondEpilines(imgpt<a href="http://docs.opencv.org/2.4/modules/calib3d/doc/camera_calibration_and_3d_reconstruction.html#correctmatches" rel="nofollow">1</a>, 2, F, lines<a href="http://docs.opencv.org/2.4/modules/calib3d/doc/camera_calibration_and_3d_reconstruction.html#correctmatches" rel="nofollow">1</a>);
    for(int j = 0; j < npt; j++ )
    {
      // error is computed as the distance between a point u_l = (x,y) and the epipolar line of its corresponding point u_r in the second image plus the reverse, so errij = d(u_l, F^T * u_r) + d(u_r, F*u_l)
      Point2f u_l = imgpts1_copy[j], // for the purpose of this function, we imagine imgpts1 to be the "left" image and imgpts2 the "right" one. Doesn't make a difference
              u_r = imgpts2_copy[j];
      float a2 = lines<a href="http://docs.opencv.org/2.4/modules/calib3d/doc/camera_calibration_and_3d_reconstruction.html#correctmatches" rel="nofollow">1</a>[j][0], // epipolar line
            b2 = lines<a href="http://docs.opencv.org/2.4/modules/calib3d/doc/camera_calibration_and_3d_reconstruction.html#correctmatches" rel="nofollow">1</a>[j]<a href="http://docs.opencv.org/2.4/modules/calib3d/doc/camera_calibration_and_3d_reconstruction.html#correctmatches" rel="nofollow">1</a>,
            c2 = lines<a href="http://docs.opencv.org/2.4/modules/calib3d/doc/camera_calibration_and_3d_reconstruction.html#correctmatches" rel="nofollow">1</a>[j][2];
      float norm_factor2 = sqrt(pow(a2, 2) + pow(b2, 2));
      float a1 = lines[0][j][0],
            b1 = lines[0][j]<a href="http://docs.opencv.org/2.4/modules/calib3d/doc/camera_calibration_and_3d_reconstruction.html#correctmatches" rel="nofollow">1</a>,
            c1 = lines[0][j][2];
      float norm_factor1 = sqrt(pow(a1, 2) + pow(b1, 2));</p>
    
    <p>double errij =
         fabs(u_l.x * a2 + u_l.y * b2 + c2) / norm_factor2 +
         fabs(u_r.x * a1 + u_r.y * b1 + c1) / norm_factor1; // distance of (x,y) to line (a,b,c) = ax + by + c / (a^2 + b^2)
      err += errij; // at this point, apply threshold and mark bad matches
    }</p>
    
    <p>return err / npt;
    }
    </pre>关键是,获取基本矩阵,用它来计算所有点的外线,然后计算距离(线以参数形式给出,所以你需要做一些代数来获取距离)。 
    这与 findFundamentalMat 使用 RANSAC 方法的结果有些相似。它 returns 一个掩码,其中对于每个匹配项都有一个 1,这意味着它被用于估计矩阵,或者如果它被丢弃则为 0。但是像这样估计基本矩阵可能不如使用棋盘准确。

编辑:看起来 oarfish 打败了我,但我会把它留在这里。

假设完美校准,基本矩阵 (F) 定义了从左图像中的一个点到右图像中对应点必须位于的一条线上的映射。这是对极线,即通过左图中的点和立体相机对的两个对极的线。有关参考,请参阅 HZ 书的 these lecture notes and this chapter

给定左右图像中的一组点对应关系:(p_L, p_R),来自 SURF(或任何其他特征匹配器),并给定 F,来自对极线的约束立体对的几何表示 p_R 应该位于 p_L 投影到右图像上的对极线上,即

{p_L}^T \cdot F \cdot {p_R} = 0

在实践中,来自噪声的校准错误以及错误的特征匹配会导致非零值。

然而,使用这个想法,您可以通过拒绝那些方程大于某个阈值的特征匹配来执行离群值去除,即拒绝(p_L,p_R)如果并且仅当:

{p_L}^T \cdot F \cdot {p_R} > threshold

选择此阈值时,请记住它是您愿意容忍的点与对极线在图像中 space 的距离,从某种意义上说,这就是您的对极误差容忍度。

退化情况: 为了从视觉上想象这意味着什么,让我们假设立体对仅在纯 X 平移方面不同。然后极线是水平的。这意味着你可以用一条线连接特征匹配点对,并拒绝那些线斜率不接近于零的点对。上面的等式是这个想法对任意立体旋转和平移的推广,由矩阵 F.

解释

您的特定图像:您的特征匹配似乎很稀疏。我建议改用密集的特征匹配方法,这样在移除异常值后,您仍然可以得到足够数量的高质量匹配。我不确定 OpenCV 中已经实现了哪个密集特征匹配器,但我建议开始 here.

为了找到基本矩阵,您需要正确的对应关系,但为了获得良好的对应关系,您需要对基本矩阵进行良好的估计。这听起来像是一个不可能的鸡生蛋还是蛋生鸡的问题,但是已经有成熟的方法可以做到这一点; RANSAC.

它随机选择一小组对应关系,使用这些来计算基本矩阵(使用 7 或 8 点算法),然后测试有多少其他对应关系符合该矩阵(使用描述的方法scribbleink 用于测量点和对极线之间的距离)。它不断测试一定数量的迭代的新对应组合,并选择具有最多内点的组合。

这已经在 OpenCV 中实现为 cv::findFundamentalMat (http://docs.opencv.org/2.4/modules/calib3d/doc/camera_calibration_and_3d_reconstruction.html#findfundamentalmat)。 Select 方法 CV_FM_RANSAC 使用 ransac 删除错误的对应关系。它将输出所有内点对应的列表。

要求是所有点不在同一平面上