diff --git a/nerfstudio/process_data/odm_utils.py b/nerfstudio/process_data/odm_utils.py index 70ad1b7f8..b9a4bffc8 100644 --- a/nerfstudio/process_data/odm_utils.py +++ b/nerfstudio/process_data/odm_utils.py @@ -104,35 +104,39 @@ def cameras2nerfds( cameras = json.loads(f.read()) camera_ids = list(cameras.keys()) - if len(camera_ids) > 1: - raise ValueError("Only one camera is supported") + if len(camera_ids) != 1: + raise ValueError("Expected exactly one ODM camera") camera_id = camera_ids[0] camera = cameras[camera_id] data = {} - if camera["projection_type"] in ["brown", "perspective"]: + projection_type = camera["projection_type"] + if projection_type in ["brown", "perspective"]: data["camera_model"] = CAMERA_MODELS["perspective"].value - elif camera["projection_type"] in ["fisheye", "fisheye_opencv"]: + elif projection_type in ["fisheye", "fisheye_opencv"]: data["camera_model"] = CAMERA_MODELS["fisheye"].value - elif camera["projection_type"] in ["spherical", "equirectangular"]: + elif projection_type in ["spherical", "equirectangular"]: data["camera_model"] = CAMERA_MODELS["equirectangular"].value else: - raise ValueError("Unsupported ODM camera model: " + data["camera_model"]) + raise ValueError("Unsupported ODM camera model: " + projection_type) - sensor_dict = {} - s = {"w": int(camera["width"]), "h": int(camera["height"])} + s: Dict[str, float] = {"w": int(camera["width"]), "h": int(camera["height"])} - s["fl_x"] = camera.get("focal_x", camera.get("focal")) * max(s["w"], s["h"]) - s["fl_y"] = camera.get("focal_y", camera.get("focal")) * max(s["w"], s["h"]) + if projection_type in ["spherical", "equirectangular"]: + s["fl_x"] = s["w"] / 2.0 + s["fl_y"] = s["h"] + s["cx"] = s["w"] / 2.0 + s["cy"] = s["h"] / 2.0 + else: + scale = max(s["w"], s["h"]) + s["fl_x"] = camera.get("focal_x", camera.get("focal")) * scale + s["fl_y"] = camera.get("focal_y", camera.get("focal")) * scale + s["cx"] = camera.get("c_x", 0.0) * scale + (s["w"] - 1.0) / 2.0 + s["cy"] = camera.get("c_y", 0.0) * scale + (s["h"] - 1.0) / 2.0 - s["cx"] = camera["c_x"] + (s["w"] - 1.0) / 2.0 - s["cy"] = camera["c_y"] + (s["h"] - 1.0) / 2.0 - - for p in ["k1", "k2", "p1", "p2", "k3"]: + for p in ["k1", "k2", "p1", "p2", "k3", "k4"]: if p in camera: s[p] = camera[p] - sensor_dict[camera_id] = s - shots_dict = {} reconstruction = None @@ -182,7 +186,7 @@ def cameras2nerfds( frame = {} frame["file_path"] = image_filename_map[fname].as_posix() - frame.update(sensor_dict[camera_id]) + frame.update(s) frame["transform_matrix"] = transform.tolist() frames.append(frame) diff --git a/tests/process_data/test_odm_utils.py b/tests/process_data/test_odm_utils.py new file mode 100644 index 000000000..41b51bc60 --- /dev/null +++ b/tests/process_data/test_odm_utils.py @@ -0,0 +1,35 @@ +"""Regression tests for OpenSfM camera intrinsics in ODM conversions.""" + +import json +from pathlib import Path + +import pytest + +from nerfstudio.process_data.odm_utils import cameras2nerfds + + +@pytest.mark.parametrize( + "camera, expected", + [ + ({"projection_type": "perspective", "focal": 0.5}, {"fl_x": 400, "cx": 399.5, "cy": 299.5}), + ({"projection_type": "brown", "focal": 0.5, "c_x": 0.1, "c_y": -0.05}, {"cx": 479.5, "cy": 259.5}), + ( + {"projection_type": "brown", "width": 600, "height": 800, "focal": 0.5, "c_x": 0.1, "c_y": -0.05}, + {"fl_x": 400, "fl_y": 400, "cx": 379.5, "cy": 359.5}, + ), + ({"projection_type": "fisheye_opencv", "focal": 0.5, "k4": 0.004}, {"k4": 0.004}), + ({"projection_type": "spherical"}, {"fl_x": 400, "fl_y": 600, "cx": 400, "cy": 300}), + ], +) +def test_odm_intrinsics(tmp_path, camera, expected): + cameras_path = tmp_path / "cameras.json" + cameras_path.write_text(json.dumps({"camera": {"width": 800, "height": 600, **camera}})) + reconstruction_path = tmp_path / "reconstruction.json" + shot = {"rotation": [0, 0, 0], "translation": [0, 0, 0]} + reconstruction_path.write_text(json.dumps([{"shots": {"sample.jpg": shot}}])) + cameras2nerfds( + {"sample": Path("sample.jpg")}, cameras_path, tmp_path / "shots.geojson", reconstruction_path, tmp_path + ) + frame = json.loads((tmp_path / "transforms.json").read_text())["frames"][0] + for key, value in expected.items(): + assert frame[key] == pytest.approx(value)