Skip to content

Camera calibration

Overview

Raiden uses ChArUco boards to calibrate cameras. Two types of calibration are supported:

  • Hand-eye calibration (wrist cameras) - determines the rigid transform from the camera frame to the gripper frame (T_camera→gripper).
  • Scene camera calibration - determines the transform from the camera frame to the robot base frame (T_camera→base).

Calibration setup

Step 1 - Print a ChArUco board

Print a ChArUco board and mount it rigidly (e.g. on a flat aluminium plate). For best results, consider an industry-grade printed target from calib.io. These are manufactured on dimensionally stable material and give lower reprojection errors than paper prints.

Step 2 - Record calibration poses

Move the robot to a series of diverse poses while the board is visible. At least 5 poses are required; 7–10 is recommended for good coverage.

rd record_calibration_poses

Keyboard controls:

  • R - record current pose
  • D - delete last recorded pose
  • L - list all recorded poses
  • Q - quit (saves if enough poses recorded)

Poses are saved to ~/.config/raiden/calibration_poses.json.

Step 3 - Run calibration

rd calibrate

If your board has different dimensions or a different dictionary, pass them here:

rd calibrate \
    --squares-x 9 \
    --squares-y 9 \
    --square-length 0.03 \
    --marker-length 0.023 \
    --dictionary DICT_6X6_250
Parameter Default Description
--squares-x 9 Number of squares along X
--squares-y 9 Number of squares along Y
--square-length 0.03 Checker square side length in metres
--marker-length 0.023 ArUco marker side length in metres
--dictionary DICT_6X6_250 ArUco dictionary

Raiden reads ~/.config/raiden/camera.json and calibrates every camera whose role is "left_wrist", "right_wrist", or "scene" automatically - no explicit camera IDs are required.

The runner will:

  1. Load the recorded poses from ~/.config/raiden/calibration_poses.json
  2. Move the robot to each pose
  3. Capture frames from all target cameras
  4. Detect the ChArUco board in each frame
  5. Run hand-eye / scene calibration
  6. Save results to ~/.config/raiden/calibration_results.json

Output

~/.config/raiden/calibration_results.json contains one entry per camera:

{
  "version": "1.0",
  "timestamp": "2026-03-12T22:00:00.000000",
  "coordinate_frame": "left_arm_base",
  "cameras": {
    "left_wrist_camera": {
      "type": "hand_eye",
      "serial_number": 16522755,
      "intrinsics": {
        "camera_matrix": [[526.7, 0, 319.4], [0, 526.7, 178.5], [0, 0, 1]],
        "distortion_coeffs": [-0.041, 0.013, 0.0, 0.0, 0.0],
        "image_size": [672, 376],
        "source": "zed_sdk_factory_calibration"
      },
      "num_poses_used": 8,
      "hand_eye_calibration": {
        "success": true,
        "rotation_matrix": [[0.999, -0.012, 0.008], [0.012, 0.999, -0.003], [-0.008, 0.003, 0.999]],
        "translation_vector": [0.012, -0.034, 0.061],
        "rotation_vector": [-0.003, 0.008, 0.012],
        "method": "Tsai"
      }
    },
    "scene_camera": {
      "type": "scene",
      "serial_number": 37038161,
      "intrinsics": {
        "camera_matrix": [[700.1, 0, 659.8], [0, 700.1, 371.2], [0, 0, 1]],
        "distortion_coeffs": [-0.052, 0.021, 0.0, 0.0, 0.0],
        "image_size": [1280, 720],
        "source": "zed_sdk_factory_calibration"
      },
      "num_poses_used": 8
    }
  },
  "bimanual_transform": {
    "right_base_to_left_base": [[1, 0, 0, -0.612], [0, 1, 0, 0.0], [0, 0, 1, 0.0], [0, 0, 0, 1]],
    "computed_from_calibration": true,
    "description": "Transform from right arm base frame to left arm base frame"
  }
}