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:
Additionally, 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)
{ }
{
vpColVector meas(2);
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;
}
vpColVector
measureGT(
const vpColVector &pos)
{
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);
vpColVector measurements(2);
measurements[0] = range;
measurements[1] = elevAngle;
return measurements;
}
{
vpColVector measurementsNoisy = measurementsGT;
measurementsNoisy[0] += m_rngRange();
measurementsNoisy[1] += m_rngElevAngle();
return measurementsNoisy;
}
private:
double m_x;
double m_y;
vpGaussRand m_rngRange;
vpGaussRand m_rngElevAngle;
};
{
public:
vpACSimulator(
const vpColVector &X0,
const vpColVector &vel,
const double &vel_std)
, m_vel(vel)
, m_rngVel(vel_std, 0.)
{
}
vpColVector
update(
const double &dt)
{
vpColVector dx = m_vel *
dt;
dx[0] += m_rngVel() *
dt;
dx[1] += m_rngVel() *
dt;
m_pos += dx;
return m_pos;
}
private:
vpColVector m_pos;
vpColVector m_vel;
vpGaussRand m_rngVel;
};
int main(const int argc, const char *argv[])
{
bool opt_useDisplay = true;
bool opt_useUserInteraction = true;
for (
int i = 1;
i < argc; ++
i) {
std::string arg(argv[i]);
if (arg == "-d") {
opt_useDisplay = false;
}
if (arg == "-c") {
opt_useUserInteraction = 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 << " -c" << std::endl;
std::cout << " Deactivate user interaction." << std::endl;
std::cout << std::endl;
std::cout << " -h, --help" << std::endl;
return 0;
}
}
const double sigmaRange = 5;
const double stdevAircraftVelocity = 0.2;
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.;
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);
using std::placeholders::_1;
ukf.setMeasurementMeanFunction(measurementMean);
ukf.setMeasurementResidualFunction(measurementResidual);
#ifdef VISP_HAVE_DISPLAY
if (opt_useDisplay) {
plot->setTitle(0,
"Position along X-axis");
plot->setUnitX(0,
"Time (s)");
plot->setUnitY(0,
"Position (m)");
plot->setLegend(0, 0,
"GT");
plot->setLegend(0, 1,
"Filtered");
plot->setTitle(1,
"Velocity along X-axis");
plot->setUnitX(1,
"Time (s)");
plot->setUnitY(1,
"Velocity (m/s)");
plot->setLegend(1, 0,
"GT");
plot->setLegend(1, 1,
"Filtered");
plot->setTitle(2,
"Position along Y-axis");
plot->setUnitX(2,
"Time (s)");
plot->setUnitY(2,
"Position (m)");
plot->setLegend(2, 0,
"GT");
plot->setLegend(2, 1,
"Filtered");
plot->setTitle(3,
"Velocity along Y-axis");
plot->setUnitX(3,
"Time (s)");
plot->setUnitY(3,
"Velocity (m/s)");
plot->setLegend(3, 0,
"GT");
plot->setLegend(3, 1,
"Filtered");
}
#else
(void)opt_useDisplay;
#endif
for (
int i = 0;
i < 500; ++
i) {
#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
}
if (opt_useUserInteraction) {
std::cout << "Press Enter to quit..." << std::endl;
std::cin.get();
}
#ifdef VISP_HAVE_DISPLAY
if (opt_useDisplay) {
}
#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.
vpColVector update(const double &dt)
Compute the new position of the aircraft after dt seconds have passed since the last update.
vpACSimulator(const vpColVector &X0, const vpColVector &vel, const double &vel_std)
Construct a new vpACSimulator object.
unsigned int size() const
Return the number of elements of the 2D array.
Implementation of column vector and the associated operations.
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...
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.
vpRadarStation(const double &x, const double &y, const double &range_std, const double &elev_angle_std)
Construct a new vpRadarStation object.
vpColVector measureWithNoise(const vpColVector &pos)
Noisy measurement of the range and elevation angle that correspond to pos.
vpColVector measureGT(const vpColVector &pos)
Perfect measurement of the range and elevation angle that correspond to pos.
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...