Visual Servoing Platform  version 3.6.1 under development (2024-11-21)
ukf-nonlinear-example.cpp
1 /*
2  * ViSP, open source Visual Servoing Platform software.
3  * Copyright (C) 2005 - 2024 by Inria. All rights reserved.
4  *
5  * This software is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  * See the file LICENSE.txt at the root directory of this source
10  * distribution for additional information about the GNU GPL.
11  *
12  * For using ViSP with software that can not be combined with the GNU
13  * GPL, please contact Inria about acquiring a ViSP Professional
14  * Edition License.
15  *
16  * See https://visp.inria.fr for more information.
17  *
18  * This software was developed at:
19  * Inria Rennes - Bretagne Atlantique
20  * Campus Universitaire de Beaulieu
21  * 35042 Rennes Cedex
22  * France
23  *
24  * If you have questions regarding the use of this file, please contact
25  * Inria at visp@inria.fr
26  *
27  * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
28  * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
29  */
30 
83 #include <iostream>
84 
85 // UKF includes
86 #include <visp3/core/vpUKSigmaDrawerMerwe.h>
87 #include <visp3/core/vpUnscentedKalman.h>
88 
89 // ViSP includes
90 #include <visp3/core/vpConfig.h>
91 #include <visp3/core/vpGaussRand.h>
92 #ifdef VISP_HAVE_DISPLAY
93 #include <visp3/gui/vpPlot.h>
94 #endif
95 
96 #if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11)
97 
98 #ifdef ENABLE_VISP_NAMESPACE
99 using namespace VISP_NAMESPACE_NAME;
100 #endif
101 
102 namespace
103 {
111 vpColVector fx(const vpColVector &chi, const double &dt)
112 {
113  vpColVector point(4);
114  point[0] = chi[1] * dt + chi[0];
115  point[1] = chi[1];
116  point[2] = chi[3] * dt + chi[2];
117  point[3] = chi[3];
118  return point;
119 }
120 
127 double normalizeAngle(const double &angle)
128 {
129  double angleIn0to2pi = vpMath::modulo(angle, 2. * M_PI);
130  double angleInMinPiPi = angleIn0to2pi;
131  if (angleInMinPiPi > M_PI) {
132  // Substract 2 PI to be in interval [-Pi; Pi]
133  angleInMinPiPi -= 2. * M_PI;
134  }
135  return angleInMinPiPi;
136 }
137 
146 vpColVector measurementMean(const std::vector<vpColVector> &measurements, const std::vector<double> &wm)
147 {
148  const unsigned int nbPoints = static_cast<unsigned int>(measurements.size());
149  const unsigned int sizeMeasurement = measurements[0].size();
150  vpColVector mean(sizeMeasurement, 0.);
151  double sumCos(0.);
152  double sumSin(0.);
153  for (unsigned int i = 0; i < nbPoints; ++i) {
154  mean[0] += wm[i] * measurements[i][0];
155  sumCos += wm[i] * std::cos(measurements[i][1]);
156  sumSin += wm[i] * std::sin(measurements[i][1]);
157  }
158 
159  mean[1] = std::atan2(sumSin, sumCos);
160 
161  return mean;
162 }
163 
172 vpColVector measurementResidual(const vpColVector &meas, const vpColVector &toSubtract)
173 {
174  vpColVector res = meas - toSubtract;
175  res[1] = normalizeAngle(res[1]);
176  return res;
177 }
178 }
179 
185 {
186 public:
195  vpRadarStation(const double &x, const double &y, const double &range_std, const double &elev_angle_std)
196  : m_x(x)
197  , m_y(y)
198  , m_rngRange(range_std, 0., 4224)
199  , m_rngElevAngle(elev_angle_std, 0., 2112)
200  { }
201 
209  {
210  vpColVector meas(2);
211  double dx = chi[0] - m_x;
212  double dy = chi[2] - m_y;
213  meas[0] = std::sqrt(dx * dx + dy * dy);
214  meas[1] = std::atan2(dy, dx);
215  return meas;
216  }
217 
227  {
228  double dx = pos[0] - m_x;
229  double dy = pos[1] - m_y;
230  double range = std::sqrt(dx * dx + dy * dy);
231  double elevAngle = std::atan2(dy, dx);
232  vpColVector measurements(2);
233  measurements[0] = range;
234  measurements[1] = elevAngle;
235  return measurements;
236  }
237 
247  {
248  vpColVector measurementsGT = measureGT(pos);
249  vpColVector measurementsNoisy = measurementsGT;
250  measurementsNoisy[0] += m_rngRange();
251  measurementsNoisy[1] += m_rngElevAngle();
252  return measurementsNoisy;
253  }
254 
255 private:
256  double m_x; // The position on the ground of the radar
257  double m_y; // The altitude of the radar
258  vpGaussRand m_rngRange; // Noise simulator for the range measurement
259  vpGaussRand m_rngElevAngle; // Noise simulator for the elevation angle measurement
260 };
261 
266 {
267 public:
275  vpACSimulator(const vpColVector &X0, const vpColVector &vel, const double &vel_std)
276  : m_pos(X0)
277  , m_vel(vel)
278  , m_rngVel(vel_std, 0.)
279  {
280 
281  }
282 
290  vpColVector update(const double &dt)
291  {
292  vpColVector dx = m_vel * dt;
293  dx[0] += m_rngVel() * dt;
294  dx[1] += m_rngVel() * dt;
295  m_pos += dx;
296  return m_pos;
297  }
298 
299 private:
300  vpColVector m_pos; // Position of the simulated aircraft
301  vpColVector m_vel; // Velocity of the simulated aircraft
302  vpGaussRand m_rngVel; // Random generator for slight variations of the velocity of the aircraft
303 };
304 
305 int main(const int argc, const char *argv[])
306 {
307  bool opt_useDisplay = true;
308  for (int i = 1; i < argc; ++i) {
309  std::string arg(argv[i]);
310  if (arg == "-d") {
311  opt_useDisplay = false;
312  }
313  else if ((arg == "-h") || (arg == "--help")) {
314  std::cout << "SYNOPSIS" << std::endl;
315  std::cout << " " << argv[0] << " [-d][-h]" << std::endl;
316  std::cout << std::endl << std::endl;
317  std::cout << "DETAILS" << std::endl;
318  std::cout << " -d" << std::endl;
319  std::cout << " Deactivate display." << std::endl;
320  std::cout << std::endl;
321  std::cout << " -h, --help" << std::endl;
322  return 0;
323  }
324  }
325 
326  const double dt = 3.; // Period of 3s
327  const double sigmaRange = 5; // Standard deviation of the range measurement: 5m
328  const double sigmaElevAngle = vpMath::rad(0.5); // Standard deviation of the elevation angle measurent: 0.5deg
329  const double stdevAircraftVelocity = 0.2; // Standard deviation of the velocity of the simulated aircraft, to make it deviate a bit from the constant velocity model
330  const double gt_X_init = -500.; // Ground truth initial position along the X-axis, in meters
331  const double gt_Y_init = 1000.; // Ground truth initial position along the Y-axis, in meters
332  const double gt_vX_init = 100.; // Ground truth initial velocity along the X-axis, in meters
333  const double gt_vY_init = 5.; // Ground truth initial velocity along the Y-axis, in meters
334 
335  // Initialize the attributes of the UKF
336  std::shared_ptr<vpUKSigmaDrawerAbstract> drawer = std::make_shared<vpUKSigmaDrawerMerwe>(4, 0.1, 2., -1.);
337 
338  vpMatrix R(2, 2, 0.); // The covariance of the noise introduced by the measurement
339  R[0][0] = sigmaRange*sigmaRange;
340  R[1][1] = sigmaElevAngle*sigmaElevAngle;
341 
342  const double processVariance = 0.1;
343  vpMatrix Q(4, 4, 0.); // The covariance of the process
344  vpMatrix Q1d(2, 2); // The covariance of a process whose states are {x, dx/dt} and for which the variance is 1
345  Q1d[0][0] = std::pow(dt, 3) / 3.;
346  Q1d[0][1] = std::pow(dt, 2)/2.;
347  Q1d[1][0] = std::pow(dt, 2)/2.;
348  Q1d[1][1] = dt;
349  Q.insert(Q1d, 0, 0);
350  Q.insert(Q1d, 2, 2);
351  Q = Q * processVariance;
352 
353  vpMatrix P0(4, 4); // The initial guess of the process covariance
354  P0.eye(4, 4);
355  P0[0][0] = std::pow(300, 2);
356  P0[1][1] = std::pow(30, 2);
357  P0[2][2] = std::pow(150, 2);
358  P0[3][3] = std::pow(30, 2);
359 
360  vpColVector X0(4);
361  X0[0] = 0.9 * gt_X_init; // x, i.e. 10% of error with regard to ground truth
362  X0[1] = 0.9 * gt_vX_init; // dx/dt, i.e. 10% of error with regard to ground truth
363  X0[2] = 0.9 * gt_Y_init; // y, i.e. 10% of error with regard to ground truth
364  X0[3] = 0.9 * gt_vY_init; // dy/dt, i.e. 10% of error with regard to ground truth
365 
367  vpRadarStation radar(0., 0., sigmaRange, sigmaElevAngle);
368  using std::placeholders::_1;
370 
371  // Initialize the UKF
372  vpUnscentedKalman ukf(Q, R, drawer, f, h);
373  ukf.init(X0, P0);
374  ukf.setMeasurementMeanFunction(measurementMean);
375  ukf.setMeasurementResidualFunction(measurementResidual);
376 
377 #ifdef VISP_HAVE_DISPLAY
378  vpPlot *plot = nullptr;
379  if (opt_useDisplay) {
380  // Initialize the plot
381  plot = new vpPlot(4);
382  plot->initGraph(0, 2);
383  plot->setTitle(0, "Position along X-axis");
384  plot->setUnitX(0, "Time (s)");
385  plot->setUnitY(0, "Position (m)");
386  plot->setLegend(0, 0, "GT");
387  plot->setLegend(0, 1, "Filtered");
388 
389  plot->initGraph(1, 2);
390  plot->setTitle(1, "Velocity along X-axis");
391  plot->setUnitX(1, "Time (s)");
392  plot->setUnitY(1, "Velocity (m/s)");
393  plot->setLegend(1, 0, "GT");
394  plot->setLegend(1, 1, "Filtered");
395 
396  plot->initGraph(2, 2);
397  plot->setTitle(2, "Position along Y-axis");
398  plot->setUnitX(2, "Time (s)");
399  plot->setUnitY(2, "Position (m)");
400  plot->setLegend(2, 0, "GT");
401  plot->setLegend(2, 1, "Filtered");
402 
403  plot->initGraph(3, 2);
404  plot->setTitle(3, "Velocity along Y-axis");
405  plot->setUnitX(3, "Time (s)");
406  plot->setUnitY(3, "Velocity (m/s)");
407  plot->setLegend(3, 0, "GT");
408  plot->setLegend(3, 1, "Filtered");
409  }
410 #endif
411 
412  // Initialize the simulation
413  vpColVector ac_pos(2);
414  ac_pos[0] = gt_X_init;
415  ac_pos[1] = gt_Y_init;
416  vpColVector ac_vel(2);
417  ac_vel[0] = gt_vX_init;
418  ac_vel[1] = gt_vY_init;
419  vpACSimulator ac(ac_pos, ac_vel, stdevAircraftVelocity);
420  vpColVector gt_Xprec = ac_pos;
421  vpColVector gt_Vprec = ac_vel;
422  for (int i = 0; i < 500; ++i) {
423  // Perform the measurement
424  vpColVector gt_X = ac.update(dt);
425  vpColVector gt_V = (gt_X - gt_Xprec) / dt;
426  vpColVector z = radar.measureWithNoise(gt_X);
427 
428  // Use the UKF to filter the measurement
429  ukf.filter(z, dt);
430  vpColVector Xest = ukf.getXest();
431 
432 #ifdef VISP_HAVE_DISPLAY
433  if (opt_useDisplay) {
434  // Plot the ground truth, measurement and filtered state
435  plot->plot(0, 0, i, gt_X[0]);
436  plot->plot(0, 1, i, Xest[0]);
437 
438  plot->plot(1, 0, i, gt_V[0]);
439  plot->plot(1, 1, i, Xest[1]);
440 
441  plot->plot(2, 0, i, gt_X[1]);
442  plot->plot(2, 1, i, Xest[2]);
443 
444  plot->plot(3, 0, i, gt_V[1]);
445  plot->plot(3, 1, i, Xest[3]);
446  }
447 #endif
448 
449  gt_Xprec = gt_X;
450  gt_Vprec = gt_V;
451  }
452 
453  if (opt_useDisplay) {
454  std::cout << "Press Enter to quit..." << std::endl;
455  std::cin.get();
456  }
457 
458 #ifdef VISP_HAVE_DISPLAY
459  if (opt_useDisplay) {
460  delete plot;
461  }
462 #endif
463 
464  vpColVector X_GT({ gt_Xprec[0], gt_Vprec[0], gt_Xprec[1], gt_Vprec[1] });
465  vpColVector finalError = ukf.getXest() - X_GT;
466  const double maxError = 2.5;
467  if (finalError.frobeniusNorm() > maxError) {
468  std::cerr << "Error: max tolerated error = " << maxError << ", final error = " << finalError.frobeniusNorm() << std::endl;
469  return -1;
470  }
471 
472  return 0;
473 }
474 #else
475 int main()
476 {
477  std::cout << "This example is only available if you compile ViSP in C++11 standard or higher." << std::endl;
478  return 0;
479 }
480 #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.
Definition: vpArray2D.h:349
Implementation of column vector and the associated operations.
Definition: vpColVector.h:191
double frobeniusNorm() const
Class for generating random number with normal probability density.
Definition: vpGaussRand.h:117
static double rad(double deg)
Definition: vpMath.h:129
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.
Definition: vpMath.h:177
Implementation of a matrix and operations on matrices.
Definition: vpMatrix.h:169
This class enables real time drawing of 2D or 3D graphics. An instance of the class open a window whi...
Definition: vpPlot.h:112
void initGraph(unsigned int graphNum, unsigned int curveNbr)
Definition: vpPlot.cpp:203
void setUnitY(unsigned int graphNum, const std::string &unity)
Definition: vpPlot.cpp:530
void setLegend(unsigned int graphNum, unsigned int curveNum, const std::string &legend)
Definition: vpPlot.cpp:552
void plot(unsigned int graphNum, unsigned int curveNum, double x, double y)
Definition: vpPlot.cpp:270
void setUnitX(unsigned int graphNum, const std::string &unitx)
Definition: vpPlot.cpp:520
void setTitle(unsigned int graphNum, const std::string &title)
Definition: vpPlot.cpp:510
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...