手的简单跟踪加方向判断


(一)跟踪

最近与同学完成了一个小项目,做了其中的一部分工作,现在答辩完了,也就可以拿出来分享一下思路:
跟踪是在识别手之后开始的,手的识别是由Hu矩加上轮廓的面积周长比:
	double contArea = fabs(contourArea(contours[i]));

	double contLength = arcLength(contours[i], false);

	double contCircularity = contLength*contLength / contArea;//周长面积比

	Moments omement;
	omement =  moments(contours[i]);
	Mat hu;
	HuMoments(omement, hu);//hu矩,主要用到前两个矩                                                                              <span style="font-family: Arial, Helvetica, sans-serif;">//然后将两者结合起来判断</span>
(这个可以加上各种学习算法优化,但不是我的主要想谈论的点,因为学习之类的算法接触得比较少,不理解原理),也就是说,识别到手之后,给跟踪器一个初始化的框,这个框的选取非常重要,可以通过框选中的内容的特征如颜色直方图,梯度等,这些决定之后的跟踪是否能够很好地收敛于目标物体。
跟踪一开始打算采取的是CamShift跟踪,因为它能够简单地判断是否跟丢物体, 简单地来说,CamShift通过MeanShift和颜色直方图来获得当前帧目标的一些尺度信息,然后再利用上一帧得到的结果作为初始值一直迭代下去,直到不符合跟踪条件,跳出跟踪。MeanShift获取反向投影图的过程是这样的:图像--->直方图--->反向投影图,反向投影亮的地方,表明物体出现在该处的概率大,可以看作是概率密度函数的表征:
unsigned int colourModel::findBin(unsigned char R,unsigned char G,unsigned char B)
{
	//根据RGB颜色返回直方图的位置
	unsigned int r,g,b;
	r = (unsigned int)floor( (float)(R/BINSIZE) );
	g = (unsigned int)floor( (float)(G/BINSIZE) );
	b = (unsigned int)floor( (float)(B/BINSIZE) );
	
	return (r + BPC*g + BPC*BPC*b);
}
之后选取Epanechnikov作为核函数计算目标模型直方图,用公式表示就是:

double CMsTracker::kernel(int x,int y,int half_x,int half_y)
{
	double euclideanDistance = sqrt( pow( ( (double)(x)/(double)(half_x) ) ,2.0) +pow( ( (double)                                          (y)/(double)(half_y) ) ,2.0) );
	if (euclideanDistance > 1)
		return( 0.0);
	else
		return(1.0-pow(euclideanDistance,2));
}
void CMsTracker::evalKernel (double*** kArray,int half_x,int half_y)//计算核函数直方图
{
	for (int x = -half_x; x < half_x; x++)
	{
		for (int y = -half_y; y < half_y; y++)
		{
			(*kArray)[x+half_x][y+half_y] = kernel(x,y,half_x,half_y);
		}
	}
}
void CMsTracker::evalKernelDeriv (int*** kArray,int half_x,int half_y)                                //计算微分核函数直方图,求局部最小值
{
	double euclideanDistance;
	for (int x = -half_x;x < half_x; x++)
	{
		for (int y = -half_y;y < half_y; y++)
		{
			euclideanDistance = sqrt( pow( ( (double)(x)/(double)(half_x) ) ,2.0) +pow( (                                                   (double)(y)/(double)(half_y) ) ,2.0) );
			if (euclideanDistance > 1)
				(*kArray)[x+half_x][y+half_y] = 0;
			else
				(*kArray)[x+half_x][y+half_y] = 1;
		}
	}
}
候选区域做类似目标区域的操作,同样能得到一个直方图, 那接下的是比较并且求出候选区域的位置 MeanShift的已实现代码中,是用Bhattacharryya--巴氏系数来衡量目标模型跟候选目标的相似度),这里计算中心的位移得到:
void CMsTracker::computeDisplacement(double*** weights,int*** kArray,unsigned int centre_x,unsigned int centre_y,int half_x,int half_y,int *dx,int *dy)
{
	double weight_sum = 0;
	double x_sum =0, y_sum=0;
	double curPixelWeight;
	for (int x = -half_x;x < half_x; x++)
	{
		for (int y = -half_y;y < half_y; y++)
		{
			curPixelWeight = (*weights)[x+half_x][y+half_y]*(*kArray)[x+half_x][y+half_y];
			weight_sum += curPixelWeight;
			x_sum += x*curPixelWeight;
			y_sum += y*curPixelWeight;
		}
	}
	*dx = (int)floor(x_sum/weight_sum);
	*dy = (int)floor(y_sum/weight_sum);
}

MeanShift算法对候选区域有个要求,即尺度的变化不能过于迅速,而且受初始值的影响大,框过大或者过小都可能会出现无法收敛的情况,很多情况下效果并不出色。 为了能够改变跟踪时的搜索框的大小,这里大概讨论一下CamShift的一些步骤:
设上面得到的反向投影图的一阶矩为 M 10、 M 01,二阶矩为 M 20、 M 02、 M 11, g(x,y) 为反向投影图在 (x,y) 处的概率值,则有:

跟踪目标的质心位置为:

            

CamShift算法在此基础上,加上一个椭圆锁定跟踪目标,椭圆的长度可由下面的几个式子得出:

            手的简单跟踪加方向判断_第1张图片

     式中,

而定义椭圆长轴与水平面的夹角为θ(θ < 180度),

后来试了一下KCF(核相关滤波)跟踪,发现KCF在快速移动的物体上效果并不是特别好,在尝试的时候,手稍微快速挥动一下都可能丢掉目标,更加关键的是暂时还没找到终止跟踪的条件,这就让我们的跟踪很难用得上,毕竟在我们的场景下不能一直保持跟踪而不去主动识别。这里就暂时不讨论KCF算法了,毕竟也还没怎么认真地看过那篇论文。

                                                          (二)方向判断


     采用freeman链码对手势的运动方向进行编码,模版如图:

                              手的简单跟踪加方向判断_第2张图片
     我们这里实际上只需用到三个方向,即向左、向右和静止,用公式表示如下:
                                     
     有了方向的定义之后,我们采取了如下的流程来计算运动方向:
                                

         除了,上述简单的一个方向判断外,我们还用MHI(运动历史图像)来求手的运动的方向,它是一种人体运动的全局描述方法,其每个像素值是此像素点上运动时间的方程。记B(x,y,t)为运动人体的二值轮廓图像序列,则运动历史图像H(x,y,t)的计算方法为:

                                                                                         

        τ为时间窗口长度,即一个运动视频序列的帧数,表示运动的持续时间。因此,得到的MHI是一个灰度图,亮度大的区域表示最新运动的区域,随着时间的推移,旧的区域会慢慢变暗,直至变为0。

      用第一个方法写出的代码如下:
int calcDirection(Point current, int& preDirection)
{
	motionPoint.push_back(current);
	int pointSize = motionPoint.size();
	int currDirection;		//计算当前的方向
	if (pointSize < 5)		//开始时运动的5个点不计算在内
		return 0;
	Point pre = motionPoint[pointSize - 2];//前一个点

	int absDist = abs(current.x - pre.x);
	if (absDist < 5){		//小范围扰动
		currDirection = 0; }
	else{
		currDirection = (current.x - pre.x) / absDist; }
	
	if (currDirection != preDirection){
		if (currDirection == -preDirection)
			dCount++;
		else{
			tCount++;
			if (tCount++ > 4){
				preDirection = currDirection;
				tCount = 0;dCount = 0;
			}
		}
		if (dCount > 5 && pointSize >10){
			motionPoint.clear();
			preDirection = currDirection;
			dCount = 0; tCount = 0;
			return -currDirection;
		}
	}
	return 0;
}
用MHI写的判断方向的代码:
void  update_mhi(const Mat& img, Rect region, int diff_threshold)
{
	double timestamp = (double)clock() / CLOCKS_PER_SEC; 
	Size size = img.size();
	int idx1 = last;
	double angle;

	if (mhi.size() != size)
	{
		mhi = Mat::zeros(size, CV_32F);
		zplane = Mat::zeros(size, CV_8U);

		buf[0] = Mat::zeros(size, CV_8U);
		buf[1] = Mat::zeros(size, CV_8U);
	}

	cvtColor(img, buf[last], COLOR_BGR2GRAY); 

	int idx2 = (last + 1) % 2;
	last = idx2;

	Mat silh = buf[idx2];
	absdiff(buf[idx1], buf[idx2], silh); 

	threshold(silh, silh, diff_threshold, 1, THRESH_BINARY); 
	updateMotionHistory(silh, mhi, timestamp, MHI_DURATION); 

	mhi.convertTo(mask, CV_8U, 255. / MHI_DURATION, (MHI_DURATION - timestamp)*255. / MHI_DURATION);
	
	calcMotionGradient(mhi, mask, orient, MAX_TIME_DELTA, MIN_TIME_DELTA, 3);
	region &= Rect(0, 0, mhi.cols, mhi.rows);
	Mat silh_roi = silh(region);
	Mat mhi_roi = mhi(region);
	Mat orient_roi = orient(region);
	Mat mask_roi = mask(region);

	angle = calcGlobalOrientation(orient_roi, mask_roi, mhi_roi, timestamp, MHI_DURATION);
	angle = 360.0 - angle; 
}

总结:一些尝试是重要的,仅仅看论文可能效果不如论文+代码的方式效果好,还有思路非常重要,看起来简单的方法往往也有意想不到的效果,团队中思想的碰撞往往能产生精彩的思路。



参考资料:
      [1] Dorin Comaniciu, Visvanathan Ramesh, Peter Meer.Kernel-based object tracking. TPAMI, 2003.
      [2] 侯建华, 黄奇, 项俊, 郑桂林.一种尺度和方向适应性的Meanshift跟踪算法. JSCUN, 2014.
      [3] 彭娟春, 顾立忠, 苏剑波.基于Camshift和Kalman滤波的仿人机器人手势跟踪. JSJTU, 2005.

你可能感兴趣的:(手的简单跟踪加方向判断)