Skip to content

Flat-ground distance carries a ~11% range-proportional scale error, not just scatter — multi-view self-consistency measures it and DA3 can anchor it #101

Description

@jonfroehlich

The measurement

Multi-view association in the auto-labeler (sidewalk-auto-labeler#27) fuses detections of the same physical ramp seen from several panos. That makes a new check possible: the same ramp, raycast from cameras at different distances, should land in the same place. It does not.

For every fused site, take each member's signed along-ray residual — how far that view's ground point sits beyond (or short of) the fused position, measured along its own ray — and regress it on that member's range. Demeaned within each site (fixed effects, so per-site offsets cancel), pooled across sites with ≥3 refit members spanning ≥4 m of range:

city source sites members slope (m per m)
paterson GSV 4,433 19,588 +0.1159 ± 0.0021
gainesville GSV 1,774 7,033 +0.1179 ± 0.0038
bend GSV 9,388 42,240 +0.1082 ± 0.0013
richmond Mapillary 890 7,040 +0.1275 ± 0.0058
sao_paulo GSV 1,877 8,156 +0.0572 ± 0.0034

All 50–90σ. The discrepancy is not scatter — it is proportional to range, the signature of a scale error in d = camera_height / tan(depression), at roughly 11% almost everywhere.

At 20 m that is a ~2.3 m radial displacement, and because a street's cameras mostly sit along one line, it does not average out — it pushes detections consistently away from the roadway.

This is the same effect §0 of detection_recall_analysis.md already saw

That doc reports flat-ground geometry and DA3 metric depth agreeing to within 6.5–8.5%, and notes geometry placing four Richmond ramps above the horizon. Those were read as agreement plus rig noise. The multi-view result says the residual has structure: it is a proportional scale error, not a spread. A scale error is a one-constant fix; noise is not.

Two independent methods — an external monocular depth model, and pure self-consistency across views — now point the same way, and neither shares the other's failure modes.

What it is not

The obvious explanation is that the assumed camera height is too high (+0.116 implies ~2.33 m against the 2.5 m this repo assumes and the 2.6 m the labeler's geo.py assumes — worth reconciling in its own right). That hypothesis fails its own test. Re-fusing the labeler's runs over 2.10–2.60 m does lower raw within-site scatter at ~2.33 m, but lowering the assumed height shrinks every range proportionally, so raw metres shrink for free. Normalize scatter by the assumed height and 2.60 m is monotonically the best value tried, on both a GSV and a Mapillary city. So this is not simply the wrong constant.

The decisive experiment, using tooling that already exists here

The multi-view regression proves the disagreement is range-proportional but cannot say which end is right — it is anchored to a weighted mean of the same biased rays. scripts/analysis/depth_extract_da3.py can anchor it:

Regress DA3 metric depth on flat-ground distance for benchmark detections and look at the slope, not the correlation.

  • Slope ≠ 1 → the geometry is biased. Fixable as a calibration; the distance buckets in detection_recall_analysis.md §1 and precision_by_distance.py are stretched, and the apparent-size relation W/d × (4096/2π) inherits it directly.
  • Slope ≈ 1, multi-view trend persists → the geometry is fine and the peak placement drifts: the heatmap's argmax moves relative to the true ground-contact point as a ramp shrinks with distance. That is a model property, and it would sit with Extend RampNet output from points to boxes/polygons (point-prompted SAM2; CenterNet-style size head; segmentation) #83 (points → extent) — a size head or mask gives a bottom edge to raycast instead of a blob centre.

Splitting those two is cheap and needs no new data.

Why it is worth doing

Note the Mapillary/GSV split does not separate the hypotheses: richmond (Mapillary, varied rigs, SfM poses) shows the largest slope while sharing no camera geometry with GSV, which argues against a single rig constant and toward something shared — the model, or the flat-ground assumption itself. São Paulo, the hilliest split, is the outlier at half the slope, which is the opposite of what pure terrain noise would predict and deserves its own look.

Measurement scripts are in the auto-labeler scratchpad (heightbias.py, heightsweep.py); the method above is complete enough to reproduce from sites.jsonl alone.

🤖 Generated with Claude Code (claude-opus-5[1m])

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions