1
0
mirror of https://bitbucket.org/librepilot/librepilot.git synced 2024-12-13 20:48:42 +01:00
LibrePilot/ground/openpilotgcs/src/plugins/config/gyro-calibration.cpp

86 lines
3.3 KiB
C++

#include <calibration.h>
#include <Eigen/Cholesky>
#include <Eigen/SVD>
#include <Eigen/QR>
/*
* Compute basic calibration parameters for a three axis gyroscope.
* The measurement equation is
* gyro_k = accelSensitivity * \tilde{accel}_k + bias + omega
*
* where, omega is the true angular rate (assumed to be zero)
* bias is the sensor bias
* accelSensitivity is the 3x3 matrix of sensitivity scale factors
* \tilde{accel}_k is the calibrated measurement of the accelerometer
* at time k
*
* After calibration, the optimized gyro measurement is given by
* \tilde{gyro}_k = gyro_k - bias - accelSensitivity * \tilde{accel}_k
*/
void gyroscope_calibration(Vector3f & bias,
Matrix3f & accelSensitivity,
Vector3f gyroSamples[],
Vector3f accelSamples[],
size_t n_samples)
{
// Assume the following measurement model:
// y = H*x
// where x is the vector of unknowns, and y is the measurement vector.
// the vector of unknowns is
// [a_x a_y a_z b_x]
// The measurement vector is
// [gyro_x]
// and the measurement matrix H is
// [accelSample_x accelSample_y accelSample_z 1]
// Note that the individual solutions for x are given by
// (H^T \times H)^-1 \times H^T y
// Everything is identical except for y and x. So, transform it
// into block form X = (H^T \times H)^-1 \times H^T Y
// where each column of X contains the partial solution for each
// column of y.
// Construct the matrix of accelerometer samples. Intermediate results
// are computed in "twice the precision that the source provides and the
// result deserves" by Kahan's thumbrule to prevent numerical problems.
Matrix<double, Dynamic, 4> H(n_samples, 4);
// And the matrix of gyro samples.
Matrix<double, Dynamic, 3> Y(n_samples, 3);
for (unsigned i = 0; i < n_samples; ++i) {
H.row(i) << accelSamples[i].transpose().cast<double>(), 1.0;
Y.row(i) << gyroSamples[i].transpose().cast<double>();
}
#if 1
Matrix<double, 4, 3> result;
// Use the cholesky-based Penrose pseudoinverse method.
(H.transpose() * H).ldlt().solve(H.transpose() * Y, &result);
// Transpose the result and return it to the caller. Cast back to float
// since there really isn't that much accuracy in the result.
bias = result.row(3).transpose().cast<float>();
accelSensitivity = result.block<3, 3>(0, 0).transpose().cast<float>();
#else
// TODO: Compare this result with a total-least-squares model.
size_t n = 4;
Matrix<double, Dynamic, 7> C;
C << H, Y;
SVD<Matrix<double, Dynamic, 7> > usv(C);
Matrix<double, 4, 3> V_ab = usv.matrixV().block<4, 3>(0, n);
Matrix<double, Dynamic, 3> V_bb = usv.matrixV().corner(BottomRight, n_samples - 4, 3);
// X = -V_ab/V_bb;
// X^T = (A * B^-1)^T
// X^T = (B^-1^T * A^T)
// X^T = (B^T^-1 * A^T)
// V_bb is orthgonal but not orthonormal. QR decomposition
// should be very fast in this case.
Matrix<double, 3, 4> result;
V_bb.transpose().qr().solve(-V_ab.transpose(), &result);
// Results only deserve single precision.
bias = result.col(3).cast<float>();
accelSensitivity = result.block<3, 3>(0, 0).cast<float>();
#endif // if 1
}