Stereo depth on repeating stripes: a perfect match can be the wrong match

Sharp edges do not guarantee a unique stereo correspondence. If a surface repeats, shifting one image by a whole pattern period can make the wrong stripe match just as well as the right one. Converting that shift to depth produces a precise-looking number without establishing that the two image locations belong to the same physical feature.

This comes before the question of how much a one-pixel error changes depth. First the matcher must choose which feature goes with which. Luxonis’s stereo explanation describes disparity through corresponding image points and explains why passive stereo needs scene texture. The example below isolates a different problem from a blank wall: there is plenty of contrast, but the pattern repeats.

Four shifts, all with zero mismatch

Take ideal parallel cameras 60 mm apart, each with a 600 px focal length, looking at a flat panel 3 m away. Our synthetic panel is 1.8 m wide and carries alternating black and white strips, each 30 mm wide. A complete black–white period projects to 12 pixels. The true geometric disparity is also 12 pixels; these are deliberately chosen example dimensions, not camera specifications.

Four whole ideal camera renders of the finite repeating panel and the panel with one black strip changed to white, from left and right views
Full ideal left and right camera renders show the finite repeating panel and one changed strip. Matching costs come from analytic sampling of stored face attributes, not these rendered pixels or a physical camera.

We compare a 61-sample horizontal window against right-image shifts from 0 through 36 pixels. The cost is simply the sum of absolute grayscale differences. In the executed standalone calculation, shifts 0, 12, 24 and 36 pixels all cost zero.

Those are different hypotheses. The positive shifts imply depths of 3, 1.5 and 1 m under this camera model. Zero disparity supplies no finite forward depth. Selecting the first or last tied entry would hide the ambiguity; the calculation leaves the selected depth undefined.

The panel is finite and every main comparison stays inside it. No texture is wrapped around an edge. An AI-operated Blender 5.2.2 LTS experiment constructed three panels and six ideal cameras: nine objects, 720 vertices and 180 faces. An independent audit checked their evaluated world/local vertices, matrices, face indices, emission attributes, camera settings, all 1,440 vertex projections and CSV rows against scalar pinhole geometry. It then reconstructed the sample profiles from the finite faces. The stored vertex projections agreed within explicit numerical comparison allowances; this does not establish physical camera accuracy. Matching costs use noiseless point samples of face attributes, not captured or rendered pixels. No camera, DepthAI or OpenCV matcher has been tested.

Three match-cost plots: periodic zero minima at 0, 12, 24 and 36 pixels; changed strip only at 12; blank at all 37 shifts; a shaded wrong search selects 24
Each point is the 61-sample absolute-difference cost reconstructed from accepted finite faces. The shaded 23–25 px search interval excludes the true 12 px correspondence; its sole zero-cost 24 px winner is wrong.

A search window can hide the right answer

Change only one black strip to white, breaking the local repetition. With the same full search and sample window, 12 pixels is now the sole zero-cost match. Make the panel uniformly white instead, and all 37 candidate shifts tie. The useful distinction is not just texture versus no texture: the visible pattern also needs to distinguish competing correspondences within the relevant search.

There is another trap. Keep the repeating panel but search only shifts 23, 24 and 25. Now 24 pixels is the unique zero-cost result, implying 1.5 m, because the actual 12-pixel match was never considered. A unique minimum within an incorrectly chosen range does not establish the correct physical identity.

Declared condition Minimum-cost shifts, px Depth decision Why
Repeating, full search 0, 12, 24, 36 No selected depth Four equally good matches
One strip changed, full search 12 3 m in this model One zero-cost match in this window
Blank, full search All 37 candidates No selected depth No distinguishing pattern
Repeating, only 23–25 searched 24 Wrong 1.5 m True 12 px match was excluded

OpenCV’s StereoSGBM declaration comments in the 4.13.0 source separately expose the disparity search range and a uniqueness margin between competing costs. Our small sum-of-differences calculation does not implement its algorithm, filters or confidence behavior. It demonstrates why those are separate questions rather than recommending parameter values.

Run this standard-library Python to reproduce the stated comparisons. The pixel samples lie at half-pixel offsets, away from the synthetic strip boundaries.

from math import floor

focal, baseline, depth = 600.0, 0.06, 3.0
samples = [i + 0.5 for i in range(-30, 31)]

def gray(camera_x, pixel_offset, pattern):
    x = camera_x + pixel_offset * depth / focal
    if not -0.9 < x < 0.9:
        return None  # Outside our finite panel; do not wrap it.
    stripe = floor((x + 0.9) / 0.03)
    if pattern == "blank":
        return 1
    return 1 if pattern == "unique" and stripe == 30 else stripe % 2

def match(pattern, shifts):
    costs = {}
    for shift in shifts:
        pairs = [(gray(-baseline/2, u, pattern),
                  gray(baseline/2, u-shift, pattern)) for u in samples]
        if all(a is not None and b is not None for a, b in pairs):
            costs[shift] = sum(abs(a-b) for a, b in pairs)
    best = min(costs.values())
    return best, [d for d, cost in costs.items() if cost == best]

for pattern in ("periodic", "unique", "blank"):
    best, shifts = match(pattern, range(37))
    print(pattern, "cost:", best, "best shifts:", shifts)

assert match("periodic", range(37)) == (0, [0, 12, 24, 36])
assert match("unique", range(37)) == (0, [12])
# An incorrect search interval can hide the true 12 px match.
assert match("periodic", [23, 24, 25]) == (0, [24])
print("restricted-search depth:", focal * baseline / 24, "m")
print("zero-disparity depth:", None)

When diagnosing repeated structures, inspect competing correspondence costs and the allowed search range before interpreting a small winning cost as depth confidence. Our changed strip distinguishes the candidates only under this stated geometry, sample window and noiseless model. It is not a general texture treatment, a calibration procedure, or evidence of real sensor accuracy. Illumination, image sampling, occlusion and the actual matcher’s behavior require their own checks.

After a correspondence is chosen, the separate one-pixel depth sensitivity study explains how a coordinate change affects the resulting distance. If an OAK output is actually zero, the invalid-depth diagnostic guide addresses device validity and configuration. A wrong finite depth and a marked invalid pixel are different outcomes; this experiment does not predict which a real matcher will emit.