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 #define CATCH_CONFIG_RUNNER
59 #ifdef ENABLE_VISP_NAMESPACE
65 bool 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);
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
195 void display(
const vpImage<T> &I,
const vpColor &color,
const std::string &legend,
196 const unsigned int &vertPosLegend,
const unsigned int &horPosLegend)
199 for (
unsigned int u = 0; u < width; ++u) {
200 int v =
static_cast<int>(eval(u));
208 unsigned int m_degree;
209 unsigned int m_height;
210 unsigned int m_width;
224 vpColVector computeABC(
const double &x0,
const double &y0,
const double &x1,
const double &y1)
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;
242 vpColVector computeABCD(
const double &x0,
const double &y0,
const double &x1,
const double &y1)
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);
260 double computeY(
const double &x,
const vpColVector &coeffs)
263 unsigned int nbCoeffs = coeffs.
size();
264 for (
unsigned int i = 0; i < nbCoeffs; ++i) {
265 y += coeffs[i] * std::pow(x, i);
279 std::vector<vpImagePoint> generateSimulatedImage(
const double &xmin,
const double &xmax,
const double &step,
const vpColVector &coeffs)
281 std::vector<vpImagePoint> points;
282 for (
double x = xmin; x <= xmax; x += step) {
283 double y = computeY(x, coeffs);
285 points.push_back(pt);
290 #ifdef VISP_HAVE_DISPLAY
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)
295 unsigned int nbPts =
static_cast<unsigned int>(pts.size());
296 for (
unsigned int i = 1; i < nbPts; ++i) {
312 vpParabolaModel computeInitialGuess(
const std::vector<vpImagePoint> &pts,
const unsigned int °ree,
const unsigned int&height,
const unsigned int &width)
319 vpParabolaModel::fillSystem(degree, height, width, pts, A, b);
323 vpParabolaModel model(X, height, width);
334 double evaluate(
const std::vector<vpImagePoint> &pts,
const vpParabolaModel &model)
337 size_t sizePts = pts.size();
338 for (
size_t i = 0; i < sizePts; ++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;
347 rmse = std::sqrt(rmse /
static_cast<double>(pts.size()));
359 return updatedCoeffs;
362 class vpAverageFunctor
365 vpAverageFunctor(
const unsigned int °ree,
const unsigned int &height,
const unsigned int &width)
371 vpColVector averagePolynomials(
const std::vector<vpColVector> &particles,
const std::vector<double> &weights,
const vpParticleFilter<std::vector<vpImagePoint>>::vpStateAddFunction &)
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);
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);
387 initPoints.push_back(pt);
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);
395 initPoints.push_back(pt);
399 vpParabolaModel::fillSystem(m_degree, m_height, m_width, initPoints, A, b);
401 return vpParabolaModel(X, m_height, m_width).toVpColVector();
405 unsigned int m_degree;
406 unsigned int m_height;
407 unsigned int m_width;
410 class vpLikelihoodFunctor
420 vpLikelihoodFunctor(
const double &stdev,
const unsigned int &height,
const unsigned int &width)
424 double sigmaDistanceSquared = stdev * stdev;
425 m_constantDenominator = 1. / std::sqrt(2. * M_PI * sigmaDistanceSquared);
426 m_constantExpDenominator = -1. / (2. * sigmaDistanceSquared);
442 double likelihood(
const vpColVector &coeffs,
const std::vector<vpImagePoint> &meas)
444 double likelihood = 0.;
445 unsigned int nbPoints =
static_cast<unsigned int>(meas.size());
446 vpParabolaModel model(coeffs, m_height, m_width);
448 double rmse = evaluate(meas, model);
449 likelihood = std::exp(rmse * m_constantExpDenominator) * m_constantDenominator;
450 likelihood = std::min(likelihood, 1.0);
451 likelihood = std::max(likelihood, 0.);
456 double m_constantDenominator;
457 double m_constantExpDenominator;
458 unsigned int m_height;
459 unsigned int m_width;
463 TEST_CASE(
"2nd-degree",
"[vpParticleFilter][Polynomial interpolation]")
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;
481 const double ampliMaxLikelihood = 16.;
482 const double sigmaLikelihood = ampliMaxLikelihood / 3.;
483 const unsigned int nbParticles = 300;
484 const double ratioAmpliMax(0.25);
485 const long seedPF = 4221;
486 const int nbThreads = 1;
490 SECTION(
"Noise-free",
"The init points are directly extracted from the curve points, without any additional noise")
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);
498 std::vector<vpImagePoint> curvePoints = generateSimulatedImage(0, width, 1., coeffs);
500 #ifdef VISP_HAVE_DISPLAY
502 std::shared_ptr<vpDisplay> pDisplay;
508 for (
unsigned int iter = 0; iter < nbTestRepet; ++iter) {
511 std::vector<vpImagePoint> initPoints;
512 for (
unsigned int j = 0; j < nbInitPoints; ++j) {
513 initPoints.push_back(suffledVector[j]);
517 vpParabolaModel modelInitial = computeInitialGuess(initPoints, degree, height, width);
521 std::vector<double> stdevsPF;
522 for (
unsigned int i = 0; i < degree + 1; ++i) {
523 stdevsPF.push_back(ratioAmpliMax * X0[0] / 3.);
526 vpLikelihoodFunctor likelihoodFtor(sigmaLikelihood, height, width);
527 using std::placeholders::_1;
528 using std::placeholders::_2;
532 vpAverageFunctor averageCpter(degree, height, width);
533 using std::placeholders::_3;
536 filter.init(X0, processFunc, likelihoodFunc, checkResamplingFunc, resamplingFunc, meanFunc);
538 for (
unsigned int i = 0; i < nbWarmUpIter; ++i) {
539 filter.filter(curvePoints, dt);
542 double meanError = 0.;
543 for (
unsigned int i = 0; i < nbEvalIter; ++i) {
544 filter.filter(curvePoints, dt);
546 vpParabolaModel model(Xest, height, width);
547 double rmse = evaluate(curvePoints, model);
550 #ifdef VISP_HAVE_DISPLAY
553 displayGeneratedImage(I, curvePoints,
vpColor::red,
"GT", 20, 20);
560 meanError /=
static_cast<double>(nbEvalIter);
561 std::cout <<
"Mean(rmse) = " << meanError << std::endl;
562 CHECK(meanError <= maxToleratedError);
566 SECTION(
"Noisy",
"Noise is added to the init points")
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);
574 std::vector<vpImagePoint> curvePoints = generateSimulatedImage(0, width, 1., coeffs);
576 #ifdef VISP_HAVE_DISPLAY
578 std::shared_ptr<vpDisplay> pDisplay;
584 const double ampliMaxInitNoise = 24.;
585 const double stdevInitNoise = ampliMaxInitNoise / 3.;
586 vpGaussRand rngInitNoise(stdevInitNoise, 0., seedInitPoints);
588 for (
unsigned int iter = 0; iter < nbTestRepet; ++iter) {
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);
598 vpParabolaModel modelInitial = computeInitialGuess(initPoints, degree, height, width);
602 std::vector<double> stdevsPF;
603 for (
unsigned int i = 0; i < degree + 1; ++i) {
604 stdevsPF.push_back(ratioAmpliMax * X0[0] / 3.);
607 vpLikelihoodFunctor likelihoodFtor(sigmaLikelihood, height, width);
608 using std::placeholders::_1;
609 using std::placeholders::_2;
613 vpAverageFunctor averageCpter(degree, height, width);
614 using std::placeholders::_3;
617 filter.init(X0, processFunc, likelihoodFunc, checkResamplingFunc, resamplingFunc, meanFunc);
619 for (
unsigned int i = 0; i < nbWarmUpIter; ++i) {
620 filter.filter(curvePoints, dt);
623 double meanError = 0.;
624 for (
unsigned int i = 0; i < nbEvalIter; ++i) {
625 filter.filter(curvePoints, dt);
627 vpParabolaModel model(Xest, height, width);
628 double rmse = evaluate(curvePoints, model);
631 #ifdef VISP_HAVE_DISPLAY
634 displayGeneratedImage(I, curvePoints,
vpColor::red,
"GT", 20, 20);
641 meanError /=
static_cast<double>(nbEvalIter);
642 std::cout <<
"Mean(rmse) = " << meanError << std::endl;
643 CHECK(meanError <= maxToleratedError);
648 TEST_CASE(
"3rd-degree",
"[vpParticleFilter][Polynomial interpolation]")
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;
666 const double ampliMaxLikelihood = 6.;
667 const double sigmaLikelihood = ampliMaxLikelihood / 3.;
668 const unsigned int nbParticles = 300;
669 const double ratioAmpliMax(0.21);
670 const long seedPF = 4221;
671 const int nbThreads = 1;
675 SECTION(
"Noise-free",
"The init points are directly extracted from the curve points, without any additional noise")
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);
683 std::vector<vpImagePoint> curvePoints = generateSimulatedImage(0, width, 1., coeffs);
685 #ifdef VISP_HAVE_DISPLAY
687 std::shared_ptr<vpDisplay> pDisplay;
693 for (
unsigned int iter = 0; iter < nbTestRepet; ++iter) {
696 std::vector<vpImagePoint> initPoints;
697 for (
unsigned int j = 0; j < nbInitPoints; ++j) {
698 initPoints.push_back(suffledVector[j]);
702 vpParabolaModel modelInitial = computeInitialGuess(initPoints, degree, height, width);
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.);
711 vpLikelihoodFunctor likelihoodFtor(sigmaLikelihood, height, width);
712 using std::placeholders::_1;
713 using std::placeholders::_2;
717 vpAverageFunctor averageCpter(degree, height, width);
718 using std::placeholders::_3;
721 filter.init(X0, processFunc, likelihoodFunc, checkResamplingFunc, resamplingFunc, meanFunc);
723 for (
unsigned int i = 0; i < nbWarmUpIter; ++i) {
724 filter.filter(curvePoints, dt);
727 double meanError = 0.;
728 for (
unsigned int i = 0; i < nbEvalIter; ++i) {
729 filter.filter(curvePoints, dt);
731 vpParabolaModel model(Xest, height, width);
732 double rmse = evaluate(curvePoints, model);
735 #ifdef VISP_HAVE_DISPLAY
738 displayGeneratedImage(I, curvePoints,
vpColor::red,
"GT", 20, 20);
745 meanError /=
static_cast<double>(nbEvalIter);
746 std::cout <<
"Mean(rmse) = " << meanError << std::endl;
747 CHECK(meanError <= maxToleratedError);
753 SECTION(
"Noisy",
"Noise is added to the init points")
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);
761 std::vector<vpImagePoint> curvePoints = generateSimulatedImage(0, width, 1., coeffs);
763 #ifdef VISP_HAVE_DISPLAY
765 std::shared_ptr<vpDisplay> pDisplay;
771 const double ampliMaxInitNoise = 1.5;
772 const double stdevInitNoise = ampliMaxInitNoise / 3.;
773 vpGaussRand rngInitNoise(stdevInitNoise, 0., seedInitPoints);
775 for (
unsigned int iter = 0; iter < nbTestRepet; ++iter) {
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);
785 vpParabolaModel modelInitial = computeInitialGuess(initPoints, degree, height, width);
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.);
794 vpLikelihoodFunctor likelihoodFtor(sigmaLikelihood * 2., height, width);
795 using std::placeholders::_1;
796 using std::placeholders::_2;
800 vpAverageFunctor averageCpter(degree, height, width);
801 using std::placeholders::_3;
804 filter.init(X0, processFunc, likelihoodFunc, checkResamplingFunc, resamplingFunc, meanFunc);
806 for (
unsigned int i = 0; i < nbWarmUpIter * 5; ++i) {
807 filter.filter(curvePoints, dt);
810 double meanError = 0.;
811 for (
unsigned int i = 0; i < nbEvalIter; ++i) {
812 filter.filter(curvePoints, dt);
814 vpParabolaModel model(Xest, height, width);
815 double rmse = evaluate(curvePoints, model);
818 #ifdef VISP_HAVE_DISPLAY
821 displayGeneratedImage(I, curvePoints,
vpColor::red,
"GT", 20, 20);
828 meanError /=
static_cast<double>(nbEvalIter);
829 std::cout <<
"Mean(rmse) = " << meanError << std::endl;
830 CHECK(meanError <= maxToleratedError);
835 int main(
int argc,
char *argv[])
837 Catch::Session session;
840 using namespace Catch::clara;
841 auto cli = session.cli()
844 (
"Activate debug display");
850 session.applyCommandLine(argc, argv);
852 int numFailed = session.run();
863 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.