Hand-Eye Calibration
Solve the camera extrinsic from robot silhouette masks and/or depth maps.
Solve camera extrinsics from initial_extrinsic.
Lifecycle
- Build the solver for new observations, or reuse stored data.
- Reset variables and sample camera or joint candidates.
- Optionally initialize q from keypoints, then run native population LM.
- Retain the best result and restart if mask mismatch remains high.
Data:
- Any target supplied: replace data and rebuild the solver.
- No targets supplied: reuse stored data.
Initial OpenCV T_camera_base or mounted-camera T_camera_mount, [4,4].
Robot silhouette masks [N, H, W] in [0, 1].
Robot-only depth maps [N, H, W] in meters, 0 = invalid.
Optional nonnegative objective weights [N].
Known [N,num_dofs], or one initial [num_dofs] shared across views with optimize_q; None uses midpoints.
Per-sample mount pose [N, 4, 4] (mounted camera).
Optional link-frame origins to project, with optimize_q.
Their observed pixels [N,P,2]. NaNs mark missing points.
Optional nonnegative confidence weights [N,P].
Optimized extrinsic [4,4] on the input device; last_q stores fitted q and last_var includes model offsets.
Eager fitting with pyramid levels, cost terms and candidate sampling.
Example
>>> config = HandEyeCalibratorConfig(optimize_q=True, optimize_camera=False) >>> config.optimize_q, config.optimize_camera, config.candidate_noise_q (True, False, None)
from robokit.helpers.hand_eye_calibration.config import HandEyeCalibratorConfig config = HandEyeCalibratorConfig(optimize_q=True, optimize_camera=False) config.optimize_q, config.optimize_camera, config.candidate_noise_q
Coarse-to-fine levels, one per solver segment.
Cost terms; each walks levels itself. MaskAlignmentOffsetTask also calibrates the kinematic model.
One segment per level; native final ranking returns the best candidate.
Largest translation (m) of the initial candidates from the initial guess; each draws 0.5-1x of it.
Largest rotation (rad) of the initial candidates from the initial guess; each draws 0.5-1x of it.
Re-solve with fresh candidates while the final mask mismatch (2 * cost / target mask area) exceeds this.
Re-solves allowed after the first; 0 turns restarts off.
Seed of the candidate noise.
Fit one unknown q vector shared across views; solve(q=...) takes an initial [num_dofs] guess.
Optimize the camera extrinsic; False keeps the supplied extrinsic fixed.
Joint sampling radius in radians/meters; None samples the full finite joint limits. Candidate 0 keeps initial q.
Relative weight of optional 2D link-origin observations.
Keypoint residual scale in input-image pixels.
Robust-loss transition in input-image pixels.
Keypoint-only initialization steps; half the broad samples remain unmodified.
Select by cost or soft IoU; IoU mode uses union-normalized coarse fitting.
Fit unknown q[T,J], optionally refining static cameras[V,4,4] shared over time; temporal penalties apply only to q.
Example (CUDA, robot meshes and observations required):
>>> from robokit.helpers.hand_eye_calibration import presets >>> config = presets.rgb_qpos_trajectory >>> config.optimize_camera False >>> # helper = QposTrajectoryCalibrator(config, robot, K, H, W) >>> # cameras = helper.solve(cameras, masks, timestamps) >>> # helper.last_q.shape # (timestamps, joints)
from robokit.helpers.hand_eye_calibration import presets config = presets.rgb_qpos_trajectory config.optimize_camera # helper = QposTrajectoryCalibrator(config, robot, K, H, W) # cameras = helper.solve(cameras, masks, timestamps) # helper.last_q.shape # (timestamps, joints)
Fit synchronized silhouettes and optional keypoints to a joint trajectory; an empty mask with positive weight constrains absence.
Lifecycle
- Validate calibration, timestamps and observations; normalize mask costs by area.
- Initialize a coherent trajectory, or use supplied q without frame sampling.
- Fit the coupled image, temporal and optional camera system.
- Resolve shared joint offsets against source frames and retain joints and cameras.
Outputs
last_frame_q: Independent frame fits, or the supplied q. last_initial_q: Coherent path before coupled refinement, or the supplied q. last_q: Final joint result [T,J] on the input device.
Static calibrated cameras [V,4,4].
Silhouettes [T,V,H,W] in [0,1].
Finite, strictly increasing source times [T].
Nonnegative mask weights [T,V]; zero marks a missing view.
Optional joint trajectory [T,J]; skips frame sampling.
Link-frame origins [P] observed by the keypoints.
Image coordinates [T,V,P,2]; NaNs mark missing points.
Optional confidence weights [T,V,P]; zero excludes a point.
Cameras [V,4,4] on the input device; fixed unless camera refinement is enabled.
Eager frame fitting followed by coupled mask and temporal refinement.
>>> config = QposTrajectoryCalibratorConfig(velocity_weight=0.01) >>> config.optimize_camera, config.velocity_weight (False, 0.01)
from robokit.helpers.hand_eye_calibration.config import QposTrajectoryCalibratorConfig config = QposTrajectoryCalibratorConfig(velocity_weight=0.01) config.optimize_camera, config.velocity_weight
Unknown-qpos frame initialization; cameras stay fixed during initialization.
Limit broad frame searches to this many visible frames; interpolate before fitting every frame. None searches all.
Alternative initial pose branches to rank against every source mask before refinement.
Additional frame searches at the worst fitted source times; 0 disables repair.
Stop starting repairs after this initialization time; the initial searches and ranking still complete.
Refine one static extrinsic per view, shared across all frames.
Prior cost relative to the supplied calibration; 0 disables it.
Translation (m) and rotation (rad) scales of shared camera corrections.