[Lie group Lie algebra] [manif] 2D robot positioning based on fixed beacons (Error State Kalman Filter)…

demo demo

27692a194a4028738a3317ee7a718792.png

operation result

We consider a robot on a plane surrounded by a small number of punctual landmarks or beacons.

The robot receives control actions in the form of axial and angular velocities and is able to measure the position of the beacon relative to its own reference frame.

The robot pose X is in SE(2), the beacon position b_k is in R^2,

| cos th -sin th x |

* X = | sin th cos th y | //Position and direction

| 0 0 1 |

* b_k = (bx_k, by_k) // lmk coordinates in the world coordinate system

The control signal u is the spinor in se(2), consisting of the longitudinal velocity v and the angular velocity w, without a transverse velocity component, integrated over the sampling time dt.

* u = (v*dt, 0, w*dt)

Control is corrupted by additive Gaussian noise u_noise with covariance

* Q = diagonal(sigma_v^2, sigma_s^2, sigma_w^2).

This noise accounts for possible lateral slip u_s through non-zero values of sigma_s,

*When control u arrives, the robot pose is updated to X <-- X * Exp(u) = X + u.

Landmark measurements are of range and bearing type, but for simplicity they are in Cartesian form.

Their noise n is a zero-mean Gaussian distribution and is specified by the covariance matrix R.

We note that the rigid motion action y = h(X,b) = X^-1 * b

* y_k = (brx_k, bry_k) // lmk coordinates in the robot coordinate system

We consider a beacon b_k located at a known location.

We define the pose to be estimated as X in SE(2).

The estimation error dx and its covariance P are expressed in tangent space at X.

*All these variables are again summarized below

* * X: robot pose, SE(2)

* u: robot control quantity, (v*dt; 0; w*dt) in se(2)

* Q: Control perturbation covariance

* b_k: k-th landmark position, R^2

* y: Cartesian landmark measurement in robot coordinate system, R^2

* R: covariance of measurement noise

* The motion and measurement models areThe motion and measurement models are

* X_(t + 1) = f(X_t, u) = X_t * Exp ( w ) //Equation of motion

* y_k = h(X, b_k) = X^-1 * b_k //Measurement equation

The algorithm below first consists of a simulator to produce measurements and then uses these measurements to estimate the state using a Lie-based error state Kalman filter. Finally, printing the simulated and estimated states as well as the unfiltered state (i.e. without Kalman correction) allows to assess the quality of the estimates.

#include "manif/SE2.h"


#include <vector>


#include <iostream>
#include <iomanip>


using std::cout;
using std::endl;


using namespace Eigen;


typedef Array<double, 2, 1> Array2d;
typedef Array<double, 3, 1> Array3d;


int main()
{
    std::srand((unsigned int) time(0));


    // START CONFIGURATION
    //
    //
    const int NUMBER_OF_LMKS_TO_MEASURE = 3;


    // Define the robot pose element and its covariance
    manif::SE2d X, X_simulation, X_unfiltered;
    Matrix3dP;


    X_simulation.setIdentity();
    X.setIdentity();
    X_unfiltered.setIdentity();
    P.setZero();


    // Define a control vector and its noise and covariance
    manif::SE2Tangentd u_simu, u_est, u_unfilt;
    Vector3d u_nom, u_noisy, u_noise;
    Array3d u_sigmas;
    Matrix3dU;


    u_nom << 0.1, 0.0, 0.05;
    u_sigmas << 0.1, 0.1, 0.1;
    U = (u_sigmas * u_sigmas).matrix().asDiagonal();


    // Declare the Jacobians of the motion wrt robot and control
    manif::SE2d::Jacobian J_x, J_u;


    // Define three landmarks in R^2
    Eigen::Vector2d b0, b1, b2, b;
    b0 << 2.0, 0.0;
    b1 << 2.0, 1.0;
    b2 << 2.0, -1.0;
    std::vector<Eigen::Vector2d> landmarks;
    landmarks.push_back(b0);
    landmarks.push_back(b1);
    landmarks.push_back(b2);


    // Define the beacon's measurements
    Vector2d y, y_noise;
    Array2d y_sigmas;
    Matrix2d R;
    std::vector<Vector2d> measurements(landmarks.size());


    y_sigmas << 0.01, 0.01;
    R = (y_sigmas * y_sigmas).matrix().asDiagonal();


    // Declare the Jacobian of the measurements wrt the robot pose
    Matrix<double, 2, 3> H; // H = J_e_x


    // Declare some temporaries
    Vector2d e, z; // expectation, innovation
    Matrix2d E, Z; // covariances of the above
    Matrix<double, 3, 2> K; // Kalman gain
    manif::SE2Tangentd dx; // optimal update step, or error-state
    manif::SE2d::Jacobian J_xi_x; // Jacobian is typedef Matrix
    Matrix<double, 2, 3> J_e_xi; // Jacobian


    //
    //
    // CONFIGURATION DONE






    //DEBUG
    cout << std::fixed << std::setprecision(3) << std::showpos << endl;
    cout << "X STATE : X Y THETA" << endl;
    cout << "----------------------------------" << endl;
    cout << "X initial : " << X_simulation.log().coeffs().transpose() << endl;
    cout << "----------------------------------" << endl;
    //END DEBUG








    // START TEMPORAL LOOP
    //
    //


    // Make 10 steps. Measure up to three landmarks each time.
    for (int t = 0; t < 10; t + + )
    {
         I. Simulation ############################################# ################################


        /// simulate noise
        u_noise = u_sigmas * Array3d::Random(); // control noise
        u_noisy = u_nom + u_noise; // noisy control


        u_simu = u_nom;
        u_est = u_noisy;
        u_unfilt = u_noisy;


        /// first we move - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        X_simulation = X_simulation + u_simu; // overloaded X.rplus(u) = X * exp(u)


        /// then we measure all landmarks - - - - - - - - - - - - - - - - - - - - - -
        for (std::size_t i = 0; i < landmarks.size(); i + + )
        {
            b = landmarks[i]; // lmk coordinates in world frame


            /// simulate noise
            y_noise = y_sigmas * Array2d::Random(); // measurement noise


            y = X_simulation.inverse().act(b); // landmark measurement, before adding noise
            y = y + y_noise; // landmark measurement, noise
            measurements[i] = y; // store for the estimator just below
        }








         II. Estimation ############################################## ################################


        /// First we move - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -


        X = X.plus(u_est, J_x, J_u); // X * exp(u), with Jacobians


        P = J_x * P * J_x.transpose() + J_u * U * J_u.transpose();




        /// Then we correct using the measurements of each lmk - - - - - - - - -
        for (int i = 0; i < NUMBER_OF_LMKS_TO_MEASURE; i + + )
        {
            // landmark
            b = landmarks[i]; // lmk coordinates in world frame


           // measurement
            y = measurements[i]; // lmk measurement, noisy


            // expectation
            e = X.inverse(J_xi_x).act(b, J_e_xi); // note: e = R.tr * ( b - t ), for X = (R,t).
            H = J_e_xi * J_xi_x; // note: H = J_e_x = J_e_xi * J_xi_x
            E = H * P * H.transpose();


            // innovation
            z = y - e;
            Z = E + R;


            // Kalman gain
            K = P * H.transpose() * Z.inverse(); // K = P * H.tr * ( H * P * H.tr + R).inv


            //Correction step
            dx = K * z; // dx is in the tangent space at X


            //Update
            X = X + dx; // overloaded X.rplus(dx) = X * exp(dx)
            P = P - K * Z * K.transpose();
        }








         III. Unfiltered ############################################## ###############################


        // move also an unfiltered version for comparison purposes
        X_unfiltered = X_unfiltered + u_unfilt;








         IV. Results ############################################## ###############################


        //DEBUG
        cout << "X simulated : " << X_simulation.log().coeffs().transpose() << endl;
        cout << "X estimated : " << X.log().coeffs().transpose() << endl;
        cout << "X unfilterd : " << X_unfiltered.log().coeffs().transpose() << endl;
        cout << "----------------------------------" << endl;
        //END DEBUG


    }


    //
    //
    // END OF TEMPORAL LOOP. DONE.


    return 0;
}

56b00b1fc4cf4e8cfa2aee0d3bed5357.png

The End