Oussema Dhaouadi1,2,3,4,*, Zuria Bauer1, Johannes Meier2,4,
Olaf Wysocki3, Marc Pollefeys1,5, Daniel Cremers2,4
1 ETH Zurich 2 TU Munich 3 University of Cambridge
4 MCML 5 Microsoft
* Corresponding author
OrthoTrack is a training-free system for continuous, absolute, metrically scaled 6-DoF UAV trajectory estimation using public orthophotos and surface models as map priors—without GPS and without post-hoc alignment.
Requirements:
--fine_matcher turbo (or fast)--fine_matcher preciseconda or venv for environment managementgit clone https://github.com/cvg/orthotrack.git
cd orthotrack
conda create -n orthotrack python=3.10 -y
conda activate orthotrack
# Install a CUDA build of PyTorch for your platform first if needed:
# https://pytorch.org/get-started/locally/
bash setup.sh
setup.sh installs deps from requirements.txt (including Open3D, NumPy, and OpenCV versions needed for mesh rendering), clones RoMaV2 into thirdparty/, and installs it editable. RoMaV2 weights download on the first tracker run (torch.hub).
For headless mesh render (dataset/create_movingdrone.py --render), install EGL/OpenGL system libs if needed:
sudo apt-get install -y libegl1 libgles2 libgl1
⚠️ Set
PYTHONPATH=.when running Python scripts from the project root.
See dataset/README.md for MovingDrone, UAVD4L, and UAVScenes.
To author new MovingDrone scenes (mesh + GES trajectory, or frames + priors), use dataset/create_movingdrone.py — see dataset/movingdrone.md.
Minimal MovingDrone download (local path is always scenes/; the server hosts files under sequences/):
PYTHONPATH=. python -c "
from dataset.MovingDrone import MovingDrone
MovingDrone(dataset_dir='data/MovingDrone', sequences=['airport10'],
predownload=True, load_dop=True, load_dsm=True)
"
CLI defaults:
--fine_matcher precise,--coarse_matcher base,--skip_frames 1,--max_image_dim 1920.
Examples below useturbowhere noted for GPUs with ~8 GB VRAM.
PYTHONPATH=. python scripts/run_tracking.py \
--sequence_dir data/MovingDrone/scenes/airport10 \
--fine_matcher turbo \
--output outputs/tracking/airport10 \
--end_frame 30 \
--save_keyframe_vis
Drop --end_frame for the full video. On ≥16 GB VRAM, omit --fine_matcher turbo to use the default precise matcher.
Provide an MP4 (or frame folder), a GeoTIFF DOP, and a GeoTIFF DSM:
bash scripts/run_custom_data.sh path/to/video.mp4 path/to/ortho.tif path/to/dem.tif outputs/my_custom_flight
Or directly:
PYTHONPATH=. python scripts/run_tracking.py \
--footage path/to/video.mp4 \
--dop path/to/ortho.tif \
--dsm path/to/dem.tif \
--output outputs/my_custom_flight \
--fine_matcher turbo \
--save_keyframe_vis
Place intrinsics.json next to the video (or pass --intrinsics) when camera calibration is known; otherwise add --force_calibration.
Loops over test scenes in splits.json:
PYTHONPATH=. python scripts/run_orthotrack_benchmark.py \
--data_root data/MovingDrone \
--output_dir outputs/benchmark \
--fine_matcher turbo
Extra run_tracking.py flags (e.g. --save_keyframe_vis, --flow_method raft) are forwarded.
Requires separate third-party installs — not covered by setup.sh. See EVALUATION.md.
These settings matter more than matcher speed for accurate trajectories:
| Setting | Recommendation | Why |
|---|---|---|
| DOP / DSM resolution (GSD) | ≤ 0.2 m/px (e.g. DOP20) | Coarse orthophotos (≥ 0.4 m/px) often cause large localization drift. DOP and DSM should share the same CRS and fully cover the flight. |
| Video resolution | Native resolution; avoid unnecessary upscaling | --max_image_dim (default 1920) downscales large frames before matching. Very small inputs (≪ 720p) reduce match density. |
Frame rate / --skip_frames | skip_frames 1 (process every frame) | Optical-flow tracking needs temporal continuity. Use --skip_frames 2+ only to save time; quality drops when frames are sparse. |
| Matchers | precise + base (defaults) | turbo / fast are faster but weaker; use them only when VRAM or latency requires it. |
| Intrinsics | Match video size, or --force_calibration | Wrong focal length / FoV breaks PnP even with good maps. |
| Map coverage | DOP + DSM overlap the entire trajectory | First-frame failure usually means the orthophoto crop does not cover the UAV footprint. |
Interactive demo: Hugging Face Space — bundled examples use 0.2 m/px geodata and show live resolution warnings for custom uploads.
| Flag | Default | Description |
|---|---|---|
--fine_matcher | precise | Fine-stage RoMa matcher (turbo / fast / base / precise). |
--coarse_matcher | base | Coarse matcher for tile search and re-localization. |
--flow_method | lk | Inter-frame tracker: lk, waft, or a ptlflow name (e.g. raft). |
--skip_frames | 1 | Process every Nth frame (1 = all frames). |
--max_image_dim | 1920 | Downscale longest image side before matching (0 = no limit). |
--force_calibration | off | Ignore bundled intrinsics and sweep FoV on the first frame. |
--intrinsics | auto | Path to intrinsics.json (also auto-read next to footage). |
--save_keyframe_vis | off | Save 4-panel keyframe match visualizations. |
--lod_obj_dir | off | LoD mesh (.obj / .npz) for wireframe overlay scripts. |
--end_frame | all | Exclusive end index for partial runs / smoke tests. |
Run PYTHONPATH=. python scripts/run_tracking.py --help for the full argument list.
After processing a sequence, all data is saved to your specified --output directory:
results.csv — Per-frame 6-DoF absolute metric pose estimates.results.json — Comprehensive log containing processing times, device info, and configurations.tracking_results.png — An overhead trajectory plot of the flight path.keyframes/ — If --save_keyframe_vis is enabled, visualization images for each keyframe trigger.OrthoTrack includes a standalone script to generate a video with 3D building wireframes dynamically projected onto the drone footage using the estimated 6-DoF poses.
Example overlays are shown on the project page. Generate one locally after tracking:
PYTHONPATH=. python scripts/visualize_results.py \
--sequence_dir data/MovingDrone/scenes/airport10 \
--results outputs/tracking/airport10/results.json \
--output outputs/lod_overlay.mp4
If you are using custom data, you can provide the video and a custom LoD mesh directly (.npz, .obj, or .ply):
PYTHONPATH=. python scripts/visualize_results.py \
--video path/to/video.mp4 \
--lod path/to/custom_mesh.ply \
--results outputs/my_custom_flight/results.json \
--output outputs/lod_overlay_custom.mp4
If you use OrthoTrack or MovingDrone in your research, please cite:
@inproceedings{dhaouadi2026orthotrack,
title = {OrthoTrack: Continuous 6-DoF UAV Trajectory Estimation Anchored in Public Orthophotos},
author = {Dhaouadi, Oussema and Bauer, Zuria and Meier, Johannes and Wysocki, Olaf and Pollefeys, Marc and Cremers, Daniel},
booktitle = {European Conference on Computer Vision (ECCV)},
year = {2026}
}
This code is released under the CC BY-NC 4.0 license (non-commercial use only). Third-party components retain their original licenses.
Oussema Dhaouadi1,2,3,4,*, Zuria Bauer1, Johannes Meier2,4,
Olaf Wysocki3, Marc Pollefeys1,5, Daniel Cremers2,4
1 ETH Zurich 2 TU Munich 3 University of Cambridge
4 MCML 5 Microsoft
* Corresponding author
OrthoTrack is a training-free system for continuous, absolute, metrically scaled 6-DoF UAV trajectory estimation using public orthophotos and surface models as map priors—without GPS and without post-hoc alignment.
Requirements:
--fine_matcher turbo (or fast)--fine_matcher preciseconda or venv for environment managementgit clone https://github.com/cvg/orthotrack.git
cd orthotrack
conda create -n orthotrack python=3.10 -y
conda activate orthotrack
# Install a CUDA build of PyTorch for your platform first if needed:
# https://pytorch.org/get-started/locally/
bash setup.sh
setup.sh installs deps from requirements.txt (including Open3D, NumPy, and OpenCV versions needed for mesh rendering), clones RoMaV2 into thirdparty/, and installs it editable. RoMaV2 weights download on the first tracker run (torch.hub).
For headless mesh render (dataset/create_movingdrone.py --render), install EGL/OpenGL system libs if needed:
sudo apt-get install -y libegl1 libgles2 libgl1
⚠️ Set
PYTHONPATH=.when running Python scripts from the project root.
See dataset/README.md for MovingDrone, UAVD4L, and UAVScenes.
To author new MovingDrone scenes (mesh + GES trajectory, or frames + priors), use dataset/create_movingdrone.py — see dataset/movingdrone.md.
Minimal MovingDrone download (local path is always scenes/; the server hosts files under sequences/):
PYTHONPATH=. python -c "
from dataset.MovingDrone import MovingDrone
MovingDrone(dataset_dir='data/MovingDrone', sequences=['airport10'],
predownload=True, load_dop=True, load_dsm=True)
"
CLI defaults:
--fine_matcher precise,--coarse_matcher base,--skip_frames 1,--max_image_dim 1920.
Examples below useturbowhere noted for GPUs with ~8 GB VRAM.
PYTHONPATH=. python scripts/run_tracking.py \
--sequence_dir data/MovingDrone/scenes/airport10 \
--fine_matcher turbo \
--output outputs/tracking/airport10 \
--end_frame 30 \
--save_keyframe_vis
Drop --end_frame for the full video. On ≥16 GB VRAM, omit --fine_matcher turbo to use the default precise matcher.
Provide an MP4 (or frame folder), a GeoTIFF DOP, and a GeoTIFF DSM:
bash scripts/run_custom_data.sh path/to/video.mp4 path/to/ortho.tif path/to/dem.tif outputs/my_custom_flight
Or directly:
PYTHONPATH=. python scripts/run_tracking.py \
--footage path/to/video.mp4 \
--dop path/to/ortho.tif \
--dsm path/to/dem.tif \
--output outputs/my_custom_flight \
--fine_matcher turbo \
--save_keyframe_vis
Place intrinsics.json next to the video (or pass --intrinsics) when camera calibration is known; otherwise add --force_calibration.
Loops over test scenes in splits.json:
PYTHONPATH=. python scripts/run_orthotrack_benchmark.py \
--data_root data/MovingDrone \
--output_dir outputs/benchmark \
--fine_matcher turbo
Extra run_tracking.py flags (e.g. --save_keyframe_vis, --flow_method raft) are forwarded.
Requires separate third-party installs — not covered by setup.sh. See EVALUATION.md.
These settings matter more than matcher speed for accurate trajectories:
| Setting | Recommendation | Why |
|---|---|---|
| DOP / DSM resolution (GSD) | ≤ 0.2 m/px (e.g. DOP20) | Coarse orthophotos (≥ 0.4 m/px) often cause large localization drift. DOP and DSM should share the same CRS and fully cover the flight. |
| Video resolution | Native resolution; avoid unnecessary upscaling | --max_image_dim (default 1920) downscales large frames before matching. Very small inputs (≪ 720p) reduce match density. |
Frame rate / --skip_frames | skip_frames 1 (process every frame) | Optical-flow tracking needs temporal continuity. Use --skip_frames 2+ only to save time; quality drops when frames are sparse. |
| Matchers | precise + base (defaults) | turbo / fast are faster but weaker; use them only when VRAM or latency requires it. |
| Intrinsics | Match video size, or --force_calibration | Wrong focal length / FoV breaks PnP even with good maps. |
| Map coverage | DOP + DSM overlap the entire trajectory | First-frame failure usually means the orthophoto crop does not cover the UAV footprint. |
Interactive demo: Hugging Face Space — bundled examples use 0.2 m/px geodata and show live resolution warnings for custom uploads.
| Flag | Default | Description |
|---|---|---|
--fine_matcher | precise | Fine-stage RoMa matcher (turbo / fast / base / precise). |
--coarse_matcher | base | Coarse matcher for tile search and re-localization. |
--flow_method | lk | Inter-frame tracker: lk, waft, or a ptlflow name (e.g. raft). |
--skip_frames | 1 | Process every Nth frame (1 = all frames). |
--max_image_dim | 1920 | Downscale longest image side before matching (0 = no limit). |
--force_calibration | off | Ignore bundled intrinsics and sweep FoV on the first frame. |
--intrinsics | auto | Path to intrinsics.json (also auto-read next to footage). |
--save_keyframe_vis | off | Save 4-panel keyframe match visualizations. |
--lod_obj_dir | off | LoD mesh (.obj / .npz) for wireframe overlay scripts. |
--end_frame | all | Exclusive end index for partial runs / smoke tests. |
Run PYTHONPATH=. python scripts/run_tracking.py --help for the full argument list.
After processing a sequence, all data is saved to your specified --output directory:
results.csv — Per-frame 6-DoF absolute metric pose estimates.results.json — Comprehensive log containing processing times, device info, and configurations.tracking_results.png — An overhead trajectory plot of the flight path.keyframes/ — If --save_keyframe_vis is enabled, visualization images for each keyframe trigger.OrthoTrack includes a standalone script to generate a video with 3D building wireframes dynamically projected onto the drone footage using the estimated 6-DoF poses.
Example overlays are shown on the project page. Generate one locally after tracking:
PYTHONPATH=. python scripts/visualize_results.py \
--sequence_dir data/MovingDrone/scenes/airport10 \
--results outputs/tracking/airport10/results.json \
--output outputs/lod_overlay.mp4
If you are using custom data, you can provide the video and a custom LoD mesh directly (.npz, .obj, or .ply):
PYTHONPATH=. python scripts/visualize_results.py \
--video path/to/video.mp4 \
--lod path/to/custom_mesh.ply \
--results outputs/my_custom_flight/results.json \
--output outputs/lod_overlay_custom.mp4
If you use OrthoTrack or MovingDrone in your research, please cite:
@inproceedings{dhaouadi2026orthotrack,
title = {OrthoTrack: Continuous 6-DoF UAV Trajectory Estimation Anchored in Public Orthophotos},
author = {Dhaouadi, Oussema and Bauer, Zuria and Meier, Johannes and Wysocki, Olaf and Pollefeys, Marc and Cremers, Daniel},
booktitle = {European Conference on Computer Vision (ECCV)},
year = {2026}
}
This code is released under the CC BY-NC 4.0 license (non-commercial use only). Third-party components retain their original licenses.