Visual Servoing Platform  version 3.6.1 under development (2024-11-21)
vpRobust.cpp
1 /*
2  * ViSP, open source Visual Servoing Platform software.
3  * Copyright (C) 2005 - 2024 by Inria. All rights reserved.
4  *
5  * This software is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  * See the file LICENSE.txt at the root directory of this source
10  * distribution for additional information about the GNU GPL.
11  *
12  * For using ViSP with software that can not be combined with the GNU
13  * GPL, please contact Inria about acquiring a ViSP Professional
14  * Edition License.
15  *
16  * See https://visp.inria.fr for more information.
17  *
18  * This software was developed at:
19  * Inria Rennes - Bretagne Atlantique
20  * Campus Universitaire de Beaulieu
21  * 35042 Rennes Cedex
22  * France
23  *
24  * If you have questions regarding the use of this file, please contact
25  * Inria at visp@inria.fr
26  *
27  * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
28  * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
29  *
30  * Description:
31  * M-Estimator and various influence function.
32  */
33 
38 #include <algorithm> // std::swap
39 #include <cmath> // std::fabs
40 #include <limits> // numeric_limits
41 #include <stdio.h>
42 #include <stdlib.h>
43 #include <string.h>
44 
45 #include <visp3/core/vpColVector.h>
46 #include <visp3/core/vpMath.h>
47 #include <visp3/core/vpRobust.h>
48 
49 BEGIN_VISP_NAMESPACE
54  : m_normres(), m_sorted_normres(), m_sorted_residues(), m_mad_min(0.0017), m_mad_prev(0),
55 #if defined(VISP_BUILD_DEPRECATED_FUNCTIONS)
56  m_iter(0),
57 #endif
58  m_size(0), m_mad(0)
59 { }
60 
64 vpRobust::vpRobust(const vpRobust &other) { *this = other; }
65 
70 {
71  m_normres = other.m_normres;
72  m_sorted_normres = other.m_sorted_normres;
73  m_sorted_residues = other.m_sorted_residues;
74  m_mad_min = other.m_mad_min;
75  m_mad = other.m_mad;
76  m_mad_prev = other.m_mad_prev;
77 #ifdef VISP_BUILD_DEPRECATED_FUNCTIONS
78  m_iter = other.m_iter;
79 #endif
80  m_size = other.m_size;
81  return *this;
82 }
83 
84 #if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11)
89 {
90  m_normres = std::move(other.m_normres);
91  m_sorted_normres = std::move(other.m_sorted_normres);
92  m_sorted_residues = std::move(other.m_sorted_residues);
93  m_mad_min = std::move(other.m_mad_min);
94  m_mad_prev = std::move(other.m_mad_prev);
95 #ifdef VISP_BUILD_DEPRECATED_FUNCTIONS
96  m_iter = std::move(other.m_iter);
97 #endif
98  m_size = std::move(other.m_size);
99  return *this;
100 }
101 #endif
102 
107 void vpRobust::resize(unsigned int n_data)
108 {
109  if (n_data != m_size) {
110  m_normres.resize(n_data);
111  m_sorted_normres.resize(n_data);
112  m_sorted_residues.resize(n_data);
113  m_size = n_data;
114  }
115 }
116 
117 // ===================================================================
130 void vpRobust::MEstimator(const vpRobustEstimatorType method, const vpColVector &residues, vpColVector &weights)
131 {
132  double med = 0; // median
133  double normmedian = 0; // Normalized median
134 
135  // resize vector only if the size of residue vector has changed
136  unsigned int n_data = residues.getRows();
137  weights.resize(n_data, false);
138  resize(n_data);
139 
140  m_sorted_residues = residues;
141 
142  unsigned int ind_med = static_cast<unsigned int>(ceil(n_data / 2.0)) - 1;
143 
144  // Calculate median
145  med = select(m_sorted_residues, 0, n_data - 1, ind_med);
146  // --comment: residualMedian = med
147 
148  // Normalize residues
149  for (unsigned int i = 0; i < n_data; ++i) {
150  m_normres[i] = (fabs(residues[i] - med));
151  m_sorted_normres[i] = (fabs(m_sorted_residues[i] - med));
152  }
153 
154  // Calculate MAD
155  normmedian = select(m_sorted_normres, 0, n_data - 1, ind_med);
156  // normalizedResidualMedian = normmedian ;
157  // 1.48 keeps scale estimate consistent for a normal probability dist.
158  m_mad = 1.4826 * normmedian; // median Absolute Deviation
159 
160  // Set a minimum threshold for sigma
161  // (when sigma reaches the level of noise in the image)
162  if (m_mad < m_mad_min) {
163  m_mad = m_mad_min;
164  }
165  switch (method) {
166  case TUKEY: {
167  psiTukey(m_mad, m_normres, weights);
168  break;
169  }
170  case CAUCHY: {
171  psiCauchy(m_mad, m_normres, weights);
172  break;
173  }
174  case HUBER: {
175  psiHuber(m_mad, m_normres, weights);
176  break;
177  }
178  default:
179  // TODO
180  std::cout << "MEstimator: method not recognised - id = " << method << std::endl;
181  }
182 }
183 
192 void vpRobust::psiTukey(double sig, const vpColVector &x, vpColVector &weights)
193 {
194  unsigned int n_data = x.getRows();
195  double C = sig * 4.6851;
196 
197  // Here we consider that sig cannot be equal to 0
198  for (unsigned int i = 0; i < n_data; ++i) {
199  double xi = x[i] / C;
200  xi *= xi;
201 
202  if (xi > 1.) {
203  weights[i] = 0;
204  }
205  else {
206  xi = 1 - xi;
207  xi *= xi;
208  weights[i] = xi;
209  }
210  }
211 }
212 
220 void vpRobust::psiHuber(double sig, const vpColVector &x, vpColVector &weights)
221 {
222  double C = sig * 1.2107;
223  unsigned int n_data = x.getRows();
224 
225  for (unsigned int i = 0; i < n_data; ++i) {
226  double xi = x[i] / C;
227  if (fabs(xi) > 1.) {
228  weights[i] = std::fabs(1. / xi);
229  }
230  else {
231  weights[i] = 1;
232  }
233  }
234 }
235 
244 void vpRobust::psiCauchy(double sig, const vpColVector &x, vpColVector &weights)
245 {
246  unsigned int n_data = x.getRows();
247  double C = sig * 2.3849;
248 
249  // Calculate Cauchy's equation
250  for (unsigned int i = 0; i < n_data; ++i) {
251  weights[i] = 1. / (1. + vpMath::sqr(x[i] / C));
252  }
253 }
254 
261 int vpRobust::partition(vpColVector &a, int l, int r)
262 {
263  int i = l - 1;
264  int j = r;
265  double v = a[r];
266 
267  for (;;) {
268  while (a[++i] < v) { }
269 
270  while (v < a[--j]) {
271  if (j == l) {
272  break;
273  }
274  }
275  if (i >= j) {
276  break;
277  }
278  std::swap(a[i], a[j]);
279  }
280  std::swap(a[i], a[r]);
281  return i;
282 }
283 
291 double vpRobust::select(vpColVector &a, int l, int r, int k)
292 {
293  while (r > l) {
294  int i = partition(a, l, r);
295  if (i >= k) {
296  r = i - 1;
297  }
298  if (i <= k) {
299  l = i + 1;
300  }
301  }
302  return a[k];
303 }
304 
305 /**********************
306  * Below are deprecated functions
307  */
308 #if defined(VISP_BUILD_DEPRECATED_FUNCTIONS)
309 #define vpITMAX 100
310 #define vpEPS 3.0e-7
311 
316 vpRobust::vpRobust(unsigned int n_data)
317  : m_normres(), m_sorted_normres(), m_sorted_residues(), m_mad_min(0.0017), m_mad_prev(0),
318 #if defined(VISP_BUILD_DEPRECATED_FUNCTIONS)
319  m_iter(0),
320 #endif
321  m_size(n_data), m_mad(0)
322 {
323  m_normres.resize(n_data);
324  m_sorted_normres.resize(n_data);
325  m_sorted_residues.resize(n_data);
326  // m_mad_min=0.0017; //Can not be more accurate than 1 pixel
327 }
328 
329 void vpRobust::MEstimator(const vpRobustEstimatorType method, const vpColVector &residues,
330  const vpColVector &all_residues, vpColVector &weights)
331 {
332  double normmedian = 0; // Normalized median
333 
334  unsigned int n_all_data = all_residues.getRows();
335  vpColVector all_normres(n_all_data);
336 
337  // compute median with the residues vector, return all_normres which are the
338  // normalized all_residues vector.
339  normmedian = computeNormalizedMedian(all_normres, residues, all_residues, weights);
340 
341  // 1.48 keeps scale estimate consistent for a normal probability dist.
342  m_mad = 1.4826 * normmedian; // Median Absolute Deviation
343 
344  // Set a minimum threshold for sigma
345  // (when sigma reaches the level of noise in the image)
346  if (m_mad < m_mad_min) {
347  m_mad = m_mad_min;
348  }
349 
350  switch (method) {
351  case TUKEY: {
352  psiTukey(m_mad, all_normres, weights);
353  break;
354  }
355  case CAUCHY: {
356  psiCauchy(m_mad, all_normres, weights);
357  break;
358  }
359  case HUBER: {
360  psiHuber(m_mad, all_normres, weights);
361  break;
362  }
363  };
364 }
365 
366 double vpRobust::computeNormalizedMedian(vpColVector &all_normres, const vpColVector &residues,
367  const vpColVector &all_residues, const vpColVector &weights)
368 {
369  double med = 0;
370  double normmedian = 0;
371 
372  unsigned int n_all_data = all_residues.getRows();
373  unsigned int n_data = residues.getRows();
374 
375  // resize vector only if the size of residue vector has changed
376  resize(n_data);
377 
378  m_sorted_residues = residues;
379  vpColVector no_null_weight_residues;
380  no_null_weight_residues.resize(n_data);
381 
382  unsigned int index = 0;
383  for (unsigned int j = 0; j < n_data; ++j) {
384  // if(weights[j]!=0)
385  if (std::fabs(weights[j]) > std::numeric_limits<double>::epsilon()) {
386  no_null_weight_residues[index] = residues[j];
387  index++;
388  }
389  }
390  m_sorted_residues.resize(index);
391  memcpy(m_sorted_residues.data, no_null_weight_residues.data, index * sizeof(double));
392  n_data = index;
393 
394  // Calculate Median
395  // Be careful to not use the rejected residues for the
396  // calculation.
397 
398  unsigned int ind_med = (unsigned int)(ceil(n_data / 2.0)) - 1;
399  med = select(m_sorted_residues, 0, n_data - 1, ind_med);
400 
401  // Normalize residues
402  for (unsigned int i = 0; i < n_all_data; ++i) {
403  all_normres[i] = (fabs(all_residues[i] - med));
404  }
405 
406  for (unsigned int i = 0; i < n_data; ++i) {
407  m_sorted_normres[i] = (fabs(m_sorted_residues[i] - med));
408  }
409  // MAD calculated only on first iteration
410  normmedian = select(m_sorted_normres, 0, n_data - 1, ind_med);
411 
412  return normmedian;
413 }
414 
421 vpColVector vpRobust::simultMEstimator(vpColVector &residues)
422 {
423  double med = 0; // Median
424 
425  unsigned int n_data = residues.getRows();
426  vpColVector norm_res(n_data); // Normalized Residue
427  vpColVector w(n_data);
428 
429  // Calculate Median
430  unsigned int ind_med = (unsigned int)(ceil(n_data / 2.0)) - 1;
431  med = select(residues, 0, n_data - 1, ind_med /*(int)n_data/2*/);
432 
433  // Normalize residues
434  for (unsigned int i = 0; i < n_data; ++i)
435  norm_res[i] = (fabs(residues[i] - med));
436 
437  // Check for various methods.
438  // For Huber compute Simultaneous scale estimate
439  // For Others use MAD calculated on first iteration
440  if (m_iter == 0) {
441  double normmedian = select(norm_res, 0, n_data - 1, ind_med); // Normalized Median
442  // 1.48 keeps scale estimate consistent for a normal probability dist.
443  m_mad = 1.4826 * normmedian; // Median Absolute Deviation
444  }
445  else {
446  // compute simultaneous scale estimate
447  m_mad = simultscale(residues);
448  }
449 
450  // Set a minimum threshold for sigma
451  // (when sigma reaches the level of noise in the image)
452  if (m_mad < m_mad_min) {
453  m_mad = m_mad_min;
454  }
455 
456  psiHuber(m_mad, norm_res, w);
457 
458  m_mad_prev = m_mad;
459 
460  return w;
461 }
462 
463 double vpRobust::simultscale(const vpColVector &x)
464 {
465  unsigned int p = 6; // Number of parameters to be estimated.
466  unsigned int n = x.getRows();
467  double sigma2 = 0;
468  /* long */ double Expectation = 0;
469  /* long */ double Sum_chi = 0;
470 
471  for (unsigned int i = 0; i < n; ++i) {
472 
473  double chiTmp = simult_chi_huber(x[i]);
474 #if defined(VISP_HAVE_FUNC_STD_ERFC)
475  Expectation += chiTmp * std::erfc(chiTmp);
476 #elif defined(VISP_HAVE_FUNC_ERFC)
477  Expectation += chiTmp * erfc(chiTmp);
478 #else
479  Expectation += chiTmp * (1 - erf(chiTmp));
480 #endif
481  Sum_chi += chiTmp;
482 
483 #ifdef VP_DEBUG
484 #if VP_DEBUG_MODE == 3
485  {
486 #if defined(VISP_HAVE_FUNC_STD_ERFC)
487  std::cout << "erf = " << std::erfc(chiTmp) << std::endl;
488 #elif defined(VISP_HAVE_FUNC_ERFC)
489  std::cout << "erf = " << erfc(chiTmp) << std::endl;
490 #else
491  std::cout << "erf = " << (1 - erf(chiTmp)) << std::endl;
492 #endif
493  std::cout << "x[i] = " << x[i] << std::endl;
494  std::cout << "chi = " << chiTmp << std::endl;
495  std::cout << "Sum chi = " << chiTmp * vpMath::sqr(m_mad_prev) << std::endl;
496 #if defined(VISP_HAVE_FUNC_STD_ERFC)
497  std::cout << "Expectation = " << chiTmp * std::erfc(chiTmp) << std::endl;
498 #elif defined(VISP_HAVE_FUNC_ERFC)
499  std::cout << "Expectation = " << chiTmp * erfc(chiTmp) << std::endl;
500 #else
501  std::cout << "Expectation = " << chiTmp * (1 - erf(chiTmp)) << std::endl;
502 #endif
503  // getchar();
504  }
505 #endif
506 #endif
507  }
508 
509  sigma2 = Sum_chi * vpMath::sqr(m_mad_prev) / ((n - p) * Expectation);
510 
511 #ifdef VP_DEBUG
512 #if VP_DEBUG_MODE == 3
513  {
514  std::cout << "Expectation = " << Expectation << std::endl;
515  std::cout << "Sum chi = " << Sum_chi << std::endl;
516  std::cout << "MAD prev" << m_mad_prev << std::endl;
517  std::cout << "sig_out" << sqrt(fabs(sigma2)) << std::endl;
518  }
519 #endif
520 #endif
521 
522  return sqrt(fabs(sigma2));
523 }
524 
525 double vpRobust::constrainedChi(vpRobustEstimatorType method, double x)
526 {
527  switch (method) {
528  case TUKEY:
529  return constrainedChiTukey(x);
530  case CAUCHY:
531  return constrainedChiCauchy(x);
532  case HUBER:
533  return constrainedChiHuber(x);
534  };
535 
536  return -1;
537 }
538 
539 double vpRobust::constrainedChiTukey(double x)
540 {
541  double sct = 0;
542  double s = m_mad_prev;
543  // double epsillon=0.5;
544 
545  if (fabs(x) <= 4.7 * m_mad_prev) {
546  double a = 4.7;
547  // sct =
548  // (vpMath::sqr(s*a-x)*vpMath::sqr(s*a+x)*vpMath::sqr(x))/(s*vpMath::sqr(vpMath::sqr(a*vpMath::sqr(s))));
549  sct = (vpMath::sqr(s * a) * x - s * vpMath::sqr(s * a) - x * vpMath::sqr(x)) *
550  (vpMath::sqr(s * a) * x + s * vpMath::sqr(s * a) - x * vpMath::sqr(x)) / s *
552  }
553  else
554  sct = -1 / s;
555 
556  return sct;
557 }
558 
559 double vpRobust::constrainedChiCauchy(double x)
560 {
561  double sct = 0;
562  // double u = x/m_mad_prev;
563  double s = m_mad_prev;
564  double b = 2.3849;
565 
566  sct = -1 * (vpMath::sqr(x) * b) / (s * (vpMath::sqr(s * b) + vpMath::sqr(x)));
567 
568  return sct;
569 }
570 
571 double vpRobust::constrainedChiHuber(double x)
572 {
573  double sct = 0;
574  double u = x / m_mad_prev;
575  double c = 1.2107; // 1.345;
576 
577  if (fabs(u) <= c)
578  sct = vpMath::sqr(u);
579  else
580  sct = vpMath::sqr(c);
581 
582  return sct;
583 }
584 
585 double vpRobust::simult_chi_huber(double x)
586 {
587  double sct = 0;
588  double u = x / m_mad_prev;
589  double c = 1.2107; // 1.345;
590 
591  if (fabs(u) <= c) {
592  // sct = 0.5*vpMath::sqr(u);
593  sct = vpMath::sqr(u);
594  }
595  else {
596  // sct = 0.5*vpMath::sqr(c);
597  sct = vpMath::sqr(c);
598  }
599 
600  return sct;
601 }
602 
603 #if !defined(VISP_HAVE_FUNC_ERFC) && !defined(VISP_HAVE_FUNC_STD_ERFC)
604 double vpRobust::erf(double x) { return x < 0.0 ? -gammp(0.5, x * x) : gammp(0.5, x * x); }
605 
606 double vpRobust::gammp(double a, double x)
607 {
608  double gamser = 0., gammcf = 0., gln;
609 
610  if (x < 0.0 || a <= 0.0)
611  std::cout << "Invalid arguments in routine GAMMP";
612  if (x < (a + 1.0)) {
613  gser(&gamser, a, x, &gln);
614  return gamser;
615  }
616  else {
617  gcf(&gammcf, a, x, &gln);
618  return 1.0 - gammcf;
619  }
620 }
621 
622 void vpRobust::gser(double *gamser, double a, double x, double *gln)
623 {
624  *gln = gammln(a);
625  if (x <= 0.0) {
626  if (x < 0.0)
627  std::cout << "x less than 0 in routine GSER";
628  *gamser = 0.0;
629  return;
630  }
631  else {
632  double ap = a;
633  double sum = 1.0 / a;
634  double del = sum;
635  for (int n = 1; n <= vpITMAX; ++n) {
636  ap += 1.0;
637  del *= x / ap;
638  sum += del;
639  if (fabs(del) < fabs(sum) * vpEPS) {
640  *gamser = sum * exp(-x + a * log(x) - (*gln));
641  return;
642  }
643  }
644  std::cout << "a too large, vpITMAX too small in routine GSER";
645  return;
646  }
647 }
648 
649 void vpRobust::gcf(double *gammcf, double a, double x, double *gln)
650 {
651  double gold = 0.0, g, fac = 1.0, b1 = 1.0;
652  double b0 = 0.0, a1, a0 = 1.0;
653 
654  *gln = gammln(a);
655  a1 = x;
656  for (int n = 1; n <= vpITMAX; ++n) {
657  double an = static_cast<double>(n);
658  double ana = an - a;
659  a0 = (a1 + a0 * ana) * fac;
660  b0 = (b1 + b0 * ana) * fac;
661  double anf = an * fac;
662  a1 = x * a0 + anf * a1;
663  b1 = x * b0 + anf * b1;
664  // if (a1)
665  if (std::fabs(a1) > std::numeric_limits<double>::epsilon()) {
666  fac = 1.0 / a1;
667  g = b1 * fac;
668  if (fabs((g - gold) / g) < vpEPS) {
669  *gammcf = exp(-x + a * log(x) - (*gln)) * g;
670  return;
671  }
672  gold = g;
673  }
674  }
675  std::cout << "a too large, vpITMAX too small in routine GCF";
676 }
677 
678 double vpRobust::gammln(double xx)
679 {
680  double x, tmp, ser;
681  static double cof[6] = { 76.18009173, -86.50532033, 24.01409822, -1.231739516, 0.120858003e-2, -0.536382e-5 };
682 
683  x = xx - 1.0;
684  tmp = x + 5.5;
685  tmp -= (x + 0.5) * log(tmp);
686  ser = 1.0;
687  for (int j = 0; j <= 5; ++j) {
688  x += 1.0;
689  ser += cof[j] / x;
690  }
691  return -tmp + log(2.50662827465 * ser);
692 }
693 #endif
694 
695 #undef vpITMAX
696 #undef vpEPS
697 
698 #endif
699 END_VISP_NAMESPACE
Type * data
Address of the first element of the data array.
Definition: vpArray2D.h:148
unsigned int getRows() const
Definition: vpArray2D.h:347
Implementation of column vector and the associated operations.
Definition: vpColVector.h:191
void resize(unsigned int i, bool flagNullify=true)
Definition: vpColVector.h:1143
static double sqr(double x)
Definition: vpMath.h:203
Contains an M-estimator and various influence function.
Definition: vpRobust.h:84
vpRobustEstimatorType
Enumeration of influence functions.
Definition: vpRobust.h:88
@ HUBER
Huber influence function.
Definition: vpRobust.h:91
@ TUKEY
Tukey influence function.
Definition: vpRobust.h:89
@ CAUCHY
Cauchy influence function.
Definition: vpRobust.h:90
void MEstimator(const vpRobustEstimatorType method, const vpColVector &residues, vpColVector &weights)
Definition: vpRobust.cpp:130
vpRobust()
Definition: vpRobust.cpp:53
vpRobust & operator=(const vpRobust &other)
Definition: vpRobust.cpp:69