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/vpMath.h>
49#include <visp3/core/vpParticleFilter.h>
50#include <visp3/core/vpUniRand.h>
52#ifdef VISP_HAVE_DISPLAY
53#include <visp3/core/vpImage.h>
54#include <visp3/gui/vpDisplayFactory.h>
57#include <catch_amalgamated.hpp>
59#ifdef ENABLE_VISP_NAMESPACE
65bool opt_display =
false;
74 inline vpParabolaModel(
const unsigned int °ree,
const unsigned int &height,
const unsigned int &width)
78 , m_coeffs(degree + 1, 0.)
89 inline vpParabolaModel(
const vpColVector &coeffs,
const unsigned int &height,
const unsigned int &width)
90 : m_degree(coeffs.size() - 1)
104 inline vpParabolaModel(
const vpMatrix &coeffs,
const unsigned int &height,
const unsigned int &width)
105 : m_degree(coeffs.getRows() - 1)
108 , m_coeffs(coeffs.getCol(0))
111 inline vpParabolaModel(
const vpParabolaModel &other)
122 inline double eval(
const double &u)
const
124 double normalizedU =
u / m_width;
126 for (
unsigned int i = 0;
i <= m_degree; ++
i) {
127 v += m_coeffs[
i] * std::pow(normalizedU, i);
138 inline vpColVector toVpColVector()
const
143 inline vpParabolaModel &operator=(
const vpParabolaModel &other)
145 m_degree = other.m_degree;
146 m_height = other.m_height;
147 m_width = other.m_width;
148 m_coeffs = other.m_coeffs;
166 static void fillSystem(
const unsigned int °ree,
const unsigned int &height,
const unsigned int &width,
const std::vector< vpImagePoint> &pts, vpMatrix &A, vpMatrix &b)
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));
174 A.
resize(nbPts, nbCoeffs, 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);
186#ifdef VISP_HAVE_DISPLAY
188#if defined(__clang__)
190# pragma clang diagnostic push
191# pragma clang diagnostic ignored "-Wexit-time-destructors"
202 void display(
const vpImage<T> &I,
const vpColor &color,
const std::string &legend,
203 const unsigned int &vertPosLegend,
const unsigned int &horPosLegend)
206 for (
unsigned int u = 0;
u <
width; ++
u) {
207 int v =
static_cast<int>(eval(u));
214#if defined(__clang__)
215# pragma clang diagnostic pop
220 unsigned int m_degree;
221 unsigned int m_height;
222 unsigned int m_width;
223 vpColVector m_coeffs;
236vpColVector computeABC(
const double &x0,
const double &y0,
const double &x1,
const double &y1)
238 double b = (y1 - y0)/(-0.5*(x1 * x1/x0) + x1 -0.5 * x0);
239 double a = -b / (2. * x0);
240 double c = y0 - 0.5 * b * x0;
254vpColVector computeABCD(
const double &x0,
const double &y0,
const double &x1,
const double &y1)
256 double factorA = -2. / (3. * (x1 + x0));
257 double factorC = -1. * ((-2. * std::pow(x0, 2))/(x1 + x0) + 2 * x0);
258 double b = (y1 - y0)/(factorA * (std::pow(x1, 3) - std::pow(x0, 3)) + (std::pow(x1, 2) - std::pow(x0, 2)) + (x1 - x0) * factorC);
259 double a = factorA * b;
260 double c = factorC * b;
261 double d = y0-(a * std::pow(x0, 3) + b * std::pow(x0, 2) + c * x0);
272double computeY(
const double &x,
const vpColVector &coeffs)
275 unsigned int nbCoeffs = coeffs.
size();
276 for (
unsigned int i = 0;
i < nbCoeffs; ++
i) {
277 y += coeffs[
i] * std::pow(x, i);
291std::vector<vpImagePoint> generateSimulatedImage(
const double &xmin,
const double &xmax,
const double &step,
const vpColVector &coeffs)
293 std::vector<vpImagePoint> points;
294 for (
double x = xmin;
x <= xmax;
x +=
step) {
295 double y = computeY(x, coeffs);
297 points.push_back(pt);
302#ifdef VISP_HAVE_DISPLAY
304void displayGeneratedImage(
const vpImage<T> &I,
const std::vector<vpImagePoint> &pts,
const vpColor &color,
305 const std::string &legend,
const unsigned int &vertOffset,
const unsigned int &horOffset)
307 unsigned int nbPts =
static_cast<unsigned int>(pts.size());
308 for (
unsigned int i = 1;
i < nbPts; ++
i) {
324vpParabolaModel computeInitialGuess(
const std::vector<vpImagePoint> &pts,
const unsigned int °ree,
const unsigned int &height,
const unsigned int &width)
331 vpParabolaModel::fillSystem(degree, height, width, pts, A, b);
335 vpParabolaModel model(X, height, width);
346double evaluate(
const std::vector<vpImagePoint> &pts,
const vpParabolaModel &model)
349 size_t sizePts = pts.size();
350 for (
size_t i = 0;
i < sizePts; ++
i) {
354 double v_model = model.eval(u);
355 double error =
v - v_model;
359 rmse = std::sqrt(rmse /
static_cast<double>(pts.size()));
371 return updatedCoeffs;
374class vpAverageFunctor
377 vpAverageFunctor(
const unsigned int °ree,
const unsigned int &height,
const unsigned int &width)
383 vpColVector averagePolynomials(
const std::vector<vpColVector> &particles,
const std::vector<double> &weights,
const vpParticleFilter<std::vector<vpImagePoint>>::vpStateAddFunction &)
385 const unsigned int nbParticles =
static_cast<unsigned int>(particles.size());
386 const double nbParticlesAsDOuble =
static_cast<double>(nbParticles);
387 const double sumWeight = std::accumulate(weights.begin(), weights.end(), 0.);
388 const double nbPointsForAverage = 10. * nbParticlesAsDOuble;
389 std::vector<vpImagePoint> initPoints;
390 for (
unsigned int i = 0;
i < nbParticles; ++
i) {
391 double nbPoints = std::floor(weights[i] * nbPointsForAverage / sumWeight);
393 vpParabolaModel curve(particles[i], m_height, m_width);
394 double widthAsDouble =
static_cast<double>(m_width);
395 double step = widthAsDouble / (nbPoints - 1.);
396 for (
double u = 0.;
u < widthAsDouble;
u +=
step) {
397 double v = curve.eval(u);
398 vpImagePoint pt(v, u);
399 initPoints.push_back(pt);
403 vpParabolaModel curve(particles[i], m_height, m_width);
404 double u =
static_cast<double>(m_width) / 2.;
405 double v = curve.eval(u);
406 vpImagePoint pt(v, u);
407 initPoints.push_back(pt);
411 vpParabolaModel::fillSystem(m_degree, m_height, m_width, initPoints, A, b);
413 return vpParabolaModel(X, m_height, m_width).toVpColVector();
417 unsigned int m_degree;
418 unsigned int m_height;
419 unsigned int m_width;
422class vpLikelihoodFunctor
432 vpLikelihoodFunctor(
const double &stdev,
const unsigned int &height,
const unsigned int &width)
436 double sigmaDistanceSquared = stdev * stdev;
437 m_constantDenominator = 1. / std::sqrt(2. * M_PI * sigmaDistanceSquared);
438 m_constantExpDenominator = -1. / (2. * sigmaDistanceSquared);
454 double likelihood(
const vpColVector &coeffs,
const std::vector<vpImagePoint> &meas)
456 double likelihood = 0.;
457 unsigned int nbPoints =
static_cast<unsigned int>(meas.size());
458 vpParabolaModel model(coeffs, m_height, m_width);
459 vpColVector residuals(nbPoints);
460 double rmse = evaluate(meas, model);
461 likelihood = std::exp(rmse * m_constantExpDenominator) * m_constantDenominator;
462 likelihood = std::min(likelihood, 1.0);
463 likelihood = std::max(likelihood, 0.);
468 double m_constantDenominator;
469 double m_constantExpDenominator;
470 unsigned int m_height;
471 unsigned int m_width;
475TEST_CASE(
"2nd-degree",
"[vpParticleFilter][Polynomial interpolation]")
478 const unsigned int width = 150;
479 const unsigned int height = 100;
480 const unsigned int degree = 2;
481 const unsigned int nbInitPoints = 10;
482 const uint64_t seedCurve = 4224;
483 const uint64_t seedInitPoints = 2112;
484 const unsigned int nbTestRepet = 5;
485 const unsigned int nbWarmUpIter = 10;
486 const unsigned int nbEvalIter = 10;
487 const double dt = 0.040;
488 const int32_t seedShuffle = 4221;
493 const double ampliMaxLikelihood = 16.;
494 const double sigmaLikelihood = ampliMaxLikelihood / 3.;
495 const unsigned int nbParticles = 200;
496 const double ratioAmpliMax(0.25);
497 const long seedPF = 4221;
498 const int nbThreads = 1;
502 SECTION(
"Noise-free",
"The init points are directly extracted from the curve points, without any additional noise")
504 const double maxToleratedError = 5.;
505 double x0 = rngCurvePoints.uniform(0., width);
506 double x1 = rngCurvePoints.uniform(0., width);
507 double y0 = rngCurvePoints.uniform(0., height);
508 double y1 = rngCurvePoints.uniform(0., height);
510 std::vector<vpImagePoint> curvePoints = generateSimulatedImage(0, width, 1., coeffs);
512#ifdef VISP_HAVE_DISPLAY
514 std::shared_ptr<vpDisplay> pDisplay;
520 for (
unsigned int iter = 0;
iter < nbTestRepet; ++
iter) {
523 std::vector<vpImagePoint> initPoints;
524 for (
unsigned int j = 0;
j < nbInitPoints; ++
j) {
525 initPoints.push_back(suffledVector[j]);
529 vpParabolaModel modelInitial = computeInitialGuess(initPoints, degree, height, width);
533 std::vector<double> stdevsPF;
534 for (
unsigned int i = 0;
i < degree + 1; ++
i) {
535 stdevsPF.push_back(ratioAmpliMax * X0[0] / 3.);
538 vpLikelihoodFunctor likelihoodFtor(sigmaLikelihood, height, width);
539 using std::placeholders::_1;
540 using std::placeholders::_2;
544 vpAverageFunctor averageCpter(degree, height, width);
545 using std::placeholders::_3;
548 filter.init(X0, processFunc, likelihoodFunc, checkResamplingFunc, resamplingFunc, meanFunc);
550 for (
unsigned int i = 0;
i < nbWarmUpIter; ++
i) {
551 filter.filter(curvePoints, dt);
554 double meanError = 0.;
555 for (
unsigned int i = 0;
i < nbEvalIter; ++
i) {
556 filter.filter(curvePoints, dt);
558 vpParabolaModel model(Xest, height, width);
559 double rmse = evaluate(curvePoints, model);
562#ifdef VISP_HAVE_DISPLAY
565 displayGeneratedImage(I, curvePoints,
vpColor::red,
"GT", 20, 20);
572 meanError /=
static_cast<double>(nbEvalIter);
573 std::cout <<
"[2nd degree][Noise-free] Test " <<
iter <<
": Mean(rmse) = " << meanError << std::endl;
574 CHECK(meanError <= maxToleratedError);
578 SECTION(
"Noisy",
"Noise is added to the init points")
580 const double maxToleratedError = 12.;
581 double x0 = rngCurvePoints.uniform(0., width);
582 double x1 = rngCurvePoints.uniform(0., width);
583 double y0 = rngCurvePoints.uniform(0., height);
584 double y1 = rngCurvePoints.uniform(0., height);
586 std::vector<vpImagePoint> curvePoints = generateSimulatedImage(0, width, 1., coeffs);
588#ifdef VISP_HAVE_DISPLAY
590 std::shared_ptr<vpDisplay> pDisplay;
596 const double ampliMaxInitNoise = 18.;
597 const double stdevInitNoise = ampliMaxInitNoise / 3.;
598 vpGaussRand rngInitNoise(stdevInitNoise, 0., seedInitPoints);
600 for (
unsigned int iter = 0;
iter < nbTestRepet; ++
iter) {
603 std::vector<vpImagePoint> initPoints;
604 for (
unsigned int j = 0;
j < nbInitPoints; ++
j) {
605 vpImagePoint noisyPt(suffledVector[j].get_i() + rngInitNoise(), suffledVector[j].get_j() + rngInitNoise());
606 initPoints.push_back(noisyPt);
610 vpParabolaModel modelInitial = computeInitialGuess(initPoints, degree, height, width);
614 std::vector<double> stdevsPF;
615 for (
unsigned int i = 0;
i < degree + 1; ++
i) {
616 stdevsPF.push_back(ratioAmpliMax * X0[0] / 3.);
619 vpLikelihoodFunctor likelihoodFtor(sigmaLikelihood, height, width);
620 using std::placeholders::_1;
621 using std::placeholders::_2;
625 vpAverageFunctor averageCpter(degree, height, width);
626 using std::placeholders::_3;
629 filter.init(X0, processFunc, likelihoodFunc, checkResamplingFunc, resamplingFunc, meanFunc);
631 for (
unsigned int i = 0;
i < nbWarmUpIter; ++
i) {
632 filter.filter(curvePoints, dt);
635 double meanError = 0.;
636 for (
unsigned int i = 0;
i < nbEvalIter; ++
i) {
637 filter.filter(curvePoints, dt);
639 vpParabolaModel model(Xest, height, width);
640 double rmse = evaluate(curvePoints, model);
643#ifdef VISP_HAVE_DISPLAY
646 displayGeneratedImage(I, curvePoints,
vpColor::red,
"GT", 20, 20);
653 meanError /=
static_cast<double>(nbEvalIter);
654 std::cout <<
"[2nd degree][Noisy] Test " <<
iter <<
": Mean(rmse) = " << meanError << std::endl;
655 CHECK(meanError <= maxToleratedError);
660TEST_CASE(
"3rd-degree",
"[vpParticleFilter][Polynomial interpolation]")
663 const unsigned int width = 150;
664 const unsigned int height = 100;
665 const unsigned int degree = 3;
666 const unsigned int nbInitPoints = 10;
667 const uint64_t seedCurve = 4224;
668 const uint64_t seedInitPoints = 2112;
669 const unsigned int nbTestRepet = 5;
670 const unsigned int nbWarmUpIter = 10;
671 const unsigned int nbEvalIter = 10;
672 const double dt = 0.040;
673 const int32_t seedShuffle = 4221;
678 const double ampliMaxLikelihood = 6.;
679 const double sigmaLikelihood = ampliMaxLikelihood / 3.;
680 const unsigned int nbParticles = 200;
681 const double ratioAmpliMax(0.21);
682 const long seedPF = 4221;
683 const int nbThreads = 1;
687 SECTION(
"Noise-free",
"The init points are directly extracted from the curve points, without any additional noise")
689 const double maxToleratedError = 10.;
690 double x0 = rngCurvePoints.uniform(0., width);
691 double x1 = rngCurvePoints.uniform(0., width);
692 double y0 = rngCurvePoints.uniform(0., height);
693 double y1 = rngCurvePoints.uniform(0., height);
695 std::vector<vpImagePoint> curvePoints = generateSimulatedImage(0, width, 1., coeffs);
697#ifdef VISP_HAVE_DISPLAY
699 std::shared_ptr<vpDisplay> pDisplay;
705 for (
unsigned int iter = 0;
iter < nbTestRepet; ++
iter) {
708 std::vector<vpImagePoint> initPoints;
709 for (
unsigned int j = 0;
j < nbInitPoints; ++
j) {
710 initPoints.push_back(suffledVector[j]);
714 vpParabolaModel modelInitial = computeInitialGuess(initPoints, degree, height, width);
718 std::vector<double> stdevsPF;
719 for (
unsigned int i = 0;
i < degree + 1; ++
i) {
720 stdevsPF.push_back(ratioAmpliMax * std::pow(0.1, i) * X0[0] / 3.);
723 vpLikelihoodFunctor likelihoodFtor(sigmaLikelihood, height, width);
724 using std::placeholders::_1;
725 using std::placeholders::_2;
729 vpAverageFunctor averageCpter(degree, height, width);
730 using std::placeholders::_3;
733 filter.init(X0, processFunc, likelihoodFunc, checkResamplingFunc, resamplingFunc, meanFunc);
735 for (
unsigned int i = 0;
i < nbWarmUpIter; ++
i) {
736 filter.filter(curvePoints, dt);
739 double meanError = 0.;
740 for (
unsigned int i = 0;
i < nbEvalIter; ++
i) {
741 filter.filter(curvePoints, dt);
743 vpParabolaModel model(Xest, height, width);
744 double rmse = evaluate(curvePoints, model);
747#ifdef VISP_HAVE_DISPLAY
750 displayGeneratedImage(I, curvePoints,
vpColor::red,
"GT", 20, 20);
757 meanError /=
static_cast<double>(nbEvalIter);
758 std::cout <<
"[3rd degree][Noise-free] Test " <<
iter <<
": Mean(rmse) = " << meanError << std::endl;
759 CHECK(meanError <= maxToleratedError);
765 SECTION(
"Noisy",
"Noise is added to the init points")
767 const double maxToleratedError = 17.;
768 double x0 = rngCurvePoints.uniform(0., width);
769 double x1 = rngCurvePoints.uniform(0., width);
770 double y0 = rngCurvePoints.uniform(0., height);
771 double y1 = rngCurvePoints.uniform(0., height);
773 std::vector<vpImagePoint> curvePoints = generateSimulatedImage(0, width, 1., coeffs);
775#ifdef VISP_HAVE_DISPLAY
777 std::shared_ptr<vpDisplay> pDisplay;
783 const double ampliMaxInitNoise = 1.5;
784 const double stdevInitNoise = ampliMaxInitNoise / 3.;
785 vpGaussRand rngInitNoise(stdevInitNoise, 0., seedInitPoints);
787 for (
unsigned int iter = 0;
iter < nbTestRepet; ++
iter) {
790 std::vector<vpImagePoint> initPoints;
791 for (
unsigned int j = 0;
j < nbInitPoints * 4; ++
j) {
792 vpImagePoint noisyPt(suffledVector[j].get_i() + rngInitNoise(), suffledVector[j].get_j() + rngInitNoise());
793 initPoints.push_back(noisyPt);
797 vpParabolaModel modelInitial = computeInitialGuess(initPoints, degree, height, width);
801 std::vector<double> stdevsPF;
802 for (
unsigned int i = 0;
i < degree + 1; ++
i) {
803 stdevsPF.push_back(ratioAmpliMax * std::pow(.05, i) * X0[0] / 3.);
806 vpLikelihoodFunctor likelihoodFtor(sigmaLikelihood * 2., height, width);
807 using std::placeholders::_1;
808 using std::placeholders::_2;
812 vpAverageFunctor averageCpter(degree, height, width);
813 using std::placeholders::_3;
816 filter.init(X0, processFunc, likelihoodFunc, checkResamplingFunc, resamplingFunc, meanFunc);
818 for (
unsigned int i = 0;
i < nbWarmUpIter * 5; ++
i) {
819 filter.filter(curvePoints, dt);
822 double meanError = 0.;
823 for (
unsigned int i = 0;
i < nbEvalIter; ++
i) {
824 filter.filter(curvePoints, dt);
826 vpParabolaModel model(Xest, height, width);
827 double rmse = evaluate(curvePoints, model);
830#ifdef VISP_HAVE_DISPLAY
833 displayGeneratedImage(I, curvePoints,
vpColor::red,
"GT", 20, 20);
840 meanError /=
static_cast<double>(nbEvalIter);
841 std::cout <<
"[3rd degree][Noisy] Test " <<
iter <<
": Mean(rmse) = " << meanError << std::endl;
842 CHECK(meanError <= maxToleratedError);
847int main(
int argc,
char *argv[])
849 Catch::Session session;
850 auto cli = session.cli()
851 | Catch::Clara::Opt(opt_display)[
"--display"](
"Activate debug display");
854 session.applyCommandLine(argc, argv);
856 int numFailed = session.run();
863int 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
static bool equal(double x, double y, double threshold=0.001)
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.