#include <visp3/core/vpConfig.h>
#if defined(VISP_HAVE_CATCH2) && (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11)
#include <limits>
#include <vector>
#include <visp3/core/vpColVector.h>
#include <visp3/core/vpImagePoint.h>
#include <visp3/core/vpParticleFilter.h>
#include <visp3/core/vpUniRand.h>
#ifdef VISP_HAVE_DISPLAY
#include <visp3/core/vpImage.h>
#include <visp3/gui/vpDisplayFactory.h>
#endif
#include <catch_amalgamated.hpp>
#ifdef ENABLE_VISP_NAMESPACE
#endif
namespace
{
bool opt_display = false;
class vpParabolaModel
{
public:
inline vpParabolaModel(const unsigned int °ree, const unsigned int &height, const unsigned int &width)
: m_degree(degree)
, m_height(height)
, m_width(width)
, m_coeffs(degree + 1, 0.)
{ }
inline vpParabolaModel(
const vpColVector &coeffs,
const unsigned int &height,
const unsigned int &width)
: m_degree(coeffs.
size() - 1)
, m_height(height)
, m_width(width)
, m_coeffs(coeffs)
{ }
inline vpParabolaModel(
const vpMatrix &coeffs,
const unsigned int &height,
const unsigned int &width)
, m_height(height)
, m_width(width)
, m_coeffs(coeffs.getCol(0))
{ }
inline vpParabolaModel(const vpParabolaModel &other)
{
*this = other;
}
inline double eval(const double &u) const
{
double normalizedU = u / m_width;
double v = 0.;
for (unsigned int i = 0; i <= m_degree; ++i) {
v += m_coeffs[i] * std::pow(normalizedU, i);
}
v *= m_height;
return v;
}
{
return m_coeffs;
}
inline vpParabolaModel &operator=(const vpParabolaModel &other)
{
m_degree = other.m_degree;
m_height = other.m_height;
m_width = other.m_width;
m_coeffs = other.m_coeffs;
return *this;
}
static void fillSystem(
const unsigned int °ree,
const unsigned int &height,
const unsigned int &width,
const std::vector< vpImagePoint> &pts,
vpMatrix &A,
vpMatrix &b)
{
const unsigned int nbPts = static_cast<unsigned int>(pts.size());
const unsigned int nbCoeffs = degree + 1;
std::vector<vpImagePoint> normalizedPts;
for (const auto &pt: pts) {
normalizedPts.push_back(
vpImagePoint(pt.get_i() / height, pt.get_j() / width));
}
A.
resize(nbPts, nbCoeffs, 1.);
for (unsigned int i = 0; i < nbPts; ++i) {
double u = normalizedPts[i].get_u();
double v = normalizedPts[i].get_v();
for (unsigned int j = 0; j < nbCoeffs; ++j) {
A[i][j] = std::pow(u, j);
}
b[i][0] = v;
}
}
#ifdef VISP_HAVE_DISPLAY
template<typename T>
const unsigned int &vertPosLegend, const unsigned int &horPosLegend)
{
for (unsigned int u = 0; u < width; ++u) {
int v = static_cast<int>(eval(u));
}
}
#endif
private:
unsigned int m_degree;
unsigned int m_height;
unsigned int m_width;
};
vpColVector computeABC(
const double &x0,
const double &y0,
const double &x1,
const double &y1)
{
double b = (y1 - y0)/(-0.5*(x1 * x1/x0) + x1 -0.5 * x0);
double a = -b / (2. * x0);
double c = y0 - 0.5 * b * x0;
}
vpColVector computeABCD(
const double &x0,
const double &y0,
const double &x1,
const double &y1)
{
double factorA = -2. / (3. * (x1 + x0));
double factorC = -1. * ((-2. * std::pow(x0, 2))/(x1 + x0) + 2 * x0);
double b = (y1 - y0)/(factorA * (std::pow(x1, 3) - std::pow(x0, 3)) + (std::pow(x1, 2) - std::pow(x0, 2)) + (x1 - x0) * factorC);
double a = factorA * b;
double c = factorC * b;
double d = y0-(a * std::pow(x0, 3) + b * std::pow(x0, 2) + c * x0);
}
double computeY(
const double &x,
const vpColVector &coeffs)
{
double y = 0.;
unsigned int nbCoeffs = coeffs.
size();
for (unsigned int i = 0; i < nbCoeffs; ++i) {
y += coeffs[i] * std::pow(x, i);
}
return y;
}
std::vector<vpImagePoint> generateSimulatedImage(
const double &xmin,
const double &xmax,
const double &step,
const vpColVector &coeffs)
{
std::vector<vpImagePoint> points;
for (double x = xmin; x <= xmax; x += step) {
double y = computeY(x, coeffs);
points.push_back(pt);
}
return points;
}
#ifdef VISP_HAVE_DISPLAY
template<typename T>
void displayGeneratedImage(
const vpImage<T> &I,
const std::vector<vpImagePoint> &pts,
const vpColor &color,
const std::string &legend, const unsigned int &vertOffset, const unsigned int &horOffset)
{
unsigned int nbPts = static_cast<unsigned int>(pts.size());
for (unsigned int i = 1; i < nbPts; ++i) {
}
}
#endif
vpParabolaModel computeInitialGuess(const std::vector<vpImagePoint> &pts, const unsigned int °ree, const unsigned int &height, const unsigned int &width)
{
vpParabolaModel::fillSystem(degree, height, width, pts, A, b);
vpParabolaModel model(X, height, width);
return model;
}
double evaluate(const std::vector<vpImagePoint> &pts, const vpParabolaModel &model)
{
double rmse = 0.;
size_t sizePts = pts.size();
for (size_t i = 0; i < sizePts; ++i) {
double v_model = model.eval(u);
double error = v - v_model;
double squareError = error * error;
rmse += squareError;
}
rmse = std::sqrt(rmse / static_cast<double>(pts.size()));
return rmse;
}
{
return updatedCoeffs;
}
class vpAverageFunctor
{
public:
vpAverageFunctor(const unsigned int °ree, const unsigned int &height, const unsigned int &width)
: m_degree(degree)
, m_height(height)
, m_width(width)
{ }
vpColVector averagePolynomials(
const std::vector<vpColVector> &particles,
const std::vector<double> &weights,
const vpParticleFilter<std::vector<vpImagePoint>>::vpStateAddFunction &)
{
const unsigned int nbParticles = static_cast<unsigned int>(particles.size());
const double nbParticlesAsDOuble = static_cast<double>(nbParticles);
const double sumWeight = std::accumulate(weights.begin(), weights.end(), 0.);
const double nbPointsForAverage = 10. * nbParticlesAsDOuble;
std::vector<vpImagePoint> initPoints;
for (unsigned int i = 0; i < nbParticles; ++i) {
double nbPoints = std::floor(weights[i] * nbPointsForAverage / sumWeight);
if (nbPoints > 1.) {
vpParabolaModel curve(particles[i], m_height, m_width);
double widthAsDouble = static_cast<double>(m_width);
double step = widthAsDouble / (nbPoints - 1.);
for (double u = 0.; u < widthAsDouble; u += step) {
double v = curve.eval(u);
initPoints.push_back(pt);
}
}
else if (nbPoints == 1.) {
vpParabolaModel curve(particles[i], m_height, m_width);
double u = static_cast<double>(m_width) / 2.;
double v = curve.eval(u);
initPoints.push_back(pt);
}
}
vpParabolaModel::fillSystem(m_degree, m_height, m_width, initPoints, A, b);
return vpParabolaModel(X, m_height, m_width).toVpColVector();
}
private:
unsigned int m_degree;
unsigned int m_height;
unsigned int m_width;
};
class vpLikelihoodFunctor
{
public:
vpLikelihoodFunctor(const double &stdev, const unsigned int &height, const unsigned int &width)
: m_height(height)
, m_width(width)
{
double sigmaDistanceSquared = stdev * stdev;
m_constantDenominator = 1. / std::sqrt(2. * M_PI * sigmaDistanceSquared);
m_constantExpDenominator = -1. / (2. * sigmaDistanceSquared);
}
double likelihood(
const vpColVector &coeffs,
const std::vector<vpImagePoint> &meas)
{
double likelihood = 0.;
unsigned int nbPoints = static_cast<unsigned int>(meas.size());
vpParabolaModel model(coeffs, m_height, m_width);
double rmse = evaluate(meas, model);
likelihood = std::exp(rmse * m_constantExpDenominator) * m_constantDenominator;
likelihood = std::min(likelihood, 1.0);
likelihood = std::max(likelihood, 0.);
return likelihood;
}
private:
double m_constantDenominator;
double m_constantExpDenominator;
unsigned int m_height;
unsigned int m_width;
};
}
TEST_CASE("2nd-degree", "[vpParticleFilter][Polynomial interpolation]")
{
const unsigned int width = 600;
const unsigned int height = 400;
const unsigned int degree = 2;
const unsigned int nbInitPoints = 10;
const uint64_t seedCurve = 4224;
const uint64_t seedInitPoints = 2112;
const unsigned int nbTestRepet = 10;
const unsigned int nbWarmUpIter = 10;
const unsigned int nbEvalIter = 20;
const double dt = 0.040;
const int32_t seedShuffle = 4221;
const double ampliMaxLikelihood = 16.;
const double sigmaLikelihood = ampliMaxLikelihood / 3.;
const unsigned int nbParticles = 300;
const double ratioAmpliMax(0.25);
const long seedPF = 4221;
const int nbThreads = 1;
SECTION("Noise-free", "The init points are directly extracted from the curve points, without any additional noise")
{
const double maxToleratedError = 5.;
double x0 = rngCurvePoints.uniform(0., width);
double x1 = rngCurvePoints.uniform(0., width);
double y0 = rngCurvePoints.uniform(0., height);
double y1 = rngCurvePoints.uniform(0., height);
std::vector<vpImagePoint> curvePoints = generateSimulatedImage(0, width, 1., coeffs);
#ifdef VISP_HAVE_DISPLAY
std::shared_ptr<vpDisplay> pDisplay;
if (opt_display) {
}
#endif
for (unsigned int iter = 0; iter < nbTestRepet; ++iter) {
std::vector<vpImagePoint> initPoints;
for (unsigned int j = 0; j < nbInitPoints; ++j) {
initPoints.push_back(suffledVector[j]);
}
vpParabolaModel modelInitial = computeInitialGuess(initPoints, degree, height, width);
std::vector<double> stdevsPF;
for (unsigned int i = 0; i < degree + 1; ++i) {
stdevsPF.push_back(ratioAmpliMax * X0[0] / 3.);
}
vpLikelihoodFunctor likelihoodFtor(sigmaLikelihood, height, width);
using std::placeholders::_1;
using std::placeholders::_2;
vpAverageFunctor averageCpter(degree, height, width);
using std::placeholders::_3;
filter.init(X0, processFunc, likelihoodFunc, checkResamplingFunc, resamplingFunc, meanFunc);
for (unsigned int i = 0; i < nbWarmUpIter; ++i) {
filter.filter(curvePoints, dt);
}
double meanError = 0.;
for (unsigned int i = 0; i < nbEvalIter; ++i) {
filter.filter(curvePoints, dt);
vpParabolaModel model(Xest, height, width);
double rmse = evaluate(curvePoints, model);
meanError += rmse;
#ifdef VISP_HAVE_DISPLAY
if (opt_display) {
displayGeneratedImage(I, curvePoints,
vpColor::red,
"GT", 20, 20);
}
#endif
}
meanError /= static_cast<double>(nbEvalIter);
std::cout << "Mean(rmse) = " << meanError << std::endl;
CHECK(meanError <= maxToleratedError);
}
}
SECTION("Noisy", "Noise is added to the init points")
{
const double maxToleratedError = 12.;
double x0 = rngCurvePoints.uniform(0., width);
double x1 = rngCurvePoints.uniform(0., width);
double y0 = rngCurvePoints.uniform(0., height);
double y1 = rngCurvePoints.uniform(0., height);
std::vector<vpImagePoint> curvePoints = generateSimulatedImage(0, width, 1., coeffs);
#ifdef VISP_HAVE_DISPLAY
std::shared_ptr<vpDisplay> pDisplay;
if (opt_display) {
}
#endif
const double ampliMaxInitNoise = 24.;
const double stdevInitNoise = ampliMaxInitNoise / 3.;
vpGaussRand rngInitNoise(stdevInitNoise, 0., seedInitPoints);
for (unsigned int iter = 0; iter < nbTestRepet; ++iter) {
std::vector<vpImagePoint> initPoints;
for (unsigned int j = 0; j < nbInitPoints; ++j) {
vpImagePoint noisyPt(suffledVector[j].get_i() + rngInitNoise(), suffledVector[j].get_j() + rngInitNoise());
initPoints.push_back(noisyPt);
}
vpParabolaModel modelInitial = computeInitialGuess(initPoints, degree, height, width);
std::vector<double> stdevsPF;
for (unsigned int i = 0; i < degree + 1; ++i) {
stdevsPF.push_back(ratioAmpliMax * X0[0] / 3.);
}
vpLikelihoodFunctor likelihoodFtor(sigmaLikelihood, height, width);
using std::placeholders::_1;
using std::placeholders::_2;
vpAverageFunctor averageCpter(degree, height, width);
using std::placeholders::_3;
filter.init(X0, processFunc, likelihoodFunc, checkResamplingFunc, resamplingFunc, meanFunc);
for (unsigned int i = 0; i < nbWarmUpIter; ++i) {
filter.filter(curvePoints, dt);
}
double meanError = 0.;
for (unsigned int i = 0; i < nbEvalIter; ++i) {
filter.filter(curvePoints, dt);
vpParabolaModel model(Xest, height, width);
double rmse = evaluate(curvePoints, model);
meanError += rmse;
#ifdef VISP_HAVE_DISPLAY
if (opt_display) {
displayGeneratedImage(I, curvePoints,
vpColor::red,
"GT", 20, 20);
}
#endif
}
meanError /= static_cast<double>(nbEvalIter);
std::cout << "Mean(rmse) = " << meanError << std::endl;
CHECK(meanError <= maxToleratedError);
}
}
}
TEST_CASE("3rd-degree", "[vpParticleFilter][Polynomial interpolation]")
{
const unsigned int width = 600;
const unsigned int height = 400;
const unsigned int degree = 3;
const unsigned int nbInitPoints = 10;
const uint64_t seedCurve = 4224;
const uint64_t seedInitPoints = 2112;
const unsigned int nbTestRepet = 10;
const unsigned int nbWarmUpIter = 10;
const unsigned int nbEvalIter = 20;
const double dt = 0.040;
const int32_t seedShuffle = 4221;
const double ampliMaxLikelihood = 6.;
const double sigmaLikelihood = ampliMaxLikelihood / 3.;
const unsigned int nbParticles = 300;
const double ratioAmpliMax(0.21);
const long seedPF = 4221;
const int nbThreads = 1;
SECTION("Noise-free", "The init points are directly extracted from the curve points, without any additional noise")
{
const double maxToleratedError = 10.;
double x0 = rngCurvePoints.uniform(0., width);
double x1 = rngCurvePoints.uniform(0., width);
double y0 = rngCurvePoints.uniform(0., height);
double y1 = rngCurvePoints.uniform(0., height);
std::vector<vpImagePoint> curvePoints = generateSimulatedImage(0, width, 1., coeffs);
#ifdef VISP_HAVE_DISPLAY
std::shared_ptr<vpDisplay> pDisplay;
if (opt_display) {
}
#endif
for (unsigned int iter = 0; iter < nbTestRepet; ++iter) {
std::vector<vpImagePoint> initPoints;
for (unsigned int j = 0; j < nbInitPoints; ++j) {
initPoints.push_back(suffledVector[j]);
}
vpParabolaModel modelInitial = computeInitialGuess(initPoints, degree, height, width);
std::vector<double> stdevsPF;
for (unsigned int i = 0; i < degree + 1; ++i) {
stdevsPF.push_back(ratioAmpliMax * std::pow(0.1, i) * X0[0] / 3.);
}
vpLikelihoodFunctor likelihoodFtor(sigmaLikelihood, height, width);
using std::placeholders::_1;
using std::placeholders::_2;
vpAverageFunctor averageCpter(degree, height, width);
using std::placeholders::_3;
filter.init(X0, processFunc, likelihoodFunc, checkResamplingFunc, resamplingFunc, meanFunc);
for (unsigned int i = 0; i < nbWarmUpIter; ++i) {
filter.filter(curvePoints, dt);
}
double meanError = 0.;
for (unsigned int i = 0; i < nbEvalIter; ++i) {
filter.filter(curvePoints, dt);
vpParabolaModel model(Xest, height, width);
double rmse = evaluate(curvePoints, model);
meanError += rmse;
#ifdef VISP_HAVE_DISPLAY
if (opt_display) {
displayGeneratedImage(I, curvePoints,
vpColor::red,
"GT", 20, 20);
}
#endif
}
meanError /= static_cast<double>(nbEvalIter);
std::cout << "Mean(rmse) = " << meanError << std::endl;
CHECK(meanError <= maxToleratedError);
}
}
SECTION("Noisy", "Noise is added to the init points")
{
const double maxToleratedError = 17.;
double x0 = rngCurvePoints.uniform(0., width);
double x1 = rngCurvePoints.uniform(0., width);
double y0 = rngCurvePoints.uniform(0., height);
double y1 = rngCurvePoints.uniform(0., height);
std::vector<vpImagePoint> curvePoints = generateSimulatedImage(0, width, 1., coeffs);
#ifdef VISP_HAVE_DISPLAY
std::shared_ptr<vpDisplay> pDisplay;
if (opt_display) {
}
#endif
const double ampliMaxInitNoise = 1.5;
const double stdevInitNoise = ampliMaxInitNoise / 3.;
vpGaussRand rngInitNoise(stdevInitNoise, 0., seedInitPoints);
for (unsigned int iter = 0; iter < nbTestRepet; ++iter) {
std::vector<vpImagePoint> initPoints;
for (unsigned int j = 0; j < nbInitPoints * 4; ++j) {
vpImagePoint noisyPt(suffledVector[j].get_i() + rngInitNoise(), suffledVector[j].get_j() + rngInitNoise());
initPoints.push_back(noisyPt);
}
vpParabolaModel modelInitial = computeInitialGuess(initPoints, degree, height, width);
std::vector<double> stdevsPF;
for (unsigned int i = 0; i < degree + 1; ++i) {
stdevsPF.push_back(ratioAmpliMax * std::pow(.05, i) * X0[0] / 3.);
}
vpLikelihoodFunctor likelihoodFtor(sigmaLikelihood * 2., height, width);
using std::placeholders::_1;
using std::placeholders::_2;
vpAverageFunctor averageCpter(degree, height, width);
using std::placeholders::_3;
filter.init(X0, processFunc, likelihoodFunc, checkResamplingFunc, resamplingFunc, meanFunc);
for (unsigned int i = 0; i < nbWarmUpIter * 5; ++i) {
filter.filter(curvePoints, dt);
}
double meanError = 0.;
for (unsigned int i = 0; i < nbEvalIter; ++i) {
filter.filter(curvePoints, dt);
vpParabolaModel model(Xest, height, width);
double rmse = evaluate(curvePoints, model);
meanError += rmse;
#ifdef VISP_HAVE_DISPLAY
if (opt_display) {
displayGeneratedImage(I, curvePoints,
vpColor::red,
"GT", 20, 20);
}
#endif
}
meanError /= static_cast<double>(nbEvalIter);
std::cout << "Mean(rmse) = " << meanError << std::endl;
CHECK(meanError <= maxToleratedError);
}
}
}
int main(int argc, char *argv[])
{
Catch::Session session;
auto cli = session.cli()
| Catch::Clara::Opt(opt_display)["--display"]("Activate debug display");
session.cli(cli);
session.applyCommandLine(argc, argv);
int numFailed = session.run();
return numFailed;
}
#else
#include <iostream>
int main() { return EXIT_SUCCESS; }
#endif
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.
unsigned int getRows() const
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.