一般而言,如果一个物体在一幅图像中被检测到关键点,那么同一个物体在其他图像中也会检测到同一个关键点。图像匹配是关键点的常用功能之一,它的作用包括关联同一场景的两幅图像、检测图像中事物的发生地点等等。
1.局部模板匹配
凭单个像素就判断两个关键点的相似度显然是不够的,因此要在匹配过程中考虑每个关键
点周围的图像块,对图像块中的像素进行逐个比较,但是并不是最可靠的。
第一步,使用FAST 检测器进行关键点提取:
// 定义特征检测器
cv::Ptr<cv::FeatureDetector> ptrDetector; // 泛型检测器指针
ptrDetector= // 这里选用FAST 检测器
cv::FastFeatureDetector::create(80);
// 检测关键点
ptrDetector->detect(image1,keypoints1);
ptrDetector->detect(image2,keypoints2);
第二步,定义匹配框,在每个图像对的关键点之间进行匹配,这里使用逐像素相差匹配:
// 在第二幅图像中找出与第一幅图像中的每个关键点最匹配的
cv::Mat result;
std::vector<cv::DMatch> matches;
// 针对图像一的全部关键点
for (int i=0; i<keypoints1.size(); i++) {
// 定义图像块
neighborhood.x = keypoints1[i].pt.x-nsize/2;
neighborhood.y = keypoints1[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<keypoints2.size(); j++) {
// 定义图像块
neighborhood.x = keypoints2[j].pt.x-nsize/2;
neighborhood.y = keypoints2[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(patch1,patch2,result, cv::TM_SQDIFF);
// 检查是否为最佳匹配
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,keypoints1, // 第一幅图像
image2,keypoints2, // 第二幅图像
matches, // 匹配项的向量
cv::Scalar(255,255,255), // 线条颜色
cv::Scalar(255,255,255)); // 点的颜色
上述方法使用图块之间相似度进行评估,也可以使用opencv中的区域模板匹配方法进一步增大搜索精确度:
// 定义搜索区域
cv::Mat roi(image2, // 这里用图像的上半部分
cv::Rect(0,0,image2.cols,image2.rows/2));
// 进行模板匹配
cv::matchTemplate(roi, // 搜索区域
target, // 模板
result, // 结果
cv::TM_SQDIFF); // 相似度
// 找到最相似的位置
double minVal, maxVal;
cv::Point minPt, maxPt;
cv::minMaxLoc(result, &minVal, &maxVal, &minPt, &maxPt);
// 在相似度最高的位置绘制矩形
// 本例中为minPt
cv::rectangle(roi, cv::Rect(minPt.x, minPt.y,
target.cols, target.rows), 255);
2.描述并匹配局部强度值模式
在图像分析中,可以用邻域包含的视觉信息来标识每个特征点,以便区分各个特征点。特征描述子通常是一个N 维的向量,在光照变化和拍摄角度发生微小扭曲时,它描述特征点的方式不会发生变化, 通常可以用简单的差值矩阵来比较描述子,例如用欧几里得距离等
基于特征的方法都包含一个检测器和一个描述子组件,与cv::Feature2D 相关的类也一样,它们都有一个检测函数(用于检测兴趣点)和一个计算函数(用于计算兴趣点的描述子)cv::SURF
和cv::SIFT,检测流程和上述一致。
// 1. 定义关键点的容器
std::vector<cv::KeyPoint> keypoints1;
std::vector<cv::KeyPoint> keypoints2;
// 2. 定义特征检测器
cv::Ptr<cv::Feature2D> ptrFeature2D =
cv::xfeatures2d::SURF::create(2000.0);
// 3. 检测关键点
ptrFeature2D->detect(image1,keypoints1);
ptrFeature2D->detect(image2,keypoints2);
// 4. 提取描述子
cv::Mat descriptors1;
cv::Mat descriptors2;
ptrFeature2D->compute(image1,keypoints1,descriptors1);
ptrFeature2D->compute(image2,keypoints2,descriptors2);
// 5. 构造匹配器
cv::BFMatcher matcher(cv::NORM_L2);
cv::BFMatcher matcher2(cv::NORM_L2, // 度量差距
true); // 可以开启 交叉检查标志
// 匹配两幅图像的描述子
std::vector<cv::DMatch> matches;
matcher.match(descriptors1,descriptors2, matches);
好的特征描述子不受照明和视角微小变动的影响,也不受图像中噪声的影响,因此它们通常
基于局部强度值的差值,SURF 描述子在关键点周围局部地应用下面的简易内核:
第一个内核度量水平方向的局部强度值差值(标为dx),第二个内核度量垂直方向的差值(标为dy)。通常将用于提取描述子向量的邻域尺寸定为特征值缩放因子的20 倍(即20σ)。然后把这个正方形区域划分成更小的4×4 子区域。对于每个子区域,在5×5 等分的位置上(用尺寸为2σ的内核)计算内核反馈值(dx 和dy)。
使用SURF 和SIFT 的特征和描述子可以进行尺度无关的匹配,能够取得较好的效果。
3.用二值描述子匹配关键点
上述描述子是浮点数类型的向量,大小为64、128等,这导致对它们的操作将耗资巨大,为了减少内存使用、降低计算量,人们引入了将一组比特位(0 和1)组合成二值描述子的概念。这里的难点在于,既要易于计算,又要在场景和视角变化时保持鲁棒性。
// 1. 定义特征检测器/描述子
// Construct the ORB feature object
cv::Ptr<cv::Feature2D> feature = cv::ORB::create(60);
// 大约60 个特征点
// 检测并描述关键点
// 2. 检测ORB 特征
feature->detectAndCompute(image1, cv::noArray(),
keypoints1, descriptors1);
feature->detectAndCompute(image2, cv::noArray(),
keypoints2, descriptors2);
// 3. 构建匹配器
cv::BFMatcher matcher(cv::NORM_HAMMING); // 二值描述子一律使用Hamming 规范】
// 4.匹配两幅图像的描述子
std::vector<cv::DMatch> matches;
matcher.match(descriptors1, descriptors2, matches);
ORB 算法在多个尺度下检测特征点,这些特征点含有方向。基于这些特征点,ORB 描述子通过简单比较强度值,提取出每个关键点的表征,在BRIEF 描述子的基础上构建的,然后在关键点周围的邻域内随机选取一对像素点,创建一个二值描述子。
比较这两个像素点的强度值,如果第一个点的强度值较大,就把对应描述子的位(bit)设为1,否则就设为0。对一批随机像素点对进行上述处理,就产生了一个由若干位(bit)组成的描述子,通常采用128 到512 位(成对地测试)。