模板匹配(TemplateMatching)就是在一幅圖像中尋找和模板圖像(template)最相似的區域,該方法原理簡單計算速度快,能夠應用於目標識別,目標跟蹤等多個領域。OpenCV中對應的函數爲matchTemplate或cvMatchTemplate(參考opencvdoc),簡單介紹下:
1、函數原型
C++: void matchTemplate(InputArray image, InputArray templ, OutputArray result, int method);
C: void cvMatchTemplate(const CvArr* image, const CvArr* templ, CvArr* result, int method);
2、參數解釋
-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)如果輸入圖像和模板圖像都是彩色圖像,則三個通道分別計算上述相似度,然後求平均值。
3、功能描述
函數通過在輸入圖像image中滑動(從左到右,從上到下),尋找各個位置的區塊(搜索窗口)與模板圖像templ的相似度,並將結果保存在結果圖像result中。該圖像中的每一個點的亮度表示該處的輸入圖像與模板圖像的匹配程度,然後可以通過某方法(一般使用函數minMaxLoc)定位result中的最大值或者最小值得到最佳匹配點,最後根據匹配點和模板圖像的矩形框標出匹配區域(如下圖,紅圈標出的亮點是最佳匹配點,黑框是模板圖像矩形框)。
4、代碼demo
(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);
}
測試視頻是PETS庫裏面的:View_001.avi
運行結果: