文档下载链接:https://download.csdn.net/download/OEMT_301/12069538
https://download.csdn.net/download/OEMT_301/12104738

粒子滤波算法是一种非线性的滤波方法。

其大致思路如下(这里以图像目标(人)跟踪为例):
1、 首先在整个图像中随机初始化一些粒子点,并对每个粒子点分配权值
2、 在视频中框出待跟踪目标
3、 更新权值,增加靠近框出的目标粒子权值
4、 根据状态转移矩阵和测量数据,对粒子权重,对粒子进行重采样

粒子滤波示过程示意图
初始化图像粒子点和权重
在这里插入图片描述
框出待跟踪目标
在这里插入图片描述
更新权重,其中权重较小的直接舍弃,权值较大的粒子点进行复制,权值越大复制的粒子点越多。
在这里插入图片描述
经过几次权值更新,可以使得大多数粒子点处于目标框附近
在这里插入图片描述
根据状态转移矩阵获取新粒子点坐标(箭头相当于状态转移矩阵)
A、经过目标移动后,理想状态为如下图
在这里插入图片描述
B、但实际上存在一些噪声,预测结果与实际结果有偏差
在这里插入图片描述
因此继续重采样,利用测量结果对其进行重采样,便可得到较为准确的跟踪效果
在这里插入图片描述
以上为粒子滤波过程示意图

重采样
在这里插入图片描述
MATLAB重采样代码示意

function w_new=resample_particles(w)
w_new=w;
Neff=1/sum(w.*w);
N=length(w);
if Neff<75 %75为预先设定阈值
    for i = 1 : N
        u = rand; 
        qtempsum = 0;
        for j = 1 : N
            qtempsum = qtempsum + w(j);
            if qtempsum >= u
                w_new(i)=w(j);
                break;
            end
        end
    end
end

一维SIR粒子滤波实例

%% initialize the variables
x = 0.1; % initial actual state
x_N = 1; % 系统过程噪声的协方差  (由于是一维的,这里就是方差)
x_R = 1; % 测量的协方差
T = 75;  % 共进行75次
N = 100; % 粒子数,越大效果越好,计算量也越大

%initilize our initial, prior particle distribution as a gaussian around
%the true initial value

V = 2; %初始分布的方差
x_P = []; % 粒子
% 用一个高斯分布随机的产生初始的粒子
for i = 1:N
    x_P(i) = x + sqrt(V) * randn;
end

z_out = [x^2 / 20 + sqrt(x_R) * randn];  %实际测量值
x_out = [x];  %the actual output vector for measurement values.
x_est = [x]; % time by time output of the particle filters estimate
x_est_out = [x_est]; % the vector of particle filter estimates.

for t = 1:T
    x = 0.5*x + 25*x/(1 + x^2) + 8*cos(1.2*(t-1)) +  sqrt(x_N)*randn;
    z = x^2/20 + sqrt(x_R)*randn;
    for i = 1:N
        %从先验p(x(k)|x(k-1))中采样
        x_P_update(i) = 0.5*x_P(i) + 25*x_P(i)/(1 + x_P(i)^2) + 8*cos(1.2*(t-1)) + sqrt(x_N)*randn;
        %计算采样粒子的值,为后面根据似然去计算权重做铺垫
        z_update(i) = x_P_update(i)^2/20;
        %对每个粒子计算其权重,这里假设量测噪声是高斯分布。所以 w = p(y|x)对应下面的计算公式
        P_w(i) = (1/sqrt(2*pi*x_R)) * exp(-(z - z_update(i))^2/(2*x_R));
    end
    % 归一化.
    P_w = P_w./sum(P_w);
  
    %% Resampling
    %这里没有用博客里之前说的histc函数,不过目的和效果是一样的
    for i = 1 : N
        x_P(i) = x_P_update(find(rand <= cumsum(P_w),1));   % 粒子权重大的将多得到后代
    end                                                     % find( ,1) 返回第一个 符合前面条件的数的 下标
    
    %状态估计,重采样以后,每个粒子的权重都变成了1/N
    x_est = mean(x_P);
    
    % Save data in arrays for later plotting
    x_out = [x_out x];
    z_out = [z_out z];
    x_est_out = [x_est_out x_est];
    
end

t = 0:T;
figure(1);
clf
plot(t, x_out, '.-b', t, x_est_out, '-.r','linewidth',3);
set(gca,'FontSize',12); set(gcf,'Color','White');
xlabel('time step'); ylabel('flight position');
legend('True flight position', 'Particle filter estimate');

在这里插入图片描述

图像粒子跟踪实例
main.m

% Parameters
F_update = [1 0 1 0; 0 1 0 1; 0 0 1 0; 0 0 0 1];    % 状态转移矩阵

Npop_particles = 4000;

Xstd_rgb = 50;  % 方差
Xstd_pos = 25;  % 位置倍率
Xstd_vec = 5;   % 速度倍率

Xrgb_trgt = [255; 0; 0];    % 检测颜色

% Loading Movie
vr = VideoReader('Person.wmv');

Npix_resolution = [vr.Width vr.Height];
Nfrm_movie = floor(vr.Duration * vr.FrameRate);

% Object Tracking by Particle Filter

X = create_particles(Npix_resolution, Npop_particles); % 粒子初始化,在画面中产生均匀分布的随机粒子

for k = 1:Nfrm_movie
    
    % Getting Image
    Y_k = read(vr, k);
    
    % Forecasting
    %通过状态模型预测  这里采用的是在上一时刻基础上叠加噪声
    X = update_particles(F_update, Xstd_pos, Xstd_vec, X); 
    
    % Calculating Log Likelihood
    L = calc_log_likelihood(Xstd_rgb, Xrgb_trgt, X(1:2, :), Y_k);
    
    % Resampling
    X = resample_particles(X, L);

    % Showing Image
    show_particles(X, Y_k); 
%    show_state_estimated(X, Y_k);

end

create_particles.m

function X = create_particles(Npix_resolution, Npop_particles)

X1 = randi(Npix_resolution(2), 1, Npop_particles);  % 产生一个 1 x Npop_particles 的行向量,各元素值为 1:Npix_resolution(2)之间的产生的均匀分布的随机整数 
X2 = randi(Npix_resolution(1), 1, Npop_particles);
X3 = zeros(2, Npop_particles);

X = [X1; X2; X3];

update_particles.m

function X = update_particles(F_update, Xstd_pos, Xstd_vec, X)
% X 所有粒子组成的矩阵  
% X(1:2, :) 各粒子在画面中的位置
N = size(X, 2);

X = F_update * X;       % 状态转移矩阵

X(1:2,:) = X(1:2,:) + Xstd_pos * randn(2, N);
X(3:4,:) = X(3:4,:) + Xstd_vec * randn(2, N);

calc_log_likelihood.m

function L = calc_log_likelihood(Xstd_rgb, Xrgb_trgt, X, Y) %#codegen

Npix_h = size(Y, 1);
Npix_w = size(Y, 2);

N = size(X,2);

L = zeros(1,N);
Y = permute(Y, [3 1 2]);  %重新安排矩阵

% 高斯函数
A = -log(sqrt(2 * pi) * Xstd_rgb);
B = - 0.5 / (Xstd_rgb.^2);

X = round(X);

for k = 1:N
    % m,n 粒子k在画面中的位置
    m = X(1,k);
    n = X(2,k);
    
    % 越界判断
    I = (m >= 1 & m <= Npix_h);
    J = (n >= 1 & n <= Npix_w);
    
    if I && J
        
        C = double(Y(:, m, n));
        
        D = C - Xrgb_trgt;
        
        D2 = D' * D;   % 欧氏距离
        
        L(k) =  A + B * D2;  %高斯似然   D2 越小  L越大  注意B为负数
    else
        
        L(k) = -Inf;
    end
end

resample_particles.m

function X = resample_particles(X, L_log)

% Calculating Cumulative Distribution

L = exp(L_log - max(L_log));
Q = L / sum(L, 2);  % a = sum(L,2)  a中元素为各行向量累加值
R = cumsum(Q, 2);   % 权值累加

% Generating Random Numbers

N = size(X, 2);
T = rand(1, N); % 随机阈值

% Resampling
X_temp = zeros(size(X));
%这里没有用博客里之前说的histc函数,不过目的和效果是一样的
for i = 1 : N
    X_temp(:,i) = X(:,find(T(i) <= R,1));               % 粒子权重大的将多得到后代
end                                                     % find( ,1) 返回第一个 符合前面条件的数的 下标
X = X_temp;
% [~, I] = histc(T, R);
% X = X(:, I + 1);

在这里插入图片描述
C++代码:

#include <opencv2/opencv.hpp>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <ctime>
#include <fstream>
#include <iostream>
#include <assert.h>

using namespace cv;
using namespace std;

//****************************定义粒子数目**********************************//
#define PARTICLE_NUMBER 300
#define HIST_SIZE       16

//*******************************定义粒子结构体类型************************//
typedef struct particle//关于typedef struct和struct见下文补充
{
    int orix, oriy;         //原始粒子坐标
    int x, y;               //当前粒子的坐标
    double scale;           //当前粒子窗口的尺寸
    int prex, prey;         //上一帧粒子的坐标
    double prescale;        //上一帧粒子窗口的尺寸
    Rect rect;              //当前粒子矩形窗口
    double weight;          //当前粒子权值
}PARTICLE;

bool leftButtonDownFlag=false;  //左键单击后的标志位
bool leftButtonUpFlag=false;    //左键单击后松开的标志位
Point Point_s;                  //矩形框起点
Point Point_e;                  //矩形框鼠标左键弹起来的终点
Point processPoint;             //矩形框移动的终点
bool  tracking = false;

//****有关粒子窗口变化用到的相关变量****// 目标运行预测值
int A1 = 2;
int A2 = -1;
int B0 = 1;
double sigmax = 1.0;
double sigmay = 0.5;
double sigmas = 0.001;

double *m_hist, *hist;
int p_num = 0;

vector<PARTICLE> newParticle;//定义一个新的粒子数组

/****粒子权重值的降序排列****/
// 排序法则,其中:< 升序    >降序
bool comparison(PARTICLE p1,PARTICLE p2)
{
 return p1.weight > p2.weight ;
}

void onMouse( int event, int x, int y, int flags, void *param )
{
    if(event==CV_EVENT_LBUTTONDOWN)
    {
        tracking = false;
        leftButtonDownFlag = true; //标志位
        leftButtonUpFlag = false;
        processPoint=Point(x,y);  //设置左键按下点的矩形起点
        Point_s=processPoint;
    }
    else if(event == CV_EVENT_MOUSEMOVE && leftButtonDownFlag)
    {
        processPoint=Point(x,y);
    }
    else if(event==CV_EVENT_LBUTTONUP && leftButtonDownFlag)
    {
        leftButtonDownFlag=false;
        processPoint=Point(x,y);
        Point_e=processPoint;
        leftButtonUpFlag = true;
        tracking = true;
    }
}

void init_target(Mat mould, vector<PARTICLE> &particles, Rect &rect)
{
    int q_r, q_g, q_b, q_temp;
    particles.clear();

    //初始化权值矩阵和目标直方图
    for (int i=0;i<HIST_SIZE * HIST_SIZE * HIST_SIZE;i++)
    {
        m_hist[i] = 0.0;
    }
    //计算目标权值直方
    for (int i = 0;i < mould.rows; i++)
    {
        for (int j = 0;j < mould.cols; j++)
        {
            //rgb颜色空间量化为16*16*16 bins
            q_r = mould.at<Vec3b>(i, j)[2]/255/HIST_SIZE;
            q_g = mould.at<Vec3b>(i, j)[1]/255/HIST_SIZE;
            q_b = mould.at<Vec3b>(i, j)[0]/255/HIST_SIZE;
            q_temp = q_r * HIST_SIZE * HIST_SIZE + q_g * HIST_SIZE + q_b;
            m_hist[q_temp]++;          // 颜色权重直方图
        }
    }

    // 归一化
    double m_hist_sum = 0.0;
    for (int i=0;i<HIST_SIZE * HIST_SIZE * HIST_SIZE;i++)
    {
        m_hist_sum += pow(m_hist[i], 2);
    }
    m_hist_sum = sqrt(m_hist_sum);
    for (int i=0;i<HIST_SIZE * HIST_SIZE * HIST_SIZE;i++)
    {
        m_hist[i] = m_hist[i]/m_hist_sum;
    }

    // 目标粒子初始化
    PARTICLE pParticle;
    for (int k=0; k<PARTICLE_NUMBER; k++)               //对于每个粒子
    {
        pParticle.x = cvRound(rect.x + 0.5*rect.width);//当前粒子的x坐标
        pParticle.y = cvRound(rect.y + 0.5*rect.height);//当前粒子的y坐标

        //粒子的原始坐标为选定矩形框(即目标)的中心
        pParticle.orix = pParticle.x;
        pParticle.oriy = pParticle.y;
        //当前粒子窗口的尺寸
        pParticle.scale = 1;//初始化为1,然后后面粒子到搜索的时候才通过计算更新

        //更新上一帧粒子的坐标
        pParticle.prex = pParticle.x;
        pParticle.prey = pParticle.y;
        //上一帧粒子窗口的尺寸
        pParticle.prescale = 1;

        //当前粒子矩形窗口
        pParticle.rect = rect;

        //当前粒子权值
        pParticle.weight = 0;//权重初始为0

        particles.push_back(pParticle);
    }
}


void My_Tracking(Mat img, vector<PARTICLE> &particles)
{
    int xpre, ypre;
    double prescale, scale;
    int x, y;
    double sum = 0.0;

    RNG rng;                        //随机数产生器
    int q_r, q_g, q_b, q_temp;
    /*计算粒子区域的直方图*/
    //初始化目标直方图
    for (int i=0;i<HIST_SIZE * HIST_SIZE * HIST_SIZE;i++)
    {
        hist[i] = 0.0;
    }

    for (int k=0; k<PARTICLE_NUMBER; k++)
    {
        //当前粒子的坐标
        xpre = particles.at(k).x;
        ypre = particles.at(k).y;

        //当前粒子窗口的尺寸
        prescale = particles.at(k).scale;

        /*更新跟踪矩形框中心,即粒子中心*///使用二阶动态回归来自动更新粒子状态
        x = cvRound(A1*(particles.at(k).x - particles.at(k).orix) + A2*(particles.at(k).prex - particles.at(k).orix) +
            B0*rng.gaussian(sigmax) + particles.at(k).orix);
        particles.at(k).x = max(0, min(x, img.cols - 1));

        y = cvRound(A1*(particles.at(k).y - particles.at(k).oriy) + A2*(particles.at(k).prey - particles.at(k).oriy) +
            B0*rng.gaussian(sigmay) + particles.at(k).oriy);
        particles.at(k).y = max(0, min(y, img.rows - 1));

        scale = A1*(particles.at(k).scale - 1) + A2*(particles.at(k).prescale - 1) + B0*(rng.gaussian(sigmas)) + 1.0;
        particles.at(k).scale = max(1.0, min(scale, 3.0));//此处参数设置存疑

        particles.at(k).prex = xpre;
        particles.at(k).prey = ypre;
        particles.at(k).prescale = prescale;

        /*计算更新得到矩形框数据*/
        particles.at(k).rect.x = max(0, min(cvRound(particles.at(k).x - 0.5*particles.at(k).scale*particles.at(k).rect.width), img.cols));
        particles.at(k).rect.y = max(0, min(cvRound(particles.at(k).y - 0.5*particles.at(k).scale*particles.at(k).rect.height), img.rows));
        particles.at(k).rect.width = min(cvRound(particles.at(k).rect.width), img.cols - particles.at(k).rect.x);
        particles.at(k).rect.height = min(cvRound(particles.at(k).rect.height), img.rows - particles.at(k).rect.y);

        //计算粒子权值直方图
        for (int i = particles.at(k).rect.y; i < particles.at(k).rect.y + particles.at(k).rect.height; i++)
        {
            for (int j = particles.at(k).rect.x; j < particles.at(k).rect.x + particles.at(k).rect.width; j++)
            {
                //rgb颜色空间量化为16*16*16 bins
                q_r = img.at<Vec3b>(i, j)[2]/255/HIST_SIZE;
                q_g = img.at<Vec3b>(i, j)[1]/255/HIST_SIZE;
                q_b = img.at<Vec3b>(i, j)[0]/255/HIST_SIZE;
                q_temp = q_r * HIST_SIZE * HIST_SIZE + q_g * HIST_SIZE + q_b;
                hist[q_temp]++;                         // 颜色权重直方图
            }
        }

        // 直方图归一化
        double hist_sum = 0.0;
        double sim_sum = 0.0;
        for (int i=0;i<HIST_SIZE * HIST_SIZE * HIST_SIZE;i++)
        {
            hist_sum += pow(hist[i], 2);
        }
        hist_sum = sqrt(hist_sum);
        for (int i=0;i<HIST_SIZE * HIST_SIZE * HIST_SIZE;i++)
        {
            hist[i] = hist[i]/hist_sum;
            if(m_hist[i] > 0.0 && hist[i] > 0.0)
            {
                sim_sum += sqrt(m_hist[i]*hist[i]);         // 计算巴氏距离
            }
        }

        particles.at(k).weight =  sim_sum;
        /*粒子权重累加*/
        sum += particles.at(k).weight;
    }

    // 赋值每个粒子权重
    for (int k=0; k<PARTICLE_NUMBER; k++)
    {
        particles.at(k).weight /= sum;
    }

    sort(particles.begin(), particles.end(), comparison);           // 权重排序

    //*********************重采样,根据粒子权重重采样********************//
    int T = 0;                  // 阈值,只要T个粒子
    bool flag = false;          // 是否对每个粒子重新赋值跳出标志位
    newParticle.clear();

    for (int k = 0;k < PARTICLE_NUMBER;k++)
    {
        if(flag)            // 完成粒子赋值,跳出循环
        {
            break;
        }
        T = cvRound(particles.at(k).weight*PARTICLE_NUMBER);    //将权重较弱的粒子淘汰掉,保留权重在阈值以上的
        if(T > 0)
        {
            for (int i = 0;i < T;i++)           // 权重越大,该点赋值数越多
            {
                newParticle.push_back(particles.at(k));
                if (newParticle.size() >= PARTICLE_NUMBER)
                {
                    flag = true;
                    break;
                }
            }
        }
        else            // 没有可以满足条件的权值,跳出循环
        {
            break;
        }
    }

    if(!flag)           // 点未全部赋值,将剩下的用最大值进行赋值
    {
        while (newParticle.size() < PARTICLE_NUMBER)
        {
            newParticle.push_back(particles.at(0));//复制大的权值的样本填满空间
        }
    }

    // 将粒子点替换为更新后的粒子点
    for (int k = 0;k < PARTICLE_NUMBER;k++)
    {
        particles.at(k) = newParticle.at(k);

    }

    //***********计算最大权重目标的期望位置,采用权值最大的1/4个粒子数作为跟踪结果************//
    Rect rectTracking;              //初始化一个Rect作为跟踪的临时
    double weight_temp = 0.0;
    for (int k = 0; k<PARTICLE_NUMBER / 4; k++)
    {
        weight_temp += particles.at(k).weight;
    }
    for (int k = 0; k<PARTICLE_NUMBER / 4; k++)
    {
        particles.at(k).weight /= weight_temp;
    }

    // 更新检测框
    for (int k = 0; k<PARTICLE_NUMBER / 4; k++)
    {
        rectTracking.x += particles.at(k).rect.x*particles.at(k).weight;
        rectTracking.y += particles.at(k).rect.y*particles.at(k).weight;
        rectTracking.width += particles.at(k).rect.width*particles.at(k).weight;
        rectTracking.height += particles.at(k).rect.height*particles.at(k).weight;
    }

    rectangle(img, rectTracking, Scalar(0, 255, 0), 3, 8, 0);//显示跟踪结果,框出
}

int main()
{
    Mat img_mould, frame, mould;
    Rect rect;
    vector<PARTICLE> particles;                 // 粒子参数


    //打开摄像头或者特定视频
    VideoCapture cap;
    cap.open(0);//或cap.open("文件名")
    //读入视频是否为空
    if (!cap.isOpened())
    {
        return -1;
    }
    namedWindow("输出视频", 1);
    setMouseCallback("输出视频", onMouse, 0);//鼠标回调函数,响应鼠标以选择跟踪区域

    m_hist = (double *)malloc(sizeof(double)*HIST_SIZE * HIST_SIZE * HIST_SIZE);         // 目标直方图
    hist = (double *)malloc(sizeof(double)*HIST_SIZE * HIST_SIZE * HIST_SIZE);         // 目标直方图

    while(1)
    {
        cap >> frame;
        if (frame.empty())
        {
            return -1;
        }
        blur(frame, frame, Size(2, 2));//先对原图进行均值滤波处理

        if(tracking && leftButtonUpFlag)
        {
            leftButtonUpFlag = false;
            rect.x = Point_s.x;
            rect.y = Point_s.y;
            rect.width = Point_e.x - Point_s.x;
            rect.height = Point_e.y - Point_s.y;
            img_mould = frame.clone();
            mould = Mat(img_mould, rect);   //目标图像

            init_target(mould, particles, rect);
            p_num = 0;
        }
        if(leftButtonDownFlag)                                      // 绘制截取目标窗口
        {
            rect.x = Point_s.x;
            rect.y = Point_s.y;
            rect.width = processPoint.x - Point_s.x;
            rect.height = processPoint.y - Point_s.y;
            rectangle(frame, rect, Scalar(0, 255, 0), 3, 8, 0);
        }

        if(tracking)
        {
            My_Tracking(frame, particles);
            cout << p_num++ << endl;            // 输出检测帧数
        }

        imshow("输出视频", frame);
        waitKey(1);
    }


    return 0;
}

注:C++代码是直接调动摄像头,效率比较低,识别准确率也有待提高,以后有时间了再优化。

自我感觉自己理解可能有误,请谨慎参考

参考:
http://blog.csdn.net/heyijia0327/article/details/40899819
https://blog.csdn.net/u011624019/article/details/80559397

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐