Goal
Given stations at which a robot pauses with a rigidly mounted camera and observes a fixed calibration target, compute the constant homogeneous transform from the camera frame to the gripper frame . The inputs at each station are the gripper-to-base pose from robot kinematics and the target-to-camera pose from extrinsic camera calibration. The algorithm reduces the problem to the homogeneous matrix equation on station-pair motions, splits it into a rotation block and a translation block, and solves each by linear least squares.
Algorithm
Let denote a homogeneous transform with rotation and translation . Write for the skew-symmetric matrix associated with , so that .
For station :
- — gripper-to-base, from robot encoders.
- — target-to-camera, from extrinsic calibration of the image taken at station .
- — camera-to-gripper, the unknown.
For each pair define the inter-station gripper motion and the inter-station camera motion (the inverse positions reflect the directions of and ). Closing the loop yields the underlying constraint
which decomposes into
A 3-vector that encodes a rotation by angle around unit axis , used in place of to keep the unknown count fixed at three.
Reconstructs from without trigonometric calls.
Write , , for the modified Rodrigues vectors of , , . The rotation block of admits a linear constraint on an unscaled axis that is parallel to and grows as :
For each station pair , satisfies a linear equation in which only the inter-station Rodrigues vectors appear.
Once is known, satisfies one linear equation per station pair.
The skew matrix has rank 2, and so does . Each station pair contributes only two independent rows; at least two pairs (i.e. three stations) are required to determine a unique solution.
-
Choose station pairs . Prefer pairs with large rotation angles and pairwise non-parallel axes.
-
For each pair, compute and . Convert and to modified Rodrigues vectors and .
-
Stack the rotation constraint over all pairs into a system and solve by linear least squares.
-
Recover the modified Rodrigues vector and rotation matrix:
-
Stack the translation constraint over all pairs into a system and solve by linear least squares.
-
Special case: if is collinear across pairs while varies, then and is parallel to ; assemble directly, then proceed to step 5.
Implementation
Two-stage solver in Rust. The same matrix is filled twice — once for the rotation system, once for the translation system — and solved with a least-squares pseudoinverse. The lines map directly to steps 3–5 of the procedure.
use nalgebra::{DMatrix, DVector, Matrix3, Vector3};
fn skew(v: &Vector3<f64>) -> Matrix3<f64> {
Matrix3::new(
0.0, -v.z, v.y,
v.z, 0.0, -v.x,
-v.y, v.x, 0.0,
)
}
fn rodrigues_of(r: &Matrix3<f64>) -> Vector3<f64> {
let cos_t = ((r.trace() - 1.0) * 0.5).clamp(-1.0, 1.0);
let theta = cos_t.acos();
if theta.abs() < 1e-12 { return Vector3::zeros(); }
let axis = Vector3::new(r[(2,1)] - r[(1,2)],
r[(0,2)] - r[(2,0)],
r[(1,0)] - r[(0,1)]) / (2.0 * theta.sin());
2.0 * (theta * 0.5).sin() * axis
}
fn rot_from_rodrigues(p: &Vector3<f64>) -> Matrix3<f64> {
let n2 = p.norm_squared();
let alpha = (4.0 - n2).max(0.0).sqrt();
(1.0 - 0.5 * n2) * Matrix3::identity()
+ 0.5 * (p * p.transpose() + alpha * skew(p))
}
fn solve_handeye(pairs: &[(Matrix3<f64>, Vector3<f64>,
Matrix3<f64>, Vector3<f64>)])
-> (Matrix3<f64>, Vector3<f64>)
{
let m = pairs.len();
let mut a = DMatrix::<f64>::zeros(3 * m, 3);
let mut b = DVector::<f64>::zeros(3 * m);
for (k, (rg, _tg, rc, _tc)) in pairs.iter().enumerate() {
let pg = rodrigues_of(rg);
let pc = rodrigues_of(rc);
a.fixed_view_mut::<3, 3>(3 * k, 0).copy_from(&skew(&(pg + pc)));
b.fixed_rows_mut::<3>(3 * k).copy_from(&(pc - pg));
}
let p_prime = a.svd(true, true).solve(&b, 1e-9).unwrap();
let p_prime = Vector3::new(p_prime[0], p_prime[1], p_prime[2]);
let p_cg = (2.0 / (1.0 + p_prime.norm_squared()).sqrt()) * p_prime;
let r_cg = rot_from_rodrigues(&p_cg);
for (k, (rg, tg, _rc, tc)) in pairs.iter().enumerate() {
a.fixed_view_mut::<3, 3>(3 * k, 0).copy_from(&(rg - Matrix3::identity()));
b.fixed_rows_mut::<3>(3 * k).copy_from(&(r_cg * tc - tg));
}
let t_cg = a.svd(true, true).solve(&b, 1e-9).unwrap();
(r_cg, Vector3::new(t_cg[0], t_cg[1], t_cg[2]))
}
Remarks
- Two-stage decoupling makes the rotation independent of the translation but couples the translation error to the rotation error: the residual on inherits any bias in through the term in step 5.
- Minimum three stations (two motion pairs); uniqueness requires the inter-station rotation axes to be non-collinear across pairs. Pairs with rotation angles close to zero contribute almost no information — both and vanish in that limit.
- The modified Rodrigues parametrisation is singularity-free for but degenerates at , where is well-defined () but the recovery formula in step 4 has to be replaced by the explicit branch in step 6.
- Computational cost is dominated by the two least-squares solves and is in the number of station pairs.
- The decoupled formulation amplifies translation error when the camera baseline between stations is short relative to the target depth; simultaneous rotation-and-translation solvers (Park-Martin on the Lie algebra, Daniilidis dual-quaternion) treat the residual jointly and tend to be more robust under that regime.
When to choose Tsai-Lenz over Daniilidis
Daniilidis dual-quaternion hand-eye (1999) solves AX = XB for the rigid hand-eye transform in a single stage by parametrising the rigid motion as a unit dual quaternion and solving an SVD on the resulting linear constraint. Tsai-Lenz solves the same problem in two decoupled stages — modified Rodrigues for rotation, then linear least squares for translation.
| Tsai-Lenz (1989) | Daniilidis (1999) | |
|---|---|---|
| Stages | two (rotation, then translation) | one (rotation + translation jointly via dual quaternions) |
| Rotation parametrisation | modified Rodrigues vector | dual quaternion (unit norm + perpendicularity constraint) |
| Translation handling | second linear solve, biased by Stage 1 rotation error | jointly optimised; residuals shared across rotation and translation |
| Min stations | 3 (two motion pairs) | 3 (two motion pairs) |
| Singular configurations | requires explicit branch handling | dual-quaternion parametrisation is singularity-free |
| Robustness when baseline is short | translation error inflates | jointly optimised; better behaviour |
Choose Tsai-Lenz when (1) you want a clean two-stage decomposition where rotation and translation can be inspected separately — useful for diagnosing which component is the dominant error source; (2) the implementation simplicity of the modified Rodrigues parametrisation matters more than singularity safety; (3) the rotation angles between station pairs are far from (the standard machine-vision case). Choose Daniilidis when (1) the inter-station rotation angles can reach (workspace-wide motion); (2) the camera baseline is short relative to the target depth — the joint solve handles the translation-amplification regime where Tsai-Lenz's decoupling fails; (3) you need a singularity-free parametrisation throughout the rotation domain.
References
- R. Y. Tsai and R. K. Lenz. A new technique for fully autonomous and efficient 3D robotics hand/eye calibration. IEEE Transactions on Robotics and Automation 5(3)–358, 1989. pdf
- Y. C. Shiu and S. Ahmad. Calibration of wrist-mounted robotic sensors by solving homogeneous transform equations of the form AX=XB. IEEE Transactions on Robotics and Automation 5(1)–29, 1989. doi
- R. Y. Tsai. A versatile camera calibration technique for high-accuracy 3D machine vision metrology using off-the-shelf TV cameras and lenses. IEEE Journal on Robotics and Automation 3(4)–344, 1987. pdf