Skip to content

cameras

cameras #

Camera geometry over Pose: the frame every splat shares, and pixels as rays.

  1. World. Every pose lives in ARKit's right-handed, metric, y-up world, the one every session relocalized into a scene's world map shares.
  2. Pose. A Pose is a camera-to-world transform in the OpenGL convention nerfstudio reads: the camera looks down its own -z with y up, which is ARKit's camera transform as captured, so camera_to_world is the quaternion's matrix beside the position.
  3. Pixels. A pose's intrinsics are expressed at its reference size; at_size re-expresses them for a resized photograph, rays turns pixel centres into world-space directions, and to_world places camera-space points.

at_size(pose, width, height) #

pose with its intrinsics expressed at width×height.

Raises:

Type Description
ValueError

If pose carries no intrinsics.

Source code in capturegraph-lib/capturegraph/recipes/splats/cameras.py
def at_size(pose: Pose, width: int, height: int) -> Pose:
    """``pose`` with its intrinsics expressed at ``width``×``height``.

    Raises:
        ValueError: If ``pose`` carries no intrinsics.
    """
    known = intrinsics(pose)
    if known is None:
        raise ValueError("a pose without intrinsics cannot be re-expressed at another size")
    scale_x = width / known.reference_width
    scale_y = height / known.reference_height
    return dataclasses.replace(
        pose,
        focal_x=known.focal_x * scale_x,
        focal_y=known.focal_y * scale_y,
        center_x=known.center_x * scale_x,
        center_y=known.center_y * scale_y,
        reference_width=float(width),
        reference_height=float(height),
    )

camera_to_world(pose) #

The 4×4 OpenGL camera-to-world matrix of pose.

Source code in capturegraph-lib/capturegraph/recipes/splats/cameras.py
def camera_to_world(pose: Pose) -> np.ndarray:
    """The 4×4 OpenGL camera-to-world matrix of ``pose``."""
    matrix = np.eye(4)
    matrix[:3, :3] = quaternion_matrix(
        pose.quaternion_w,
        pose.quaternion_x,
        pose.quaternion_y,
        pose.quaternion_z,
    )
    matrix[:3, 3] = position(pose)
    return matrix

intrinsics(pose) #

The pinhole intrinsics pose carries, or None when the device delivered none.

Source code in capturegraph-lib/capturegraph/recipes/splats/cameras.py
def intrinsics(pose: Pose) -> CameraIntrinsics | None:
    """The pinhole intrinsics ``pose`` carries, or ``None`` when the device delivered none."""
    if (
        pose.focal_x is None
        or pose.focal_y is None
        or pose.center_x is None
        or pose.center_y is None
        or not pose.reference_width
        or not pose.reference_height
    ):
        return None
    return CameraIntrinsics(
        focal_x=pose.focal_x,
        focal_y=pose.focal_y,
        center_x=pose.center_x,
        center_y=pose.center_y,
        reference_width=pose.reference_width,
        reference_height=pose.reference_height,
    )

position(pose) #

The camera centre of pose in world coordinates.

Source code in capturegraph-lib/capturegraph/recipes/splats/cameras.py
def position(pose: Pose) -> np.ndarray:
    """The camera centre of ``pose`` in world coordinates."""
    return np.array([pose.position_x, pose.position_y, pose.position_z], dtype=np.float64)

quaternion_matrix(quaternion_w, quaternion_x, quaternion_y, quaternion_z) #

The rotation matrix of a (possibly unnormalized) quaternion.

Source code in capturegraph-lib/capturegraph/recipes/splats/cameras.py
def quaternion_matrix(
    quaternion_w: float,
    quaternion_x: float,
    quaternion_y: float,
    quaternion_z: float,
) -> np.ndarray:
    """The rotation matrix of a (possibly unnormalized) quaternion."""
    norm = math.sqrt(quaternion_w**2 + quaternion_x**2 + quaternion_y**2 + quaternion_z**2)
    if norm == 0.0:
        return np.eye(3)
    w, x, y, z = (
        quaternion_w / norm,
        quaternion_x / norm,
        quaternion_y / norm,
        quaternion_z / norm,
    )
    return np.array(
        [
            [1 - 2 * (y * y + z * z), 2 * (x * y - z * w), 2 * (x * z + y * w)],
            [2 * (x * y + z * w), 1 - 2 * (x * x + z * z), 2 * (y * z - x * w)],
            [2 * (x * z - y * w), 2 * (y * z + x * w), 1 - 2 * (x * x + y * y)],
        ],
        dtype=np.float64,
    )

rays(pose, columns, rows) #

Unit world-space directions through the pixel centres (columns, rows) of pose.

Raises:

Type Description
ValueError

If pose carries no intrinsics.

Source code in capturegraph-lib/capturegraph/recipes/splats/cameras.py
def rays(pose: Pose, columns: np.ndarray, rows: np.ndarray) -> np.ndarray:
    """Unit world-space directions through the pixel centres ``(columns, rows)`` of ``pose``.

    Raises:
        ValueError: If ``pose`` carries no intrinsics.
    """
    known = intrinsics(pose)
    if known is None:
        raise ValueError("a pose without intrinsics has no rays")
    right = (columns + 0.5 - known.center_x) / known.focal_x
    up = -(rows + 0.5 - known.center_y) / known.focal_y
    directions = np.stack([right, up, -np.ones_like(right)], axis=-1)
    directions /= np.linalg.norm(directions, axis=-1, keepdims=True)
    return directions @ camera_to_world(pose)[:3, :3].T

to_world(pose, camera_points) #

OpenCV camera-space points (x right, y down, z forward) in the world.

Source code in capturegraph-lib/capturegraph/recipes/splats/cameras.py
def to_world(pose: Pose, camera_points: np.ndarray) -> np.ndarray:
    """OpenCV camera-space points (``x`` right, ``y`` down, ``z`` forward) in the world."""
    camera_frame = camera_points * np.array([1.0, -1.0, -1.0])
    return camera_frame @ camera_to_world(pose)[:3, :3].T + position(pose)