Skip to content

Calibrate

SUMMARY

calibrate() solves tcp_T_camera, the fixed transform between a robot's TCP (flange) and a wrist-mounted camera, from images of a static calibration board taken at a series of robot poses.

SUPPORTED TARGETS

Requires a CharucoTarget, per-corner ArUco ids are needed to recover a target pose per frame. See All Supported Targets.

UNITS

Input images are BGR ndarrays. robot_T_tcp_list and tcp_T_camera are (4, 4) SE(3) matrices with translation in meters. reprojection_error is in pixels.

The Skill

python
result = calibrator.calibrate(
    robot_T_tcp_list,
    image_list,
    method="TSAI",
    camera_T_target_list=[],
    intrinsic_matrix=None,
    distortion_coefficients=None,
    output_path=None,
)

There are three input modes, selected by which optional arguments are supplied:

ModeHow to triggerBehavior
Full pipeline (default)Leave intrinsic_matrix and camera_T_target_list unsetRuns intrinsic calibration on image_list, estimates a per-frame target pose, then cv::calibrateHandEye.
Known intrinsicsPass intrinsic_matrix (and optionally distortion_coefficients)Skips the intrinsic-calibration stage; per-frame poses come from ChArUco detection + solvePnP against the given K, d.
Strict pass-throughPass camera_T_target_listSkips detection entirely. The caller is responsible for aligning robot_T_tcp_list and image_list to the supplied poses.

The Code

Requires images of a calibration board alongside the robot's TCP pose (robot_T_tcp, as 4x4 homogeneous matrices) for each frame.

python
from telekinesis.axon import EyeInHandCalibrator
from telekinesis.axon.targets import CharucoTarget

target = CharucoTarget(squares_x=6, squares_y=9, square_length=0.012, marker_length=0.009)

calibrator = EyeInHandCalibrator(target)

result = calibrator.calibrate(
    robot_T_tcp_list=robot_poses,  # list of (4, 4) np.ndarray
    image_list=images,
    method="TSAI",
)

result.ok               # bool
result.tcp_T_camera     # (4, 4) np.ndarray
result.intrinsic_matrix
result.distortion_coefficients
result.reprojection_error
result.consistency       # TargetConsistencyStats, when result.has_consistency

Pass pre-computed per-frame poses

If you have camera_T_target transforms from an external source, pass them via camera_T_target_list to skip EyeInHandCalibrator's internal solvePnP (see compute_camera_T_target_list() for how to produce them):

python
result = calibrator.calibrate(
    robot_T_tcp_list=robot_poses,
    image_list=images,
    camera_T_target_list=external_camera_T_target_list,
    intrinsic_matrix=K,
    distortion_coefficients=d,
)

Hand-Eye Methods

HAND_EYE_METHOD_BY_NAME_MAP maps method names to cv::HandEyeCalibrationMethod values:

MethodNotes
"TSAI"Default; robust general-purpose choice.
"PARK"
"HORAUD"
"ANDREFF"
"DANIILIDIS"Occasionally diverges to an outlier solution, see Compare Hand-Eye Methods for cross-method comparison.
python
from telekinesis.axon.calibration import HAND_EYE_METHOD_BY_NAME_MAP

HandEyeResult

FieldTypeDescription
okboolFalse when calibration failed; tcp_T_camera is then empty.
tcp_T_camera(4, 4) ndarrayTransform from TCP (flange) frame to camera frame.
intrinsic_matrix(3, 3) ndarrayCamera matrix, empty in the strict pass-through path when none was supplied.
distortion_coefficientsndarrayDistortion coefficients, may be empty, see above.
reprojection_errorfloatIntrinsic mean reprojection error (px); NaN when not computed (strict pass-through with no intrinsic stage run).
valid_robot_T_tcp_listlist[ndarray]Robot poses that survived detection and per-view filtering.
valid_image_listlist[ndarray]Images that survived detection and per-view filtering.
camera_T_target_listlist[(4, 4) ndarray]Per-frame target poses used in the solve.
has_consistencyboolTrue when the solve succeeded and consistency is populated.
consistencyTargetConsistencyStatsSee Eye-in-Hand State.

See EyeInHandCalibrator for theory and best practices, and EyeInHandCalibrator State to read back the last result.