该应用程序的重点是从已设置的图像列表中识别图像。图像列表已将其 SIFT 描述符提取并保存在文件中。这里没有什么有趣的:
std::vector<cv::KeyPoint> detectedKeypoints;
cv::Mat objectDescriptors;
// Extract data
cv::SIFT sift;
sift.detect(image, detectedKeypoints);
sift.compute(image, detectedKeypoints, objectDescriptors);
// Save the file
cv::FileStorage fs(file, cv::FileStorage::WRITE);
fs << "descriptors" << objectDescriptors;
fs << "keypoints" << detectedKeypoints;
fs.release();
然后设备拍照。SIFT 描述符以相同的方式提取。现在的想法是将描述符与文件中的描述符进行比较。我正在使用来自 OpenCV 的 FLANN 匹配器来做到这一点。我正在尝试逐个图像地量化相似度。在浏览完整个列表之后,我应该有最好的匹配。
const cv::Ptr<cv::flann::IndexParams>& indexParams = new cv::flann::KDTreeIndexParams(1);
const cv::Ptr<cv::flann::SearchParams>& searchParams = new cv::flann::SearchParams(64);
// Match using Flann
cv::Mat indexMat;
cv::FlannBasedMatcher matcher(indexParams, searchParams);
std::vector< cv::DMatch > matches;
matcher.match(objectDescriptors, readDescriptors, matches);
匹配后,我了解到我得到了特征向量之间最近发现距离的列表。我找到了最小距离,使用它我可以计算“好匹配”,甚至可以得到相应点的列表:
// Count the number of mathes where the distance is less than 2 * min_dist
int goodCount = 0;
for (int i = 0; i < objectDescriptors.rows; i++)
{
if (matches[i].distance < 2 * min_dist)
{
++goodCount;
// Save the points for the homography calculation
obj.push_back(detectedKeypoints[matches[i].queryIdx].pt);
scene.push_back(readKeypoints[matches[i].trainIdx].pt);
}
}
我展示代码的简单部分只是为了让这更容易理解,我知道其中一些不需要在这里。
继续,我希望像这样简单地计算好匹配的数量就足够了,但结果主要只是将我指向具有最多描述符的图像。在此之后我尝试的是计算单应性。目的是计算它并查看它是否是有效的同态。希望是一个好的匹配,并且只有一个好的匹配,会有一个好的转换的单应性。创建单应性只需在obj和场景上使用cv::findHomography 即可,它们是std::vector< cv::Point2f>。我使用我在网上找到的一些代码检查了单应性的有效性:
bool niceHomography(cv::Mat H)
{
std::cout << H << std::endl;
const double det = H.at<double>(0, 0) * H.at<double>(1, 1) - H.at<double>(1, 0) * H.at<double>(0, 1);
if (det < 0)
{
std::cout << "Homography: bad determinant" << std::endl;
return false;
}
const double N1 = sqrt(H.at<double>(0, 0) * H.at<double>(0, 0) + H.at<double>(1, 0) * H.at<double>(1, 0));
if (N1 > 4 || N1 < 0.1)
{
std::cout << "Homography: bad first column" << std::endl;
return false;
}
const double N2 = sqrt(H.at<double>(0, 1) * H.at<double>(0, 1) + H.at<double>(1, 1) * H.at<double>(1, 1));
if (N2 > 4 || N2 < 0.1)
{
std::cout << "Homography: bad second column" << std::endl;
return false;
}
const double N3 = sqrt(H.at<double>(2, 0) * H.at<double>(2, 0) + H.at<double>(2, 1) * H.at<double>(2, 1));
if (N3 > 0.002)
{
std::cout << "Homography: bad third row" << std::endl;
return false;
}
return true;
}
我不明白这背后的数学,所以在测试时,我有时用简单的检查单应性的行列式是否为正来替换这个函数。问题是我一直在这里遇到问题。单应性要么都是坏的,要么是不应该的(当我只检查行列式时)。
我想我应该实际使用单应性,并且对于一些点,只需使用它们在源图像中的位置来计算它们在目标图像中的位置。然后我会比较这些平均距离,理想情况下,在正确图像的情况下,我会得到一个非常明显更小的平均距离。这根本不起作用。所有的距离都是巨大的。我想我可能已经反过来使用单应性来计算正确的位置,但是相互切换obj和场景会得到类似的结果。
我尝试过的其他事情是 SURF 描述符而不是 SIFT,BFMatcher(蛮力)而不是 FLANN,根据最小距离为每个图像获取n 个最小距离而不是一个数字,或者根据全局最大距离获取距离。这些方法都没有给我明确的好结果,我现在感觉卡住了。
我唯一的下一个策略是锐化图像,甚至使用一些局部阈值或一些用于分割的算法将它们变成二值图像。我正在寻找任何人都可以在我的工作中看到的任何建议或错误。
我不知道这是否相关,但我添加了一些我正在测试的图像。很多时候,在测试图像中,大多数 SIFT 向量来自帧(对比度更高)而不是绘画。这就是为什么我认为锐化图像可能会起作用,但我不想更深入,以防我以前做的事情是错误的。
图像库在这里,标题中有描述。图像分辨率很高,请查看以防提供一些提示。