40 #include <visp3/core/vpConfig.h>
42 #if defined(VISP_HAVE_CATCH2) && (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11)
46 #include <visp3/core/vpColVector.h>
47 #include <visp3/core/vpImagePoint.h>
48 #include <visp3/core/vpParticleFilter.h>
49 #include <visp3/core/vpUniRand.h>
51 #ifdef VISP_HAVE_DISPLAY
52 #include <visp3/core/vpImage.h>
53 #include <visp3/gui/vpDisplayFactory.h>
56 #include <catch_amalgamated.hpp>
58 #ifdef ENABLE_VISP_NAMESPACE
64 bool opt_display =
false;
73 inline vpParabolaModel(
const unsigned int °ree,
const unsigned int &height,
const unsigned int &width)
77 , m_coeffs(degree + 1, 0.)
88 inline vpParabolaModel(
const vpColVector &coeffs,
const unsigned int &height,
const unsigned int &width)
89 : m_degree(coeffs.size() - 1)
103 inline vpParabolaModel(
const vpMatrix &coeffs,
const unsigned int &height,
const unsigned int &width)
104 : m_degree(coeffs.getRows() - 1)
107 , m_coeffs(coeffs.getCol(0))
110 inline vpParabolaModel(
const vpParabolaModel &other)
121 inline double eval(
const double &u)
const
123 double normalizedU = u / m_width;
125 for (
unsigned int i = 0; i <= m_degree; ++i) {
126 v += m_coeffs[i] * std::pow(normalizedU, i);
142 inline vpParabolaModel &operator=(
const vpParabolaModel &other)
144 m_degree = other.m_degree;
145 m_height = other.m_height;
146 m_width = other.m_width;
147 m_coeffs = other.m_coeffs;
165 static void fillSystem(
const unsigned int °ree,
const unsigned int &height,
const unsigned int &width,
const std::vector< vpImagePoint> &pts,
vpMatrix &A,
vpMatrix &b)
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));
173 A.
resize(nbPts, nbCoeffs, 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);
185 #ifdef VISP_HAVE_DISPLAY
194 void display(
const vpImage<T> &I,
const vpColor &color,
const std::string &legend,
195 const unsigned int &vertPosLegend,
const unsigned int &horPosLegend)
198 for (
unsigned int u = 0; u < width; ++u) {
199 int v =
static_cast<int>(eval(u));
207 unsigned int m_degree;
208 unsigned int m_height;
209 unsigned int m_width;
223 vpColVector computeABC(
const double &x0,
const double &y0,
const double &x1,
const double &y1)
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;
241 vpColVector computeABCD(
const double &x0,
const double &y0,
const double &x1,
const double &y1)
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);
259 double computeY(
const double &x,
const vpColVector &coeffs)
262 unsigned int nbCoeffs = coeffs.
size();
263 for (
unsigned int i = 0; i < nbCoeffs; ++i) {
264 y += coeffs[i] * std::pow(x, i);
278 std::vector<vpImagePoint> generateSimulatedImage(
const double &xmin,
const double &xmax,
const double &step,
const vpColVector &coeffs)
280 std::vector<vpImagePoint> points;
281 for (
double x = xmin; x <= xmax; x += step) {
282 double y = computeY(x, coeffs);
284 points.push_back(pt);
289 #ifdef VISP_HAVE_DISPLAY
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)
294 unsigned int nbPts =
static_cast<unsigned int>(pts.size());
295 for (
unsigned int i = 1; i < nbPts; ++i) {
311 vpParabolaModel computeInitialGuess(
const std::vector<vpImagePoint> &pts,
const unsigned int °ree,
const unsigned int &height,
const unsigned int &width)
318 vpParabolaModel::fillSystem(degree, height, width, pts, A, b);
322 vpParabolaModel model(X, height, width);
333 double evaluate(
const std::vector<vpImagePoint> &pts,
const vpParabolaModel &model)
336 size_t sizePts = pts.size();
337 for (
size_t i = 0; i < sizePts; ++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;
346 rmse = std::sqrt(rmse /
static_cast<double>(pts.size()));
358 return updatedCoeffs;
361 class vpAverageFunctor
364 vpAverageFunctor(
const unsigned int °ree,
const unsigned int &height,
const unsigned int &width)
370 vpColVector averagePolynomials(
const std::vector<vpColVector> &particles,
const std::vector<double> &weights,
const vpParticleFilter<std::vector<vpImagePoint>>::vpStateAddFunction &)
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);
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);
386 initPoints.push_back(pt);
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);
394 initPoints.push_back(pt);
398 vpParabolaModel::fillSystem(m_degree, m_height, m_width, initPoints, A, b);
400 return vpParabolaModel(X, m_height, m_width).toVpColVector();
404 unsigned int m_degree;
405 unsigned int m_height;
406 unsigned int m_width;
409 class vpLikelihoodFunctor
419 vpLikelihoodFunctor(
const double &stdev,
const unsigned int &height,
const unsigned int &width)
423 double sigmaDistanceSquared = stdev * stdev;
424 m_constantDenominator = 1. / std::sqrt(2. * M_PI * sigmaDistanceSquared);
425 m_constantExpDenominator = -1. / (2. * sigmaDistanceSquared);
441 double likelihood(
const vpColVector &coeffs,
const std::vector<vpImagePoint> &meas)
443 double likelihood = 0.;
444 unsigned int nbPoints =
static_cast<unsigned int>(meas.size());
445 vpParabolaModel model(coeffs, m_height, m_width);
447 double rmse = evaluate(meas, model);
448 likelihood = std::exp(rmse * m_constantExpDenominator) * m_constantDenominator;
449 likelihood = std::min(likelihood, 1.0);
450 likelihood = std::max(likelihood, 0.);
455 double m_constantDenominator;
456 double m_constantExpDenominator;
457 unsigned int m_height;
458 unsigned int m_width;
462 TEST_CASE(
"2nd-degree",
"[vpParticleFilter][Polynomial interpolation]")
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;
480 const double ampliMaxLikelihood = 16.;
481 const double sigmaLikelihood = ampliMaxLikelihood / 3.;
482 const unsigned int nbParticles = 300;
483 const double ratioAmpliMax(0.25);
484 const long seedPF = 4221;
485 const int nbThreads = 1;
489 SECTION(
"Noise-free",
"The init points are directly extracted from the curve points, without any additional noise")
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);
497 std::vector<vpImagePoint> curvePoints = generateSimulatedImage(0, width, 1., coeffs);
499 #ifdef VISP_HAVE_DISPLAY
501 std::shared_ptr<vpDisplay> pDisplay;
507 for (
unsigned int iter = 0; iter < nbTestRepet; ++iter) {
510 std::vector<vpImagePoint> initPoints;
511 for (
unsigned int j = 0; j < nbInitPoints; ++j) {
512 initPoints.push_back(suffledVector[j]);
516 vpParabolaModel modelInitial = computeInitialGuess(initPoints, degree, height, width);
520 std::vector<double> stdevsPF;
521 for (
unsigned int i = 0; i < degree + 1; ++i) {
522 stdevsPF.push_back(ratioAmpliMax * X0[0] / 3.);
525 vpLikelihoodFunctor likelihoodFtor(sigmaLikelihood, height, width);
526 using std::placeholders::_1;
527 using std::placeholders::_2;
531 vpAverageFunctor averageCpter(degree, height, width);
532 using std::placeholders::_3;
535 filter.init(X0, processFunc, likelihoodFunc, checkResamplingFunc, resamplingFunc, meanFunc);
537 for (
unsigned int i = 0; i < nbWarmUpIter; ++i) {
538 filter.filter(curvePoints, dt);
541 double meanError = 0.;
542 for (
unsigned int i = 0; i < nbEvalIter; ++i) {
543 filter.filter(curvePoints, dt);
545 vpParabolaModel model(Xest, height, width);
546 double rmse = evaluate(curvePoints, model);
549 #ifdef VISP_HAVE_DISPLAY
552 displayGeneratedImage(I, curvePoints,
vpColor::red,
"GT", 20, 20);
559 meanError /=
static_cast<double>(nbEvalIter);
560 std::cout <<
"Mean(rmse) = " << meanError << std::endl;
561 CHECK(meanError <= maxToleratedError);
565 SECTION(
"Noisy",
"Noise is added to the init points")
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);
573 std::vector<vpImagePoint> curvePoints = generateSimulatedImage(0, width, 1., coeffs);
575 #ifdef VISP_HAVE_DISPLAY
577 std::shared_ptr<vpDisplay> pDisplay;
583 const double ampliMaxInitNoise = 24.;
584 const double stdevInitNoise = ampliMaxInitNoise / 3.;
585 vpGaussRand rngInitNoise(stdevInitNoise, 0., seedInitPoints);
587 for (
unsigned int iter = 0; iter < nbTestRepet; ++iter) {
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);
597 vpParabolaModel modelInitial = computeInitialGuess(initPoints, degree, height, width);
601 std::vector<double> stdevsPF;
602 for (
unsigned int i = 0; i < degree + 1; ++i) {
603 stdevsPF.push_back(ratioAmpliMax * X0[0] / 3.);
606 vpLikelihoodFunctor likelihoodFtor(sigmaLikelihood, height, width);
607 using std::placeholders::_1;
608 using std::placeholders::_2;
612 vpAverageFunctor averageCpter(degree, height, width);
613 using std::placeholders::_3;
616 filter.init(X0, processFunc, likelihoodFunc, checkResamplingFunc, resamplingFunc, meanFunc);
618 for (
unsigned int i = 0; i < nbWarmUpIter; ++i) {
619 filter.filter(curvePoints, dt);
622 double meanError = 0.;
623 for (
unsigned int i = 0; i < nbEvalIter; ++i) {
624 filter.filter(curvePoints, dt);
626 vpParabolaModel model(Xest, height, width);
627 double rmse = evaluate(curvePoints, model);
630 #ifdef VISP_HAVE_DISPLAY
633 displayGeneratedImage(I, curvePoints,
vpColor::red,
"GT", 20, 20);
640 meanError /=
static_cast<double>(nbEvalIter);
641 std::cout <<
"Mean(rmse) = " << meanError << std::endl;
642 CHECK(meanError <= maxToleratedError);
647 TEST_CASE(
"3rd-degree",
"[vpParticleFilter][Polynomial interpolation]")
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;
665 const double ampliMaxLikelihood = 6.;
666 const double sigmaLikelihood = ampliMaxLikelihood / 3.;
667 const unsigned int nbParticles = 300;
668 const double ratioAmpliMax(0.21);
669 const long seedPF = 4221;
670 const int nbThreads = 1;
674 SECTION(
"Noise-free",
"The init points are directly extracted from the curve points, without any additional noise")
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);
682 std::vector<vpImagePoint> curvePoints = generateSimulatedImage(0, width, 1., coeffs);
684 #ifdef VISP_HAVE_DISPLAY
686 std::shared_ptr<vpDisplay> pDisplay;
692 for (
unsigned int iter = 0; iter < nbTestRepet; ++iter) {
695 std::vector<vpImagePoint> initPoints;
696 for (
unsigned int j = 0; j < nbInitPoints; ++j) {
697 initPoints.push_back(suffledVector[j]);
701 vpParabolaModel modelInitial = computeInitialGuess(initPoints, degree, height, width);
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.);
710 vpLikelihoodFunctor likelihoodFtor(sigmaLikelihood, height, width);
711 using std::placeholders::_1;
712 using std::placeholders::_2;
716 vpAverageFunctor averageCpter(degree, height, width);
717 using std::placeholders::_3;
720 filter.init(X0, processFunc, likelihoodFunc, checkResamplingFunc, resamplingFunc, meanFunc);
722 for (
unsigned int i = 0; i < nbWarmUpIter; ++i) {
723 filter.filter(curvePoints, dt);
726 double meanError = 0.;
727 for (
unsigned int i = 0; i < nbEvalIter; ++i) {
728 filter.filter(curvePoints, dt);
730 vpParabolaModel model(Xest, height, width);
731 double rmse = evaluate(curvePoints, model);
734 #ifdef VISP_HAVE_DISPLAY
737 displayGeneratedImage(I, curvePoints,
vpColor::red,
"GT", 20, 20);
744 meanError /=
static_cast<double>(nbEvalIter);
745 std::cout <<
"Mean(rmse) = " << meanError << std::endl;
746 CHECK(meanError <= maxToleratedError);
752 SECTION(
"Noisy",
"Noise is added to the init points")
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);
760 std::vector<vpImagePoint> curvePoints = generateSimulatedImage(0, width, 1., coeffs);
762 #ifdef VISP_HAVE_DISPLAY
764 std::shared_ptr<vpDisplay> pDisplay;
770 const double ampliMaxInitNoise = 1.5;
771 const double stdevInitNoise = ampliMaxInitNoise / 3.;
772 vpGaussRand rngInitNoise(stdevInitNoise, 0., seedInitPoints);
774 for (
unsigned int iter = 0; iter < nbTestRepet; ++iter) {
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);
784 vpParabolaModel modelInitial = computeInitialGuess(initPoints, degree, height, width);
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.);
793 vpLikelihoodFunctor likelihoodFtor(sigmaLikelihood * 2., height, width);
794 using std::placeholders::_1;
795 using std::placeholders::_2;
799 vpAverageFunctor averageCpter(degree, height, width);
800 using std::placeholders::_3;
803 filter.init(X0, processFunc, likelihoodFunc, checkResamplingFunc, resamplingFunc, meanFunc);
805 for (
unsigned int i = 0; i < nbWarmUpIter * 5; ++i) {
806 filter.filter(curvePoints, dt);
809 double meanError = 0.;
810 for (
unsigned int i = 0; i < nbEvalIter; ++i) {
811 filter.filter(curvePoints, dt);
813 vpParabolaModel model(Xest, height, width);
814 double rmse = evaluate(curvePoints, model);
817 #ifdef VISP_HAVE_DISPLAY
820 displayGeneratedImage(I, curvePoints,
vpColor::red,
"GT", 20, 20);
827 meanError /=
static_cast<double>(nbEvalIter);
828 std::cout <<
"Mean(rmse) = " << meanError << std::endl;
829 CHECK(meanError <= maxToleratedError);
834 int main(
int argc,
char *argv[])
836 Catch::Session session;
837 auto cli = session.cli()
838 | Catch::Clara::Opt(opt_display)[
"--display"](
"Activate debug display");
841 session.applyCommandLine(argc, argv);
843 int numFailed = session.run();
850 int main() {
return EXIT_SUCCESS; }
void resize(unsigned int nrows, unsigned int ncols, bool flagNullify=true, bool recopy_=true)
unsigned int size() const
Return the number of elements of the 2D array.
Implementation of column vector and the associated operations.
Class to define RGB colors available for display functionalities.
static const vpColor blue
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.
Class that defines a 2D point in an image. This class is useful for image processing and stores only ...
Definition of the vpImage class member functions.
unsigned int getWidth() const
Implementation of a matrix and operations on matrices.
vpMatrix pseudoInverse(double svThreshold=1e-6) const
The class permits to use a Particle Filter.
Class for generating random numbers with uniform probability density.
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.
std::shared_ptr< vpDisplay > createDisplay()
Return a smart pointer vpDisplay specialization if a GUI library is available or nullptr otherwise.