Visual Servoing Platform version 3.7.0
Loading...
Searching...
No Matches
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
82
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
99using namespace VISP_NAMESPACE_NAME;
100#endif
101
102namespace
103{
111vpColVector 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
127double 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
146vpColVector 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
172vpColVector 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{
186public:
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
255private:
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{
267public:
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
299private:
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
305int main(const int argc, const char *argv[])
306{
307 bool opt_useDisplay = true;
308 bool opt_useUserInteraction = true;
309 for (int i = 1; i < argc; ++i) {
310 std::string arg(argv[i]);
311 if (arg == "-d") {
312 opt_useDisplay = false;
313 }
314 if (arg == "-c") {
315 opt_useUserInteraction = false;
316 }
317 else if ((arg == "-h") || (arg == "--help")) {
318 std::cout << "SYNOPSIS" << std::endl;
319 std::cout << " " << argv[0] << " [-d][-h]" << std::endl;
320 std::cout << std::endl << std::endl;
321 std::cout << "DETAILS" << std::endl;
322 std::cout << " -d" << std::endl;
323 std::cout << " Deactivate display." << std::endl;
324 std::cout << " -c" << std::endl;
325 std::cout << " Deactivate user interaction." << std::endl;
326 std::cout << std::endl;
327 std::cout << " -h, --help" << std::endl;
328 return 0;
329 }
330 }
331
332 const double dt = 3.; // Period of 3s
333 const double sigmaRange = 5; // Standard deviation of the range measurement: 5m
334 const double sigmaElevAngle = vpMath::rad(0.5); // Standard deviation of the elevation angle measurent: 0.5deg
335 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
336 const double gt_X_init = -500.; // Ground truth initial position along the X-axis, in meters
337 const double gt_Y_init = 1000.; // Ground truth initial position along the Y-axis, in meters
338 const double gt_vX_init = 100.; // Ground truth initial velocity along the X-axis, in meters
339 const double gt_vY_init = 5.; // Ground truth initial velocity along the Y-axis, in meters
340
341 // Initialize the attributes of the UKF
342 std::shared_ptr<vpUKSigmaDrawerAbstract> drawer = std::make_shared<vpUKSigmaDrawerMerwe>(4, 0.1, 2., -1.);
343
344 vpMatrix R(2, 2, 0.); // The covariance of the noise introduced by the measurement
345 R[0][0] = sigmaRange*sigmaRange;
346 R[1][1] = sigmaElevAngle*sigmaElevAngle;
347
348 const double processVariance = 0.1;
349 vpMatrix Q(4, 4, 0.); // The covariance of the process
350 vpMatrix Q1d(2, 2); // The covariance of a process whose states are {x, dx/dt} and for which the variance is 1
351 Q1d[0][0] = std::pow(dt, 3) / 3.;
352 Q1d[0][1] = std::pow(dt, 2)/2.;
353 Q1d[1][0] = std::pow(dt, 2)/2.;
354 Q1d[1][1] = dt;
355 Q.insert(Q1d, 0, 0);
356 Q.insert(Q1d, 2, 2);
357 Q = Q * processVariance;
358
359 vpMatrix P0(4, 4); // The initial guess of the process covariance
360 P0.eye(4, 4);
361 P0[0][0] = std::pow(300, 2);
362 P0[1][1] = std::pow(30, 2);
363 P0[2][2] = std::pow(150, 2);
364 P0[3][3] = std::pow(30, 2);
365
366 vpColVector X0(4);
367 X0[0] = 0.9 * gt_X_init; // x, i.e. 10% of error with regard to ground truth
368 X0[1] = 0.9 * gt_vX_init; // dx/dt, i.e. 10% of error with regard to ground truth
369 X0[2] = 0.9 * gt_Y_init; // y, i.e. 10% of error with regard to ground truth
370 X0[3] = 0.9 * gt_vY_init; // dy/dt, i.e. 10% of error with regard to ground truth
371
373 vpRadarStation radar(0., 0., sigmaRange, sigmaElevAngle);
374 using std::placeholders::_1;
376
377 // Initialize the UKF
378 vpUnscentedKalman ukf(Q, R, drawer, f, h);
379 ukf.init(X0, P0);
380 ukf.setMeasurementMeanFunction(measurementMean);
381 ukf.setMeasurementResidualFunction(measurementResidual);
382
383#ifdef VISP_HAVE_DISPLAY
384 vpPlot *plot = nullptr;
385 if (opt_useDisplay) {
386 // Initialize the plot
387 plot = new vpPlot(4);
388 plot->initGraph(0, 2);
389 plot->setTitle(0, "Position along X-axis");
390 plot->setUnitX(0, "Time (s)");
391 plot->setUnitY(0, "Position (m)");
392 plot->setLegend(0, 0, "GT");
393 plot->setLegend(0, 1, "Filtered");
394
395 plot->initGraph(1, 2);
396 plot->setTitle(1, "Velocity along X-axis");
397 plot->setUnitX(1, "Time (s)");
398 plot->setUnitY(1, "Velocity (m/s)");
399 plot->setLegend(1, 0, "GT");
400 plot->setLegend(1, 1, "Filtered");
401
402 plot->initGraph(2, 2);
403 plot->setTitle(2, "Position along Y-axis");
404 plot->setUnitX(2, "Time (s)");
405 plot->setUnitY(2, "Position (m)");
406 plot->setLegend(2, 0, "GT");
407 plot->setLegend(2, 1, "Filtered");
408
409 plot->initGraph(3, 2);
410 plot->setTitle(3, "Velocity along Y-axis");
411 plot->setUnitX(3, "Time (s)");
412 plot->setUnitY(3, "Velocity (m/s)");
413 plot->setLegend(3, 0, "GT");
414 plot->setLegend(3, 1, "Filtered");
415 }
416#else
417 (void)opt_useDisplay;
418#endif
419
420 // Initialize the simulation
422 ac_pos[0] = gt_X_init;
423 ac_pos[1] = gt_Y_init;
425 ac_vel[0] = gt_vX_init;
426 ac_vel[1] = gt_vY_init;
427 vpACSimulator ac(ac_pos, ac_vel, stdevAircraftVelocity);
428 vpColVector gt_Xprec = ac_pos;
429 vpColVector gt_Vprec = ac_vel;
430 for (int i = 0; i < 500; ++i) {
431 // Perform the measurement
432 vpColVector gt_X = ac.update(dt);
433 vpColVector gt_V = (gt_X - gt_Xprec) / dt;
434 vpColVector z = radar.measureWithNoise(gt_X);
435
436 // Use the UKF to filter the measurement
437 ukf.filter(z, dt);
438 vpColVector Xest = ukf.getXest();
439
440#ifdef VISP_HAVE_DISPLAY
441 if (opt_useDisplay) {
442 // Plot the ground truth, measurement and filtered state
443 plot->plot(0, 0, i, gt_X[0]);
444 plot->plot(0, 1, i, Xest[0]);
445
446 plot->plot(1, 0, i, gt_V[0]);
447 plot->plot(1, 1, i, Xest[1]);
448
449 plot->plot(2, 0, i, gt_X[1]);
450 plot->plot(2, 1, i, Xest[2]);
451
452 plot->plot(3, 0, i, gt_V[1]);
453 plot->plot(3, 1, i, Xest[3]);
454 }
455#endif
456
457 gt_Xprec = gt_X;
458 gt_Vprec = gt_V;
459 }
460
461 if (opt_useUserInteraction) {
462 std::cout << "Press Enter to quit..." << std::endl;
463 std::cin.get();
464 }
465
466#ifdef VISP_HAVE_DISPLAY
467 if (opt_useDisplay) {
468 delete plot;
469 }
470#endif
471
472 vpColVector X_GT({ gt_Xprec[0], gt_Vprec[0], gt_Xprec[1], gt_Vprec[1] });
473 vpColVector finalError = ukf.getXest() - X_GT;
474 const double maxError = 2.5;
475 if (finalError.frobeniusNorm() > maxError) {
476 std::cerr << "Error: max tolerated error = " << maxError << ", final error = " << finalError.frobeniusNorm() << std::endl;
477 return -1;
478 }
479
480 return 0;
481}
482#else
483int main()
484{
485 std::cout << "This example is only available if you compile ViSP in C++11 standard or higher." << std::endl;
486 return 0;
487}
488#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:435
Implementation of column vector and the associated operations.
Class for generating random number with normal probability density.
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:175
This class enables real time drawing of 2D or 3D graphics. An instance of the class open a window whi...
Definition vpPlot.h:117
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...
ColVector measurementResidual(ColVector meas, ColVector to_subtract)