放在最前:致谢taotao1233、yangyangcv、yang_xian521 以及先驱 Rob Hess 所开源的代码和思路。
本篇:基本为工程翻译,以及对上面版本的一些修正,使用的是opencv2.49,以Mat类型代替了容易导致内存泄漏的Iplimage*类型,总的来说,就是从旧版的opencv数据结构翻译到opencv2.49下。yang_xian521 也做过类似工作,但是不知道是否我操作有误,运行其程序,处理速度很慢,而且跟踪效果不好,既然如此,何不自己动手,丰衣足食?
程序的流程可以大致参考:粒子滤波初探(一)利用粒子滤波实现视频目标跟踪的大致流程 ,会有一点点不同和简化
直接获取完整代码见这里
既然是粒子滤波,首先我们构造粒子的结构体:
/*********************结构体************************/
// 粒子结构体
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时刻】
4.1、粒子状态生成(博客一的第六、七步)&&重要性采样(博客一的第八步)
涉及函数如下:
/************************粒子状态转移(新位置生成预测)***********************/
//相关定义
/* 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 粒子滤波:从推导到应用(三)】
涉及函数如下:
/*************************粒子重采样********************************/
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
操作步骤如下:
int np, k = 0;
//将粒子按权重从高到低排序
qsort(particles, num_particles, sizeof(particle), &particle_cmp);
//重采样
resample(particles, new_particles, num_particles);
4.4 求期望,获取跟踪位置 【这里笔者偷了个懒,直接以权重最高的粒子作为目标了,正统做法应当是按粒子权重求期望】
//排序
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