Visual Servoing Platform version 3.7.0
Loading...
Searching...
No Matches
vpRobust.cpp
1/*
2 * ViSP, open source Visual Servoing Platform software.
3 * Copyright (C) 2005 - 2025 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
37
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
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
64vpRobust::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
107void 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// ===================================================================
130void 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 throw(vpException(vpException::fatalError, "Unsupported robust estimator type in vpRobust::MEstimator()"));
180 }
181 }
182}
183
191
192void vpRobust::psiTukey(double sigma, const vpColVector &x, vpColVector &weights)
193{
194 unsigned int n_data = x.getRows();
195 double C = sigma * 4.6851;
196
197 // Here we consider that sigma 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
220void vpRobust::psiHuber(double sigma, const vpColVector &x, vpColVector &weights)
221{
222 double C = sigma * 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
243
244void vpRobust::psiCauchy(double sigma, const vpColVector &x, vpColVector &weights)
245{
246 unsigned int n_data = x.getRows();
247 double C = sigma * 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
261int 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
291double 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)
313vpRobust::vpRobust(unsigned int n_data)
314 : m_normres(), m_sorted_normres(), m_sorted_residues(), m_mad_min(0.0017), m_mad_prev(0),
315#if defined(VISP_BUILD_DEPRECATED_FUNCTIONS)
316 m_iter(0),
317#endif
318 m_size(n_data), m_mad(0)
319{
320 m_normres.resize(n_data);
321 m_sorted_normres.resize(n_data);
322 m_sorted_residues.resize(n_data);
323 // m_mad_min=0.0017; //Can not be more accurate than 1 pixel
324}
325
326void vpRobust::MEstimator(const vpRobustEstimatorType method, const vpColVector &residues,
327 const vpColVector &all_residues, vpColVector &weights)
328{
329 double normmedian = 0; // Normalized median
330
331 unsigned int n_all_data = all_residues.getRows();
332 vpColVector all_normres(n_all_data);
333
334 // compute median with the residues vector, return all_normres which are the
335 // normalized all_residues vector.
336 normmedian = computeNormalizedMedian(all_normres, residues, all_residues, weights);
337
338 // 1.48 keeps scale estimate consistent for a normal probability dist.
339 m_mad = 1.4826 * normmedian; // Median Absolute Deviation
340
341 // Set a minimum threshold for sigma
342 // (when sigma reaches the level of noise in the image)
343 if (m_mad < m_mad_min) {
344 m_mad = m_mad_min;
345 }
346
347 switch (method) {
348 case TUKEY: {
349 psiTukey(m_mad, all_normres, weights);
350 break;
351 }
352 case CAUCHY: {
353 psiCauchy(m_mad, all_normres, weights);
354 break;
355 }
356 case HUBER: {
357 psiHuber(m_mad, all_normres, weights);
358 break;
359 }
360 default: {
361 throw(vpException(vpException::fatalError, "Unsupported robust estimator type in vpRobust::MEstimator()"));
362 }
363 }
364}
365
366double 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 = static_cast<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
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 = static_cast<unsigned int>(ceil(n_data / 2.0)) - 1;
431 med = select(residues, 0, n_data - 1, ind_med);
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
463double 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
525double 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 default: {
535 throw(vpException(vpException::fatalError, "Unsupported robust estimator type in vpRobust::constrainedChi()"));
536 }
537 }
538
539 return -1;
540}
541
542double vpRobust::constrainedChiTukey(double x)
543{
544 double sct = 0;
545 double s = m_mad_prev;
546 // double epsillon=0.5;
547
548 if (fabs(x) <= 4.7 * m_mad_prev) {
549 double a = 4.7;
550 // sct =
551 // (vpMath::sqr(s*a-x)*vpMath::sqr(s*a+x)*vpMath::sqr(x))/(s*vpMath::sqr(vpMath::sqr(a*vpMath::sqr(s))));
552 sct = (vpMath::sqr(s * a) * x - s * vpMath::sqr(s * a) - x * vpMath::sqr(x)) *
553 (vpMath::sqr(s * a) * x + s * vpMath::sqr(s * a) - x * vpMath::sqr(x)) / s *
555 }
556 else
557 sct = -1 / s;
558
559 return sct;
560}
561
562double vpRobust::constrainedChiCauchy(double x)
563{
564 double sct = 0;
565 // double u = x/m_mad_prev;
566 double s = m_mad_prev;
567 double b = 2.3849;
568
569 sct = -1 * (vpMath::sqr(x) * b) / (s * (vpMath::sqr(s * b) + vpMath::sqr(x)));
570
571 return sct;
572}
573
574double vpRobust::constrainedChiHuber(double x)
575{
576 double sct = 0;
577 double u = x / m_mad_prev;
578 double c = 1.2107; // 1.345;
579
580 if (fabs(u) <= c)
581 sct = vpMath::sqr(u);
582 else
583 sct = vpMath::sqr(c);
584
585 return sct;
586}
587
588double vpRobust::simult_chi_huber(double x)
589{
590 double sct = 0;
591 double u = x / m_mad_prev;
592 double c = 1.2107; // 1.345;
593
594 if (fabs(u) <= c) {
595 // sct = 0.5*vpMath::sqr(u);
596 sct = vpMath::sqr(u);
597 }
598 else {
599 // sct = 0.5*vpMath::sqr(c);
600 sct = vpMath::sqr(c);
601 }
602
603 return sct;
604}
605
606#if !defined(VISP_HAVE_FUNC_ERFC) && !defined(VISP_HAVE_FUNC_STD_ERFC)
607
608#define vpITMAX 100
609#define vpEPS 3.0e-7
610
611double vpRobust::erf(double x) { return x < 0.0 ? -gammp(0.5, x * x) : gammp(0.5, x * x); }
612
613double vpRobust::gammp(double a, double x)
614{
615 double gamser = 0., gammcf = 0., gln;
616
617 if (x < 0.0 || a <= 0.0)
618 std::cout << "Invalid arguments in routine GAMMP";
619 if (x < (a + 1.0)) {
620 gser(&gamser, a, x, &gln);
621 return gamser;
622 }
623 else {
624 gcf(&gammcf, a, x, &gln);
625 return 1.0 - gammcf;
626 }
627}
628
629void vpRobust::gser(double *gamser, double a, double x, double *gln)
630{
631 *gln = gammln(a);
632 if (x <= 0.0) {
633 if (x < 0.0)
634 std::cout << "x less than 0 in routine GSER";
635 *gamser = 0.0;
636 return;
637 }
638 else {
639 double ap = a;
640 double sum = 1.0 / a;
641 double del = sum;
642 for (int n = 1; n <= vpITMAX; ++n) {
643 ap += 1.0;
644 del *= x / ap;
645 sum += del;
646 if (fabs(del) < fabs(sum) * vpEPS) {
647 *gamser = sum * exp(-x + a * log(x) - (*gln));
648 return;
649 }
650 }
651 std::cout << "a too large, vpITMAX too small in routine GSER";
652 return;
653 }
654}
655
656void vpRobust::gcf(double *gammcf, double a, double x, double *gln)
657{
658 double gold = 0.0, g, fac = 1.0, b1 = 1.0;
659 double b0 = 0.0, a1, a0 = 1.0;
660
661 *gln = gammln(a);
662 a1 = x;
663 for (int n = 1; n <= vpITMAX; ++n) {
664 double an = static_cast<double>(n);
665 double ana = an - a;
666 a0 = (a1 + a0 * ana) * fac;
667 b0 = (b1 + b0 * ana) * fac;
668 double anf = an * fac;
669 a1 = x * a0 + anf * a1;
670 b1 = x * b0 + anf * b1;
671 // if (a1)
672 if (std::fabs(a1) > std::numeric_limits<double>::epsilon()) {
673 fac = 1.0 / a1;
674 g = b1 * fac;
675 if (fabs((g - gold) / g) < vpEPS) {
676 *gammcf = exp(-x + a * log(x) - (*gln)) * g;
677 return;
678 }
679 gold = g;
680 }
681 }
682 std::cout << "a too large, vpITMAX too small in routine GCF";
683}
684
685double vpRobust::gammln(double xx)
686{
687 double x, tmp, ser;
688 static double cof[6] = { 76.18009173, -86.50532033, 24.01409822, -1.231739516, 0.120858003e-2, -0.536382e-5 };
689
690 x = xx - 1.0;
691 tmp = x + 5.5;
692 tmp -= (x + 0.5) * log(tmp);
693 ser = 1.0;
694 for (int j = 0; j <= 5; ++j) {
695 x += 1.0;
696 ser += cof[j] / x;
697 }
698 return -tmp + log(2.50662827465 * ser);
699}
700#endif
701
702#undef vpITMAX
703#undef vpEPS
704
705#endif
706END_VISP_NAMESPACE
Type * data
Address of the first element of the data array.
Definition vpArray2D.h:149
unsigned int getRows() const
Definition vpArray2D.h:433
Implementation of column vector and the associated operations.
void resize(unsigned int i, bool flagNullify=true)
error that can be emitted by ViSP classes.
Definition vpException.h:60
@ fatalError
Fatal error.
Definition vpException.h:72
static double sqr(double x)
Definition vpMath.h:203
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 & operator=(const vpRobust &other)
Definition vpRobust.cpp:69
VP_DEPRECATED vpColVector simultMEstimator(vpColVector &residues)
Definition vpRobust.cpp:421