Visual Servoing Platform version 3.7.0
Loading...
Searching...
No Matches
ukf-nonlinear-complex-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
76
77#include <iostream>
78
79// UKF includes
80#include <visp3/core/vpUKSigmaDrawerMerwe.h>
81#include <visp3/core/vpUnscentedKalman.h>
82
83// ViSP includes
84#include <visp3/core/vpConfig.h>
85#include <visp3/core/vpGaussRand.h>
86#ifdef VISP_HAVE_DISPLAY
87#include <visp3/gui/vpPlot.h>
88#endif
89
90#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11)
91
92#ifdef ENABLE_VISP_NAMESPACE
93using namespace VISP_NAMESPACE_NAME;
94#endif
95
96namespace
97{
104double normalizeAngle(const double &angle)
105{
106 double angleIn0to2pi = vpMath::modulo(angle, 2. * M_PI);
107 double angleInMinPiPi = angleIn0to2pi;
108 if (angleInMinPiPi > M_PI) {
109 // Substract 2 PI to be in interval [-Pi; Pi]
110 angleInMinPiPi -= 2. * M_PI;
111 }
112 return angleInMinPiPi;
113}
114
122vpColVector measurementMean(const std::vector<vpColVector> &measurements, const std::vector<double> &wm)
123{
124 const unsigned int nbPoints = static_cast<unsigned int>(measurements.size());
125 const unsigned int sizeMeasurement = measurements[0].size();
126 const unsigned int nbLandmarks = sizeMeasurement / 2;
127 vpColVector mean(sizeMeasurement, 0.);
128 std::vector<double> sumCos(nbLandmarks, 0.);
129 std::vector<double> sumSin(nbLandmarks, 0.);
130 for (unsigned int i = 0; i < nbPoints; ++i) {
131 for (unsigned int j = 0; j < nbLandmarks; ++j) {
132 mean[2*j] += wm[i] * measurements[i][2*j];
133 sumCos[j] += wm[i] * std::cos(measurements[i][(2*j)+1]);
134 sumSin[j] += wm[i] * std::sin(measurements[i][(2*j)+1]);
135 }
136 }
137 for (unsigned int j = 0; j < nbLandmarks; ++j) {
138 mean[(2*j)+1] = std::atan2(sumSin[j], sumCos[j]);
139 }
140 return mean;
141}
142
151vpColVector measurementResidual(const vpColVector &meas, const vpColVector &toSubtract)
152{
153 vpColVector res = meas - toSubtract;
154 unsigned int nbMeasures = res.size();
155 for (unsigned int i = 1; i < nbMeasures; i += 2) {
156 res[i] = normalizeAngle(res[i]);
157 }
158 return res;
159}
160
169vpColVector stateAdd(const vpColVector &state, const vpColVector &toAdd)
170{
171 vpColVector add = state + toAdd;
172 add[2] = normalizeAngle(add[2]);
173 return add;
174}
175
183vpColVector stateMean(const std::vector<vpColVector> &states, const std::vector<double> &wm)
184{
185 vpColVector mean(3, 0.);
186 unsigned int nbPoints = static_cast<unsigned int>(states.size());
187 double sumCos = 0.;
188 double sumSin = 0.;
189 for (unsigned int i = 0; i < nbPoints; ++i) {
190 mean[0] += wm[i] * states[i][0];
191 mean[1] += wm[i] * states[i][1];
192 sumCos += wm[i] * std::cos(states[i][2]);
193 sumSin += wm[i] * std::sin(states[i][2]);
194 }
195 mean[2] = std::atan2(sumSin, sumCos);
196 return mean;
197}
198
207vpColVector stateResidual(const vpColVector &state, const vpColVector &toSubtract)
208{
209 vpColVector res = state - toSubtract;
210 res[2] = normalizeAngle(res[2]);
211 return res;
212}
213
221vpColVector fx(const vpColVector &x, const double & /*dt*/)
222{
223 return x;
224}
225
235std::vector<vpColVector> generateTurnCommands(const double &v, const double &angleStart, const double &angleStop, const unsigned int &nbSteps)
236{
237 std::vector<vpColVector> cmds;
238 double dTheta = vpMath::rad(angleStop - angleStart) / static_cast<double>(nbSteps - 1);
239 for (unsigned int i = 0; i < nbSteps; ++i) {
240 double theta = vpMath::rad(angleStart) + dTheta * static_cast<double>(i);
241 vpColVector cmd(2);
242 cmd[0] = v;
243 cmd[1] = theta;
244 cmds.push_back(cmd);
245 }
246 return cmds;
247}
248
254std::vector<vpColVector> generateCommands()
255{
256 std::vector<vpColVector> cmds;
257 // Starting by an straight line acceleration
258 unsigned int nbSteps = 30;
259 double dv = (1.1 - 0.001) / static_cast<double>(nbSteps - 1);
260 for (unsigned int i = 0; i < nbSteps; ++i) {
261 vpColVector cmd(2);
262 cmd[0] = 0.001 + static_cast<double>(i) * dv;
263 cmd[1] = 0.;
264 cmds.push_back(cmd);
265 }
266
267 // Left turn
268 double lastLinearVelocity = cmds[cmds.size() -1][0];
269 std::vector<vpColVector> leftTurnCmds = generateTurnCommands(lastLinearVelocity, 0, 2, 15);
270 cmds.insert(cmds.end(), leftTurnCmds.begin(), leftTurnCmds.end());
271 for (unsigned int i = 0; i < 100; ++i) {
272 cmds.push_back(cmds[cmds.size() -1]);
273 }
274
275 // Right turn
276 lastLinearVelocity = cmds[cmds.size() -1][0];
277 std::vector<vpColVector> rightTurnCmds = generateTurnCommands(lastLinearVelocity, 2, -2, 15);
278 cmds.insert(cmds.end(), rightTurnCmds.begin(), rightTurnCmds.end());
279 for (unsigned int i = 0; i < 200; ++i) {
280 cmds.push_back(cmds[cmds.size() -1]);
281 }
282
283 // Left turn again
284 lastLinearVelocity = cmds[cmds.size() -1][0];
285 leftTurnCmds = generateTurnCommands(lastLinearVelocity, -2, 0, 15);
286 cmds.insert(cmds.end(), leftTurnCmds.begin(), leftTurnCmds.end());
287 for (unsigned int i = 0; i < 150; ++i) {
288 cmds.push_back(cmds[cmds.size() -1]);
289 }
290
291 lastLinearVelocity = cmds[cmds.size() -1][0];
292 leftTurnCmds = generateTurnCommands(lastLinearVelocity, 0, 1, 25);
293 cmds.insert(cmds.end(), leftTurnCmds.begin(), leftTurnCmds.end());
294 for (unsigned int i = 0; i < 150; ++i) {
295 cmds.push_back(cmds[cmds.size() -1]);
296 }
297
298 return cmds;
299}
300}
301
306{
307public:
313 vpBicycleModel(const double &w)
314 : m_w(w)
315 { }
316
325 vpColVector computeMotion(const vpColVector &u, const vpColVector &x, const double &dt)
326 {
327 double heading = x[2];
328 double vel = u[0];
329 double steeringAngle = u[1];
330 double distance = vel * dt;
331
332 if (std::abs(steeringAngle) > 0.001) {
333 // The robot is turning
334 double beta = (distance / m_w) * std::tan(steeringAngle);
335 double radius = m_w / std::tan(steeringAngle);
336 double sinh = std::sin(heading);
337 double sinhb = std::sin(heading + beta);
338 double cosh = std::cos(heading);
339 double coshb = std::cos(heading + beta);
340 vpColVector motion(3);
341 motion[0] = -radius * sinh + radius * sinhb;
342 motion[1] = radius * cosh - radius * coshb;
343 motion[2] = beta;
344 return motion;
345 }
346 else {
347 // The robot is moving in straight line
348 vpColVector motion(3);
349 motion[0] = distance * std::cos(heading);
350 motion[1] = distance * std::sin(heading);
351 motion[2] = 0.;
352 return motion;
353 }
354 }
355
365 vpColVector move(const vpColVector &u, const vpColVector &x, const double &dt)
366 {
367 vpColVector motion = computeMotion(u, x, dt);
368 vpColVector newX = x + motion;
369 newX[2] = normalizeAngle(newX[2]);
370 return newX;
371 }
372private:
373 double m_w; // The length of the wheelbase.
374};
375
381{
382public:
391 vpLandmarkMeasurements(const double &x, const double &y, const double &range_std, const double &rel_angle_std)
392 : m_x(x)
393 , m_y(y)
394 , m_rngRange(range_std, 0., 4224)
395 , m_rngRelativeAngle(rel_angle_std, 0., 2112)
396 { }
397
405 {
406 vpColVector meas(2);
407 double dx = m_x - chi[0];
408 double dy = m_y - chi[1];
409 meas[0] = std::sqrt(dx * dx + dy * dy);
410 meas[1] = normalizeAngle(std::atan2(dy, dx) - chi[2]);
411 return meas;
412 }
413
422 {
423 double dx = m_x - pos[0];
424 double dy = m_y - pos[1];
425 double range = std::sqrt(dx * dx + dy * dy);
426 double orientation = normalizeAngle(std::atan2(dy, dx) - pos[2]);
427 vpColVector measurements(2);
428 measurements[0] = range;
429 measurements[1] = orientation;
430 return measurements;
431 }
432
441 {
442 vpColVector measurementsGT = measureGT(pos);
443 vpColVector measurementsNoisy = measurementsGT;
444 measurementsNoisy[0] += m_rngRange();
445 measurementsNoisy[1] += m_rngRelativeAngle();
446 measurementsNoisy[1] = normalizeAngle(measurementsNoisy[1]);
447 return measurementsNoisy;
448 }
449
450private:
451 double m_x; // The position along the x-axis of the landmark
452 double m_y; // The position along the y-axis of the landmark
453 vpGaussRand m_rngRange; // Noise simulator for the range measurement
454 vpGaussRand m_rngRelativeAngle; // Noise simulator for the relative angle measurement
455};
456
462{
463public:
469 vpLandmarksGrid(const std::vector<vpLandmarkMeasurements> &landmarks)
470 : m_landmarks(landmarks)
471 { }
472
480 {
481 unsigned int nbLandmarks = static_cast<unsigned int>(m_landmarks.size());
482 vpColVector measurements(2*nbLandmarks);
483 for (unsigned int i = 0; i < nbLandmarks; ++i) {
484 vpColVector landmarkMeas = m_landmarks[i].state_to_measurement(chi);
485 measurements[2*i] = landmarkMeas[0];
486 measurements[(2*i) + 1] = landmarkMeas[1];
487 }
488 return measurements;
489 }
490
500 {
501 unsigned int nbLandmarks = static_cast<unsigned int>(m_landmarks.size());
502 vpColVector measurements(2*nbLandmarks);
503 for (unsigned int i = 0; i < nbLandmarks; ++i) {
504 vpColVector landmarkMeas = m_landmarks[i].measureGT(pos);
505 measurements[2*i] = landmarkMeas[0];
506 measurements[(2*i) + 1] = landmarkMeas[1];
507 }
508 return measurements;
509 }
510
520 {
521 unsigned int nbLandmarks = static_cast<unsigned int>(m_landmarks.size());
522 vpColVector measurements(2*nbLandmarks);
523 for (unsigned int i = 0; i < nbLandmarks; ++i) {
524 vpColVector landmarkMeas = m_landmarks[i].measureWithNoise(pos);
525 measurements[2*i] = landmarkMeas[0];
526 measurements[(2*i) + 1] = landmarkMeas[1];
527 }
528 return measurements;
529 }
530
531private:
532 std::vector<vpLandmarkMeasurements> m_landmarks;
533};
534
535int main(const int argc, const char *argv[])
536{
537 bool opt_useDisplay = true;
538 bool opt_useUserInteraction = true;
539 for (int i = 1; i < argc; ++i) {
540 std::string arg(argv[i]);
541 if (arg == "-d") {
542 opt_useDisplay = false;
543 }
544 if (arg == "-c") {
545 opt_useUserInteraction = false;
546 }
547 else if ((arg == "-h") || (arg == "--help")) {
548 std::cout << "SYNOPSIS" << std::endl;
549 std::cout << " " << argv[0] << " [-d][-h]" << std::endl;
550 std::cout << std::endl << std::endl;
551 std::cout << "DETAILS" << std::endl;
552 std::cout << " -d" << std::endl;
553 std::cout << " Deactivate display." << std::endl;
554 std::cout << " -c" << std::endl;
555 std::cout << " Deactivate user interaction." << std::endl;
556 std::cout << std::endl;
557 std::cout << " -h, --help" << std::endl;
558 return 0;
559 }
560 }
561
562 const double dt = 0.1; // Period of 0.1s
563 const double step = 1.; // Number of update of the robot position between two UKF filtering
564 const double sigmaRange = 0.3; // Standard deviation of the range measurement: 0.3m
565 const double sigmaBearing = vpMath::rad(0.5); // Standard deviation of the bearing angle: 0.5deg
566 const double wheelbase = 0.5; // Wheelbase of 0.5m
567 const std::vector<vpLandmarkMeasurements> landmarks = { vpLandmarkMeasurements(5, 10, sigmaRange, sigmaBearing)
568 , vpLandmarkMeasurements(10, 5, sigmaRange, sigmaBearing)
569 , vpLandmarkMeasurements(15, 15, sigmaRange, sigmaBearing)
570 , vpLandmarkMeasurements(20, 5, sigmaRange, sigmaBearing)
571 , vpLandmarkMeasurements(0, 30, sigmaRange, sigmaBearing)
572 , vpLandmarkMeasurements(50, 30, sigmaRange, sigmaBearing)
573 , vpLandmarkMeasurements(40, 10, sigmaRange, sigmaBearing) }; // Vector of landmarks constituting the grid
574 const unsigned int nbLandmarks = static_cast<unsigned int>(landmarks.size()); // Number of landmarks constituting the grid
575 std::vector<vpColVector> cmds = generateCommands();
576 const unsigned int nbCmds = static_cast<unsigned int>(cmds.size());
577
578 // Initialize the attributes of the UKF
579 std::shared_ptr<vpUKSigmaDrawerAbstract> drawer = std::make_shared<vpUKSigmaDrawerMerwe>(3, 0.1, 2., 0, stateResidual, stateAdd);
580
581 vpMatrix R1landmark(2, 2, 0.); // The covariance of the noise introduced by the measurement with 1 landmark
582 R1landmark[0][0] = sigmaRange*sigmaRange;
583 R1landmark[1][1] = sigmaBearing*sigmaBearing;
584 vpMatrix R(2*nbLandmarks, 2 * nbLandmarks);
585 for (unsigned int i = 0; i < nbLandmarks; ++i) {
586 R.insert(R1landmark, 2*i, 2*i);
587 }
588
589 const double processVariance = 0.0001;
590 vpMatrix Q; // The covariance of the process
591 Q.eye(3);
592 Q = Q * processVariance;
593
594 vpMatrix P0(3, 3); // The initial guess of the process covariance
595 P0.eye(3);
596 P0[0][0] = 0.1;
597 P0[1][1] = 0.1;
598 P0[2][2] = 0.05;
599
600 vpColVector X0(3);
601 X0[0] = 2.; // x = 2m
602 X0[1] = 6.; // y = 6m
603 X0[2] = 0.3; // robot orientation = 0.3 rad
604
606 vpLandmarksGrid grid(landmarks);
607 vpBicycleModel robot(wheelbase);
608 using std::placeholders::_1;
609 using std::placeholders::_2;
610 using std::placeholders::_3;
613
614 // Initialize the UKF
615 vpUnscentedKalman ukf(Q, R, drawer, f, h);
616 ukf.init(X0, P0);
617 ukf.setCommandStateFunction(bx);
618 ukf.setMeasurementMeanFunction(measurementMean);
619 ukf.setMeasurementResidualFunction(measurementResidual);
620 ukf.setStateAddFunction(stateAdd);
621 ukf.setStateMeanFunction(stateMean);
622 ukf.setStateResidualFunction(stateResidual);
623
624#ifdef VISP_HAVE_DISPLAY
625 vpPlot *plot = nullptr;
626 if (opt_useDisplay) {
627 // Initialize the plot
628 plot = new vpPlot(1);
629 plot->initGraph(0, 2);
630 plot->setTitle(0, "Position of the robot");
631 plot->setUnitX(0, "Position along x(m)");
632 plot->setUnitY(0, "Position along y (m)");
633 plot->setLegend(0, 0, "GT");
634 plot->setLegend(0, 1, "Filtered");
635 }
636#else
637 (void)opt_useDisplay;
638#endif
639
640 // Initialize the simulation
642
643 for (unsigned int i = 0; i < nbCmds; ++i) {
644 robot_pos = robot.move(cmds[i], robot_pos, dt / step);
645 if (i % static_cast<int>(step) == 0) {
646 // Perform the measurement
647 vpColVector z = grid.measureWithNoise(robot_pos);
648
649 // Use the UKF to filter the measurement
650 ukf.filter(z, dt, cmds[i]);
651
652#ifdef VISP_HAVE_DISPLAY
653 if (opt_useDisplay) {
654 // Plot the filtered state
655 vpColVector Xest = ukf.getXest();
656 plot->plot(0, 1, Xest[0], Xest[1]);
657 }
658#endif
659 }
660
661#ifdef VISP_HAVE_DISPLAY
662 if (opt_useDisplay) {
663 // Plot the ground truth
664 plot->plot(0, 0, robot_pos[0], robot_pos[1]);
665 }
666#endif
667 }
668
669 if (opt_useUserInteraction) {
670 std::cout << "Press Enter to quit..." << std::endl;
671 std::cin.get();
672 }
673
674#ifdef VISP_HAVE_DISPLAY
675 if (opt_useDisplay) {
676 delete plot;
677 }
678#endif
679
680 vpColVector finalError = grid.state_to_measurement(ukf.getXest()) - grid.measureGT(robot_pos);
681 const double maxError = 0.3;
682 if (finalError.frobeniusNorm() > maxError) {
683 std::cerr << "Error: max tolerated error = " << maxError << ", final error = " << finalError.frobeniusNorm() << std::endl;
684 return -1;
685 }
686 return 0;
687}
688#else
689int main()
690{
691 std::cout << "This example is only available if you compile ViSP in C++11 standard or higher." << std::endl;
692 return 0;
693}
694#endif
unsigned int size() const
Return the number of elements of the 2D array.
Definition vpArray2D.h:435
Class that approximates a 4-wheel robot using a bicycle model.
vpColVector computeMotion(const vpColVector &u, const vpColVector &x, const double &dt)
Models the effect of the command on the state model.
vpBicycleModel(const double &w)
Construct a new vpBicycleModel object.
vpColVector move(const vpColVector &u, const vpColVector &x, const double &dt)
Move the robot according to its current position and the commands.
Implementation of column vector and the associated operations.
double frobeniusNorm() const
Class for generating random number with normal probability density.
Class that permits to convert the position + heading of the 4-wheel robot into measurements from a la...
vpColVector measureGT(const vpColVector &pos)
Perfect measurement of the range and relative orientation of the robot located at pos.
vpColVector state_to_measurement(const vpColVector &chi)
Convert the prior of the UKF into the measurement space.
vpLandmarkMeasurements(const double &x, const double &y, const double &range_std, const double &rel_angle_std)
Construct a new vpLandmarkMeasurements object.
vpColVector measureWithNoise(const vpColVector &pos)
Noisy measurement of the range and relative orientation that correspond to pos.
Class that represent a grid of landmarks that measure the distance and relative orientation of the 4-...
vpColVector measureGT(const vpColVector &pos)
Perfect measurement from each landmark of the range and relative orientation of the robot located at ...
vpLandmarksGrid(const std::vector< vpLandmarkMeasurements > &landmarks)
Construct a new vpLandmarksGrid object.
vpColVector measureWithNoise(const vpColVector &pos)
Noisy measurement from each landmark of the range and relative orientation that correspond to pos.
vpColVector state_to_measurement(const vpColVector &chi)
Convert the prior of the UKF into the measurement space.
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
std::function< vpColVector(const vpColVector &, const vpColVector &, const double &)> vpCommandStateFunction
Command model function, which projects effect of the command on the state, when the effect of the com...
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)