模板匹配(TemplateMatching)就是在一幅图像中寻找和模板图像(template)最相似的区域,该方法原理简单计算速度快,能够应用于目标识别,目标跟踪等多个领域。OpenCV中对应的函数为matchTemplate或cvMatchTemplate(参考opencvdoc),简单介绍下:
C++: void matchTemplate(InputArray image, InputArray templ, OutputArray result, int method); C: void cvMatchTemplate(const CvArr* image, const CvArr* templ, CvArr* result, int method);
-image:输入图像。必须为8位或者32位的浮点型。
-templ:用于搜索的模板图像。必须小于输入图像并且是一样的数据类型。
-result:匹配结果图像。必须是单通道32位浮点型,且大小是(W-w+1)*(H-h+1),其中W,H分别为输入图像的宽和高,w,h分别为模板图像的宽和高。
-method:相似度衡量的方法。具体如下(这里T是templ,I是image,R是result,x’是从0到w-1,y’是从0到h-1):
(1)method=CV_TM_SQDIFF(Sum of SquaredDifference)
平方差匹配法,最好的匹配为0,值越大匹配越差;
(2)method=CV_TM_SQDIFF_NORMED
归一化平方差匹配法,第一种方法的归一化形式;
(3)method=CV_TM_CCORR(Cross Correlation)
相关匹配法,采用乘法操作,数值越大表明匹配越好;
(4)method=CV_TM_CCORR_NORMED
归一化相关匹配法,第三种方法的归一化形式;
(5)method=CV_TM_CCOEFF
,其中:相关系数匹配法,最好的匹配为1,-1表示最差的匹配;
(6)method=CV_TM_CCOEFF_NORMED
归一化相关系数匹配法,第五种方法的归一化形式;
最后需要注意:
(1)前面两种方法为越小的值表示越匹配,后四种方法值越大越匹配。原因在于我们把第一种方法CV_TM_SQDIFF的计算公式展开可以得到:
上式的第一项(模板图像T的能量)是一个常数,第三项(图像I局部的能量)也可以近似一个常数,剩下的第二项与第三种方法CV_TM_CCORR的计算公式一样。而我们知道互相关系数(Cross Correlation)越大相似度越大,所以第一、二种方法的数值越小相似度越高,而第三、四种方法的数值越大相似度越高,第五、六种方法的计算公式与第三、四种方法的类似,所以也是数值越大相似度越高。
(2)如果输入图像和模板图像都是彩色图像,则三个通道分别计算上述相似度,然后求平均值。
函数通过在输入图像image中滑动(从左到右,从上到下),寻找各个位置的区块(搜索窗口)与模板图像templ的相似度,并将结果保存在结果图像result中。该图像中的每一个点的亮度表示该处的输入图像与模板图像的匹配程度,然后可以通过某方法(一般使用函数minMaxLoc)定位result中的最大值或者最小值得到最佳匹配点,最后根据匹配点和模板图像的矩形框标出匹配区域(如下图,红圈标出的亮点是最佳匹配点,黑框是模板图像矩形框)。
(1)使用matchTemplate进行目标匹配
/** * Object matching using function matchTemplate */ #include "opencv2/highgui/highgui.hpp" #include "opencv2/imgproc/imgproc.hpp" #include <iostream> #include <stdio.h> using namespace std; using namespace cv; /// 全局变量 /// Mat srcImg; //原始图像 Mat templImg; //模板图像 Mat resultImg; //匹配结果图像 const char* imageWindow = "Source Image"; //原始图像显示窗口 const char* resultWindow = "Result Window"; //匹配结果图像显示窗口 int matchMethod; //匹配方法index int maxTrackbar = 5; //滑动条范围(与匹配方法个数对应) /// 函数声明 /// void MatchingMethod( int, void* ); //匹配函数 int main( int argc, char** argv ) { // 加载原始图像和模板图像 srcImg = imread( "D:\\opencv_pic\\cat0.jpg", 1 ); templImg = imread( "D:\\opencv_pic\\cat3d120.jpg", 1 ); // 创建显示窗口 namedWindow( imageWindow, CV_WINDOW_AUTOSIZE ); namedWindow( resultWindow, CV_WINDOW_AUTOSIZE ); // 创建滑动条 char* trackbarLabel = "Method: \n \ 0: SQDIFF \n \ 1: SQDIFF NORMED \n \ 2: TM CCORR \n \ 3: TM CCORR NORMED \n \ 4: TM COEFF \n \ 5: TM COEFF NORMED"; //参数:滑动条名称 显示窗口名称 匹配方法index 滑动条范围 回调函数 createTrackbar( trackbarLabel, imageWindow, &matchMethod, maxTrackbar, MatchingMethod ); MatchingMethod( 0, 0 ); waitKey(0); return 0; } /// 函数定义 /// void MatchingMethod( int, void* ) //匹配函数 { // 深拷贝用于显示 Mat displayImg; srcImg.copyTo( displayImg ); // 创建匹配结果图像,为每个模板位置存储匹配结果 // 匹配结果图像大小为:(W-w+1)*(H-h+1) int result_cols = srcImg.cols - templImg.cols + 1; int result_rows = srcImg.rows - templImg.rows + 1; resultImg.create( result_cols, result_rows, CV_32FC1 ); // 进行匹配并归一化 matchTemplate( srcImg, templImg, resultImg, matchMethod ); normalize( resultImg, resultImg, 0, 1, NORM_MINMAX, -1, Mat() ); // 使用minMaxLoc找出最佳匹配 double minVal, maxVal; Point minLoc, maxLoc, matchLoc; minMaxLoc( resultImg, &minVal, &maxVal, &minLoc, &maxLoc, Mat() ); // 对于CV_TM_SQDIFF和 CV_TM_SQDIFF_NORMED这两种方法,最小值为最佳匹配;对于别的方法最大值为最佳匹配 if( matchMethod == CV_TM_SQDIFF || matchMethod == CV_TM_SQDIFF_NORMED ) { matchLoc = minLoc; } else { matchLoc = maxLoc; } // 在原始图像和匹配结果图像中以最佳匹配点为左上角标出最佳匹配框 rectangle( displayImg, matchLoc, Point( matchLoc.x + templImg.cols , matchLoc.y + templImg.rows ), Scalar::all(0), 2, 8, 0 ); rectangle( resultImg, matchLoc, Point( matchLoc.x + templImg.cols , matchLoc.y + templImg.rows ), Scalar::all(0), 2, 8, 0 ); imshow( imageWindow, displayImg ); imshow( resultWindow, resultImg ); return; }模板图为:
运行结果:
这是第一种方法的结果,最暗处为最佳匹配;其他几种方法的结果是最亮处为最佳匹配。
(2)使用matchTemplate进行目标跟踪
/** * Object tracking using function matchTemplate */ #include "opencv2/highgui/highgui.hpp" #include "opencv2/imgproc/imgproc.hpp" #include <iostream> #include <stdio.h> using namespace std; using namespace cv; /// 全局变量 /// Rect templRect; //模板矩形框 bool drawing_rect = false; //是否绘制矩形框 const char* windowName = "Object Tracker"; //窗口名称 /// 绘制模板矩形框 /// void mouseHandler(int event, int x, int y, int flags, void *param) { switch( event ) { case CV_EVENT_LBUTTONDOWN: drawing_rect = true; //初始位置 templRect = Rect( x, y, 0, 0 ); break; case CV_EVENT_LBUTTONUP: drawing_rect = false; //从右往左 if( templRect.width < 0 ) { templRect.x += templRect.width; templRect.width *= -1; } //从下往上 if( templRect.height < 0 ) { templRect.y += templRect.height; templRect.height *= -1; } break; case CV_EVENT_MOUSEMOVE: if (drawing_rect) { //从左往右,从上往下 templRect.width = x - templRect.x; templRect.height = y - templRect.y; } break; } } /// 函数声明 /// void tracking(Mat frame, Mat &templ, Rect &rect); int main(int argc, char * argv[]) { //读取视频 VideoCapture capture; capture.open("D:\\opencv_video\\PETS_test\\View_001.avi"); if (!capture.isOpened()) { cout << "capture device failed to open!" << endl; return -1; } //注册鼠标事件回调函数 namedWindow(windowName, CV_WINDOW_AUTOSIZE); setMouseCallback(windowName, mouseHandler, NULL ); //显示第一帧 Mat frame, grayFrame; capture >> frame; imshow(windowName, frame); //等待绘制模板矩形框 waitKey(0); //提取模板矩形框区域作为模板图像 cvtColor(frame, grayFrame, CV_RGB2GRAY); //灰度化 Mat templ = grayFrame(templRect); //持续跟踪 int countFrame = 0; while (1) { capture >> frame; if (frame.empty()) break; countFrame++; //计时 double start = (double)getTickCount(); //基于模板匹配的跟踪过程 //参数:帧图像,模板图像,模板矩形框 tracking(frame, templ, templRect); //显示 rectangle(frame, templRect, Scalar(0, 0, 255), 3); imshow(windowName, frame); //计时 double end = (double)getTickCount(); cout << "Frame: "<<countFrame<<"\tTime: " << (end - start)*1000 / (getTickFrequency()) <<"ms"<< endl; if (waitKey(1) == 27) break; } return 0; } /// 函数定义 /// //根据上一个模板矩形框rect来设定当前搜索窗口searchWindow, //并通过匹配(matchTemplate)搜索窗口searchWindow与当前模板图像templ来找到最佳匹配, //最后更新模板矩形框rect与模板图像templ。 void tracking(Mat frame, Mat &templ, Rect &rect) { Mat grayFrame; cvtColor(frame, grayFrame, CV_RGB2GRAY); //选取搜索窗口 Rect searchWindow; searchWindow.width = rect.width * 3; searchWindow.height = rect.height * 3; searchWindow.x = rect.x + rect.width * 0.5 - searchWindow.width * 0.5; searchWindow.y = rect.y + rect.height * 0.5 - searchWindow.height * 0.5; searchWindow &= Rect(0, 0, frame.cols, frame.rows); //模板匹配 Mat similarity; matchTemplate(grayFrame(searchWindow), templ, similarity, CV_TM_CCOEFF_NORMED); //找出最佳匹配的位置 double maxVal; Point maxLoc; minMaxLoc(similarity, 0, &maxVal, 0, &maxLoc); //更新模板矩形框 rect.x = maxLoc.x + searchWindow.x; rect.y = maxLoc.y + searchWindow.y; //更新模板 templ = grayFrame(rect); }
运行结果: