Meridian / docs /inference.md
yycc's picture
Two-adapter release: docs and code
1f487cc verified
|
Raw
History Blame Contribute Delete
15.6 kB
# Inference: camera and time
[← Meridian](../README.md) · [Installation](installation.md) · [Method](../README.md#method) · [Studio demo](../README.md#self-hosting-the-demo)
Run the examples from the release directory after completing installation. The CLI selects one GPU
through `CUDA_VISIBLE_DEVICES`; it does not split a take across cards.
```bash
CUDA_VISIBLE_DEVICES=0 python inference/sample.py \
--video examples/media/sp_bouldering_hang.mp4 \
--yaw 15 --sweep --out out/orbit
```
The default is a 73-frame, 24 fps take using the distilled student: `--steps 4 --flow-shift 3`.
Use a fresh `--out` directory for each take; filenames are fixed rather than automatically versioned.
## Prepare the input
Use a single continuous shot. The **CLI does not normalize frame rate or detect cuts**: it reads
decoded frames by index and always writes at 24 fps. A 30 fps or variable-frame-rate input can
therefore change pace and lose audio alignment unless you normalize it first.
```bash
# Preserve playback duration while exporting a constant 24 fps input.
ffmpeg -i clip.mp4 -vf "setpts=PTS-STARTPTS,fps=24" \
-c:v libx264 -crf 18 -pix_fmt yuv420p -c:a aac clip_24fps.mp4
# Inspect the actual decoded frame count, not only the container's FPS label.
ffprobe -v error -select_streams v:0 -count_frames \
-show_entries stream=width,height,r_frame_rate,nb_read_frames \
-of default=noprint_wrappers=1 clip_24fps.mp4
```
For a normal take, the input must contain at least `start + frames` decoded frames. The two included
sample clips each contain exactly **73 frames at 24 fps** and no audio. A longer take needs a longer
input or an explicit hold; simply increasing `--frames` on those samples will not extend the action.
## Camera recipes
The following commands all work with the included 73-frame sample as an input, subject to the
hardware and model setup. Motion quality still depends on reconstruction and viewpoint coverage.
### Orbit with a gentle start and stop
```bash
python inference/sample.py --video examples/media/sp_bouldering_hang.mp4 \
--yaw 15 --sweep --ease --out out/eased_orbit
```
Positive yaw moves the camera **left** around the pivot. Without `--sweep`, the offset is applied
throughout the clip instead of ramping from the original view. It remains an offset from each source
camera, not necessarily a camera fixed in world space.
### Push in without changing the lens
```bash
python inference/sample.py --video examples/media/sp_bouldering_hang.mp4 \
--dolly 0.8 --zoom 1 --sweep --ease --out out/push_in
```
**`--dolly` alone performs a dolly zoom:** it changes both the camera radius and focal length to
approximately preserve the pivot plane's size. Add `--zoom 1` for a fixed-lens push-in, where the
subject grows in frame. `--zoom 1.5` without translation is an optical zoom; these pure-zoom takes
can be ignored by the model. Prefer moves with parallax.
### Slide or crane while keeping the subject framed
```bash
# Move right by 0.15 pivot-depth units and aim back toward the pivot.
python inference/sample.py --video examples/media/sp_bouldering_hang.mp4 \
--truck 0.15 --aim --sweep --ease --out out/slide
# Raise the camera by 0.15 pivot-depth units.
python inference/sample.py --video examples/media/sp_bouldering_hang.mp4 \
--boom 0.15 --aim --sweep --ease --out out/crane
```
### Choose the orbit center
```bash
python inference/sample.py --video examples/media/sp_bouldering_hang.mp4 \
--pivot 0.5,0.5 --pivot-lock --yaw 15 --sweep --out out/pivot_orbit
```
`--pivot fx,fy` uses fractions of the **center-cropped picture**, not the full letterbox or original
uncropped image. Choose a point on the subject, away from image boundaries, sky, and missing depth.
The example selects the crop center; adjust it for your footage. `--pivot` sets the depth scale;
`--pivot-lock` additionally moves the orbit center to the picked 3D point.
**Choose the point in the frame used for pivot depth.** Without `--freeze`, this is
the first selected source frame (`--start`). With `--freeze F:N`, it is frame `F`.
An athlete-centered point at the held apex can land on the distant audience in the
approach frame: do not reuse it unchanged when removing the hold. The depth is a
median over a neighborhood extending roughly 5% of the picture in each direction,
so check that neighborhood as well as the exact pixel. `--pivot-lock` is not dynamic
subject tracking; inspect the projected reference throughout the shot before
treating the requested trajectory as a successful composition.
## Timing
All CLI source indices are **zero-based absolute frame indices** in the supplied file.
### Select a passage
```bash
# On an input with at least 121 frames, use source frames 48 through 120 inclusive.
python inference/sample.py --video clip_24fps.mp4 \
--start 48 --frames 73 --yaw 15 --sweep --out out/later_moment
```
`--start 48` is two seconds into a 24 fps input. It is not a seek time in seconds.
### Hold a moment while moving the camera
```bash
# 24 live frames, then source frame 24 repeated for 49 output frames; no tail.
python inference/sample.py --video examples/media/sp_bouldering_reach.mp4 \
--yaw 35 --freeze 24:49 --out out/bullet
# Hold one instant for the entire take; the camera still sweeps through 20 degrees.
python inference/sample.py --video examples/media/sp_bouldering_reach.mp4 \
--yaw 20 --freeze 24:73 --start 24 --out out/held_moment
```
For `--freeze F:N`, the output consists of:
1. Source frames `start` through `F - 1`, once each.
2. Source frame `F`, repeated `N` times.
3. Source frames after `F`, once each, until the requested output length is reached.
The tail length is `frames - N - (F - start)`. Use a positive `N`, `F >= start`, and a non-negative
tail. The source must reach frame `F + tail`. With a hold, fewer distinct source frames can produce
a longer output, but the repeated interval contains no new event motion.
By default, the camera ramp progresses only during the held interval. Add `--sweep` to move during
the live lead-in and tail too; `--live-speed` sets their ramp speed relative to the held interval
(default `0.33`). It controls the **camera ramp**, not playback speed.
The CLI can also decode a still image, for example
`--video still.png --freeze 0:73 --yaw 15`. This is a camera move over a held image, not animation of
the subject. The studio's video upload workflow does not offer this short-input path.
### Slow motion and speed-ups
**Set the event's pace first, then author the camera.** Export the retimed source at a constant
24 fps and use that export as the input to either the CLI or the studio.
```bash
# 0.5x: twice the duration.
ffmpeg -i clip.mp4 -vf "setpts=2*(PTS-STARTPTS),fps=24" -an clip_slow.mp4
# 2x: half the duration.
ffmpeg -i clip.mp4 -vf "setpts=0.5*(PTS-STARTPTS),fps=24" -an clip_fast.mp4
python inference/sample.py --video clip_slow.mp4 \
--yaw 15 --sweep --out out/slow_orbit
python inference/sample.py --video clip_fast.mp4 \
--yaw 15 --sweep --out out/fast_orbit
```
Changing playback metadata alone is not enough for the CLI: timing must be baked into the **decoded
frame sequence**. `fps=24` duplicates or drops frames; it does not interpolate new motion. Slow-motion
smoothness depends on the source frame rate and any interpolation applied before inference.
Check the retimed file's frame count before rendering. In particular, acceleration shortens the input:
the included 73-frame samples become too short for an ordinary 73-frame take at 2x. Use longer footage
or explicitly hold a moment. The commands above omit audio; retime a soundtrack separately if needed.
In the studio, one source frame per output frame preserves the export's retimed pace. Stretching keys
across the unchanged source is a different workflow: it duplicates or skips reconstructed frames and
can trigger a speed warning.
## Output lengths and resolution
| `--frames` | Duration at 24 fps |
|---|---|
| 73 | 3.04 s |
| 90 | 3.75 s |
| 107 | 4.46 s |
| 124 | 5.17 s |
| 141 | 5.88 s |
| 158 | 6.58 s |
| 175 | 7.29 s |
| 243 | 10.13 s |
These are the lengths with shipped text and audio-layout assets. Other values are not accepted by
the CLI. This is a per-take limit, not a limit on the total duration of the input file.
Output uses an aspect-matched 768-class bucket, usually about 1.03 million pixels; 16:9 maps to
1344 × 768. Both references use the smaller 480-class bucket: 832 × 480 for a 16:9 input.
## Output files
| File in `--out` | Contents |
|---|---|
| `out.mp4` | Generated take, 24 fps, no generated audio. |
| `render.mp4` | Geometry reference at conditioning resolution, including grey holes. |
| `source.mp4` | Source images after the selected frame mapping and output crop. A hold is visible here too. |
| `grid.mp4` | Source, geometry reference, and generated take side by side. |
| `out_audio.mp4` | For non-freeze commands: source-window audio muxed onto the take, when the input has audio. A silent input remains silent. |
| `last.png` | Final generated frame. Reusing it is possible, but does not guarantee cross-take consistency. |
| `cams.npz` | Source/target camera matrices, intrinsics, pivot metadata, crop, canvas, FPS, and command arguments. |
`cams.npz` stores `c2w_src` and `c2w_dst` as camera-to-world matrices; `intr_src` and `intr_dst` are
in the 512-space geometry grid. The `*_px` arrays are exported for the output canvas. Translation
units are reconstruction-relative, not meters. The archive is diagnostic metadata, not a scene model.
For reproducibility, retain the input export, command, seed, checkpoint revisions, and environment.
Do not assume the CLI and studio, or different dependency/backend versions, produce bit-identical
results from the same seed.
## CLI reference
Run `python inference/sample.py --help` for the parser's complete help. The tables below group the
options by purpose; boolean flags are off unless stated otherwise.
### Input and model
| Option | Default | Meaning |
|---|---|---|
| `--video` | Required | Input video or decodable still image. |
| `--out` | Required | Output directory. |
| `--start` | `0` | First source-frame index. |
| `--frames` | `73` | Supported output length from the table above. |
| `--seed` | `1234` | Random seed. |
| `--ckpt` | `transformer/` inside `--model-dir` | A local Diffusers transformer directory. The default is the unmodified MiniMax-H3 transformer. |
| `--lora` | `<release>/teacher_lora <release>/turbo_lora` | One or more adapter directories, applied together at weight 1.0 in the order given. |
| `--no-lora` | Off | Load no adapter: stock MiniMax-H3, which cannot re-camera. Diagnostic only. |
| `--steps` | `4` | Scheduler grid points, including the terminal point. |
| `--flow-shift` | `3` | Video schedule shift; use `12` for the teacher. |
| `--model-dir` | `MiniMaxAI/MiniMax-H3` | Hub repo or local directory containing `vae/`. |
| `--vggt-repo`, `--vggt` | Environment-based | VGGT-Omega source checkout and checkpoint; see [Installation](installation.md#2-obtain-vggt-omega-separately). |
| `--attn-backend` | `_native_cudnn` | Diffusers attention backend. Alternatives are hardware- and version-dependent. |
To use the teacher alone — the re-camera adapter without the distillation, 50 steps instead of 3 forwards:
```bash
python inference/sample.py --video examples/media/sp_bouldering_hang.mp4 \
--yaw 15 --sweep --lora teacher_lora --steps 50 --flow-shift 12 --out out/teacher
```
### Camera and motion
| Option | Default | Meaning |
|---|---|---|
| `--yaw` | `0` | Orbit angle in degrees; positive moves left. |
| `--yaw-from` | `0` | Initial yaw when ramping; the live lead-in holds this value unless also swept. |
| `--truck` | `0` | Sideways shift in pivot-depth units; positive moves right. |
| `--boom` | `0` | Vertical shift in pivot-depth units; positive raises the camera. |
| `--dolly` | `1` | Orbit-radius scale; below one moves closer and, by default, widens the lens. |
| `--zoom` | `0` | Zero means automatic dolly-linked focal scaling; a positive value specifies the final focal multiplier. |
| `--pivot` | Unset | `fx,fy` in the crop; selects the depth-scale neighborhood. |
| `--pivot-lock` | Off | With `--pivot`, orbit about the selected 3D point. |
| `--aim` | Off | Reorient toward the pivot after translation. |
| `--pivot-to` | Unset | With `--aim`, a second `fx,fy` point toward which the aim transitions. |
| `--sweep` | Off | Ramp from the initial to the final offset over the take. |
| `--ease` | Off | Cosine ease-in/out applied to the ramp; it does not create a ramp by itself. |
| `--bounce` | Off | There-and-back ramp, `0 → 1 → 0`; implies a ramp even without `--sweep`. |
| `--swing` | Off | Sine ramp, `0 → 1 → 0 → −1 → 0`; implies a ramp. |
| `--freeze` | Unset | `F:N`: hold source frame `F` for `N` output frames. |
| `--live-speed` | `0.33` | With `--freeze --sweep`, relative camera-ramp speed outside the hold. |
Use one basic ramp shape at a time. Combining `--ease`, `--bounce`, and `--swing` composes their
functions in code order; it does not select between independent motion presets.
### Advanced and diagnostic controls
| Option | Default | Meaning and caveat |
|---|---|---|
| `--gauge-only` | Off | Reconstruct, warp, print geometry gauges, then stop before loading H3. No normal output artifacts are written. |
| `--follow` | Off | Replay estimated source cameras over the **first selected frame's fixed geometry and RGB**. Ignores the authored yaw/translation/lens controls; it does not retain the event's live motion. |
| `--smooth` | `8` | With `--follow`, Gaussian smoothing sigma in frames for estimated camera poses and intrinsics. `0` disables smoothing. |
| `--cull` | Off | Reject surfaces seen from behind according to estimated depth-map normals. This removes misleading splats; it does not reveal hidden surfaces. |
| `--fast-back` | `1` | Above one, compress the middle half of the camera ramp. Does not improve the reconstruction of an unseen back view. |
| `--canvas` | Automatic | Explicit `WxH`, with dimensions divisible by 32; advanced override outside the reported default benchmarks. |
| `--full` | `0` → 1280 | Override the square letterbox side. Higher values increase point-cloud sampling and memory, not VGGT's 512-pixel input resolution. |
The CLI prints `ahead`, coverage, and other geometry diagnostics but **does not reject a take using
the studio's clearance/motion thresholds**. Inspect the diagnostics and `render.mp4`; do not treat a
successful process exit as a quality check.
## Improving a take
1. **Inspect the geometry reference first.** A bent subject or unstable depth in `render.mp4` usually
needs a better source shot or a smaller move, not more denoising steps.
2. **Use modest viewpoint changes.** Large orbits reveal surfaces absent from the source. A plausible
completion can still be wrong; roughly 40° is a reported caution point, not a universal threshold.
3. **Check the depth scale.** If a small numerical move sends the camera through the scene, pick a
pivot on the subject and reduce the translation.
4. **Add parallax to lens changes.** Use a small dolly rather than relying on a pure optical zoom.
5. **Check timing before inference.** Stuttering from repeated input frames is not a geometry failure;
use higher-frame-rate footage or an interpolated export when smooth slow motion matters.
6. **Do not cross cuts.** Split the input into continuous shots yourself when using the CLI.
For dependency and memory errors, see [Setup problems](installation.md#setup-problems).