3D visionExplainer

Disparity map to depth conversion: bands are not fixed mm

Why equal disparity bands are unequal depth, the Z = f·B/d conversion with a worked example, and how to color true 100 mm depth bands on a disparity map.

LumaSpec Vision9 min read Share on LinkedIn

On this page 8 sections
  1. 01Why is a fixed disparity band not a fixed depth band?
  2. 02What formula converts disparity to depth?
  3. 03How much depth error does one disparity pixel cost?
  4. 04How do you draw depth bands every 100 mm on a disparity map?
  5. 05Which rectified-image pixel does each disparity pixel belong to?
  6. 06Do you need the full point map just to color by depth?
  7. 07Which 3D cameras skip disparity conversion entirely?
  8. 08Questions

A disparity map is not a depth map in millimetres, and the difference bites the moment you try to draw color rings at fixed depth steps — every 100 mm, say — on top of a live stereo view. Equal disparities sit at unequal depths, so rings that look right at 1.2 m drift apart as the part moves away. This guide gives you the conversion formula, worked numbers you can re-run with your own calibration, the pixel mapping back to the rectified image, and the decision between coloring in a shader, thresholding on precomputed edges, or choosing a sensor that outputs depth directly. Stereo 3D camera integrators in inspection and robot guidance can apply every step from the SDK's calibration parameters alone.

Why is a fixed disparity band not a fixed depth band?

Because Z = f·B/d is a hyperbola: at a 1000 px focal length and 120 mm baseline, a 10 px band around d = 100 px covers about 120 mm of depth, while the same band around d = 50 px covers about 485 mm.

Disparity is the pixel shift between the same surface point in the two rectified views. The shift doubles when the distance halves and fades toward zero as the surface recedes, and that inverse curve is the whole story here.

A color cycle keyed to raw disparity therefore measures the scene in disparity steps, not millimetres: ring pairs stay at fixed pixel positions while the depth scale they represent stretches with distance. The map gains its millimetres only when every value passes through the conversion in the next section, and the unit questions behind stereo disparity scale and calibration for depth estimates start exactly there.

What formula converts disparity to depth?

Depth comes from the pinhole stereo relation Z = f·B/d: rectified focal length in pixels times baseline in millimetres, divided by disparity in pixels, gives depth in millimetres along the optical axis.

Z = f · B / d

f is the fx value of the rectified camera's intrinsics, not the lens's millimetre rating; B is the horizontal separation of the two camera centres, in the unit you want out of Z; d is the matched pixel shift. The relation falls out of similar triangles on one matched point pair — depth from a matched stereo point pair: disparity to 3D coordinates works the derivation step by step.

Calibration packs f, B and the principal points into one reprojection matrix Q. Applying it per pixel is exactly what produces the point map: converters such as OpenCV's reprojectImageTo3D do it image-wide, and one SDK names its point-map command ComputePointMap(). Z points along the optical axis, so straight-line range to an off-centre pixel is a different quantity — depth value vs true distance: convert a pixel's Z to camera distance gives that conversion.

How much depth error does one disparity pixel cost?

One pixel of disparity uncertainty costs Z²/(f·B) of depth: 12 mm at Z = 1200 mm and 48 mm at Z = 2400 mm for the 1000 px, 120 mm example, so depth precision degrades with the square of distance.

ΔZ = Z² · Δd / (f · B)

The arithmetic: 1200² / (1000 × 120) = 12 mm, and 2400² / (1000 × 120) = 48 mm. Doubling the stand-off quadruples the quantization.

Subpixel matching softens the steps: at 0.25 px effective resolution the same camera resolves 3 mm at 1.2 m and 12 mm at 2.4 m. Three consequences follow. Far band edges draw ragged, because one disparity pixel crosses half a 100 mm band at 2.4 m. Measured heights pick up a quantization bias that stereo depth height overestimation from disparity quantization quantifies. And matching noise swells into depth noise by the same square law, which is why far disparity maps turn speckled — the troubleshooting checklist for noisy DNN stereo disparity depth walks the checks on the matching side.

How do you draw depth bands every 100 mm on a disparity map?

Convert first, color second: turn every disparity into Z with the reprojection parameters, or precompute the disparity thresholds for each band edge and threshold the raw map — there is no fixed disparity count that equals 100 mm.

Bands in depth become bands in disparity by inverting the same relation:

d_edge = f · B / Z_edge

For the example camera, f = 1000 px and B = 120 mm, the true 100 mm bands shrink to fewer and fewer disparities as Z grows:

Depth band (mm) Disparity at near edge (px) Disparity at far edge (px) Band width (px)
1000–1100 120.0 109.1 10.9
1200–1300 100.0 92.3 7.7
1500–1600 80.0 75.0 5.0
1900–2000 63.2 60.0 3.2
2400–2500 50.0 48.0 2.0

Two implementations, and the choice is easy. Threshold the raw disparity map against edges like the table's: no per-pixel arithmetic at all, just remember disparity falls as depth rises, so the comparisons run backwards. Or convert every pixel to Z first and color on Z: more work, but it gives smooth gradients and survives any later change of band width. What remains wrong is coloring unconverted disparities in uniform steps for a measurement overlay — the rings are honest in pixels only.

If converted Z disagrees with a tape measure, the coloring is not the fault — the camera needs metric calibration, and depth map to real distance calibration for XYZ point clouds walks that fix.

Which rectified-image pixel does each disparity pixel belong to?

The disparity map is a shifted crop of the rectified image: map pixel (x, y) lands on rectified pixel (x + ox, y + oy), with the offset read from the camera's parameters — one SDK calls it DisparityMapOffset and reports the same offset for the point map.

The offset exists because rectification and the disparity search window leave a valid region smaller than the full image. Software handles it two ways: some SDKs crop the map and publish the offset as a camera parameter, others keep the full size and mark the outer pixels invalid. Either way, read the value at run time instead of assuming zero. The overlay recipe is then mechanical: compute the color from Z at map pixel (x, y), write it into the rectified image at (x + ox, y + oy).

Border pixels have no valid disparity at all; why stereo depth images have an invalid band at the edges explains that band. If the overlay still slides after a correct offset, stereo disparity map troubleshooting after rectification lists the rectification-side causes.

Do you need the full point map just to color by depth?

No: the conversion is one multiply and one divide per pixel, so color in a pass over the smaller disparity map — applying Z = f·B/d inline, the way viewer render passes do — and build the full point map only when you need actual XYZ coordinates.

The data sizes explain the saving: a 16-bit disparity map costs about 2 bytes per pixel, while an XYZ point map of 32-bit floats costs 12 — six times the memory and the transfer, for a color index you could have computed in place. Thresholding against a table like the one above skips the arithmetic entirely.

Build the real point map when something downstream needs coordinates anyway: robot guidance, volume checks, CAD comparison. Then color on Z directly, because the point map is already in millimetres and shares the same pixel offset onto the rectified image.

Which 3D cameras skip disparity conversion entirely?

Time-of-flight, projected structured-light and laser profiler sensors deliver depth natively — their depth and point maps are already in millimetres, so band coloring is a direct threshold on Z and the conversion step disappears.

The choice sits upstream of the software. Passive stereo buys high-resolution texture and low latency but leaves the conversion on your side; native-depth sensors trade some texture detail to delete the whole question. The three camera types below cover the common integration cases, each returning 3D data directly instead of a raw disparity map:

3D sensorsHikrobot MV-DT01WMU ToF sensing cameraMV-DT01WMU
Type
Depth / ToF camera
Price on requestView and quote
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

For profile-by-profile measurement on a conveyor, a 3D laser profile sensor outputs height maps in millimetres, so the conversion never reaches your code:

3D sensorsHikrobot MIS-DP3850-01P 3D laser profile sensorMIS-DP3850-01P
Type
Laser profiler
$12,379.00 · quotedView and quote

Whichever family you pick, placement decides accuracy before any coloring does — the 3D camera working distance and positioning guide covers that geometry. To compare native-depth options, start at the 3D sensors category, narrow down in the camera finder, or step through the system configurator; more depth-integration guides sit on the 3D topic page.

Questions

What units should the focal length and baseline use in Z = f·B/d?

Focal length must be in pixels: the fx value of the rectified camera's intrinsics, not the lens rating in millimetres. The baseline sets the output unit — a baseline entered in millimetres returns Z in millimetres, one in metres returns metres. Disparity is always in pixels. An SDK's reprojection matrix already packs focal length, baseline and principal points in consistent units, so prefer it over typing your own constants.

Why do the edges of my colored depth bands look ragged?

Disparity arrives in discrete steps, and each step grows with the square of distance. In the worked example one disparity pixel equals 12 mm of depth at 1.2 m, so a boundary drawn at exactly 1200 mm jumps in 12 mm increments as the scene moves. Subpixel disparity narrows this — 0.25 px steps move the same boundary in 3 mm increments — and light smoothing of the disparity map before thresholding removes the remaining staircase.

Can I read the baseline and focal length from the camera instead of measuring them?

Yes, and you should. The calibration data the SDK exposes for the rectified pair already contains the focal length in pixels (fx) and the baseline as the horizontal component of the left-to-right camera translation. Read both at run time: after a lens swap or a refocus the effective focal length in pixels changes, and hardcoded constants silently skew every depth value.

Does every pixel of the disparity map carry a usable depth value?

No. Pixels where the matcher found no correspondence hold an invalid marker, and the borders fall outside the two views' overlap. Convert nothing until invalid pixels are masked: dividing f·B by an invalid, zero or negative disparity yields an extreme Z that corrupts band statistics and can peg the color scale at its limits.

How close can a part come before the disparity conversion breaks down?

At the matcher's maximum disparity d_max. Depth saturates at Z_min = f·B/d_max, because a nearer surface needs more shift than the search window allows. With the example numbers, f = 1000 px, B = 120 mm and d_max = 128 px, that is 120000/128 ≈ 940 mm; anything closer returns saturated or invalid disparities rather than a useful depth.