Example of a simple non-linear use-case of the Particle Filter (PF).
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 PF 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.
#include <iostream>
#include <visp3/core/vpConfig.h>
#include <visp3/core/vpGaussRand.h>
#ifdef VISP_HAVE_DISPLAY
#include <visp3/gui/vpPlot.h>
#endif
#include <visp3/core/vpParticleFilter.h>
#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11)
#ifdef ENABLE_VISP_NAMESPACE
#endif
namespace
{
{
point[0] = particle[1] * dt + particle[0];
point[1] = particle[1];
point[2] = particle[3] * dt + particle[2];
point[3] = particle[3];
return point;
}
void computeCoordinatesFromMeasurement(
const vpColVector &z,
const double &xRadar,
const double &yRadar,
double &x,
double &y)
{
double dx = z[0] * std::cos(z[1]);
double dy = z[0] * std::sin(z[1]);
x = dx + xRadar;
y = dy + yRadar;
}
double computeError(const double &x0, const double &y0, const double &x1, const double &y1)
{
double dx = x0 - x1;
double dy = y0 - y1;
double error = std::sqrt(dx * dx + dy * dy);
return error;
}
{
double error = computeError(state[0], state[2], gt_X[0], gt_X[1]);
return error;
}
double computeMeasurementsError(
const vpColVector &z,
const double &xRadar,
const double &yRadar,
const vpColVector >_X)
{
double xMeas = 0., yMeas = 0.;
computeCoordinatesFromMeasurement(z, xRadar, yRadar, xMeas, yMeas);
double error = computeError(xMeas, yMeas, gt_X[0], gt_X[1]);
return error;
}
}
{
public:
vpRadarStation(
const double &x,
const double &y,
const double &range_std,
const double &elev_angle_std,
const double &distMaxAllowed)
: m_x(x)
, m_y(y)
, m_rngRange(range_std, 0., 4224)
, m_rngElevAngle(elev_angle_std, 0., 2112)
{
double sigmaDistance = distMaxAllowed / 3.;
double sigmaDistanceSquared = sigmaDistance * sigmaDistance;
m_constantDenominator = 1. / std::sqrt(2. * M_PI * sigmaDistanceSquared);
m_constantExpDenominator = -1. / (2. * sigmaDistanceSquared);
}
{
vpColVector meas(2);
double dx = particle[0] - m_x;
double dy = particle[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;
}
double likelihood(
const vpColVector &particle,
const vpColVector &meas)
{
double xParticle = particle[0];
double yParticle = particle[2];
double xMeas = 0., yMeas = 0.;
computeCoordinatesFromMeasurement(meas, m_x, m_y, xMeas, yMeas);
double dist = computeError(xParticle, yParticle, xMeas, yMeas);
double likelihood = std::exp(m_constantExpDenominator * dist) * m_constantDenominator;
}
private:
double m_x;
double m_y;
vpGaussRand m_rngRange;
vpGaussRand m_rngElevAngle;
double m_constantDenominator;
double m_constantExpDenominator;
};
{
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 parseArgs(
const int argc,
const char *argv[])
{
while (i < argc) {
std::string arg(argv[i]);
if ((arg == "--nb-steps-main") && ((i+1) < argc)) {
}
else if ((arg == "--nb-steps-warmup") && ((i+1) < argc)) {
}
else if ((arg == "--dt") && ((i+1) < argc)) {
m_dt = std::atoi(argv[i + 1]);
}
else if ((arg == "--stdev-range") && ((i+1) < argc)) {
}
else if ((arg == "--stdev-elev-angle") && ((i+1) < argc)) {
}
else if ((arg == "--stdev-aircraft-vel") && ((i+1) < argc)) {
}
else if ((arg == "--radar-X") && ((i+1) < argc)) {
}
else if ((arg == "--radar-Y") && ((i+1) < argc)) {
}
else if ((arg == "--gt-X0") && ((i+1) < argc)) {
}
else if ((arg == "--gt-Y0") && ((i+1) < argc)) {
}
else if ((arg == "--gt-vX0") && ((i+1) < argc)) {
}
else if ((arg == "--gt-vY0") && ((i+1) < argc)) {
}
else if ((arg == "--max-distance-likelihood") && ((i+1) < argc)) {
}
else if (((arg == "-N") || (arg == "--nb-particles")) && ((i+1) < argc)) {
m_N = std::atoi(argv[i + 1]);
}
else if ((arg == "--seed") && ((i+1) < argc)) {
}
else if ((arg == "--nb-threads") && ((i+1) < argc)) {
}
else if ((arg == "--ampli-max-X") && ((i+1) < argc)) {
}
else if ((arg == "--ampli-max-Y") && ((i+1) < argc)) {
}
else if ((arg == "--ampli-max-vX") && ((i+1) < argc)) {
}
else if ((arg == "--ampli-max-vY") && ((i+1) < argc)) {
}
else if (arg == "-d") {
}
else if (arg == "-c" ) {
}
else if ((arg == "-h") || (arg == "--help")) {
printUsage(std::string(argv[0]));
defaultArgs.
printDetails();
return 0;
}
else {
std::cout << "WARNING: unrecognised argument \"" << arg << "\"";
if (i + 1 < argc) {
std::cout << " with associated value(s) { ";
int nbValues = 0;
bool hasToRun = true;
while ((j < argc) && hasToRun) {
std::string nextValue(argv[j]);
if (nextValue.find("--") == std::string::npos) {
std::cout << nextValue << " ";
++nbValues;
}
else {
hasToRun = false;
}
}
std::cout << "}" << std::endl;
}
}
}
}
private:
void printUsage(const std::string &softName)
{
std::cout << "SYNOPSIS" << std::endl;
std::cout << " " << softName << " [--nb-steps-main <uint>] [--nb-steps-warmup <uint>]" << std::endl;
std::cout << " [--dt <double>] [--stdev-range <double>] [--stdev-elev-angle <double>] [--stdev-aircraft-vel <double>]" << std::endl;
std::cout << " [--radar-X <double>] [--radar-Y <double>]" << std::endl;
std::cout << " [--gt-X0 <double>] [--gt-Y0 <double>] [--gt-vX0 <double>] [--gt-vY0 <double>]" << std::endl;
std::cout << " [--max-distance-likelihood <double>] [-N, --nb-particles <uint>] [--seed <int>] [--nb-threads <int>]" << std::endl;
std::cout << " [--ampli-max-X <double>] [--ampli-max-Y <double>] [--ampli-max-vX <double>] [--ampli-max-vY <double>]" << std::endl;
std::cout << " [-d, --no-display] [-h]" << std::endl;
std::cout << " [-c] [-h]" << std::endl;
}
void printDetails()
{
std::cout << std::endl << std::endl;
std::cout << "DETAILS" << std::endl;
std::cout << " --nb-steps-main" << std::endl;
std::cout << " Number of steps in the main loop." << std::endl;
std::cout <<
" Default: " <<
m_nbSteps << std::endl;
std::cout << std::endl;
std::cout << " --nb-steps-warmup" << std::endl;
std::cout << " Number of steps in the warmup loop." << std::endl;
std::cout << std::endl;
std::cout << " --dt" << std::endl;
std::cout << " Timestep of the simulation, in seconds." << std::endl;
std::cout <<
" Default: " <<
m_dt << std::endl;
std::cout << std::endl;
std::cout << " --stdev-range" << std::endl;
std::cout << " Standard deviation of the range measurements, in meters." << std::endl;
std::cout << std::endl;
std::cout << " --stdev-elev-angle" << std::endl;
std::cout << " Standard deviation of the elevation angle measurements, in degrees." << std::endl;
std::cout << std::endl;
std::cout << " --stdev-aircraft-vel" << std::endl;
std::cout << " Standard deviation of the aircraft velocity, in m/s." << std::endl;
std::cout << std::endl;
std::cout << " --radar-X" << std::endl;
std::cout << " Position along the X-axis of the radar, in meters." << std::endl;
std::cout << " Be careful, because singularities happen if the aircraft flies above the radar." << std::endl;
std::cout <<
" Default: " <<
m_radar_X << std::endl;
std::cout << std::endl;
std::cout << " --radar-Y" << std::endl;
std::cout << " Position along the Y-axis of the radar, in meters." << std::endl;
std::cout << " Be careful, because singularities happen if the aircraft flies above the radar." << std::endl;
std::cout <<
" Default: " <<
m_radar_Y << std::endl;
std::cout << std::endl;
std::cout << " --gt-X0" << std::endl;
std::cout << " Initial position along the X-axis of the aircraft, in meters." << std::endl;
std::cout << " Be careful, because singularities happen if the aircraft flies above the radar." << std::endl;
std::cout << std::endl;
std::cout << " --gt-Y0" << std::endl;
std::cout << " Initial position along the Y-axis of the aircraft, in meters." << std::endl;
std::cout << " Be careful, because singularities happen if the aircraft flies above the radar." << std::endl;
std::cout << std::endl;
std::cout << " --gt-vX0" << std::endl;
std::cout << " Initial velocity along the X-axis of the aircraft, in m/s." << std::endl;
std::cout << " Be careful, because singularities happen if the aircraft flies above the radar." << std::endl;
std::cout << std::endl;
std::cout << " --gt-vY0" << std::endl;
std::cout << " Initial velocity along the Y-axis of the aircraft, in m/s." << std::endl;
std::cout << " Be careful, because singularities happen if the aircraft flies above the radar." << std::endl;
std::cout << std::endl;
std::cout << " --max-distance-likelihood" << std::endl;
std::cout << " Maximum tolerated distance between a particle and the measurements." << std::endl;
std::cout << " Above this value, the likelihood of the particle is 0." << std::endl;
std::cout << std::endl;
std::cout << " -N, --nb-particles" << std::endl;
std::cout << " Number of particles of the Particle Filter." << std::endl;
std::cout <<
" Default: " <<
m_N << std::endl;
std::cout << std::endl;
std::cout << " --seed" << std::endl;
std::cout << " Seed to initialize the Particle Filter." << std::endl;
std::cout << " Use a negative value makes to use the current timestamp instead." << std::endl;
std::cout <<
" Default: " <<
m_seedPF << std::endl;
std::cout << std::endl;
std::cout << " --nb-threads" << std::endl;
std::cout << " Set the number of threads to use in the Particle Filter (only if OpenMP is available)." << std::endl;
std::cout << " Use a negative value to use the maximum number of threads instead." << std::endl;
std::cout << std::endl;
std::cout << " --ampli-max-X" << std::endl;
std::cout << " Maximum amplitude of the noise added to a particle along the X-axis." << std::endl;
std::cout << std::endl;
std::cout << " --ampli-max-Y" << std::endl;
std::cout << " Maximum amplitude of the noise added to a particle along the Y-axis." << std::endl;
std::cout << std::endl;
std::cout << " --ampli-max-vX" << std::endl;
std::cout << " Maximum amplitude of the noise added to a particle to the velocity along the X-axis component." << std::endl;
std::cout << std::endl;
std::cout << " --ampli-max-vY" << std::endl;
std::cout << " Maximum amplitude of the noise added to a particle to the velocity along the Y-axis component." << std::endl;
std::cout << std::endl;
std::cout << " -d, --no-display" << std::endl;
std::cout << " Deactivate display." << std::endl;
std::cout << " Default: display is ";
#ifdef VISP_HAVE_DISPLAY
std::cout << "ON" << std::endl;
#else
std::cout << "OFF" << std::endl;
#endif
std::cout << std::endl;
std::cout << " -c" << std::endl;
std::cout << " Deactivate user interaction." << std::endl;
std::cout << std::endl;
std::cout << " -h, --help" << std::endl;
std::cout << " Display this help." << std::endl;
std::cout << std::endl;
}
};
int main(const int argc, const char *argv[])
{
int returnCode =
args.parseArgs(argc, argv);
return returnCode;
}
std::vector<double> stdevsPF = {
args.m_ampliMaxX /3.,
args.m_ampliMaxVx /3.,
args.m_ampliMaxY /3. ,
args.m_ampliMaxVy /3. };
int seedPF =
args.m_seedPF;
unsigned int nbParticles =
args.m_N;
int nbThreads =
args.m_nbThreads;
X0[0] = 0.9 *
args.m_gt_X_init;
X0[1] = 0.9 *
args.m_gt_vX_init;
X0[2] = 0.9 *
args.m_gt_Y_init;
X0[3] = 0.9 *
args.m_gt_vY_init;
using std::placeholders::_1;
using std::placeholders::_2;
filter.init(X0, f, likelihoodFunc, checkResamplingFunc, resamplingFunc);
#ifdef VISP_HAVE_DISPLAY
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->setLegend(0, 2,
"Measure");
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->setLegend(1, 2,
"Measure");
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->setLegend(2, 2,
"Measure");
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");
plot->setLegend(3, 2,
"Measure");
}
#endif
double averageFilteringTime = 0.;
double meanErrorFilter = 0., meanErrorNoise = 0.;
double xNoise_prec = 0., yNoise_prec = 0.;
const unsigned int nbStepsWarmUp =
args.m_nbStepsWarmUp;
for (
unsigned int i = 0;
i < nbStepsWarmUp; ++
i) {
filter.filter(z,
args.m_dt);
computeCoordinatesFromMeasurement(z,
args.m_radar_X,
args.m_radar_Y, xNoise_prec, yNoise_prec);
}
for (
unsigned int i = 0;
i <
args.m_nbSteps; ++
i) {
filter.filter(z,
args.m_dt);
double normErrorFilter = computeStateError(Xest, gt_X);
meanErrorFilter += normErrorFilter;
double xNoise = 0., yNoise = 0.;
computeCoordinatesFromMeasurement(z,
args.m_radar_X,
args.m_radar_Y, xNoise, yNoise);
double normErrorNoise = computeMeasurementsError(z,
args.m_radar_X,
args.m_radar_Y, gt_X);
meanErrorNoise += normErrorNoise;
#ifdef VISP_HAVE_DISPLAY
plot->plot(0, 0, i, gt_X[0]);
plot->plot(0, 1, i, Xest[0]);
plot->plot(0, 2, i, xNoise);
double vxNoise = (xNoise - xNoise_prec) /
args.m_dt;
plot->plot(1, 0, i, gt_V[0]);
plot->plot(1, 1, i, Xest[1]);
plot->plot(1, 2, i, vxNoise);
plot->plot(2, 0, i, gt_X[1]);
plot->plot(2, 1, i, Xest[2]);
plot->plot(2, 2, i, yNoise);
double vyNoise = (yNoise - yNoise_prec) /
args.m_dt;
plot->plot(3, 0, i, gt_V[1]);
plot->plot(3, 1, i, Xest[3]);
plot->plot(3, 2, i, vyNoise);
}
#endif
xNoise_prec = xNoise;
yNoise_prec = yNoise;
}
meanErrorFilter /=
static_cast<double>(
args.m_nbSteps);
meanErrorNoise /=
static_cast<double>(
args.m_nbSteps);
averageFilteringTime = averageFilteringTime / (
static_cast<double>(
args.m_nbSteps) +
static_cast<double>(nbStepsWarmUp));
std::cout << "Mean error filter = " << meanErrorFilter << "m" << std::endl;
std::cout << "Mean error noise = " << meanErrorNoise << "m" << std::endl;
std::cout << "Mean filtering time = " << averageFilteringTime << "us" << std::endl;
if (
args.m_useUserInteraction) {
std::cout << "Press Enter to quit..." << std::endl;
std::cin.get();
}
#ifdef VISP_HAVE_DISPLAY
}
#endif
const double maxError = 150.;
if (meanErrorFilter > maxError) {
std::cerr << "Error: max tolerated error = " << maxError << ", mean error = " << meanErrorFilter << std::endl;
return -1;
}
else if (meanErrorFilter >= meanErrorNoise) {
std::cerr << "Error: mean error without filter = " << meanErrorNoise << ", mean error with filter = " << meanErrorFilter << 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.
Implementation of column vector and the associated operations.
static const vpColor blue
static const vpColor black
static double rad(double deg)
static double deg(double rad)
The class permits to use a Particle Filter.
std::function< vpParticlesWithWeights(const std::vector< vpColVector > &, const std::vector< double > &)> vpResamplingFunction
Function that takes as argument the vector of particles and the vector of associated weights....
static bool simpleResamplingCheck(const unsigned int &N, const std::vector< double > &weights)
Returns true if the following condition is fulfilled, or if all the particles diverged: .
std::function< vpColVector(const vpColVector &, const double &)> vpProcessFunction
Process model function, which projects a particle forward in time. The first argument is a particle,...
static vpParticlesWithWeights simpleImportanceResampling(const std::vector< vpColVector > &particles, const std::vector< double > &weights)
Function implementing the resampling of a Simple Importance Resampling Particle Filter.
std::function< bool(const unsigned int &, const std::vector< double > &)> vpResamplingConditionFunction
Function that takes as argument the number of particles and the vector of weights associated to each ...
std::function< double(const vpColVector &, const MeasurementsType &)> vpLikelihoodFunction
Likelihood function, which evaluates the likelihood of a particle with regard to the measurements....
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.
double likelihood(const vpColVector &particle, const vpColVector &meas)
Compute the likelihood of a particle (value between 0. and 1.) knowing the measurements....
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.
VISP_EXPORT double measureTimeMicros()
bool m_useDisplay
If true, activate the plot and the renderer if VISP_HAVE_DISPLAY is defined.
int m_nbThreads
Number of thread to use in the Particle Filter.
unsigned int m_nbSteps
Number of steps for the main loop.
static const int SOFTWARE_CONTINUE
double m_stdevAircraftVelocity
double m_ampliMaxVx
Amplitude max of the noise for the state component corresponding to the velocity along the X-axis.
double m_ampliMaxY
Amplitude max of the noise for the state component corresponding to the Y coordinate.
unsigned int m_N
The number of particles.
double m_ampliMaxVy
Amplitude max of the noise for the state component corresponding to the velocity along the Y-axis.
double m_maxDistanceForLikelihood
The maximum allowed distance between a particle and the measurement, leading to a likelihood equal to...
int parseArgs(const int argc, const char *argv[])
unsigned int m_nbStepsWarmUp
Number of steps for the warmup phase.
bool m_useUserInteraction
If true, program will require some user inputs.
double m_ampliMaxX
Amplitude max of the noise for the state component corresponding to the X coordinate.
long m_seedPF
Seed for the random generators of the PF.