3D visionTroubleshooting

Disparity16 values 0 and 255: read the raw depth stream

Values 0 and 255 in a Disparity16 stream mean eight bits reached your code. Find the wrong buffer or cast, read the 16-bit data and convert it to meters.

LumaSpec Vision9 min read Share on LinkedIn

On this page 10 sections
  1. 01What a Disparity16 pixel holds
  2. 02Why exactly 0 and 255 means eight bits
  3. 03The four likely causes, in order
  4. 04Read the buffer as 16-bit and prove what arrived
  5. 05Convert disparity to distance
  6. 06Decide the LSB factor from one plane target
  7. 07When the encoding already says 16-bit
  8. 08Checks before you trust the stream
  9. 09When you need depth, not disparity
  10. 10Questions

You switch your stereo depth camera's output to a 16-bit disparity format, and the numbers that arrive are 0 and 255 — a posterized, two-color map instead of a smooth depth image. That symptom is a data-path fault, not a broken camera: something between the sensor and your variable is discarding the low byte. This guide shows what a Disparity16 pixel actually holds, why exactly two values betray eight-bit data, the four places where bit depth usually gets lost, and the formula that turns a correct raw value into millimeters. If you write the capture loop yourself — a driver node, an SDK frame callback or a file reader — you can find and fix this in an afternoon.

What a Disparity16 pixel holds

A Disparity16 pixel is a 16-bit unsigned integer that carries one stereo disparity in fixed subpixel steps. The standard packing is 16 LSBs per pixel of disparity, so the arithmetic is simple: a raw value of 80 is 80 / 16 = 5 px of disparity, a raw 33 is 2.0625 px, and the full 0 to 65,535 range would span about 4,096 px. Real stereo pairs sit far below that ceiling, so the format itself is never the bottleneck. Zero is reserved: it marks pixels where the stereo match found no correspondence, not a distance. The format is one entry in the depth stream's pixel-format selector, an enumeration in the driver or capture configuration that takes effect when the stream starts.

Why exactly 0 and 255 means eight bits

An eight-bit unsigned integer offers 256 values, 0 through 255; a 16-bit unsigned integer offers 65,536, as Microsoft's native pixel formats overview spells out. If your buffer holds only the two end points, one bit of information per pixel survived the trip: every measurement was reduced to a yes or a no.

Three mechanisms do this in practice. A display copy mistaken for the data stream: many drivers publish a colorized or thresholded image beside the raw one. A copy into an eight-bit container, which truncates or clamps every value above 255. Or a viewer that rescales the 16-bit frame into an eight-bit window, flattening the histogram on screen while the numbers stay intact. A stream spread across 0 to 255 is a different clue: that is an eight-bit scaled copy of real data, not a mask.

The four likely causes, in order

Work down the list; each item carries a test that settles it in minutes.

  1. You tapped the display copy. Drivers commonly publish a preview next to the raw stream. Test: read the encoding field of the message you actually receive — if it declares eight bits per pixel, you are on the preview. Fix: subscribe to the stream whose encoding reads 16-bit unsigned.

  2. Sixteen-bit data landed in an eight-bit container. The camera sends unsigned 16-bit; your image class or copy target is unsigned 8-bit, so values wrap or clamp at 255. Test: print the container type and the frame maximum — an 8-bit type cannot exceed 255 by construction. Fix: declare the buffer 16-bit and size every row at two bytes per pixel.

  3. The format change never took effect. Some drivers lock the pixel-format selector once streaming, so the configuration says disparity while the wire still carries the old eight-bit encoding. Test: query the active format back from the driver after start and log it; the same lock-up appears in a pixel-format selector stuck read-only until the scan type is set. Fix: set the format before the stream opens, or stop, set, restart.

  4. The data is fine and the viewer is flattening it. Test: histogram on the raw buffer, never on the render. Fix: widen the display range or use a 16-bit-aware renderer, and quote measurements from code only.

Read the buffer as 16-bit and prove what arrived

Take the buffer straight from the frame callback, read it as 16-bit words, and count what is inside before trusting any rendering. The loop below uses our own variable names; map them to your SDK's frame fields.

// Our names — map them to your SDK's frame fields.
uint32_t width  = 640;               // pixels per row
uint32_t height = 480;               // rows
uint32_t stride = width * 2;         // bytes per row, 16-bit data
const uint8_t* base = frame_bytes;   // first byte the SDK handed you

uint16_t vmax     = 0;               // highest raw value seen
uint32_t distinct = 0;               // distinct non-zero raw values
bool seen[65536] = {};               // one flag per possible raw value

for (uint32_t y = 0; y < height; ++y) {
    const uint16_t* row = reinterpret_cast<const uint16_t*>(base + y * stride);
    for (uint32_t x = 0; x < width; ++x) {
        uint16_t v = row[x];         // one 16-bit read, not two bytes
        if (v == 0) continue;        // zero = no match; skip it
        if (!seen[v]) { seen[v] = true; ++distinct; }
        if (v > vmax) vmax = v;
    }
}
// Healthy stream: vmax far above 255, distinct in the hundreds.

A corrected stream shows a maximum far above 255 and hundreds of distinct values on any textured scene. If the count stays at two, the flattening happened upstream of this loop, and the causes above tell you where. Copy the frame into your own storage inside the callback: drivers reuse buffers, and a frame read too late comes back torn or gray — the same mechanism behind frames that save wrong until you copy the Mat data out of the SDK buffer.

Convert disparity to distance

With real 16-bit values flowing, distance is one division:

d = raw / 16 // disparity in pixels, 1/16 px per LSB Z = f_px × B / d // f_px = focal length in pixels, B = baseline ΔZ ≈ Z² × Δd / (f_px × B) // distance error from a disparity error Δd

Worked example, with numbers you can check in your head. Raw value 80, so d = 80 / 16 = 5 px. With a calibrated focal length of 500 px and a 2 mm baseline, Z = 500 × 2 / 5 = 200 mm. Units: f_px carries no length, so Z inherits the unit of B; divide by 1,000 for meters.

Quantization follows the third line. One LSB is 1/16 px = 0.0625 px, so at 200 mm: ΔZ = 200² × 0.0625 / (500 × 2) = 2.5 mm. Move the same point out to 400 mm: the disparity halves to 2.5 px, and the same LSB now costs 160,000 × 0.0625 / 1,000 = 10 mm. Equal disparity steps are therefore not equal millimeter steps — the bands compress with distance, as disparity map to depth conversion: bands are not fixed mm works through.

Decide the LSB factor from one plane target

The 16-LSB packing is the default in stereo toolkits, but a stream can also carry floating-point disparity or a different fixed-point factor, and quoting meters with the wrong factor scales every answer you publish. One measurement settles it. Aim the camera square at a plane at a known distance — 500 mm here — and compute the expected disparity: d = 500 × 2 / 500 = 2 px. Read the raw value on that plane. Raw 32 confirms the standard packing, because 2 × 16 = 32. Raw 500 would mean 500 / 2 = 250 LSB per pixel, and you would divide by 250 from then on. Whatever raw divided by expected turns out to be is your factor. Where calibration noise clouds the reading, stereo disparity scale and calibration for depth estimates extends the method.

When the encoding already says 16-bit

Two places hide a flattening after the format looks right. Stride: a 16-bit row occupies 2 × width bytes, and a reader that advances one byte per pixel, or trusts an assumed width, rebuilds a smeared image that can look two-valued — compare the buffer's stride against 2 × width before anything else. Valid masks: a frame of nothing but zeros is not a two-valued depth map, it is a missing one, usually a stream-schema fault; the SDK returns an empty depth image buffer traces that path. And when values are plausible but the map tears row to row, the fault sits upstream in stereo rectification, a different failure with its own fix.

Checks before you trust the stream

  • The encoding field of the received message reads 16-bit unsigned — proves you tapped the data stream, not the preview.
  • Your container is 16-bit and the stride equals 2 × width — proves the copy path keeps both bytes.
  • The histogram, zeros excluded, spans hundreds of distinct values — proves nothing flattened the frame.
  • Zeros are counted separately — proves invalid pixels are not dragging min, max or means.
  • The plane-target quotient returns your assumed LSB factor — proves the scale before any distance is quoted.
  • One caliper check on that plane lands within a few millimeters — proves the chain end to end.

When you need depth, not disparity

Debugging the raw stream pays off when your algorithm consumes disparity itself. If the task only needs metric results — heights, volumes, pick poses — binocular stereo and structured-light sensors do the matching internally and publish depth or point clouds in real units, so this converter never enters your code. The MV-DPS200P-02 projects structured light; the MV-DLS300P-04 scans a laser line across a stereo pair. Both sit in the 3D sensors range.

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

To compare the whole range, open the camera finder; if the open question is which camera class fits the task at all, 2D or 3D camera for conveyor pick and place walks that decision. From a corrected stream, the next building block is geometry: applying per-axis scale factors turns a depth image into a point cloud, and the 3D camera topic page collects the rest of the fault-finding guides.

Build the whole station

The configurator matches a camera, lens, light, light controller and cables to each other, step by step, and lists only parts that fit what you already picked.

Start with the camera

Stuck on a particular SDK's behavior? Tell us the camera model and what your histogram shows, and we will go through the numbers with you.

Questions

What does a Disparity16 value of 0 mean?

Zero means the stereo match failed or the pixel was masked: there is no measurement there, not a distance of 0 mm. Treat every zero as invalid — exclude it from statistics and color maps, and count it separately. A small fraction of zeros is normal in a textured scene; a frame that is mostly zeros points at weak texture, exposure or matching settings rather than a conversion bug.

How do I convert a raw disparity value to meters by hand?

Divide the raw value by the subpixel scale to get pixels, then divide focal length times baseline by it: Z = f_px × B / d, with B and Z in the same unit. Example: raw 96 at 1/16 px scaling is 6 px of disparity; with a 500 px focal length and a 30 mm baseline, Z = 500 × 30 / 6 = 2,500 mm, or 2.5 m.

How can I reduce stereo depth error at long range?

Error per LSB scales with distance squared divided by focal length times baseline, so widen the geometry: doubling the baseline halves the error. At 400 mm range with a 500 px focal length, a 2 mm baseline gives 10 mm per 1/16 px LSB; a 4 mm baseline gives 5 mm. A longer focal length helps the same way, at the cost of field of view.

Does every stereo camera pack disparity as 1/16 pixel steps?

It is the default in most stereo toolkits, but not universal: some SDKs emit floating-point disparity — use those values as pixels directly, no divide by 16 — and a few pack fixed-point factors other than 16. Settle it with one plane target at a known distance and divide the raw value by the disparity you expect; the quotient is your stream's factor.

How many distinct values should a healthy disparity frame show?

A textured scene a few hundred thousand pixels in size typically yields thousands of distinct raw values after subpixel packing. Single digits mean a mask or an eight-bit path; a few dozen suggests a flat, low-texture scene or heavy smoothing; values crowding against the maximum hint at clipping. Track the count per frame — it is the fastest single health metric a disparity stream offers.