IMM-UKF代码阅读

https://github.com/LetusRoll/IMM-UKF-JPDA-tracking/blob/main/src/JPDA_UKF_Tracking/src/UKF/UKF.h

源代码如上,结合了交互多模型和无迹卡尔曼滤波方法,应该是个优秀的轮子,为了整合进自己的项目,特此注释一些代码笔记如下,侵删。

以上为IMM的模型协方差混合方程,产生对上一时刻模型j状态混合估计均值和协方差,导入各个子UKF滤波模块。

以CTRV模型为例,它在代码中的变量为X_ctrv,P_ctrv,至此输入UKF。

  1. X = X_ctrv;
  2. P = P_ctrv;   
  3. X_sig.col(0) = X;

Eigen::MatrixXd P_sqrt = P.llt().matrixL();##似乎是计算矩阵的Cholesky分解,用完这里之后P就没用了

  1.     for (int i = 1; i < state_num_ + 1; ++i)
  2.     {
  3.         X_sig.col(i) = X + sqrt(state_num_ + lambda) * P_sqrt.col(i - 1);
  4.         X_sig.col(i)[3] = NormalizeAngle(X_sig.col(i)[3]);
  5.     }

状态预测函数PredictMotion(const double dt, int mode)中,X_sig即无迹变换中k-1时刻状态的sigma采样点。k时刻的预测状态均值在CTRV(X_sig)中进行了计算,X_ctrv发生更新,已经变为状态sigma采样点在k时刻的预测值的均值,与UKF方法原公式中描述的相符。但是CTRV(X_sig)函数中似乎未对X_sig本身进行更新,因为一直没有出现在等式左边,这又与UKF精神不符。

  1. void UKF::CTRV(const Eigen::MatrixXd &X_sig, const double dt)
  2. {
  3.     for (int i = 0; i < X_sig.cols(); ++i)
  4.     {
  5.         //防止分母为0
  6.         if (fabs(X_sig(4, i)) > 0.001)
  7.         {
  8.             X_predict_sig_ctrv(0, i) = X_sig(0, i) + X_sig(2, i) / X_sig(4, i) * (sin(dt * X_sig(4, i) + X_sig(3, i)) - sin(X_sig(3, i)));  //X
  9.             X_predict_sig_ctrv(1, i) = X_sig(1, i) + X_sig(2, i) / X_sig(4, i) * (-cos(dt * X_sig(4, i) + X_sig(3, i)) + cos(X_sig(3, i))); //Y
  10.         }
  11.         else
  12.         {
  13.             X_predict_sig_ctrv(0, i) = X_sig(0, i) + X_sig(2, i) * dt * sin(X_sig(3, i)); //x
  14.             X_predict_sig_ctrv(1, i) = X_sig(1, i) + X_sig(2, i) * dt * cos(X_sig(3, i)); //y
  15.         }
  16.         X_predict_sig_ctrv(2, i) = X_sig(2, i);                    //v
  17.         X_predict_sig_ctrv(3, i) = X_sig(3, i) + dt * X_sig(4, i); //yaw
  18.         X_predict_sig_ctrv(4, i) = X_sig(4, i);                    //dyaw
  19.         X_predict_sig_ctrv(3, i) = NormalizeAngle(X_predict_sig_ctrv(3, i));
  20.         X_ctrv += w_s(i) * X_predict_sig_ctrv.col(i);
  21.     }
  22.     X_ctrv(3) = NormalizeAngle(X_ctrv(3));
  23. }

(CTRV函数传入了一个X_sig引用,结果却对另外的变量进行了更新,挺神奇的写法)

以下为P_ctrv的更新。

  1. CTRV(X_sig, dt);##对k-1时刻X_sig采样点,根据模型前进一步推理得到k时刻的预测状态
  2. P_ctrv.fill(0);#本来是用来存储模型的初始混合协方差的,代表上一时刻k-1到当前时刻k的转移,之前已经用过它,这里就将它清空了,用来存储k时刻sigma点的预测方差
  3. Eigen::VectorXd diff = X_sig.col(i) - X_ctrv;##这里减的仍是未经状态预测的X_ctrv
  4.             diff(3) = NormalizeAngle(diff(3));
  5.             P_ctrv += w_c(i) * diff * diff.transpose();

(正确的更新应该是对采样点预测之后,再进行加权均值处理,如下图)

预测测量函数void UKF::PredictMeasurement(int mode)

对k时刻预测状态进行sigma采样,得到X_predict_meas_sig,不过这次没有对角度进行正则化,可能考虑到后续的观测方程也没用到角度,猜测是用了lidar的观测模型,这里用到的P_ctrv已经更新为k时刻预测sigma点拟合的方差了

  1. if (mode == Mode::CTRV)
  2.     {
  3.         X = X_ctrv;
  4.         P = P_ctrv;
  5.     }
  6. X_predict_meas_sig.col(0) = X;
  7.     X_predict_meas_sig.col(0) = X;
  8.     Eigen::MatrixXd P_sqrt = P.llt().matrixL();
  9.     for (int i = 1; i < state_num_ + 1; ++i)
  10.     {
  11.         X_predict_meas_sig.col(i) = X + sqrt(state_num_ + lambda) * P_sqrt.col(i);
  12.     }
  13.     for (int i = state_num_ + 1; i < 2 * state_num_ + 1; ++i)
  14.     {
  15.         X_predict_meas_sig.col(i) = X - sqrt(state_num_ + lambda) * P_sqrt.col(i - state_num_ - 1);
  16.     }

直接赋值给了Z_sig,应该是代表对当前k时刻预测状态的预测状态观测,只取了x,y,可能是采用了Lidar的线性观测模型。(反思:既然这一步是是线性的,为何还要用无迹变换呢)

  1.     for (int i = 0; i < 2 * state_num_ + 1; ++i)
  2.     {
  3.         Z_sig(0, i) = X_predict_meas_sig(0, i); //x
  4.         Z_sig(1, i) = X_predict_meas_sig(1, i); //y
  5.     }
  6.         Z_sigma_ctrv = Z_sig;
  7.         for (int i = 0; i < 2 * state_num_ + 1; ++i)
  8.         {
  9.             Z_predict_ctrv += w_s(i) * Z_sigma_ctrv.col(i);
  10.         }
  11.         S_ctrv.fill(0);
  12.         for (int i = 0; i < 2 * state_num_ + 1; ++i)
  13.         {
  14.             Eigen::VectorXd diff = Z_sigma_ctrv.col(i) - Z_predict_ctrv;
  15.             S_ctrv += w_c(i) * diff * diff.transpose();
  16.         }
  17.         S_ctrv += R_ctrv;

更新了预测观测的均值Z_predict_ctrv和方差(似乎见到有种说法叫“新息”)S_ctrv

以上就是UKF中比较关键的观测和预测了,主要是明白了IMM到UKF的接口是X_ctrv和P_ctrv;

-----------------------------------------------  ----(手动分割线)-------------------------------------------------------

进入IMM环节的学习,首先经过考证,有理由认为该代码的交互矩阵计算出错

void UKF::MixProbability()
{
    Eigen::VectorXd sum = Eigen::VectorXd(3);

    sum << initial_mode_trans_prob(0, 0) * mode_prob(0) + initial_mode_trans_prob(0, 1) * mode_prob(0) + initial_mode_trans_prob(0, 2) * mode_prob(0),
        initial_mode_trans_prob(1, 0) * mode_prob(1) + initial_mode_trans_prob(1, 1) * mode_prob(1) + initial_mode_trans_prob(1, 2) * mode_prob(1),
        initial_mode_trans_prob(2, 0) * mode_prob(2) + initial_mode_trans_prob(2, 1) * mode_prob(2) + initial_mode_trans_prob(2, 2) * mode_prob(2);

    for (int i = 0; i < mode_trans_prob.rows(); ++i)
    {
        for (int j = 0; j < mode_trans_prob.cols(); ++j)
        {

            mode_trans_prob(i, j) = initial_mode_trans_prob(i, j) * mode_prob(i) / sum(i);
        }
    }
}

 此处sum的计算出错,sum={Cj}其中C_{j}=\sum_{i=0 }^{r}p_{ij}{u_{i}(k-1)}即对当前模型j,遍历所有的模型i到当前模型j的可能性。而源代码中的写法,显然变成了C_{i}=\sum_{j=0 }^{r}p_{ij}{u_{i}(k-1)}即对当前模型i,遍历当前模型i到其他所有模型j的可能性。读者可以仔细理解这两句话的区别,前者是收,后者是放。前者可以表示当前模型的先验概率,而后者不知道表示什么意思。


源代码写法会导致sum的中每一项都可以提出一个公因数mode_prob(j),从而导致mode_trans_prob(i,j)=initial_mode_trans_prob*mode_prob(i)/sum(i)的更新中,实际上会分子分母同时约掉一个mode_prob(i),导致mode_trans_prob(i,j)=initial_mode_trans_prob*mode_prob(i)/\summode_prob(i),从而失去更新意义,变成常数项。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值