3D visionExplainer

Depth gray value to mm conversion for 16-bit range images

Convert depth gray values to mm: the raw-16-bit scale formula, the DepthMin-DepthMax conversion for viewer saves, and the 8-bit precision trap.

LumaSpec Vision11 min read Share on LinkedIn

On this page 9 sections
  1. 01Which formula turns a depth gray value into millimetres?
  2. 02Why do raw captures and viewer saves scale differently?
  3. 03How do you convert a raw 16-bit depth image to mm?
  4. 04How do you convert a range image saved by a viewer?
  5. 05How much precision do you lose with an 8-bit depth image?
  6. 06How do you tell which encoding a saved depth image uses?
  7. 07When is gray-to-mm conversion the wrong tool?
  8. 08What should you check before you trust a converted depth value?
  9. 09Questions

A saved depth image is not a picture of distance — it is distance encoded as gray levels, and two different tools encode the same scene two different ways. Convert a raw SDK capture with the wrong factor, or apply the raw scale to an image a viewer re-scaled, and a plate at 3200 mm reads as 1800 mm or 6000 mm. This guide gives you both conversions with the arithmetic worked through, the quantization numbers behind the 8-bit warning, and a bench test that identifies the encoding of any saved file. It is written for anyone measuring distance, height or volume from 16-bit range maps on time-of-flight, structured-light and profile cameras, and it connects to the downstream steps — point clouds, deprojection and calibration — once the millimetres are right.

Which formula turns a depth gray value into millimetres?

Two formulas exist and the file's origin decides between them: a raw capture converts as distance = gray × (full-scale range / 65535), while an image a viewer re-scaled converts as distance = DepthMin + pixel × (DepthMax - DepthMin) / 65535.

Both mappings are linear, so a correct conversion is exact arithmetic, not an approximation. The difference is which two distances the gray values 0 and 65535 stand for. On a raw 16-bit depth image the endpoints are the camera's full measurement range: a camera with a 0-10000 mm full scale digitizes that span into 0-65535 counts, so one count is 10000 / 65535 = 0.15259 mm. GenICam 3D cameras publish that number as the Scan3dCoordinateScale feature, which means you can query it at runtime instead of typing a constant into your code.

On an image saved by a viewer, the endpoints are the depth window that was on screen when the file was written: DepthMin maps to gray 0 and DepthMax maps to gray 65535. Everything else in this guide follows from that split — match the formula to the file and the millimetres come out right.

The pattern is the familiar span-and-offset conversion from instrumentation. If you have scaled analog raw counts to engineering units on PLC inputs, the S5 to S7 analog raw value conversion reference walks through the identical arithmetic on electrical signals.

Why do raw captures and viewer saves scale differently?

A viewer saves what you see, not what the sensor digitized: it stretches the depth window on screen across the full 0-65535 range to maximize contrast, and that stretch — nothing more — is the only processing it applies to the depth values.

The raw path never re-scales. The camera converts each pixel's measurement into a count between 0 and 65535 across the configured range, so one fixed factor serves every pixel, every frame, in short-range and long-range operation alike. The viewer path starts from that same data and re-encodes each pixel's distance into the display window you chose. Because both encodings are linear in distance, a viewer save carries the same information as a raw capture — different numbers, same millimetres, provided you invert the right mapping.

File source Gray 0 means Gray 65535 means Conversion Example scale
Raw SDK capture 0 mm full-scale range gray × range / 65535 0.15259 mm per count at 10000 mm
Viewer save, 200-5200 mm window DepthMin, 200 mm DepthMax, 5200 mm DepthMin + pixel × span / 65535 0.07629 mm per count plus a 200 mm offset
Viewer save, 8-bit, same window DepthMin DepthMax DepthMin + pixel × span / 255 19.61 mm per level plus the offset

The viewer's re-encoding is also one reason exported point clouds differ from the viewer render: the display pipeline and the export pipeline do not always hand your code the same scaled values.

How do you convert a raw 16-bit depth image to mm?

Read the scale from the camera and multiply: with a scale of 0.15259 mm per count, a pixel reporting 20972 converts to 20972 × 0.15259 = 3200.1 mm, and every pixel in the frame uses the same factor.

gray2mm = full_scale_mm / 65535 distance_mm = gray_value × gray2mm

The inverse direction matters just as often: divide the distance by the scale to predict the gray value a feature should produce. The factor does not change when the camera switches between short-range and long-range operation, because it is set by the digitization rather than the mode. It does change between models with different full-scale ranges, which is exactly why querying Scan3dCoordinateScale at startup beats hard-coding 0.15259.

Time-of-flight cameras are the classic source of range maps like this — the MV-DT01WMU is one example:

3D sensorsHikrobot MV-DT01WMU ToF sensing cameraMV-DT01WMU
Type
Depth / ToF camera
Price on requestView and quote

How do you convert a range image saved by a viewer?

Restore the window floor before scaling: distance = DepthMin + pixel × (DepthMax - DepthMin) / 65535. With DepthMin = 200 mm and DepthMax = 5200 mm that is 200 mm + pixel × 0.07629, and pixel 39321 reads exactly 3200.0 mm.

span_mm = DepthMax - DepthMin distance_mm = DepthMin + pixel_value × span_mm / 65535

Swap the formulas and the error is enormous. Apply the raw scale to the viewer image: 39321 × 0.15259 = 6000.0 mm — 2800 mm too high. Apply the window formula to the raw capture: 200 + 20972 × 0.07629 = 1800.1 mm — 1399.9 mm too low. The factor here is exact rather than coincidental: the full scale is 10000 mm and the window span is 5000 mm, so the two scales differ by 10000 / 5000 = 2, and the wrong formula lands at double or half the true distance, plus the missing 200 mm offset. Other windows give other large factors; the error is never small.

A wrong scale or offset also poisons everything downstream — one classic symptom is point clouds cut off at close range when the reconstruction receives the wrong span.

One more distinction: both formulas return Z measured along the camera's optical axis, not the straight-line distance from lens to object. Converting between the two needs its own geometry, covered step by step in depth value versus true distance.

How much precision do you lose with an 8-bit depth image?

An 8-bit save keeps only 256 levels, so a 200-5200 mm window quantizes at 5000 / 255 = 19.6 mm per level — a ±9.8 mm stair-step on every reading, against ±0.076 mm for the raw 16-bit image. Do not measure from 8-bit depth files.

Quantization error is half a level, so it follows the span directly:

Encoding Levels mm per level Uncertainty per reading
Raw 16-bit, 0-10000 mm 65536 0.15259 ±0.076 mm
Viewer 16-bit, 5000 mm window 65536 0.07629 ±0.038 mm
Viewer 8-bit, 5000 mm window 256 19.61 ±9.8 mm

On a 300 mm box measured from an 8-bit file, ±9.8 mm per face is up to 3% of the dimension from encoding alone, before sensor noise, calibration or mounting error enters. That is why 8-bit range saves are not recommended for measurement: 256 levels cannot usefully represent a 5000 mm span.

Note the inversion in the middle row: the viewer's 16-bit save is finer than the raw image, ±0.038 mm against ±0.076 mm, because the same 65536 levels are packed into a narrower span. The gain lives only in the file encoding. The sensor's precision was fixed when the measurement was taken, so a narrow window cannot recover information the digitization never captured — and a window narrower than the scene's depth spread clips everything outside it.

For measured data, save 16-bit PNG or TIFF from your capture code with the scale recorded alongside, and treat viewer screenshots and 8-bit exports as visualization only.

How do you tell which encoding a saved depth image uses?

Combine provenance, pixel format and one known-distance measurement: divide the known distance by the reported gray value, and the implied scale tells you within seconds whether the file is raw or window-encoded.

  1. Trace the provenance. A file your capture code wrote through the SDK is raw; a file a viewer wrote is window-encoded, and the window it displayed — its DepthMin and DepthMax — is the window it saved with.
  2. Read the pixel format. Mono16 and other 16-bit formats use the /65535 conversions; an 8-bit file uses the /255 conversion and should stay out of measurement entirely.
  3. Run the known-distance test. Put a flat target at a known distance, read the same fixed pixel on every capture — the approach in center pixel depth measurement — and test both conversions. A viewer save also fills its histogram end to end by construction, while a raw image clusters where the scene sits, which is a quick visual tell before any arithmetic.

When is gray-to-mm conversion the wrong tool?

Skip this conversion whenever the device already outputs calibrated geometry: laser profilers deliver Z profiles in millimetres per trigger, structured-light and stereo SDKs deliver X-Y-Z coordinate sets scaled inside the SDK, and none of them hands you a raw gray count to multiply.

Ask one question of any new 3D device: does the code receive integer range counts, or engineering units? Integer range counts — Mono16 coordinate data on GenICam cameras — need the factor from Scan3dCoordinateScale. Engineering-unit outputs, whether float depth maps, point arrays or calibrated profiles, do not, and pushing them through the gray-to-mm formula corrupts them by a huge factor.

A laser profiler such as the MIS-DP3850-01P returns a calibrated height profile for every trigger, so its measurement math runs on millimetres from the first sample:

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

Projected structured-light cameras behave the same way at the SDK level: the MV-DPS200P-02 exposes its depth through 3D data interfaces your code consumes as calibrated values rather than gray counts:

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

Once the data is a depth map in millimetres, the next step is geometry, not scaling: turning a depth map into a point cloud with the deprojection calculation, and applying per-axis scale factors when X and Y are scaled differently from Z.

What should you check before you trust a converted depth value?

Four checks catch nearly every depth-conversion error before it reaches production data: confirm the file's encoding, read the scale from the camera at runtime, keep the pipeline 16-bit end to end, and validate against a physical target before the first run.

  1. Encoding. Run the provenance and known-distance tests above on the first saved file of every new project; a two-minute bench test beats a week of mysteriously wrong measurements.
  2. Runtime scale. Query Scan3dCoordinateScale when the session opens rather than pasting constants between projects — models with different full-scale ranges differ by exactly the ratio of those ranges, and 10000 mm against 5000 mm is a factor of two.
  3. Bit depth end to end. Keep Mono16 in memory, save 16-bit PNG or TIFF, and reject any step that exports JPEG; reopen the file and confirm values above 255 exist before trusting the data.
  4. Physical validation. Measure one artifact of known dimensions at the working distance and expect agreement within quantization plus the camera's error budget; for permanent fixtures, the depth map to real distance calibration folds the residual offset into the conversion.

The decision in one line: buy cameras that export raw 16-bit depth with a published scale, capture through the SDK when data is for measurement, and treat viewer saves as visualization. Browse the 3D sensors and area scan cameras categories, filter the camera finder by interface and resolution, or start at the camera configurator — and if you are unsure which encoding a camera outputs, ask the applications team before wiring the conversion into your line. More conversions and fixes are collected on the 3D vision topic page.

Questions

How do I find the scale factor when my camera does not publish one?

Derive it from the documented full-scale range: scale = range in mm / 65535. A 5000 mm camera scales at 5000 / 65535 = 0.07629 mm per count. Then confirm on the bench: capture a flat target at a known distance, divide the distance by the reported gray value, and check the quotient matches the derived scale within half a count of quantization.

Is my saved depth file really 16-bit?

Check the file itself: 16-bit grayscale PNG and TIFF carry 65536 levels, JPEG is always 8-bit, and many tools downconvert unless a 16-bit option is selected. Reopen the saved file and look for pixel values above 255 — their presence confirms 16-bit survived the save. If the file properties say 8-bit, quantization error is already baked in and the original capture is the only usable source.

Can I improve depth resolution by narrowing the DepthMin-DepthMax window?

For viewer-saved files, yes: encoded resolution is span / 65535, so a 1000 mm window quantizes at 0.01526 mm per level against 0.07629 mm over a 5000 mm window. The sensor's true precision is fixed at capture, though — the window changes only how measurements are encoded, and a window narrower than the scene's depth spread clips points outside it.

Do depth and intensity images carry the same values?

No. On this class of camera the range map is computed independently of the intensity image, so depth pixels never carry brightness and intensity pixels never carry distance. Measure only from the depth stream; where you need texture on the same points, use the camera's registered or aligned pair so each depth pixel maps to its intensity counterpart.

Does every 3D camera encode depth as gray counts?

No — the gray-to-mm step applies to integer range maps only. Laser profilers output calibrated Z profiles in millimetres, some SDKs deliver float depth in metres, and GenICam 3D cameras can stream coordinate sets with the scale already applied. Check the pixel format: Mono16 coordinate data needs the scale factor; coordinate or engineering-unit data does not.