Incorporating new Orientation Complementary MARG filter #215
Conversation
… Madgwick AHRS paper) * Refactored Madgwick IMU and MARG filter * Removed old Magdwick and Mahony external dependencies * Added new filter derived from Madgwick filter that appears to eliminate drift in most cases. * Updated magnetometer_calibration tool to include a calibration phase that records the magnetometer field when the controller is in the "identity" upright pose * Added mechanism for transforming sensor readings to different coordinate frames (default is open_gl frame) * Added new math sub folder with utilities used by the orientation system - psmove_alignment.cpp/.h - utility methods for aligning pairs of vector (used by orientation filters) - psmove_glm_math.hpp - wraps up glm headers + glm utility macros - psmove_math.c/.h - C math constants and utility macros used by orientation filter - psmove_quaternion.cpp/.h - wrappers for a few missing GLM quaternion functions used by orientation filter - psmove_vector.h/.c - methods for the PSMove_3AxisVector structure (used mostly for magnetometer vector transformation)
|
Some additional notes:
In Madgwick's paper he does one gradient step once per frame. He also defines a way for the true identity sensor frame to be converged to over time (starting from a guess). This has the advantage that it requires no calibration of the identity frame and that it's fast. However in practice this approach still appears to have a few problems:
In my variation, I do the following:
The cool thing about this approach is that it's a lot more explicit and inspect-able. You can adjust the m-g blend weight value to see that contribution or adjust it the other way and see pure gyro integration. You can look at the result of the m-g alignment (see psmove_alignment_quaternion_between_vector_frames) and see how good of an alignment was computed. You can step through alignment function and watch the error function converge. And the identity orientation is clear (computed at calibration time). The new code also uses glm math primitives so it's more clear what mathematical objects you are dealing with. |
|
@BinaryShift Sorry for the delay. I finally got around to trying this out. Unfortunately, I'm getting pretty bad results. I'm not seeing drift, but the orientation I'm getting seems to be pretty far off from the real-world orientation. With the old algorithm, I was getting some yaw drift, but the orientation matched up well with real-world orientation. Since a video is better than a thousand words (or something like that), I've made a short video to demonstrate. In the video, I:
For reference, I've attached the magnetometer calibration output (had to change the extension to .txt because GitHub doesn't accept .csv). Let me know if you need any more information or want me to test something. |
|
Thanks for giving this a test @rovarma! And also thanks for the video, that helps a lot. I think I know what you are seeing. One of the drawbacks with the new calibration approach is that the controller has to be aligned with the facing or your pseye camera like so: Pardon my crude mspaint diagram. This is because you are basically defining what the global reference frame of the controller is in the second stage of the calibration. I could call that out better in the comment displayed on screen in I can get the same effect you are seeing if I don't line up the controller facing. Here is a video showing the effect of having correct and incorrect alignment: Also I can't seem to get functional tracking on my psmove unless I have the exposure set to low, thus the nearly black background in my locally built version of |
|
Ah ok, thanks. I wasn't aware of that limitation. I'll give the "correct" calibration a try tomorrow. I don't quite understand where that limitation comes from though. From what I gathered from the comments in the code, I assumed that when running calibration, you're basically determining what the "identity" pose is for the controller (ie. its orientation in real-world-space). I had hoped that that would be the case, since orientation-in-real-world-space is exactly what I need for my use case, but from what you're describing, that's not actually what I'm getting? Unless I'm misunderstanding something. If this is not how it works, is there a way to get the calibration/orientation to work without the requirement for alignment with the camera? Alignment with the camera is impractical for my use case, since there are multiple cameras. EDIT: That's not to say that the requirement for alignment with the camera isn't good enough for regular PSMoveAPI use though. Just being selfish and trying to figure out how this all fits into what I need from it :) |
|
TL;DR This will work for your multi-camera case (but you might want to call some special setup functions) So all that the second step of the magnetometer calibration is doing it recording the direction of the magnetometer vector when the controller is standing vertically (gravity is pointing downward). When the orientation filter gets a magnetometer/accelerometer reading from this recorded real world controller pose, the filter will return the identity quaternion. The reason you have to line up the controller facing with the camera is really only to make the render model line up in the test open gl app. It defines the identity quaternion corresponds to the controller 3d model lying flat pointing at the screen/camera. If you want to define some other coordinate system whose X/Y/Z axes are not aligned with any particular cameras frame of view you are free to do that. Simply align the controller facing along your coordinate system when running the second magnetometer calibration step. Then when running your application you'll get the identity quaternion when in this pose. There are two new functions that might be useful to your use case:
Remember how I said that the filter returns the identity quaternion when the controller is standing upright facing the camera? Well the open_gl test app assumes that identity quaternion means that the controller is pointing at the screen (rotated about the X-axis 90 degrees). To fix this, I provide a "calibration transform" that rotates the calibration readings by 90 degrees. It's as if you did the second step of the calibration with the controller lying flat pointing at the screen. Since this is a hard pose to maintain in real life I have you record this calibration vertically. However this is all done just to maintain reverse compatibility with how psmoveapi worked. Your application doesn't need to rotate the calibration data so you can just do this at startup:
The other wrinkle has to do with the coordinate system of the orientation filter. Before this pull request, the sensor data in the orientation code has been rotated 90 degrees clockwise about the x-axis. The original Madgwick orientation filter was coded to assume an OpenGL style coordinate system (+x=right, +y=up, +z=out of screen), rather than than PSMove's coordinate system where:
The current default sets the sensor transform to assume an OpenGL style coordinate system in order to maintain reverse compatibility, but you don't have to do this in your application. You can just leave the sensor data in it's natural coordinate system like so:
EDIT: Thanks for bringing this up BTW. This is an important subtle detail that I need to document more clearly and this discussion will help me make sure I have all of the bases covered. And I think your use case should be supported. I wan't to support multiple camera in my project as well (but we should talk about that in another thread). |
|
I see. Thanks for the detailed explanation; that makes perfect sense now. I agree that this needs to be documented somewhere (readme, headers?), as the effects of "doing it wrong" are rather opaque if you're not intimately familar with how the filter works internally. IMO this should be done before this PR is accepted (or as part of this PR), since this filter is now the default, which means people's existing projects will break if they redo the calibration wrongly, like I did. |
|
|
||
| /* Swig complains that the following variable declarations may leak memory. */ | ||
|
|
||
| ADDAPI extern const PSMove_3AxisTransform *k_psmove_zero_transform; |
There was a problem hiding this comment.
Can those be put into psmove_private.h? If not, maybe we should have API methods for getting/setting those values instead of extern declarations.
* Removed external PSMove_3AxisTransform constants from public API and moved into psmove_orientation.h * Added psmove_set_calibration_pose(PSMove *move, enum PSMove_CalibrationPose_Type calibration_pose) * Added psmove_set_sensor_data_basis(PSMove *move, enum PSMove_SensorDataBasis_Type basis_type)
|
I just added the recommended changes to the sensor and calibration transform API. As for the magnetometer documentation I almost wonder if this would be best suited as a wiki page? I feel like pictures/video links are best for clarity. I have a previous wiki write up for this I could adapt if you like. |
Incorporating new Orientation Complementary MARG filter
|
Merged, thanks! |
- Changed is_valid_float to use std::isnan instead of isnan (isnan is not supported on mingw). std::isnan is the portable version, but is obviously C++ only - Moved is_valid_float and assert_valid_float macros from psmove_math.h into psmove_quaternion.cpp (this is the only place where it was used anyway) in order to prevent it from being included by .c files by accident (which would cause a compile error due to std:: prefix)

Derived from Madgwick AHRS paper