You calibrate the camera to the robot, publish the transform, then park the flange on a known point and read two sets of numbers: (Xc, Yc, Zc) from the camera and (Xr, Yr, Zr) from the teach pendant. They do not match, and the first instinct is that the calibration failed or the tool was built for a different mounting. It did not fail. This guide explains what a hand-eye calibration computes, why the two coordinate systems stay numerically different by design, how to convert a camera point into robot base coordinates with the arithmetic shown, and which checks prove the result before the robot moves at speed.
Why are camera coordinates still different from robot coordinates after calibration?
It is expected, and it is not a fault: calibration computes the fixed transform between two coordinate systems and never merges them, so the same point reads (150.0, 60.0, 420.0) mm in the camera frame and (1000.0, -180.0, 520.0) mm in the robot base frame.
The two frames disagree on origin and on axis directions. A camera measures from its own sensor along its optical axis; the pendant reports from the centre of the robot base, with z pointing up into the workspace. Both readings are correct, and a correct calibration changes neither one.
What calibration adds is the conversion between the two, so the numbers you compared are related but never equal. If a parked point ever prints identical values on both sides without your code transforming anything, someone upstream has already applied the transform. Check which frame your subscription claims before debugging the calibration.
What does a hand-eye calibration actually compute?
It computes one rigid transform — a 3×3 rotation R and a 3×1 translation t — between the camera frame and the robot base frame in an eye-to-hand set-up, six numbers that stay valid for every point because neither frame moves relative to the other.
The solver builds those six numbers from samples: the robot reports where its flange was, the tracking driver reports where the marker was in the camera frame, and the solver finds the rotation and translation that make the two descriptions consistent at every pose. Eye-to-hand (camera fixed on a stand, the usual ROS parameter eye_on_hand set to false) solves camera-to-base; eye-in-hand (camera on the wrist) solves camera-to-flange instead. The inputs are the same robot pose and frame pairs in both cases, and the rotated point cloud after calibration article shows what a convention mismatch does to the rotation part.
R carries the mounting attitude, which is why a camera hung upside down comes out near a 180° flip about the base x-axis. t carries the camera origin's position in base coordinates. The publisher puts the result on the frame tree as one static relationship; it is data you multiply with, not a merge of the two systems, and the pendant keeps reporting base values exactly as before.
Which frame am I reading when the camera reports Xc, Yc, Zc?
Almost always the camera's own frame, and with many depth cameras the values follow the optical convention — z pointing out of the lens, x right, y down — which is not automatically the frame your calibration assumed, so name the frame before you multiply anything.
Every number in the chain belongs to one of five places, and mixing two of them is the most common source of a flipped or mirrored result:
| Frame | Origin sits at | Axes you will meet | Published by |
|---|---|---|---|
| Camera / optical | The camera sensor or model centre | Optical: z out of the lens, x right, y down | The camera SDK |
| Robot base | The centre of the base mount | z up, x and y in the mounting plane | The robot controller |
| Flange / tool | The end-effector interface | z out of the face plate | The robot controller |
| Marker | The printed marker centre | In the marker plane | The tracking driver |
| base ↔ camera | — | — | The calibration result |
Many depth cameras emit clouds in the optical convention while the calibration frame is the camera frame, and the two differ by a fixed rotation. Apply that rotation first, or every converted point inherits the mismatch. The optical-frame to camera-frame transform guide shows the conversion, and the camera-frame versus point-cloud axes guide explains why the axes differ at all.
The convention is sensor-wide: a structured-light camera, a time-of-flight unit and a laser profiler all report in their own frame, and none of them knows where your robot stands. The frame work happens in your transform code, not in the sensor.
- Type
- Stereo / structured light
- Type
- Stereo / structured light
- Type
- Laser profiler
How do I convert a camera point to robot base coordinates?
Rotate first, then translate: with a 180° about-x rotation and t = (850.0, -120.0, 940.0) mm, the camera reading (150.0, 60.0, 420.0) mm becomes the base command (1000.0, -180.0, 520.0) mm.
Worked example, end to end. The cell: a camera hung upside down from a beam over the workspace, calibrated eye-to-hand. The solver returned a rotation of 180° about the base x-axis (the camera points straight down) and t = (850.0, -120.0, 940.0) mm, the camera origin in base coordinates. The camera reports a part corner at p_cam = (150.0, 60.0, 420.0) mm; z is positive because the inverted camera looks down its own axis toward the part.
For a 180° turn about x, R = diag(1, -1, -1): x passes through, y and z flip sign.
- x: 1 × 150.0 mm = 150.0 mm, then 150.0 mm + 850.0 mm = 1000.0 mm
- y: -1 × 60.0 mm = -60.0 mm, then -60.0 mm + (-120.0 mm) = -180.0 mm
- z: -1 × 420.0 mm = -420.0 mm, then -420.0 mm + 940.0 mm = 520.0 mm
So p_base = (1000.0, -180.0, 520.0) mm. The corner sits 520 mm above the table under a lens at 940 mm, which is 420 mm below it: the geometry closes. Not one component equals the camera reading — that is the transform doing work, exactly as intended. Command the robot to x = 1000.0 mm, y = -180.0 mm, z = 520.0 mm in its base frame.
Order matters. Adding t before rotating measures from the wrong origin, and multiplying with the inverse transform (base-to-camera instead of camera-to-base) lands points mirrored about the cell. The robot-base pick computation guide extends the same multiplication to a full pick list, and the image-to-robot coordinates guide covers the 2D camera over a known plane. The same two lines worked end to end for a six-axis arm are in a pixel-to-robot-base transform walkthrough.
How do I check that the calibration is correct?
Watch the vector from flange to marker while the robot moves: in a correct calibration that end-effector-to-marker transform stays constant to within a few tenths of a millimetre — for a ±0.5 mm pick process, aim for repeat scatter near ±0.2 mm, about a third of the process tolerance.
Comparing camera values with pendant values proves nothing; they differ by design, as the first section showed. The evidence is the flange-to-marker vector, built from the robot's own pose on one side and the tracking driver's camera-to-marker measurement on the other. Hand-eye tools ship a check for exactly this: in the ROS tooling it is a provided launch file (check_calibration.launch) that draws the robot links, the base-to-camera result and the camera-to-marker measurement in one TF view. If the marker stays glued to the flange through the whole motion, the constellation is right; if it drifts, re-collect with more rotation variety before blaming the solver.
Put numbers on the pass mark. Three poses, marker position expressed in the flange frame:
- Pose A, robot 400 mm left: (12.1, 4.8, 158.3) mm
- Pose B, robot 400 mm right: (12.2, 4.6, 158.1) mm
- Deviation d = √(0.1² + 0.2² + 0.2²) mm = √0.09 mm = 0.30 mm — a pass for the ±0.5 mm process
- Pose C at (12.4, 5.2, 158.8) mm gives √(0.3² + 0.4² + 0.5²) mm ≈ 0.71 mm — check the marker mount before blaming the solver
The eye-to-hand verification walkthrough runs this check against the camera-to-base result step by step.
Can I calibrate a camera that is mounted inverted on a stand?
Yes — inverted is a normal eye-to-hand mounting: with eye_on_hand set to false the calibration needs only a camera that never moves relative to the robot base, and the upside-down pose simply appears as a near-180° rotation in R.
Three conditions matter more than the pose itself. The mount must be rigid: a cantilevered stand that flexes under cell vibration changes t after the fact and shifts every converted point by the same millimetres. The tracking driver must see the marker across the whole workspace, including the poses with the flange rotated well away from level. And after calibration the camera is not to be touched: a bump moves t one-to-one into every result. The eye-to-hand set-up for a camera above the robot base covers the mounting geometry, and for a fast re-check after a disturbance the three-point-pair depth camera calibration is quicker than a full re-run.
When do camera and robot coordinates match exactly?
Only in two cases: someone deliberately aligned the two frames, for example by defining a robot base or tool frame equal to the calibrated camera pose, or your code has already multiplied through the transform — a fresh calibration never makes the raw numbers equal by itself.
The deliberate cases are worth knowing, because they look like success. Some cells define a base or tool frame at the camera pose so jog commands feel camera-like to the operator; the pendant then prints numbers that match what the camera sees, while the camera's own output is unchanged. Some stacks re-express detections in base coordinates before publishing, so the camera numbers you read are already transformed. In both cases the underlying frames stay distinct. Check the frame label on the topic before concluding that a calibration merged anything, and if a viewer shows the cloud somewhere other than you assumed, the fixed-frame diagnosis for rviz covers that failure.
What should I check before the first robot move?
Verify the constant marker vector, touch one converted point in reduced-speed manual mode, and budget the errors: a 0.1° rotation error already moves a point 600 mm down the axis by about 1.0 mm, and every millimetre of camera-stand movement passes 1:1 into every converted point.
The rotation residual dominates at working distance; the translation passes straight through. For ±0.5 mm picks, keep the vector scatter near ±0.2 mm and treat the camera mount as immovable after calibration. Run this list once per cell, and again after any collision or re-mount:
- Flange-to-marker vector repeats within your scatter budget across the workspace, at five or more poses with rotation variety.
- One known corner converts and touches correctly, in reduced-speed manual mode with the enabling device held.
- Both sides of the multiplication use the same frame convention; optical-frame cloud data received its rotation first.
- The camera has not moved since calibration; t re-solved or re-measured after any bump.
- The TF view shows robot links, base-to-camera and camera-to-marker consistent in one picture.
Once the frames are proven, the sensor is the remaining choice. For seam, gap and height work along a line, a laser profiler is the fixed camera in exactly this eye-to-hand chain.
- Type
- Laser profiler
The 3D sensors range spans laser profilers, stereo and structured-light cameras and ToF units, and the camera finder filters them by type and resolution. More frame and calibration guides sit on the systems topic page, and if you want a second pair of eyes on your frames and residuals before the first run, contact the team — expert support comes with every order.
Questions
Which transform should stay constant during verification: flange to marker or base to camera?
The flange-to-marker vector is the one to watch. Base-to-camera is the calibration result itself, so it is constant by definition once published and cannot tell you anything. The camera-to-marker measurement, compared against the robot's own flange pose, is the independent evidence: when that difference holds still across the workspace, the whole frame constellation is consistent.
Do I need to recalibrate after the camera stand gets bumped?
Yes. The translation part passes straight through: a 3 mm bump moves every converted base point 3 mm. Rotation is worse with distance, so a small tilt costs roughly 0.9 mm per 0.1° at a 500 mm working distance. Re-run the calibration, or at minimum re-touch one converted known point before running.
How many pose pairs does a hand-eye calibration need?
Collect 10 to 20 pose pairs spread over the workspace. Fewer than about 5 leaves the rotation poorly conditioned, and beyond roughly 20 the residual stops improving. Varying the flange orientation matters more than the count: poses that differ only in position cannot pin down the camera rotation, which dominates converted-point error.
Can I transform raw pixel coordinates straight into robot coordinates?
Not directly. A pixel gives a direction, not a point: you need the depth value at that pixel to get a 3D position in the camera frame, and only then does the calibrated rotation and translation carry it into base coordinates. For a 2D camera over a known plane, a plane intersection replaces the depth value, and the same transform finishes the job.
Does any of this change for eye-in-hand, with the camera riding on the wrist?
The frame pair changes, not the logic. Eye-in-hand solves for the flange-to-camera transform while the marker sits fixed in the cell, so the constant to watch becomes the marker's pose in the base frame: it must hold still as the robot moves. Conversion follows the same rotate-then-translate rule with the camera-to-flange chain included.


