How calibrationd Calibrates Device Pose and Camera Extrinsics for Accurate Driving Assistance
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, 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 carrieswideFromDeviceEulerfor wide‑camera mounting offsets androadTransformTransfor height estimates.carState— SuppliesvEgo(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 previousrpyCalib,validBlocks,wideFromDeviceEuler, andheightfrom theCalibrationParamskey 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), 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_FILTERandtrans[0] > MIN_SPEED_FILTERensure forward motion dominates. - Yaw rate limit:
|rot[2]| < MAX_YAW_RATE_FILTERfilters out turning maneuvers. - Uncertainty bounds: Angle standard deviation must stay below
MAX_VEL_ANGLE_STDand height standard deviation belowMAX_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:
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:
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 = 100samples. Whenself.idxwraps to 0, the block index advances andvalid_blocksincrements (lines 124‑128). - Calibration readiness: The status transitions from
uncalibratedtocalibratedonly after accumulatingINPUTS_NEEDED = 5valid blocks. - Spread monitoring:
update_status(lines 138‑147) tracks the max‑min spread of RPY values. Excessive spread triggers an automatic reset torecalibrating(lines 162‑166). - Persistence: Every few cycles, the daemon serializes parameters to the
Paramsstore 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, orrecalibrating).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 inselfdrive/locationd/locationd.py) readsrpyCalibto computedevice_from_calib = rot_from_euler(calib), rotating incomingcameraOdometryinto the vehicle frame.modeld(line 348 inselfdrive/modeld/modeld.py) uses the calibrated RPY to transform neural network predictions into the correct camera pose.- UI monitors
calStatusto display progress bars and alert the driver when recalibration is required after physical device movement.
Practical Code Examples
Reading Persisted Calibration Parameters
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
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
calibrationdfusescameraOdometrywith 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_odomcomposes incremental orientation updates while avoiding gimbal lock. - Block‑based smoothing with
BLOCK_SIZE = 100andINPUTS_NEEDED = 5provides stable, drift‑resistant averages. - Persistence to
CalibrationParamsallows recovery across reboots, whileliveCalibrationmessages 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.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →