You have an RGB frame and a depth frame arriving over the stream, and you need the distance to one point. Every answer you find points at an SDK call such as get_distance. You do not need it. The depth image is the measurement: each pixel already stores the range as an integer count, and one multiplication turns it into millimetres. This guide shows what the pixel value encodes, where the depth scale comes from, how to read a pixel from a saved file or a live frame without any vendor library, and how to handle the zeros that mean no data. You will also get a worked example, the checks that catch a wrong scale, and the point at which a point cloud is the better tool.
What a depth pixel stores
A depth image is a single-channel image in which every pixel holds the range to the surface that pixel sees. In the common encoding the frame is 16-bit unsigned, so each pixel stores an integer from 0 to 65,535. That number is a count, not a distance. The ratio that turns counts into millimetres is the depth scale, in millimetres per count.
Real streams use more than one scale. Many depth cameras encode 1 mm per count; one well-known depth sensor used 13 of its 16 bits to span 80 mm to 8,000 mm. Some robotics datasets go finer, packing depth into a 24-bit value spread across the three colour channels at a fixed step of 1/256 mm. The container and the scale travel separately, so the file format alone never tells you the unit; the stream configuration does.
Zero has one meaning: no measurement. The pixel saw nothing within range, the return was too weak, or it was occluded. It is not 0 mm. Mask zeros before you average, fit or convert, because unmasked zeros drag every result toward the floor. The deprojection-zero troubleshooting guide shows what invalid points do once they reach a cloud.
The formula: count times depth scale
One line converts any pixel:
With a 1 mm scale, the count is the distance in millimetres: a pixel reading 1,245 is 1,245 mm. With a 1/256 mm scale, the same count is 1,245 / 256 ≈ 4.9 mm. The multiplication is the whole method, which is why a wrong scale is the most damaging mistake here: every distance you report inherits it.
Two limits come with the encoding. Quantisation is half a count, so on a 1 mm stream every read carries ±0.5 mm of rounding, and averaging the same pixel never removes it. Range headroom rarely binds: a 16-bit word at 1 mm per count tops out at 65,535 mm, while indoor depth cameras measure a few metres, so the optics, not the file format, set your working span.
Get the scale the honest way: query the depth-units control from the camera's own API at start-up, or read the unit per count in the camera documentation. Print it once per session. A scale that is assumed rather than queried is the failure behind clouds that land at a tenth or a 256th of their true size, and when a cloud comes in cut off near the camera, the depth scale mismatch guide walks through exactly that failure.
Reading the pixel from a file or a live frame
The mechanics are array indexing, not camera maths.
From a file, save depth as 16-bit PNG or TIFF; both are lossless, so counts survive the trip to disk. Load with the bit depth forced to 16. If the array comes back 8-bit, some stage divided by 256 on the way and the data is gone. Index by row and column: the pixel at column u, row v sits at array position [v, u]. If your capture side is LabVIEW and you need the array position of a known image coordinate, the knowledgebase page on locating LabVIEW image coordinates from a 2D pixel array covers that indexing.
From a live stream, take the depth frame as a 16-bit buffer before any format conversion. A grabber that hands you RGB frames has already thrown the range away, because a depth frame is not a picture. In Python, the usual trap is dtype and stride handling on the raw buffer; the numpy depth-image guide to strides and dtypes shows the checks that catch it.
One viewing note saves time: a 16-bit depth PNG looks black in a normal viewer, because the viewer maps 0–255 to the screen and your counts sit near the bottom of 0–65,535. Normalise for display only and keep the raw 16-bit values for measurement.
Worked example: distance to a tote face from a saved 16-bit PNG
Inputs, each with its source:
- File: 16-bit grayscale PNG saved straight from the depth stream, so the counts are intact.
- Depth scale: 1 mm per count, queried from the camera's depth-units control at capture time; 1 mm is the encoding many depth cameras use.
- Pixel: column u = 640, row v = 360, the centre of the tote face in this frame.
- Raw values: the pixel holds 1,245 counts; its neighbour at (641, 360) holds 0.
Rule in words: distance in millimetres equals the count times the depth scale in millimetres per count, and a zero pixel is excluded rather than measured.
Arithmetic:
- 1,245 counts × 1 mm/count = 1,245 mm, which is 1.245 m.
- Quantisation: ±half a count = ±0.5 mm.
- The zero neighbour is masked, so it contributes nothing to the read or to any average around it.
- Scale cross-check: from a 1/256 mm stream, the identical count would give 1,245 / 256 = 4.863 mm ≈ 4.9 mm.
Decision: report 1,245 mm ± 0.5 mm plus the camera's own distance error — the tote face sits about 1.25 m away. If a flat surface you know is near 1.2 m reads single-digit millimetre values instead, your stream is on a finer scale than 1 mm. Re-query the depth-units control and multiply again; no filtering downstream repairs a wrong scale.
Depth value or slant range: which number is in the file
The pixel stores what the camera measures, and depth cameras differ here. Many report Z, the distance along the optical axis: every point on a wall parallel to the camera then reads the same value. Some streams store slant range, the length along each pixel's ray, which reads longer away from the centre.
The relation is fixed: slant r = Z / cos θ, with θ the pixel's angle off axis. At θ = 30°, 1 / cos 30° = 1 / 0.866 ≈ 1.15, so the same physical point differs by about 15% between the two conventions. Which one your camera uses is stated in its documentation or exposed as a setting. Centre-pixel reads are the safe first check, because the two conventions agree on axis: the centre pixel depth measurement guide covers that read, and the depth value vs true distance guide gives the off-axis conversion with the intrinsics.
What the RGB frame can and cannot add
An RGB pixel carries colour and texture, not range. The 2D analogue exists, and it is worth knowing for its limits: with a reference of known size at a known plane, you convert pixel lengths to real units for that plane, and the knowledgebase page on calculating pixel distance in real units works that conversion through. It is a calibration for one plane, not a rangemap: move the part 100 mm toward the camera and every converted length skews. Stereo matching recovers range from two RGB views, but that is a calibrated stereo system rather than a single frame; the stereo disparity scale and calibration guide covers what the disparity-to-depth conversion needs.
Decision: when a depth frame exists in the system, read distance from it. Reach for RGB-only estimation only when no depth data exists and a known-size reference sits in the measurement plane.
Cameras that stream depth maps like this
Anything in the 3D sensors range that outputs a depth frame decodes exactly this way. A time-of-flight camera such as the MV-DT01WMU returns a dense depth map every capture. A projected structured-light camera such as the MV-DPS200P-02 builds that map from projected patterns, which suits indoor, textured surfaces. For logistics volume measurement, structured-light 3D cameras in the logistics range pair depth capture with a volume-measurement application.
- Type
- Depth / ToF camera
- Type
- Stereo / structured light
For height profiles across a moving part or belt, the matching tool is a laser profiler:
- Type
- Laser profiler
A profiler projects a laser line onto the part and returns the height profile across that line, so range data arrives line by line as the part or belt moves. Reading a distance from it means reading the profile value at your feature, along the scan line rather than a camera axis.
Checks before you trust the number
- Print the depth scale once per session, from the camera's own API or documentation, and never carry a scale over from another camera.
- Confirm the loaded array dtype is 16-bit unsigned. In an 8-bit array no value can exceed 255, so any count above 255 proves the chain kept the bit depth.
- Keep depth in PNG or TIFF end to end. If a frame ever transits JPEG, it is 8-bit and lossy, and the counts are unrecoverable.
- Settle whether the file stores Z or slant range, and check a centre-pixel read on a flat wall parallel to the camera against the documentation value.
- Mask zeros and saturated pixels before you average or fit, and decide the policy once: skip the pixel or fail the part.
- Prove the whole chain against a physical target: place a flat plate at exactly 1,000 mm with a metre rule, read one pixel, and expect 1,000 mm ± 0.5 mm plus the camera's own error. A reading near 4 mm means an 8-bit collapse; a reading near 1 m but out by tens of millimetres means camera error or a slant-versus-Z mix-up.
When to build a point cloud instead
One pixel answers 'how far is that point'. Geometry questions — part extent, tilt, volume, whether a pin sits proud — need many pixels converted with the camera intrinsics, which is deprojection. The path is short from here: scale the depth frame, then apply the per-axis scale factors as in the depth image to point cloud guide, deproject with the correct camera model as in the aligned depth intrinsics guide, and cut the working region down with an XY region-of-interest crop before any heavy processing. If the frame needs calibrating against real distances first, the depth map to real distance calibration guide picks up exactly there.
The decision
You can read the distance without the SDK. For a 16-bit depth frame on disk or on the wire: load it unsigned 16-bit, index the pixel by row and column, multiply by the scale you queried from the camera, and mask the zeros. From RGB alone there is no distance in the pixels; add a depth camera or a known-size reference in the plane. For geometry rather than a single point, deproject to a point cloud with the intrinsics and work there.
If you are choosing hardware for this, open the 3D camera finder or start at the camera step of the system configurator. The 3D vision topic page collects the depth and point-cloud guides, and the team answers fitting questions through the contact page.
Questions
What does a zero in a depth image mean?
Zeros cluster at the edges of the camera's measurable range: beyond maximum range, closer than the minimum, or on dark and shiny surfaces that return too little signal. A frame full of zeros at one distance band points at exposure or range settings, not at a broken file. Mask zeros from any average, and if a whole region reads zero, fix the illumination or the range settings before interpreting the rest.
How do I find the depth scale for my camera's stream?
Two places: the camera's API exposes a depth-units or depth-scale control you can query at runtime, and the camera documentation states the unit per count. Some cameras also let you set the unit as a stream setting, so re-query after changing modes. A third way needs no documentation: point the camera at a wall at a known distance, read the count, and compute scale = true distance / count.
What bit depth should I request from the camera?
Request the native depth format, which is 16-bit unsigned on most depth cameras, and keep it unchanged end to end. A 16-bit stream holds 65,536 levels, so on a 1 mm scale it spans 0 to 65.5 m; an 8-bit stream holds only 256 levels, which cannot even span a workshop at millimetre steps. Ask for 16-bit at the source and save lossless.
Why do depth values pin at 65,535?
A pixel sitting at the maximum of the 16-bit range is saturated, not measured. At a 1 mm scale that ceiling is 65,535 mm, about 65.5 m, well beyond what an indoor depth camera measures, so a saturated pixel at room scale means the return is invalid or your scale is wrong. Treat saturated pixels the same as zeros: mask them, and check the stream's scale and range settings before reading anything else.
My pipeline delivers depth as 32-bit float in metres. What changes?
The scale multiplication is already done: a pixel value of 1.245 is 1.245 m, so read it directly. Two things replace the scale check. Confirm the unit is metres and not millimetres, because a 1,245 in a float frame means millimetres. And expect NaN or infinity as the invalid marker in float frames rather than zero, so mask those where you would mask zeros.


