LevMarFitting.h
Go to the documentation of this file.
1 #ifndef LEVMARFITTING_HEADER
2 #define LEVMARFITTING_HEADER
3 #include <algorithm>
4 #include <cmath>
5 #include <cstring>
6 #include <iostream>
7 
8 #include "LevMarFunc.h"
9 #include "Vector.h"
10 #ifdef DOPARALLEL
11 #include <omp.h>
12 #endif
13 
14 template <class ScalarT>
15 bool
16 Cholesky(ScalarT* a, size_t n, ScalarT p[])
17 /*Given a positive-definite symmetric matrix a[1..n][1..n], this routine constructs its Cholesky
18 decomposition, A = L � LT . On input, only the upper triangle of a need be given; it is not
19 modified. The Cholesky factor L is returned in the lower triangle of a, except for its diagonal
20 elements which are returned in p[1..n].*/
21 {
22  size_t i, j, k;
23  ScalarT sum;
24  for (i = 0; i < n; ++i)
25  {
26  for (j = i; j < n; ++j)
27  {
28  for (sum = a[i * n + j], k = i - 1; k != -1; --k)
29  {
30  sum -= a[i * n + k] * a[j * n + k];
31  }
32  if (i == j)
33  {
34  if (sum <= ScalarT(0))
35  // a, with rounding errors, is not positive definite.
36  {
37  return false;
38  }
39  p[i] = std::sqrt(sum);
40  }
41  else
42  {
43  a[j * n + i] = sum / p[i];
44  }
45  }
46  }
47  return true;
48 }
49 
50 template <class ScalarT, unsigned int N>
51 bool
52 Cholesky(ScalarT* a, ScalarT p[])
53 /*Given a positive-definite symmetric matrix a[1..n][1..n], this routine constructs its Cholesky
54 decomposition, A = L � LT . On input, only the upper triangle of a need be given; it is not
55 modified. The Cholesky factor L is returned in the lower triangle of a, except for its diagonal
56 elements which are returned in p[1..n].*/
57 {
58  size_t i, j, k;
59  ScalarT sum;
60  for (i = 0; i < N; ++i)
61  {
62  for (j = i; j < N; ++j)
63  {
64  for (sum = a[i * N + j], k = i - 1; k != -1; --k)
65  {
66  sum -= a[i * N + k] * a[j * N + k];
67  }
68  if (i == j)
69  {
70  if (sum <= ScalarT(0))
71  // a, with rounding errors, is not positive definite.
72  {
73  return false;
74  }
75  p[i] = std::sqrt(sum);
76  }
77  else
78  {
79  a[j * N + i] = sum / p[i];
80  }
81  }
82  }
83  return true;
84 }
85 
86 template <class ScalarT>
87 void
88 CholeskySolve(ScalarT* a, size_t n, ScalarT p[], ScalarT b[], ScalarT x[])
89 /*Solves the set of n linear equations A � x = b, where a is a positive-definite symmetric matrix.
90 a[1..n][1..n] and p[1..n] are input as the output of the routine choldc. Only the lower
91 subdiagonal portion of a is accessed. b[1..n] is input as the right-hand side vector. The
92 solution vector is returned in x[1..n]. a, n, and p are not modified and can be left in place
93 for successive calls with different right-hand sides b. b is not modified unless you identify b and
94 x in the calling sequence, which is allowed.*/
95 {
96  size_t i, k;
97  ScalarT sum;
98  for (i = 0; i < n; i++)
99  {
100  // Solve L � y = b, storing y in x.
101  for (sum = b[i], k = i - 1; k != -1; --k)
102  {
103  sum -= a[i * n + k] * x[k];
104  }
105  x[i] = sum / p[i];
106  }
107  for (i = n - 1; i != -1; --i)
108  {
109  // Solve LT � x = y.
110  for (sum = x[i], k = i + 1; k < n; ++k)
111  {
112  sum -= a[k * n + i] * x[k];
113  }
114  x[i] = sum / p[i];
115  }
116 }
117 
118 template <class ScalarT, unsigned int N>
119 void
120 CholeskySolve(ScalarT* a, ScalarT p[], ScalarT b[], ScalarT x[])
121 /*Solves the set of n linear equations A � x = b, where a is a positive-definite symmetric matrix.
122 a[1..n][1..n] and p[1..n] are input as the output of the routine choldc. Only the lower
123 subdiagonal portion of a is accessed. b[1..n] is input as the right-hand side vector. The
124 solution vector is returned in x[1..n]. a, n, and p are not modified and can be left in place
125 for successive calls with different right-hand sides b. b is not modified unless you identify b and
126 x in the calling sequence, which is allowed.*/
127 {
128  size_t i, k;
129  ScalarT sum;
130  for (i = 0; i < N; i++)
131  {
132  // Solve L � y = b, storing y in x.
133  for (sum = b[i], k = i - 1; k != -1; --k)
134  {
135  sum -= a[i * N + k] * x[k];
136  }
137  x[i] = sum / p[i];
138  }
139  for (i = N - 1; i != -1; --i)
140  {
141  // Solve LT � x = y.
142  for (sum = x[i], k = i + 1; k < N; ++k)
143  {
144  sum -= a[k * N + i] * x[k];
145  }
146  x[i] = sum / p[i];
147  }
148 }
149 
150 template <class IteratorT, class FuncT>
151 bool
152 LevMar(IteratorT begin, IteratorT end, FuncT& func, typename FuncT::ScalarType* param)
153 {
154  typedef typename FuncT::ScalarType ScalarType;
155 
156  enum
157  {
158  paramDim = FuncT::NumParams
159  };
160 
161  bool retVal = true;
162  unsigned int totalSize = end - begin;
163  if (!totalSize)
164  {
165  return false;
166  }
167  ScalarType lambda = ScalarType(0.0001);
168  ScalarType* F0 = new ScalarType[totalSize * paramDim];
169  ScalarType* U = new ScalarType[paramDim * paramDim];
170  ScalarType* H = new ScalarType[paramDim * paramDim];
171  ScalarType* v = new ScalarType[paramDim];
172  ScalarType* d = new ScalarType[totalSize];
173  ScalarType* temp = new ScalarType[totalSize];
174  ScalarType* x = new ScalarType[paramDim];
175  ScalarType* p = new ScalarType[paramDim];
176  ScalarType* paramNew = new ScalarType[paramDim];
177  size_t nu = 2;
178  func.Normalize(param);
179  ScalarType paramNorm = 0;
180 
181  // do fitting in different steps
182  unsigned int subsets =
183  std::max(int(std::floor(std::log((float)totalSize) / std::log(2.f))) - 8, 1);
184 #ifdef PRECISIONLEVMAR
185  subsets = 1;
186 #endif
187  MiscLib::Vector<unsigned int> subsetSizes(subsets);
188  for (unsigned int i = subsetSizes.size(); i;)
189  {
190  --i;
191  subsetSizes[i] = totalSize;
192  if (i)
193  {
194  subsetSizes[i] = subsetSizes[i] >> 1;
195  }
196  totalSize -= subsetSizes[i];
197  }
198  unsigned int curSubset = 0;
199  unsigned int size = 0;
200 
201  // get current error
202  ScalarType chi = 0, newChi = 0;
203  ScalarType rho = 1;
204  unsigned int outerIter = 0,
205 #ifndef PRECISIONLEVMAR
206  maxOuterIter = 200 / subsetSizes.size(),
207 #else
208  maxOuterIter = 500,
209 #endif
210  usefulIter = 0, totalIter = 0;
211  ;
212  do
213  {
214  // get current error
215  size += subsetSizes[curSubset];
216  newChi = func.Chi(param, begin, begin + size, d, temp);
217  for (unsigned int i = 0; i < paramDim; ++i)
218  {
219  paramNew[i] = param[i];
220  }
221  outerIter = 0;
222  if (rho < 0)
223  {
224  rho = 1;
225  }
226  do
227  {
228  ++outerIter;
229  ++totalIter;
230  if (rho > 0)
231  {
232  nu = 2;
233  chi = newChi;
234  for (size_t i = 0; i < paramDim; ++i)
235  {
236  param[i] = paramNew[i];
237  }
238 #ifndef PRECISIONLEVMAR
239  if (std::sqrt(chi / size) <
240  ScalarType(1e-5)) // chi very small? -> will be hard to improve
241  {
242  //std::cout << "LevMar converged because of small chi" << std::endl;
243  break;
244  }
245 #endif
246  paramNorm = 0;
247  for (size_t i = 0; i < paramDim; ++i)
248  {
249  paramNorm += param[i] * param[i];
250  }
251  paramNorm = std::sqrt(paramNorm);
252  // construct the needed matrices
253  // F0 is the matrix constructed from param
254  // F0 has gradient_i(param) as its ith row
255  func.Derivatives(param, begin, begin + size, d, temp, F0);
256 // U = F0_t * F0
257 // v = F0_t * d(param) (d(param) = [d_i(param)])
258 #pragma omp parallel for
259  for (int i = 0; i < paramDim; ++i)
260  {
261  for (size_t j = i; j < paramDim;
262  ++j) // j = i since only upper triangle is needed
263  {
264  U[i * paramDim + j] = 0;
265  for (size_t k = 0; k < size; ++k)
266  {
267  U[i * paramDim + j] += F0[k * paramDim + i] * F0[k * paramDim + j];
268  }
269  }
270  }
271  ScalarType vmag = 0; // magnitude of v
272 #pragma omp parallel for
273  for (int i = 0; i < paramDim; ++i)
274  {
275  v[i] = 0;
276  for (size_t k = 0; k < size; ++k)
277  {
278  v[i] += F0[k * paramDim + i] * d[k];
279  }
280  v[i] *= -1;
281 #ifndef DOPARALLEL
282  vmag = std::max(abs(v[i]), vmag);
283 #endif
284  }
285 #ifdef DOPARALLEL
286  for (unsigned int i = 0; i < paramDim; ++i)
287  {
288  vmag = std::max(abs(v[i]), vmag);
289  }
290 #endif
291  // and check for convergence with magnitude of v
292 #ifndef PRECISIONLEVMAR
293  if (vmag < ScalarType(1e-6))
294 #else
295  if (vmag < ScalarType(1e-8))
296 #endif
297  {
298  //std::cout << "LevMar converged with small gradient" << std::endl;
299  //retVal = chi < initialChi;
300  //goto cleanup;
301  break;
302  }
303  if (outerIter == 1)
304  {
305  // compute magnitue of F0
306  ScalarType fmag = abs(F0[0]);
307  for (size_t i = 1; i < paramDim * size; ++i)
308  if (fmag < abs(F0[i]))
309  {
310  fmag = abs(F0[i]);
311  }
312  lambda = 1e-3f * fmag;
313  }
314  else
315  {
316  lambda *= std::max(ScalarType(.3), ScalarType(1 - std::pow(2 * rho - 1, 3)));
317  }
318  }
319 
320  std::memcpy(H, U, sizeof(ScalarType) * paramDim * paramDim);
321  for (size_t i = 0; i < paramDim; ++i)
322  {
323  H[i * paramDim + i] += lambda; // * (ScalarType(1) + H[i * paramDim + i]);
324  }
325  // now H is positive definite and symmetric
326  // solve Hx = -v with Cholesky
327  ScalarType xNorm = 0, L = 0;
328  if (!Cholesky<ScalarType, paramDim>(H, p))
329  {
330  goto increment;
331  }
332  CholeskySolve<ScalarType, paramDim>(H, p, v, x);
333 
334  // magnitude of x small? If yes we are done
335  for (size_t i = 0; i < paramDim; ++i)
336  {
337  xNorm += x[i] * x[i];
338  }
339  xNorm = std::sqrt(xNorm);
340 #ifndef PRECISIONLEVMAR
341  if (xNorm <= ScalarType(1e-6) * (paramNorm + ScalarType(1e-6)))
342 #else
343  if (xNorm <= ScalarType(1e-8) * (paramNorm + ScalarType(1e-8)))
344 #endif
345  {
346  //std::cout << "LevMar converged with small step" << std::endl;
347  //goto cleanup;
348  break;
349  }
350 
351  for (size_t i = 0; i < paramDim; ++i)
352  {
353  paramNew[i] = param[i] + x[i];
354  }
355  func.Normalize(paramNew);
356 
357  // get new error
358  newChi = func.Chi(paramNew, begin, begin + size, d, temp);
359 
360  // the following test is taken from
361  // "Methods for non-linear least squares problems"
362  // by Madsen, Nielsen, Tingleff
363  L = 0;
364  for (size_t i = 0; i < paramDim; ++i)
365  {
366  L += .5f * x[i] * (lambda * x[i] + v[i]);
367  }
368  rho = (chi - newChi) / L;
369  if (rho > 0)
370  {
371  ++usefulIter;
372 #ifndef PRECISIONLEVMAR
373  if ((chi - newChi) < 1e-4 * chi)
374  {
375  //std::cout << "LevMar converged with small chi difference" << std::endl;
376  chi = newChi;
377  for (size_t i = 0; i < paramDim; ++i)
378  {
379  param[i] = paramNew[i];
380  }
381  break;
382  }
383 #endif
384  continue;
385  }
386 
387  increment:
388  rho = -1;
389  // increment lambda
390  lambda = nu * lambda;
391  size_t nu2 = nu << 1;
392  if (nu2 < nu)
393  {
394  nu2 = 2;
395  }
396  nu = nu2;
397  } while (outerIter < maxOuterIter);
398  ++curSubset;
399  } while (curSubset < subsetSizes.size());
400  retVal = usefulIter > 0;
401  delete[] F0;
402  delete[] U;
403  delete[] H;
404  delete[] v;
405  delete[] d;
406  delete[] temp;
407  delete[] x;
408  delete[] p;
409  delete[] paramNew;
410  return retVal;
411 }
412 
413 template <class ScalarT>
414 ScalarT
415 LevMar(unsigned int paramDim,
416  unsigned int imgDim,
417  const LevMarFunc<ScalarT>** funcs,
418  ScalarT* param)
419 {
420  ScalarT retVal = -1;
421  size_t size = imgDim;
422  float lambda = ScalarT(0.0001);
423  ScalarT* F0 = new ScalarT[size * paramDim];
424  ScalarT* U = new ScalarT[paramDim * paramDim];
425  ScalarT* H = new ScalarT[paramDim * paramDim];
426  ScalarT* v = new ScalarT[paramDim];
427  ScalarT* d = new ScalarT[size];
428  ScalarT* dNew = new ScalarT[size];
429  ScalarT* x = new ScalarT[paramDim];
430  ScalarT* p = new ScalarT[paramDim];
431  ScalarT* paramNew = new ScalarT[paramDim];
432  size_t outerIter = 0, maxOuterIter = 10;
433  // get current error
434  ScalarT chi = 0, newChi;
435  for (size_t i = 0; i < size; ++i)
436  {
437  d[i] = (*(funcs[i]))(param);
438  chi += d[i] * d[i];
439  }
440  do
441  {
442  ++outerIter;
443  lambda *= ScalarT(0.04);
444  // construct the needed matrices
445  // F0 is the matrix constructed from param
446  // F0 has gradient_i(param) as its ith row
447  for (size_t i = 0; i < size; ++i)
448  {
449  (*(funcs[i]))(param, &F0[i * paramDim]);
450  }
451  // U = F0_t * F0
452  for (size_t i = 0; i < paramDim; ++i)
453  for (size_t j = i; j < paramDim; ++j) // j = i since only upper triangle is needed
454  {
455  U[i * paramDim + j] = 0;
456  for (size_t k = 0; k < size; ++k)
457  U[i * paramDim + j] += F0[k * paramDim + i] * F0[k * paramDim + j];
458  }
459  // v = F_t * d(param) (d(param) = [d_i(param)])
460  for (size_t i = 0; i < paramDim; ++i)
461  {
462  v[i] = 0;
463  for (size_t k = 0; k < size; ++k)
464  {
465  v[i] += F0[k * paramDim + i] * d[k];
466  }
467  v[i] *= -1;
468  }
469  size_t iter = 0, maxIter = 10;
470  do
471  {
472  ++iter;
473  // increment lambda
474  lambda = 10 * lambda;
475  memcpy(H, U, sizeof(ScalarT) * paramDim * paramDim);
476  for (size_t i = 0; i < paramDim; ++i)
477  {
478  H[i * paramDim + i] += lambda * (ScalarT(1) + H[i * paramDim + i]);
479  }
480  // now H is positive definite and symmetric
481  // solve Hx = -v with Cholesky
482  if (!Cholesky(H, paramDim, p))
483  {
484  goto cleanup;
485  }
486  CholeskySolve(H, paramDim, p, v, x);
487  for (size_t i = 0; i < paramDim; ++i)
488  {
489  paramNew[i] = param[i] + x[i];
490  }
491  // get new error
492  newChi = 0;
493  for (size_t i = 0; i < size; ++i)
494  {
495  dNew[i] = (*(funcs[i]))(paramNew);
496  newChi += dNew[i] * dNew[i];
497  }
498  // check for convergence
499  /*float cvgTest = 0;
500  for(size_t i = 0; i < paramDim; ++i)
501  {
502  //float c = param[i] - paramNew[i];
503  cvgTest += x[i] * x[i];
504  }
505  if(std::sqrt(cvgTest) < 1e-6)
506  {
507  for(size_t i = 0; i < paramDim; ++i)
508  param[i] = paramNew[i];
509  goto cleanup;
510  }*/
511  if (/*newChi <= chi &&*/ abs(chi - newChi) / chi < ScalarT(1e-4))
512  {
513  for (size_t i = 0; i < paramDim; ++i)
514  {
515  param[i] = paramNew[i];
516  }
517  retVal = newChi;
518  goto cleanup;
519  }
520  } while (newChi > chi && iter < maxIter);
521  if (newChi < chi)
522  {
523  chi = newChi;
524  for (size_t i = 0; i < paramDim; ++i)
525  {
526  param[i] = paramNew[i];
527  }
528  std::swap(d, dNew);
529  }
530  } while (outerIter < maxOuterIter);
531 cleanup:
532  delete[] F0;
533  delete[] U;
534  delete[] H;
535  delete[] v;
536  delete[] d;
537  delete[] dNew;
538  delete[] x;
539  delete[] p;
540  delete[] paramNew;
541  return retVal;
542 }
543 
544 #endif
LevMarFunc
Definition: LevMarFunc.h:5
Vector.h
CholeskySolve
void CholeskySolve(ScalarT *a, size_t n, ScalarT p[], ScalarT b[], ScalarT x[])
Definition: LevMarFitting.h:88
Cholesky
bool Cholesky(ScalarT *a, size_t n, ScalarT p[])
Definition: LevMarFitting.h:16
magic_enum::detail::n
constexpr auto n() noexcept
Definition: magic_enum.hpp:418
MiscLib::Vector
Definition: Vector.h:19
LevMarFunc.h
armarx::armem::client::util::swap
void swap(SubscriptionHandle &first, SubscriptionHandle &second)
Definition: SubscriptionHandle.cpp:66
MiscLib::Vector::size
size_type size() const
Definition: Vector.h:215
armarx::ctrlutil::a
double a(double t, double a0, double j)
Definition: CtrlUtil.h:45
armarx::abs
std::vector< T > abs(const std::vector< T > &v)
Definition: VectorHelpers.h:281
max
T max(T t1, T t2)
Definition: gdiam.h:51
GfxTL::sqrt
VectorXD< D, T > sqrt(const VectorXD< D, T > &a)
Definition: VectorXD.h:704
armarx::ctrlutil::v
double v(double t, double v0, double a0, double j)
Definition: CtrlUtil.h:39
LevMar
bool LevMar(IteratorT begin, IteratorT end, FuncT &func, typename FuncT::ScalarType *param)
Definition: LevMarFitting.h:152
armarx
This file offers overloads of toIce() and fromIce() functions for STL container types.
Definition: ArmarXTimeserver.cpp:27