Spaces:
Running on Zero
Running on Zero
File size: 5,299 Bytes
49d36c0 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 | # SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
"""
Camera utility functions.
"""
import math
import torch
from gem.utils.geo_transform import convert_bbx_xys_to_lurb, cvt_to_bi01_p2d
def perspective_projection(points, K):
"""Standard pinhole camera projection.
Args:
points: (*, J, 3)
K: (*, 3, 3)
Returns:
p2d: (*, J, 2)
"""
projected = points / points[..., -1:].clamp(min=1e-6)
projected = torch.einsum("...ij,...kj->...ki", K, projected.float())
return projected[..., :2]
def compute_transl_full_cam(pred_cam, bbx_xys, K_fullimg):
"""Convert weak-perspective camera parameters to full-perspective translation.
Args:
pred_cam: (*, 3) [s, tx, ty]
bbx_xys: (*, 3) [cx, cy, size]
K_fullimg: (*, 3, 3)
Returns:
cam_t: (*, 3)
"""
s, tx, ty = pred_cam[..., 0], pred_cam[..., 1], pred_cam[..., 2]
focal_length = K_fullimg[..., 0, 0]
icx = K_fullimg[..., 0, 2]
icy = K_fullimg[..., 1, 2]
sb = s * bbx_xys[..., 2]
cx = 2 * (bbx_xys[..., 0] - icx) / (sb + 1e-9)
cy = 2 * (bbx_xys[..., 1] - icy) / (sb + 1e-9)
tz = 2 * focal_length / (sb + 1e-9)
return torch.stack([tx + cx, ty + cy, tz], dim=-1)
def get_a_pred_cam(transl, bbx_xys, K_fullimg):
"""Inverse of compute_transl_full_cam: full translation → weak-perspective params.
Args:
transl: (*, 3)
bbx_xys: (*, 3)
K_fullimg: (*, 3, 3) one extra leading dim compared to transl/bbx_xys
Returns:
pred_cam: (*, 3) [s, tx, ty]
"""
f = K_fullimg[..., 0, 0]
cx = K_fullimg[..., 0, 2]
cy = K_fullimg[..., 1, 2]
gt_s = 2 * f / (transl[..., 2] * bbx_xys[..., 2])
gt_x = transl[..., 0] - transl[..., 2] / f * (bbx_xys[..., 0] - cx)
gt_y = transl[..., 1] - transl[..., 2] / f * (bbx_xys[..., 1] - cy)
return torch.stack([gt_s, gt_x, gt_y], dim=-1)
def compute_bbox_info_bedlam(bbx_xys, K_fullimg):
"""Compute BEDLAM-style bounding box info (normalised by focal length).
Args:
bbx_xys: (*, 3) [cx, cy, size]
K_fullimg: (*, 3, 3)
Returns:
bbox_info: (*, 3)
"""
fl = K_fullimg[..., 0, 0].unsqueeze(-1)
icx = K_fullimg[..., 0, 2]
icy = K_fullimg[..., 1, 2]
cx, cy, b = bbx_xys[..., 0], bbx_xys[..., 1], bbx_xys[..., 2]
bbox_info = torch.stack([cx - icx, cy - icy, b], dim=-1)
return bbox_info / fl
def safely_render_x3d_K(x3d, K_fullimg, thr=0.3):
"""Project 3D vertices to 2D, clamping near-zero depths for safety.
Args:
x3d: (*, V, 3)
K_fullimg: (*, 3, 3)
thr: minimum depth threshold
Returns:
i_x2d: (*, V, 2)
"""
x3d = x3d.clone()
x3d[..., 2] = x3d[..., 2].clamp(min=thr)
return perspective_projection(x3d, K_fullimg)
def estimate_K(width, height):
"""Estimate default pinhole intrinsics from image dimensions.
Uses max(w, h) as focal length — a reasonable approximation when
the true focal length is unknown.
Args:
width, height: image dimensions (int or float)
Returns:
K: (3, 3) camera intrinsics tensor
"""
focal = float(max(width, height))
K = torch.eye(3)
K[0, 0] = focal
K[1, 1] = focal
K[0, 2] = float(width) / 2.0
K[1, 2] = float(height) / 2.0
return K
def resize_K(K, scale):
"""Scale camera intrinsics for a resized image.
Args:
K: (*, 3, 3) intrinsics tensor
scale: scalar scale factor
Returns:
K_new: (*, 3, 3) scaled intrinsics
"""
K_new = K.clone().float()
K_new[..., :2, :] = K_new[..., :2, :] * scale
return K_new
def create_camera_sensor(width, height, fov_deg=60):
"""Create camera intrinsics from image dimensions and field-of-view angle.
Args:
width, height: image dimensions
fov_deg: horizontal field of view in degrees
Returns:
(width, height, K) where K is (3, 3)
"""
focal = float(width) / (2.0 * math.tan(math.radians(fov_deg / 2.0)))
K = torch.eye(3)
K[0, 0] = focal
K[1, 1] = focal
K[0, 2] = float(width) / 2.0
K[1, 2] = float(height) / 2.0
return width, height, K
def get_bbx_xys_from_xyxy(xyxy, base_zoom=1.0, minimal_size=10):
"""Convert bounding box from [x1, y1, x2, y2] to [cx, cy, size].
Args:
xyxy: (..., 4) bounding box in xyxy format
base_zoom: zoom factor applied to the box size
minimal_size: minimum allowed box size
Returns:
xys: (..., 3) [cx, cy, size]
"""
x1, y1, x2, y2 = xyxy[..., 0], xyxy[..., 1], xyxy[..., 2], xyxy[..., 3]
cx = (x1 + x2) / 2
cy = (y1 + y2) / 2
size = torch.max(x2 - x1, y2 - y1) * base_zoom
size = size.clamp(min=minimal_size)
return torch.stack([cx, cy, size], dim=-1)
def project_to_bi01(points, bbx_xys, K_fullimg):
"""Project 3D points to bounding-box-normalised [0,1] 2D coordinates.
Args:
points: (*, J, 3)
bbx_xys: (*, 3)
K_fullimg: (*, 3, 3)
Returns:
p2d_bi01: (*, J, 2)
"""
p2d = perspective_projection(points, K_fullimg)
bbx_lurb = convert_bbx_xys_to_lurb(bbx_xys)
return cvt_to_bi01_p2d(p2d, bbx_lurb)
|