3D visionHow-to

Convert a hand-eye quaternion to a transform matrix

Turn the hand-eye quaternion and translation into a 4x4 transform matrix, chain camera to flange to base, and convert a detected XYZ into a robot pose.

LumaSpec Vision9 min read Share on LinkedIn

On this page 8 sections
  1. 01What does the calibration output actually give you?
  2. 02How do you convert the quaternion into a rotation matrix?
  3. 03How do you assemble R and t into one 4x4 matrix?
  4. 04How do you turn a detected XYZ into a reachable robot pose?
  5. 05Does the multiply order change the answer?
  6. 06Why do my points land rotated 90 degrees?
  7. 07How do you check the result before the first live pick?
  8. 08Questions

You ran the calibration. It printed a quaternion and three numbers, your detector prints an XYZ point, and the robot still wants a pose in its base frame before it will move. The conversion is fixed arithmetic: R11 = 1 - 2(qy² + qz²) and its eight siblings turn qw, qx, qy, qz into the 3x3 rotation block, you drop the translation into the fourth column, and p_base = T_base_tool0 · T_tool_cam · p_cam lands your detection in base coordinates. Worked here on one real eye-in-hand result — qw = 0.67295, qz = 0.73846, about 95° about Z with a 0.103 m offset, and a detected point at (0.45, -0.12, 0.35) m that comes out at (0.400, 0.206, 0.810) m in base — so you can validate your own code line by line.

What does the calibration output actually give you?

Two things: a unit quaternion (qw, qx, qy, qz) that encodes the rotation, and a translation (x, y, z) in metres. Together they describe one rigid pose — for eye-in-hand, the camera frame expressed in the tool frame. They are not yet a matrix you can multiply with.

transformation:
  qw:  0.67295
  qx: -0.03935
  qy:  0.01642
  qz:  0.73846
  x:   0.03442
  y:  -0.07885
  z:  -0.05604

The four components are unit length: 0.67295² + 0.03935² + 0.01642² + 0.73846² = 0.45286 + 0.00155 + 0.00027 + 0.54532 = 1.00000. That norm is the first thing to confirm, because every conversion formula assumes it. The translation prints in the unit your frames use — metres here. The rotation decodes to θ = 2·acos(qw) = 2·acos(0.67295) ≈ 95° about an axis within about 3° of Z, plus a 0.103 m offset. A near-90° turn about Z with a 10 cm lever is the classic result for a camera bolted to a flange and aimed at the work, so this sample is a good template for checking your own output.

How do you convert the quaternion into a rotation matrix?

Four multiplications and one subtraction per element: every entry is a short combination of squared and cross-multiplied quaternion terms, and the full 3x3 needs nine such lines — no trigonometry, no Euler intermediate, no gimbal-lock ambiguity on the way.

R11 = 1 - 2(qy² + qz²) R12 = 2(qx·qy - qw·qz) R13 = 2(qx·qz + qw·qy) R21 = 2(qx·qy + qw·qz) R22 = 1 - 2(qx² + qz²) R23 = 2(qy·qz - qw·qx) R31 = 2(qx·qz - qw·qy) R32 = 2(qy·qz + qw·qx) R33 = 1 - 2(qx² + qy²)

Worked on the sample: qw·qz = 0.67295 × 0.73846 = 0.49695 and qx·qy = -0.03935 × 0.01642 = -0.00065, so R21 = 2(-0.00065 + 0.49695) = 0.99260 and R12 = 2(-0.00065 - 0.49695) = -0.99520. The diagonal uses the squares: R11 = 1 - 2(0.01642² + 0.73846²) = 1 - 2(0.00027 + 0.54532) = -0.09119. Completing all nine entries gives:

R = [ -0.0912 -0.9952 -0.0360 ] [ 0.9926 -0.0937 0.0772 ] [ -0.0802 -0.0287 0.9964 ]

Three properties confirm the arithmetic: each column has length 1.000, the columns are mutually perpendicular, and the determinant is +1.000. A determinant of -1 means one sign was transcribed and you built a mirror instead of a rotation. If a log file rounds the quaternion to a norm of 0.999 or 1.001, divide all four components by the norm first — the formulas assume a unit quaternion. These are the Hamilton-convention formulas, the same ones your calibration library and robotics references use.

How do you assemble R and t into one 4x4 matrix?

Copy R into the top-left 3x3 block, t into the top-right column, and close with the row 0, 0, 0, 1. Vision libraries and robot controllers then treat the result as one object you can multiply, invert and store.

[ -0.0912 -0.9952 -0.0360 0.03442 ] T_tool_cam = [ 0.9926 -0.0937 0.0772 -0.07885 ] [ -0.0802 -0.0287 0.9964 -0.05604 ] [ 0 0 0 1 ]

Read the subscript as a direction: T_tool_cam maps a point from the camera frame into the tool frame. The inverse maps back, and for a rigid transform it is direct — transpose R and replace t with -Rᵀt, no generic matrix inversion needed. Compute the matrix once and store it. Publishing a hand-eye calibration as a static camera transform shows the set-up that keeps it alive across restarts. The formulas here assume column vectors, as ROS, Eigen and most vision SDKs do; if your library composes row vectors from the left, transpose R and move t into the bottom row.

How do you turn a detected XYZ into a reachable robot pose?

Multiply the homogeneous point (x, y, z, 1) by T_tool_cam to land in the tool frame, then by the flange pose the controller reported at image time to land in base. The result is a pick position the planner already understands.

Where the camera-frame pose itself comes from at runtime is its own topic — object pose in camera frame at runtime after hand-eye calibration covers that step. From there the chain is five moves:

  1. Capture together: the detection point, p_cam = (0.45, -0.12, 0.35) m in this example, and the flange pose T_base_tool0 from the same trigger instant.
  2. Multiply by the hand-eye matrix — append the 1 and carry out three dot products:
x = (-0.0912)(0.45) + (-0.9952)(-0.12) + (-0.0360)(0.35) + 0.03442 = 0.100 y = ( 0.9926)(0.45) + (-0.0937)(-0.12) + ( 0.0772)(0.35) - 0.07885 = 0.406 z = (-0.0802)(0.45) + (-0.0287)(-0.12) + ( 0.9964)(0.35) - 0.05604 = 0.260

So p_tool = (0.100, 0.406, 0.260) m. The 0.45 m forward component lands almost entirely on the tool-frame y axis — that is the ~95° rotation doing its work, and it is why eyeballing axes without the matrix fails.

  1. Chain into base: p_base = T_base_tool0 · p_tool. For a quick numeric example, if the flange sat at (0.30, -0.20, 0.55) with zero rotation at capture, the pick is (0.400, 0.206, 0.810) m in base coordinates.
  2. Build the approach orientation the same way: R_base = R_base_tool0 · R_tool_cam, or convert the final matrix to the Euler order your controller's target expects. Deriving hand-eye angles from a rotation matrix walks that conversion.
  3. Command the move and check it against reality before trusting the chain at production speed; the checks are in the next section.

Because the part is fixed, its base-frame coordinates do not change between frames. Capture the flange pose with every image, transform once, and the pick stays put even though the camera travels with the arm. The full job — pixel to camera point to base pose — is the same chain worked end to end in this pixel-to-robot coordinate transform walkthrough.

Does the multiply order change the answer?

Yes — with column vectors, transforms apply right to left. The correct eye-in-hand chain is p_base = T_base_tool0 · T_tool_cam · p_cam, and swapping the two matrices silently moves the same point by tens of centimetres.

The naming convention keeps the order readable: T_A_B always maps from frame B into frame A. Write the chain as a sentence — camera into tool, tool into base — and multiply in the reverse of that reading. An error here does not raise an exception. The robot reaches a consistent-looking wrong position, which is the most expensive way to find a bug.

A fixed camera collapses the chain to one multiplication, p_base = T_base_cam · p_cam, because the solver returns the camera pose in base directly. Using eye-to-hand calibration results to compute robot-base picks works that case through. When your input is still pixels rather than metres, eye-in-hand pixel to world coordinates: chain the transforms covers the steps in front of this one.

Why do my points land rotated 90 degrees?

Almost always a frame-convention mismatch, not a bad calibration. The transform is valid between the two frames it was computed for, and your point cloud is expressed in a different one — typically an optical frame whose axes are permuted relative to the camera body frame.

The sample calibration tracked camera_link, the body frame with x forward. Depth clouds usually arrive in a frame like camera_depth_optical_frame or camera_rgb_optical_frame, arranged z forward, x right, y down. Feed an optical-frame point through a matrix built for the body frame and the coordinates swap axes: distances survive, directions turn 90°. Express the detection in the frame the calibration names first — the two frames are tied by a fixed rotation you look up once — and then apply T_tool_cam. Point cloud rotated 90 degrees after hand-eye calibration dissects that failure, and fiducial pose wrong distance: check calibration and frames covers the distance-off variant. If distances are wrong too, the calibration itself is suspect — a target printed on paper and taped to the flange flexes enough to corrupt every sample.

How do you check the result before the first live pick?

Run three checks: the quaternion norm must be 1.000 within rounding, the matrix determinant must be +1.000 with orthonormal columns, and a computed point must land within a few millimetres of where the tool physically measures the same feature.

The first two take seconds in code. The third is the real test: jog the tool tip to the calibration target's corner, record the base coordinate the controller reports, and compare it with p_base computed from the camera's detection of the same corner. Repeat-touch agreement of 1-3 mm is a healthy result with a rigid target and well-spread poses. Errors of 10-20 mm are common enough, and they usually trace to pose spread or target quality, not your matrix arithmetic. Hand-eye calibration accuracy verification: test before you trust it gives the fuller procedure, and hand-eye calibration translation error: is 1-2 cm normal? sets expectations for that second band.

The whole chain rests on the camera reporting metric XYZ in a named frame; structured-light and stereo 3D cameras do exactly that. The 3D cameras category lists them, and the two below are the class to start from for close-range picking.

3D sensorsHikrobot MV-DPS200P-02 Projected structured-light 3D cameraMV-DPS200P-02
Type
Stereo / structured light
$8,829.00 · quotedView and quote
3D sensorsHikrobot MV-DLS300P-04 Laser galvanometer stereo cameraMV-DLS300P-04
Type
Stereo / structured light
$16,579.00 · quotedView and quote

If you are still selecting hardware, the 3D camera finder filters on depth range and field of view first, and a plain area scan camera needs a depth source paired with it before any of this applies. When the numbers above refuse to reconcile, send your calibration output and one detection to us through the contact page and we will trace the chain with you.

Build the whole station

The configurator matches a camera, lens, light, light controller and cables to each other, step by step, and lists only parts that fit what you already picked.

Start with the camera

Questions

What units is the hand-eye translation in?

In whatever unit your frames use. A ROS-style setup works in metres, and the sample calibration above declared its marker as 0.14 m, so the translation came out in metres. If your pipeline measures in millimetres, multiply x, y and z by 1000 and leave the quaternion untouched, because rotation is dimensionless. Confirm the unit by transforming the corner of an object you have measured with a rule.

Do I still need Euler angles for the robot controller?

Only for the target orientation, and only if your controller's target format demands them. Convert the final rotation matrix to the Euler convention your controller actually uses; the order matters, and the wrong order puts the tool in a different attitude even though the same three angles are printed. Position never needs Euler angles, because the 4x4 handles it.

Why did my calibration print a negative qw?

A quaternion and its negation describe the same rotation, so one solver prints qw = -0.673 where another prints +0.673, and both produce the same matrix after conversion. If you prefer a positive scalar part, negate all four components together. Never flip a single component on its own, because that changes the rotation to a different one.

Does anything change when the camera is bolted to the table instead of the flange?

The matrix math is identical, but the solver now returns the camera pose in the base frame, so you skip the flange pose entirely and multiply every detection by one fixed transform. Re-run the calibration only after the camera or the robot is remounted; the result does not drift with robot motion between runs.

How many poses does a hand-eye calibration need?

Plan on 15 to 25 robot poses with the marker seen from clearly different positions and tilts. Two or three views cannot constrain the rotation, and the solver returns a plausible-looking but poor fit. Spreading the poses matters more than adding more of them, and no pose count fixes a target that flexes.