目标跟踪算法--模板匹配

  • 目前目标跟踪在机器视觉领域发展较快,有很多跟踪方法,例如传统的模板匹配、camshift、meanshift等算法。最近在疯狂学习opencv中,所以从最简单的模板匹配实现开始。

  • 为了提高兴趣,做一个人脸检测与跟踪算法,检测部分算法见【人脸检测】。这里采用鼠标选取跟踪的目标,在利用模板匹配的方式进行目标跟踪。

  • 首先效果如视频所示:


模板匹配

算法流程

模板匹配就是在一幅图像中寻找与给定模板最相似的区域的技术。大致步骤就是:

  1. 给定模板,给定图像,
  2. 计算初始位置与模板匹配度(有很多计算方法),
  3. 滑动窗口重复步骤2,直到整副图像搜索计算完成,
  4. 根据采用的相似度计算方法,找到相似度最高的位置,标识为新的目标位置。
  5. 步骤1
  • 当然,从以上步骤可以看出如果每一次找寻与模板匹配的目标位置都要搜索整张图像,那么计算量将非常大,因此需要改进一下,因为目标移动速度没有那么快,所以我们滑动窗口搜索的区域只搜索以上一个目标位置为中心3*3的区域,这样计算量就小很多。

    opencv相关函数

    1
    2
    3
    4
    5
    6
    1. matchTemplate(InputArray image, InputArray temp1, outputArray result, int method);
    参数:
    InputArray image : 输入图像
    InputArray temp1 : 模板
    outputArray result :匹配结果
    method :匹配方法(有很多种,见匹配原理)

这个函数就是根据参数进行模板匹配,返回整个匹配区域的匹配结果,根据这个结果,利用minMaxLoc函数可以找到匹配度最高的位置。

1
2
3
4
5
6
7
8
2. void minMaxLoc(InputArray src, double* minVal,double* maxVal=0, Point* minLoc=0, Point* maxLoc=0, InputArray mask=noArray());
参数:
InputArray src : 输入单通道数据
double* minVal : 返回最小值指针
double* maxVal=0 : 返回最大值指针
Point* minLoc=0 : 返回最小值位置指针
Point* maxLoc=0 : 返回最大值位置指针
InputArray mask=noArray() : 掩模

这个函数就是找到一个阵列数据中的最大值或者最小值。。。

匹配原理

  1. 平方差匹配法 method = TM_SQDIFF
    利用被搜索位置和模板的平方差进行匹配,匹配度最高时结果为0:
  2. 归一化平方差匹配法 method = TM_SQDIFF_NORMED
  3. 相关匹配法 method = TM_CCORR
  4. 归一化相关匹配法 method = TM_CCORR_NORMED
  5. 相关系数匹配法 method = TM_CCOEFF
  6. 归一化相关系数匹配法 method = TM_CCOEFF_NORMED

算法实现

具体函数和原理都搞定了,那么实现来吧。
具体实现代码如下图:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
#include <iostream>
#include <opencv2\opencv.hpp>
using namespace std;
using namespace cv;

//全局变量
bool drawing_box = false;
bool gotModel = false;
Rect box;

//鼠标回调函数
void on_Mouse(int event, int x, int y, int flags, void * param)
{
switch(event)
{
case CV_EVENT_MOUSEMOVE:
if(drawing_box)
{
box.width = x - box.x;
box.height = y - box.y;
}
break;
case CV_EVENT_LBUTTONDOWN:
drawing_box = true;
box = Rect(x,y,0,0);
break;
case CV_EVENT_LBUTTONUP:
drawing_box = false;
if(box.width < 0)
{
box.x += box.width;
box.width *= -1;
}
if(box.height < 0)
{
box.y += box.height;
box.height *= -1;
}
gotModel = true;
break;
}
}

// 模板匹配
// Mat frame : 需要跟踪的图像
// Mat & model : 需要跟踪的模板,可以更新的
// Rect & trackBox: 得到的结果
void tracking(Mat frame, Mat & model, Rect & trackBox)
{
Mat graySrc;
cvtColor(frame, graySrc, CV_RGB2GRAY);
Rect searchWindow; //设定目标搜索范围为3*3的范围
searchWindow.width = trackBox.width * 3;
searchWindow.height = trackBox.height * 3;
searchWindow.x = trackBox.x + trackBox.width * 0.5 - searchWindow.width * 0.5;
searchWindow.y = trackBox.y + trackBox.height * 0.5 - searchWindow.height * 0.5;
searchWindow &= Rect(0, 0, frame.cols, frame.rows);

Mat similarity;
matchTemplate(graySrc(searchWindow), model, similarity, CV_TM_CCOEFF_NORMED); //采用matchTemplate函数

double mag_r;
Point point;
minMaxLoc(similarity, 0, &mag_r, 0, &point); //因为我们采用的是归一化相关匹配法,匹配程度越高,值越大,采用minMaxLoc得到最大值及位置
trackBox.x = point.x + searchWindow.x; // 更新目标位置
trackBox.y = point.y + searchWindow.y;
model = graySrc(trackBox); // 更新模板
}
// 主函数
int main()
{
VideoCapture capture;
VideoWriter outputVideo;
bool fromfile = false;
Mat frame, model;
capture.open(0);
if(!capture.isOpened())
{
cout << "Capture device failed to open!" << endl;
return -1;
}
namedWindow("TrackByModel", CV_WINDOW_AUTOSIZE);
setMouseCallback("TrackByModel", on_Mouse, NULL);
capture >> frame;
while(!gotModel)
{
if(!fromfile)
capture >> frame;

rectangle(frame,box, Scalar(0,0,255),3);

imshow("TrackByModel", frame);
if(waitKey(27) == 'q')
return 1;
}

// 如果选择了跟踪模板
cvSetMouseCallback("Tracker", NULL, NULL ); //Remove callback
Mat grayImg;
cvtColor(frame, grayImg, CV_RGB2GRAY);
model = grayImg(box); // 设置鼠标选中的方框为ROI
int frameCount = 0;
outputVideo.open( //初始化视频输出,这个调试用的,不用看这个
"my_video4.avi", //输出文件名
CV_FOURCC('D','I','V','X'), // MPEG-4 编码
30.0, // 帧率 (FPS)
frame.size(), // 单帧图片分辨率为 640x480
true // 只输入彩色图
);
if(!outputVideo.isOpened())
{
cout << "OutputVedio device failed to open!\n" << endl;
return -1;
}
while(1)
{
capture >> frame;
if( frame.empty())
return -1;
double t = (double)getTickCount();
frameCount++;
tracking(frame, model, box);
stringstream buf;
buf << frameCount;
string num = buf.str();
putText(frame, num, Point(20,20), FONT_HERSHEY_SIMPLEX, 1,Scalar(0,0,255),3);
rectangle(frame,box, Scalar(0,0,255),3);
imshow("TrackByModel", frame);
outputVideo << frame;
t = (double)cvGetTickCount() - t;
cout << "cost time: " << t / ((double)cvGetTickFrequency()*1000.) << endl;
if(waitKey(1) ==27)
break;
}
return 0;
}