-
Notifications
You must be signed in to change notification settings - Fork 1.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Render Omni-directional stereo video (360 3D VR) #1986
Conversation
Adding the omni-directional stereo camera model to render 3D 360 video and images for VR. This includes the camera models for the left and right eyes and additions to the render script to stack the left and right renders for the final render. I have also updated the documentation on the custom_dataset page to have information on ODS but let me know if there is a better place to put it.
nerfstudio/cameras/cameras.py
Outdated
ods_theta = -torch.pi * ((x - cx) / fx)[0] | ||
|
||
# local axes of ODS camera | ||
ods_x_axis = torch.tensor([1, 0, 0]).to("cuda") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably won't make much a difference here since the tensor is small, but it is better to construct the tensor on the target device, rather than move it to the device. Also avoid specifying cuda
since users may be using cpu.
somethings like:
ods_x_axis = torch.tensor([1, 0, 0], device=c2w.device)
nerfstudio/cameras/cameras.py
Outdated
# assign final camera origins | ||
c2w[..., :3, 3] = ods_origins_circle | ||
|
||
if CameraType.OMNIDIRECTIONALSTEREO_R.value in cam_types: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This code is very similar to the left eye. Can you consolidate in a helper function.
nerfstudio/cameras/cameras.py
Outdated
for value in cam_types: | ||
if value not in [CameraType.PERSPECTIVE.value, CameraType.FISHEYE.value, CameraType.EQUIRECTANGULAR.value]: | ||
if value not in [ | ||
CameraType.PERSPECTIVE.value, | ||
CameraType.FISHEYE.value, | ||
CameraType.EQUIRECTANGULAR.value, | ||
CameraType.OMNIDIRECTIONALSTEREO_L.value, | ||
CameraType.OMNIDIRECTIONALSTEREO_R.value, | ||
]: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you refactor this with the above code into,
for cam_type in cam_types:
if cam_type == CameraType.PERSPECTIVE.value:
...
continue
if cam_type == CameraType.FISHEYE.value:
...
continue
...
raise ValueError(f"Camera type {cam_type} not supported.")
nerfstudio/scripts/render.py
Outdated
@@ -342,6 +344,16 @@ def main(self) -> None: | |||
crop_data = get_crop_from_json(camera_path) | |||
camera_path = get_path_from_json(camera_path) | |||
|
|||
if camera_path.camera_type[0] == CameraType.OMNIDIRECTIONALSTEREO_L.value: | |||
# temp folder for writing left and right view renders | |||
temp_folder_path = os.path.splitext(str(self.output_path))[0] + "_temp" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Avoid os.path
, use pathlib instead
temp_folder_path = self.output_path.parent / (self.output_path.stem + "_temp" )
nerfstudio/scripts/render.py
Outdated
# temp folder for writing left and right view renders | ||
temp_folder_path = os.path.splitext(str(self.output_path))[0] + "_temp" | ||
Path(temp_folder_path).mkdir(parents=True, exist_ok=True) | ||
left_eye_path = Path(str(temp_folder_path) + "//ods_render_Left.mp4") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This can be cleaner since temp_folder_path
is a Path
. One reason what this is preferred is that file paths (ie //
) may be different between systems (windows vs linux).
left_eye_path = temp_folder_path / "ods_render_Left.mp4"
nerfstudio/scripts/render.py
Outdated
# declare paths for left and right renders | ||
|
||
left_eye_path = self.output_path | ||
right_eye_path = Path(str(left_eye_path.parent) + "//ods_render_Right.mp4") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
left_eye_path = left_eye_path.parent / "ods_render_Left.mp4"
nerfstudio/scripts/render.py
Outdated
self.output_path = Path(str(left_eye_path.parent)[:-5]) | ||
self.output_path.mkdir(parents=True, exist_ok=True) | ||
if self.image_format == "png": | ||
ffmpeg_ods_command = f'ffmpeg -y -pattern_type glob -i "{str(left_eye_path).replace(".mp4","")+"//*.png"}" -pattern_type glob -i "{str(right_eye_path).replace(".mp4","")+"//*.png"}" -filter_complex vstack "{str(self.output_path)+"//%05d.png"}"' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use pathlib here and below for renaming
{str(left_eye_path.with_suffix(".png"))}
Updates based on suggested code changes including adding a helper function for ODS and improving path creation. I also added a part where if a user tries to render a video with an output path that doesn't have an extension it will automatically add .mp4 to the end. Otherwise this usually results in an error when there is no file extension when rendering a video.
Updated python formatting for render and camera_paths script
nerfstudio/cameras/cameras.py
Outdated
CameraType.OMNIDIRECTIONALSTEREO_R.value, | ||
]: | ||
raise ValueError(f"Camera type {value} not supported.") | ||
for cam_type in cam_types: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry I should have been more specific. The logic above should exist in each if condition, ie:
for cam_type in cam_types:
if cam_type == CameraType.PERSPECTIVE.value:
mask = (self.camera_type[true_indices] == CameraType.PERSPECTIVE.value).squeeze(-1) # (num_rays)
mask = torch.stack([mask, mask, mask], dim=0)
directions_stack[..., 0][mask] = torch.masked_select(coord_stack[..., 0], mask).float()
directions_stack[..., 1][mask] = torch.masked_select(coord_stack[..., 1], mask).float()
directions_stack[..., 2][mask] = -1.0
elif cam_type == CameraType.EQUIRECTANGULAR.value:
# Fill in
# Add other cases
else:
raise ValueError(f"Camera type {cam_type} not supported.")
nerfstudio/cameras/cameras.py
Outdated
directions_stack[..., 2][mask] = torch.masked_select(-torch.cos(theta) * torch.sin(phi), mask).float() | ||
|
||
if CameraType.OMNIDIRECTIONALSTEREO_L.value in cam_types: | ||
def omni_directional_stereo_camera(eye): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add _
to the beginning since we don't expect it to be used outside this file, add types, and add docstring.
ie,
def _compute_rays_for_omnidirectional_stereo(eye: Literal["left", "right"]) -> Tuple[Float[Tensor "(fill in shape)", Float[Tensor "(fill in shape)"]:
""" Compute the rays for an omnidirectional stereo camera
Args:
eye: Which eye to compute rays for.
Returns:
A tuple containing the origins and the directions of the rays.
"""
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It doesn't look like you are using self
in this function, you could move it outside the class.
Added the type checking and doc string for the ODS camera rays function and reformatted the cam_type ray if statements.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
Adding the omni-directional stereo camera model to render 3D 360 video and images for VR. This includes the camera models for the left and right eyes and additions to the render script to stack the left and right renders for the final render.
I have also updated the documentation on the custom_dataset page to have information on ODS but let me know if there is a better place to put it.