Skip to content
PreciSim
Back to the blog
2 min read

The minus sign in hand-eye calibration

Anyone who has trained a junior engineer has seen this: calibration completes, the residual is reasonable, then the machine moves and X tracks while Y drifts further and further away.

Eight times out of ten it is a sign.

Where the conflict comes from

Two conventions coexist, and neither can be changed:

  • Image frame: origin top-left, u right, v down. Every camera SDK does this.
  • World frame: right-handed, X right, Y away from the operator, Z up.

v down, Y away. Those directions are opposite.

So the matrix must contain a mirror

The 2D similarity transform from image to world:

[x_W]   [ s·cosθ   s·sinθ ] [ u ]   [tx]
[y_W] = [ s·sinθ  -s·cosθ ] [ v ] + [ty]
                    ↑

That is not an ordinary rotation matrix — a rotation would be [cos -sin; sin cos]. It is a rotation composed with a reflection, and the reflection comes from the flipped v axis.

Why omitting it raises no error

This is the insidious part.

Use [cos -sin; sin cos] and least squares still converges. It finds parameters with a plausible residual, because for a given set of poses "rotate by 180° − θ" and "mirror then rotate by θ" can compensate for each other at those particular points.

The calibration report looks fine. Then the machine runs and Y is inverted.

How to catch it in a minute

Do not re-derive the algebra. Run an experiment:

  1. Move X by +10 mm;
  2. Note how far and in which direction the marker moved in the image;
  3. Convert that pixel displacement back to mm with your transform.

It should read +10, 0. If it reads -10, 0 or 0, +10, you now know whether the problem is a sign or an axis mapping.

A minute on the virtual rig. Half an hour on real hardware, with a crash risk attached.

While we are here: configuration

Hand-eye calibration has two configurations:

  • eye-in-hand (camera on the moving member) → solve T_Tool_Cam;
  • eye-to-hand (camera fixed to the frame) → solve T_World_Cam.

Choosing wrong also raises no error and also produces a small residual with a wrong answer. On a virtual device the truth reveals it immediately; on real hardware only a closed-loop check will.

One defensive rule

Every coordinate-bearing variable carries its frame:

var pTargetW  = ...;   // world, mm
var pTargetI  = ...;   // image, px
var tWorldCam = ...;
var pW = tWorldCam * pI;   // subscripts meet in the middle — visibly correct

It reads as fussy. It also turns "a sign got flipped" from a machine crash into a code review comment.

Further reading