4
0
mirror of https://github.com/nerfstudio-project/nerfstudio.git synced 2026-11-11 02:24:38 +08:00

iPhone (Record3D) Dataset (#235)

* init branch commit.

* intrinsics done.

* created dataset for record3d.

* clean up and documentation.

* cleanup.

* fixed orientation.
This commit is contained in:
Alexander Kristoffersen 2022-08-16 23:24:49 -07:00 committed by GitHub
parent 261f1f3344
commit b471736a61
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 119 additions and 5 deletions

View File

@ -24,6 +24,7 @@ from typing import Optional, Union
import imageio
import numpy as np
import torch
from scipy.spatial.transform import Rotation
from nerfactory.dataloaders.colmap_utils import (
read_cameras_binary,
@ -277,7 +278,7 @@ class Mipnerf360(Dataset):
aabb_scale = 4
@classmethod
def _normalize_orientation(cls, poses: np.ndarray):
def normalize_orientation(cls, poses: np.ndarray):
"""Set the _up_ direction to be in the positive Y direction.
Args:
@ -341,7 +342,7 @@ class Mipnerf360(Dataset):
poses = np.concatenate([poses[:, :, 1:2], -poses[:, :, 0:1], poses[:, :, 2:]], axis=-1)
# Center poses and rotate. (Compute up from average of all poses)
poses = self._normalize_orientation(poses)
poses = self.normalize_orientation(poses)
# Scale factor used in mipnerf
if self.auto_scale:
@ -376,6 +377,110 @@ class Mipnerf360(Dataset):
return dataset_inputs
@dataclass
class Record3D(Dataset):
"""Record3D Dataset
Args:
data_directory: Location of data
downscale_factor: How much to downscale images. Defaults to 1.
val_skip: 1/val_skip images to use for validation. Defaults to 8.
aabb_scale: Scene scale, Defaults to 4.0.
"""
data_directory: str
downscale_factor: int = 1
val_skip: int = 8
aabb_scale = 4.0
def _generate_dataset_inputs(self, split: str = "train") -> DatasetInputs:
abs_dir = get_absolute_path(self.data_directory)
image_dir = os.path.join(abs_dir, "rgb")
if not os.path.exists(image_dir):
raise ValueError(f"Image directory {image_dir} doesn't exist")
ext = ".jpg"
image_filenames = []
for f in os.listdir(image_dir):
image_filenames.append(os.path.join(image_dir, f))
image_filenames = sorted(image_filenames, key=lambda fn: int(os.path.basename(fn)[: -len(ext)]))
num_images = len(image_filenames)
metadata_path = os.path.join(abs_dir, "metadata.json")
metadata_dict = load_from_json(metadata_path)
poses_data = np.array(metadata_dict["poses"])
# (N, 3, 4)
poses = np.concatenate(
[Rotation.from_quat(poses_data[:, :4]).as_matrix(), poses_data[:, 4:, None]],
axis=-1,
).astype(np.float32)
# Normalization similar to Mipnerf360
poses = Mipnerf360.normalize_orientation(poses)
bottom = np.reshape([0, 0, 0, 1.0], [1, 4])
bottom = np.tile(np.reshape(bottom, [1, 1, 4]), [poses.shape[0], 1, 1])
poses = np.concatenate([poses[:, :3, :4], bottom], -2).astype(np.float32)
rotation_matrix = np.array(
[
[1.0, 0.0, 0.0, 0.0],
[0.0, 0.0, -1.0, 0.0],
[0.0, 1.0, 0.0, 0.0],
[0.0, 0.0, 0.0, 1.0],
],
dtype=np.float32,
)
poses = rotation_matrix @ poses
idx_test = np.arange(num_images)[:: self.val_skip]
idx_train = np.array([i for i in np.arange(num_images) if i not in idx_test])
idx = idx_train if split == "train" else idx_test
if num_images != poses.shape[0]:
raise RuntimeError(f"Different number of images ({num_images}), and poses ({poses.shape[0]})")
image_filenames = np.array(image_filenames)[idx]
poses = poses[idx]
# Centering poses
poses[:, :3, 3] = poses[:, :3, 3] - np.mean(poses[:, :3, 3], axis=0)
camera_to_world = torch.from_numpy(poses[:, :3, :4]) # camera to world transform
# Camera intrinsics
K = np.array(metadata_dict["K"]).reshape((3, 3)).T
focal_length = K[0, 0]
H = metadata_dict["h"]
W = metadata_dict["w"]
# TODO(akristoffersen): The metadata dict comes with principle points,
# but caused errors in image coord indexing. Should update once that is fixed.
cx, cy = W / 2, H / 2
num_cameras = len(image_filenames)
num_intrinsics_params = 3
intrinsics = torch.ones((num_cameras, num_intrinsics_params), dtype=torch.float32)
intrinsics *= torch.tensor([cx, cy, focal_length])
# scene_bounds = SceneBounds.from_camera_poses(camera_to_world, self.aabb_scale)
aabb = torch.tensor([[-1, -1, -1], [1, 1, 1]], dtype=torch.float32) * self.aabb_scale
scene_bounds = SceneBounds(aabb=aabb)
dataset_inputs = DatasetInputs(
image_filenames=image_filenames,
downscale_factor=1,
intrinsics=intrinsics,
camera_to_world=camera_to_world,
scene_bounds=scene_bounds,
)
return dataset_inputs
@dataclass
class Friends(Dataset):
"""Friends Dataset

View File

@ -99,6 +99,13 @@ class SceneBounds:
aabb = torch.tensor([json_[0], json_[1]])
return SceneBounds(aabb=aabb)
@staticmethod
def from_camera_poses(poses: TensorType[..., 3, 4], scale_factor: float) -> "SceneBounds":
"""Returns the instance of SceneBounds that fully envelopes a set of poses"""
xyzs = poses[..., :3, -1]
aabb = torch.stack([torch.min(xyzs, dim=0)[0], torch.max(xyzs, dim=0)[0]])
return SceneBounds(aabb=aabb * scale_factor)
@dataclass
class DatasetInputs:
@ -106,12 +113,14 @@ class DatasetInputs:
Args:
image_filenames: Filenames for the images.
intrinsics: Tensor of per-image camera intrisics.
camera_to_world: Tensor of per-image c2w matrices, in [R | t] format.
...
"""
image_filenames: List[str]
intrinsics: torch.Tensor
camera_to_world: torch.Tensor
intrinsics: TensorType["num_cameras", "num_intrinsics_params"]
camera_to_world: TensorType["num_cameras", 3, 4]
downscale_factor: int = 1
mask_filenames: Optional[List[str]] = None
depth_filenames: Optional[List[str]] = None

View File

@ -149,7 +149,7 @@ def main(
Args:
config_name: Name of the config file to use.
checkpoint_dir: Directory to load the checkpoint from.
rendered_output_name: Name of the renderer output to use.
rendered_output_name: Name of the renderer output to use (rgb, depth, etc.).
method: Method to use for evaluation. PSNR computes metrics, TRAJ renders a trajectory.
traj: Trajectory to render.
output_filename: Name of the output file.