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:
- Move X by
+10 mm; - Note how far and in which direction the marker moved in the image;
- 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 correctIt reads as fussy. It also turns "a sign got flipped" from a machine crash into a code review comment.
Further reading
- Coordinate frames and sign conventions
- Lab 2 — produce the "wrong configuration, small residual" illusion yourself