模板匹配是一项在一-幅图像中寻找与另一幅模板图像最匹配(相似)部分的技术。在OpenCV2和OpenCV3中,模板匹配由MatchTemplate()函数完成。需要
注意,模板匹配不是基于直方图的,而是通过在输入图像上滑动图像块,对实际的图像块和输入图像进行匹配的一种匹配方法。
模板匹配的工作方式
模板匹配的工作方式跟直方图的反向投影基本一样,大致过程是这样的:通过在输入图像上滑动图像块对实际的图像块和输入图像进行匹配。
假设我们有一张100x100的输入图像,有一张10x10的模板图像,查找的过程是这样的:
(1)从输入图像的左上角(0,0)开始,切割一块(0,0)至(10,10)的临时图像;
(2)用临时图像和模板图像进行对比,对比结果记为c;
(3)对比结果c,就是结果图像(0,0)处的像素值;
(4)切割输入图像从(0,1)至(10,11)的临时图像,对比,并记录到结果图像;
(5)重复(1)~(4)步直到输入图像的右下角。
程序实现了什么?
- 载入一幅输入图像和一幅模板图像块 (template)
-
- 通过使用函数 matchTemplate 实现之前所述的6种匹配方法的任一个. 用户可以通过滑动条选取任何一种方法.
- 归一化匹配后的输出结果
- 定位最匹配的区域
- 用矩形标注最匹配的区域
#include <opencv2/core/core.hpp> #include <opencv2/imgproc/imgproc.hpp> #include <opencv2/highgui/highgui.hpp> #include <iostream> using namespace cv; using namespace std; Mat g_srcImage, g_tempalteImage, g_resultImage; int g_nMatchMethod; int g_nMaxTrackbarNum = 5; void on_matching(int, void*) { Mat srcImage; g_srcImage.copyTo(srcImage); int resultImage_cols = g_srcImage.cols - g_tempalteImage.cols + 1; int resultImage_rows = g_srcImage.rows - g_tempalteImage.rows + 1; g_resultImage.create(resultImage_cols, resultImage_rows, CV_32FC1); matchTemplate(g_srcImage, g_tempalteImage, g_resultImage, g_nMatchMethod); normalize(g_resultImage, g_resultImage, 0, 2, NORM_MINMAX, -1, Mat()); double minValue, maxValue; Point minLocation, maxLocation, matchLocation; minMaxLoc(g_resultImage, &minValue, &maxValue, &minLocation, &maxLocation); if (g_nMatchMethod == TM_SQDIFF || g_nMatchMethod == CV_TM_SQDIFF_NORMED) { matchLocation = minLocation; } else { matchLocation = maxLocation; } rectangle(srcImage, matchLocation, Point(matchLocation.x + g_tempalteImage.cols, matchLocation.y + g_tempalteImage.rows), Scalar(0, 0, 255), 2, 8, 0); rectangle(g_resultImage, matchLocation, Point(matchLocation.x + g_tempalteImage.cols, matchLocation.y + g_tempalteImage.rows), Scalar(0, 0, 255), 2, 8, 0); imshow("原始图", srcImage); imshow("效果图", g_resultImage); } int main() { g_srcImage = imread("E:\VS2015Opencv\vs2015\project\picture\02.jpg"); if (!g_srcImage.data) { cout << "原始图读取失败" << endl; return -1; } g_tempalteImage = imread("E:\VS2015Opencv\vs2015\project\picture\021.jpg"); if (!g_tempalteImage.data) { cout << "模板图读取失败" << endl; return -1; } namedWindow("原始图", CV_WINDOW_AUTOSIZE); namedWindow("效果图", CV_WINDOW_AUTOSIZE); createTrackbar("方法", "原始图", &g_nMatchMethod, g_nMaxTrackbarNum, on_matching); on_matching(0, NULL); waitKey(0); return 0; }
代码解析:
-
定义一些全局变量, 例如原图像g_srcImage, 模板图像g_tempalteImage,和结果图像g_resultImage, 还有匹配方法;
Mat g_srcImage, g_tempalteImage, g_resultImage; int g_nMatchMethod; int g_nMaxTrackbarNum = 5;
-
载入原图像和匹配块:
img = imread( argv[1], 1 ); templ = imread( argv[2], 1 );
-
创建窗口,显示原图像和结果图像:
g_srcImage = imread("E:\VS2015Opencv\vs2015\project\picture\02.jpg"); if (!g_srcImage.data) { cout << "原始图读取失败" << endl; return -1; } g_tempalteImage = imread("E:\VS2015Opencv\vs2015\project\picture\021.jpg"); if (!g_tempalteImage.data) { cout << "模板图读取失败" << endl; return -1; } namedWindow("原始图", CV_WINDOW_AUTOSIZE); namedWindow("效果图", CV_WINDOW_AUTOSIZE);
-
创建滑动条并输入将被使用的匹配方法. 一旦滑动条发生改变,回调函数 on_matching 就会被调用.
createTrackbar("方法", "原始图", &g_nMatchMethod, g_nMaxTrackbarNum, on_matching); on_matching(0, NULL); waitKey(0); return 0;
-
一直等待,直到用户退出这个程序.
waitKey(0); return 0;
-
让我们先看看回调函数. 首先, 它对原图像进行了一份复制:
Mat srcImage; g_srcImage.copyTo(srcImage);
-
然后, 它创建了一幅用来存放匹配结果的输出图像矩阵. 仔细看看输出矩阵的大小(它包含了所有可能的匹配位置)
int resultImage_cols = g_srcImage.cols - g_tempalteImage.cols + 1; int resultImage_rows = g_srcImage.rows - g_tempalteImage.rows + 1; g_resultImage.create(resultImage_cols, resultImage_rows, CV_32FC1);
-
执行模板匹配操作:
matchTemplate(g_srcImage, g_tempalteImage, g_resultImage, g_nMatchMethod);
很自然地,参数是输入图像 I, 模板图像 T, 结果图像 R 还有匹配方法 (通过滑动条给出)
-
我们对结果进行归一化:
normalize(g_resultImage, g_resultImage, 0, 2, NORM_MINMAX, -1, Mat());
-
通过使用函数 minMaxLoc ,我们确定结果矩阵 R 的最大值和最小值的位置.
double minValue, maxValue; Point minLocation, maxLocation, matchLocation; minMaxLoc(g_resultImage, &minValue, &maxValue, &minLocation, &maxLocation);
函数中的参数有:
- result: 匹配结果矩阵
- &minVal 和 &maxVal: 在矩阵 result 中存储的最小值和最大值
- &minLoc 和 &maxLoc: 在结果矩阵中最小值和最大值的坐标.
- Mat(): 可选的掩模
-
对于前二种方法 ( CV_SQDIFF 和 CV_SQDIFF_NORMED ) 最低的数值标识最好的匹配. 对于其他的, 越大的数值代表越好的匹配. 所以, 我们在 matchLoc 中存放相符的变量值:
if (g_nMatchMethod == TM_SQDIFF || g_nMatchMethod == CV_TM_SQDIFF_NORMED) { matchLocation = minLocation; } else { matchLocation = maxLocation; }
显示原图像和结果图像. 再用矩形框标注最符合的区域:
rec – 确定矩形的另一种方式,给左上角坐标和长宽
rectangle(srcImage, matchLocation, Point(matchLocation.x + g_tempalteImage.cols, matchLocation.y + g_tempalteImage.rows), Scalar(0, 0, 255), 2, 8, 0); rectangle(g_resultImage, matchLocation, Point(matchLocation.x + g_tempalteImage.cols, matchLocation.y + g_tempalteImage.rows), Scalar(0, 0, 255), 2, 8, 0); imshow("原始图", srcImage); imshow("效果图", g_resultImage);
方法0,1,3,4,5都是上面这个结果,方法2是下面这个结果;
前二种方法 ( CV_SQDIFF 和 CV_SQDIFF_NORMED ) 最低的数值标识最好的匹配. 对于其他的, 越大的数值代表越好的匹配. 所以, 我们在 matchLoc 中存放相符的变量值: