OpenCV:如何使用findHomography()/findFundamental()和RANSAC获得内线点



OpenCV本身不提供ransac函数,或者至少以这样一种形式,您可以调用它并完成它(例如cv::ransac(...))。所有能够使用RANSAC的函数/方法都有一个启用它的标志。然而,这并不总是有用的,如果你真的想在你估计了一个单应性/基本矩阵之后用内线RANSAC计算做其他事情,例如在Octave或类似的软件/库中创建一个漂亮的图,在剩余的过滤匹配集上应用额外的算法等。

匹配两个图像后,得到一个匹配向量。与此同时,我们当然有2组关键点(每个图像一个)用于匹配过程。使用匹配和关键点,我们创建两个点向量(例如cv::Point2f points)并将它们传递给findHomography()。从这篇文章和这篇文章中,我发现了如何准确地使用掩码标记内层,我们传递给那个函数。掩码内的每一行都与一个内值/离群值相关。然而,我无法弄清楚如何从我的两组点使用行索引信息。查看OpenCV的源代码并没有让我走得太远。在findFundamental()中(类似于findHomography(),当涉及到它的签名和掩码部分时),他们使用compressPoints(),这似乎以某种方式将我们作为输入(源和目的点)的两个集合组合成一个。在测试时,为了确定掩码的性质,我尝试了2组匹配点(将cv::Keypoints转换为cv::Point2f -一个标准程序)。每组包含300个点,所以我们总共有600个点。返回的掩码包含300行(值对于当前的主题并不重要)。

编辑:在写这篇文章的时候,我发现了答案(见下文),但还是决定把这个问题贴出来,以防有人需要尽快以紧凑的形式提供这些信息。注意,我们仍然需要一个OpenCV的函数,它支持RANSAC。因此,如果你有一组点,但无意计算单应性或基本矩阵,这显然不是方法,我敢说,我无法在OpenCV的API中找到任何有用的东西,可以帮助避免这个障碍,因此你需要使用外部库。

解决方案实际上非常简单。正如我们所知,如果我们有一个内值或离群值,掩码中的每一行都会给出信息。然而,我们有两组点作为输入,那么包含单个值的行如何准确地表示两个点呢?当我想到这两组点实际上是如何在findHomography()中出现时(在我的例子中,我是在计算两个图像之间的同形性),这种索引的性质就出现在我的脑海中。这两个集合中有相同数量的点,因为它们是从我们对图像之间的匹配中提取的。这意味着掩码中的一行是两个集合中点的实际索引,也是两个图像的匹配向量中的索引。我已经成功地手动引用了基于此的匹配点的一小部分,结果如预期的那样。重要的是,你不要改变你的匹配和2D点的顺序,你已经从他们提取使用每个cv::DMatch中引用的关键点。下面你可以看到一个简单的例子:

for(int i = 0; i < matchesObjectScene.size(); ++i)
{
   // extract points from keypoints based on matches
   pointsObject.push_back(keypointsObject.at(matchesObjectScene.at(i).queryIdx).pt);
   pointsScene.push_back(keypointsScene.at(matchesObjectScene.at(i).trainIdx).pt);
}
// compute homography using RANSAC
cv::Mat mask;
cv::Mat H = cv::findHomography(pointsObject, pointsScene, CV_RANSAC, ransacThreshold, mask);

在上面的例子中,如果我们打印一些

int maskRow = 10;
std::cout << "POINTS: object(" << pointsObject.at(maskRow).x << "," << pointsObject.at(maskRow).y << ") - scene(" << pointsScene.at(maskRow).x << "," << pointsScene.at(maskRow).y << ")" << std::endl;

,然后再一次,但这次使用我们的关键点(也可以用提取的2D点)

std::cout << "POINTS (via match-set): object(" << keypointsObject.at(matchesCurrentObject.at(maskRow).queryIdx).pt.x << "," << keypointsObject.at(matchesCurrentObject.at(maskRow).queryIdx).pt.y << ") - scene(" << keypointsScene.at(matchesCurrentObject.at(maskRow).trainIdx).pt.x << "," << keypointsScene.at(matchesCurrentObject.at(maskRow).trainIdx).pt.y << ")" << std::endl;

我们实际上得到了相同的输出:

POINTS: object(462,199) - sscene(485,49)
POINTS (via match-set): object(462,199) - scene(485,49)

要得到实际的内线值,我们只需要检查掩码中的当前行是否包含0或非0值:

if((unsigned int)mask.at<uchar>(maskRow))
  // store match or keypoints or points somewhere where you can access them later

另说。在OpenCV中,RANSAC可能不可能作为一个函数单独存在,因为RANSAC是一种拒绝异常值的抽象技术。RANSAC依赖于一个基本模型来执行离群值拒绝。现在基本模型是非常通用的。它可以是任何东西(不一定是它们之间有某种关系的点)。这可能就是为什么RANSAC只存在于其他函数中,这些函数执行一些定义的任务,这些任务具有一些定义的范围,如findHomography, findFundamentalMat等。

最新更新