#include <visp3/core/vpConfig.h>
#include <visp3/core/vpException.h>
#include <visp3/core/vpMouseButton.h>
#include <visp3/core/vpTime.h>
#include <visp3/core/vpUniRand.h>
#ifdef VISP_HAVE_DISPLAY
#include <visp3/gui/vpPlot.h>
#endif
#include <visp3/core/vpParticleFilter.h>
#include "vpTutoCommonData.h"
#include "vpTutoMeanSquareFitting.h"
#include "vpTutoParabolaModel.h"
#include "vpTutoSegmentation.h"
#ifdef ENABLE_VISP_NAMESPACE
#endif
#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) && defined(VISP_HAVE_DISPLAY)
#ifndef DOXYGEN_SHOULD_SKIP_THIS
namespace tutorial
{
double evaluate(
const vpImagePoint &pt,
const vpTutoParabolaModel &model)
{
double v_model = model.eval(u);
double error = v - v_model;
double squareError = error * error;
return squareError;
}
double evaluate(
const vpColVector &coeffs,
const unsigned int &height,
const unsigned int &width,
const std::vector<vpImagePoint> &pts)
{
unsigned int nbPts = static_cast<unsigned int>(pts.size());
vpTutoParabolaModel model(coeffs, height, width);
for (unsigned int i = 0; i < nbPts; ++i) {
double squareError = evaluate(pts[i], model);
residuals[i] = squareError;
}
double meanSquareError = residuals.
sum() /
static_cast<double>(nbPts);
return std::sqrt(meanSquareError);
}
template<typename T>
const unsigned int &vertPosLegend, const unsigned int &horPosLegend)
{
#if defined(VISP_HAVE_DISPLAY)
for (unsigned int u = 0; u < width; ++u) {
double v = model.eval(u);
}
#else
(void)coeffs;
(void)I;
(void)color;
(void)vertPosLegend;
(void)horPosLegend;
#endif
}
std::vector<vpImagePoint> automaticInitialization(tutorial::vpTutoCommonData &data)
{
const unsigned int minNbPts = data.m_degree + 1;
const unsigned int nbPtsToUse = 10 * minNbPts;
std::vector<vpImagePoint> initPoints;
tutorial::performSegmentationHSV(data);
std::vector<vpImagePoint> edgePoints = tutorial::extractSkeleton(data);
unsigned int nbEdgePoints = static_cast<unsigned int>(edgePoints.size());
if (nbEdgePoints < nbPtsToUse) {
return edgePoints;
}
return ptA.
get_u() < ptB.get_u();
};
std::sort(edgePoints.begin(), edgePoints.end(), ptHasLowerU);
unsigned int idStart, idStop;
if (nbEdgePoints > nbPtsToUse + 20) {
idStart = 10;
idStop = static_cast<unsigned int>(edgePoints.size()) - 10;
}
else {
idStart = 0;
idStop = static_cast<unsigned int>(edgePoints.size());
}
unsigned int sizeWindow = idStop - idStart + 1;
unsigned int step = sizeWindow / (nbPtsToUse - 1);
for (unsigned int id = idStart; id <= idStop; id += step) {
initPoints.push_back(edgePoints[id]);
}
return initPoints;
}
std::vector<vpImagePoint> manualInitialization(const tutorial::vpTutoCommonData &data)
{
const bool waitForClick = true;
const unsigned int sizeCross = 10;
const unsigned int thicknessCross = 2;
const unsigned int minNbPts = data.m_degree + 1;
std::vector<vpImagePoint> initPoints;
bool notEnoughPoints = true;
while (notEnoughPoints) {
vpDisplay::displayText(data.m_I_orig, data.m_ipLegend,
"Left click to add init point (min.: " + std::to_string(minNbPts) +
"), right click to estimate the initial coefficients of the Particle Filter.", data.m_colorLegend);
vpDisplay::displayText(data.m_I_orig, data.m_ipLegend + data.m_legendOffset,
"A middle click reinitialize the list of init points.", data.m_colorLegend);
vpDisplay::displayText(data.m_I_orig, data.m_ipLegend + data.m_legendOffset + data.m_legendOffset,
"If not enough points have been selected, a right click has no effect.", data.m_colorLegend);
unsigned int nbInitPoints = static_cast<unsigned int>(initPoints.size());
for (unsigned int i = 0; i < nbInitPoints; ++i) {
}
switch (button) {
case vpMouseButton::vpMouseButtonType::button1:
initPoints.push_back(ipClick);
break;
case vpMouseButton::vpMouseButtonType::button2:
initPoints.clear();
break;
case vpMouseButton::vpMouseButtonType::button3:
(initPoints.size() >= minNbPts ? notEnoughPoints = false : notEnoughPoints = true);
break;
default:
break;
}
}
return initPoints;
}
vpColVector computeInitialGuess(tutorial::vpTutoCommonData &data)
{
std::vector<vpImagePoint> initPoints;
#ifdef VISP_HAVE_DISPLAY
const bool waitForClick = true;
const unsigned int sizeCross = 10;
const unsigned int thicknessCross = 2;
bool automaticInit = false;
vpDisplay::displayText(data.m_I_orig, data.m_ipLegend,
"Left click to manually select the init points, right click to automatically initialize the PF", data.m_colorLegend);
switch (button) {
case vpMouseButton::vpMouseButtonType::button1:
automaticInit = false;
break;
case vpMouseButton::vpMouseButtonType::button3:
automaticInit = true;
break;
default:
break;
}
if (automaticInit) {
initPoints = tutorial::automaticInitialization(data);
}
else {
initPoints = tutorial::manualInitialization(data);
}
#else
initPoints = tutorial::automaticInitialization(data);
#endif
tutorial::vpTutoMeanSquareFitting lmsFitter(data.m_degree, data.m_I_orig.getHeight(), data.m_I_orig.getWidth());
lmsFitter.fit(initPoints);
std::cout << "---[Initial fit]---" << std::endl;
std::cout << lmsFitter.getModel();
std::cout << "---[Initial fit]---" << std::endl;
vpDisplay::displayText(data.m_I_orig, data.m_ipLegend,
"Here are the points selected for the initialization.", data.m_colorLegend);
unsigned int nbInitPoints = static_cast<unsigned int>(initPoints.size());
for (unsigned int i = 0; i < nbInitPoints; ++i) {
}
lmsFitter.display(data.m_I_orig,
vpColor::red,
static_cast<unsigned int>(data.m_ipLegend.get_v() + 2 * data.m_legendOffset.get_v()),
static_cast<unsigned int>(data.m_ipLegend.get_u()));
vpDisplay::displayText(data.m_I_orig, data.m_ipLegend + data.m_legendOffset,
"A click to continue.", data.m_colorLegend);
return X0;
}
{
return updatedCoeffs;
}
class vpTutoAverageFunctor
{
public:
vpTutoAverageFunctor(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.) {
vpTutoParabolaModel 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.) {
vpTutoParabolaModel curve(particles[i], m_height, m_width);
double u = static_cast<double>(m_width) / 2.;
double v = curve.eval(u);
initPoints.push_back(pt);
}
}
vpTutoMeanSquareFitting lms(m_degree, m_height, m_width);
lms.fit(initPoints);
return lms.getCoeffs();
}
private:
unsigned int m_degree;
unsigned int m_height;
unsigned int m_width;
};
class vpTutoLikelihoodFunctor
{
public:
vpTutoLikelihoodFunctor(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());
vpTutoParabolaModel model(coeffs, m_height, m_width);
for (unsigned int i = 0; i < nbPoints; ++i) {
double squareError = tutorial::evaluate(meas[i], model);
residuals[i] = squareError;
}
likelihood = std::exp(m_constantExpDenominator * sumError / w.
sum()) * 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;
};
}
#endif
int main(const int argc, const char *argv[])
{
tutorial::vpTutoCommonData data;
int returnCode = data.init(argc, argv);
if (returnCode != tutorial::vpTutoCommonData::SOFTWARE_CONTINUE) {
return returnCode;
}
const unsigned int vertOffset = static_cast<unsigned int>(data.m_legendOffset.get_i());
const unsigned int horOffset = static_cast<unsigned int>(data.m_ipLegend.get_j());
const unsigned int legendPFVert = data.m_I_orig.getHeight() - 2 * vertOffset, legendPFHor = horOffset;
const double maxDistanceForLikelihood = data.m_pfMaxDistanceForLikelihood;
const double sigmaLikelihood = maxDistanceForLikelihood / 3.;
const unsigned int nbParticles = data.m_pfN;
std::vector<double> stdevsPF;
for (unsigned int i = 0; i < data.m_degree + 1; ++i) {
double ampliMax = data.m_pfRatiosAmpliMax[i] * X0[i];
stdevsPF.push_back(ampliMax / 3.);
}
unsigned long seedPF;
const float period = 33.3f;
if (data.m_pfSeed < 0) {
}
else {
seedPF = data.m_pfSeed;
}
const int nbThread = data.m_pfNbThreads;
tutorial::vpTutoLikelihoodFunctor likelihoodFtor(sigmaLikelihood, data.m_I_orig.getHeight(), data.m_I_orig.getWidth());
using std::placeholders::_1;
using std::placeholders::_2;
tutorial::vpTutoAverageFunctor averageCpter(data.m_degree, data.m_I_orig.getHeight(), data.m_I_orig.getWidth());
using std::placeholders::_3;
filter.init(X0, processFunc, likelihoodFunc, checkResamplingFunc, resamplingFunc, meanFunc);
#ifdef VISP_HAVE_DISPLAY
unsigned int plotHeight = 350, plotWidth = 350;
int plotXpos = static_cast<int>(data.m_legendOffset.get_u());
int plotYpos = static_cast<int>(data.m_I_orig.getHeight() + 4. * data.m_legendOffset.get_v());
vpPlot plot(1, plotHeight, plotWidth, plotXpos, plotYpos,
"Root mean-square error");
plot.initGraph(0, 1);
plot.setLegend(0, 0, "PF estimator");
#endif
bool run = true;
unsigned int nbIter = 0;
double meanDtPF = 0.;
double meanRootMeanSquareErrorPF = 0.;
while (!data.m_grabber.end() && run) {
std::cout << "Iter " << nbIter << std::endl;
data.m_grabber.acquire(data.m_I_orig);
tutorial::performSegmentationHSV(data);
std::vector<vpImagePoint> edgePoints = tutorial::extractSkeleton(data);
std::vector<vpImagePoint> noisyEdgePoints = tutorial::addSaltAndPepperNoise(edgePoints, data);
#ifdef VISP_HAVE_DISPLAY
#endif
filter.filter(noisyEdgePoints, period);
double pfError = tutorial::evaluate(Xest, data.m_I_orig.getHeight(), data.m_I_orig.getWidth(), edgePoints);
std::cout << " [Particle Filter method] " << std::endl;
std::cout <<
" Coeffs = [" << Xest.
transpose() <<
" ]" << std::endl;
std::cout << " Root Mean Square Error = " << pfError << " pixels" << std::endl;
std::cout << " Fitting duration = " << dtPF << " ms" << std::endl;
meanDtPF += dtPF;
meanRootMeanSquareErrorPF += pfError;
#ifdef VISP_HAVE_DISPLAY
tutorial::display(Xest, data.m_IskeletonNoisy,
vpColor::red, legendPFVert, legendPFHor);
plot.plot(0, 0, nbIter, pfError);
data.displayLegend(data.m_I_orig);
run = data.manageClicks(data.m_I_orig, data.m_stepbystep);
#endif
++nbIter;
}
double iterAsDouble = static_cast<double>(nbIter);
std::cout << std::endl << std::endl << "-----[Statistics summary]-----" << std::endl;
std::cout << " [Particle Filter method] " << std::endl;
std::cout << " Average Root Mean Square Error = " << meanRootMeanSquareErrorPF / iterAsDouble << " pixels" << std::endl;
std::cout << " Average fitting duration = " << meanDtPF / iterAsDouble << " ms" << std::endl;
#ifdef VISP_HAVE_DISPLAY
if (data.m_grabber.end() && (!data.m_stepbystep)) {
vpDisplay::displayText(data.m_I_orig, data.m_ipLegend,
"End of sequence reached. Click to exit.", data.m_colorLegend);
}
#endif
return 0;
}
#else
int main()
{
std::cerr << "ViSP must be compiled with C++ standard >= C++11 to use this tutorial." << std::endl;
std::cerr << "ViSP must also have a 3rd party enabling display features, such as X11 or OpenCV." << std::endl;
return EXIT_FAILURE;
}
#endif
Implementation of column vector and the associated operations.
vpColVector hadamard(const vpColVector &v) const
vpRowVector transpose() const
Class to define RGB colors available for display functionalities.
static bool getClick(const vpImage< unsigned char > &I, bool blocking=true)
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 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 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
unsigned int getHeight() const
The class permits to use a Particle Filter.
This class enables real time drawing of 2D or 3D graphics. An instance of the class open a window whi...
Contains an M-estimator and various influence function.
@ TUKEY
Tukey influence function.
void MEstimator(const vpRobustEstimatorType method, const vpColVector &residues, vpColVector &weights)
VISP_EXPORT double measureTimeMicros()
VISP_EXPORT double measureTimeMs()