← All writing

Building a GPS-Free Pedestrian Navigation System

Why raw IMU integration fails, why foot-mounted sensing helps, what ZUPT actually does, and what the system currently does and doesn't handle.

Why GPS-free navigation is hard

GPS works by receiving signals from multiple satellites and computing a position from the timing differences. It is reliable in open sky. It fails inside buildings, in urban canyons where signals bounce off walls, and in any environment where the satellite geometry is blocked or degraded.

The obvious alternative is inertial navigation: use accelerometers and gyroscopes on the body to track motion directly. The problem is that inertial sensors accumulate error. Every small measurement noise and every small calibration error integrates over time. Position error from raw IMU integration grows roughly as the square of elapsed time. After 60 seconds of walking, a consumer-grade IMU produces position estimates that are tens of meters off. After a few minutes, they are useless.

This is not a fixable problem in the general case. It is a fundamental property of integration. Any GPS-free system that uses inertial sensing has to find a way to interrupt and correct this error growth, not eliminate its source.

Why foot-mounted sensing helps

Most wearable navigation research places the IMU on the hip, wrist, or torso. These positions produce smoother, more comfortable signal, but they miss something the foot has: a predictable zero-velocity phase during every walking stride.

When a foot contacts the ground and the body’s weight shifts forward, the foot is briefly stationary — flat, unmoving, resting on the floor. This happens at the same point in every normal walking stride. If you can detect this moment reliably, you know the foot’s true velocity is zero (or very close to it). You can use that known true state to correct the estimated state.

This is called a Zero Velocity Update (ZUPT). It is not a perfect correction — the detection isn’t always perfectly timed, and “nearly zero” is not the same as “exactly zero” — but it interrupts the drift accumulation at each step rather than letting it grow unbounded.

What calibration does

Before any estimation, the raw sensor output needs calibration. IMU accelerometers have bias: a constant offset added to every reading even when the sensor is stationary. Gyroscopes have drift: a slow-changing offset that makes the sensor think it is rotating when it is not.

These offsets are small — fractions of a meter per second squared, fractions of a degree per second. But they integrate. A 0.01 m/s² accelerometer bias, integrated twice over 60 seconds, produces a 18-meter position error. The bias is not noise — it does not average out. It accumulates monotonically.

The calibration routine I use characterizes bias by recording sensor output during a known stationary period, averaging over many samples to reduce noise, and subtracting the mean from subsequent readings. For gyroscopes, this means recording at rest. For accelerometers, it also involves knowing what the gravity vector should read in each orientation.

Getting calibration right before implementing anything else saved significant debugging time. Most early position errors I encountered were calibration problems, not filter problems.

What ZUPT and ZARU are trying to solve

ZUPT (Zero Velocity Update) tells the filter: at this moment, the foot velocity is zero. The filter uses this as a measurement to correct accumulated velocity error.

ZARU (Zero Angular Rate Update) tells the filter: at this moment, the foot angular rate is zero. This corrects gyroscope drift in orientation.

Both corrections happen at the same phase of the stride — when the foot is flat and stationary on the ground. The stride detector has to identify this phase reliably. I use a combination of:

  • Accelerometer magnitude: during the stationary phase, total acceleration approximates gravity (about 9.8 m/s²) and is smooth
  • Gyroscope norm: during the stationary phase, angular rate is near zero across all axes

Both conditions must hold simultaneously for a ZUPT to be triggered. This reduces false triggers during stair climbing or unusual gait, where one condition might be met without the other.

The detection thresholds are sensitive to walking speed. Slow walking produces a longer, cleaner stationary phase. Fast walking compresses it. A threshold that works for normal pace produces too many missed detections at a jog. This is part of why the ML-assisted configuration selection exists in TARAF — different movement modes need different parameters.

Why computer vision ground truth matters

To know whether the navigation estimate is any good, you need something to compare it to. In a lab, the cleanest option is a motion capture system. Those are expensive and require a specialized room.

I built an alternative using an overhead camera and ArUco fiducial markers. The foot unit carries a printed ArUco marker. A calibrated camera mounted overhead tracks the marker position at video frame rate. This gives a position trace for the foot that is independent of the IMU estimate.

Comparing the two traces — IMU-estimated position versus camera-tracked position — gives a direct measure of position error over a trajectory. It is not as precise as motion capture, but it is accurate enough to distinguish good from bad estimation, and it can be set up in any room with a camera mounting point and a calibrated lens.

The camera calibration matters. An uncalibrated camera introduces its own position error (distortion, scale). I use a standard OpenCV checkerboard calibration to characterize the lens before using it as a reference.

What the system currently does

As of now:

  • ESP32 + BNO055 logging calibrated IMU data at 100 Hz
  • Calibration routine for accelerometer bias and gyroscope bias
  • Low-pass filter on accelerometer data before integration
  • Stride detection using acceleration magnitude and gyroscope norm thresholds
  • ZUPT injection during detected zero-velocity phases
  • ESKF estimator propagating position, velocity, and quaternion orientation
  • ArUco tracking pipeline for ground truth comparison
  • Offline analysis scripts in Python for trajectory comparison and error metrics

ZUPT-corrected trajectories show substantially reduced velocity error compared to raw integration. Quantitative drift numbers against ArUco ground truth are being collected.

What still needs improvement

The stride detector’s fixed thresholds do not generalize well across walking speeds or irregular terrain. This is the most immediate limitation.

The ESKF does not currently model heading drift explicitly. Long-distance trajectories (many minutes) accumulate heading error even with good ZUPT correction, because small gyroscope bias in the yaw axis is not corrected by ZUPT alone.

The offline ML-assisted configuration selection is functional for labeled data but not yet integrated into a real-time or replay system that selects configuration automatically.

I will update this post as the system develops.