图像处理中有着目标识别与目标跟踪两种概念,后者也被常被成为Tracking。网上大部分的目标捕捉教程都是“目标识别”,譬如特征提取、光流法等等。然而将目标识别与目标跟踪结合使用,能稳定捕捉频率、提高性能。
先谈谈为什么单纯使用目标识别不能“稳定捕捉频率”“提高性能”:
1、不能稳定捕捉频率
因为目标识别常需要通过提取特征、判断诸多条件来实现,而如果每一帧都进行识别操作,会导致目标的捕捉与否与识别率直接挂钩。因某些特定帧会出现无法识别的情况,这将导致目标捕捉的时断时续,且断续的频率不稳定。这会使电控、机械端较难使用图像捕捉的结果进行控制操作。比如你通过串口给主控板传输时断时续的图像识别结果,主控板中定要对这些数据进行整合,而这将导致实际运动的明显延迟。
2、不能提高性能
性能最直接的反馈之一是帧率,因所用方法的区别,大部分的目标识别消耗的时间都比目标跟踪消耗的时间多。因此即便通过降低目标识别的“门槛”(对特征的筛选条件等),来弥补单纯使用目标识别而导致的如上1中的问题,也无法弥补性能方面识别与跟踪的差距。
而将目标识别与目标跟踪结合使用,就能较好地解决以上两个问题:对于对识别准确率要求不是特别高的目标,在未识别到目标的帧中使用目标跟踪能在兼顾性能的同时,稳定捕捉频率。
在未识别到目标的帧中使用目标跟踪”,简述其思路。
OpenCV中提供了实现目标跟踪Tracking的诸多方法,博主暂时对这些现成的方法没有多少研究。本文中用的目标跟踪方法是模板匹配,即OpenCV3中的matchTemplate()函数与minMaxLoc()函数,模板匹配及这两个函数的使用方法可见要点初见:从旅行青蛙开始的OpenCV3中模板匹配的探索
如何在未识别到目标的帧中使用目标跟踪呢?最重要的有五点:
(1)目标识别成功时,将目标保存为模板匹配所需的模板,以供下一帧的目标跟踪使用;
(2)对连续识别到目标的帧数进行计数,若超过一定帧数连续识别到目标,则允许接下来的若干帧可以在识别不到目标的情况下进行模板匹配(即目标跟踪);
(3)限制跟踪窗口大小!即不对全图,而是对识别或跟踪到的目标的附近用模板比对,可以大大降低运算量;
(4)对模板匹配到的目标进行简单的特征判断,尽可能排除假匹配。因为模板匹配一定会找到一个最相似区域,无论是否目标是否存在;
(5)对连续未识别到目标的帧数进行计数,超过一定帧数未识别到,则中断模板匹配(即目标跟踪)。这样可以避免目标消失后仍在强行跟踪过长时间。
以上五点我们将在如下代码片段中进行分析:
vector<Point> track_points;
int result_cols = 0;
int result_rows = 0;
if(convert_dirct.z == 1)//如果识别到目标
{
Is_recognizing = 1;//更新识别状态为正在识别
Is_tracking = 0;//更新跟踪状态为不在跟踪
TargetGet_times++;//统计识别到的帧数
TargetLost_times = 0;//更新未识别到的帧数为0
//当识别到目标时,更新目标跟踪的模板为识别到的目标
for(int i = 0;i < 4;i++)
{
if(rectpoint[i].x > imagehsv.cols)//rectpoint[i]为识别到的矩形目标的四个角点
rectpoint[i].x = imagehsv.cols-1;//如果没有-1,数据可能会超过摄像头分辨率的行数或列数,从而导致程序中断,下方的-1与1同此
if(rectpoint[i].y > imagehsv.rows)
rectpoint[i].y = imagehsv.rows-1;
if(rectpoint[i].x < 0)
rectpoint[i].x = 1;
if(rectpoint[i].y < 0)
rectpoint[i].y = 1;
track_points.push_back(rectpoint[i]);//需以这种方式导入四个角点
}
Rect track_window = boundingRect(track_points);//创建目标跟踪的窗口为一个ROI区域(这里非常容易因超出行数列数而导致程序中断)
//下方的1与-1是为了避免目标跟踪的窗口超出分辨率边缘
if(track_window.x <= 0)
track_window.x = 1;
if(track_window.x + track_window.width >= imagehsv.cols)//imagehsv为当前待测图片
track_window.x = imagehsv.cols - track_window.width - 1;
if(track_window.y <= 0)
track_window.y = 1;
if(track_window.y + track_window.height >= imagehsv.rows)
track_window.y = imagehsv.rows - track_window.height - 1;
track_HSV = imagehsv(track_window);//此处把跟踪窗口转成hsv是为了与hsv格式的原图匹配
result_cols = imagehsv.cols - track_HSV.cols + 1;
result_rows = imagehsv.rows - track_HSV.rows + 1;
//cout << result_cols << " result " << result_cols << endl;//用于调试中断是否因为ROI超出边缘
}
else//如果未识别到目标(注意此处未设置跟踪状态)
{
Is_recognizing = 0;//更新识别状态为不在识别
TargetGet_times = 0;//更新识别到的帧数为0
TargetLost_times++;//统计未识别到的帧数
}
Mat result(result_cols,result_rows,CV_32FC1);
//何时目标跟踪,何时停止目标跟踪
if(TargetGet_times > 2)//当连续识别到目标超过2帧时
{
track_object = 1;//目标跟踪flag置1
}
if(TargetLost_times > 300)//当连续未识别到目标超过300帧时
{
track_object = 0;//停止目标跟踪(即300帧内都允许跟踪)
Is_tracking = 0;//更新跟踪状态为不在跟踪
}
Mat image_roix;
int side_x = 0;
int side_y = 0;
int trackArea_x = 300;//跟踪窗口大小长300
int trackArea_y = 300;//跟踪窗口大小宽300
if(track_object && (track_HSV.cols <= 300) && (track_HSV.rows <= 300))//目标跟踪flag要求为1,且限制跟踪窗口不能超过300*300大小(因目标在视野中过大时,易出现目标仅有一部分在视野中)
{
if(convert_dirct.z == 0)//如果未识别到目标
{
Is_tracking = 1;//更新跟踪状态为正在跟踪
//使目标跟踪的ROI窗口在靠近分辨率边缘时,能不超出边缘(否则程序会崩溃)
if((front_x + trackArea_x/2 < imagehsv.cols) && (front_y + trackArea_y/2 < imagehsv.rows) && (front_x - trackArea_x/2 > 0) && (front_y - trackArea_y/2 > 0))//当跟踪窗口不靠近分辨率边缘时(front_x和front_y是上一帧目标中心点的横纵坐标(左上坐标系))
{
image_roix = imagehsv(Rect(front_x-trackArea_x/2,front_y-trackArea_y/2,trackArea_x,trackArea_y));//重新设定ROI避免超出边缘;300为跟踪的长宽,修改跟踪窗口大小时需连着修改
side_x = 1;//确定窗口中心位置时x的系数
side_y = 1;//确定窗口中心位置时y的系数
}
if((front_x + trackArea_x/2 >= imagehsv.cols) && (front_y + trackArea_y/2 < imagehsv.rows) && (front_y - trackArea_y/2 > 0))//当跟踪窗口在分辨率右边缘时
{
image_roix = imagehsv(Rect(front_x-trackArea_x/2,front_y-trackArea_y/2,imagehsv.cols-(front_x-trackArea_x/2),trackArea_y));
side_x = 1;
side_y = 1;
}
if((front_y + trackArea_y/2 >= imagehsv.rows) && (front_x + trackArea_x/2 < imagehsv.cols) && (front_x - trackArea_x/2 > 0))//当跟踪窗口在分辨率下边缘时
{
image_roix = imagehsv(Rect(front_x-trackArea_x/2,front_y-trackArea_y/2,trackArea_x,imagehsv.rows-(front_y-trackArea_y/2)));
side_x = 1;
side_y = 1;
}
if((front_x + trackArea_x/2 >= imagehsv.cols) && (front_y + trackArea_y/2 >= imagehsv.rows))//当跟踪窗口在分辨率右下边缘时
{
image_roix = imagehsv(Rect(front_x-trackArea_x/2,front_y-trackArea_y/2,imagehsv.cols-(front_x-trackArea_x/2),imagehsv.rows-(front_y-trackArea_y/2)));
side_x = 1;
side_y = 1;
}
if((front_y + trackArea_y/2 < imagehsv.rows) && (front_x - trackArea_x/2 <= 0) && (front_y - trackArea_y/2 > 0))//当跟踪窗口在分辨率左边缘时
{
image_roix = imagehsv(Rect(0,front_y-trackArea_y/2,trackArea_x,trackArea_y));
side_x = 0;
side_y = 1;
}
if((front_x + trackArea_x/2 < imagehsv.cols) && (front_x - trackArea_x/2 > 0) && (front_y - trackArea_y/2 <= 0))//当跟踪窗口在分辨率上边缘时
{
image_roix = imagehsv(Rect(front_x-trackArea_x/2,0,trackArea_x,trackArea_y));
side_x = 1;
side_y = 0;
}
if((front_x - trackArea_x/2 <= 0) && (front_y - trackArea_y/2 <= 0))//当跟踪窗口在分辨率左上边缘时
{
image_roix = imagehsv(Rect(0,0,trackArea_x,trackArea_y));
side_x = 0;
side_y = 0;
}
if((front_y + trackArea_y/2 >= imagehsv.rows) && (front_x - trackArea_x/2 <= 0))//当跟踪窗口在分辨率左下边缘时
{
image_roix = imagehsv(Rect(0,front_y-trackArea_y/2,trackArea_x,imagehsv.rows-(front_y-trackArea_y/2)));
side_x = 0;
side_y = 1;
}
if((front_x + trackArea_x/2 >= imagehsv.cols) && (front_y - trackArea_y/2 <= 0))//当跟踪窗口在分辨率右上边缘时
{
image_roix = imagehsv(Rect(front_x-trackArea_x/2,0,imagehsv.cols-(front_x-trackArea_x/2),trackArea_y));
side_x = 1;
side_y = 0;
}
matchTemplate(image_roix,track_HSV,result,CV_TM_SQDIFF_NORMED);//对跟踪区域,用跟踪模板进行匹配
normalize(result,result,0,1,NORM_MINMAX,-1,Mat());
double minVal;
double maxVal;
Point minLoc;
Point maxLoc;
Point matchLoc;
minMaxLoc(result, &minVal, &maxVal, &minLoc, &maxLoc, Mat());
matchLoc = minLoc;//等于minLoc还是maxLoc受上方CV_TM_SQDIFF_NORMED的影响
//此处为对模板匹配到的目标进行简单的特征判断,尽可能排除假匹配
......
}
}
}
这段目标跟踪代码跟踪到的目标中心的横坐标为
(front_x - trackArea_x/2)*side_x + (matchLoc.x + matchLoc.x + track_HSV.cols)/2
纵坐标为
(front_y - trackArea_y/2)*side_y + (matchLoc.y + matchLoc.y + track_HSV.rows)/2
欢迎讨论!