ZED 2i + Ouster + GNSS

Localization

What global localization is, the three sensors, the EKF, and the two global paths

LOADING LIMIT

What is global localization

Global localization is knowing where the robot is in a persistent, externally-fixed frame, a map or a GPS datum, not just where it is relative to where it started. Local odometry answers "how far from the starting point"; global localization answers "what is the robot's pose in the world", so the same place has the same coordinates every run.

Everything in this section runs on limit, the robot. The three sensors, the ZED 2i, the Ouster OS0-128, and the F9P, are on the dog, and so is the full Nav2 live stack: the EKF, navsat_transform, AMCL, and Nav2 all run on the robot. It is a self-contained system with no separate server in the loop.

Three approaches feed the pose:

The three sensors

All three run on the robot and feed a different part of the pose. The ZED 2i is the continuous source; the Ouster and the F9P pin it to a world frame.

SensorRole in global localizationKey specMain topics
ZED 2i (front stereo) PRIMARY continuous VIO odometry (local pose) + the IMU source. The foundation the EKF fuses. stereo, depth-referenced (meters, no scale drift); VIO ~20–28 Hz · IMU ~99 Hz · depth cloud ~4.9 Hz · RGB ~2.3 Hz /zed2i_front/zed_node/odom, /imu/data
Ouster OS0-128 (rev7) AMCL map-matching input (indoor) + the obstacle/costmap layer. 128-channel mid-range LiDAR; ~3–6 Hz (driver reader cap); AMCL reads it out to 30 m /ouster/points, /ouster/scan → /scan_corrected
u-blox F9P (GNSS) outdoor absolute anchor (lat/lon/alt → navsat datum). 5 constellations (GPS/GLONASS/BeiDou/Galileo/QZSS); single-point ~±5 m, RTK 2 cm + 2 ppm; ~1 Hz; USB /dev/ttyACM0 /fix, /raw_nmea

The ZED 2i's VIO is relative and drifts over long runs, in position, heading, and z; yaw is the fragile axis on a quadruped that rotates in place. The EKF cannot correct drift it cannot see, which is why the Ouster (AMCL) and the F9P (GNSS) exist. The ZED node runs with publish_tf:=false, so Nav2 owns the TF tree.

How the pose is built

One ekf_localization filter on the robot ties it together: a full 15-state 3D EKF (two_d_mode: false) at 30 Hz. It fuses the ZED IMU, the ZED VIO odometry, and the GNSS anchor when a fix is available, and publishes /odom plus the odom → base_link TF.

InputSourceUsed for
imu0ZED 2i IMU via stamp_shift (/imu_corrected)RPY + acceleration, gravity removed (~80–99 Hz)
odom0stamp-shifted ZED VIO odometry (/zed_odom_corrected)position + orientation only
odom1navsat_transform output (/odometry/gps)x, y when a GNSS fix is available

With no fix (odom1 silent), the filter runs on IMU + VIO alone: valid 30 Hz /odom, origin at the ZED boot point. The moment a GNSS fix flows, odom1 adds x and y and the same output is globally anchored, with no reconfiguration.

Path A, GNSS (outdoor, absolute)

Chain: F9P on /dev/ttyACM0 → nmea_navsat_driver → /fix → navsat_transform → /odometry/gps → EKF odom1. Active when the antenna has sky view; once /fix is valid, navsat_transform sets the datum and /odometry/gps flows into the EKF. Indoors the receiver is on standby and the EKF runs on ZED VIO + IMU alone.

Path B, AMCL (indoor, map-relative)

Chain: /ouster/scan → stamp_shift → /scan_corrected → AMCL + costmaps. AMCL places the robot on a pre-built map using the Ouster scan and publishes map → odom (tf_broadcast: true). Config: set_initial_pose: true, robot_model_type: DifferentialMotionModel, laser_max_range: 30.0.

Position source fusion

The robot can estimate its position in several different ways, and each one can fail for a different reason. This is the pivotal point of the stack: the pose has to come from somewhere, and the sources will disagree.

The ZED EKF solution is one option for addressing this: instead of trusting one source, it fuses them into a single estimate, weighted by how much each can be trusted at that moment. Additional functionality, per-sensor health checks and calibration visibility, is available through the live Limit dash. It is not part of the demo on this site.

What the EKF is

An EKF, an Extended Kalman filter, is a state estimator that alternates two steps. Predict advances the current best estimate with the motion model, here the IMU and the VIO odometry. Update corrects that prediction when a measurement arrives, a GNSS fix or a map match, in proportion to the relative trust in the two, tracked as a covariance, the filter's measured uncertainty in its own estimate. "Extended" means it handles the nonlinear motion model by linearizing it around the current estimate. The output is a single position with an honest error bar, so a disagreement between sources becomes a number you can read instead of a mystery.

This is also the point where we fine tune calibration, and calibration is the area where unusual behavior can result in missed understanding for users. The direction is total transparency: which source the robot trusted, why, and how confident it was, all readable. Machine learning evaluation of the position solution, the gait logic, and the vision systems is planned and not yet implemented.

F9P accuracy

The F9P has two accuracy modes, and the difference comes down to corrected data:

ModeWhat it isPosition accuracy
Single-pointthe receiver alone, no base station~±5 m
RTKthe receiver subtracts a base station's measured error against its known position2 cm + 2 ppm

The target operating point is RTK: a base station knows exactly where it is, measures the same satellites, and broadcasts its error; the F9P removes it and meter-class becomes centimeter-class. Indoors, with no sky view, the receiver publishes no fix at all, a design state rather than a failure: the EKF runs on ZED VIO + IMU until sky view returns.

What this setup enables

Once the pose is mapped to a GPS datum, the robot lives in a world frame that persists between runs. That is the "distance detail" this sensor set is built for:

Related docs

Nav2 (stack dataflow, /cmd_vel chain) · Locomotion (CHAMP + CAN) · Topics (full roster)