3D visionHow-to

Eye-in-hand pixel to world coordinates: chain the transforms

Convert an eye-in-hand pixel to world coordinates: unproject with depth, apply the hand-eye transform, then the flange pose, with a worked example.

LumaSpec Vision11 min read Share on LinkedIn

On this page 9 sections
  1. 01What is the transform chain from one pixel to a base-coordinate point?
  2. 02How do you build the pipeline step by step?
  3. 03How do you turn a depth pixel into a camera-frame point?
  4. 04How do the transforms multiply in a worked example?
  5. 05Why must the flange pose be re-read at every capture?
  6. 06What accuracy should you plan around?
  7. 07What hardware carries the eye-in-hand pipeline?
  8. 08How do you verify the chain before the first pick?
  9. 09Questions

Your camera rides the robot flange, so the same pixel maps to a different base coordinate every time the arm moves. That is the whole difficulty of eye-in-hand guidance, and it collapses into a fixed chain of three transforms with one moving part: the flange pose you read at each capture. If you are wiring a depth camera on a robot arm to vision-guided motion, this walkthrough gets you from any pixel to a base coordinate you can command — the unprojection math, the hand-eye link, the pose read, a worked example you can check by hand, the accuracy to budget, and the checks that catch a wrong chain before the first crash.

What is the transform chain from one pixel to a base-coordinate point?

Three transforms in fixed order: depth plus intrinsics unproject the pixel to a camera-frame point, the hand-eye transform moves that point onto the tool flange, and the flange pose reported by the controller carries it into base coordinates.

Name the frames before the math. The camera frame sits at the depth sensor's optical center with z pointing out of the lens. The flange frame sits at the tool mounting face with z along the tool axis. The base frame is the robot's own coordinate system — the world your controller reports positions in. Defining that world frame is the first integration decision, and everything downstream inherits it.

The chain multiplies three transforms:

P_base = T_base_flange · T_flange_camera · P_camera

T_flange_camera is your hand-eye calibration result, and it never changes until hardware is remounted. T_base_flange is the flange pose the controller reports, and it is different at every robot position. If the 4x4 convention itself is unfamiliar, the homogeneous transformation matrix walkthrough covers the multiplication order used below.

This is also what separates the two mounting styles. An eye-to-hand camera bolted to a stand needs one camera-to-base calibration that then behaves as a static transform — the static eye-to-hand transform workflow shows how little changes per cycle there. Eye-in-hand keeps only the camera-to-flange link fixed and pays for the moving viewpoint with a pose read every capture.

How do you build the pipeline step by step?

Six steps: mount and calibrate once, then for every capture read the depth at the pixel, unproject to the camera frame, apply the hand-eye transform, read the flange pose at the same instant, and multiply the chain.

  1. Mount the camera on a rigid bracket at the flange. Flex or backlash between camera and flange shows up later as pick scatter that no software fix removes.
  2. Calibrate hand-eye once. Park the flange over a calibration target at several positions and orientations, capture, and solve for the single 4x4 camera-to-flange transform. The robot-mounted capture-and-solve procedure walks the capture, and the calibration pose selection guide explains why the poses must vary in rotation, not just position. Re-run it after any remount, any change that moves the camera, or any knock.
  3. Pull the depth intrinsics fx, fy, cx, cy from the camera SDK, at the resolution you actually stream.
  4. Per capture, read depth at the target pixel and unproject (next section). If your SDK publishes a point cloud, the unprojection is already done — just confirm which frame the cloud uses, a mix-up the optical frame versus camera frame check untangles.
  5. Read the flange pose — position plus rotation, in base frame — sampled at the same instant as the image.
  6. Multiply the chain and command the move. Only the hand-eye result survives from cycle to cycle; pixel, depth and pose all refresh.

How do you turn a depth pixel into a camera-frame point?

Multiply the pixel's offset from the image center by the depth and divide by the focal length in pixels: at Z = 350 mm with fx = 600 px, a pixel 40 rows below center sits 40 × 350 / 600 = 23 mm off axis.

The pinhole model needs only the depth at that pixel and the intrinsics:

x_c = (u - cx) × Z / fx y_c = (v - cy) × Z / fy z_c = Z

(u, v) is the pixel, (cx, cy) the principal point — in practice the image center — and fx, fy are focal lengths in pixels. Depth streams come rectified, so no distortion step follows.

The same relation sets your lateral scale: one pixel spans Z / fx at the target. At Z = 350 mm with fx = 600 px that is 0.58 mm per pixel, so a 1280-pixel width covers about 747 mm at that depth. The calculator below pairs working distance, pixel count and focal length when you size the camera:

Camera resolution for the smallest feature
Units
Pixels needed
–
Sensor size
– MP
Smallest sensor that fits
–
Pixel on the part
– mm
Pixels across the feature, that sensor
–

Pixels needed = field / feature × pixels per feature on each axis (field incl. margin). The lens must resolve the pixels too: Nyquist = 1000 / (2 × pixel µm) lp/mm.

Find cameras with enough pixels Open the full calculator

Reading one pixel's depth, rather than a full cloud, is a technique in its own right; the fixed-image-point depth read uses it to find a table or part surface under the lens.

How do the transforms multiply in a worked example?

Pixel (640, 400) at 0.350 m depth, a 180° bracket twist with 30 mm and 75 mm offsets, and a flange pose of (850, 120, 500) mm yawed 90° land the point at (873, 150, 925) mm in base coordinates — every number below checks by hand.

Camera: 1280 × 720 depth image, fx = fy = 600 px, cx = 640, cy = 360. Target pixel (640, 400), depth Z = 0.350 m. Work in metres.

Step 1 — unproject.

x_c = (640 - 640) × 0.350 / 600 = 0.000 m y_c = (400 - 360) × 0.350 / 600 = 0.023 m z_c = 0.350 m

Step 2 — apply the hand-eye transform. Say the bracket twists the camera 180° about z and offsets it 30 mm in flange x, 75 mm toward the work, so R_h = diag(−1, −1, 1) and t_h = (0.030, 0.000, 0.075) m:

x_f = -0.000 + 0.030 = 0.030 m y_f = -0.023 + 0.000 = -0.023 m z_f = 0.350 + 0.075 = 0.425 m

Step 3 — apply the flange pose. The controller reports the flange at (0.850, 0.120, 0.500) m, yawed 90° about base z. Rz(90°) sends a flange x to base y and a flange y to base −x:

x_b = 0 × 0.030 - 1 × (-0.023) + 0.850 = 0.873 m y_b = 1 × 0.030 + 0 × (-0.023) + 0.120 = 0.150 m z_b = 0.425 + 0.500 = 0.925 m

The pixel lands at (873, 150, 925) mm in base coordinates. Now put the same scene behind a flange yawed to −90°: steps 1 and 2 return identical numbers, but step 3's rotation flips sign, and a pixel that unprojects identically in the camera frame lands at a different base point. That asymmetry is why step 3 has no memory — the pose belongs to this capture only.

Why must the flange pose be re-read at every capture?

The camera is part of the kinematic chain, so its pose in base coordinates changes with every joint move; a flange pose read 50 ms out of sync at 250 mm/s travel puts every converted point about 12 mm off.

A fixed camera earns its simplicity from one permanent rotation. For a moving camera, orientation in base coordinates is a function of all six joints, so no single rotation matrix survives a repositioning. The remedy is timing, not more calibration. Stop-and-shoot is the dependable default: command in-position, wait 50–100 ms for settle, capture, read the pose, compute. Capturing on the fly needs a pose sampled at exposure time, which only some controllers expose — until yours does, stop and shoot.

Controller conventions deserve one check: some flange-position readouts return world coordinates even when the active tool frame suggests otherwise, and note on flange positions returned in world coordinates instead of tool-frame values shows the symptom and the fix. If your program consumes targets in a user frame or work object rather than base coordinates, the camera point to work object conversion is the hop after the chain. The eye-to-hand counterpart computes base targets from a transform that never changes — the eye-to-hand base computation workflow shows that version.

What accuracy should you plan around?

Budget about 1 mm total position error at 350 mm working distance and about 7 mm at 1 m — depth error scales with distance squared — while lateral error stays near 0.6 mm per pixel at 350 mm.

Depth dominates, and for a stereo depth camera with focal length fx, baseline b and disparity noise σ_d it follows:

σ_Z ≈ Z² × σ_d / (fx × b)

Take a representative close-range sensor: fx = 600 px, b = 50 mm, σ_d = 0.2 px on a textured scene.

σ_Z(0.350 m) = 0.350² × 0.2 / (600 × 0.050) ≈ 0.0008 m ≈ 0.8 mm σ_Z(1.000 m) = 1.000² × 0.2 / (600 × 0.050) ≈ 0.0067 m ≈ 6.7 mm

A structured-light projector lowers σ_d on untextured or shiny parts, pulling both numbers down; it does not change the Z² growth. On top of depth error add the hand-eye residual — a fraction of a millimetre with a good target and an arm that repeats to 0.02–0.1 mm — and the robot's own repeatability. The budget at 350 mm is therefore about 1 mm, and that sets the pick tolerance: tight enough for rigid grasping with compliance, and the reason long-reach picks at 1 m want a second, closer look. The 3D camera working distance guide covers how range and resolution trade against each other when you position the sensor.

What hardware carries the eye-in-hand pipeline?

The set-up is a depth sensor light enough for the flange, one Ethernet drop down the arm, and a vision controller with at least two spare GigE ports — one for the camera, one for the robot or PLC.

For the sensor, pick the smallest depth source that resolves your feature at the working distance. Structured light handles untextured or shiny targets because the projector paints in the texture stereo matching needs; a ToF sensing camera such as the MV-DT01WMU trades detail for speed when bin-finding or coarse pose is the job. Both families live in the 3D sensors range, and the 3D camera finder filters them by type and depth range. A projected structured-light camera like the MV-DPS200P-02 is the close-range workhorse of the two.

3D sensorsHikrobot MV-DPS200P-02 Projected structured-light 3D cameraMV-DPS200P-02
Type
Stereo / structured light
$8,829.00 · quotedView and quote

On the compute side, the controller needs one port for the camera, one for the robot or PLC, and headroom for a second camera or a photometric light later. PoE camera ports put power and image data down the same cable that runs down the arm — check the PoE row in the table below. The vision controllers range runs from compact fanless boxes to GPU inference terminals; both of these fit the eye-in-hand pattern and expose built-in light controller channels:

Specification MV-VC3301H-128G66 MV-VC5525-256G70-NN
CPU Intel® Core i3-8100 Intel® Core i5-12400
Memory 8 GB 32 GB
Storage 128G SSD 256 GB SSD
GPU Intel® HD Graphics 630 Intel® UHD Graphics 730
Operating system Windows 10 64-bit, Not activated Unactivated Windows 10
Video output HDMI interface × 1, VGA interface × 1; Supports independent display output at the same time; Max. resolution 1920 × 1080 @ 60 Hz HDMI interface × 1, VGA interface × 1; Supports independent display output at the same time; Max. resolution 1920 × 1080 @ 60 Hz
Digital I/O 8 digital isolated inputs supported; 16 opto-isolated outputs supported (NPN/PNP switchable) Opto-isolated input × 8; Opto-isolated output × 12 ( supports NPN/PNP switching), digital-isolated low-side driver output × 8; Non-isolated bi-directional differential I/O × 4
Network interface 6× Gigabit Ethernet ports GigE interface × 7, including interface for PoE × 4
USB Interface USB 2.0 × 4; USB 3.0 × 4; Built-in USB 2.0 × 1 USB 2.0 × 4, USB 3.0 × 4, built-in USB 2.0 × 1
Serial Port 2× full-function RS-232, RS-485/RS-422 switchable RS-232 × 2 by default, supports switching to RS-485 and RS-422
Light interface 4-channel 24 V constant-voltage light drive, Max. power per channel: 72 W, Max. power (4 channels): 150 W 24 VDC constant voltage × 8; (Max. power consumption of single channel is 72 W, total max. power consumption 190 W)
Extended interface 1× PCIe 3.0 x16 slot SATA 3.0 interface × 2, M.2 NVME interface × 1; PCIe × 16 extended interface × 1, PCIe × 8 extended interface × 1, PCIe × 4 extended interface × 2; (When the PCIe × 8 extended interface is used, the PCIe × 16 will only provide half of the bandwidth.)
Price $2,419.00 $4,479.00 · quoted

All 2 share: Humidity 20% RH to 95% RH (no condensation); Certification CE, KC. Values as printed in each product's data sheet; open a part for its drawing and full specifications.

How do you verify the chain before the first pick?

View one fixed target from three flange orientations and recompute each time: a correct chain returns the same base coordinates within about 1 mm at 350 mm working distance, while a stale pose or a wrong rotation scatters it by millimetres to centimetres.

Run these checks on a bench target before anything moves under the robot:

  • Three-orientation repeat. One target, three flange yaws spread over 90°, recompute each time. A spread within about 1 mm at 350 mm working distance says the chain is consistent.
  • Gauge-block depth. Set a 25.0 mm gauge block on the table and read the depth jump between table and block top; it should return 25 mm within the depth error from the accuracy section.
  • Touch-off. Command the computed point with a pointed tip over a shim stack and record the residual and its direction.
  • Read the residual pattern. A constant offset in one direction points to a hand-eye or convention error — left-handed image y, row-major versus column-major matrices, or an optical-frame mix-up. Scatter that grows with robot motion is a pose-timing fault. Scatter that grows with depth is the sensor. The eye-in-hand pick offset diagnosis sorts residuals by cause.

Close with the controller hand-off. Where the program consumes targets through a user frame, one pattern writes the vision-computed point into a frame each cycle — feeding vision world coordinates into a dynamic user frame shows the mechanism — and the 3D camera robot integration checklist sweeps the compatibility questions before power-up.

The decision in one line: unproject with depth, keep the hand-eye result fixed, read the flange pose every capture, and trust the chain only after the three-orientation test passes within about 1 mm. To spec the hardware end to end, size the camera in the 3D camera finder, then run the system configurator over the whole set-up; the neighbouring guides live on the 3D vision topic page, and where a set-up needs a drawn spec, contact the applications team with your working distance and target size.

Questions

Does the hand-eye transform survive a tool or bracket change?

No. Any change that moves the camera relative to the flange — a new bracket, a swapped tool that carries the camera, a knock against the fixture — invalidates it, and the calibration must be re-run. Changing only the robot's TCP or tool-frame definition does not affect the chain, because it uses the flange pose, which the controller reports mechanically regardless of the active tool.

Can I convert pixels with a 2D camera and no depth sensor?

Yes, if every point of interest sits on one known plane. Establish that plane once in camera coordinates (for example, a flat table whose height in the camera frame you measure with a gauge block), unproject each pixel onto the plane with the intrinsics, then chain the same hand-eye transform and flange pose. Points above or below the plane add error roughly proportional to their height difference.

What order do I multiply the matrices in code?

With column vectors and 4x4 homogeneous matrices, P_base = T_base_flange × T_flange_camera × P_camera. Read the chain right to left: the hand-eye result carries the point camera to flange first, the reported flange pose carries it flange to base second. If your library composes transforms instead of multiplying points, the same order applies — apply the camera-to-flange transform before the flange pose.

Why do my converted points drift as the arm reaches further out?

Depth error grows with the square of distance. With a 50 mm baseline and fx = 600 px, a stereo depth camera is about 0.8 mm off at 350 mm and about 6.7 mm at 1 m, so the same pixel unprojects less accurately at range. The lateral scale stretches too: one pixel spans roughly 0.58 mm at 350 mm but about 1.7 mm at 1 m.

Do I need to stop the robot for every capture?

For the simplest pipeline, yes: move, settle, capture, read pose, compute. Capturing on the fly works only if you read the flange pose sampled at the same instant as the image, which some controllers expose through their encoder stream or an exposure-time position record. Without that, at 250 mm/s travel a pose read 50 ms late shifts the point about 12 mm.