Forum

Author Topic: COLMAP export mismatch with custom exporter (camera pose precision issue?)  (Read 7595 times)

Fele

  • Newbie
  • *
  • Posts: 1
    • View Profile
Hello,

I exported a 3D scan from Metashape as both FBX (with cameras) and COLMAP.
I then processed the FBX in Houdini and exported it back to COLMAP using a custom Python exporter. During my tests, I just imported the fbx and exported as COLMAP wihtout touching Scene.

When comparing the original Metashape COLMAP export with my Houdini-generated COLMAP, the results match almost perfectly (~99%), but I’m seeing small differences in the decimal values of the camera poses (rotation and translation). (Compare GIF below)

The discrepancies are minor, but I’d like to fix them to get the exact same results.


Are there documented conventions, common pitfalls, or known sources of small numerical differences when generating COLMAP camera poses (e.g. coordinate systems, transform direction, precision, etc.)?
From the Metashape side: is there any additional processing or convention applied during COLMAP export that might not be obvious from the format itself?
Would it be possible to look at the COLMAP exporter implementation used by Metashape, or is any reference implementation available for comparison?


Full disclosure: I don’t understand this level of math, and I can’t write code this complex. It’s "AI-slop coded".


Thanks
Felix



Relevant code snippets:


COLMAP pose export
Code: [Select]
def get_camera_pose(tsec):
    M = cam.worldTransformAtTime(tsec)

    # Extract rotation and translation (Houdini space)
    R = mat3_to_np(M.extractRotationMatrix3())
    t = np.array(M.extractTranslates())

    # Axis flip (COLMAP / Metashape convention)
    F = np.diag([1, -1, -1])

    # Apply coordinate system conversion
    t = F @ t
    R_flip = F @ R @ F

    # Convert rotation to quaternion
    qw, qx, qy, qz = quat_from_R(R_flip)

    # Convert to COLMAP-style translation
    t = -R_flip @ t

    return qw, qx, qy, qz, t[0], t[1], t[2]



Quaternion conversion
Code: [Select]
def quat_from_R(R):
    trace = np.trace(R)
    if trace > 0:
        s = np.sqrt(trace + 1.0) * 2
        qw = 0.25 * s
        qx = (R[2,1] - R[1,2]) / s
        qy = (R[0,2] - R[2,0]) / s
        qz = (R[1,0] - R[0,1]) / s
    else:
        i = np.argmax(np.diag(R))
        if i == 0:
            s = np.sqrt(1 + R[0,0] - R[1,1] - R[2,2]) * 2
            qw = (R[2,1] - R[1,2]) / s
            qx = 0.25 * s
            qy = (R[0,1] + R[1,0]) / s
            qz = (R[0,2] + R[2,0]) / s
        elif i == 1:
            s = np.sqrt(1 + R[1,1] - R[0,0] - R[2,2]) * 2
            qw = (R[0,2] - R[2,0]) / s
            qx = (R[0,1] + R[1,0]) / s
            qy = 0.25 * s
            qz = (R[1,2] + R[2,1]) / s
        else:
            s = np.sqrt(1 + R[2,2] - R[0,0] - R[1,1]) * 2
            qw = (R[1,0] - R[0,1]) / s
            qx = (R[0,2] + R[2,0]) / s
            qy = (R[1,2] + R[2,1]) / s
            qz = 0.25 * s

    q = np.array([qw, qx, qy, qz])
    return q / np.linalg.norm(q)


Point cloud axis flip
Code: [Select]
pts = np.array([p.attribValue("P") for p in points], dtype=np.float32)

# Axis conversion (same convention as cameras)
pts[:,1] *= -1
pts[:,2] *= -1


Camera intrinsics
Code: [Select]
fx = (focal_length_mm / sensor_width_mm) * image_width
fy = fx
cx = image_width * 0.5
cy = image_height * 0.5


Houdini COLMAP Camera:
0 0.881718624894961 0.421885433732614 0.197111127422913 0.075710968582176 -0.101257561880197 1.409076302291411 10.873460085694473 1 IMG_5331.jpg


Metashape COLMAP Camera:
0 0.8821619613006131 0.4213870373509246 0.1960171815014118 0.07616103559635141 -0.1290817466061904 1.418968976769138 10.87187852770778 0 IMG_5331.jpg



« Last Edit: April 17, 2026, 12:26:03 PM by Fele »

Alexey Pasumansky

  • Agisoft Technical Support
  • Hero Member
  • *****
  • Posts: 15710
    • View Profile
Hello Felix,

You can check the old script from our GitHub repository that was suggested to be used for COLMAP export before the feature has been implemented in GUI:
https://github.com/agisoft-llc/metashape-scripts/blob/2.2/src/export_for_gaussian_splatting.py
Best regards,
Alexey Pasumansky,
Agisoft LLC