模板匹配是一种在图像中定位目标的方法,通过把输入图像在实际图像上逐像素点滑动,计算特征相似性,以此来判断当前滑块图像所在位置是目标图像的概率。
在Opencv中,模板匹配定义了6种相似性对比方式:
CV_TM_SQDIFF 平方差匹配法:计算图像像素间的距离之和,最好的匹配是0,值越大,是目标的概率就越低。
CV_TM_CCORR 相关匹配法:一种乘法操作;数值从小到大,匹配概率越来越高。
CV_TM_CCOEFF 相关系数匹配法:从-1到1,匹配概率越来越高。
CV_TM_SQDIFF_NORMED 归一化平方差匹配
CV_TM_CCORR_NORMED 归一化相关匹配
CV_TM_CCOEFF_NORMED 归一化相关系数匹配
视频文件中移动物体的跟踪,本质上还是图像上目标跟踪,可以使用模板匹配方法,实现简单的匹配跟踪效果,只不过模板匹配要逐像素移动去匹配目标图像,计算量大,实时性差。
以下代码实现基于模板匹配的目标跟踪。通过鼠标单击在视频上画出矩形,定义需要跟踪的目标,在匹配到目标的时候,用目标图像刷新输入图像:
#include "core/core.hpp"
#include "highgui/highgui.hpp"
#include "imgproc/imgproc.hpp"
#include<iostream>
using namespace cv;
using namespace std;
Mat image; //视频流
Mat imageCopy; //绘制矩形框时用来拷贝原图的图像
Mat rectImage; //子图像
bool leftButtonDownFlag=false; //左键单击后视频暂停播放的标志位
Point originalPoint; //矩形框起点
Point processPoint; //矩形框终点
int resultRows; //模板匹配result的行
int resultcols; //模板匹配result的列
Mat ImageResult; //模板匹配result
double minValue; //模板匹配result最小值
double maxValude; //模板匹配result最大值
Point minPoint; //模板匹配result最小值位置
Point maxPoint; //模板匹配result最大值位置
int frameCount=0; //帧数统计
void onMouse(int event,int x,int y,int flags ,void* ustc); //鼠标回调函数
int main(int argc,char*argv[])
{
VideoCapture video(argv[1]);
double fps=video.get(CV_CAP_PROP_FPS); //获取视频帧率
double pauseTime=1000/fps; //两幅画面中间间隔
namedWindow("Man",0);
setMouseCallback("Man",onMouse);
while(true)
{
if(!leftButtonDownFlag) //鼠标左键按下绘制矩形时,视频暂停播放
{
video>>image;
frameCount++; //帧数
}
if(!image.data||waitKey(pauseTime+30)==27) //图像为空或Esc键按下退出播放
{
break;
}
if(rectImage.data)
{
ImageResult=Mat::zeros(resultRows,resultcols,CV_32FC1);
matchTemplate(image,rectImage,ImageResult,CV_TM_SQDIFF); //模板匹配
minMaxLoc(ImageResult,&minValue,&maxValude,&minPoint,&maxPoint,Mat()); //最小值最大值获取
rectangle(image,minPoint,Point(minPoint.x+rectImage.cols,minPoint.y+rectImage.rows),Scalar(0,0,255),2);
//更新当前模板匹配的模板
Mat resultImage=image(Rect(minPoint,Point(minPoint.x+rectImage.cols,minPoint.y+rectImage.rows)));
rectImage=resultImage.clone();
//当前帧数输出到视频流
stringstream ss;
ss<<frameCount;
string h="Current frame is: ";
string fff=h+ss.str();
putText(image,fff,Point(50,60),CV_FONT_HERSHEY_COMPLEX_SMALL,2,Scalar(0,0,255),2);
}
imshow("Man",image);
}
return 0;
}
//*******************************************************************//
//鼠标回调函数
void onMouse(int event,int x,int y,int flags,void *ustc)
{
if(event==CV_EVENT_LBUTTONDOWN)
{
leftButtonDownFlag=true; //标志位
originalPoint=Point(x,y); //设置左键按下点的矩形起点
processPoint=originalPoint;
}
if(event==CV_EVENT_MOUSEMOVE&&leftButtonDownFlag)
{
imageCopy=image.clone();
processPoint=Point(x,y);
if(originalPoint!=processPoint)
{
//在复制的图像上绘制矩形
rectangle(imageCopy,originalPoint,processPoint,Scalar(0,0,255),2);
}
imshow("Man",imageCopy);
}
if(event==CV_EVENT_LBUTTONUP)
{
leftButtonDownFlag=false;
Mat subImage=image(Rect(originalPoint,processPoint)); //子图像
rectImage=subImage.clone();
resultRows=image.rows-rectImage.rows+1;
resultcols=image.cols-rectImage.rows+1;
imshow("Sub Image",rectImage);
}
}
框选出的跟踪目标:
跟踪效果:
在目标特征变化不是特别快的情况下,跟踪效果还可以,同时也存在两个问题:
1. 模板匹配的速度很慢:原始视频图像大小是1920*1080的彩色RGB图像,直接拿来目标匹配,在我的机器上消耗时间大约为1s,根本谈不上实时性。优化方向可以考虑原始图像和输入图像都做金字塔缩放,速度提升应该很明显。
2. 存在跟踪漂移:随时更新跟踪目标这种在线跟踪方法,很容易导致跟踪漂移问题,特别是在目标本身的特征变化较大的情况下,严重的还有可能完全跟丢目标,并且永久丢失对目标的跟踪。优化方向可以考虑对历史上检测出的目标图像采用累积权重法生成下一个输入图像。另一个针对完全跟丢的情况,可以对目标匹配的概率设置一个阈值,小于阈值的,可能检测到的是一个跟目标差异很大的物体,不对输入图像做更新。