8/7 - [25/10K] - bev_corners = box3d_to_corners(bboxes_3d) 분석
박스를 그리는 함수가 있는데 chat gpt에게 물어봐서 코드를 분석 했다.
bev_corners = box3d_to_corners(bboxes_3d)[:, [0, 3, 4, 7]][
..., [0, 1]
]
xs = bev_corners[..., 0] / bev_resolution + bev_w / 2
ys = -bev_corners[..., 1] / bev_resolution + bev_h / 2
for obj_idx, (x, y) in enumerate(zip(xs, ys)):
for p1, p2 in ((0, 1), (0, 2), (1, 3), (2, 3)):
if isinstance(color[0], (list, tuple)):
tmp = color[obj_idx]
else:
tmp = color
cv2.line(
bev,
(int(x[p1]), int(y[p1])),
(int(x[p2]), int(y[p2])),
tmp,
thickness=thickness,
)
box3d_to_corners(bboxes_3d) 의 출력 크기는 (11, 8, 3)
bev_corners (11, 4, 2) 의 출력 크기를 가지게 됨. 그래서 박스를 그릴 수 있게 됨.
bev_corners = box3d_to_corners(bboxes_3d)[:, [0, 3, 4, 7]][..., [0, 1]]
- box3d_to_corners(bboxes_3d):
- 이 함수는 3D 박스를 입력받아, 각 박스의 8개 모서리의 좌표를 반환합니다.
- 출력되는 결과는 [num_boxes, 8, 3] 형태의 배열입니다.
- num_boxes는 박스의 수를 의미합니다.
- 8은 각 박스의 8개의 코너 포인트를 나타냅니다.
- 3은 각 코너 포인트의 3D 좌표 (x, y, z)를 나타냅니다.
- [:, [0, 3, 4, 7]]:
- 이 부분은 각 박스에서 특정 4개의 코너를 선택합니다.
- 0, 3, 4, 7은 각 박스의 8개 코너 중에서 이 4개를 선택하는 것을 의미합니다.
- 선택한 코너들은 보통 3D 박스의 바닥면을 구성하는 코너들입니다. (상하좌우 네 모서리)
- [..., [0, 1]]:
- 여기서 ...는 생략 연산자입니다. 이는 기존의 모든 차원을 유지하면서 특정 차원만 선택한다는 의미입니다.
- [0, 1]은 3D 좌표에서 x와 y 축만 선택하여 2D 좌표로 변환합니다.
- 결과적으로, 3D 공간에서 x, y 값만 남기고 z 좌표를 제외하여 2D 평면에서의 좌표로 변환하는 역할을 합니다.
여기서 입력으로 사용되는 "bboxes_3d" 는
- X (위치): 박스 중심의 X 좌표.
- Y (위치): 박스 중심의 Y 좌표.
- Z (위치): 박스 중심의 Z 좌표.
- W (너비): 박스의 너비 (width).
- L (길이): 박스의 길이 (length).
- H (높이): 박스의 높이 (height).
- YAW (회전 각도): 박스의 회전 각도. 보통 Y 축을 기준으로 한 회전 각도.
- SIN_YAW (옵션): 회전 각도의 사인 값 (선택적으로 포함될 수 있음).
- COS_YAW (옵션): 회전 각도의 코사인 값 (선택적으로 포함될 수 있음).
- VX (옵션): X 방향 속도 (선택적으로 포함될 수 있음).
- VY (옵션): Y 방향 속도 (선택적으로 포함될 수 있음).
- VZ (옵션): Z 방향 속도 (선택적으로 포함될 수 있음).
상수 정의
YAW = 6 # decoded
X, Y, Z, W, L, H, SIN_YAW, COS_YAW, VX, VY, VZ = list(range(11)) # undecoded
CNS, YNS = 0, 1 # centerness and yawness indices in qulity
박스 tensor 변환
if isinstance(box3d, torch.Tensor):
box3d = box3d.detach().cpu().numpy()
모서리 좌표 변환
corners_norm = np.stack(np.unravel_index(np.arange(8), [2] * 3), axis=1)
corners_norm = corners_norm[[0, 1, 3, 2, 4, 5, 7, 6]]
# 원점 0.0으로 이동.
corners_norm = corners_norm - np.array([0.5, 0.5, 0.5])
# 11(박스개수)x1x3 * 1x8x3 = 11 x 8 x 3
corners = box3d[:, None, [W, L, H]] * corners_norm.reshape([1, 8, 3])
# rotate around z axis
rot_cos = np.cos(box3d[:, YAW])
rot_sin = np.sin(box3d[:, YAW])
rot_mat = np.tile(np.eye(3)[None], (box3d.shape[0], 1, 1))
rot_mat[:, 0, 0] = rot_cos
rot_mat[:, 0, 1] = -rot_sin
rot_mat[:, 1, 0] = rot_sin
rot_mat[:, 1, 1] = rot_cos
corners = (rot_mat[:, None] @ corners[..., None]).squeeze(axis=-1)
# 위치 이동
corners += box3d[:, None, :3]
회전 행렬과 코너 좌표의 곱
np.eye(3)[None].shape
(1, 3, 3)
(box3d.shape[0], 1, 1)
(11, 1, 1)
rot_mat = np.tile(np.eye(3)[None], (box3d.shape[0], 1, 1))
rot_mat.shape
(11, 3, 3)
rot_mat[:, None].shape
(11, 1, 3, 3)
corners[..., None].shape
(11, 8, 3, 1)
(rot_mat[:, None] @ corners[..., None]).shape
(11, 8, 3, 1)
(rot_mat[:, None] * corners[..., None]).shape
(11, 8, 3, 3)
box3d[:, None, :3].shape
(11, 1, 3)
corners.shape
(11, 8, 3)
rot_mat[:, None]:
rot_mat의 형태는 (num_boxes, 3, 3)입니다. 이 형태에서 None을 추가하면 (num_boxes, 1, 3, 3)이 됩니다.
None을 추가함으로써 새로운 차원을 추가하고, 이를 통해 브로드캐스팅을 통해 다차원 곱셈이 가능합니다.
corners[..., None]:
corners의 형태는 (num_boxes, 8, 3)입니다.
None을 추가하면 (num_boxes, 8, 3, 1)이 됩니다.
...은 모든 차원을 포함하는 것을 의미합니다.
rot_mat[:, None] @ corners[..., None]:
이 연산은 rot_mat과 corners의 행렬 곱을 수행합니다.
rot_mat[:, None]의 형태가 (num_boxes, 1, 3, 3)이고, corners[..., None]의 형태가 (num_boxes, 8, 3, 1)이므로, 결과는 (num_boxes, 8, 3, 1)입니다.
결과적으로 각 박스에 대해 (8, 3) 형태의 코너 좌표가 회전 행렬에 의해 변환됩니다.
위치 이동
box3d[:, None, :3]:
box3d의 형태는 (num_boxes, 10)입니다.
box3d[:, None, :3]의 형태는 (num_boxes, 1, 3)입니다.
:3은 박스의 중심 좌표 (X, Y, Z)를 의미합니다.
corners += box3d[:, None, :3]:
이 연산은 각 박스의 중심 좌표를 회전된 코너 좌표에 더합니다.
브로드캐스팅에 의해 box3d[:, None, :3]는 (num_boxes, 8, 3) 형태로 확장됩니다.
결과적으로 각 박스의 회전된 코너 좌표가 원래 박스의 중심 좌표로 이동합니다.
@
다차원 배열의 경우:
다차원 배열에서 @ 연산자는 기본적으로 마지막 두 차원의 행렬 곱셈을 수행합니다.
나머지 차원은 브로드캐스팅에 따라 확장됩니다.
np.tile
np.tile(A, repeat_shape)
형태이며, A 배열이 repeat_shape 형태로 반복되어 쌓인 형태가 반환됩니다.
모델의 입력
아래와 같다. 어떻게 계산이 되었는지 파악해보자!!!
'img_metas':
- T_global
- T_global_inv
- timestamp
- data['img_metas'][0]['T_global'].shape
(4, 4)
data['img_metas'][0]['T_global_inv'].shape
(4, 4)
data['img_metas'][0]['timestamp']
1533151603.54759
data['img'].shape
torch.Size([1, 6, 3, 256, 704])
data['timestamp']
tensor([1.5332e+09], device='cuda:0', dtype=torch.float64)
data['projection_mat'].shape
torch.Size([1, 6, 4, 4])
data['image_wh'].shape
torch.Size([1, 6, 2])
data['image_wh']
tensor([[[704., 256.],
[704., 256.],
[704., 256.],
[704., 256.],
[704., 256.],
[704., 256.]]], device='cuda:0')
data['projection_mat']
tensor([[
[[ 5.4692e+02, 3.6989e+02, 1.4416e+01, -1.5591e+02],
[-6.3702e+00, 9.6405e+01, -5.4680e+02, -2.2414e+02],
[-1.1703e-02, 9.9847e-01, 5.4022e-02, -4.2520e-01],
[ 0.0000e+00, 0.0000e+00, 0.0000e+00, 1.0000e+00]],
[[ 6.0058e+02, -2.7248e+02, -1.7749e+01, -2.0312e+02],
[ 4.8886e+01, 6.5852e+01, -5.5001e+02, -2.1927e+02],
[ 8.4341e-01, 5.3631e-01, 3.2160e-02, -6.1037e-01],
[ 0.0000e+00, 0.0000e+00, 0.0000e+00, 1.0000e+00]],
[[ 1.4243e+01, 6.6139e+02, 3.4154e+01, -1.3307e+02],
[-5.6023e+01, 6.1763e+01, -5.5025e+02, -2.2479e+02],
[-8.2342e-01, 5.6594e-01, 4.1220e-02, -5.2968e-01],
[ 0.0000e+00, 0.0000e+00, 0.0000e+00, 1.0000e+00]],
[[-3.5375e+02, -3.7432e+02, -1.1633e+01, -3.8315e+02],
[-3.5956e+00, -5.6038e+01, -3.5308e+02, -1.6952e+02],
[-8.3335e-03, -9.9920e-01, -3.9103e-02, -1.0165e+00],
[ 0.0000e+00, 0.0000e+00, 0.0000e+00, 1.0000e+00]],
[[-5.2209e+02, 4.0624e+02, 2.3436e+01, -2.7515e+02],
[-7.0893e+01, -3.9026e-01, -5.5153e+02, -1.8665e+02],
[-9.4759e-01, -3.1948e-01, 3.1695e-03, -4.3253e-01],
[ 0.0000e+00, 0.0000e+00, 0.0000e+00, 1.0000e+00]],
[[ 1.2548e+02, -6.4648e+02, -2.6208e+01, -1.1994e+02],
[ 6.6316e+01, -5.2879e-01, -5.4965e+02, -1.9378e+02],
[ 9.2405e-01, -3.8225e-01, -3.7099e-03, -4.6465e-01],
[ 0.0000e+00, 0.0000e+00, 0.0000e+00, 1.0000e+00]]]],
device='cuda:0')