3

MeanShift 均值漂移跟踪算法原理

 2 years ago
source link: https://changkun.de/blog/posts/meanshift-algorithm/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

MeanShift 均值漂移跟踪算法原理

Published at:

2013-09-04

  |  

Reading: 2336 words ~5min

  |  

PV/UV: 11/10

整个暑假都在做Tracking,其中最为重要的核心就是均值漂移了,均值漂移算法指的是一个迭代的步骤,即先算出当前点的漂移均值,移动该点到其漂移均值,然后以此为新的起始点,继续移动,知道满足一定条件结束。所以,均值漂移实际上是一种在一种数据的密度分布中寻找局部极值的稳定的方法。如果分布连续,那么处理则变得非常容易,在这种情况下本质上只需要对数据的密度直方图应用爬山算法。更加准确的说,均值漂移是与核密度估计的规则有关的算法。而所谓“核”,实际上是一个如同高斯分布的局部函数。如果在充足的点处拥有足够合适的带权重和尺度的核,数据的分布则可以完全根据这些核来表示。然而又与核密度的估计不同,均值漂移仅仅估计数据分布的梯度。如果变化为0的地方则表示是这个分布的峰值(当然,也有可能是局部的)。当然在附近或其他尺度上还是有可能有峰值的。

给定dd维空间RdRd中nn个样本点xi,i=1,2,…,nxi,i=1,2,…,n,nn在xx点的均值漂移向量的基本形式定义为:

Mh(x)=1k∑xi∈Sk(xi−x)Mh(x)=1k∑xi∈Sk(xi−x)

均值漂移采用的是特征值的加权概率分布来描述目标模型,属于模式识别中主要描述目标的模型,不同于自动控制理论中采用的状态方程。 目标模型一共具有mm个特征值(可以理解为像素的灰度值),于是对于序列q=qnq=qn,而u∈1,…,mu∈1,…,m有

q(u)=C∑i=1nk(∥∥∥Xi−X0H∥∥∥2)q(u)=C∑i=1nk(‖Xi−X0H‖2)

其中,X0X0为窗口中心点向量值(可能为RGB向量或者灰度值),XiXi是窗口内第ii点向量值。CC为归一化常数,保障∑mi=1qi=1∑i=1mqi=1 ,HH为核函数的带宽向量。MM为特征值的个数,对于图像处理可以理解为灰度等级划分的个数,从而特征值uu为对应的灰度等级。 δδ函数为脉冲函数,保证只具有uu特征值的像素才对概率分布作出贡献。从而kk函数可以理解为uu灰度值的一个加权频数。

同样采用的是特征值加权概率分布:

Pu(Y)=Ch∑i=1nkk(∥∥∥Xi−YHh∥∥∥2)δ(b(Xi)−u)Pu(Y)=Ch∑i=1nkk(‖Xi−YHh‖2)δ(b(Xi)−u)

其中i∈[1,…,nh]i∈[1,…,nh],YY为匹配对象的中心, XiXi是匹配窗口内第ii点向量值。 HhHh为匹配窗口的核函数带宽向量,ChCh为匹配窗口特征向量的归一化常数。

匹配对象与目标模型的相似程度,相似函数采用的是Bhattacharyya函数

ρ(p(Y),q)=∑u=1mPu(Y)qu−−−−−−−√ρ(p(Y),q)=∑u=1mPu(Y)qu

均值漂移采用梯度下降法,首先

ρ(Y)ρ(Y)
在\rho(Y_{0})附近进行泰勒展开,去前两项,得到
ρ(Y)≈ρ(Y0)+dρdp(p(Y)−p(Y0))ρ(Y)≈ρ(Y0)+dρdp(p(Y)−p(Y0))
ρu(Y)=pu(Y)qu−−−−−−−√ρu(Y)=pu(Y)qu

ρu(Y)=ρu(Y0)+qu2pu(Y0)qu√(pu(Y)−pu(Y0))ρu(Y)=ρu(Y0)+qu2pu(Y0)qu(pu(Y)−pu(Y0))

ρu(Y)=12ρu(Y0)+12pu(Y)qupu(Y0)−−−−−−−√ρu(Y)=12ρu(Y0)+12pu(Y)qupu(Y0)
ρ(Y)=12(∑u=1mpu(Y0)qu−−−−−−−√+∑u=1m(pu(Y0)qupu(Y0)−−−−−−√))ρ(Y)=12(∑u=1mpu(Y0)qu+∑u=1m(pu(Y0)qupu(Y0)))

要使得ρ(Y)ρ(Y)向最大值迭代,只要YY的搜索方向与梯度方向一致即可,通过求导得到Y0Y0的梯度方向为:

∇ρ(Y0)=ChH2h[∑i=1nkwig(∥∥∥Y0−XiHh∥∥∥2)]⎡⎣⎢⎢∑nki=1Xiwig(∥∥Y0−XiHh∥∥2)∑nki=1wig(∥∥Y0−XiHh∥∥2)−Y0⎤⎦⎥⎥∇ρ(Y0)=ChHh2[∑i=1nkwig(‖Y0−XiHh‖2)][∑i=1nkXiwig(‖Y0−XiHh‖2)∑i=1nkwig(‖Y0−XiHh‖2)−Y0]
wi=∑u=1mqupu(Y0)−−−−−−√δ(b(Xi)−u)wi=∑u=1mqupu(Y0)δ(b(Xi)−u)

为权值。设

Y1=∑nki=1Xiwig(∥∥Y0−XiHh∥∥2)∑nki=1wig(∥∥Y0−XiHh∥∥2)Y1=∑i=1nkXiwig(‖Y0−XiHh‖2)∑i=1nkwig(‖Y0−XiHh‖2)

因此如果如下确定Y1Y1,那么Y1−Y0Y1−Y0(此即为均值漂移向量)将于梯度方向一致。

均值漂移跟踪算法的思路很清晰,我们从一个核

K(X−Xi)=ck(∥∥∥X−XiH∥∥∥2)K(X−Xi)=ck(‖X−XiH‖2)

和一个近似概率分布

P(x)=1n∑i=1nK(x−xi)P(x)=1n∑i=1nK(x−xi)

出发,重点关心P(x)P(x)的梯度

∇P(x)=1n∇K(x−xi)∇P(x)=1n∇K(x−xi)

令g(x)=−k′(x)g(x)=−k′(x),并得出了这样的式子:

∇P(x)=cn[∑i=1ngi(∥∥∥x−xiH∥∥∥2)]⎡⎣⎢∑ni=1xigi(∥∥x−xiH∥∥2)∑ni=1gi(∥∥x−xiH∥∥2)−x⎤⎦⎥∇P(x)=cn[∑i=1ngi(‖x−xiH‖2)][∑i=1nxigi(‖x−xiH‖2)∑i=1ngi(‖x−xiH‖2)−x]

对于xx向量通过上面的公式得到新的向量为

⎡⎣⎢∑ni=1xigi(∥∥x−xiH∥∥2)∑ni=1gi(∥∥x−xiH∥∥2)−x⎤⎦⎥[∑i=1nxigi(‖x−xiH‖2)∑i=1ngi(‖x−xiH‖2)−x]

而HH则是对于以xx为心的半径。 矩形核并不随着到中心的距离下降,而是一个突然变成零的突然转换。这个与高斯核的指数衰减不同,与Epanechnikov核的随着到中心的距离的开放衰减也不同。我们用自然语言来描述整个过程:

  1. 选择搜索域(包括域的初始位置,域的类型[均匀、多项式、指数或者高斯类型],域的形状,域的大小)。
  2. 计算域(可能是带权重的)的重心。
  3. 将域的重心设置在计算出的重心处。
  4. 返回(2),直到域的位置不再变化(通常会,迭代过程由最大迭代次数或者两次迭代中心变化的程度进行限制。虽然如此,迭代过程最后还是会收敛。)
int cvMeanShift( const void* imgProb, CvRect windowIn, CvTermCriteria criteria, CvConnectedComp* comp )
{
	// CvMoments计算矩形的形状特征(重心、面积等)
	CvMoments moments;
	int    i = 0, eps;
	CvMat  stub, *mat = (CvMat*)imgProb;
	CvMat  cur_win;
	CvRect cur_rect = windowIn;

	// 初始化跟踪窗口
	if( comp )
		comp->rect = windowIn;

	// 把0阶矩和1阶矩初始化置0
	moments.m00 = moments.m10 = moments.m01 = 0;

	mat = cvGetMat( mat, &stub );

	if( CV_MAT_CN( mat->type ) > 1 )
		CV_Error( CV_BadNumChannels, cvUnsupportedFormat );

	if( windowIn.height <= 0 || windowIn.width <= 0 )
 		CV_Error( CV_StsBadArg, "输入窗口大小错误" );
 	windowIn = cv::Rect(windowIn) & cv::Rect(0, 0, mat->cols, mat->rows);

	// 迭代的标准,精度为1.0,迭代次数为100。
	criteria = cvCheckTermCriteria( criteria, 1., 100 );
	// 精度eps = 1
	eps = cvRound( criteria.epsilon * criteria.epsilon );

	// 最大循环次数 = 最大迭代次数criteria.max_iter = 100
	for( i = 0; i < criteria.max_iter; i++ )
 	{
 		int dx, dy, nx, ny;
 		double inv_m00;
 		cur_rect = cv::Rect(cur_rect) & cv::Rect(0, 0, mat->cols, mat->rows);
		if( cv::Rect(cur_rect) == cv::Rect() )
		{
			cur_rect.x = mat->cols/2;
			cur_rect.y = mat->rows/2;
		}
		cur_rect.width = MAX(cur_rect.width, 1);
		cur_rect.height = MAX(cur_rect.height, 1);

		// 选取搜索区域,对改矩形区域计算它的0,1阶矩
		cvGetSubRect( mat, &cur_win, cur_rect );
		cvMoments( &cur_win, &moments );

		// 计算质心
		if( fabs(moments.m00) < DBL_EPSILON )
			break;

		// 搜索区域的质量m00
		inv_m00 = moments.inv_sqrt_m00*moments.inv_sqrt_m00;
		// 搜索区域的水平重心偏移量为dx
		dx = cvRound( moments.m10 * inv_m00 - windowIn.width*0.5 );
		// 搜索区域的垂直重心偏移量为dy
		dy = cvRound( moments.m01 * inv_m00 - windowIn.height*0.5 );

		// 搜索区域的重心坐标(nx,ny)
		nx = cur_rect.x + dx;
		ny = cur_rect.y + dy;

		// 跟踪目标处于图像边缘情况下的处理
		if( nx < 0 )
 			nx = 0;
 		else if( nx + cur_rect.width > mat->cols )
			nx = mat->cols - cur_rect.width;

		if( ny < 0 )
 			ny = 0;
 		else if( ny + cur_rect.height > mat->rows )
			ny = mat->rows - cur_rect.height;

		dx = nx - cur_rect.x;
		dy = ny - cur_rect.y;
		cur_rect.x = nx;
		cur_rect.y = ny;

		// 覆盖中心质量和窗口的检查,当精度达到要求则跳出循环
		if( dx*dx + dy*dy < eps )
 			break;
 	}
 	// 返回值
 	if( comp )
 	{
 		comp->rect = cur_rect;
		comp->area = (float)moments.m00;
	}

	return i;
}

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK