0biglife.

AUTONOMOUS-DRIVING TOOLS

Coordinate frame converter

KITTI, nuScenes, Waymo and ROS all disagree about which axis is forward and where a heading angle starts. Pick two, and see the same physical car described by two different sets of numbers — plus the matrix and the code that carry you between them.

SOURCE FRAME

nuScenes LIDAR_TOP · x=right y=forward z=up

TARGET FRAME

KITTI camera (rect) · x=right y=down z=forward

INPUT ROTATION

yaw (deg)

about +z (up), from +x (right)

SAMPLE POINT IN SOURCE FRAME

x

y

z

SAME CAR · SAME ARROWS · DIFFERENT LABELS

drag to orbit

SOURCE

nuScenes LIDAR_TOP · RFU

TARGET

KITTI camera (rect) · RDF

AXIS MAP — T, WHERE p_target = T · p_source

100
00-1
010

point (1, 0, 0)

→ (1, 0, 0)

handedness

right-handed, det(T) = +1

This part is never ambiguous. Points, translations and any direction vector go through T and nothing else.

BOX HEADING — WHAT A LABEL FILE MEANS

yaw in nuSc lidar

30°

rotation_y in KITTI cam

-30°

relation

rotation_y = −yaw

Derived by carrying the physical facing direction across, not from a table. A box's geometry does not get relabeled when you change frames.

ROTATION RE-EXPRESSED — R_target = T · R_source · Tᵀ

0.86600.0000-0.5000
0.00001.00000.0000
0.50000.00000.8660

SAME ROTATION, OTHER REPRESENTATIONS

quaternion in (w,x,y,z)

0.96593 0.00000 0.00000 0.25882

quaternion out (w,x,y,z)

0.96593 0.00000 -0.25882 0.00000

euler out (roll,pitch,yaw)

0° -30° 0°

Quaternions print with w ≥ 0 so the same orientation always shows the same numbers — q and −q are the same rotation.

PYTHON

import numpy as np

# nuScenes LIDAR_TOP (RFU)  ->  KITTI camera (rect) (RDF)
T = np.array([
    [ 1.,  0.,  0.],
    [ 0.,  0., -1.],
    [ 0.,  1.,  0.],
])

# 1. points, translations, and any direction vector
p_kitti_cam = T @ p_nuscenes_lidar

# 2. poses / extrinsics — re-express the rotation operator in the new basis
R_kitti_cam = T @ R_nuscenes_lidar @ T.T

# 3. box labels — carry the physical facing direction across
#    yaw: about +z (up), from +x (right)
#    rotation_y: about +y (down), from +x (right) — the KITTI label field
rotation_y = -yaw
rotation_y = np.arctan2(np.sin(rotation_y), np.cos(rotation_y))  # wrap to [-pi, pi)

WHERE THESE DEFINITIONS COME FROM

nuScenes LIDAR_TOP · RFU

Derived from kitti_to_nu_lidar = Quaternion(axis=(0,0,1), angle=+pi/2) in export_kitti.py

KITTI camera (rect) · RDF

KITTI object devkit — rotation_y is the rotation about the camera Y axis

The KITTI and nuScenes axes were pinned from nuscenes-devkit/export_kitti.py rather than from prose — it asserts the velodyne→camera rotation outright, and defines nuScenes LIDAR_TOP as KITTI rotated +90° about z. Everything on this page is derived from those axis definitions; no pairwise formula is hard-coded.

왜 이렇게 되는지 ↗

3D 기하 기초 — 좌표계·회전·yaw, 그리고 쿼터니언

왜 이렇게 되는지 ↗

센서 캘리브레이션 — 좌표계를 맞추지 못하면

Runs entirely in your browser — nothing is uploaded, and the URL carries the whole state, so a shared link reproduces exactly what you are looking at. Found a convention that is wrong or missing? tell me.