twanghcmut's picture
download
raw
4.36 kB
#!/usr/bin/env python
"""RGB | MoGe depth side-by-side video for a whole clip.
Run with the ``fpgm`` env (it only needs numpy/cv2/imageio to read the ``.npz``
files ``moge_infer_episode.py`` already wrote -- no torch, no MoGe import).
**One global depth normalisation for the whole clip, never per frame.** Per-frame
normalisation makes the depth panel flicker as the scene's near/far extremes move,
which is indistinguishable, by eye, from the model's own temporal instability --
exactly the thing this video is meant to let someone judge. Robust percentiles, so
one stray pixel cannot crush the range. The measured per-frame scale drift for
MoGe-2 on this project's demo episode was CV 9.6%, so the distinction matters here.
Usage:
PYTHONPATH=src python scripts/moge_side_by_side.py \\
--video <clip.mp4> --npz-dir outputs/moge/marker_per_frame \\
--out outputs/moge/marker_side_by_side.mp4
"""
from __future__ import annotations
import argparse
import subprocess
from pathlib import Path
import cv2
import imageio.v3 as iio
import numpy as np
from fpgm.viz.video import _resolve_ffmpeg
_PERCENTILES = (2.0, 98.0)
def write_h264(frames_bgr, out: Path, fps: float) -> None:
t, h, w, _ = frames_bgr.shape
out.parent.mkdir(parents=True, exist_ok=True)
cmd = [
_resolve_ffmpeg(), "-y", "-hide_banner", "-loglevel", "error",
"-f", "rawvideo", "-pix_fmt", "bgr24", "-s", f"{w}x{h}", "-r", f"{fps:.4f}",
"-i", "pipe:0", "-c:v", "libx264", "-pix_fmt", "yuv420p", "-crf", "18", str(out),
]
p = subprocess.Popen(cmd, stdin=subprocess.PIPE)
assert p.stdin is not None
try:
for i in range(t):
p.stdin.write(np.ascontiguousarray(frames_bgr[i]).tobytes())
finally:
p.stdin.close()
if p.wait() != 0:
raise RuntimeError(f"ffmpeg failed writing {out}")
def main() -> None:
ap = argparse.ArgumentParser(description=__doc__)
ap.add_argument("--video", required=True, type=Path)
ap.add_argument("--npz-dir", required=True, type=Path)
ap.add_argument("--out", required=True, type=Path)
ap.add_argument("--fps", type=float, default=15.0)
ap.add_argument("--panel-width", type=int, default=640)
args = ap.parse_args()
npzs = sorted(args.npz_dir.glob("*.npz"))
if not npzs:
raise SystemExit(f"no .npz under {args.npz_dir}")
rgb = np.asarray(iio.imread(args.video, plugin="pyav"))
n = min(len(rgb), len(npzs))
print(f"rgb {rgb.shape} | npz {len(npzs)} -> using {n} frames")
# Pass 1: global range over a subsample (loading 992 full-res depth maps twice
# is wasteful; every 5th frame is plenty to fix a robust percentile range).
vals = []
for p in npzs[: n : max(1, n // 200)]:
d = np.load(p)
depth, mask = d["depth"], d["mask"]
v = depth[mask & np.isfinite(depth) & (depth > 0)]
if v.size:
vals.append(np.random.default_rng(0).choice(v, size=min(v.size, 20000), replace=False))
allv = np.concatenate(vals)
lo, hi = np.percentile(1.0 / allv, _PERCENTILES)
print(f"global inverse-depth range: {lo:.4f} .. {hi:.4f} per-m "
f"(metric {1/hi:.3f} .. {1/lo:.3f} m)")
pw = args.panel_width
ph = int(round(pw * rgb.shape[1] / rgb.shape[2]))
out = np.empty((n, ph, pw * 2, 3), dtype=np.uint8)
for i in range(n):
d = np.load(npzs[i])
depth, mask = d["depth"], d["mask"]
g = np.zeros(depth.shape, np.float32)
m = mask & np.isfinite(depth) & (depth > 0)
if m.any():
g[m] = np.clip((1.0 / depth[m] - lo) / (hi - lo), 0.0, 1.0)
col = cv2.applyColorMap((g * 255).astype(np.uint8), cv2.COLORMAP_TURBO)
col[~m] = 0
left = cv2.resize(rgb[i][..., ::-1], (pw, ph))
right = cv2.resize(col, (pw, ph), interpolation=cv2.INTER_NEAREST)
for panel, text in ((left, f"RGB f{i:04d}"), (right, "MoGe-2 depth")):
cv2.rectangle(panel, (0, 0), (len(text) * 11 + 10, 24), (0, 0, 0), -1)
cv2.putText(panel, text, (5, 17), cv2.FONT_HERSHEY_SIMPLEX, 0.55,
(255, 255, 255), 1, cv2.LINE_AA)
out[i] = np.concatenate([left, right], axis=1)
write_h264(out, args.out, args.fps)
print(f"wrote {args.out} ({n} frames, {pw*2}x{ph})")
if __name__ == "__main__":
main()

Xet Storage Details

Size:
4.36 kB
·
Xet hash:
4429dd0ff63b5b9857291761a9526e108cba2b706d5bf9f76285ee035dca2593

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.