room-visualizer / verify_r1_scale_sim.py
GitHub Actions
Deploy from GitHub commit ca72656c17476e5aa37a4735af6e47ff9f94fa1a
b20c82e
Raw
History Blame Contribute Delete
4.22 kB
"""R1-3 — metric scale certification on an exact synthetic scene (CI-safe:
no torch, analytic depth).
Scene: pinhole camera (f = image width), height 1.5 m, pitch 25 deg, looking
at an infinite ground plane. Depth is computed analytically, the homography is
built exactly from four ground points with a known plane-unit scale, so the
true metersPerUnit is known in closed form.
Checks (real implementation extracted from app.py):
1. recovery: estimate_meters_per_unit returns the true scale within 2%
2. rejection: a sheared homography (the synthetic-VP failure mode) returns
None instead of a confidently-wrong scale
3. relative depth (normalised [0,1]) returns None (metric-only feature)
"""
import cv2
import numpy as np
# --- real implementation from app.py ----------------------------------------
src = open("app.py").read()
ns = {"np": np, "cv2": cv2, "depth_model_is_metric": lambda name=None: True}
start = src.index("def estimate_meters_per_unit")
end = src.index("\n# ---", start)
exec(compile(src[start:end], "app.py", "exec"), ns)
estimate_meters_per_unit = ns["estimate_meters_per_unit"]
W, H = 800, 600
F = float(W)
CAM_H = 1.5
PITCH = np.deg2rad(25.0)
UNITS_PER_M = 200.0 # plane-unit scale baked into the homography
TRUE_MPU = 1.0 / UNITS_PER_M
def scene():
cx, cy = W / 2.0, H / 2.0
u, v = np.meshgrid(np.arange(W, dtype=np.float64), np.arange(H, dtype=np.float64))
# ground plane: 1/Z = (sin(t) + cos(t) * (v - cy)/f) / h (v grows downward)
inv_z = (np.sin(PITCH) + np.cos(PITCH) * (v - cy) / F) / CAM_H
mask = inv_z > 1.0 / 30.0 # floor visible, within 30 m
z = np.where(mask, 1.0 / np.maximum(inv_z, 1e-9), 0.0)
# camera-frame 3D, then world ground coordinates. Camera pitched DOWN by
# PITCH, world y up, image v down: world_y = -cos*y_c - sin*z (must be
# exactly -CAM_H on the ground — asserted), forward = cos*z - sin*y_c.
x_c = z * (u - cx) / F
y_c = z * (v - cy) / F
world_y = -np.cos(PITCH) * y_c - np.sin(PITCH) * z
assert np.allclose(world_y[mask], -CAM_H, atol=1e-9), "sim geometry inconsistent"
x_w = x_c
fwd_w = np.cos(PITCH) * z - np.sin(PITCH) * y_c
return mask.astype(np.uint8), z.astype(np.float32), x_w, fwd_w
def exact_homography(mask, x_w, fwd_w):
ys, xs = np.nonzero(mask)
# four well-spread ground points
picks = []
for fy, fx in [(0.95, 0.2), (0.95, 0.8), (0.55, 0.3), (0.55, 0.7)]:
yy = int(np.percentile(ys, fy * 100))
row = xs[ys == yy]
xx = int(np.percentile(row, fx * 100))
picks.append((xx, yy))
src_pts = np.float32(picks)
dst_pts = np.float32(
[[x_w[y, x] * UNITS_PER_M, fwd_w[y, x] * UNITS_PER_M] for x, y in picks]
)
return cv2.getPerspectiveTransform(src_pts, dst_pts)
def main():
ok = True
mask, z, x_w, fwd_w = scene()
Hm = exact_homography(mask, x_w, fwd_w)
mpu = estimate_meters_per_unit(z, mask, Hm.flatten().tolist(), W, H)
if mpu is None:
print(" [FAIL] recovery: returned None on exact scene")
ok = False
else:
err = abs(mpu - TRUE_MPU) / TRUE_MPU
good = err <= 0.02
print(f" [{'PASS' if good else 'FAIL'}] recovery: mpu={mpu:.6f} "
f"(true {TRUE_MPU:.6f}, err {err * 100:.2f}%)")
ok &= good
# synthetic-VP failure mode: progressive horizontal shear of plane coords
S = np.array([[1.0, 0.35, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]])
H_bad = S @ Hm
mpu_bad = estimate_meters_per_unit(z, mask, H_bad.flatten().tolist(), W, H)
print(f" [{'PASS' if mpu_bad is None else 'FAIL'}] rejection: sheared homography -> {mpu_bad}")
ok &= mpu_bad is None
rel = (z - z[mask > 0].min()) / (z[mask > 0].max() - z[mask > 0].min())
ns["depth_model_is_metric"] = lambda name=None: False
mpu_rel = estimate_meters_per_unit(rel.astype(np.float32), mask, Hm.flatten().tolist(), W, H)
print(f" [{'PASS' if mpu_rel is None else 'FAIL'}] relative depth -> {mpu_rel}")
ok &= mpu_rel is None
print("\n" + ("ALL R1-3 SIM CHECKS PASSED" if ok else "R1-3 SIM CHECKS FAILED"))
return 0 if ok else 1
if __name__ == "__main__":
raise SystemExit(main())