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)