Visual Odometry from Frame Motion
Watch the ground slide past frame by frame and add up the steps. Visual odometry turns pixel motion between consecutive frames into a running camera path.
Why Does This Exist?
GPS dies indoors and wheels slip, so a camera must dead-reckon from what it sees. Visual odometry exists for that incremental job: estimate rotation and translation between consecutive frames and chain them into a path. It is the front end inside visual SLAM; SLAM adds the map reuse and loop closure that odometry lacks.
This page covers frame-to-frame motion only. Pixel motion basics live in optical flow; global correction lives in pose graph optimization.
Think of It Like This
Counting steps by watching floor tiles
Walk with eyes on the floor tiles and count how each tile slides past. Big fast slides mean long strides; slow slides mean shuffling. After a hundred steps your count is close but off, because each step estimate carried a small error.
Tiles are tracked corners and strides are and steps. The analogy stops at scale: tile size is known while a single camera never learns meters without a baseline or known object.
How It Actually Works
Each step detects corners, tracks them into the next frame, and solves for the motion that best explains the 2D shifts. Calibrated stereo triangulates points at true scale and solves perspective-n-point for ; monocular setups estimate the essential matrix and recover motion up to an unknown scale factor. Keyframes anchor the chain: new frames track against the last keyframe, and a new keyframe spawns when parallax or feature loss passes a threshold. Chaining composes poses as , so per-step errors of percent compound into percent-level drift per hundred meters.
Worked numbers
At Hz with m per step, frames cover m. A percent scale bias per step adds m extra each time, totaling about m after steps, plus roughly m of random walk from m per-step noise. The path ends about m from truth with no loop to pin it, the textbook odometry drift curve.
Code
def chain_poses(steps: list[float]) -> float: total = 0.0 for s in steps: total += s # 1D chaining stands in for SE(3) composition return total
print(chain_poses([0.505] * 200)) # -> 100.9999999999998Watch Out For
Trusting monocular scale over distance
Monocular odometry recovers direction but not meters, so scale drifts silently with scene depth. The symptom is a path with the right shape at the wrong size. Fix scale from stereo, an IMU, or a known-height object, and never report meters from one camera alone.
Tracking through pure rotation
Distant scenes under rotation give tiny parallax, so translation estimates swing wildly while rotation looks clean. Gate keyframes on translation parallax and translation magnitude, and freeze the position update when the baseline collapses.
The Quick Version
- Visual odometry chains frame-to-frame and into a running path.
- Stereo gives metric scale; monocular recovers motion only up to scale.
- Keyframes bound cost by tracking against sparse anchors.
- Per-step errors near percent compound into meters over hundreds of meters.
- Odometry drifts by design; loop closure is what removes the drift.
- Pure rotation and textureless frames are the standard tracking traps.