Drone Survey Area reconstitution:
Problem statement:
Aerial drone images extracted from a drone video are sufficient to reconstitute the survey area with image selection to create a mosaic that fully covers the survey area. This method does away with the knowledge of flight path of the drone. Write a python implementation that places selections from the input on the tiles in a grid to increase the likelihood of match with the overall survey area.
Solution:
The following implementation assumes that the images have GPS/EXIF metadata and leverages OpenDroneMap to create a mosaic.
Usage:
pip install pyodm
docker run -p 3000:3000 opendronemap/nodeodm --test
Code:
#! /usr/bin/python
from pathlib import Path
import shutil
import sys
from pyodm import Node, exceptions
def find_images(input_folder: Path):
exts = {".jpg", ".jpeg", ".JPG", ".JPEG"}
images = sorted([str(p) for p in input_folder.iterdir() if p.suffix in exts])
return images
def pick_orthomosaic_file(results_dir: Path):
candidates = []
for ext in ("*.tif", "*.tiff", "*.png", "*.jpg", "*.jpeg"):
candidates.extend(results_dir.rglob(ext))
preferred = []
for p in candidates:
s = str(p).lower()
if "orthophoto" in s or "orthomosaic" in s or "odm_orthophoto" in s:
preferred.append(p)
if preferred:
preferred.sort(key=lambda p: (0 if p.suffix.lower() in [".tif", ".tiff"] else 1, len(str(p))))
return preferred[0]
if candidates:
candidates.sort(key=lambda p: (0 if p.suffix.lower() in [".tif", ".tiff"] else 1, len(str(p))))
return candidates[0]
return None
def reconstruct_mosaic(input_folder: str, node_url="localhost", node_port=3000):
input_path = Path(input_folder).resolve()
if not input_path.exists() or not input_path.is_dir():
raise FileNotFoundError(f"Folder not found: {input_path}")
images = find_images(input_path)
if len(images) < 3:
raise ValueError("Need at least 3 overlapping drone images for a meaningful mosaic.")
output_dir = input_path / "odm_results"
output_dir.mkdir(parents=True, exist_ok=True)
node = Node(node_url, port=node_port)
print(node.info())
options = {
"auto-boundary": True,
"crop": 0,
"fast-orthophoto": True,
"skip-post-processing": False,
"orthophoto-resolution": 5,
"use-exif": True,
"optimize-disk-space": True,
}
try:
task = node.create_task(images, options)
print("Task created:", task.info().task_id)
task.wait_for_completion()
task.download_assets(str(output_dir))
orthomosaic = pick_orthomosaic_file(output_dir)
if orthomosaic is None:
raise FileNotFoundError("No orthomosaic file was produced by ODM.")
final_name = input_path / f"{input_path.name}_orthomosaic{orthomosaic.suffix.lower()}"
shutil.copy2(orthomosaic, final_name)
print(f"Orthomosaic saved to: {final_name}")
return str(final_name)
except exceptions.NodeConnectionError as e:
raise RuntimeError(f"Cannot connect to NodeODM at {node_url}:{node_port}. Error: {e}")
except exceptions.TaskFailedError as e:
raise RuntimeError(f"ODM task failed: {e}")
if __name__ == "__main__":
if len(sys.argv) < 2:
print("Usage: python odm_mosaic.py /path/to/drone_images")
sys.exit(1)
reconstruct_mosaic(sys.argv[1])
References: compare to previous article:
No comments:
Post a Comment