Vehicular Fusion (Odometry-IMU)
Node ID: vehicularFusion · Role: Filter · Realtime config: no
Description
Vehicle dead-reckoning filter fusing wheel odometry (CAN bus speed) with IMU. Uses bicycle or differential drive model. Outputs 2D vehicle pose with heading, velocity, and UTM coordinates.
Algorithm notes
How it works
A planar dead-reckoning filter for wheeled vehicles. Wheel speed and the gyroscope turn rate propagate position and heading; GNSS position fixes (or, alternatively, an optical tracking system) correct the accumulated drift. One fused pose is published per wheel-speed sample. Because the motion comes from the wheels and the gyro, the filter coasts cleanly through GNSS outages. While driving it also learns the gyroscope yaw bias and a wheel-speed scale factor (tire wear and pressure).
What you must configure
- IMU-to-Car Rotation and IMU Turn Rate Axis - how the IMU is mounted and which gyro axis measures the vehicle’s turn rate. If these are wrong, the heading diverges as soon as you turn.
- A valid wheel-speed input. It paces the filter: without wheel speed there is no output.
How it starts
The filter waits for an RTK Fixed GNSS position (turn on Use GNSS Without RTK Fix to accept lower quality) and a heading. The heading comes from a dual-antenna receiver (Initialize from GNSS Orientation) or, with a single antenna, from the direction of travel once you drive straight faster than Init Velocity Threshold.
Tuning
- Measurement Error - trust in GNSS position. Lower makes the output follow the fixes tightly; higher leans on wheel/gyro dead reckoning.
- Velocity Error and Angular Velocity Error - trust in the wheel-speed and turn-rate inputs.
- Smoothing hides the small step at each GNSS fix.
- In areas with weak RTK, Weight GNSS by Fix Quality admits DGPS and float fixes with a loose weight, so drift stays bounded through long RTK gaps.
- With a dual-antenna receiver, Use GNSS Yaw Measurement keeps the heading corrected even through position outages.
Troubleshooting
- No output - no wheel-speed data, or initialization never completed (fix quality too low, or the vehicle never drove straight above the initialization speed).
- Heading runs away in turns - wrong IMU Turn Rate Axis or IMU-to-Car Rotation.
- Output stops when the IMU stream drops - intended; the filter will not dead-reckon on a stale turn rate. Wheel Turn-Rate Fallback switches to the wheel-derived turn rate instead, but enable it only if that signal is trustworthy.
- Position reacts sluggishly right after a long outage - the outlier handling de-weights the first fixes; this resolves itself within a few fixes.
Inputs / Outputs
- Inputs:
Gnss,Imu,VehicleSpeed,Optical - Outputs:
FusedVehiclePose,FusedVehiclePoseV2
Config aliases
OdometryImuFilter, odometryImuFilter, vehicularFusion
Required feature
vehicular_fusion
Properties
Each row shows the setting’s label in the node’s Properties panel and, in code, its key in the node’s settings object in the config file. A setting omitted from the config file uses the default shown here.
Core Tuning
| Property | Type | Default | Description |
|---|---|---|---|
Velocity Error fuser.velError | number | 0.278 | Control noise std-dev on the wheel-speed input in m/s. Higher values trust the odometry speed less; lower values smooth speed but lag real accelerations. |
Angular Velocity Error fuser.omegaError | number | 0.5 | Control noise std-dev on the turn-rate input in rad/s. Controls how quickly the heading may change between updates. Lower values produce smoother heading but slower response to real turns. |
Measurement Error fuser.measurementError | number | 0.1 | Measurement noise std-dev for the GNSS / optical position update, in meters. Lower values trust the position fixes more; higher values let odometry dead-reckoning carry more weight. |
Smoothing fuser.smoothFit | boolean | true | Smooth the output by interpolating between the lagged and current filter states across measurement epochs. Removes the visible position step at each fix at the cost of a small effective latency. |
Outlier Handling
| Property | Type | Default | Description |
|---|---|---|---|
Use Position Outlier De-weighting fuser.usePositionOutlierDeweighting | boolean | true | When a position update exceeds the innovation gate, inflate its measurement noise and apply it softly instead of applying it at full weight. |
Innovation Gate (sigma) fuser.innovationGate | number | 5.0 | Chi-square threshold for enabled outlier handling: a fix whose squared Mahalanobis distance exceeds dof + gate x sqrt(2 x dof) is treated as an outlier. 0 disables position outlier de-weighting. |
Wheel Odometry & Turn Rate
| Property | Type | Default | Description |
|---|---|---|---|
Use IMU Turn Rate fuser.useImuTurnRate | boolean | true | Take the vehicle yaw rate from the IMU gyroscope (about the IMU Turn Rate Axis) instead of deriving it from differential wheel speeds. Recommended when a gyro is available, as it is far less sensitive to wheel slip and track-width error. |
Wheel Turn-Rate Fallback fuser.useWheelAngularFallback | boolean | false | Fall back to the wheel-derived turn rate when the IMU stream is lost. Off by default: with no measured wheel angular rate a dead IMU leaves no heading reference, so output is stopped rather than dead-reckoned on a stale turn rate. Enable only when a trustworthy wheel angular rate is available. |
IMU Timeout (s) fuser.imuTimeoutLimit | number | 0.5 | Time in seconds without an IMU sample before the IMU stream is declared lost. On loss the filter stops output - or switches to the wheel turn rate if Wheel Turn-Rate Fallback is on and a valid wheel rate is present. The IMU normally streams at 100 Hz; raise this only to tolerate longer dropouts. |
IMU Turn Rate Axis fuser.imuTurnRateAxis | vector3 | {"x":0,"y":0,"z":1} | Unit vector selecting which IMU gyroscope axis (in the IMU frame) supplies the vehicle yaw rate. Default (0,0,1) uses the IMU Z axis; set to match the axis pointing up through the vehicle. |
Wheel Omega Scale fuser.wheelOmegaScale | number | 1.0 | Multiplier applied to the wheel-derived angular velocity to correct for track-width or wheel-radius calibration error. Only relevant when Use IMU Turn Rate is off. 1.0 leaves the wheel turn rate unscaled. |
Detect Wheel Slip fuser.detectSlip | boolean | true | Inflate the control noise on ticks where the IMU and wheel-derived turn rates disagree (wheel slip or wrong track width). |
Estimate Wheel Scale fuser.estimateWheelScale | boolean | true | Estimate a slow wheel-speed scale factor (tire wear/pressure) from the ratio of RTK-fixed travelled distance to integrated odometry distance. |
Estimate Gyro Bias fuser.estimateGyroBias | boolean | true | Estimate the gyro yaw-rate bias as a Kalman filter state and subtract it from the IMU turn rate - the dominant yaw-drift source during GNSS outages. Verified multi-second GNSS-yaw windows and stopped gyro samples measure it directly. |
Gyro Bias Random Walk fuser.gyroBiasRandomWalk | number | 0.0005 | Random-walk process noise density for the odometry-stage gyro yaw-bias state, in rad/s per sqrt(s). Lets the in-run bias track slow thermal changes. |
Gyro Bias ZUPT Error fuser.gyroBiasZuptError | number | 0.0035 | Measurement noise for stopped zero-angular-rate gyro-bias updates, in rad/s. |
Orientation & Heading
| Property | Type | Default | Description |
|---|---|---|---|
Initialize from GNSS Orientation fuser.initializeFromGnssOrientation | boolean | false | Seed the filter’s initial heading from the GNSS dual-antenna orientation, avoiding the need to drive forward to resolve heading at startup. Only used when Use GNSS Without RTK Fix is off; when off without a valid reported orientation (single antenna), or when on, the heading is initialized from course over ground instead. |
Use GNSS Yaw Measurement fuser.useGnssYawMeasurement | boolean | false | Use the GNSS dual-antenna heading as a 1D yaw measurement while moving, instead of relying on position cross-covariance alone. Also keeps the heading corrected through a position-only outage and feeds the moving gyro-bias estimator. |
GNSS Yaw Error fuser.gnssYawError | number | 0.05 | GNSS heading measurement noise std-dev in radians. |
Stopped Detection
| Property | Type | Default | Description |
|---|---|---|---|
Retain State When Stopped fuser.retainStateWhenStopped | boolean | true | Freeze the filter state while the vehicle is detected as stationary, preventing position and heading drift from sensor noise when not moving. |
Velocity Threshold fuser.velocityThreshold | number | 0.01 | Vehicle speed in m/s below which the vehicle is considered stopped (with hysteresis at half this value). Used for stopped-state detection and optional state freezing. |
Initialization & Convergence
| Property | Type | Default | Description |
|---|---|---|---|
Init Velocity Threshold fuser.initVelocityThreshold | number | 3.0 | Minimum speed in m/s for the course-over-ground heading initialization. Heading is locked in after several consecutive GNSS samples above this speed. |
Init Velocity Tolerance fuser.initVelocityTolerance | number | 0.4 | Maximum allowed difference in m/s between the GNSS-derived speed and the wheel speed for a sample to count toward the course-over-ground heading initialization. |
RTK & GNSS Quality
| Property | Type | Default | Description |
|---|---|---|---|
Use GNSS Without RTK Fix fuser.useGpsOnRtkFloat | boolean | false | Accept GNSS position fixes of any quality instead of only RTK Fixed (quality 4). When off, the filter stays uninitialized until the first RTK Fixed sample; position accuracy degrades to that of the raw fixes when on. |
Weight GNSS by Fix Quality fuser.useGnssQualityWeighting | boolean | false | Weight GNSS position fixes by their NMEA fix quality instead of the all-or-nothing RTK gate. DGPS and RTK-float fixes are admitted as a loose drift leash (using the sigmas below) that bounds dead-reckoning drift during an RTK outage without being chased like an RTK Fixed fix. Single-point fixes stay rejected. Supersedes Use GNSS Without RTK Fix when on. |
DGPS Position Error fuser.gnssDgpsError | number | 4.0 | Position measurement noise std-dev in meters for DGPS / SBAS fixes (quality 2). Only used when Weight GNSS by Fix Quality is on. |
RTK Float Position Error fuser.gnssFloatError | number | 12.0 | Position measurement noise std-dev in meters for RTK Float fixes (quality 5). Only used when Weight GNSS by Fix Quality is on. |
Use RTCM Age De-weighting fuser.useRtcmAgeDeweighting | boolean | true | Inflate the GNSS position and heading measurement noise with the receiver’s RTCM correction age, so an RTK-fixed fix on stale corrections is smoothly de-weighted toward dead reckoning. When off, RTK-fixed fixes are trusted regardless of correction age. |
RTCM Age Sigma Per Second (m/s) fuser.rtcmAgeSigmaPerSec | number | 0.05 | Per-second growth in meters of the GNSS position sigma when RTK FIXED with a non-zero RTCM correction age. Effective measurement error becomes measurementError + diffAge * this. Only used when Use RTCM Age De-weighting is on. |
IMU Mounting & Frames
| Property | Type | Default | Description |
|---|---|---|---|
IMU-to-Car Rotation fuser.imuToCarRotation | quaternion | {"w":1,"x":0,"y":0,"z":0} | Quaternion (w,x,y,z) describing the rotation from the IMU sensor frame to the vehicle body frame. Accounts for the physical mounting orientation of the IMU. |
Optical
| Property | Type | Default | Description |
|---|---|---|---|
Use Optical Data fuser.useOpticalData | boolean | false | Incorporate an attached optical pose stream as an additional position measurement, useful for indoor or GNSS-denied segments. |
Optical Scaling Factor fuser.opticalScalingFactor | number | 10.0 | Scale factor applied to incoming optical position to convert it into meters / correct for tracking-volume scaling before fusion. Only used when Use Optical Data is enabled. |
Align Optical to GNSS fuser.alignOpticalToGnss | boolean | true | Rotate the optical coordinate frame to match the GNSS/world frame before fusing, so optical and GNSS positions share a common reference. Only used when Use Optical Data is enabled. |
Example node definition
A minimal entry in config.json looks like the block below. Paste it under the sinks key, keyed by an instance name of your choice. All settings are optional: anything not listed under settings uses the default from the Properties table above. See The configuration file for how the sections and endpoint wiring work.
{
"sinks": {
"vehicularFusion": {
"dataEndpoint": "inproc://vehicularFusion_data",
"inputEndpoints": [
"inproc://gnss_data",
"inproc://imu_data",
"inproc://vehicleSpeed_data",
"inproc://optical_data"
],
"inputDataFilter": [
"Gnss",
"Imu",
"VehicleSpeed",
"Optical"
],
"settings": {
"fuser": {
"imuToCarRotation": {
"w": 1,
"x": 0,
"y": 0,
"z": 0
},
"imuTurnRateAxis": {
"x": 0,
"y": 0,
"z": 1
}
}
}
}
}
}