Example of a simple non-linear use-case of the Unscented Kalman Filter (UKF).
The system we are interested in is an aircraft flying in the sky and observed by a radar station. Its velocity is not completely constant: a Gaussian noise is added to the velocity to simulate the effect of wind on the motion of the aircraft.
We consider the plan perpendicular to the ground and passing by both the radar station and the aircraft. The x-axis corresponds to the position on the ground and the y-axis to the altitude.
The state vector of the UKF corresponds to a constant velocity model and can be written as:
Some noise is added to the measurement vector to simulate a sensor which is not perfect.
The mean of several angles must be computed in the Unscented Transform. The definition we chose to use is the following:
As the Unscented Transform uses a weighted mean, the actual implementation of the weighted mean of several angles is the following:
Additionnally, the addition and subtraction of angles must be carefully done, as the result must stay in the interval or . We decided to use the interval .
#include <iostream>
#include <visp3/core/vpUKSigmaDrawerMerwe.h>
#include <visp3/core/vpUnscentedKalman.h>
#include <visp3/core/vpConfig.h>
#include <visp3/core/vpGaussRand.h>
#ifdef VISP_HAVE_DISPLAY
#include <visp3/gui/vpPlot.h>
#endif
#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11)
#ifdef ENABLE_VISP_NAMESPACE
#endif
namespace
{
{
point[0] = chi[1] * dt + chi[0];
point[1] = chi[1];
point[2] = chi[3] * dt + chi[2];
point[3] = chi[3];
return point;
}
double normalizeAngle(const double &angle)
{
double angleInMinPiPi = angleIn0to2pi;
if (angleInMinPiPi > M_PI) {
angleInMinPiPi -= 2. * M_PI;
}
return angleInMinPiPi;
}
vpColVector measurementMean(
const std::vector<vpColVector> &measurements,
const std::vector<double> &wm)
{
const unsigned int nbPoints = static_cast<unsigned int>(measurements.size());
const unsigned int sizeMeasurement = measurements[0].
size();
double sumCos(0.);
double sumSin(0.);
for (unsigned int i = 0; i < nbPoints; ++i) {
mean[0] += wm[i] * measurements[i][0];
sumCos += wm[i] * std::cos(measurements[i][1]);
sumSin += wm[i] * std::sin(measurements[i][1]);
}
mean[1] = std::atan2(sumSin, sumCos);
return mean;
}
{
res[1] = normalizeAngle(res[1]);
return res;
}
}
{
public:
vpRadarStation(
const double &x,
const double &y,
const double &range_std,
const double &elev_angle_std)
: m_x(x)
, m_y(y)
, m_rngRange(range_std, 0., 4224)
, m_rngElevAngle(elev_angle_std, 0., 2112)
{ }
{
double dx = chi[0] - m_x;
double dy = chi[2] - m_y;
meas[0] = std::sqrt(dx * dx + dy * dy);
meas[1] = std::atan2(dy, dx);
return meas;
}
{
double dx = pos[0] - m_x;
double dy = pos[1] - m_y;
double range = std::sqrt(dx * dx + dy * dy);
double elevAngle = std::atan2(dy, dx);
measurements[0] = range;
measurements[1] = elevAngle;
return measurements;
}
{
measurementsNoisy[0] += m_rngRange();
measurementsNoisy[1] += m_rngElevAngle();
return measurementsNoisy;
}
private:
double m_x;
double m_y;
};
{
public:
: m_pos(X0)
, m_vel(vel)
, m_rngVel(vel_std, 0.)
{
}
{
dx[0] += m_rngVel() * dt;
dx[1] += m_rngVel() * dt;
m_pos += dx;
return m_pos;
}
private:
};
int main(const int argc, const char *argv[])
{
bool opt_useDisplay = true;
for (int i = 1; i < argc; ++i) {
std::string arg(argv[i]);
if (arg == "-d") {
opt_useDisplay = false;
}
else if ((arg == "-h") || (arg == "--help")) {
std::cout << "SYNOPSIS" << std::endl;
std::cout << " " << argv[0] << " [-d][-h]" << std::endl;
std::cout << std::endl << std::endl;
std::cout << "DETAILS" << std::endl;
std::cout << " -d" << std::endl;
std::cout << " Deactivate display." << std::endl;
std::cout << std::endl;
std::cout << " -h, --help" << std::endl;
return 0;
}
}
const double dt = 3.;
const double sigmaRange = 5;
const double stdevAircraftVelocity = 0.2;
const double gt_X_init = -500.;
const double gt_Y_init = 1000.;
const double gt_vX_init = 100.;
const double gt_vY_init = 5.;
std::shared_ptr<vpUKSigmaDrawerAbstract> drawer = std::make_shared<vpUKSigmaDrawerMerwe>(4, 0.1, 2., -1.);
R[0][0] = sigmaRange*sigmaRange;
R[1][1] = sigmaElevAngle*sigmaElevAngle;
const double processVariance = 0.1;
Q1d[0][0] = std::pow(dt, 3) / 3.;
Q1d[0][1] = std::pow(dt, 2)/2.;
Q1d[1][0] = std::pow(dt, 2)/2.;
Q1d[1][1] = dt;
Q.insert(Q1d, 0, 0);
Q.insert(Q1d, 2, 2);
Q = Q * processVariance;
P0.eye(4, 4);
P0[0][0] = std::pow(300, 2);
P0[1][1] = std::pow(30, 2);
P0[2][2] = std::pow(150, 2);
P0[3][3] = std::pow(30, 2);
X0[0] = 0.9 * gt_X_init;
X0[1] = 0.9 * gt_vX_init;
X0[2] = 0.9 * gt_Y_init;
X0[3] = 0.9 * gt_vY_init;
using std::placeholders::_1;
ukf.init(X0, P0);
ukf.setMeasurementMeanFunction(measurementMean);
ukf.setMeasurementResidualFunction(measurementResidual);
#ifdef VISP_HAVE_DISPLAY
if (opt_useDisplay) {
plot->
setTitle(0,
"Position along X-axis");
plot->
setTitle(1,
"Velocity along X-axis");
plot->
setTitle(2,
"Position along Y-axis");
plot->
setTitle(3,
"Velocity along Y-axis");
}
#endif
ac_pos[0] = gt_X_init;
ac_pos[1] = gt_Y_init;
ac_vel[0] = gt_vX_init;
ac_vel[1] = gt_vY_init;
for (int i = 0; i < 500; ++i) {
ukf.filter(z, dt);
#ifdef VISP_HAVE_DISPLAY
if (opt_useDisplay) {
plot->
plot(0, 0, i, gt_X[0]);
plot->
plot(0, 1, i, Xest[0]);
plot->
plot(1, 0, i, gt_V[0]);
plot->
plot(1, 1, i, Xest[1]);
plot->
plot(2, 0, i, gt_X[1]);
plot->
plot(2, 1, i, Xest[2]);
plot->
plot(3, 0, i, gt_V[1]);
plot->
plot(3, 1, i, Xest[3]);
}
#endif
gt_Xprec = gt_X;
gt_Vprec = gt_V;
}
if (opt_useDisplay) {
std::cout << "Press Enter to quit..." << std::endl;
std::cin.get();
}
#ifdef VISP_HAVE_DISPLAY
if (opt_useDisplay) {
delete plot;
}
#endif
vpColVector X_GT({ gt_Xprec[0], gt_Vprec[0], gt_Xprec[1], gt_Vprec[1] });
const double maxError = 2.5;
if (finalError.frobeniusNorm() > maxError) {
std::cerr <<
"Error: max tolerated error = " << maxError <<
", final error = " << finalError.
frobeniusNorm() << std::endl;
return -1;
}
return 0;
}
#else
int main()
{
std::cout << "This example is only available if you compile ViSP in C++11 standard or higher." << std::endl;
return 0;
}
#endif
Class to simulate a flying aircraft.
unsigned int size() const
Return the number of elements of the 2D array.
Implementation of column vector and the associated operations.
double frobeniusNorm() const
Class for generating random number with normal probability density.
static double rad(double deg)
static float modulo(const float &value, const float &modulo)
Gives the rest of value divided by modulo when the quotient can only be an integer.
Implementation of a matrix and operations on matrices.
This class enables real time drawing of 2D or 3D graphics. An instance of the class open a window whi...
void initGraph(unsigned int graphNum, unsigned int curveNbr)
void setUnitY(unsigned int graphNum, const std::string &unity)
void setLegend(unsigned int graphNum, unsigned int curveNum, const std::string &legend)
void plot(unsigned int graphNum, unsigned int curveNum, double x, double y)
void setUnitX(unsigned int graphNum, const std::string &unitx)
void setTitle(unsigned int graphNum, const std::string &title)
Class that permits to convert the position of the aircraft into range and elevation angle measurement...
vpColVector state_to_measurement(const vpColVector &chi)
Convert the prior of the UKF into the measurement space.
std::function< vpColVector(const vpColVector &)> vpMeasurementFunction
Measurement function, which converts the prior points in the measurement space. The argument is a poi...
std::function< vpColVector(const vpColVector &, const double &)> vpProcessFunction
Process model function, which projects the sigma points forward in time. The first argument is a sigm...