Fixing exterior orientation: skipping Align Photos with known EO¶
- Status: unverified
- Applies to: Metashape Pro 2.x — and PhotoScan 1.x via the same Import Cameras / triangulate workflow
- Edition: Pro
- Diátaxis: how-to
- Confidence: high
- Last reviewed: 2026-05-28
Confidence: high. The workflow (Import Cameras + Build Points /
triangulateTiePointswith fixed orientation) is directly attested by Agisoft support across multiple forum threads with permalinks. The Python pattern for settingcamera.transformfrom reference data is forum-attested with a complete recipe.
Problem¶
Your imagery's exterior orientation (camera position + rotation, EO) and interior orientation (intrinsics, IO) are already known to high precision — from a separate aerial-triangulation pass, from an inertial-navigation system (INS), from a custom external package, or from a previous Metashape project. You want to use this data as-is and skip Align Photos entirely. The bundle should not refine the camera parameters; it should generate products (point cloud, mesh, orthomosaic) using the known EO/IO.
Context¶
Metashape's Align Photos operation does two things:
- Match Photos — feature detection + pair matching to build the tie-point cloud.
- Align Cameras — bundle adjustment to solve for camera poses + intrinsics + tie-point 3D positions.
Skipping step 2 means you get the matched tie-point cloud without any pose refinement. That requires:
- Camera poses (
camera.transform) set to the known EO before starting. - Camera intrinsics (
sensor.calibrationand / orsensor.user_calib) set to the known IO. - The "fix" flags on each sensor enabled so the bundle does not modify them.
Solution¶
"Knowing the complete exterior orientation parameters (location and orientation angles) you can skip Align Cameras stage. It would work similar to Import Cameras feature that is Building Point cloud according to the imported EO/IO data — the images are matched, but EO and IO parameters are not refined." — Agisoft support, 2019-04-29, Metashape 1.5 (permalink)
Path 1 — Import Cameras command (built-in)¶
For data formats Metashape natively supports (BINGO, PATB, ORIMA Inpho, EnsoMOSAIC, Bundler, BlocksExchange, Pix4D):
- File → Import → Import Cameras… and select the file.
- Verify the imported cameras' positions and orientations in the Reference pane and the 3D view.
- Load the camera calibration via Tools → Camera Calibration → Load (XML format) and check Initial → Fix calibration.
- Workflow → Build Point Cloud (was: Build Dense Cloud in 1.x), or first run Workflow → Triangulate Tie Points if the tie-point cloud is needed independently.
The tie-point cloud is produced by re-running matching against the fixed camera poses; the cameras themselves are not adjusted.
Path 2 — Python (when the format is unsupported)¶
For external formats not in the Import Cameras dropdown, set
camera.transform programmatically.
Demo verified: ✗ — pending Tier 3 reproduction on a real Metashape install.
import Metashape
chunk = Metashape.app.document.chunk
crs = chunk.crs
T = chunk.transform.matrix
# Set each camera's transform from its reference values
for camera in chunk.cameras:
if camera.type != Metashape.Camera.Type.Regular:
continue
if not camera.reference.location or not camera.reference.rotation:
continue
# 1. Project geographic coordinates into the chunk's local frame
pos_world = crs.unproject(camera.reference.location)
pos_local = T.inv().mulp(pos_world)
# 2. Build the rotation matrix from omega/phi/kappa or yaw/pitch/roll
# — pick the one matching your reference convention
rot = Metashape.Utils.opk2mat(camera.reference.rotation)
# rot = Metashape.Utils.ypr2mat(camera.reference.rotation) # if YPR
# 3. The camera-to-world transform in chunk-local frame
# must also be expressed via crs.localframe at the position
local_frame = crs.localframe(pos_world)
rot_local = T.rotation() * local_frame.inv().rotation() * rot
# 4. Compose the 4x4 transform: rotation + translation
M = Metashape.Matrix.Translation(pos_local) * \
Metashape.Matrix.Rotation(rot_local)
camera.transform = M
(The construction of the rotation in chunk-local coordinates depends on whether the reference rotation is omega-phi-kappa photogrammetric convention or yaw-pitch-roll drone convention — see YPR rotation conventions for the choice and sign-convention details.)
After setting all camera transforms:
Demo verified: ✗ — pending Tier 3 reproduction on a real Metashape install.
# Lock each sensor's calibration before triangulating
for sensor in chunk.sensors:
sensor.fixed_calibration = True
sensor.fixed = True # fix all parameters: extrinsics + intrinsics
# (not just calibration)
# Generate the tie-point cloud using the fixed camera poses
chunk.matchPhotos(downscale=1, generic_preselection=False, reference_preselection=False)
chunk.triangulateTiePoints() # was Chunk.buildPoints in 1.x
The chunk.triangulateTiePoints() call performs the
re-triangulation step that Build Points in older versions
does — it computes 3D positions for each matched feature given
the fixed camera poses, but does not refine the poses themselves.
Verifying the EO is fixed¶
After running matching + triangulation:
Demo verified: ✗ — pending Tier 3 reproduction on a real Metashape install.
for camera in chunk.cameras[:3]:
print(f"{camera.label}")
print(f" reference.location: {camera.reference.location}")
print(f" reference.rotation: {camera.reference.rotation}")
print(f" transform translation: {camera.transform.translation()}")
If the bundle did not modify the cameras, the camera.transform
translations should match the reference values (after CRS
projection). If they differ, the fixed flag was not set
correctly.
Caveats¶
- Calibration must also be fixed. If
sensor.fixed_calibrationisFalse, the bundle's optional intrinsics refinement still runs and can perturb the focal length / distortion coefficients even when extrinsics are locked. Set both:
sensor.fixed_calibration = True # don't refine intrinsics
sensor.fixed = True # don't refine extrinsics
-
reference.rotationconvention matters. Metashape reads rotation references as omega-phi-kappa or yaw-pitch-roll depending on the Reference Settings → Rotation angles setting in the Reference pane. The Pythonopk2mat/ypr2mathelpers must match your convention; the wrong choice flips a pitch sign and produces cameras pointing in the opposite direction. See YPR rotation conventions. -
Without an existing chunk transform,
chunk.transform.matrixis identity. The first camera you set the transform for effectively defines the chunk's local frame origin. To preserve a specific origin, setchunk.transform.matrixfirst via Repositioning a chunk. -
Re-running Align Photos after this workflow will discard the imported EO unless Reset current alignment is unchecked AND the cameras are marked as already aligned in the Workspace pane.
See also¶
- Repositioning a chunk: moving the origin to a known point
- Drone metadata: DJI altitude semantics and RTK XMP accuracy tags
- YPR rotation conventions:
ypr2matvscamera.reference.rotation - Importing camera orientation: EXIF, OPK, yaw/pitch/roll
sensor.calibrationvssensor.user_calib— initial vs adjusted values
References¶
- Metashape Pro User Manual (2.3), ch. 3 General workflow, § Align Photos → Importing camera positions — describes the Import Cameras command and supported formats.
- Metashape Python API Reference (2.3.1):
Camera.transform,Sensor.fixed_calibration,Sensor.fixed,Chunk.triangulateTiePoints,Utils.opk2mat,Utils.ypr2mat,CoordinateSystem.localframe,CoordinateSystem.unproject. - Forum thread, Metashape External Orientation instead of Aerial Triangulation, 2019-2020 — the canonical Q&A; includes the Python recipe (msg 51996).
- Forum thread, Importing LIDAR Data, 2018 — earlier Q&A on the same workflow with photogrammetric data from external packages.