# How calibrationd Calibrates Device Pose and Camera Extrinsics for Accurate Driving Assistance

> Learn how calibrationd calibrates device pose and camera extrinsics using fused camera odometry and a kinematic model for precise driving assistance with openpilot.

- Repository: [comma.ai/openpilot](https://github.com/commaai/openpilot)
- Tags: internals
- Published: 2026-03-05

---

**The `calibrationd` daemon continuously estimates the device’s roll‑pitch‑yaw (RPY) pose and wide‑angle camera extrinsics by fusing high‑confidence camera odometry with a kinematic model, smoothing estimates through a block‑based moving average, and publishing a `liveCalibration` message that corrects perception pipelines across the openpilot stack.**

The `calibrationd` process is essential to openpilot’s accuracy, ensuring the vehicle‑mounted device understands its orientation relative to the car and the camera’s physical offset. Located in [`selfdrive/locationd/calibrationd.py`](https://github.com/commaai/openpilot/blob/main/selfdrive/locationd/calibrationd.py), this dedicated daemon ingests raw sensor data, validates motion conditions, and produces the geometric transforms consumed by `locationd`, `modeld`, and the UI to maintain precise lane‑keeping and path planning.

## Data Inputs for calibrationd

The daemon subscribes to three primary data sources to build its state:

- **`cameraOdometry`** — Provides raw vehicle‑relative translation (`trans`), rotation (`rot`), and their standard deviations (`transStd`, `rotStd`). It also carries `wideFromDeviceEuler` for wide‑camera mounting offsets and `roadTransformTrans` for height estimates.
- **`carState`** — Supplies `vEgo` (vehicle speed) used to gate calibration updates to high‑speed, straight‑line motion where odometry is most reliable.
- **`liveCalibration` (cached)** — On startup, the daemon loads previous `rpyCalib`, `validBlocks`, `wideFromDeviceEuler`, and `height` from the `CalibrationParams` key to resume calibration across reboots.

## The Calibration Pipeline: From Raw Odometry to Validated Pose

Each incoming odometry sample triggers `handle_cam_odom` (lines 81‑100 in [`calibrationd.py`](https://github.com/commaai/openpilot/blob/main/calibrationd.py)), which executes a multi‑stage validation and fusion pipeline.

### Quality Gating with Straight-and-Fast Logic

Before accepting a sample, `calibrationd` verifies the vehicle is moving fast and straight to minimize integration error. The checks include:

- **Speed thresholds**: `v_ego > MIN_SPEED_FILTER` and `trans[0] > MIN_SPEED_FILTER` ensure forward motion dominates.
- **Yaw rate limit**: `|rot[2]| < MAX_YAW_RATE_FILTER` filters out turning maneuvers.
- **Uncertainty bounds**: Angle standard deviation must stay below `MAX_VEL_ANGLE_STD` and height standard deviation below `MAX_HEIGHT_STD`.

If any check fails, the sample is discarded immediately.

### Converting Translation to Orientation Observations

For valid samples, the daemon interprets the 3‑D translation vector as a small‑angle rotation to derive observed pitch and yaw:

```python
observed_rpy = np.array([0,
                        -np.arctan2(trans[2], trans[0]),   # pitch

                        np.arctan2(trans[1], trans[0])])   # yaw

```

This calculation (lines 102‑104) yields the instantaneous orientation of the camera relative to the vehicle frame based purely on the current odometry drift.

### Fusing Observations with Rotation Matrices

Rather than averaging Euler angles directly, the daemon composes rotations in matrix form to avoid gimbal lock and preserve geometric consistency:

```python
new_rpy = euler_from_rot(
            rot_from_euler(self.get_smooth_rpy())
            .dot(rot_from_euler(observed_rpy))
        )

```

This operation (line 105) applies the observed rotation **in the device frame**, respecting the current smoothed estimate while integrating fresh data. The result is sanity‑clipped against hard limits (`PITCH_LIMITS`, `YAW_LIMITS`, and `RPY_INIT`) to reject NaNs or impossible values (lines 106‑108).

### Block-Based Smoothing and Persistence

Validated observations are blended into a per‑block circular buffer using `moving_avg_with_linear_decay` (lines 118‑122), which weights recent data more heavily while preserving older samples for stability.

- **Block structure**: Each block contains `BLOCK_SIZE = 100` samples. When `self.idx` wraps to 0, the block index advances and `valid_blocks` increments (lines 124‑128).
- **Calibration readiness**: The status transitions from `uncalibrated` to `calibrated` only after accumulating `INPUTS_NEEDED = 5` valid blocks.
- **Spread monitoring**: `update_status` (lines 138‑147) tracks the max‑min spread of RPY values. Excessive spread triggers an automatic reset to `recalibrating` (lines 162‑166).
- **Persistence**: Every few cycles, the daemon serializes parameters to the `Params` store under the key `"CalibrationParams"` (line 70), enabling seamless recovery after power cycles.

## Publishing the liveCalibration Message

The `get_msg` function (lines 233‑247) constructs a Cap’n Proto message published on the `liveCalibration` channel at approximately **4 Hz** (every 5th camera odometry frame). The message contains:

- `validBlocks` — Count of filled 100‑sample blocks.
- `calStatus` — Enum state (`uncalibrated`, `calibrating`, `calibrated`, `invalid`, or `recalibrating`).
- `calPerc` — Percentage progress toward the required block count.
- `rpyCalib` — Smoothed roll‑pitch‑yaw (roll is fixed to zero per design).
- `rpyCalibSpread` — Current pitch/yaw spread values.
- `wideFromDeviceEuler` — Extrinsic RPY of the wide‑angle camera relative to the device.
- `height` — Calibrated camera height above the ground in meters.

## Downstream Consumption in openpilot

Downstream modules consume the `liveCalibration` message to align their coordinate frames:

- **`locationd`** (line 55 in [`selfdrive/locationd/locationd.py`](https://github.com/commaai/openpilot/blob/main/selfdrive/locationd/locationd.py)) reads `rpyCalib` to compute `device_from_calib = rot_from_euler(calib)`, rotating incoming `cameraOdometry` into the vehicle frame.
- **`modeld`** (line 348 in [`selfdrive/modeld/modeld.py`](https://github.com/commaai/openpilot/blob/main/selfdrive/modeld/modeld.py)) uses the calibrated RPY to transform neural network predictions into the correct camera pose.
- **UI** monitors `calStatus` to display progress bars and alert the driver when recalibration is required after physical device movement.

## Practical Code Examples

### Reading Persisted Calibration Parameters

```python
from cereal import messaging
from openpilot.common.params import Params

params = Params()
cal_bytes = params.get("CalibrationParams")
if cal_bytes:
    calib_msg = messaging.log_from_bytes(cal_bytes, "log.Event").liveCalibration
    rpy = calib_msg.rpyCalib          # [roll, pitch, yaw] in radians

    height = calib_msg.height[0]      # meters above road

    wide_euler = calib_msg.wideFromDeviceEuler
    print(f"RPY: {rpy}, Height: {height:.2f} m")

```

### Applying Calibration in a Perception Pipeline

```python
import numpy as np
from openpilot.common.transformations.orientation import rot_from_euler

# live_calibration_msg obtained from liveCalibration subscription

device_R = rot_from_euler(np.array(live_calibration_msg.rpyCalib))

# Rotate raw camera translation into vehicle NED frame

trans_vehicle = device_R @ np.array(camera_odometry_msg.trans)

```

## Summary

- **`calibrationd`** fuses `cameraOdometry` with vehicle speed data to estimate device pose and camera extrinsics in real time.
- **Quality gating** restricts updates to straight‑and‑fast motion, ensuring high‑confidence observations.
- **Rotation matrix fusion** in `handle_cam_odom` composes incremental orientation updates while avoiding gimbal lock.
- **Block‑based smoothing** with `BLOCK_SIZE = 100` and `INPUTS_NEEDED = 5` provides stable, drift‑resistant averages.
- **Persistence** to `CalibrationParams` allows recovery across reboots, while `liveCalibration` messages synchronize the entire openpilot stack.

## Frequently Asked Questions

### How often does calibrationd update its estimates?

The daemon processes every incoming `cameraOdometry` message but publishes the `liveCalibration` result at roughly **4 Hz**, corresponding to every 5th odometry frame. This rate balances real‑time responsiveness with network overhead.

### What triggers a recalibration event?

If `update_status` detects that the spread (max‑min) of accumulated RPY values exceeds safe thresholds, or if the device is physically moved while powered off (detected via persistent parameter mismatch), the state transitions to `recalibrating` and the block buffer resets.

### Why does calibrationd require straight‑line driving?

Curved paths introduce centripetal acceleration and yaw‑rate errors that corrupt the small‑angle assumption used to derive `observed_rpy` from the translation vector. The `MAX_YAW_RATE_FILTER` and minimum speed checks ensure the vehicle motion approximates a straight line, making the odometry‑to‑pose conversion mathematically valid.

### Where is the calibration data stored between drives?

The daemon serializes the current `rpyCalib`, `validBlocks`, `wideFromDeviceEuler`, and `height` to the `Params` database under the key `"CalibrationParams"` approximately every few blocks. On startup, `calibrationd` attempts to load these values, allowing the system to resume calibration without requiring a full 500‑sample collection cycle after every reboot.