描述并匹配局部强度值模式 - sumpig/OpenCV GitHub Wiki
本节将介绍如何用特征描述子来描述兴趣点的邻域。
特征描述子是一种非常强大的工具,能进行目标的匹配。它通常是一个 N 维的向量,在光照变化和拍摄角度发生微小扭曲时,它描述特征点的方式不 会发生变化。
通常可以用简单的差值矩阵来比较描述子,例如用欧几里得距离。
大多数基于特征的方法都包含一个检测器和一个描述子组件。它们都有一个检测函数(用于检测兴趣点)和一个计算函数(用于计算兴趣点的描述子)。
例如,可以用cv::SURF 的实例检测并描述两幅图像的特征点:
// 定义关键点的容器
std::vector<cv::KeyPoint> keypoints1;
std::vector<cv::KeyPoint> keypoints2;
// 定义特征检测器
cv::Ptr<cv::Feature2D> ptrFeature2D = cv::xfeatures2d::SURF::create(2000.0);
// 检测关键点
ptrFeature2D->detect(image1,keypoints1);
ptrFeature2D->detect(image2,keypoints2);
// 提取描述子
cv::Mat descriptors1;
cv::Mat descriptors2;
ptrFeature2D->compute(image1,keypoints1,descriptors1);
ptrFeature2D->compute(image2,keypoints2,descriptors2);
兴趣点描述子的计算结果是一个矩阵(即 cv::Mat 实例),矩阵的行数等于关键点容器的元素个数。每行是一个N 维的描述子容器。SURF 描述子的默认尺寸是64,而 SIFT 的默认尺寸是128。
这个容器用于区分特征点周围的强度值图案。两个特征点越相似,它们的描述子容器就会越接近。
SURF 兴趣点并不一定要使用SURF描述子,SIFT 也一样;检测器和描述子可以任意搭配。
现在可以用这些描述子来进行关键点匹配了。将第一幅图像的每个特征描述子向量与第二幅图像的全部特征描述子进行比较,把相似度最高的一对(即两个描述子向量之间的距离最短)保留下来,作为最佳匹配项。对第一幅图像的每个特征重复上述步骤。这个过程已经在 OpenCV 的 cv::BFMatcher 类中实现。
类的用法如下:
// 构造匹配器
cv::BFMatcher matcher(cv::NORM_L2);
// 匹配两幅图像的描述子
std::vector<cv::DMatch> matches;
matcher.match(descriptors1, descriptors2, matches);
略
cv::Feature2D 类有一个很实用的函数,可在检测兴趣点的同时计算它们的描述子,调用方法为:
ptrFeature2D->detectAndCompute(image, cv::noArray(), keypoints, descriptors);
有一些策略可以提高匹配的质量,这里介绍其中的三种。
有一种简单的方法可以验证得到的匹配项,即重新进行同一个匹配过程,但在第二次匹配时,将第二幅图像的每个关键点逐个与第一幅图像的全部关键点进行比较。只有在两个方向都匹配了同一对关键点(即两个关键点互为最佳匹配)时,才认为是一个有效的匹配项。
函数cv::BFMatcher提供了一个选项来使用这个策略。把有关标志设置为true,函数就会对匹配进行双向的交叉检查:
cv::BFMatcher matcher2(cv::NORM_L2, // 度量差距
true); // 交叉检查标志
上述交叉匹配中,一个关键点可以与多个其他关键点匹配。其中错误的匹配项非常多,最好能够把它们排除掉。
为此我们需要为每个关键点找到两个最佳的匹配项,可以用 cv::Descriptor Matcher 类的 knnMatch 方法实现这个功能。因为只需要两个最佳匹配项,所以指定 k = 2:
// 为每个关键点找出两个最佳匹配项
std::vector<std::vector<cv::DMatch>> matches;
matcher.knnMatch(descriptors1, descriptors2, matches, 2); // 找出 k 个最佳匹配项
下一步是排除与第二个匹配项非常接近的全部最佳匹配项。
具体做法是循环遍历每个关键点匹配项,然后执行比率检验法,即计算排名第二的匹配项与排名第一的匹配项的差值之比(如果两个最佳匹配项相等,那么比率为1)。比率值较高的匹配项将作为模糊匹配项,从结果中被排除掉。代码如下所示:
// 执行比率检验法
double ratio= 0.85;
std::vector<std::vector<cv::DMatch>>::iterator it;
for (it= matches.begin(); it!= matches.end(); ++it) {
// 第一个最佳匹配项 / 第二个最佳匹配项
if ((*it)[0].distance / (*it)[1].distance < ratio) {
// 这个匹配项可以接受
newMatches.push_back((*it)[0]);
}
}
// newMatches 是新的匹配项集合
还有一种更加简单的策略,就是把描述子之间差值太大的匹配项排除。实现此功能的是 cv::DescriptorMatcher 类的 radiusMatch 方法:
// 指定范围的匹配
float maxDist= 0.4;
std::vector<std::vector<cv::DMatch>> matches2;
matcher.radiusMatch(descriptors1, descriptors2, matches2, maxDist);