局部模板匹配
- 通过特征点匹配,可以将一幅图像的点集和另一幅图像(或一批图像)的点集关联起来。如果两个点集对应着现实世界中的同一个场景元素,它们就应该是匹配的。
- 仅凭单个像素就判断两个关键点的相似度显然是不够的,因此要在匹配过程中考虑每个关键点周围的图像块。如果两幅图像块对应着同一个场景元素,那么它们的像素值应该会比较相似。
- 最常见的图像块是边长为奇数的正方形,关键点的位置就是正方形的中心。可通过比较块内像素的强度值来衡量两个正方形图像块的相似度。常见的方案是采用简单的差的平方和(Sum of Squared Differences,SSD)算法。
示例程序
#include <iostream>
#include <algorithm>
#include <vector>
#include <opencv2/core/core.hpp>
#include </home/jlm/3rdparty/opencv/opencv_contrib/modules/xfeatures2d/include/opencv2/xfeatures2d.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
using namespace std;
int main(int argc, char** argv) {
if(argc != 3)
{
cerr << "don't get the right numbers of image" << endl;
return -1;
}
cv::Mat image1 = cv::imread(argv[1],cv::IMREAD_GRAYSCALE);
cv::Mat image2 = cv::imread(argv[2],cv::IMREAD_GRAYSCALE);
if(image1.empty() || image2.empty())
{
cout << "don't get the data of the argv[1]" << endl;
return -1;
}
cv::imshow("Original1", image1);
cv::imshow("Original2", image2);
cv::waitKey(0);
// 定义特征检测器
cv::Ptr<cv::FeatureDetector> ptrDetector ; // 泛型检测器指针
ptrDetector = // 这里选用FAST检测器
cv::FastFeatureDetector::create(80);
// 这里采用了可以指向任何特征检测器的泛型指针类型 cv::Ptr<cv::FeatureDetector> 。
// 上述代码可用于各种兴趣点检测器,只需在调用函数时更换检测器即可。
vector<cv::KeyPoint> vKeypoints1;
vector<cv::KeyPoint> vkeypoints2;
// 检测关键点
ptrDetector-> detect(image1, vKeypoints1);
ptrDetector-> detect(image2, vkeypoints2);
// 定义正方形邻域11X11
const int nsize(11); //邻域尺寸
cv::Rect neighborhood(0, 0, nsize, nsize); // 11X11
cv::Mat patch1;
cv::Mat patch2;
// 将一幅图像的关键点与另一幅图像的全部关键点进行比较。在第二幅图像中找出与第一幅图
// 像中的每个关键点最相似的图像块。
// 在第二幅图像中找出与第一幅图像中的每个关键点最匹配的
cv::Mat result;
std::vector<cv::DMatch> matches;
// 针对图像一的全部关键点
for (int i = 0; i < vKeypoints1.size(); i++)
{
// 定义图像块
neighborhood.x = vKeypoints1[i].pt.x - nsize/2;
neighborhood.y = vKeypoints1[i].pt.y - nsize/2;
// 如果超出图像范围,就继续处理下一个点
// 以关键点为中心的正方形邻域
if (neighborhood.x < 0 || neighborhood.y < 0 ||
neighborhood.x + nsize >= image1.cols ||
neighborhood.y + nsize >= image1.rows)
{
continue;
}
// 第一幅图像的块
patch1 = image1(neighborhood);
// 存放最匹配的值
cv::DMatch bestMatch;
// 针对第二幅图像的全部关键点
for (int j = 0; j < vkeypoints2.size(); j++)
{
// 定义图像块
neighborhood.x = vkeypoints2[j].pt.x - nsize/2;
neighborhood.y = vkeypoints2[j].pt.y - nsize/2;
// 如果邻域超出图像范围,就继续处理下一个点
if (neighborhood.x < 0 || neighborhood.y < 0 ||
neighborhood.x + nsize >= image2.cols ||
neighborhood.y + nsize >= image2.rows)
{
continue;
}
// 第二幅图像的块
patch2 = image2(neighborhood);
// 匹配两个图像块
// 注意,这里用 cv::matchTemplate 函数来计算图像块的相似度
cv::matchTemplate(patch1, patch2, result, cv::TM_SQDIFF);
// 检查是否位最佳匹配
// 找到一个可能的匹配项后,用一个 cv::DMatch 对象来表示。这个工具类存储了两个被
// 匹配关键点的序号和它们的相似度。
if(result.at<float>(0,0) < bestMatch.distance)
{
bestMatch.distance = result.at<float>(0, 0);
bestMatch.queryIdx = i;
bestMatch.trainIdx = j;
}
}
// 添加最佳匹配
matches.push_back(bestMatch);
}
// 两个图像块越相似,它们对应着同一个场景点的可能性就越大。因此需要根据相似度对匹配结果进行排序
// 提取最佳的25个匹配项
std::nth_element(matches.begin(), matches.begin() + 25, matches.end());
matches.erase(matches.begin() + 25, matches.end());
// 画出匹配结果
cv::Mat matchImage;
cv::drawMatches(image1, vKeypoints1,
image2, vkeypoints2,
matches,
matchImage,
cv::Scalar(255, 255, 255),
cv::Scalar(255, 255, 255));
cv::imshow("Result Image",matchImage);
cv::waitKey(0);
return 0;
}
这里用一个简单的标准来比较图像块,即指定 cv::TM_SQDIFF 标志,逐个像素地计算差值的平方和。在比较图像 I 1 的像素(x, y)和图像 I 2 的像素(x’, y’ )时,用下面的公式衡量相似度:
这些(i, j)点的累加值就是以每个点为中心的整个正方形模板的偏移值。如果两个图像块比较相似,它们的相邻像素之间的差距就比较小,因此累加值最小的块就是最匹配的图像块。
识别出的匹配项存储在 cv::DMatch 类型的向量中。 cv::DMatch 数据结构本质上包含两个索引,第一个索引指向第一个关键点向量中的元素,第二个索引指向第二个关键点向量中匹配上的特征点。它还包含一个数值,表示两个已匹配的描述子之间的差距。