38#include <visp3/core/vpConfig.h>
39#include <visp3/core/vpException.h>
40#include <visp3/core/vpMath.h>
41#include <visp3/core/vpMouseButton.h>
42#include <visp3/core/vpTime.h>
44#ifdef VISP_HAVE_DISPLAY
45#include <visp3/gui/vpPlot.h>
49#include <visp3/core/vpParticleFilter.h>
52#include "vpTutoCommonData.h"
53#include "vpTutoMeanSquareFitting.h"
54#include "vpTutoParabolaModel.h"
55#include "vpTutoSegmentation.h"
57#ifdef ENABLE_VISP_NAMESPACE
61#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) && defined(VISP_HAVE_DISPLAY)
62#ifndef DOXYGEN_SHOULD_SKIP_THIS
74double evaluate(
const vpImagePoint &pt,
const vpTutoParabolaModel &model)
78 double v_model = model.eval(u);
79 double error =
v - v_model;
94double evaluate(
const vpColVector &coeffs,
const unsigned int &height,
const unsigned int &width,
const std::vector<vpImagePoint> &pts)
96 unsigned int nbPts =
static_cast<unsigned int>(pts.size());
97 vpColVector residuals(nbPts);
98 vpColVector weights(nbPts, 1.);
99 vpTutoParabolaModel model(coeffs, height, width);
101 for (
unsigned int i = 0;
i < nbPts; ++
i) {
102 double squareError = evaluate(pts[i], model);
103 residuals[
i] = squareError;
105 double meanSquareError = residuals.sum() /
static_cast<double>(nbPts);
106 return std::sqrt(meanSquareError);
120void display(
const vpColVector &coeffs,
const vpImage<T> &I,
const vpColor &color,
121 const unsigned int &vertPosLegend,
const unsigned int &horPosLegend)
123#if defined(VISP_HAVE_DISPLAY)
126 for (
unsigned int u = 0;
u <
width; ++
u) {
127 unsigned int v =
static_cast<unsigned int>(model.eval(u));
149std::vector<vpImagePoint> automaticInitialization(tutorial::vpTutoCommonData &data)
152 const unsigned int minNbPts =
data.m_degree + 1;
153 const unsigned int nbPtsToUse = 10 * minNbPts;
154 std::vector<vpImagePoint> initPoints;
157 tutorial::performSegmentationHSV(data);
160 std::vector<vpImagePoint> edgePoints = tutorial::extractSkeleton(data);
161 unsigned int nbEdgePoints =
static_cast<unsigned int>(edgePoints.size());
163 if (nbEdgePoints < nbPtsToUse) {
168 auto ptHasLowerU = [](
const vpImagePoint &ptA,
const vpImagePoint &ptB) {
169 return ptA.
get_u() < ptB.get_u();
171 std::sort(edgePoints.begin(), edgePoints.end(), ptHasLowerU);
173 unsigned int idStart, idStop;
174 if (nbEdgePoints > nbPtsToUse + 20) {
177 idStop =
static_cast<unsigned int>(edgePoints.size()) - 10;
182 idStop =
static_cast<unsigned int>(edgePoints.size());
186 unsigned int sizeWindow = idStop - idStart + 1;
187 unsigned int step = sizeWindow / (nbPtsToUse - 1);
188 for (
unsigned int id = idStart;
id <= idStop;
id +=
step) {
189 initPoints.push_back(edgePoints[
id]);
200std::vector<vpImagePoint> manualInitialization(
const tutorial::vpTutoCommonData &data)
203 const bool waitForClick =
true;
204 vpImagePoint ipClick;
208 const unsigned int sizeCross = 10;
209 const unsigned int thicknessCross = 2;
213 const unsigned int minNbPts =
data.m_degree + 1;
214 std::vector<vpImagePoint> initPoints;
216 bool notEnoughPoints =
true;
217 while (notEnoughPoints) {
222 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);
227 unsigned int nbInitPoints =
static_cast<unsigned int>(initPoints.size());
228 for (
unsigned int i = 0;
i < nbInitPoints; ++
i) {
241 initPoints.push_back(ipClick);
247 (initPoints.size() >= minNbPts ? notEnoughPoints =
false : notEnoughPoints =
true);
265vpColVector computeInitialGuess(tutorial::vpTutoCommonData &data)
268 std::vector<vpImagePoint> initPoints;
270#ifdef VISP_HAVE_DISPLAY
272 const bool waitForClick =
true;
273 vpImagePoint ipClick;
277 const unsigned int sizeCross = 10;
278 const unsigned int thicknessCross = 2;
281 bool automaticInit =
false;
285 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);
296 automaticInit =
false;
299 automaticInit =
true;
307 initPoints = tutorial::automaticInitialization(data);
311 initPoints = tutorial::manualInitialization(data);
316 initPoints = tutorial::automaticInitialization(data);
320 tutorial::vpTutoMeanSquareFitting lmsFitter(
data.m_degree,
data.m_I_orig.getHeight(),
data.m_I_orig.getWidth());
321 lmsFitter.fit(initPoints);
322 vpColVector
X0 = lmsFitter.getCoeffs();
323 std::cout <<
"---[Initial fit]---" << std::endl;
324 std::cout << lmsFitter.getModel();
325 std::cout <<
"---[Initial fit]---" << std::endl;
330 size_t nbInitPoints = initPoints.size();
331 for (
size_t i = 0;
i < nbInitPoints; ++
i) {
332 const vpImagePoint &ip = initPoints[
i];
337 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()));
347vpColVector fx(
const vpColVector &coeffs,
const double &)
349 vpColVector updatedCoeffs = coeffs;
350 return updatedCoeffs;
355class vpTutoAverageFunctor
358 vpTutoAverageFunctor(
const unsigned int °ree,
const unsigned int &height,
const unsigned int &width)
373 vpColVector averagePolynomials(
const std::vector<vpColVector> &particles,
const std::vector<double> &weights,
const vpParticleFilter<std::vector<vpImagePoint>>::vpStateAddFunction &)
375 const unsigned int nbParticles =
static_cast<unsigned int>(particles.size());
376 const double nbParticlesAsDOuble =
static_cast<double>(nbParticles);
378 const double sumWeight = std::accumulate(weights.begin(), weights.end(), 0.);
381 const double nbPointsForAverage = 10. * nbParticlesAsDOuble;
382 std::vector<vpImagePoint> initPoints;
385 for (
unsigned int i = 0;
i < nbParticles; ++
i) {
387 double nbPoints = std::floor(weights[i] * nbPointsForAverage / sumWeight);
390 vpTutoParabolaModel curve(particles[i], m_height, m_width);
391 double widthAsDouble =
static_cast<double>(m_width);
393 double step = widthAsDouble / (nbPoints - 1.);
394 for (
double u = 0.;
u < widthAsDouble;
u +=
step) {
395 double v = curve.eval(u);
396 vpImagePoint pt(v, u);
397 initPoints.push_back(pt);
403 vpTutoParabolaModel 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 vpTutoMeanSquareFitting lms(m_degree, m_height, m_width);
413 return lms.getCoeffs();
417 unsigned int m_degree;
418 unsigned int m_height;
419 unsigned int m_width;
424class vpTutoLikelihoodFunctor
434 vpTutoLikelihoodFunctor(
const double &stdev,
const unsigned int &height,
const unsigned int &width)
438 double sigmaDistanceSquared = stdev * stdev;
439 m_constantDenominator = 1. / std::sqrt(2. * M_PI * sigmaDistanceSquared);
440 m_constantExpDenominator = -1. / (2. * sigmaDistanceSquared);
456 double likelihood(
const vpColVector &coeffs,
const std::vector<vpImagePoint> &meas)
458 double likelihood = 0.;
459 unsigned int nbPoints =
static_cast<unsigned int>(meas.size());
462 vpTutoParabolaModel model(coeffs, m_height, m_width);
465 vpColVector residuals(nbPoints);
466 for (
unsigned int i = 0;
i < nbPoints; ++
i) {
467 double squareError = tutorial::evaluate(meas[i], model);
468 residuals[
i] = squareError;
473 vpColVector
w(nbPoints, 1.);
475 double sumError =
w.hadamard(residuals).sum();
478 likelihood = std::exp(m_constantExpDenominator * sumError /
w.sum()) * m_constantDenominator;
479 likelihood = std::min(likelihood, 1.0);
480 likelihood = std::max(likelihood, 0.);
485 double m_constantDenominator;
486 double m_constantExpDenominator;
487 unsigned int m_height;
488 unsigned int m_width;
494int main(
const int argc,
const char *argv[])
496 tutorial::vpTutoCommonData
data;
497 int returnCode =
data.init(argc, argv);
498 if (returnCode != tutorial::vpTutoCommonData::SOFTWARE_CONTINUE) {
501 tutorial::vpTutoMeanSquareFitting lmsFitter(
data.m_degree,
data.m_I_orig.getHeight(),
data.m_I_orig.getWidth());
502 const unsigned int vertOffset =
static_cast<unsigned int>(
data.m_legendOffset.get_i());
503 const unsigned int horOffset =
static_cast<unsigned int>(
data.m_ipLegend.get_j());
504 const unsigned int legendLmsVert =
data.m_I_orig.getHeight() - 3 * vertOffset;
505 const unsigned int legendLmsHor = horOffset;
506 const unsigned int legendPFVert =
data.m_I_orig.getHeight() - 2 * vertOffset, legendPFHor = horOffset;
514 const double maxDistanceForLikelihood =
data.m_pfMaxDistanceForLikelihood;
515 const double sigmaLikelihood = maxDistanceForLikelihood / 3.;
516 const unsigned int nbParticles =
data.m_pfN;
517 std::vector<double> stdevsPF;
518 for (
unsigned int i = 0;
i <
data.m_degree + 1; ++
i) {
519 double ampliMax =
data.m_pfRatiosAmpliMax[
i] *
X0[
i];
520 stdevsPF.push_back(ampliMax / 3.);
522 unsigned long seedPF;
523 const float period = 33.3f;
524 if (
data.m_pfSeed < 0) {
528 seedPF =
data.m_pfSeed;
530 const int nbThread =
data.m_pfNbThreads;
535 tutorial::vpTutoLikelihoodFunctor likelihoodFtor(sigmaLikelihood,
data.m_I_orig.getHeight(),
data.m_I_orig.getWidth());
536 using std::placeholders::_1;
537 using std::placeholders::_2;
541 tutorial::vpTutoAverageFunctor averageCpter(
data.m_degree,
data.m_I_orig.getHeight(),
data.m_I_orig.getWidth());
542 using std::placeholders::_3;
549 filter.init(X0, processFunc, likelihoodFunc, checkResamplingFunc, resamplingFunc, meanFunc);
553#ifdef VISP_HAVE_DISPLAY
554 unsigned int plotHeight = 350, plotWidth = 350;
555 int plotXpos =
static_cast<int>(
data.m_legendOffset.get_u());
556 int plotYpos =
static_cast<int>(
data.m_I_orig.getHeight() + 4. *
data.m_legendOffset.get_v());
557 vpPlot plot(1, plotHeight, plotWidth, plotXpos, plotYpos,
"Root mean-square error");
558 plot.initGraph(0, 2);
559 plot.setLegend(0, 0,
"LMS estimator");
561 plot.setLegend(0, 1,
"PF estimator");
567 unsigned int nbIter = 0;
568 double meanDtLMS = 0., meanDtPF = 0.;
569 double meanRootMeanSquareErrorLMS = 0., meanRootMeanSquareErrorPF = 0.;
570 while (!
data.m_grabber.end() && run) {
571 std::cout <<
"Iter " << nbIter << std::endl;
572 data.m_grabber.acquire(
data.m_I_orig);
574 tutorial::performSegmentationHSV(data);
577 std::vector<vpImagePoint> edgePoints = tutorial::extractSkeleton(data);
580 std::vector<vpImagePoint> noisyEdgePoints = tutorial::addSaltAndPepperNoise(edgePoints, data);
582#ifdef VISP_HAVE_DISPLAY
591 lmsFitter.fit(noisyEdgePoints);
593 double lmsRootMeanSquareError = lmsFitter.evaluate(edgePoints);
594 std::cout <<
" [Least-Mean Square method] " << std::endl;
595 std::cout <<
" Coeffs = [" << lmsFitter.getCoeffs().transpose() <<
" ]" << std::endl;
596 std::cout <<
" Root Mean Square Error = " << lmsRootMeanSquareError <<
" pixels" << std::endl;
597 std::cout <<
" Fitting duration = " << dtLms <<
" ms" << std::endl;
599 meanRootMeanSquareErrorLMS += lmsRootMeanSquareError;
604 filter.filter(noisyEdgePoints, period);
613 double pfError = tutorial::evaluate(Xest,
data.m_I_orig.getHeight(),
data.m_I_orig.getWidth(), edgePoints);
615 std::cout <<
" [Particle Filter method] " << std::endl;
616 std::cout <<
" Coeffs = [" <<
Xest.transpose() <<
" ]" << std::endl;
617 std::cout <<
" Root Mean Square Error = " << pfError <<
" pixels" << std::endl;
618 std::cout <<
" Fitting duration = " << dtPF <<
" ms" << std::endl;
620 meanRootMeanSquareErrorPF += pfError;
622#ifdef VISP_HAVE_DISPLAY
624 lmsFitter.display<
unsigned char>(
data.m_IskeletonNoisy,
vpColor::gray, legendLmsVert, legendLmsHor);
625 tutorial::display(Xest,
data.m_IskeletonNoisy,
vpColor::red, legendPFVert, legendPFHor);
628 plot.plot(0, 0, nbIter, lmsRootMeanSquareError);
629 plot.plot(0, 1, nbIter, pfError);
635 run =
data.manageClicks(
data.m_I_orig,
data.m_stepbystep);
640 double iterAsDouble =
static_cast<double>(nbIter);
642 std::cout << std::endl << std::endl <<
"-----[Statistics summary]-----" << std::endl;
644 std::cout <<
" [LMS method] " << std::endl;
645 std::cout <<
" Average Root Mean Square Error = " << meanRootMeanSquareErrorLMS / iterAsDouble <<
" pixels" << std::endl;
646 std::cout <<
" Average fitting duration = " << meanDtLMS / iterAsDouble <<
" ms" << std::endl;
648 std::cout <<
" [Particle Filter method] " << std::endl;
649 std::cout <<
" Average Root Mean Square Error = " << meanRootMeanSquareErrorPF / iterAsDouble <<
" pixels" << std::endl;
650 std::cout <<
" Average fitting duration = " << meanDtPF / iterAsDouble <<
" ms" << std::endl;
652#ifdef VISP_HAVE_DISPLAY
653 if (
data.m_grabber.end() && (!
data.m_stepbystep)) {
670 std::cerr <<
"ViSP must be compiled with C++ standard >= C++11 to use this tutorial." << std::endl;
671 std::cerr <<
"ViSP must also have a 3rd party enabling display features, such as X11 or OpenCV." << std::endl;
Implementation of column vector and the associated operations.
static const vpColor gray
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)
unsigned int getWidth() const
unsigned int getHeight() const
static bool equal(double x, double y, double threshold=0.001)
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...
@ TUKEY
Tukey influence function.
void MEstimator(const vpRobustEstimatorType method, const vpColVector &residues, vpColVector &weights)
VISP_EXPORT double measureTimeMs()
VISP_EXPORT double measureTimeMicros()