[Python] How to correctly generate Nerfstudio transforms.json from drone GPS + yaw/pitch/roll...

Discussão em 'Python' iniciado por Stack, Setembro 6, 2025.

  1. Stack

    Stack Membro Participativo

    I am training a NeRF with Nerfstudio using drone imagery from a MicaSense Rededge-P camera.

    For each capture I have metadata:

    1. lat, lon, alt (WGS84 position)
    2. yaw, pitch, roll (from MicaSense DLS, radians)
    3. camera intrinsics (focal length, principal point, distortion)

    My goal is that when the NeRF is trained, the reconstructed point cloud is already in geographic space (UTM/WGS84) so that I can load it directly into GIS software (Metashape) without having to manually georectify or align it.

    What I did


    1. Convert lat, lon, alt → UTM Zone 6N (EPSG:32606).


    2. Build rotation matrix from yaw, pitch, roll.


    3. Construct [R|t] 4×4 transform matrices.


    4. Write transforms.json in the format Nerfstudio expects.

    The issue

    In reality all the camera poses are facing down (Nadir), but when I visualize the dataset in Nerfstudio’s viewer, the camera poses are scattered and spiky. It seems like the coordinate frames (NED vs ENU vs OpenGL) and/or the large UTM translations are not handled correctly.

    [​IMG]

    Here’s the code I currently use to generate transforms.json:

    import json
    import numpy as np
    import pyproj

    # --- Rotation from yaw, pitch, roll (radians) ---
    # NOTE: Nerfstudio expects OpenGL-style (X right, Y up, Z back).
    # MicaSense yaw/pitch/roll are in NED, so we adjust to ENU.
    def rotation_matrix(yaw, pitch, roll):
    # Convert NED -> ENU by flipping pitch and roll
    pitch = -pitch
    roll = -roll

    cy, sy = np.cos(yaw), np.sin(yaw)
    cp, sp = np.cos(pitch), np.sin(pitch)
    cr, sr = np.cos(roll), np.sin(roll)

    R = np.array([
    [cy * cp, cy * sp * sr - sy * cr, cy * sp * cr + sy * sr],
    [sy * cp, sy * sp * sr + cy * cr, sy * sp * cr - cy * sr],
    [-sp, cp * sr, cp * cr]
    ])
    return R

    # --- Projection (WGS84 -> UTM Zone 6N) ---
    wgs84 = pyproj.CRS("EPSG:4326")
    utm6n = pyproj.CRS("EPSG:32606")
    transformer = pyproj.Transformer.from_crs(wgs84, utm6n, always_xy=True)

    def geodetic_to_utm(lat, lon, alt):
    e, n, h = transformer.transform(lon, lat, alt)
    return np.array([e, n, h])

    # --- Read metadata from data.json ---
    with open("data.json", "r") as f:
    data_list = json.load(f)

    positions = []
    rotations = []
    frames = []

    # Collect positions/rotations
    for entry in data_list:
    lat = entry["lat"]
    lon = entry["lon"]
    alt = entry["alt"]
    yaw = entry["yaw"]
    pitch = entry["pitch"]
    roll = entry["roll"]

    # Position in UTM Zone 6N
    t = geodetic_to_utm(lat, lon, alt)
    positions.append(t)

    # Orientation matrix
    R = rotation_matrix(yaw, pitch, roll)
    rotations.append(R)

    # Recenter translations (move mean position to origin)
    positions = np.array(positions)
    center = positions.mean(axis=0)
    positions -= center

    # Build frames list
    for entry, t, R in zip(data_list, positions, rotations):
    T = np.eye(4)
    T[:3, :3] = R
    T[:3, 3] = t

    frame = {
    "file_path": entry.get("file_path", f"images/frame_{entry['capture_id']:05d}.jpeg"),
    "transform_matrix": T.tolist()
    }
    frames.append(frame)

    # --- Use intrinsics stored in the first entry of data.json ---
    first = data_list[0]
    transforms = {
    "camera_model": first.get("camera_model", "OPENCV_FISHEYE"),
    "fl_x": first["fl_x"],
    "fl_y": first["fl_y"],
    "cx": first["cx"],
    "cy": first["cy"],
    "w": first["w"],
    "h": first["h"],
    "k1": first.get("k1", 0.0),
    "k2": first.get("k2", 0.0),
    "k3": first.get("k3", 0.0),
    "k4": first.get("k4", 0.0),
    "p1": first.get("p1", 0.0),
    "p2": first.get("p2", 0.0),
    "frames": frames
    }

    with open("transforms.json", "w") as f:
    json.dump(transforms, f, indent=2)

    print("Wrote transforms.json with", len(frames), "frames")

    Continue reading...

Compartilhe esta Página