Example on how to use a Particle Filter (PF) on a complex non-linear use-case. The system is an object, whose coordinate frame origin is the point O, on which are sticked four markers. The object revolves in a plane parallel to the ground around a fixed point W whose coordinate frame is the world frame. The scene is observed by a pinhole camera whose coordinate frame has the origin C and which is fixed to the ceiling.
Some noise is added to the measurement vector to simulate measurements which are not perfect.
#include <visp3/core/vpConfig.h>
#include <visp3/core/vpCameraParameters.h>
#include <visp3/core/vpGaussRand.h>
#include <visp3/core/vpHomogeneousMatrix.h>
#include <visp3/core/vpMeterPixelConversion.h>
#include <visp3/core/vpPixelMeterConversion.h>
#ifdef VISP_HAVE_DISPLAY
#include <visp3/gui/vpPlot.h>
#include <visp3/gui/vpDisplayFactory.h>
#endif
#include <visp3/vision/vpPose.h>
#include <visp3/core/vpParticleFilter.h>
#ifdef ENABLE_VISP_NAMESPACE
#endif
#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11)
{
x_kPlus1[0] = x[0] * std::cos(x[3]) - x[1] * std::sin(x[3]);
x_kPlus1[1] = x[0] * std::sin(x[3]) + x[1] * std::cos(x[3]);
x_kPlus1[2] = x[2];
x_kPlus1[3] = x[3];
return x_kPlus1;
}
{
public:
vpObjectSimulator(
const double &R,
const double &w,
const double &phi,
const double &wZ)
: m_R(R)
, m_w(w)
, m_phi(phi)
, m_wZ(wZ)
{ }
{
wX[0] = m_R * std::cos(m_w * t + m_phi);
wX[1] = m_R * std::sin(m_w * t + m_phi);
wX[2] = m_wZ;
return wX;
}
private:
double m_R;
double m_w;
double m_phi;
const double m_wZ;
};
{
public:
const std::vector<vpColVector> &markers, const double &sigmaDistance,
const double &noise_stdev, const long &seed)
: m_cam(cam)
, m_cMw(cMw)
, m_wRo(wRo)
, m_markers(markers)
, m_rng(noise_stdev, 0., seed)
{
double sigmaDistanceSquared = sigmaDistance * sigmaDistance;
m_constantDenominator = 1. / std::sqrt(2. * M_PI * sigmaDistanceSquared);
m_constantExpDenominator = -1. / (2. * sigmaDistanceSquared);
}
{
unsigned int nbMarkers = static_cast<unsigned int>(m_markers.size());
double likelihood = 0.;
const unsigned int sizePt2D = 2;
const unsigned int idX = 0, idY = 1, idZ = 2;
double sumError = 0.;
for (unsigned int i = 0; i < nbMarkers; ++i) {
vpImagePoint measPt(meas[sizePt2D * i + 1], meas[sizePt2D * i]);
sumError += error;
}
likelihood = std::exp(m_constantExpDenominator * sumError / static_cast<double>(nbMarkers)) * m_constantDenominator;
likelihood = std::min(likelihood, 1.0);
likelihood = std::max(likelihood, 0.);
return likelihood;
}
{
unsigned int nbMarkers = static_cast<unsigned int>(m_markers.size());
for (unsigned int i = 0; i < nbMarkers; ++i) {
double u = 0., v = 0.;
meas[2*i] = u;
meas[2*i + 1] = v;
}
return meas;
}
{
unsigned int nbMarkers = static_cast<unsigned int>(m_markers.size());
for (unsigned int i = 0; i < nbMarkers; ++i) {
double u = 0., v = 0.;
meas[2*i] = u;
meas[2*i + 1] = v;
}
return meas;
}
{
unsigned int sizeMeasurement = measurementsGT.
size();
for (unsigned int i = 0; i < sizeMeasurement; ++i) {
measurementsNoisy[i] += m_rng();
}
return measurementsNoisy;
}
private:
std::vector<vpColVector> m_markers;
double m_constantDenominator;
double m_constantExpDenominator;
};
{
double x = 0, y = 0;
for (unsigned int i = 0; i < point.size(); i++) {
point[i].set_x(x);
point[i].set_y(y);
}
return cMo;
}
{
static const int SOFTWARE_CONTINUE = 42;
bool m_useDisplay;
unsigned int m_nbStepsWarmUp;
unsigned int m_nbSteps;
unsigned int m_N;
double m_maxDistanceForLikelihood;
double m_ampliMaxX;
double m_ampliMaxY;
double m_ampliMaxZ;
double m_ampliMaxOmega;
long m_seedPF;
unsigned int m_nbThreads;
: m_useDisplay(true)
, m_nbStepsWarmUp(200)
, m_nbSteps(300)
, m_N(500)
, m_maxDistanceForLikelihood(10.)
, m_ampliMaxX(0.02)
, m_ampliMaxY(0.02)
, m_ampliMaxZ(0.01)
, m_ampliMaxOmega(0.02)
, m_seedPF(4224)
, m_nbThreads(1)
{ }
int parseArgs(const int argc, const char *argv[])
{
int i = 1;
while (i < argc) {
std::string arg(argv[i]);
if ((arg == "--nb-steps-main") && ((i+1) < argc)) {
m_nbSteps = std::atoi(argv[i + 1]);
++i;
}
else if ((arg == "--nb-steps-warmup") && ((i+1) < argc)) {
m_nbStepsWarmUp = std::atoi(argv[i + 1]);
++i;
}
else if ((arg == "--max-distance-likelihood") && ((i+1) < argc)) {
m_maxDistanceForLikelihood = std::atof(argv[i + 1]);
++i;
}
else if (((arg == "-N") || (arg == "--nb-particles")) && ((i+1) < argc)) {
m_N = std::atoi(argv[i + 1]);
++i;
}
else if ((arg == "--seed") && ((i+1) < argc)) {
m_seedPF = std::atoi(argv[i + 1]);
++i;
}
else if ((arg == "--nb-threads") && ((i+1) < argc)) {
m_nbThreads = std::atoi(argv[i + 1]);
++i;
}
else if ((arg == "--ampli-max-X") && ((i+1) < argc)) {
m_ampliMaxX = std::atof(argv[i + 1]);
++i;
}
else if ((arg == "--ampli-max-Y") && ((i+1) < argc)) {
m_ampliMaxY = std::atof(argv[i + 1]);
++i;
}
else if ((arg == "--ampli-max-Z") && ((i+1) < argc)) {
m_ampliMaxZ = std::atof(argv[i + 1]);
++i;
}
else if ((arg == "--ampli-max-omega") && ((i+1) < argc)) {
m_ampliMaxOmega = std::atof(argv[i + 1]);
++i;
}
else if (arg == "-d") {
m_useDisplay = false;
}
else if ((arg == "-h") || (arg == "--help")) {
printUsage(std::string(argv[0]));
defaultArgs.
printDetails();
return 0;
}
else {
std::cout << "WARNING: unrecognised argument \"" << arg << "\"";
if (i + 1 < argc) {
std::cout << " with associated value(s) { ";
int nbValues = 0;
int j = i + 1;
bool hasToRun = true;
while ((j < argc) && hasToRun) {
std::string nextValue(argv[j]);
if (nextValue.find("--") == std::string::npos) {
std::cout << nextValue << " ";
++nbValues;
}
else {
hasToRun = false;
}
++j;
}
std::cout << "}" << std::endl;
i += nbValues;
}
}
++i;
}
return SOFTWARE_CONTINUE;
}
private:
void printUsage(const std::string &softName)
{
std::cout << "SYNOPSIS" << std::endl;
std::cout << " " << softName << " [--nb-steps-main <uint>] [--nb-steps-warmup <uint>]" << std::endl;
std::cout << " [--max-distance-likelihood <double>] [-N, --nb-particles <uint>] [--seed <int>] [--nb-threads <int>]" << std::endl;
std::cout << " [--ampli-max-X <double>] [--ampli-max-Y <double>] [--ampli-max-Z <double>] [--ampli-max-omega <double>]" << std::endl;
std::cout << " [-d, --no-display] [-h]" << std::endl;
}
void printDetails()
{
std::cout << std::endl << std::endl;
std::cout << "DETAILS" << std::endl;
std::cout << " --nb-steps-main" << std::endl;
std::cout << " Number of steps in the main loop." << std::endl;
std::cout << " Default: " << m_nbSteps << std::endl;
std::cout << std::endl;
std::cout << " --nb-steps-warmup" << std::endl;
std::cout << " Number of steps in the warmup loop." << std::endl;
std::cout << " Default: " << m_nbStepsWarmUp << std::endl;
std::cout << std::endl;
std::cout << " --max-distance-likelihood" << std::endl;
std::cout << " Maximum mean distance of the projection of the markers corresponding" << std::endl;
std::cout << " to a particle with the measurements. Above this value, the likelihood of the particle is 0." << std::endl;
std::cout << " Default: " << m_maxDistanceForLikelihood << std::endl;
std::cout << std::endl;
std::cout << " -N, --nb-particles" << std::endl;
std::cout << " Number of particles of the Particle Filter." << std::endl;
std::cout << " Default: " << m_N << std::endl;
std::cout << std::endl;
std::cout << " --seed" << std::endl;
std::cout << " Seed to initialize the Particle Filter." << std::endl;
std::cout << " Use a negative value makes to use the current timestamp instead." << std::endl;
std::cout << " Default: " << m_seedPF << std::endl;
std::cout << std::endl;
std::cout << " --nb-threads" << std::endl;
std::cout << " Set the number of threads to use in the Particle Filter (only if OpenMP is available)." << std::endl;
std::cout << " Use a negative value to use the maximum number of threads instead." << std::endl;
std::cout << " Default: " << m_nbThreads << std::endl;
std::cout << std::endl;
std::cout << " --ampli-max-X" << std::endl;
std::cout << " Maximum amplitude of the noise added to a particle along the X-axis." << std::endl;
std::cout << " Default: " << m_ampliMaxX << std::endl;
std::cout << std::endl;
std::cout << " --ampli-max-Y" << std::endl;
std::cout << " Maximum amplitude of the noise added to a particle along the Y-axis." << std::endl;
std::cout << " Default: " << m_ampliMaxY << std::endl;
std::cout << std::endl;
std::cout << " --ampli-max-Z" << std::endl;
std::cout << " Maximum amplitude of the noise added to a particle along the Z-axis." << std::endl;
std::cout << " Default: " << m_ampliMaxZ << std::endl;
std::cout << std::endl;
std::cout << " --ampli-max-omega" << std::endl;
std::cout << " Maximum amplitude of the noise added to a particle affecting the pulsation of the motion." << std::endl;
std::cout << " Default: " << m_ampliMaxOmega << std::endl;
std::cout << std::endl;
std::cout << " -d, --no-display" << std::endl;
std::cout << " Deactivate display." << std::endl;
std::cout << " Default: display is ";
#ifdef VISP_HAVE_DISPLAY
std::cout << "ON" << std::endl;
#else
std::cout << "OFF" << std::endl;
#endif
std::cout << std::endl;
std::cout << " -h, --help" << std::endl;
std::cout << " Display this help." << std::endl;
std::cout << std::endl;
}
};
int main(const int argc, const char *argv[])
{
return returnCode;
}
const double dt = 0.001;
const double sigmaMeasurements = 2.;
const double radius = 0.25;
const double w = 2 * M_PI * 10;
const double phi = 2;
const long seedMeasurements = 42;
const std::vector<vpColVector> markers = {
vpColVector({-0.05, 0.05, 0., 1.})
unsigned int nbMarkers = static_cast<unsigned int>(markers.size());
std::vector<vpPoint> markersAsVpPoint;
for (unsigned int i = 0; i < nbMarkers; ++i) {
markersAsVpPoint.push_back(
vpPoint(marker[0], marker[1], marker[2]));
}
cMw[0][0] = 1.; cMw[0][1] = 0.; cMw[0][2] = 0.; cMw[0][3] = 0.2;
cMw[1][0] = 0.; cMw[1][1] = -1.; cMw[1][2] = 0.; cMw[1][3] = 0.3;
cMw[2][0] = 0.; cMw[2][1] = 0.; cMw[2][2] = -1.; cMw[2][3] = 1.;
wMo[0][0] = 1.; wMo[0][1] = 0.; wMo[0][2] = 0.; wMo[0][3] = radius;
wMo[1][0] = 0.; wMo[1][1] = 1.; wMo[1][2] = 0.; wMo[1][3] = 0;
wMo[2][0] = 0.; wMo[2][1] = 0.; wMo[2][2] = 1.; wMo[2][3] = 0.2;
const double wZ = wMo[2][3];
const double px = 600., py = 600., u0 = 320., v0 = 240.;
const double sigmaLikelihood = maxDistanceForLikelihood / 3.;
const unsigned int nbParticles = args.
m_N;
const std::vector<double> stdevsPF = { ampliMaxX/3., ampliMaxY/3., ampliMaxZ/3., ampliMaxOmega/3. };
X0[0] = radius;
X0[1] = 0.;
X0[2] = 0.95 * wZ;
X0[3] = 0.95 * w * dt;
vpMarkersMeasurements markerMeas(cam, cMw, wRo, markers, sigmaLikelihood, sigmaMeasurements, seedMeasurements);
using std::placeholders::_1;
using std::placeholders::_2;
filter.init(X0, processFunc, likelihoodFunc, checkResamplingFunc, resamplingFunc);
#ifdef VISP_HAVE_DISPLAY
plot->
setTitle(0,
"Position of the robot wX");
plot->
setUnitX(0,
"Position along x(m)");
plot->
setUnitY(0,
"Position along y (m)");
plot->
initRange(0, -1.25 * radius, 1.25 * radius, -1.25 * radius, 1.25 * radius);
}
#endif
#ifdef VISP_HAVE_DISPLAY
std::shared_ptr<vpDisplay> d;
}
#endif
object_pos[3] = 1.;
double averageFilteringTime = 0.;
for (unsigned int i = 0; i < nbStepsWarmUp; ++i) {
object_pos = object.move(dt * static_cast<double>(i));
vpColVector z = markerMeas.measureWithNoise(object_pos);
filter.filter(z, dt);
}
const double invNbSteps = 1. / static_cast<double>(nbSteps);
double meanErrorFilter = 0.;
double meanErrorNoise = 0.;
for (unsigned int i = 0; i < nbSteps; ++i) {
object_pos = object.move(dt * static_cast<double>(i));
vpColVector z = markerMeas.measureWithNoise(object_pos);
filter.filter(z, dt);
std::vector<vpImagePoint> ip;
for (unsigned int id = 0; id < nbMarkers; ++id) {
ip.push_back(markerProjNoisy);
}
#ifdef VISP_HAVE_DISPLAY
plot->
plot(0, 0, object_pos[0], object_pos[1]);
plot->
plot(0, 1, Xest[0], Xest[1]);
double wXnoisy = wMo_noisy[0][3];
double wYnoisy = wMo_noisy[1][3];
plot->
plot(0, 2, wXnoisy, wYnoisy);
vpColVector zFilt = markerMeas.state_to_measurement(Xest);
for (unsigned int id = 0; id < nbMarkers; ++id) {
}
ipText.set_i(ipText.get_i() + 20);
ipText.set_i(ipText.get_i() + 20);
}
#endif
double error = std::sqrt(std::pow(Xest[0] - object_pos[0], 2) + std::pow(Xest[1] - object_pos[1], 2) + std::pow(Xest[2] - object_pos[2], 2));
meanErrorFilter += invNbSteps * error;
error = std::sqrt(std::pow(wMo_noisy[0][3] - object_pos[0], 2) + std::pow(wMo_noisy[1][3] - object_pos[1], 2) + std::pow(wMo_noisy[2][3] - object_pos[2], 2));
meanErrorNoise += invNbSteps * error;
}
averageFilteringTime = averageFilteringTime / (static_cast<double>(nbSteps) + static_cast<double>(nbStepsWarmUp));
std::cout << "Mean error filter = " << meanErrorFilter << std::endl;
std::cout << "Mean error noise = " << meanErrorNoise << std::endl;
std::cout << "Mean filtering time = " << averageFilteringTime << "us" << std::endl;
std::cout << "Press Enter to quit..." << std::endl;
std::cin.get();
}
#ifdef VISP_HAVE_DISPLAY
if (plot != nullptr) {
delete plot;
}
#endif
if (meanErrorFilter > meanErrorNoise) {
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
#else
int main()
{
std::cout << "This example is only available if you compile ViSP in C++11 standard or higher." << std::endl;
return EXIT_SUCCESS;
}
#endif
unsigned int size() const
Return the number of elements of the 2D array.
Generic class defining intrinsic camera parameters.
void initPersProjWithoutDistortion(double px, double py, double u0, double v0)
Implementation of column vector and the associated operations.
static const vpColor blue
static const vpColor black
static void display(const vpImage< unsigned char > &I)
static void displayCross(const vpImage< unsigned char > &I, const vpImagePoint &ip, unsigned int size, const vpColor &color, unsigned int thickness=1)
static void flush(const vpImage< unsigned char > &I)
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.
Implementation of an homogeneous matrix and operations on such kind of matrices.
vpHomogeneousMatrix & build(const vpTranslationVector &t, const vpRotationMatrix &R)
vpHomogeneousMatrix inverse() const
void extract(vpRotationMatrix &R) const
Class that defines a 2D point in an image. This class is useful for image processing and stores only ...
static double sqrDistance(const vpImagePoint &iP1, const vpImagePoint &iP2)
double likelihoodParticle(const vpColVector &x, const vpColVector &meas)
[Likelihood_function]
static void convertPoint(const vpCameraParameters &cam, const double &x, const double &y, double &u, double &v)
The class permits to use a Particle Filter.
std::function< vpParticlesWithWeights(const std::vector< vpColVector > &, const std::vector< double > &)> vpResamplingFunction
Function that takes as argument the vector of particles and the vector of associated weights....
std::function< vpColVector(const vpColVector &, const double &)> vpProcessFunction
Process model function, which projects a particle forward in time. The first argument is a particle,...
std::function< bool(const unsigned int &, const std::vector< double > &)> vpResamplingConditionFunction
Function that takes as argument the number of particles and the vector of weights associated to each ...
std::function< double(const vpColVector &, const MeasurementsType &)> vpLikelihoodFunction
Likelihood function, which evaluates the likelihood of a particle with regard to the measurements....
static void convertPoint(const vpCameraParameters &cam, const double &u, const double &v, double &x, double &y)
This class enables real time drawing of 2D or 3D graphics. An instance of the class open a window whi...
void initGraph(unsigned int graphNum, unsigned int curveNbr)
void setUnitY(unsigned int graphNum, const std::string &unity)
void initRange(unsigned int graphNum, double xmin, double xmax, double ymin, double ymax)
void setLegend(unsigned int graphNum, unsigned int curveNum, const std::string &legend)
void plot(unsigned int graphNum, unsigned int curveNum, double x, double y)
void setUnitX(unsigned int graphNum, const std::string &unitx)
void setColor(unsigned int graphNum, unsigned int curveNum, vpColor color)
void setTitle(unsigned int graphNum, const std::string &title)
Class that defines a 3D point in the object frame and allows forward projection of a 3D point in the ...
Class used for pose computation from N points (pose from point only). Some of the algorithms implemen...
void addPoint(const vpPoint &P)
@ DEMENTHON_LAGRANGE_VIRTUAL_VS
bool computePose(vpPoseMethodType method, vpHomogeneousMatrix &cMo, FuncCheckValidityPose func=nullptr)
Implementation of a rotation matrix and operations on such kind of matrices.
Class that consider the case of a translation vector.
std::shared_ptr< vpDisplay > createDisplay()
Return a smart pointer vpDisplay specialization if a GUI library is available or nullptr otherwise.
VISP_EXPORT int wait(double t0, double t)
VISP_EXPORT double measureTimeMicros()
bool m_useDisplay
If true, activate the plot and the renderer if VISP_HAVE_DISPLAY is defined.
unsigned int m_nbSteps
?umber of steps for the main loop.
static const int SOFTWARE_CONTINUE
unsigned int m_nbThreads
Number of thread to use in the Particle Filter.
double m_ampliMaxY
Amplitude max of the noise for the state component corresponding to the Y coordinate.
unsigned int m_N
The number of particles.
double m_maxDistanceForLikelihood
The maximum allowed distance between a particle and the measurement, leading to a likelihood equal to...
double m_ampliMaxZ
Amplitude max of the noise for the state component corresponding to the Z coordinate.
int parseArgs(const int argc, const char *argv[])
double m_ampliMaxOmega
Amplitude max of the noise for the state component corresponding to the pulsation.
unsigned int m_nbStepsWarmUp
Number of steps for the warmup phase.
double m_ampliMaxX
Amplitude max of the noise for the state component corresponding to the X coordinate.
long m_seedPF
Seed for the random generators of the PF.