Visual Servoing Platform  version 3.6.1 under development (2024-10-02)
testParticleFilter.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  * Description:
31  * Test Particle Filter functionalities.
32  */
33 
40 #include <visp3/core/vpConfig.h>
41 
42 #if defined(VISP_HAVE_CATCH2) && (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11)
43 #include <limits>
44 #include <vector>
45 
46 #include <visp3/core/vpColVector.h>
47 #include <visp3/core/vpImagePoint.h>
48 #include <visp3/core/vpParticleFilter.h>
49 #include <visp3/core/vpUniRand.h>
50 
51 #ifdef VISP_HAVE_DISPLAY
52 #include <visp3/core/vpImage.h>
53 #include <visp3/gui/vpDisplayFactory.h>
54 #endif
55 
56 #define CATCH_CONFIG_RUNNER
57 #include <catch.hpp>
58 
59 #ifdef ENABLE_VISP_NAMESPACE
60 using namespace VISP_NAMESPACE_NAME;
61 #endif
62 
63 namespace
64 {
65 bool opt_display = false;
66 
71 class vpParabolaModel
72 {
73 public:
74  inline vpParabolaModel(const unsigned int &degree, const unsigned int &height, const unsigned int &width)
75  : m_degree(degree)
76  , m_height(height)
77  , m_width(width)
78  , m_coeffs(degree + 1, 0.)
79  { }
80 
89  inline vpParabolaModel(const vpColVector &coeffs, const unsigned int &height, const unsigned int &width)
90  : m_degree(coeffs.size() - 1)
91  , m_height(height)
92  , m_width(width)
93  , m_coeffs(coeffs)
94  { }
95 
104  inline vpParabolaModel(const vpMatrix &coeffs, const unsigned int &height, const unsigned int &width)
105  : m_degree(coeffs.getRows() - 1)
106  , m_height(height)
107  , m_width(width)
108  , m_coeffs(coeffs.getCol(0))
109  { }
110 
111  inline vpParabolaModel(const vpParabolaModel &other)
112  {
113  *this = other;
114  }
115 
122  inline double eval(const double &u) const
123  {
124  double normalizedU = u / m_width;
125  double v = 0.;
126  for (unsigned int i = 0; i <= m_degree; ++i) {
127  v += m_coeffs[i] * std::pow(normalizedU, i);
128  }
129  v *= m_height;
130  return v;
131  }
132 
138  inline vpColVector toVpColVector() const
139  {
140  return m_coeffs;
141  }
142 
143  inline vpParabolaModel &operator=(const vpParabolaModel &other)
144  {
145  m_degree = other.m_degree;
146  m_height = other.m_height;
147  m_width = other.m_width;
148  m_coeffs = other.m_coeffs;
149  return *this;
150  }
151 
166  static void fillSystem(const unsigned int &degree, const unsigned int &height, const unsigned int&width, const std::vector< vpImagePoint> &pts, vpMatrix &A, vpMatrix &b)
167  {
168  const unsigned int nbPts = static_cast<unsigned int>(pts.size());
169  const unsigned int nbCoeffs = degree + 1;
170  std::vector<vpImagePoint> normalizedPts;
171  for (const auto &pt: pts) {
172  normalizedPts.push_back(vpImagePoint(pt.get_i() / height, pt.get_j() / width));
173  }
174  A.resize(nbPts, nbCoeffs, 1.);
175  b.resize(nbPts, 1);
176  for (unsigned int i = 0; i < nbPts; ++i) {
177  double u = normalizedPts[i].get_u();
178  double v = normalizedPts[i].get_v();
179  for (unsigned int j = 0; j < nbCoeffs; ++j) {
180  A[i][j] = std::pow(u, j);
181  }
182  b[i][0] = v;
183  }
184  }
185 
186 #ifdef VISP_HAVE_DISPLAY
194  template<typename T>
195  void display(const vpImage<T> &I, const vpColor &color, const std::string &legend,
196  const unsigned int &vertPosLegend, const unsigned int &horPosLegend)
197  {
198  unsigned int width = I.getWidth();
199  for (unsigned int u = 0; u < width; ++u) {
200  int v = static_cast<int>(eval(u));
201  vpDisplay::displayPoint(I, v, u, color, 1);
202  vpDisplay::displayText(I, vertPosLegend, horPosLegend, legend, color);
203  }
204  }
205 #endif
206 
207 private:
208  unsigned int m_degree;
209  unsigned int m_height;
210  unsigned int m_width;
211  vpColVector m_coeffs;
212 };
213 
224 vpColVector computeABC(const double &x0, const double &y0, const double &x1, const double &y1)
225 {
226  double b = (y1 - y0)/(-0.5*(x1 * x1/x0) + x1 -0.5 * x0);
227  double a = -b / (2. * x0);
228  double c = y0 - 0.5 * b * x0;
229  return vpColVector({ c, b, a });
230 }
231 
242 vpColVector computeABCD(const double &x0, const double &y0, const double &x1, const double &y1)
243 {
244  double factorA = -2. / (3. * (x1 + x0));
245  double factorC = -1. * ((-2. * std::pow(x0, 2))/(x1 + x0) + 2 * x0);
246  double b = (y1 - y0)/(factorA * (std::pow(x1, 3) - std::pow(x0, 3)) + (std::pow(x1, 2) - std::pow(x0, 2)) + (x1 - x0) * factorC);
247  double a = factorA * b;
248  double c = factorC * b;
249  double d = y0-(a * std::pow(x0, 3) + b * std::pow(x0, 2) + c * x0);
250  return vpColVector({ d, c, b, a });
251 }
252 
260 double computeY(const double &x, const vpColVector &coeffs)
261 {
262  double y = 0.;
263  unsigned int nbCoeffs = coeffs.size();
264  for (unsigned int i = 0; i < nbCoeffs; ++i) {
265  y += coeffs[i] * std::pow(x, i);
266  }
267  return y;
268 }
269 
279 std::vector<vpImagePoint> generateSimulatedImage(const double &xmin, const double &xmax, const double &step, const vpColVector &coeffs)
280 {
281  std::vector<vpImagePoint> points;
282  for (double x = xmin; x <= xmax; x += step) {
283  double y = computeY(x, coeffs);
284  vpImagePoint pt(y, x);
285  points.push_back(pt);
286  }
287  return points;
288 }
289 
290 #ifdef VISP_HAVE_DISPLAY
291 template<typename T>
292 void displayGeneratedImage(const vpImage<T> &I, const std::vector<vpImagePoint> &pts, const vpColor &color,
293  const std::string &legend, const unsigned int &vertOffset, const unsigned int &horOffset)
294 {
295  unsigned int nbPts = static_cast<unsigned int>(pts.size());
296  for (unsigned int i = 1; i < nbPts; ++i) {
297  vpDisplay::displayPoint(I, pts[i], color, 1);
298  }
299  vpDisplay::displayText(I, vertOffset, horOffset, legend, color);
300 }
301 #endif
302 
312 vpParabolaModel computeInitialGuess(const std::vector<vpImagePoint> &pts, const unsigned int &degree, const unsigned int&height, const unsigned int &width)
313 {
314  vpMatrix A; // The matrix that contains the u^2, u and 1s
315  vpMatrix X; // The matrix we want to estimate, that contains the a, b and c coefficients.
316  vpMatrix b; // The matrix that contains the v values
317 
318  // Fill the matrices that form the system we want to solve
319  vpParabolaModel::fillSystem(degree, height, width, pts, A, b);
320 
321  // Compute the parabola coefficients using the least-mean-square method.
322  X = A.pseudoInverse() * b;
323  vpParabolaModel model(X, height, width);
324  return model;
325 }
326 
334 double evaluate(const std::vector<vpImagePoint> &pts, const vpParabolaModel &model)
335 {
336  double rmse = 0.;
337  size_t sizePts = pts.size();
338  for (size_t i = 0; i < sizePts; ++i) {
339  const vpImagePoint &pt = pts[i];
340  double u = pt.get_u();
341  double v = pt.get_v();
342  double v_model = model.eval(u);
343  double error = v - v_model;
344  double squareError = error * error;
345  rmse += squareError;
346  }
347  rmse = std::sqrt(rmse / static_cast<double>(pts.size()));
348  return rmse;
349 }
350 
356 vpColVector fx(const vpColVector &coeffs, const double &/*dt*/)
357 {
358  vpColVector updatedCoeffs = coeffs;
359  return updatedCoeffs;
360 }
361 
362 class vpAverageFunctor
363 {
364 public:
365  vpAverageFunctor(const unsigned int &degree, const unsigned int &height, const unsigned int &width)
366  : m_degree(degree)
367  , m_height(height)
368  , m_width(width)
369  { }
370 
371  vpColVector averagePolynomials(const std::vector<vpColVector> &particles, const std::vector<double> &weights, const vpParticleFilter<std::vector<vpImagePoint>>::vpStateAddFunction &)
372  {
373  const unsigned int nbParticles = static_cast<unsigned int>(particles.size());
374  const double nbParticlesAsDOuble = static_cast<double>(nbParticles);
375  const double sumWeight = std::accumulate(weights.begin(), weights.end(), 0.);
376  const double nbPointsForAverage = 10. * nbParticlesAsDOuble;
377  std::vector<vpImagePoint> initPoints;
378  for (unsigned int i = 0; i < nbParticles; ++i) {
379  double nbPoints = std::floor(weights[i] * nbPointsForAverage / sumWeight);
380  if (nbPoints > 1.) {
381  vpParabolaModel curve(particles[i], m_height, m_width);
382  double widthAsDouble = static_cast<double>(m_width);
383  double step = widthAsDouble / (nbPoints - 1.);
384  for (double u = 0.; u < widthAsDouble; u += step) {
385  double v = curve.eval(u);
386  vpImagePoint pt(v, u);
387  initPoints.push_back(pt);
388  }
389  }
390  else if (nbPoints == 1.) {
391  vpParabolaModel curve(particles[i], m_height, m_width);
392  double u = static_cast<double>(m_width) / 2.;
393  double v = curve.eval(u);
394  vpImagePoint pt(v, u);
395  initPoints.push_back(pt);
396  }
397  }
398  vpMatrix A, X, b;
399  vpParabolaModel::fillSystem(m_degree, m_height, m_width, initPoints, A, b);
400  X = A.pseudoInverse() * b;
401  return vpParabolaModel(X, m_height, m_width).toVpColVector();
402  }
403 
404 private:
405  unsigned int m_degree;
406  unsigned int m_height;
407  unsigned int m_width;
408 };
409 
410 class vpLikelihoodFunctor
411 {
412 public:
420  vpLikelihoodFunctor(const double &stdev, const unsigned int &height, const unsigned int &width)
421  : m_height(height)
422  , m_width(width)
423  {
424  double sigmaDistanceSquared = stdev * stdev;
425  m_constantDenominator = 1. / std::sqrt(2. * M_PI * sigmaDistanceSquared);
426  m_constantExpDenominator = -1. / (2. * sigmaDistanceSquared);
427  }
428 
430 
442  double likelihood(const vpColVector &coeffs, const std::vector<vpImagePoint> &meas)
443  {
444  double likelihood = 0.;
445  unsigned int nbPoints = static_cast<unsigned int>(meas.size());
446  vpParabolaModel model(coeffs, m_height, m_width);
447  vpColVector residuals(nbPoints);
448  double rmse = evaluate(meas, model);
449  likelihood = std::exp(rmse * m_constantExpDenominator) * m_constantDenominator;
450  likelihood = std::min(likelihood, 1.0); // Clamp to have likelihood <= 1.
451  likelihood = std::max(likelihood, 0.); // Clamp to have likelihood >= 0.
452  return likelihood;
453  }
455 private:
456  double m_constantDenominator;
457  double m_constantExpDenominator;
458  unsigned int m_height;
459  unsigned int m_width;
460 };
461 }
462 
463 TEST_CASE("2nd-degree", "[vpParticleFilter][Polynomial interpolation]")
464 {
466  const unsigned int width = 600;
467  const unsigned int height = 400;
468  const unsigned int degree = 2;
469  const unsigned int nbInitPoints = 10;
470  const uint64_t seedCurve = 4224;
471  const uint64_t seedInitPoints = 2112;
472  const unsigned int nbTestRepet = 10;
473  const unsigned int nbWarmUpIter = 10;
474  const unsigned int nbEvalIter = 20;
475  const double dt = 0.040;
476  const int32_t seedShuffle = 4221;
477 
479  // The maximum amplitude for the likelihood compute.
480  // A particle whose "distance" with the measurements is greater than this value has a likelihood of 0
481  const double ampliMaxLikelihood = 16.;
482  const double sigmaLikelihood = ampliMaxLikelihood / 3.; //:< The corresponding standard deviation
483  const unsigned int nbParticles = 300;
484  const double ratioAmpliMax(0.25);
485  const long seedPF = 4221;
486  const int nbThreads = 1; //<! Number of threads to use for the PF
487  vpUniRand rngCurvePoints(seedCurve);
488  vpUniRand rngInitPoints(seedInitPoints);
489 
490  SECTION("Noise-free", "The init points are directly extracted from the curve points, without any additional noise")
491  {
492  const double maxToleratedError = 5.;
493  double x0 = rngCurvePoints.uniform(0., width);
494  double x1 = rngCurvePoints.uniform(0., width);
495  double y0 = rngCurvePoints.uniform(0., height);
496  double y1 = rngCurvePoints.uniform(0., height);
497  vpColVector coeffs = computeABC(x0, y0, x1, y1);
498  std::vector<vpImagePoint> curvePoints = generateSimulatedImage(0, width, 1., coeffs);
499 
500 #ifdef VISP_HAVE_DISPLAY
501  vpImage<vpRGBa> I(height, width);
502  std::shared_ptr<vpDisplay> pDisplay;
503  if (opt_display) {
504  pDisplay = vpDisplayFactory::createDisplay(I);
505  }
506 #endif
507 
508  for (unsigned int iter = 0; iter < nbTestRepet; ++iter) {
509  // Randomly select the initialization points
510  std::vector<vpImagePoint> suffledVector = vpUniRand::shuffleVector(curvePoints, seedShuffle);
511  std::vector<vpImagePoint> initPoints;
512  for (unsigned int j = 0; j < nbInitPoints; ++j) {
513  initPoints.push_back(suffledVector[j]);
514  }
515 
516  // Compute the initial model
517  vpParabolaModel modelInitial = computeInitialGuess(initPoints, degree, height, width);
518  vpColVector X0 = modelInitial.toVpColVector();
519 
520  // Initialize the Particle Filter
521  std::vector<double> stdevsPF;
522  for (unsigned int i = 0; i < degree + 1; ++i) {
523  stdevsPF.push_back(ratioAmpliMax * X0[0] / 3.);
524  }
525  vpParticleFilter<std::vector<vpImagePoint>>::vpProcessFunction processFunc = fx;
526  vpLikelihoodFunctor likelihoodFtor(sigmaLikelihood, height, width);
527  using std::placeholders::_1;
528  using std::placeholders::_2;
529  vpParticleFilter<std::vector<vpImagePoint>>::vpLikelihoodFunction likelihoodFunc = std::bind(&vpLikelihoodFunctor::likelihood, &likelihoodFtor, _1, _2);
530  vpParticleFilter<std::vector<vpImagePoint>>::vpResamplingConditionFunction checkResamplingFunc = vpParticleFilter<std::vector<vpImagePoint>>::simpleResamplingCheck;
531  vpParticleFilter<std::vector<vpImagePoint>>::vpResamplingFunction resamplingFunc = vpParticleFilter<std::vector<vpImagePoint>>::simpleImportanceResampling;
532  vpAverageFunctor averageCpter(degree, height, width);
533  using std::placeholders::_3;
534  vpParticleFilter<std::vector<vpImagePoint>>::vpFilterFunction meanFunc = std::bind(&vpAverageFunctor::averagePolynomials, &averageCpter, _1, _2, _3);
535  vpParticleFilter<std::vector<vpImagePoint>> filter(nbParticles, stdevsPF, seedPF, nbThreads);
536  filter.init(X0, processFunc, likelihoodFunc, checkResamplingFunc, resamplingFunc, meanFunc);
537 
538  for (unsigned int i = 0; i < nbWarmUpIter; ++i) {
539  filter.filter(curvePoints, dt);
540  }
541 
542  double meanError = 0.;
543  for (unsigned int i = 0; i < nbEvalIter; ++i) {
544  filter.filter(curvePoints, dt);
545  vpColVector Xest = filter.computeFilteredState();
546  vpParabolaModel model(Xest, height, width);
547  double rmse = evaluate(curvePoints, model);
548  meanError += rmse;
549 
550 #ifdef VISP_HAVE_DISPLAY
551  if (opt_display) {
553  displayGeneratedImage(I, curvePoints, vpColor::red, "GT", 20, 20);
554  model.display(I, vpColor::blue, "Model", 40, 20);
555  vpDisplay::flush(I);
557  }
558 #endif
559  }
560  meanError /= static_cast<double>(nbEvalIter);
561  std::cout << "Mean(rmse) = " << meanError << std::endl;
562  CHECK(meanError <= maxToleratedError);
563  }
564  }
565 
566  SECTION("Noisy", "Noise is added to the init points")
567  {
568  const double maxToleratedError = 12.;
569  double x0 = rngCurvePoints.uniform(0., width);
570  double x1 = rngCurvePoints.uniform(0., width);
571  double y0 = rngCurvePoints.uniform(0., height);
572  double y1 = rngCurvePoints.uniform(0., height);
573  vpColVector coeffs = computeABC(x0, y0, x1, y1);
574  std::vector<vpImagePoint> curvePoints = generateSimulatedImage(0, width, 1., coeffs);
575 
576 #ifdef VISP_HAVE_DISPLAY
577  vpImage<vpRGBa> I(height, width);
578  std::shared_ptr<vpDisplay> pDisplay;
579  if (opt_display) {
580  pDisplay = vpDisplayFactory::createDisplay(I);
581  }
582 #endif
583 
584  const double ampliMaxInitNoise = 24.;
585  const double stdevInitNoise = ampliMaxInitNoise / 3.;
586  vpGaussRand rngInitNoise(stdevInitNoise, 0., seedInitPoints);
587 
588  for (unsigned int iter = 0; iter < nbTestRepet; ++iter) {
589  // Randomly select the initialization points
590  std::vector<vpImagePoint> suffledVector = vpUniRand::shuffleVector(curvePoints, seedShuffle);
591  std::vector<vpImagePoint> initPoints;
592  for (unsigned int j = 0; j < nbInitPoints; ++j) {
593  vpImagePoint noisyPt(suffledVector[j].get_i() + rngInitNoise(), suffledVector[j].get_j() + rngInitNoise());
594  initPoints.push_back(noisyPt);
595  }
596 
597  // Compute the initial model
598  vpParabolaModel modelInitial = computeInitialGuess(initPoints, degree, height, width);
599  vpColVector X0 = modelInitial.toVpColVector();
600 
601  // Initialize the Particle Filter
602  std::vector<double> stdevsPF;
603  for (unsigned int i = 0; i < degree + 1; ++i) {
604  stdevsPF.push_back(ratioAmpliMax * X0[0] / 3.);
605  }
606  vpParticleFilter<std::vector<vpImagePoint>>::vpProcessFunction processFunc = fx;
607  vpLikelihoodFunctor likelihoodFtor(sigmaLikelihood, height, width);
608  using std::placeholders::_1;
609  using std::placeholders::_2;
610  vpParticleFilter<std::vector<vpImagePoint>>::vpLikelihoodFunction likelihoodFunc = std::bind(&vpLikelihoodFunctor::likelihood, &likelihoodFtor, _1, _2);
611  vpParticleFilter<std::vector<vpImagePoint>>::vpResamplingConditionFunction checkResamplingFunc = vpParticleFilter<std::vector<vpImagePoint>>::simpleResamplingCheck;
612  vpParticleFilter<std::vector<vpImagePoint>>::vpResamplingFunction resamplingFunc = vpParticleFilter<std::vector<vpImagePoint>>::simpleImportanceResampling;
613  vpAverageFunctor averageCpter(degree, height, width);
614  using std::placeholders::_3;
615  vpParticleFilter<std::vector<vpImagePoint>>::vpFilterFunction meanFunc = std::bind(&vpAverageFunctor::averagePolynomials, &averageCpter, _1, _2, _3);
616  vpParticleFilter<std::vector<vpImagePoint>> filter(nbParticles, stdevsPF, seedPF, nbThreads);
617  filter.init(X0, processFunc, likelihoodFunc, checkResamplingFunc, resamplingFunc, meanFunc);
618 
619  for (unsigned int i = 0; i < nbWarmUpIter; ++i) {
620  filter.filter(curvePoints, dt);
621  }
622 
623  double meanError = 0.;
624  for (unsigned int i = 0; i < nbEvalIter; ++i) {
625  filter.filter(curvePoints, dt);
626  vpColVector Xest = filter.computeFilteredState();
627  vpParabolaModel model(Xest, height, width);
628  double rmse = evaluate(curvePoints, model);
629  meanError += rmse;
630 
631 #ifdef VISP_HAVE_DISPLAY
632  if (opt_display) {
634  displayGeneratedImage(I, curvePoints, vpColor::red, "GT", 20, 20);
635  model.display(I, vpColor::blue, "Model", 40, 20);
636  vpDisplay::flush(I);
638  }
639 #endif
640  }
641  meanError /= static_cast<double>(nbEvalIter);
642  std::cout << "Mean(rmse) = " << meanError << std::endl;
643  CHECK(meanError <= maxToleratedError);
644  }
645  }
646 }
647 
648 TEST_CASE("3rd-degree", "[vpParticleFilter][Polynomial interpolation]")
649 {
651  const unsigned int width = 600;
652  const unsigned int height = 400;
653  const unsigned int degree = 3;
654  const unsigned int nbInitPoints = 10;
655  const uint64_t seedCurve = 4224;
656  const uint64_t seedInitPoints = 2112;
657  const unsigned int nbTestRepet = 10;
658  const unsigned int nbWarmUpIter = 10;
659  const unsigned int nbEvalIter = 20;
660  const double dt = 0.040;
661  const int32_t seedShuffle = 4221;
662 
664  // The maximum amplitude for the likelihood compute.
665  // A particle whose "distance" with the measurements is greater than this value has a likelihood of 0
666  const double ampliMaxLikelihood = 6.;
667  const double sigmaLikelihood = ampliMaxLikelihood / 3.; //:< The corresponding standard deviation
668  const unsigned int nbParticles = 300;
669  const double ratioAmpliMax(0.21);
670  const long seedPF = 4221;
671  const int nbThreads = 1; //<! Number of threads to use for the PF
672  vpUniRand rngCurvePoints(seedCurve);
673  vpUniRand rngInitPoints(seedInitPoints);
674 
675  SECTION("Noise-free", "The init points are directly extracted from the curve points, without any additional noise")
676  {
677  const double maxToleratedError = 10.;
678  double x0 = rngCurvePoints.uniform(0., width);
679  double x1 = rngCurvePoints.uniform(0., width);
680  double y0 = rngCurvePoints.uniform(0., height);
681  double y1 = rngCurvePoints.uniform(0., height);
682  vpColVector coeffs = computeABCD(x0, y0, x1, y1);
683  std::vector<vpImagePoint> curvePoints = generateSimulatedImage(0, width, 1., coeffs);
684 
685 #ifdef VISP_HAVE_DISPLAY
686  vpImage<vpRGBa> I(height, width);
687  std::shared_ptr<vpDisplay> pDisplay;
688  if (opt_display) {
689  pDisplay = vpDisplayFactory::createDisplay(I);
690  }
691 #endif
692 
693  for (unsigned int iter = 0; iter < nbTestRepet; ++iter) {
694  // Randomly select the initialization points
695  std::vector<vpImagePoint> suffledVector = vpUniRand::shuffleVector(curvePoints, seedShuffle);
696  std::vector<vpImagePoint> initPoints;
697  for (unsigned int j = 0; j < nbInitPoints; ++j) {
698  initPoints.push_back(suffledVector[j]);
699  }
700 
701  // Compute the initial model
702  vpParabolaModel modelInitial = computeInitialGuess(initPoints, degree, height, width);
703  vpColVector X0 = modelInitial.toVpColVector();
704 
705  // Initialize the Particle Filter
706  std::vector<double> stdevsPF;
707  for (unsigned int i = 0; i < degree + 1; ++i) {
708  stdevsPF.push_back(ratioAmpliMax * std::pow(0.1, i) * X0[0] / 3.);
709  }
710  vpParticleFilter<std::vector<vpImagePoint>>::vpProcessFunction processFunc = fx;
711  vpLikelihoodFunctor likelihoodFtor(sigmaLikelihood, height, width);
712  using std::placeholders::_1;
713  using std::placeholders::_2;
714  vpParticleFilter<std::vector<vpImagePoint>>::vpLikelihoodFunction likelihoodFunc = std::bind(&vpLikelihoodFunctor::likelihood, &likelihoodFtor, _1, _2);
715  vpParticleFilter<std::vector<vpImagePoint>>::vpResamplingConditionFunction checkResamplingFunc = vpParticleFilter<std::vector<vpImagePoint>>::simpleResamplingCheck;
716  vpParticleFilter<std::vector<vpImagePoint>>::vpResamplingFunction resamplingFunc = vpParticleFilter<std::vector<vpImagePoint>>::simpleImportanceResampling;
717  vpAverageFunctor averageCpter(degree, height, width);
718  using std::placeholders::_3;
719  vpParticleFilter<std::vector<vpImagePoint>>::vpFilterFunction meanFunc = std::bind(&vpAverageFunctor::averagePolynomials, &averageCpter, _1, _2, _3);
720  vpParticleFilter<std::vector<vpImagePoint>> filter(nbParticles, stdevsPF, seedPF, nbThreads);
721  filter.init(X0, processFunc, likelihoodFunc, checkResamplingFunc, resamplingFunc, meanFunc);
722 
723  for (unsigned int i = 0; i < nbWarmUpIter; ++i) {
724  filter.filter(curvePoints, dt);
725  }
726 
727  double meanError = 0.;
728  for (unsigned int i = 0; i < nbEvalIter; ++i) {
729  filter.filter(curvePoints, dt);
730  vpColVector Xest = filter.computeFilteredState();
731  vpParabolaModel model(Xest, height, width);
732  double rmse = evaluate(curvePoints, model);
733  meanError += rmse;
734 
735 #ifdef VISP_HAVE_DISPLAY
736  if (opt_display) {
738  displayGeneratedImage(I, curvePoints, vpColor::red, "GT", 20, 20);
739  model.display(I, vpColor::blue, "Model", 40, 20);
740  vpDisplay::flush(I);
742  }
743 #endif
744  }
745  meanError /= static_cast<double>(nbEvalIter);
746  std::cout << "Mean(rmse) = " << meanError << std::endl;
747  CHECK(meanError <= maxToleratedError);
748  }
749  }
750 
751 
752 
753  SECTION("Noisy", "Noise is added to the init points")
754  {
755  const double maxToleratedError = 17.;
756  double x0 = rngCurvePoints.uniform(0., width);
757  double x1 = rngCurvePoints.uniform(0., width);
758  double y0 = rngCurvePoints.uniform(0., height);
759  double y1 = rngCurvePoints.uniform(0., height);
760  vpColVector coeffs = computeABCD(x0, y0, x1, y1);
761  std::vector<vpImagePoint> curvePoints = generateSimulatedImage(0, width, 1., coeffs);
762 
763 #ifdef VISP_HAVE_DISPLAY
764  vpImage<vpRGBa> I(height, width);
765  std::shared_ptr<vpDisplay> pDisplay;
766  if (opt_display) {
767  pDisplay = vpDisplayFactory::createDisplay(I);
768  }
769 #endif
770 
771  const double ampliMaxInitNoise = 1.5;
772  const double stdevInitNoise = ampliMaxInitNoise / 3.;
773  vpGaussRand rngInitNoise(stdevInitNoise, 0., seedInitPoints);
774 
775  for (unsigned int iter = 0; iter < nbTestRepet; ++iter) {
776  // Randomly select the initialization points
777  std::vector<vpImagePoint> suffledVector = vpUniRand::shuffleVector(curvePoints, seedShuffle);
778  std::vector<vpImagePoint> initPoints;
779  for (unsigned int j = 0; j < nbInitPoints * 4; ++j) {
780  vpImagePoint noisyPt(suffledVector[j].get_i() + rngInitNoise(), suffledVector[j].get_j() + rngInitNoise());
781  initPoints.push_back(noisyPt);
782  }
783 
784  // Compute the initial model
785  vpParabolaModel modelInitial = computeInitialGuess(initPoints, degree, height, width);
786  vpColVector X0 = modelInitial.toVpColVector();
787 
788  // Initialize the Particle Filter
789  std::vector<double> stdevsPF;
790  for (unsigned int i = 0; i < degree + 1; ++i) {
791  stdevsPF.push_back(ratioAmpliMax * std::pow(.05, i) * X0[0] / 3.);
792  }
793  vpParticleFilter<std::vector<vpImagePoint>>::vpProcessFunction processFunc = fx;
794  vpLikelihoodFunctor likelihoodFtor(sigmaLikelihood * 2., height, width);
795  using std::placeholders::_1;
796  using std::placeholders::_2;
797  vpParticleFilter<std::vector<vpImagePoint>>::vpLikelihoodFunction likelihoodFunc = std::bind(&vpLikelihoodFunctor::likelihood, &likelihoodFtor, _1, _2);
798  vpParticleFilter<std::vector<vpImagePoint>>::vpResamplingConditionFunction checkResamplingFunc = vpParticleFilter<std::vector<vpImagePoint>>::simpleResamplingCheck;
799  vpParticleFilter<std::vector<vpImagePoint>>::vpResamplingFunction resamplingFunc = vpParticleFilter<std::vector<vpImagePoint>>::simpleImportanceResampling;
800  vpAverageFunctor averageCpter(degree, height, width);
801  using std::placeholders::_3;
802  vpParticleFilter<std::vector<vpImagePoint>>::vpFilterFunction meanFunc = std::bind(&vpAverageFunctor::averagePolynomials, &averageCpter, _1, _2, _3);
803  vpParticleFilter<std::vector<vpImagePoint>> filter(nbParticles, stdevsPF, seedPF, nbThreads);
804  filter.init(X0, processFunc, likelihoodFunc, checkResamplingFunc, resamplingFunc, meanFunc);
805 
806  for (unsigned int i = 0; i < nbWarmUpIter * 5; ++i) {
807  filter.filter(curvePoints, dt);
808  }
809 
810  double meanError = 0.;
811  for (unsigned int i = 0; i < nbEvalIter; ++i) {
812  filter.filter(curvePoints, dt);
813  vpColVector Xest = filter.computeFilteredState();
814  vpParabolaModel model(Xest, height, width);
815  double rmse = evaluate(curvePoints, model);
816  meanError += rmse;
817 
818 #ifdef VISP_HAVE_DISPLAY
819  if (opt_display) {
821  displayGeneratedImage(I, curvePoints, vpColor::red, "GT", 20, 20);
822  model.display(I, vpColor::blue, "Model", 40, 20);
823  vpDisplay::flush(I);
825  }
826 #endif
827  }
828  meanError /= static_cast<double>(nbEvalIter);
829  std::cout << "Mean(rmse) = " << meanError << std::endl;
830  CHECK(meanError <= maxToleratedError);
831  }
832  }
833 }
834 
835 int main(int argc, char *argv[])
836 {
837  Catch::Session session; // There must be exactly one instance
838 
839  // Build a new parser on top of Catch's
840  using namespace Catch::clara;
841  auto cli = session.cli() // Get Catch's composite command line parser
842  | Opt(opt_display) // bind variable to a new option, with a hint string
843  ["--display"] // the option names it will respond to
844  ("Activate debug display"); // description string for the help output
845 
846  // Now pass the new composite back to Catch so it uses that
847  session.cli(cli);
848 
849  // Let Catch (using Clara) parse the command line
850  session.applyCommandLine(argc, argv);
851 
852  int numFailed = session.run();
853 
854  // numFailed is clamped to 255 as some unices only use the lower 8 bits.
855  // This clamping has already been applied, so just return it here
856  // You can also do any post run clean-up here
857  return numFailed;
858 }
859 
860 #else
861 #include <iostream>
862 
863 int main() { return EXIT_SUCCESS; }
864 #endif
void resize(unsigned int nrows, unsigned int ncols, bool flagNullify=true, bool recopy_=true)
Definition: vpArray2D.h:362
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
Class to define RGB colors available for display functionalities.
Definition: vpColor.h:157
static const vpColor red
Definition: vpColor.h:217
static const vpColor blue
Definition: vpColor.h:223
static bool getClick(const vpImage< unsigned char > &I, bool blocking=true)
static void display(const vpImage< unsigned char > &I)
static void flush(const vpImage< unsigned char > &I)
static void displayPoint(const vpImage< unsigned char > &I, const vpImagePoint &ip, const vpColor &color, unsigned int thickness=1)
static void displayText(const vpImage< unsigned char > &I, const vpImagePoint &ip, const std::string &s, const vpColor &color)
Class for generating random number with normal probability density.
Definition: vpGaussRand.h:117
Class that defines a 2D point in an image. This class is useful for image processing and stores only ...
Definition: vpImagePoint.h:82
double get_u() const
Definition: vpImagePoint.h:136
double get_v() const
Definition: vpImagePoint.h:147
Definition of the vpImage class member functions.
Definition: vpImage.h:131
unsigned int getWidth() const
Definition: vpImage.h:242
Implementation of a matrix and operations on matrices.
Definition: vpMatrix.h:169
vpMatrix pseudoInverse(double svThreshold=1e-6) const
The class permits to use a Particle Filter.
Class for generating random numbers with uniform probability density.
Definition: vpUniRand.h:127
static std::vector< T > shuffleVector(const std::vector< T > &inputVector, const int32_t &seed=-1)
Create a new vector that is a shuffled version of the inputVector.
Definition: vpUniRand.h:149
std::shared_ptr< vpDisplay > createDisplay()
Return a smart pointer vpDisplay specialization if a GUI library is available or nullptr otherwise.