粒子滤波初探(二)利用粒子滤波实现视频目标跟踪-代码部分(C++&&opencv2.49)

利用粒子滤波实现视频目标跟踪工程实战


放在最前:致谢taotao1233yangyangcvyang_xian521 以及先驱 Rob Hess 所开源的代码和思路。

本篇:基本为工程翻译,以及对上面版本的一些修正,使用的是opencv2.49,以Mat类型代替了容易导致内存泄漏的Iplimage*类型,总的来说,就是从旧版的opencv数据结构翻译到opencv2.49下。yang_xian521 也做过类似工作,但是不知道是否我操作有误,运行其程序,处理速度很慢,而且跟踪效果不好,既然如此,何不自己动手,丰衣足食?

 程序的流程可以大致参考:粒子滤波初探(一)利用粒子滤波实现视频目标跟踪的大致流程 ,会有一点点不同和简化

  • 我尝试使用opencv2.49自带的随机数生成器RNG,效果不如gsl1.8的随机数生成器,因此本工程依旧采用gsl1.8【因此需要配置gsl1.8,这里不赘述】,如果哪位爷研究出来了,请下方留言。
  • 本文不再赘述通过鼠标选取目标的回调部分,详细请参见:ROI选取

直接获取完整代码见这里


 既然是粒子滤波,首先我们构造粒子的结构体:

/*********************结构体************************/
// 粒子结构体
typedef struct particle {
	double x;			// 当前x坐标
	double y;			// 当前y坐标
	double scale;		// 窗口比例系数
	double xPre;			// x坐标预测位置
	double yPre;			// y坐标预测位置
	double scalePre;		// 窗口预测比例系数
	double xOri;			// 原始x坐标
	double yOri;			// 原始y坐标
	int width;			// 原始区域宽度
	int height;			// 原始区域高度
	//Rect rect;			// 原始区域大小
	MatND hist;			// 粒子区域的特征直方图
	double weight;		// 该粒子的权重
} PARTICLE;

零、创建粒子数组:

	int num_particles = NUM_PARTICLE; //粒子数
	PARTICLE particles[NUM_PARTICLE];
	PARTICLE new_particles[NUM_PARTICLE];

一、t-1时刻:视频读取【不展开】

二、等待鼠标选取目标【不展开】

三、选取目标完成,则进行如下操作  

【这里对应博客一的步骤一到步骤五,并作出了一些简化(直接初始化粒子到目标区域,省去了一些步骤)】

(3.1)将选取的目标区域转换到hsv空间下

(3.2)计算目标区域的hsv直方图,只计算h和s两个通道即可

(3.3)归一化上面计算的直方图到(0,1)

(3.4)初始化粒子

涉及函数如下:

/*************************粒子初始化******************************************/
void particle_init(particle* particles,int _num_particle,MatND hist)
{
	for (int i = 0; i<_num_particle; i++)
	{
		//所有粒子初始化到框中的目标中心
		particles[i].x = roiRect.x + 0.5 * roiRect.width;
		particles[i].y = roiRect.y + 0.5 * roiRect.height;
		particles[i].xPre = particles[i].x;
		particles[i].yPre = particles[i].y;
		particles[i].xOri = particles[i].x;
		particles[i].yOri = particles[i].y;
		//pParticles->rect = roiRect;
		particles[i].width = roiRect.width;
		particles[i].height = roiRect.height;
		particles[i].scale = 1.0;
		particles[i].scalePre = 1.0;
		particles[i].hist = hist;
		//权重全部为0?
		particles[i].weight = 0;
	}
}
/******************************************************************************/

 第三步代码如下:

if (getTargetFlag == true){
			//目标区域转换到hsv空间
			cvtColor(roiImage, hsv_roiImage, COLOR_BGR2HSV);
			//计算目标区域的直方图
			calcHist(&hsv_roiImage, 1, channels, Mat(), hist, 2, histSize, ranges);
			normalize(hist, hist,0,1,NORM_MINMAX,-1,Mat());	// 归一化L2
			//粒子初始化
			particle_init(particles, num_particles, hist);
}

四、选取目标完成&&初始化完成,进入循环:【进入t时刻】

  • 读取新的一帧frame

4.1、粒子状态生成博客一的第六、七步)&&重要性采样博客一的第八步)

  • 由上一时刻粒子群的状态和权重,利用gsl库生成高斯分布随机数,进而生成出新的粒子群的位置和状态(函数transition())
  • 对每个粒子进行操作:
  • (1)以粒子为中心,以粒子结构体中的.width和.height以及缩放因子scale,作为粒子所在区域的范围,在frame中截取该范围,转换到hsv空间下
  • (2)计算上面截取的hsv区域,计算其直方图,并归一化到(0,1)
  • (3)这个新计算的直方图与第三步初始化时计算的目标直方图进行比较(cv::compareHist())【计算BHATTACHARYYA距离】,将结果作为该粒子的权重

涉及函数如下: 

/************************粒子状态转移(新位置生成预测)***********************/
//相关定义
/* standard deviations for gaussian sampling in transition model */
#define TRANS_X_STD 1.0
#define TRANS_Y_STD 0.5
#define TRANS_S_STD 0.001
/* autoregressive dynamics parameters for transition model */
#define A1  2.0//2.0
#define A2  -1.0//-1.0
#define B0  1.0000
particle transition(particle p, int w, int h, gsl_rng* rng)
{
	//double rng_nu_x = rng.uniform(0., 1.);
	//double rng_nu_y = rng.uniform(0., 0.5);
	float x, y, s;
	particle pn;

	/* sample new state using second-order autoregressive dynamics */
	x = A1 * (p.x - p.xOri) + A2 * (p.xPre - p.xOri) +
		B0 * gsl_ran_gaussian(rng, TRANS_X_STD)/*rng.gaussian(TRANS_X_STD)*/ + p.xOri;  //计算该粒子下一时刻的x
	pn.x = MAX(0.0, MIN((float)w - 1.0, x));
	y = A1 * (p.y - p.yOri) + A2 * (p.yPre - p.yOri) +
		B0 * gsl_ran_gaussian(rng, TRANS_Y_STD)/*rng.gaussian(TRANS_Y_STD)*/ + p.yOri;
	pn.y = MAX(0.0, MIN((float)h - 1.0, y));
	s = A1 * (p.scale - 1.0) + A2 * (p.scalePre - 1.0) +
		B0 * gsl_ran_gaussian(rng, TRANS_S_STD)/*rng.gaussian(TRANS_S_STD)*/ + 1.0;
	pn.scale = MAX(0.1, s);
	pn.xPre = p.x;
	pn.yPre = p.y;
	pn.scalePre = p.scale;
	pn.xOri = p.xOri;
	pn.yOri = p.yOri;
	pn.width = p.width;
	pn.height = p.height;
	//pn.hist = p.hist;
	pn.weight = 0;

	return pn;
}

对每个粒子的操作如下: 

			//对每个粒子的操作:
			for (j = 0; j < num_particles; j++){
				//rng = rng.next();
				//这里利用高斯分布的随机数来生成每个粒子下一次的位置以及范围
				particles[j] = transition(particles[j], frame.cols, frame.rows, rng);
				s = particles[j].scale;
				
				//根据新生成的粒子信息截取对应frame上的区域
				Rect imgParticleRect = Rect(std::max(0, std::min(cvRound(particles[j].x - 0.5*particles[j].width), cvRound(frame.cols - particles[j].width*s))),
					std::max(0, std::min(cvRound(particles[j].y - 0.5*particles[j].height), cvRound(frame.rows - particles[j].height*s))),
					cvRound(particles[j].width*s),
					cvRound(particles[j].height*s));

				Mat imgParticle = current_frame(imgParticleRect).clone();
				//上述区域转换到hsv空间
				cvtColor(imgParticle, imgParticle, CV_BGR2HSV);
				//计算区域的直方图
				calcHist(&imgParticle, 1, channels, Mat(), particles[j].hist, 2, histSize, ranges);
				//直方图归一化到(0,1)
				normalize(particles[j].hist, particles[j].hist, 0, 1, NORM_MINMAX, -1, Mat());	// 归一化L2
				//画出蓝色的粒子框
				rectangle(frame, imgParticleRect, Scalar(255, 0, 0), 1, 8);
				imshow("particle", imgParticle);
				//比较目标的直方图和上述计算的区域直方图,更新particle权重
				particles[j].weight = exp(-100 * (compareHist(hist, particles[j].hist, CV_COMP_BHATTACHARYYA))); //CV_COMP_CORREL
				
				int jj = 0;
			}

4.2 归一化权重

/*************************粒子权重归一化****************************/
void normalize_weights(particle* particles, int n)
{
	float sum = 0;
	int i;

	for (i = 0; i < n; i++)
		sum += particles[i].weight;
	for (i = 0; i < n; i++)
		particles[i].weight /= sum;
}
			//归一化权重 
			normalize_weights(particles, num_particles);

4.3 重采样博客一的第九步) 【原理参见:白巧克力亦唯心的总结:Particle Filter Tutorial 粒子滤波:从推导到应用(三)

  • (1)将粒子按权重从高到低排序
  • (2)重采样(函数resample())

涉及函数如下: 

/*************************粒子重采样********************************/
void resample(particle* particles,particle* new_particles,int num_particles)
{
	//计算每个粒子的概率累计和
	double sum[NUM_PARTICLE], temp_sum = 0;
	int k = 0;
	for (int j = num_particles - 1; j >= 0; j--){
		temp_sum += particles[j].weight;
		sum[j] = temp_sum;
	}
	//为每个粒子生成一个均匀分布【0,1】的随机数
	RNG sum_rng(time(NULL));
	double Ran[NUM_PARTICLE];
	for (int j = 0; j < num_particles; j++){
		sum_rng = sum_rng.next();
		Ran[j] = sum_rng.uniform(0., 1.);
	}
	//在粒子概率累积和数组中找到最小的大于给定随机数的索引,复制该索引的粒子一次到新的粒子数组中 【从权重高的粒子开始】
	for (int j = 0; j <num_particles; j++){
		int copy_index = get_min_index(sum, num_particles, Ran[j]);
		new_particles[k++] = particles[copy_index];
		if (k == num_particles)
			break;
	}
	//如果上面的操作完成,新粒子数组的数量仍少于原给定粒子数量,则复制权重最高的粒子,直到粒子数相等
	while (k < num_particles)
	{
		new_particles[k++] = particles[0]; //复制权值最高的粒子
	}
	//以新粒子数组覆盖久的粒子数组
	for (int i = 0; i<num_particles; i++)
	{
		particles[i] = new_particles[i];  //复制新粒子到particles
	}
}

操作步骤如下: 

			int np, k = 0;
			//将粒子按权重从高到低排序
			qsort(particles, num_particles, sizeof(particle), &particle_cmp);
			//重采样
			resample(particles, new_particles, num_particles);

 4.4 求期望,获取跟踪位置  【这里笔者偷了个懒,直接以权重最高的粒子作为目标了,正统做法应当是按粒子权重求期望】

  • (1)将粒子按权重从高到低排序
  • (2)提取目标位置
			//排序
			qsort(particles, num_particles, sizeof(particle), &particle_cmp);			
			//这里直接取权重最高的作为目标了,标准做法应该是按加权平均来计算目标位置
			s = particles[0].scale;
			Rect rectTrackingTemp = Rect(std::max(0, std::min(cvRound(particles[0].x - 0.5*particles[0].width), cvRound(frame.cols - particles[0].width*s))),
				std::max(0, std::min(cvRound(particles[0].y - 0.5*particles[0].height), cvRound(frame.rows - particles[0].height*s))),
				cvRound(particles[0].width*s),
				cvRound(particles[0].height*s));
			rectangle(frame, rectTrackingTemp, Scalar(0, 0, 255), 1, 8, 0);

完整代码:

依个人来讲,初学者大可不必看上面的代码,直接运行一次代码再说。在看代码的过程中有不明白的,再慢慢翻看。

这里就直接给出可运行的代码了,基于【vs2013+opencv2.49+gsl1.8】

如果你觉得值:可以从csdn下载,给小弟一个赚积分的机会

(1)csdn渠道:https://download.csdn.net/download/dieju8330/10858046

如果你没有csdn积分,没关系,

(2)百度wanpan:链接:https://pan.baidu.com/s/1VR_UYAqKk0fBTwZB_8ehPw   提取码:6xvc 

效果图如下: 

 

猜你喜欢

转载自blog.csdn.net/dieju8330/article/details/85061448