Visual Servoing Platform  version 3.3.0 under development (2020-02-17)
vpMbtTukeyEstimator.h
1 /****************************************************************************
2  *
3  * ViSP, open source Visual Servoing Platform software.
4  * Copyright (C) 2005 - 2019 by Inria. All rights reserved.
5  *
6  * This software is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  * See the file LICENSE.txt at the root directory of this source
11  * distribution for additional information about the GNU GPL.
12  *
13  * For using ViSP with software that can not be combined with the GNU
14  * GPL, please contact Inria about acquiring a ViSP Professional
15  * Edition License.
16  *
17  * See http://visp.inria.fr for more information.
18  *
19  * This software was developed at:
20  * Inria Rennes - Bretagne Atlantique
21  * Campus Universitaire de Beaulieu
22  * 35042 Rennes Cedex
23  * France
24  *
25  * If you have questions regarding the use of this file, please contact
26  * Inria at visp@inria.fr
27  *
28  * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
29  * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
30  *
31  * Description:
32  * Tukey M-estimator.
33  *
34  *****************************************************************************/
35 
36 #ifndef _vpMbtTukeyEstimator_h_
37 #define _vpMbtTukeyEstimator_h_
38 
39 #include <vector>
40 #include <visp3/core/vpColVector.h>
41 
42 #ifndef DOXYGEN_SHOULD_SKIP_THIS
43 
44 template <typename T> class vpMbtTukeyEstimator
45 {
46 public:
47  void MEstimator(const std::vector<T> &residues, std::vector<T> &weights, const T NoiseThreshold);
48  void MEstimator(const vpColVector &residues, vpColVector &weights, const double NoiseThreshold);
49 
50 private:
51  T getMedian(std::vector<T> &vec);
52  void MEstimator_impl(const std::vector<T> &residues, std::vector<T> &weights, const T NoiseThreshold);
53  void MEstimator_impl_ssse3(const std::vector<T> &residues, std::vector<T> &weights, const T NoiseThreshold);
54  void psiTukey(const T sig, std::vector<T> &x, std::vector<T> &weights);
55  void psiTukey(const T sig, std::vector<T> &x, vpColVector &weights);
56 
57  std::vector<T> m_normres;
58  std::vector<T> m_residues;
59 };
60 #endif //#ifndef DOXYGEN_SHOULD_SKIP_THIS
61 
62 
63 /*
64  * The code bellow previously in vpMbtTuckeyEstimator.cpp produced
65  * a link issue with MinGW-W64 x86_64-8.1.0-posix-seh-rt_v6-rev0 (g++ 8.1.0)
66  * libvisp_mbt.so.3.1.0: undefined reference to
67  * `vpMbtTukeyEstimator<double>::MEstimator(std::vector<double,
68  * std::allocator<double> > const&, std::vector<double, std::allocator<double>
69  * >&, double)'
70  * Note that with the previous MinGW-W64 version x86_64-7.3.0-posix-seh-rt_v6-rev0 (g++ 7.3.0)
71  * the build succeed.
72  *
73  * To remove this link issue the solution was to move the content of vpMbtTuckeyEstimator.cpp
74  * before remove.
75  */
76 #include <algorithm>
77 #include <cmath>
78 #include <iostream>
79 
80 #include <visp3/core/vpCPUFeatures.h>
81 
82 #define USE_TRANSFORM 1
83 #if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) && USE_TRANSFORM
84 #define HAVE_TRANSFORM 1
85 #endif
86 
87 #if HAVE_TRANSFORM
88 #include <functional>
89 #endif
90 
91 #if defined __SSE2__ || defined _M_X64 || (defined _M_IX86_FP && _M_IX86_FP >= 2)
92 #include <emmintrin.h>
93 #define VISP_HAVE_SSE2 1
94 
95 #if defined __SSE3__ || (defined _MSC_VER && _MSC_VER >= 1500)
96 #include <pmmintrin.h>
97 #define VISP_HAVE_SSE3 1
98 #endif
99 #if defined __SSSE3__ || (defined _MSC_VER && _MSC_VER >= 1500)
100 #include <tmmintrin.h>
101 #define VISP_HAVE_SSSE3 1
102 #endif
103 #endif
104 
105 #define USE_ORIGINAL_TUKEY_CODE 1
106 
107 #ifndef DOXYGEN_SHOULD_SKIP_THIS
108 
109 #if HAVE_TRANSFORM
110 namespace
111 {
112 template <typename T> struct AbsDiff : public std::binary_function<T, T, T> {
113  T operator()(const T a, const T b) const { return std::fabs(a - b); }
114 };
115 }
116 #endif
117 
118 template class vpMbtTukeyEstimator<float>;
119 template class vpMbtTukeyEstimator<double>;
120 
121 #if VISP_HAVE_SSSE3
122 namespace
123 {
124 inline __m128 abs_ps(__m128 x)
125 {
126  static const __m128 sign_mask = _mm_set1_ps(-0.f); // -0.f = 1 << 31
127  return _mm_andnot_ps(sign_mask, x);
128 }
129 }
130 #endif
131 
132 template <typename T> T vpMbtTukeyEstimator<T>::getMedian(std::vector<T> &vec)
133 {
134  // Not the exact median when even number of elements
135  int index = (int)(ceil(vec.size() / 2.0)) - 1;
136  std::nth_element(vec.begin(), vec.begin() + index, vec.end());
137  return vec[index];
138 }
139 
140 // Without MEstimator_impl, error with g++4.6, ok with gcc 5.4.0
141 // Ubuntu-12.04-Linux-i386-g++4.6-Dyn-RelWithDebInfo-dc1394-v4l2-X11-OpenCV2.3.1-lapack-gsl-Coin-jpeg-png-xml-pthread-OpenMP-dmtx-zbar-Wov-Weq-Moment:
142 // libvisp_mbt.so.3.1.0: undefined reference to
143 // `vpMbtTukeyEstimator<double>::MEstimator(std::vector<double,
144 // std::allocator<double> > const&, std::vector<double, std::allocator<double>
145 // >&, double)'
146 template <typename T>
147 void vpMbtTukeyEstimator<T>::MEstimator_impl(const std::vector<T> &residues, std::vector<T> &weights,
148  const T NoiseThreshold)
149 {
150  if (residues.empty()) {
151  return;
152  }
153 
154  m_residues = residues;
155 
156  T med = getMedian(m_residues);
157  m_normres.resize(residues.size());
158 
159 #if HAVE_TRANSFORM
160  std::transform(residues.begin(), residues.end(), m_normres.begin(),
161  std::bind(AbsDiff<T>(), std::placeholders::_1, med));
162 #else
163  for (size_t i = 0; i < m_residues.size(); i++) {
164  m_normres[i] = (std::fabs(residues[i] - med));
165  }
166 #endif
167 
168  m_residues = m_normres;
169  T normmedian = getMedian(m_residues);
170 
171  // 1.48 keeps scale estimate consistent for a normal probability dist.
172  T sigma = static_cast<T>(1.4826 * normmedian); // median Absolute Deviation
173 
174  // Set a minimum threshold for sigma
175  // (when sigma reaches the level of noise in the image)
176  if (sigma < NoiseThreshold) {
177  sigma = NoiseThreshold;
178  }
179 
180  psiTukey(sigma, m_normres, weights);
181 }
182 
183 template <>
184 inline void vpMbtTukeyEstimator<float>::MEstimator_impl_ssse3(const std::vector<float> &residues, std::vector<float> &weights,
185  const float NoiseThreshold)
186 {
187 #if VISP_HAVE_SSSE3
188  if (residues.empty()) {
189  return;
190  }
191 
192  m_residues = residues;
193 
194  float med = getMedian(m_residues);
195  m_normres.resize(residues.size());
196 
197  size_t i = 0;
198  __m128 med_128 = _mm_set_ps1(med);
199 
200  if (m_residues.size() >= 4) {
201  for (i = 0; i <= m_residues.size() - 4; i += 4) {
202  __m128 residues_128 = _mm_loadu_ps(residues.data() + i);
203  _mm_storeu_ps(m_normres.data() + i, abs_ps(_mm_sub_ps(residues_128, med_128)));
204  }
205  }
206 
207  for (; i < m_residues.size(); i++) {
208  m_normres[i] = (std::fabs(residues[i] - med));
209  }
210 
211  m_residues = m_normres;
212  float normmedian = getMedian(m_residues);
213 
214  // 1.48 keeps scale estimate consistent for a normal probability dist.
215  float sigma = 1.4826f * normmedian; // median Absolute Deviation
216 
217  // Set a minimum threshold for sigma
218  // (when sigma reaches the level of noise in the image)
219  if (sigma < NoiseThreshold) {
220  sigma = NoiseThreshold;
221  }
222 
223  psiTukey(sigma, m_normres, weights);
224 #else
225  (void)residues;
226  (void)weights;
227  (void)NoiseThreshold;
228 #endif
229 }
230 
231 template <>
232 inline void vpMbtTukeyEstimator<double>::MEstimator_impl_ssse3(const std::vector<double> &residues,
233  std::vector<double> &weights, const double NoiseThreshold)
234 {
235 #if VISP_HAVE_SSSE3
236  if (residues.empty()) {
237  return;
238  }
239 
240  m_residues = residues;
241 
242  double med = getMedian(m_residues);
243  m_normres.resize(residues.size());
244 
245 #if HAVE_TRANSFORM
246  std::transform(residues.begin(), residues.end(), m_normres.begin(),
247  std::bind(AbsDiff<double>(), std::placeholders::_1, med));
248 #else
249  for (size_t i = 0; i < m_residues.size(); i++) {
250  m_normres[i] = (std::fabs(residues[i] - med));
251  }
252 #endif
253 
254  m_residues = m_normres;
255  double normmedian = getMedian(m_residues);
256 
257  // 1.48 keeps scale estimate consistent for a normal probability dist.
258  double sigma = 1.4826 * normmedian; // median Absolute Deviation
259 
260  // Set a minimum threshold for sigma
261  // (when sigma reaches the level of noise in the image)
262  if (sigma < NoiseThreshold) {
263  sigma = NoiseThreshold;
264  }
265 
266  psiTukey(sigma, m_normres, weights);
267 #else
268  (void)residues;
269  (void)weights;
270  (void)NoiseThreshold;
271 #endif
272 }
273 
274 template <>
275 inline void vpMbtTukeyEstimator<float>::MEstimator(const std::vector<float> &residues, std::vector<float> &weights,
276  const float NoiseThreshold)
277 {
279 #if !VISP_HAVE_SSSE3
280  checkSSSE3 = false;
281 #endif
282 
283  if (checkSSSE3)
284  MEstimator_impl_ssse3(residues, weights, NoiseThreshold);
285  else
286  MEstimator_impl(residues, weights, NoiseThreshold);
287 }
288 
289 template <>
290 inline void vpMbtTukeyEstimator<double>::MEstimator(const std::vector<double> &residues, std::vector<double> &weights,
291  const double NoiseThreshold)
292 {
293  bool checkSSSE3 = vpCPUFeatures::checkSSSE3();
294 #if !VISP_HAVE_SSSE3
295  checkSSSE3 = false;
296 #endif
297 
298  if (checkSSSE3)
299  MEstimator_impl_ssse3(residues, weights, NoiseThreshold);
300  else
301  MEstimator_impl(residues, weights, NoiseThreshold);
302 }
303 
304 template <typename T> void vpMbtTukeyEstimator<T>::psiTukey(const T sig, std::vector<T> &x, vpColVector &weights)
305 {
306  T cst_const = static_cast<T>(4.6851);
307  T inv_cst_const = 1 / cst_const;
308  T inv_sig = 1 / sig;
309 
310  for (unsigned int i = 0; i < (unsigned int)x.size(); i++) {
311 #if USE_ORIGINAL_TUKEY_CODE
312  if (std::fabs(sig) <= std::numeric_limits<T>::epsilon() &&
313  std::fabs(weights[i]) > std::numeric_limits<double>::epsilon() // sig should be equal to 0
314  // only if NoiseThreshold
315  // ==
316  // 0
317  ) {
318  weights[i] = 1;
319  continue;
320  }
321 #endif
322 
323  double xi_sig = x[(size_t)i] * inv_sig;
324 
325  if ((std::fabs(xi_sig) <= cst_const)
326 #if USE_ORIGINAL_TUKEY_CODE
327  && std::fabs(weights[i]) > std::numeric_limits<double>::epsilon() // Consider the previous
328  // weights here
329 #endif
330  ) {
331  weights[i] = (1 - (xi_sig * inv_cst_const) * (xi_sig * inv_cst_const)) *
332  (1 - (xi_sig * inv_cst_const) * (xi_sig * inv_cst_const)); // vpMath::sqr( 1 -
333  // vpMath::sqr(xi_sig *
334  // inv_cst_const) );
335  } else {
336  // Outlier - could resize list of points tracked here?
337  weights[i] = 0;
338  }
339  }
340 }
341 
342 template <>
343 inline void vpMbtTukeyEstimator<double>::MEstimator(const vpColVector &residues, vpColVector &weights,
344  const double NoiseThreshold)
345 {
346  if (residues.size() == 0) {
347  return;
348  }
349 
350  m_residues.resize(0);
351  m_residues.reserve(residues.size());
352  m_residues.insert(m_residues.end(), &residues.data[0], &residues.data[residues.size()]);
353 
354  double med = getMedian(m_residues);
355 
356  m_normres.resize(residues.size());
357  for (size_t i = 0; i < m_residues.size(); i++) {
358  m_normres[i] = std::fabs(residues[(unsigned int)i] - med);
359  }
360 
361  m_residues = m_normres;
362  double normmedian = getMedian(m_residues);
363 
364  // 1.48 keeps scale estimate consistent for a normal probability dist.
365  double sigma = 1.4826 * normmedian; // median Absolute Deviation
366 
367  // Set a minimum threshold for sigma
368  // (when sigma reaches the level of noise in the image)
369  if (sigma < NoiseThreshold) {
370  sigma = NoiseThreshold;
371  }
372 
373  psiTukey(sigma, m_normres, weights);
374 }
375 
376 template <>
377 inline void vpMbtTukeyEstimator<float>::MEstimator(const vpColVector &residues, vpColVector &weights,
378  const double NoiseThreshold)
379 {
380  if (residues.size() == 0) {
381  return;
382  }
383 
384  m_residues.resize(0);
385  m_residues.reserve(residues.size());
386  for (unsigned int i = 0; i < residues.size(); i++) {
387  m_residues.push_back((float)residues[i]);
388  }
389 
390  float med = getMedian(m_residues);
391 
392  m_normres.resize(residues.size());
393  for (size_t i = 0; i < m_residues.size(); i++) {
394  m_normres[i] = (float)std::fabs(residues[(unsigned int)i] - med);
395  }
396 
397  m_residues = m_normres;
398  float normmedian = getMedian(m_residues);
399 
400  // 1.48 keeps scale estimate consistent for a normal probability dist.
401  float sigma = 1.4826f * normmedian; // median Absolute Deviation
402 
403  // Set a minimum threshold for sigma
404  // (when sigma reaches the level of noise in the image)
405  if (sigma < NoiseThreshold) {
406  sigma = (float)NoiseThreshold;
407  }
408 
409  psiTukey(sigma, m_normres, weights);
410 }
411 
412 template <class T> void vpMbtTukeyEstimator<T>::psiTukey(const T sig, std::vector<T> &x, std::vector<T> &weights)
413 {
414  T cst_const = static_cast<T>(4.6851);
415  T inv_cst_const = 1 / cst_const;
416  T inv_sig = 1 / sig;
417 
418  for (size_t i = 0; i < x.size(); i++) {
419 #if USE_ORIGINAL_TUKEY_CODE
420  if (std::fabs(sig) <= std::numeric_limits<T>::epsilon() &&
421  std::fabs(weights[i]) > std::numeric_limits<T>::epsilon() // sig should be equal to 0 only
422  // if NoiseThreshold == 0
423  ) {
424  weights[i] = 1;
425  continue;
426  }
427 #endif
428 
429  T xi_sig = x[i] * inv_sig;
430 
431  if ((std::fabs(xi_sig) <= cst_const)
432 #if USE_ORIGINAL_TUKEY_CODE
433  && std::fabs(weights[i]) > std::numeric_limits<T>::epsilon() // Consider the previous
434  // weights here
435 #endif
436  ) {
437  // vpMath::sqr( 1 - vpMath::sqr(xi_sig * inv_cst_const) );
438  weights[i] = (1 - (xi_sig * inv_cst_const) * (xi_sig * inv_cst_const)) *
439  (1 - (xi_sig * inv_cst_const) * (xi_sig * inv_cst_const));
440  } else {
441  // Outlier - could resize list of points tracked here?
442  weights[i] = 0;
443  }
444  }
445 }
446 #endif //#ifndef DOXYGEN_SHOULD_SKIP_THIS
447 
448 #endif
Type * data
Address of the first element of the data array.
Definition: vpArray2D.h:145
unsigned int size() const
Return the number of elements of the 2D array.
Definition: vpArray2D.h:291
VISP_EXPORT bool checkSSSE3()
void resize(unsigned int i, bool flagNullify=true)
Definition: vpColVector.h:310
Implementation of column vector and the associated operations.
Definition: vpColVector.h:130