Files

204 lines
7.6 KiB
Python

# SPDX-License-Identifier: Apache-2.0
# Ported from NVIDIA GEN3C: cosmos_predict1/diffusion/inference/camera_utils.py
"""Camera trajectory generation utilities for GEN3C 3D cache conditioning."""
import math
import torch
def apply_transformation(Bx4x4: torch.Tensor, another_matrix: torch.Tensor) -> torch.Tensor:
"""Apply batch transformation to a matrix."""
B = Bx4x4.shape[0]
if another_matrix.dim() == 2:
another_matrix = another_matrix.unsqueeze(0).expand(B, -1, -1)
return torch.bmm(Bx4x4, another_matrix)
def look_at_matrix(camera_pos: torch.Tensor, target: torch.Tensor, invert_pos: bool = True) -> torch.Tensor:
"""Create a 4x4 look-at view matrix pointing camera toward target."""
forward = (target - camera_pos).float()
forward = forward / torch.norm(forward)
up = torch.tensor([0.0, 1.0, 0.0], device=camera_pos.device)
right = torch.cross(up, forward)
right = right / torch.norm(right)
up = torch.cross(forward, right)
look_at = torch.eye(4, device=camera_pos.device)
look_at[0, :3] = right
look_at[1, :3] = up
look_at[2, :3] = forward
look_at[:3, 3] = (-camera_pos) if invert_pos else camera_pos
return look_at
def create_horizontal_trajectory(
world_to_camera_matrix: torch.Tensor,
center_depth: float,
positive: bool = True,
n_steps: int = 13,
distance: float = 0.1,
device: str = "cuda",
axis: str = "x",
camera_rotation: str = "center_facing",
) -> torch.Tensor:
"""Create a linear camera trajectory along a specified axis."""
look_at_target = torch.tensor([0.0, 0.0, center_depth]).to(device)
trajectory = []
initial_camera_pos = torch.tensor([0, 0, 0], device=device, dtype=torch.float32)
translation_positions = []
for i in range(n_steps):
offset = i * distance * center_depth / n_steps * (1 if positive else -1)
if axis == "x":
pos = torch.tensor([offset, 0, 0], device=device)
elif axis == "y":
pos = torch.tensor([0, offset, 0], device=device)
elif axis == "z":
pos = torch.tensor([0, 0, offset], device=device)
else:
raise ValueError(f"Axis should be x, y or z, got {axis}")
translation_positions.append(pos)
for pos in translation_positions:
camera_pos = initial_camera_pos + pos
if camera_rotation == "trajectory_aligned":
_look_at = look_at_target + pos * 2
elif camera_rotation == "center_facing":
_look_at = look_at_target
elif camera_rotation == "no_rotation":
_look_at = look_at_target + pos
else:
raise ValueError(f"camera_rotation should be center_facing, trajectory_aligned, "
f"or no_rotation, got {camera_rotation}")
view_matrix = look_at_matrix(camera_pos, _look_at)
trajectory.append(view_matrix)
trajectory = torch.stack(trajectory)
return apply_transformation(trajectory, world_to_camera_matrix)
def create_spiral_trajectory(
world_to_camera_matrix: torch.Tensor,
center_depth: float,
radius_x: float = 0.03,
radius_y: float = 0.02,
radius_z: float = 0.0,
positive: bool = True,
camera_rotation: str = "center_facing",
n_steps: int = 13,
device: str = "cuda",
start_from_zero: bool = True,
num_circles: int = 1,
) -> torch.Tensor:
"""Create a spiral/circular camera trajectory."""
look_at_target = torch.tensor([0.0, 0.0, center_depth]).to(device)
trajectory = []
initial_camera_pos = torch.tensor([0, 0, 0], device=device, dtype=torch.float32)
theta_max = 2 * math.pi * num_circles
spiral_positions = []
for i in range(n_steps):
theta = theta_max * i / (n_steps - 1)
if start_from_zero:
x = radius_x * (math.cos(theta) - 1) * (1 if positive else -1) * center_depth
else:
x = radius_x * math.cos(theta) * center_depth
y = radius_y * math.sin(theta) * center_depth
z = radius_z * math.sin(theta) * center_depth
spiral_positions.append(torch.tensor([x, y, z], device=device))
for pos in spiral_positions:
camera_pos = initial_camera_pos + pos
if camera_rotation == "center_facing":
view_matrix = look_at_matrix(camera_pos, look_at_target)
elif camera_rotation == "trajectory_aligned":
view_matrix = look_at_matrix(camera_pos, look_at_target + pos * 2)
elif camera_rotation == "no_rotation":
view_matrix = look_at_matrix(camera_pos, look_at_target + pos)
else:
raise ValueError(f"camera_rotation should be center_facing, trajectory_aligned, "
f"or no_rotation, got {camera_rotation}")
trajectory.append(view_matrix)
trajectory = torch.stack(trajectory)
return apply_transformation(trajectory, world_to_camera_matrix)
def generate_camera_trajectory(
trajectory_type: str,
initial_w2c: torch.Tensor,
initial_intrinsics: torch.Tensor,
num_frames: int,
movement_distance: float,
camera_rotation: str = "center_facing",
center_depth: float = 1.0,
device: str = "cuda",
) -> tuple[torch.Tensor, torch.Tensor]:
"""
Generate camera trajectory for GEN3C video generation.
Args:
trajectory_type: One of "left", "right", "up", "down", "zoom_in",
"zoom_out", "clockwise", "counterclockwise".
initial_w2c: Initial world-to-camera matrix (4, 4).
initial_intrinsics: Camera intrinsics matrix (3, 3).
num_frames: Number of frames in the trajectory.
movement_distance: Distance factor for camera movement.
camera_rotation: "center_facing", "no_rotation", or "trajectory_aligned".
center_depth: Depth of the scene center point.
device: Computation device.
Returns:
generated_w2cs: (1, num_frames, 4, 4) world-to-camera matrices.
generated_intrinsics: (1, num_frames, 3, 3) camera intrinsics.
"""
if trajectory_type in ["clockwise", "counterclockwise"]:
new_w2cs_seq = create_spiral_trajectory(
world_to_camera_matrix=initial_w2c,
center_depth=center_depth,
n_steps=num_frames,
positive=trajectory_type == "clockwise",
device=device,
camera_rotation=camera_rotation,
radius_x=movement_distance,
radius_y=movement_distance,
)
elif trajectory_type == "none":
# Static camera - repeat identity
new_w2cs_seq = initial_w2c.unsqueeze(0).expand(num_frames, -1, -1)
else:
axis_map = {
"left": (False, "x"),
"right": (True, "x"),
"up": (False, "y"),
"down": (True, "y"),
"zoom_in": (True, "z"),
"zoom_out": (False, "z"),
}
if trajectory_type not in axis_map:
raise ValueError(f"Unsupported trajectory type: {trajectory_type}")
positive, axis = axis_map[trajectory_type]
new_w2cs_seq = create_horizontal_trajectory(
world_to_camera_matrix=initial_w2c,
center_depth=center_depth,
n_steps=num_frames,
positive=positive,
axis=axis,
distance=movement_distance,
device=device,
camera_rotation=camera_rotation,
)
generated_w2cs = new_w2cs_seq.unsqueeze(0) # (1, num_frames, 4, 4)
if initial_intrinsics.dim() == 2:
generated_intrinsics = initial_intrinsics.unsqueeze(0).unsqueeze(0).repeat(1, num_frames, 1, 1)
else:
generated_intrinsics = initial_intrinsics.unsqueeze(0)
return generated_w2cs, generated_intrinsics