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

15
OpenCV并没有提供一个RANSAC函数,至少没有像这样可以直接调用的形式(例如`cv::ransac(...)`)。 能够使用RANSAC的所有函数/方法都有一个标志,它使其可用。但是,如果您实际上想要在估计了单应性/基础矩阵之后对RANSAC计算出的内点进行其他操作(例如,在Octave或类似软件/库中创建漂亮的绘图,对剩余的匹配进行附加算法等),则不总是有用。
匹配两个图像后,会得到一组匹配向量。当然,我们还有两组关键点(每幅图像各一组)用于匹配过程。使用匹配和关键点,我们创建两个点向量(例如`cv :: Point2f points`),将其传递给`findHomography()`。从此帖子我发现如何使用掩码标记内点,我们将其传递给该函数。掩模内的每行都与内点/外点相关联。但是,我无法弄清楚如何使用来自我的两组点的行索引信息。查看OpenCV的源代码没有帮助我太多。在`findFundamental()`中(类似于`findHomography()`,当涉及其签名和遮罩部分时),它们使用`compressPoints()`,它似乎以某种方式将我们作为输入的两组集合(源点和目标点)合并为一个。为了确定掩码的性质而进行的测试中,我尝试了2组匹配点(将`cv :: Keypoints`转换为`cv :: Point2f`-标准过程)。每组包含300个点,因此总共有600个点。返回的掩码包含300行(值对于此主题并不重要)。
编辑:在编写本文时,我发现了答案(请参见下文),但决定发布此问题,以防有人需要尽快获得这些信息并以紧凑的形式呈现。请注意,我们仍然需要OpenCV的一个支持RANSAC的函数。因此,如果您有一组点但没有计算单应性或基本矩阵的意图,则显然不是这种方式,并且我敢说我无法在OpenCV的API中找到任何有用的东西,可以帮助避免这种障碍,因此需要使用外部库。
2个回答

14

解决方案实际上非常简单。我们知道,我们掩码中的每一行都给出了有关我们是否具有内点或外点的信息。但是,由于我们有两组输入点,因此一个包含单个值的行如何表示两个点呢?我脑海中浮现出这种索引方式的本质,正是在思考findHomography()函数中那两组点是如何出现的(在我的情况下,我正在计算两个图像之间的单应性)。由于它们是从我们的一对图像的匹配中提取出来的,因此这两组都具有相同数量的点。这意味着,我们掩码中的一行是两组点的实际索引,也是两个图像的匹配向量中的索引。基于此,我已成功地手动引用了一小部分匹配点,并且结果符合预期。重要的是,不要改变你的匹配顺序以及使用每个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或非零值:

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

1
对于第一句话,实际上掩码中的行并不代表一个内点。该行表示内点或外点的状态。请参考此帖子(链接)以获取进一步的解释。 - Nothing More
是的,那是正确的,这就是我想表达的意思,但是现在我重新阅读这个句子,我看到它可能会被误解。我会修改它。 - rbaleksandar
但是行代表内点或外点的状态,但每个内点或外点的状态上没有关键点。关键点存储在向量中。那么我们如何在掩码中获取相应的内点关键点? - Abc
我很难理解你的问题。也许你可以发布一个新的问题(如果你愿意,你可以链接到我的问题,以帮助观众更好地了解你的情况)? - rbaleksandar

2

另外,需要注意的是,RANSAC 可能无法在 OpenCV 中作为一个独立的函数存在,因为 RANSAC 是一种抑制离群点的抽象技术。RANSAC 依赖于一个基础模型来执行离群点的拒绝。现在基础模型非常通用,它可以是任何东西(不一定是具有某些关系的点)。这可能是为什么 RANSAC 只存在于执行一些定义了范围的任务的其他函数中,例如 findHomographyfindFundamentalMat 等。


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