今天呢有小伙伴问我,基于模板匹配技术方面的问题。那针对他提出来的问题我们来看看什么是模板匹配技术。有学习过《slam十四讲》的同学知道我们在进行单目稠密重建的时候,首先需要做的就是在极线上去进行块匹配,那什么是极线,什么是块匹配呢,这个也做一个粗略的解释:
(1) 极线:当前相机观测3维空间中的路标点时,会在该相机的归一化平面上有对应的投影像素点,那么该点的空间位置一定在相机光心与该像素点的连线方向上,从另一个视角看,该线段的投影也在该方向的相机成像平面下留下一条线段,这个线段就叫极线。
(2)块匹配: 确定了极线,在相机变换矩阵未知的情况下,我们如何去在另一个视角下的投影面上去找和之前相机位姿下的匹配路标投影点(像素)呢,采用了受环境因素很大的假设方法灰度不变假设, 灰度不变假设要求不同视角下观察到的同一点的像素(亮度)是相同的。由于找不同图像下同一空间点的投影匹配,单点寻找由于像素的区分性不大(该图像上有多个与之前像素亮度差不多的点),所以我们采取块匹配的方式去寻找匹配点。此时就提出了SAD, SSD, NCC方法来计算两个小块匹配度的大小(相关性) 同时也有其他的计算图像或点相关性的方法如:去均的SSD和NCC方法。
既然回忆的部分我们有了解到图像匹配相关的知识,那我们来学习OpenCV中是如何定义和解释这种匹配的。
1 模板匹配
1.1 原理
模板匹配是一项在一幅图像中寻找与另一幅模板图像最匹配(相似)部分的技术。
模板匹配不是基于直方图的(后续来将直方图),而是在输入的图像上滑动图像块,对实际的图像块与输入图像进行匹配的一种方法。
1.2 实现模板匹配的函数:matchTemplate()函数.
此时就有疑问了,slam告诉我们验证图像块的相似度的方式很多,为什么接口只有一个,那就需要我们来了解它对应的参数是什么?
函数原型:void matchTemplate(InputArray image, InputArray templ, OutputArrat result, int method)
;
- 参数一:待搜索的图像(需要在上面找匹配的图像块),8位或32位浮点型图像;
- 参数二:搜索模板,需要和原图像有一样的数据类型,且尺寸不能大于原图;
- 参数三:比较结果的映射图像,必须是单通道,32位浮点型图像,原图尺寸是WXH模板尺寸是wxh,那result一定是(W-w+1)x(H-h+1);
- 参数四:method,匹配方法,有6种匹配方法。
方法一:平方差匹配法 method = TM_SQDIFF
利用平方差进行匹配,最好匹配为0,若匹配值越大,则匹配效果越差。
方法二:归一化平方差匹配法 method = TM_SQDIFF_NORMED
方法三:相关匹配法 method = TM_CCORR
利用了模板和图像间的乘法操作,数值大时匹配度高,0表示最坏的匹配效果
方法四:归一化相关匹配 method = TM_CCORR_NORMED
方法五:系数匹配法 method = TM_CCOEFF
将模板对其均值的相对值与图像对其均值的相关值进行匹配,1表示完美匹配,-1最差匹配,0无关
方法六:归一化相关系数匹配 method=TM_CCOFF_NORMED
从精度和复杂的关系来说,平方差——相关匹配——相关系数匹配,是一个复杂度由简单到复杂的过程,但是就匹配精度,也就是匹配度来看,是一个越来越精确匹配的过程。这两个互为矛盾的特征要求我们多次测试,在自己应用时选择这种的方案。
1.3 综合实例:模板匹配
实现代码:
#include <iostream>
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
using namespace std;
using namespace cv;
#define WINDOW_NAME1 "原图"
#define WINDOW_NAME2 "效果图"
int g_MatchMethod ;
void on_Matching(int , void*);
Mat g_srcImage, g_templateImage, g_resultImage;
int main(int argc, char **argv)
{
g_srcImage = imread("../1.jpg");
g_templateImage = imread("../2.jpg");
namedWindow(WINDOW_NAME1, CV_WINDOW_AUTOSIZE);
namedWindow(WINDOW_NAME2, CV_WINDOW_AUTOSIZE);
createTrackbar("方法", WINDOW_NAME1, &g_MatchMethod, 5, on_Matching);
on_Matching(0,0);
waitKey(0);
// while(char (waitKey(1)) == 'q'){}
return 0;
}
void on_Matching(int , void*)
{
Mat srcImage;
g_srcImage.copyTo(srcImage);
int resultImage_cols = srcImage.cols - g_templateImage.rows + 1;
int resultImage_rows = srcImage.rows - g_templateImage.rows + 1;
//进行匹配和标准化
matchTemplate(g_srcImage, g_templateImage, g_resultImage, g_MatchMethod);
normalize(g_resultImage, g_resultImage, 0, 1, NORM_MINMAX, -1, Mat());
//通过函数minMaxLoc定位最匹配的位值
double minValue;
double maxValue;
Point minLocation;
Point maxLocation;
Point matchLocation;
minMaxLoc(g_resultImage, &minValue, &maxValue, &minLocation, &maxLocation, Mat());
//对于方法SQDIFF和SQDIFF_NORMED, 越小的数值有更高的匹配结果,而其他方法值越大匹配效果越好
if(g_MatchMethod == CV_TM_SQDIFF || g_MatchMethod == TM_SQDIFF_NORMED)
{
matchLocation = minLocation;
} else {
matchLocation = maxLocation;
}
//绘出矩形,显示结果
rectangle(srcImage, matchLocation, Point(matchLocation.x + g_templateImage.cols , matchLocation.y + g_templateImage.rows), Scalar(0, 0, 255), 2, 8, 0);
rectangle(g_resultImage, matchLocation, Point(matchLocation.x + g_templateImage.cols, matchLocation.y + g_templateImage.rows), Scalar(0,0, 255), 2, 8 ,0);
imshow(WINDOW_NAME1, srcImage);
imshow(WINDOW_NAME2, g_resultImage);
}
实现效果:
可以发现数值为2的相关匹配的到了错误的匹配,其他匹配还是较为准确的。