2 ******************************************************************************
4 * @file calibrationutils.c
5 * @author The OpenPilot Team, http://www.openpilot.org Copyright (C) 2013.
7 * @brief Utilities for calibration. Ellipsoid and polynomial fit algorithms
8 * @see The GNU Public License (GPL) Version 3
12 *****************************************************************************/
14 * This program is free software; you can redistribute it and/or modify
15 * it under the terms of the GNU General Public License as published by
16 * the Free Software Foundation; either version 3 of the License, or
17 * (at your option) any later version.
19 * This program is distributed in the hope that it will be useful, but
20 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
21 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
24 * You should have received a copy of the GNU General Public License along
25 * with this program; if not, write to the Free Software Foundation, Inc.,
26 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
29 #include "calibrationutils.h"
31 using namespace Eigen
;
33 float CalibrationUtils::ComputeSigma(Eigen::VectorXf
*samplesY
)
35 Eigen::ArrayXd tmpd
= samplesY
->cast
<double>().array();
36 double mean
= tmpd
.mean();
38 return (float)sqrt((tmpd
- mean
).square().mean());
42 * The following ellipsoid calibration code is based on RazorImu calibration samples that can be found here:
43 * https://github.com/ptrbrtz/razor-9dof-ahrs/tree/master/Matlab/magnetometer_calibration
45 bool CalibrationUtils::EllipsoidCalibration(Eigen::VectorXf
*samplesX
, Eigen::VectorXf
*samplesY
, Eigen::VectorXf
*samplesZ
,
47 EllipsoidCalibrationResult
*result
,
50 Eigen::VectorXf radii
;
51 Eigen::Vector3f center
;
52 Eigen::MatrixXf evecs
;
54 EllipsoidFit(samplesX
, samplesY
, samplesZ
, ¢er
, &radii
, &evecs
, fitAlongXYZ
);
56 result
->Scale
.setZero();
58 result
->Scale
<< nominalRange
/ radii
.coeff(0),
59 nominalRange
/ radii
.coeff(1),
60 nominalRange
/ radii
.coeff(2);
63 tmp
<< result
->Scale
.coeff(0), 0, 0,
64 0, result
->Scale
.coeff(1), 0,
65 0, 0, result
->Scale
.coeff(2);
67 result
->CalibrationMatrix
= evecs
* tmp
* evecs
.transpose();
68 result
->Bias
.setZero();
69 result
->Bias
<< center
.coeff(0), center
.coeff(1), center
.coeff(2);
73 bool CalibrationUtils::PolynomialCalibration(VectorXf
*samplesX
, Eigen::VectorXf
*samplesY
, int degree
, Eigen::Ref
<Eigen::VectorXf
> result
, const double maxRelativeError
)
75 int samples
= samplesX
->rows();
76 // perform internal calculation using doubles
77 VectorXd doubleX
= samplesX
->cast
<double>();
78 VectorXd doubleY
= samplesY
->cast
<double>();
79 Eigen::MatrixXd
x(samples
, degree
+ 1);
81 x
.setOnes(samples
, degree
+ 1);
83 for (int i
= 1; i
< degree
+ 1; i
++) {
84 Eigen::MatrixXd tmp
= Eigen::MatrixXd(x
.col(i
- 1));
85 Eigen::MatrixXd tmp2
= tmp
.cwiseProduct(doubleX
);
89 Eigen::MatrixXd xt
= x
.transpose();
91 Eigen::MatrixXd xtx
= xt
* x
;
93 Eigen::VectorXd xty
= xt
* doubleY
;
94 Eigen::VectorXd tmpx
= xtx
.fullPivHouseholderQr().solve(xty
);
95 result
= tmpx
.cast
<float>();
96 double relativeError
= (xtx
* tmpx
- xty
).norm() / xty
.norm();
97 return relativeError
< maxRelativeError
;
100 void CalibrationUtils::ComputePoly(VectorXf
*samplesX
, Eigen::VectorXf
*polynomial
, VectorXf
*polyY
)
102 polyY
->array().fill(polynomial
->coeff(0));
103 for (int i
= 1; i
< polynomial
->rows(); i
++) {
104 polyY
->array() += samplesX
->array().pow(i
) * polynomial
->coeff(i
);
108 /* C++ Implementation of Yury Petrov's ellipsoid fit algorithm
109 * Following is the origial code and its license from which this implementation is derived
111 Copyright (c) 2009, Yury Petrov
114 Redistribution and use in source and binary forms, with or without
115 modification, are permitted provided that the following conditions are
118 * Redistributions of source code must retain the above copyright
119 notice, this list of conditions and the following disclaimer.
120 * Redistributions in binary form must reproduce the above copyright
121 notice, this list of conditions and the following disclaimer in
122 the documentation and/or other materials provided with the distribution
124 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
125 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
126 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
127 ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
128 LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
129 CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
130 SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
131 INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
132 CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
133 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
134 POSSIBILITY OF SUCH DAMAGE.
137 function [ center, radii, evecs, v ] = ellipsoid_fit( X, flag, equals )
139 % Fit an ellispoid/sphere to a set of xyz data points:
141 % [center, radii, evecs, pars ] = ellipsoid_fit( X )
142 % [center, radii, evecs, pars ] = ellipsoid_fit( [x y z] );
143 % [center, radii, evecs, pars ] = ellipsoid_fit( X, 1 );
144 % [center, radii, evecs, pars ] = ellipsoid_fit( X, 2, 'xz' );
145 % [center, radii, evecs, pars ] = ellipsoid_fit( X, 3 );
148 % * X, [x y z] - Cartesian data, n x 3 matrix or three n x 1 vectors
149 % * flag - 0 fits an arbitrary ellipsoid (default),
150 % - 1 fits an ellipsoid with its axes along [x y z] axes
151 % - 2 followed by, say, 'xy' fits as 1 but also x_rad = y_rad
155 % * center - ellispoid center coordinates [xc; yc; zc]
156 % * ax - ellipsoid radii [a; b; c]
157 % * evecs - ellipsoid radii directions as columns of the 3x3 matrix
158 % * v - the 9 parameters describing the ellipsoid algebraically:
159 % Ax^2 + By^2 + Cz^2 + 2Dxy + 2Exz + 2Fyz + 2Gx + 2Hy + 2Iz = 1
162 % Yury Petrov, Northeastern University, Boston, MA
165 error( nargchk( 1, 3, nargin ) ); % check input arguments
167 flag = 0; % default to a free ellipsoid
169 if flag == 2 && nargin == 2
174 error( 'Input data must have three columns!' );
181 % need nine or more data points
182 if length( x ) < 9 && flag == 0
183 error( 'Must have at least 9 points to fit a unique ellipsoid' );
185 if length( x ) < 6 && flag == 1
186 error( 'Must have at least 6 points to fit a unique oriented ellipsoid' );
188 if length( x ) < 5 && flag == 2
189 error( 'Must have at least 5 points to fit a unique oriented ellipsoid with two axes equal' );
191 if length( x ) < 3 && flag == 3
192 error( 'Must have at least 4 points to fit a unique sphere' );
196 % fit ellipsoid in the form Ax^2 + By^2 + Cz^2 + 2Dxy + 2Exz + 2Fyz + 2Gx + 2Hy + 2Iz = 1
205 2 * z ]; % ndatapoints x 9 ellipsoid parameters
207 % fit ellipsoid in the form Ax^2 + By^2 + Cz^2 + 2Gx + 2Hy + 2Iz = 1
213 2 * z ]; % ndatapoints x 6 ellipsoid parameters
215 % fit ellipsoid in the form Ax^2 + By^2 + Cz^2 + 2Gx + 2Hy + 2Iz = 1,
216 % where A = B or B = C or A = C
217 if strcmp( equals, 'yz' ) || strcmp( equals, 'zy' )
218 D = [ y .* y + z .* z, ...
223 elseif strcmp( equals, 'xz' ) || strcmp( equals, 'zx' )
224 D = [ x .* x + z .* z, ...
230 D = [ x .* x + y .* y, ...
237 % fit sphere in the form A(x^2 + y^2 + z^2) + 2Gx + 2Hy + 2Iz = 1
238 D = [ x .* x + y .* y + z .* z, ...
241 2 * z ]; % ndatapoints x 4 sphere parameters
244 % solve the normal system of equations
245 v = ( D' * D ) \ ( D' * ones( size( x, 1 ), 1 ) );
247 % find the ellipsoid parameters
249 % form the algebraic form of the ellipsoid
250 A = [ v(1) v(4) v(5) v(7); ...
251 v(4) v(2) v(6) v(8); ...
252 v(5) v(6) v(3) v(9); ...
254 % find the center of the ellipsoid
255 center = -A( 1:3, 1:3 ) \ [ v(7); v(8); v(9) ];
256 % form the corresponding translation matrix
258 T( 4, 1:3 ) = center';
259 % translate to the center
261 % solve the eigenproblem
262 [ evecs evals ] = eig( R( 1:3, 1:3 ) / -R( 4, 4 ) );
263 radii = sqrt( 1 ./ diag( evals ) );
266 v = [ v(1) v(2) v(3) 0 0 0 v(4) v(5) v(6) ];
268 if strcmp( equals, 'xz' ) || strcmp( equals, 'zx' )
269 v = [ v(1) v(2) v(1) 0 0 0 v(3) v(4) v(5) ];
270 elseif strcmp( equals, 'yz' ) || strcmp( equals, 'zy' )
271 v = [ v(2) v(1) v(1) 0 0 0 v(3) v(4) v(5) ];
273 v = [ v(1) v(1) v(2) 0 0 0 v(3) v(4) v(5) ];
276 v = [ v(1) v(1) v(1) 0 0 0 v(2) v(3) v(4) ];
278 center = ( -v( 7:9 ) ./ v( 1:3 ) )';
279 gam = 1 + ( v(7)^2 / v(1) + v(8)^2 / v(2) + v(9)^2 / v(3) );
280 radii = ( sqrt( gam ./ v( 1:3 ) ) )';
286 void CalibrationUtils::EllipsoidFit(Eigen::VectorXf
*samplesX
, Eigen::VectorXf
*samplesY
, Eigen::VectorXf
*samplesZ
,
287 Eigen::Vector3f
*center
,
288 Eigen::VectorXf
*radii
,
289 Eigen::MatrixXf
*evecs
,
292 int numSamples
= (*samplesX
).rows();
296 D
.setZero(numSamples
, 9);
297 D
.col(0) = (*samplesX
).cwiseProduct(*samplesX
);
298 D
.col(1) = (*samplesY
).cwiseProduct(*samplesY
);
299 D
.col(2) = (*samplesZ
).cwiseProduct(*samplesZ
);
300 D
.col(3) = (*samplesX
).cwiseProduct(*samplesY
) * 2;
301 D
.col(4) = (*samplesX
).cwiseProduct(*samplesZ
) * 2;
302 D
.col(5) = (*samplesY
).cwiseProduct(*samplesZ
) * 2;
303 D
.col(6) = 2 * (*samplesX
);
304 D
.col(7) = 2 * (*samplesY
);
305 D
.col(8) = 2 * (*samplesZ
);
307 D
.setZero(numSamples
, 6);
309 D
.col(0) = (*samplesX
).cwiseProduct(*samplesX
);
310 D
.col(1) = (*samplesY
).cwiseProduct(*samplesY
);
311 D
.col(2) = (*samplesZ
).cwiseProduct(*samplesZ
);
312 D
.col(3) = 2 * (*samplesX
);
313 D
.col(4) = 2 * (*samplesY
);
314 D
.col(5) = 2 * (*samplesZ
);
316 Eigen::VectorXf
ones(numSamples
);
317 ones
.setOnes(numSamples
);
319 Eigen::MatrixXf dt1
= (D
.transpose() * D
);
320 Eigen::MatrixXf dt2
= (D
.transpose() * ones
);
321 Eigen::VectorXf v
= dt1
.inverse() * dt2
;
325 A
<< v
.coeff(0), v
.coeff(3), v
.coeff(4), v
.coeff(6),
326 v
.coeff(3), v
.coeff(1), v
.coeff(5), v
.coeff(7),
327 v
.coeff(4), v
.coeff(5), v
.coeff(2), v
.coeff(8),
328 v
.coeff(6), v
.coeff(7), v
.coeff(8), -1;
330 (*center
) = -1 * A
.block(0, 0, 3, 3).inverse() * v
.segment(6, 3);
332 Eigen::Matrix4f t
= Eigen::Matrix4f::Identity();
333 t
.block(3, 0, 1, 3) = center
->transpose();
335 Eigen::Matrix4f r
= t
* A
* t
.transpose();
337 Eigen::Matrix3f tmp2
= r
.block(0, 0, 3, 3) * -1 / r
.coeff(3, 3);
338 Eigen::EigenSolver
<Eigen::Matrix3f
> es(tmp2
);
339 Eigen::VectorXf evals
= es
.eigenvalues().real();
341 (*evecs
) = es
.eigenvectors().real();
343 (*radii
) = (evals
.segment(0, 3)).cwiseInverse().cwiseSqrt();
345 Eigen::VectorXf
v1(9);
347 v1
<< v
.coeff(0), v
.coeff(1), v
.coeff(2), 0.0f
, 0.0f
, 0.0f
, v
.coeff(3), v
.coeff(4), v
.coeff(5);
348 (*center
) = (-1) * v1
.segment(6, 3).cwiseProduct(v1
.segment(0, 3).cwiseInverse());
350 float gam
= 1 + (v1
.coeff(6) * v1
.coeff(6) / v1
.coeff(0) +
351 v1
.coeff(7) * v1
.coeff(7) / v1
.coeff(1) +
352 v1
.coeff(8) * v1
.coeff(8) / v1
.coeff(2));
354 (*radii
) = (v1
.segment(0, 3).cwiseInverse() * gam
).cwiseSqrt();
355 evecs
->setIdentity(3, 3);
359 int CalibrationUtils::SixPointInConstFieldCal(double ConstMag
, double x
[6], double y
[6], double z
[6], double S
[3], double b
[3])
364 double xp
, yp
, zp
, Sx
;
366 // Fill in matrix A -
367 // write six difference-in-magnitude equations of the form
368 // Sx^2(x2^2-x1^2) + 2*Sx*bx*(x2-x1) + Sy^2(y2^2-y1^2) + 2*Sy*by*(y2-y1) + Sz^2(z2^2-z1^2) + 2*Sz*bz*(z2-z1) = 0
370 // 2*Sx*bx*(x2-x1)/Sx^2 + Sy^2(y2^2-y1^2)/Sx^2 + 2*Sy*by*(y2-y1)/Sx^2 + Sz^2(z2^2-z1^2)/Sx^2 + 2*Sz*bz*(z2-z1)/Sx^2 = (x1^2-x2^2)
371 for (i
= 0; i
< 5; i
++) {
372 A
[i
][0] = 2.0 * (x
[i
+ 1] - x
[i
]);
373 A
[i
][1] = y
[i
+ 1] * y
[i
+ 1] - y
[i
] * y
[i
];
374 A
[i
][2] = 2.0 * (y
[i
+ 1] - y
[i
]);
375 A
[i
][3] = z
[i
+ 1] * z
[i
+ 1] - z
[i
] * z
[i
];
376 A
[i
][4] = 2.0 * (z
[i
+ 1] - z
[i
]);
377 f
[i
] = x
[i
] * x
[i
] - x
[i
+ 1] * x
[i
+ 1];
380 // solve for c0=bx/Sx, c1=Sy^2/Sx^2; c2=Sy*by/Sx^2, c3=Sz^2/Sx^2, c4=Sz*bz/Sx^2
381 if (!LinearEquationsSolve(5, (double *)A
, f
, c
)) {
385 // use one magnitude equation and c's to find Sx - doesn't matter which - all give the same answer
386 xp
= x
[0]; yp
= y
[0]; zp
= z
[0];
387 Sx
= sqrt(ConstMag
* ConstMag
/ (xp
* xp
+ 2 * c
[0] * xp
+ c
[0] * c
[0] + c
[1] * yp
* yp
+ 2 * c
[2] * yp
+ c
[2] * c
[2] / c
[1] + c
[3] * zp
* zp
+ 2 * c
[4] * zp
+ c
[4] * c
[4] / c
[3]));
391 S
[1] = sqrt(c
[1] * Sx
* Sx
);
392 b
[1] = c
[2] * Sx
* Sx
/ S
[1];
393 S
[2] = sqrt(c
[3] * Sx
* Sx
);
394 b
[2] = c
[4] * Sx
* Sx
/ S
[2];
400 int CalibrationUtils::LinearEquationsSolve(int nDim
, double *pfMatr
, double *pfVect
, double *pfSolution
)
407 for (k
= 0; k
< (nDim
- 1); k
++) { // base row of matrix
408 // search of line with max element
409 fMaxElem
= fabs(pfMatr
[k
* nDim
+ k
]);
411 for (i
= k
+ 1; i
< nDim
; i
++) {
412 if (fMaxElem
< fabs(pfMatr
[i
* nDim
+ k
])) {
413 fMaxElem
= pfMatr
[i
* nDim
+ k
];
418 // permutation of base line (index k) and max element line(index m)
420 for (i
= k
; i
< nDim
; i
++) {
421 fAcc
= pfMatr
[k
* nDim
+ i
];
422 pfMatr
[k
* nDim
+ i
] = pfMatr
[m
* nDim
+ i
];
423 pfMatr
[m
* nDim
+ i
] = fAcc
;
426 pfVect
[k
] = pfVect
[m
];
430 if (pfMatr
[k
* nDim
+ k
] == 0.) {
431 return 0; // needs improvement !!!
433 // triangulation of matrix with coefficients
434 for (j
= (k
+ 1); j
< nDim
; j
++) { // current row of matrix
435 fAcc
= -pfMatr
[j
* nDim
+ k
] / pfMatr
[k
* nDim
+ k
];
436 for (i
= k
; i
< nDim
; i
++) {
437 pfMatr
[j
* nDim
+ i
] = pfMatr
[j
* nDim
+ i
] + fAcc
* pfMatr
[k
* nDim
+ i
];
439 pfVect
[j
] = pfVect
[j
] + fAcc
* pfVect
[k
]; // free member recalculation
443 for (k
= (nDim
- 1); k
>= 0; k
--) {
444 pfSolution
[k
] = pfVect
[k
];
445 for (i
= (k
+ 1); i
< nDim
; i
++) {
446 pfSolution
[k
] -= (pfMatr
[k
* nDim
+ i
] * pfSolution
[i
]);
448 pfSolution
[k
] = pfSolution
[k
] / pfMatr
[k
* nDim
+ k
];
454 double CalibrationUtils::listMean(QList
<double> list
)
458 for (int i
= 0; i
< list
.size(); i
++) {
461 return accum
/ list
.size();
464 double CalibrationUtils::listVar(QList
<double> list
)
466 double mean_accum
= 0;
467 double var_accum
= 0;
470 for (int i
= 0; i
< list
.size(); i
++) {
471 mean_accum
+= list
[i
];
473 mean
= mean_accum
/ list
.size();
475 for (int i
= 0; i
< list
.size(); i
++) {
476 var_accum
+= (list
[i
] - mean
) * (list
[i
] - mean
);
479 // Use unbiased estimator
480 return var_accum
/ (list
.size() - 1);