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