From f811777789ed9901e7f94d3a1d9f4adfbdd8beb4 Mon Sep 17 00:00:00 2001 From: Siddharth Bharat Purohit <siddharthbharatpurohit@gmail.com> Date: Mon, 14 Nov 2016 13:59:49 +0530 Subject: [PATCH] commander: add new math for sphere fit for compass calibration --- .../commander/calibration_routines.cpp | 259 ++++++++++++++++++ src/modules/commander/calibration_routines.h | 8 +- src/modules/commander/mag_calibration.cpp | 18 +- 3 files changed, 278 insertions(+), 7 deletions(-) diff --git a/src/modules/commander/calibration_routines.cpp b/src/modules/commander/calibration_routines.cpp index b1ee6f5aea..1b77658e20 100644 --- a/src/modules/commander/calibration_routines.cpp +++ b/src/modules/commander/calibration_routines.cpp @@ -233,6 +233,265 @@ int sphere_fit_least_squares(const float x[], const float y[], const float z[], return 0; } +int ellipsoid_fit_least_squares(const float x[], const float y[], const float z[], + unsigned int size, unsigned int max_iterations, float delta, float *offset_x, float *offset_y, float *offset_z, + float *sphere_radius, float *diag_x, float *diag_y, float *diag_z, float *offdiag_x, float *offdiag_y, float *offdiag_z) +{ + float _fitness = 1.0e30f, _sphere_lambda = 1.0f; + for(int i=0; i < max_iterations; i++) { + //printf("%d, offset: %.6f %.6f %.6f %.6f fitness: %.6f\n", i, (double)*offset_x, (double)*offset_y, (double)*offset_z, (double)*sphere_radius, (double)_fitness); + run_lm_sphere_fit(x, y, z, _fitness, _sphere_lambda, + size, offset_x, offset_y, offset_z, + sphere_radius, diag_x, diag_y, diag_z, offdiag_x, offdiag_y, offdiag_z); + } + return 0; +} + +int run_lm_sphere_fit(const float x[], const float y[], const float z[], float &_fitness, float &_sphere_lambda, + unsigned int size, float *offset_x, float *offset_y, float *offset_z, + float *sphere_radius, float *diag_x, float *diag_y, float *diag_z, float *offdiag_x, float *offdiag_y, float *offdiag_z) +{ + //Run Sphere Fit using Levenberg Marquardt LSq Fit + const float lma_damping = 10.0f; + float _samples_collected = size; + float fitness = _fitness; + float fit1 = 0.0f, fit2 = 0.0f; + + float JTJ[16]; + float JTJ2[16]; + float JTFI[4]; + float residual = 0.0f; + memset(&JTJ,0,sizeof(JTJ)); + memset(&JTJ2,0,sizeof(JTJ2)); + memset(&JTFI,0,sizeof(JTFI)); + // Gauss Newton Part common for all kind of extensions including LM + for(uint16_t k = 0; k<_samples_collected; k++) { + + float sphere_jacob[4]; + //Calculate Jacobian + float A = (*diag_x * (x[k] + *offset_x)) + (*offdiag_x * (y[k] + *offset_y)) + (*offdiag_y * (z[k] + *offset_z)); + float B = (*offdiag_x * (x[k] + *offset_x)) + (*diag_y * (y[k] + *offset_y)) + (*offdiag_z * (z[k] + *offset_z)); + float C = (*offdiag_y * (x[k] + *offset_x)) + (*offdiag_z * (y[k] + *offset_y)) + (*diag_z * (z[k] + *offset_z)); + float length = sqrtf(A*A + B*B + C*C); + + // 0: partial derivative (radius wrt fitness fn) fn operated on sample + sphere_jacob[0] = 1.0f; + // 1-3: partial derivative (offsets wrt fitness fn) fn operated on sample + sphere_jacob[1] = -1.0f * (((*diag_x * A) + (*offdiag_x * B) + (*offdiag_y * C))/length); + sphere_jacob[2] = -1.0f * (((*offdiag_x * A) + (*diag_y * B) + (*offdiag_z * C))/length); + sphere_jacob[3] = -1.0f * (((*offdiag_y * A) + (*offdiag_z * B) + (*diag_z * C))/length); + residual = *sphere_radius - length; + + for(uint8_t i = 0;i < 4; i++) { + // compute JTJ + for(uint8_t j = 0; j < 4; j++) { + JTJ[i*4 + j] += sphere_jacob[i] * sphere_jacob[j]; + JTJ2[i*4 + j] += sphere_jacob[i] * sphere_jacob[j]; //a backup JTJ for LM + } + JTFI[i] += sphere_jacob[i] * residual; + } + } + + + //------------------------Levenberg-Marquardt-part-starts-here---------------------------------// + //refer: http://en.wikipedia.org/wiki/Levenberg%E2%80%93Marquardt_algorithm#Choice_of_damping_parameter + float fit1_params[4] = {*sphere_radius, *offset_x, *offset_y, *offset_z}; + float fit2_params[4]; + memcpy(fit2_params,fit1_params,sizeof(fit1_params)); + + for(uint8_t i = 0; i < 4; i++) { + JTJ[i*4 + i] += _sphere_lambda; + JTJ2[i*4 + i] += _sphere_lambda/lma_damping; + } + + if(!inverse4x4(JTJ, JTJ)) { + return -1; + } + + if(!inverse4x4(JTJ2, JTJ2)) { + return -1; + } + + for(uint8_t row=0; row < 4; row++) { + for(uint8_t col=0; col < 4; col++) { + fit1_params[row] -= JTFI[col] * JTJ[row*4 + col]; + fit2_params[row] -= JTFI[col] * JTJ2[row*4 + col]; + } + } + //Calculate mean squared residuals + for(uint16_t k=0; k < _samples_collected; k++){ + float A = (*diag_x * (x[k] + fit1_params[1])) + (*offdiag_x * (y[k] + fit1_params[2])) + (*offdiag_y * (z[k] + fit1_params[3])); + float B = (*offdiag_x * (x[k] + fit1_params[1])) + (*diag_y * (y[k] + fit1_params[2])) + (*offdiag_z * (z[k] + fit1_params[3])); + float C = (*offdiag_y * (x[k] + fit1_params[1])) + (*offdiag_z * (y[k] + fit1_params[2])) + (*diag_z * (z[k] + fit1_params[3])); + float length = sqrtf(A*A + B*B + C*C); + residual = fit1_params[0] - length; + fit1 += residual*residual; + + A = (*diag_x * (x[k] + fit2_params[1])) + (*offdiag_x * (y[k] + fit2_params[2])) + (*offdiag_y * (z[k] + fit2_params[3])); + B = (*offdiag_x * (x[k] + fit2_params[1])) + (*diag_y * (y[k] + fit2_params[2])) + (*offdiag_z * (z[k] + fit2_params[3])); + C = (*offdiag_y * (x[k] + fit2_params[1])) + (*offdiag_z * (y[k] + fit2_params[2])) + (*diag_z * (z[k] + fit2_params[3])); + length = sqrtf(A*A + B*B + C*C); + residual = fit2_params[0] - length; + fit2 += residual*residual; + } + + fit1 = sqrtf(fit1)/_samples_collected; + fit2 = sqrtf(fit2)/_samples_collected; + + if(fit1 > _fitness && fit2 > _fitness){ + _sphere_lambda *= lma_damping; + } else if(fit2 < _fitness && fit2 < fit1) { + _sphere_lambda /= lma_damping; + memcpy(fit1_params,fit2_params,sizeof(fit1_params)); + fitness = fit2; + } else if(fit1 < _fitness){ + fitness = fit1; + } + //--------------------Levenberg-Marquardt-part-ends-here--------------------------------// + + if(!isnan(fitness) && fitness < _fitness) { + _fitness = fitness; + *sphere_radius = fit1_params[0]; + *offset_x = fit1_params[1]; + *offset_y = fit1_params[2]; + *offset_z = fit1_params[3]; + return 0; + } else { + return -1; + } +} + + +bool inverse4x4(float m[],float invOut[]) +{ + float inv[16], det; + uint8_t i; + + inv[0] = m[5] * m[10] * m[15] - + m[5] * m[11] * m[14] - + m[9] * m[6] * m[15] + + m[9] * m[7] * m[14] + + m[13] * m[6] * m[11] - + m[13] * m[7] * m[10]; + + inv[4] = -m[4] * m[10] * m[15] + + m[4] * m[11] * m[14] + + m[8] * m[6] * m[15] - + m[8] * m[7] * m[14] - + m[12] * m[6] * m[11] + + m[12] * m[7] * m[10]; + + inv[8] = m[4] * m[9] * m[15] - + m[4] * m[11] * m[13] - + m[8] * m[5] * m[15] + + m[8] * m[7] * m[13] + + m[12] * m[5] * m[11] - + m[12] * m[7] * m[9]; + + inv[12] = -m[4] * m[9] * m[14] + + m[4] * m[10] * m[13] + + m[8] * m[5] * m[14] - + m[8] * m[6] * m[13] - + m[12] * m[5] * m[10] + + m[12] * m[6] * m[9]; + + inv[1] = -m[1] * m[10] * m[15] + + m[1] * m[11] * m[14] + + m[9] * m[2] * m[15] - + m[9] * m[3] * m[14] - + m[13] * m[2] * m[11] + + m[13] * m[3] * m[10]; + + inv[5] = m[0] * m[10] * m[15] - + m[0] * m[11] * m[14] - + m[8] * m[2] * m[15] + + m[8] * m[3] * m[14] + + m[12] * m[2] * m[11] - + m[12] * m[3] * m[10]; + + inv[9] = -m[0] * m[9] * m[15] + + m[0] * m[11] * m[13] + + m[8] * m[1] * m[15] - + m[8] * m[3] * m[13] - + m[12] * m[1] * m[11] + + m[12] * m[3] * m[9]; + + inv[13] = m[0] * m[9] * m[14] - + m[0] * m[10] * m[13] - + m[8] * m[1] * m[14] + + m[8] * m[2] * m[13] + + m[12] * m[1] * m[10] - + m[12] * m[2] * m[9]; + + inv[2] = m[1] * m[6] * m[15] - + m[1] * m[7] * m[14] - + m[5] * m[2] * m[15] + + m[5] * m[3] * m[14] + + m[13] * m[2] * m[7] - + m[13] * m[3] * m[6]; + + inv[6] = -m[0] * m[6] * m[15] + + m[0] * m[7] * m[14] + + m[4] * m[2] * m[15] - + m[4] * m[3] * m[14] - + m[12] * m[2] * m[7] + + m[12] * m[3] * m[6]; + + inv[10] = m[0] * m[5] * m[15] - + m[0] * m[7] * m[13] - + m[4] * m[1] * m[15] + + m[4] * m[3] * m[13] + + m[12] * m[1] * m[7] - + m[12] * m[3] * m[5]; + + inv[14] = -m[0] * m[5] * m[14] + + m[0] * m[6] * m[13] + + m[4] * m[1] * m[14] - + m[4] * m[2] * m[13] - + m[12] * m[1] * m[6] + + m[12] * m[2] * m[5]; + + inv[3] = -m[1] * m[6] * m[11] + + m[1] * m[7] * m[10] + + m[5] * m[2] * m[11] - + m[5] * m[3] * m[10] - + m[9] * m[2] * m[7] + + m[9] * m[3] * m[6]; + + inv[7] = m[0] * m[6] * m[11] - + m[0] * m[7] * m[10] - + m[4] * m[2] * m[11] + + m[4] * m[3] * m[10] + + m[8] * m[2] * m[7] - + m[8] * m[3] * m[6]; + + inv[11] = -m[0] * m[5] * m[11] + + m[0] * m[7] * m[9] + + m[4] * m[1] * m[11] - + m[4] * m[3] * m[9] - + m[8] * m[1] * m[7] + + m[8] * m[3] * m[5]; + + inv[15] = m[0] * m[5] * m[10] - + m[0] * m[6] * m[9] - + m[4] * m[1] * m[10] + + m[4] * m[2] * m[9] + + m[8] * m[1] * m[6] - + m[8] * m[2] * m[5]; + + det = m[0] * inv[0] + m[1] * inv[4] + m[2] * inv[8] + m[3] * inv[12]; + + if(fabsf(det) < 1.1755e-38f){ + return false; + } + + det = 1.0f / det; + + for (i = 0; i < 16; i++) + invOut[i] = inv[i] * det; + return true; +} + enum detect_orientation_return detect_orientation(orb_advert_t *mavlink_log_pub, int cancel_sub, int accel_sub, bool lenient_still_position) { const unsigned ndim = 3; diff --git a/src/modules/commander/calibration_routines.h b/src/modules/commander/calibration_routines.h index c8ed7689f4..e0d335f085 100644 --- a/src/modules/commander/calibration_routines.h +++ b/src/modules/commander/calibration_routines.h @@ -55,7 +55,13 @@ int sphere_fit_least_squares(const float x[], const float y[], const float z[], unsigned int size, unsigned int max_iterations, float delta, float *sphere_x, float *sphere_y, float *sphere_z, float *sphere_radius); - +int ellipsoid_fit_least_squares(const float x[], const float y[], const float z[], + unsigned int size, unsigned int max_iterations, float delta, float *offset_x, float *offset_y, float *offset_z, + float *sphere_radius, float *diag_x, float *diag_y, float *diag_z, float *offdiag_x, float *offdiag_y, float *offdiag_z); +int run_lm_sphere_fit(const float x[], const float y[], const float z[], float &_fitness, float &_sphere_lambda, + unsigned int size, float *offset_x, float *offset_y, float *offset_z, + float *sphere_radius, float *diag_x, float *diag_y, float *diag_z, float *offdiag_x, float *offdiag_y, float *offdiag_z); +bool inverse4x4(float m[],float invOut[]); // FIXME: Change the name static const unsigned max_accel_sens = 3; diff --git a/src/modules/commander/mag_calibration.cpp b/src/modules/commander/mag_calibration.cpp index 282f6a7a47..2d6f87d306 100644 --- a/src/modules/commander/mag_calibration.cpp +++ b/src/modules/commander/mag_calibration.cpp @@ -568,10 +568,16 @@ calibrate_return mag_calibrate_all(orb_advert_t *mavlink_log_pub) // Calculate calibration values for each mag - float sphere_x[max_mags]; - float sphere_y[max_mags]; - float sphere_z[max_mags]; - float sphere_radius[max_mags]; + float sphere_x[max_mags] = {0.0f}; + float sphere_y[max_mags] = {0.0f}; + float sphere_z[max_mags] = {0.0f}; + float sphere_radius[max_mags] = {0.2f}; + float diag_x[max_mags] = {1.0f}; + float diag_y[max_mags] = {1.0f}; + float diag_z[max_mags] = {1.0f}; + float offdiag_x[max_mags] = {0.0f}; + float offdiag_y[max_mags] = {0.0f}; + float offdiag_z[max_mags] = {0.0f}; // Sphere fit the data to get calibration values if (result == calibrate_return_ok) { @@ -579,11 +585,11 @@ calibrate_return mag_calibrate_all(orb_advert_t *mavlink_log_pub) if (device_ids[cur_mag] != 0) { // Mag in this slot is available and we should have values for it to calibrate - sphere_fit_least_squares(worker_data.x[cur_mag], worker_data.y[cur_mag], worker_data.z[cur_mag], + ellipsoid_fit_least_squares(worker_data.x[cur_mag], worker_data.y[cur_mag], worker_data.z[cur_mag], worker_data.calibration_counter_total[cur_mag], 100, 0.0f, &sphere_x[cur_mag], &sphere_y[cur_mag], &sphere_z[cur_mag], - &sphere_radius[cur_mag]); + &sphere_radius[cur_mag], &diag_x[cur_mag], &diag_y[cur_mag], &diag_z[cur_mag], &offdiag_x[cur_mag], &offdiag_y[cur_mag], &offdiag_z[cur_mag]); if (!PX4_ISFINITE(sphere_x[cur_mag]) || !PX4_ISFINITE(sphere_y[cur_mag]) || !PX4_ISFINITE(sphere_z[cur_mag])) { calibration_log_emergency(mavlink_log_pub, "ERROR: Retry calibration (sphere NaN, #%u)", cur_mag); -- GitLab