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

时间:2014-06-27 16:36:32

标签: opencv mask extraction points ransac

OpenCV本身不提供RANSAC功能,或者至少以这种形式提供,您只需调用它并完成它(例如cv::ransac(...))。能够使用RANSAC的所有函数/方法都有一个启用它的标志。然而,如果您实际上想要在估算单应性/基本矩阵之后使用内部RANSAC计算其他内容,例如在Octave或类似软件/点库中创建一个好的绘图,则应用其他算法并不总是有用。剩余的一组过滤后的匹配等。

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

编辑:在写这篇文章时我发现了答案(见下文),但无论如何都决定发布这个问题,以防有人需要尽快并以紧凑的形式提供这些信息。请注意,我们仍然需要一个支持RANSAC的OpenCV功能。所以,如果你有一套点,但无意计算单应性或基本矩阵,这显然不是那种方式,我敢说我无法在OpenCV的API中找到任何有用的东西,可以帮助避免这个障碍,因此你需要使用外部图书馆。

2 个答案:

答案 0 :(得分:10)

解决方案实际上非常简单。我们知道,如果我们有一个内部或异常值,我们掩码中的每一行都会给出信息。但是我们有两组点作为输入,那么包含单个值的行究竟是如何表示两个点的呢?这种索引的本质出现在我脑海中,同时思考这两组点实际上是如何出现在findHomography()中的(在我的情况下,我是在计算两个图像之间的单应性)。两个集合中的点数相等,因为它们是从我们的图像对之间的匹配中提取的简单事实。这意味着我们的掩码中的行是两个集合中的点的实际索引,也是两个图像的匹配向量中的索引。我已成功设法基于此手动引用一小部分匹配点,结果如预期。重要的是,不要使用每个cv::DMatch中引用的关键点更改匹配顺序和从中提取的2D点。您可以在下面看到一对内部函数的简单示例。

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);

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

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)

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

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

答案 1 :(得分:1)

在另一个音符上。由于RANSAC是拒绝异常值的抽象技术,因此RANSAC本身不可能作为函数存在于OpenCV中。 RANSAC依靠基本模型执行离群值剔除。现在,基本模型非常通用。它可以是任何东西(不一定是彼此之间具有某种关系的点)。这可能就是为什么RANSAC仅作为其他功能的功能存在的原因,这些功能执行某些已定义的任务,这些任务具有一定的定义范围,例如findHomographyfindFundamentalMat等。