Marine IMU and wave-processing algorithms in modern C++ for sensor fusion, sea-state estimation, and simulation-driven validation. Designed for ship MRU (Motion Reference Unit), Marine INS (Inertial Navigation System), AHRS (Attitude and Heading Reference System).
A marine AHRS cannot just reuse typical popular drone and aerospace IMU filters unchanged. In aerospace, motion is usually modeled as rotation about the center of mass (satellite), and drones often initialize while sitting still before takeoff, so the accelerometer gives a clean gravity direction. On a ship, the system may be turned on while already moving in waves and wind, with heave, roll, pitch, and translational accelerations all mixed into the IMU signals. That means the filter has to learn tilt during motion, avoid trusting wave-distorted acceleration as pure gravity, and keep working across very different sea conditions. In practice, a ship AHRS/INS needs wave-aware initialization, motion compensation, and tuning that can adapt to different sea states dynamically.
The algorithms presented here not only implement tilt-compensated compass and basic roll/pitch/rate-of-turn sensors, they provide corrections for wave induced motion, and additionally reconstruct 3D displacement of a vessel (heave - strongly observable, surge/sway - weakly) in real-time and estimate apparent (to the vessel being observer) waves direction.
The code of filters is written in C++ and can run on a microcontroller such as esp32 or on a regular computer. The compass calibration implemented as a part of this library can be run directly on your microcontroller unit. The testing framework uses Stokes/Airy waves with Pierson-Moskowitz or JONSWAP spectrums and a choice of a directional spread model (cosine by default).
The articles describing the math behind the methods:
INS Filters:
Heave-only filters (comparison baselines, see docs/heave-only.md):
Research Studies:
IMU Calibration:
Frequency tracking:
Wave Models:
Autopilot Use Cases:
There are several versions of Kalman INS filters and two filters (PII observer and NLO) based on control theory:
- CARMA(4,1) stochastic process with Two Frame Lie Group filter delivers the lowest RMS of heave error (below 3% of significant wave height).
- CARMA(4,1) with QMEKF is a quaternion counterpart of the previous one.
- OU_III uses higher-order integral drift correction. It is a 21-dimensional-state Kalman filter and is the OU variant used for the main 3-D navigation study.
- TFG is a right-invariant two-frame Lie-group error-state EKF. Same 21 dimensional state and the same OU wave model as OU_III, but attitude, the world-frame kinematic states and the body-frame biases live on one group, so an attitude correction rotates velocity, position, integral displacement and wave acceleration coherently instead of leaving them behind.
- OU_II uses more direct integral drift correction and is more responsive to sea-state changes. It is an 18-dimensional-state Kalman filter.
- PII observer is based on control theory. It is very computationally light-weight with no matrix operations. It's less accurate than Kalman filters.
- NLO is a time-varying-gain nonlinear observer. It uses explicit attitude and translational injection terms with the gains published by Bryne/Fossen/Johansen instead of a propagated covariance, and its translational loop is scheduled on the tracked wave frequency.
- All above filters are adaptive.
- All filters tested to run on esp32s3.
- All filters tested to run on Windows and Linux as well.
Arduino .ino sketches for esp32s3 (on atomS3R):
- Kalman OU_II: https://github.com/bareboat-necessities/ocean-imu/tree/main/sensors/full_marine_ins/atomS3R_ins_kalman_ou2
- Kalman OU_III: https://github.com/bareboat-necessities/ocean-imu/tree/main/sensors/full_marine_ins/atomS3R_ins_kalman_ou3
- Kalman CARMA3 (experimental): https://github.com/bareboat-necessities/ocean-imu/tree/main/sensors/full_marine_ins/atomS3R_ins_carma3
- Kalman TFG-CARMA3 (experimental): https://github.com/bareboat-necessities/ocean-imu/tree/main/sensors/full_marine_ins/atomS3R_ins_tfg_carma3
- Two-Frame Lie-Group (TFG): https://github.com/bareboat-necessities/ocean-imu/tree/main/sensors/full_marine_ins/atomS3R_ins_tfg
- PII observer: https://github.com/bareboat-necessities/ocean-imu/tree/main/sensors/full_marine_ins/atomS3R_ins_pii_observer
- Non-Linear Observer NLO: https://github.com/bareboat-necessities/ocean-imu/tree/main/sensors/full_marine_ins/atomS3R_ins_nlo
ocean-imu collects reusable components for marine motion estimation and wave analytics. The codebase is organized so individual modules can be built and exercised from tests/* without requiring a monolithic build system.
GUI client (Windows and Linux):
https://github.com/bareboat-necessities/ins-dashboard-gtk
- Attitude and heading workflows (yaw/roll/pitch, AHRS-oriented utilities)
- Wave and frequency-domain analysis
- Wave direction detection
- Kalman-based sea-state models (OU-II and OU-III variants)
- Tuning and filtering helpers for marine sensor pipelines
Results of the tests, simulations and documentation built from the development branch are published at https://github.com/bareboat-necessities/ocean-imu/releases/tag/v2.7.0
The figures below are the current OU-III diagnostics and validation evidence. The per-wave and spectrum figures under reports/results/readme/ are copied from SVG artifacts generated by successful build runs on main; the aggregate error plots come from the versioned full validation bundle under reports/results/ou_validation/, and the transition, engine-vibration, and lever-arm figures come from their own versioned study bundles under reports/results/. Every figure ships with the CSV, summary, and manifest that produced it, so the front page stays tied to reproducible outputs instead of manually copied sample images.
The simulator is driven by directional spectral seas, not only scalar sinusoidal heave. The existing build-generated 3-D spectrum below shows the frequency-direction energy surface for the same representative medium PM/Stokes case used by the motion plots above.
The two plots below use the same medium PM/Stokes wave case (H_s = 1.5 m). The vertical plot shows heave-chain tracking; the companion plot shows the more weakly observable horizontal surge/sway channels rather than presenting Z alone.
| Z kinematics | X/Y kinematics |
The corresponding attitude trace from that same generated case is also retained with the gallery:
OU-III estimates inertial-sensor biases as filter states. These plots show the simulated truth and estimated accelerometer and gyroscope biases over the same wave record, so bias convergence is visible rather than only final RMS navigation error.
| Accelerometer bias | Gyroscope bias |
The validation plots summarize the full paired noisy study rather than one selected trajectory. Displacement and attitude error plots expose performance across the declared sea cases, while the transition plot exercises adaptation as the sea state changes.
| 3-D displacement error | Attitude error |
The transition figure is the round trip, not a one-way build. A single realization holds H_s = 1.5 m for the first 400 s, blends up to H_s = 4.0 m over the next two minutes, holds the big sea until 800 s, blends back down to H_s = 1.5 m by 920 s, and finishes in the mild sea it started in. A low-to-high record only exercises the tightening half of the scheduler; coming back down is the direction where an adaptive filter can be left stuck on a sea that is no longer there, so it is the half worth showing. The low-sea and high-sea reference lines on the schedule panel are independently calibrated frozen operating points for the two endpoint records: the applied tau, sigma_aw, and R_s traces should leave the low fixed point, reach the high one, and come back to the low one without hysteresis.
Motoring puts machinery vibration straight into the IMU. The engine-noise degradation study replays the eight stationary JONSWAP / PM-Stokes records through OU-II, OU-III, and TFG with a sensor-path model of a mid-size cruising sailboat's auxiliary diesel added on top of the ordinary sensor noise: crank orders, shaft- and blade-rate lines, a broadband structural floor, governor hunting, mount transmissibility, the sensor's finite anti-alias bandwidth, accelerometer vibration rectification, and gyroscope g-sensitivity. The vessel's rigid-body response to the sea is unchanged, so what the figures bound is the sensor-path cost of running the engine.
| Response against engine speed | What actually drives it |
The left figure sweeps engine speed at a fixed hull vibration level; the dotted horizontals are each family's engine-off baseline. The right figure is the mechanism: a level sweep, a bandwidth sweep with its matched-power control, every cell of the study collapsed onto recorded vibration RMS, and the rectified static tilt offset underneath it all. The damage tracks recorded out-of-band accelerometer power, not where the aliased crank orders happen to land, and it is largely a standing tilt bias rather than added jitter. At nominal cruise, OU-III's pooled 3-D error is 8.1x its engine-off baseline; a sensor bolted near the engine bed reaches 72x.
Because the cause is out-of-band power in one sensor, it has a front-end remedy. AccelVibrationGuard low-passes the accelerometer in the empty decade between the wave band and the machinery band (2 poles at 14 Hz), engages only when a separate high-pass detector says there is machinery to remove, and raises the commanded accelerometer sigma from the same gated excess so the covariance describes the conditioned signal.
Across the sweep the guard pulls OU-III back to within 1.08-1.17x of engine-off, and the engine-off rows are identical to every digit because the detector never crosses its lower rail with no machinery running. The residual does not reach 1.00 and cannot: conditioning costs group delay, which is paid whether or not there is anything left to remove. The 800 rpm row is the other limit showing itself - machinery whose orders reach into the wave band cannot be separated from the sea by any front-end filter.
The wave records define motion at the vessel's centre of gravity, but an IMU is bolted wherever the installation allows. A rigid sensor displaced by a body-frame vector r additionally measures alpha x r + omega x (omega x r). The lever-arm study applies that term to the accelerometer truth before sensor corruption and the filter's own lever-arm model after it, over a matrix of 8 records, 3 axes (athwartships, fore-aft, vertical), 3 offsets (10/20/30 cm), and 3 modelling arms: unmodeled (no filter-side correction), gyro (compensated from the measured rate - the deployable one), and exact (compensated from truth kinematics - an oracle bound). Nothing else is retuned.
| Displacement penalty and its removal | Attitude penalty and its removal |
The two channels are hurt by different axes. Displacement degrades most with a vertical offset (1.022x at 30 cm unmodeled); attitude degrades most with an athwartships offset, where 30 cm nearly doubles the tilt RMS. The exact arm returns to the CG baseline to numerical precision in every cell, so the whole penalty is deterministic and recoverable rather than an intrinsic OU-III limit, and the gyro arm recovers most of it from the measured rate alone.
| What is injected, what is left | Derivative band of the deployable model |
The deployable arm reconstructs the angular acceleration through a two-pole low-pass followed by a causal second-order difference, and that corner is its one design parameter. The sweep on the right is why it is set to 15 Hz: too narrow and the phase lag misaligns a correction of the right magnitude, too wide and differentiated gyro noise dominates. Both failure directions cost more than the raw penalty they were meant to remove.
Pooled ratios understate the effect and the per-sea figure is the honest one. An RMS over all eight seas is dominated by the largest, where the injected rotational term is smallest relative to the wave signal; broken out sea by sea, the displacement penalty is worst in the mildest seas and the attitude penalty worst in the steepest.
The experimental CARMA3 and TFG-CARMA3 filters carry the same deployable model, applied at their fusion wrapper's input; their own off-CoG study, out to 2 m and against OU-III's in-filter placement, is docs/carma-imu-lever-arm.md.
The OU-III implementation is not only a desktop simulation. The repository includes an AtomS3R marine-INS sketch that runs the adaptive SeaStateFusion OU-III estimator at 200 Hz on the M5Stack AtomS3R. Keeping the embedded target visible here matters because computational feasibility, startup behavior, calibration, heading lock, and real-time estimator integration are part of the intended marine use case, not an after-the-fact port of an offline algorithm.
src/ core algorithms and reusable components
ahrs/ attitude and heading routines
avg/ averaging and smoothing helpers
detrend/ detrending helpers
discrete/ discrete-time utilities
freq/ frequency-domain utilities
imu_calibrate/ IMU calibration logic
kalman_ou_ii/ OU-II Kalman model components
kalman_ou_iii/ OU-III Kalman model components
kalman_tfg/ two-frame Lie-group filter components
lie/ Lie group operations the TFG filter is built on
nmea/ NMEA parsing/helpers
pii_observer/ observer/filter components
tuner/ auto-tuning helpers
util/ shared support code
wave_dir/ wave direction estimation
tests/ module-level build and validation targets
ahrs/ AHRS-focused tests and examples
detrend/ detrending tests
freq/ builds freq-track
heave_only/ heave-only filters (Godhavn, Richter, Kuchler) and heave_only-sim
imu_calibrate/ IMU calibration tests
kalman_ou_ii/ builds kalman_ou_ii-sim
kalman_ou_iii/ builds kalman_ou_iii-sim
pii_observer/ builds pii_observer-adaptive
wave_sim/ wave simulation programs
sensors/ sensor integration and application examples
*/ standalone sensor-oriented demos/utilities
doc/ module documentation and notes
plots/ generated plotting scripts/assets
img/ images and sample result figures
g++with C++20 supportmake- Eigen headers (typically from
libeigen3-dev)
Typical Eigen include location on Linux:
/usr/include/eigen3
Some validation and simulation workflows depend on data released in:
All simulations use release v1.2.3 and sim-data-files-vessel-rao-28ft.zip: an estimated stationary 28 ft fin-keel sailboat RAO at the CG. Motion CSVs contain vessel response; spectrum CSVs and filename heights describe the incident sea. The preset is analytical, not a measured hull response. make ensure-sim-data verifies the pinned SHA-256 and replaces stale surface inputs.
You can fetch and unpack this data for local runs with:
make fetch-sim-dataRun builds from the specific test folder you want to validate:
cd tests/freq && make all
cd tests/kalman_ou_ii && make all
cd tests/kalman_ou_iii && make all
cd tests/pii_observer && make allIf Eigen is not on the default include path for your environment, pass an include override:
make all CPPFLAGS+='-I/usr/include/eigen3'Primary project validation command (when available in your environment):
make allFor module-level validation, run make all inside the relevant folder under tests/.
The OU-II/OU-III statistical validation runner scores continuous metrics over the final 15 minutes of each 20-minute run. Deterministic simulator regression gates remain executable diagnostics, but they are not Monte Carlo inclusion criteria and are not exported as pass/fail evidence. Wave realization, IMU noise, and initialization are paired across both filters and matched ablations.
python3 tools/ou_validation.py --mode smoke
python3 tools/ou_validation.py --mode fullFull mode produces raw and summary CSV, JSON, LaTeX, paired-effect, manifest,
and SVG plot artifacts under reports/results/ou_validation/. See docs/ou-validation.md for the
protocol, provenance contract, seed controls, and interpretation.