mirror of
https://github.com/nerfstudio-project/nerfstudio.git
synced 2026-11-13 11:24:00 +08:00
Render VR180 video (#2261)
* Render VR180 video Adding the VR180 camera model to render VR180 video and images. I condensed the omnidirectional stereo and VR180 render post processing in the render script. I have updated the documentation for VR180 support. * Condensing formatting of camera types Condensing the if statement for camera type intrinsics.
This commit is contained in:
1 parent
4f2157cd58
commit
8682e481b1
5 files changed
+187
-47
No files matched your search
@@ -6,7 +6,7 @@
|
||||
|
||||
## Overview
|
||||
|
||||
This Blender add-on allows for compositing with a Nerfstudio render as a background layer by generating a camera path JSON file from the Blender camera path, as well as a way to import Nerfstudio JSON files as a Blender camera baked with the Nerfstudio camera path. This add-on also allows compositing multiple NeRF objects into a NeRF scene. This is achieved by importing a mesh or point-cloud representation of the NeRF scene from Nerfstudio into Blender and getting the camera coordinates relative to the transformations of the NeRF representation. Dynamic FOV from the Blender camera is supported and will match the Nerfstudio render. Perspective, equirectangular, and omnidirectional stereo (VR 360) cameras are supported.
|
||||
This Blender add-on allows for compositing with a Nerfstudio render as a background layer by generating a camera path JSON file from the Blender camera path, as well as a way to import Nerfstudio JSON files as a Blender camera baked with the Nerfstudio camera path. This add-on also allows compositing multiple NeRF objects into a NeRF scene. This is achieved by importing a mesh or point-cloud representation of the NeRF scene from Nerfstudio into Blender and getting the camera coordinates relative to the transformations of the NeRF representation. Dynamic FOV from the Blender camera is supported and will match the Nerfstudio render. Perspective, equirectangular, VR180, and omnidirectional stereo (VR 360) cameras are supported.
|
||||
|
||||
<center>
|
||||
<img width="800" alt="image" src="https://user-images.githubusercontent.com/9502341/211442247-99d1ebc7-3ef9-46f7-9bcc-0e18553f19b7.PNG">
|
||||
@@ -89,7 +89,7 @@ This Blender add-on allows for compositing with a Nerfstudio render as a backgro
|
||||
- The generated JSON camera path follows the user specified frame start, end, and step fields in the Output Properties in Blender. The JSON file specifies the user specified x and y render resolutions at the given % in the Output Properties.
|
||||
- The add-on computes the camera coordinates based on the active camera in the scene.
|
||||
- FOV animated changes of the camera will be matched with the NeRF render.
|
||||
- Perspective, equirectangular, and omnidirectional stereo cameras are supported and can be selected within Blender.
|
||||
- Perspective, equirectangular, VR180, and omnidirectional stereo cameras are supported and can be configured within Blender.
|
||||
- The generated JSON file can be imported into Nerfstudio. Each keyframe of the camera transform in the frame sequence in Blender will be a keyframe in Nerfstudio. The exported JSON camera path is baked, where each frame in the sequence is a keyframe. This is to ensure that frame interpolation across Nerfstudio and Blender do not differ.
|
||||
<center>
|
||||
<img width="800" alt="image" src="https://user-images.githubusercontent.com/9502341/211245108-587a97f7-d48d-4515-b09a-5644fd966298.png">
|
||||
@@ -101,7 +101,10 @@ This Blender add-on allows for compositing with a Nerfstudio render as a backgro
|
||||
- It is recommended to export a high fidelity mesh as the NeRF representation from Nerfstudio. However if that is not possible or the scene is too large, a point cloud representation also works.
|
||||
- For compositing, it is recommended to convert the video render into image frames to ensure the Blender and NeRF renders are in sync.
|
||||
- Currently, dynamic camera focus is not supported.
|
||||
- For compositing Blender objects or NeRFs with Nerfstudio omnidirectional stereo renders (VR 360), configure the Blender camera to be equirectangular and enable stereoscopy in the Output Properties. Under the Stereoscopy panel the Blender camera settings, change the mode to "Parallel", set the Interocular Distance to 0.064 m, and checkmark "Spherical Stereo".
|
||||
- Compositing with Blender Objects and VR180 or ODS Renders
|
||||
- Configure the Blender camera to be panoramic equirectangular and enable stereoscopy in the Output Properties. For the VR180 Blender camera, set the panoramic longitude min and max to -90 and 90.
|
||||
- Under the Stereoscopy panel the Blender camera settings, change the mode to "Parallel", set the Interocular Distance to 0.064 m, and checkmark "Spherical Stereo".
|
||||
|
||||
<center>
|
||||
<img width="300" alt="image" src="https://github-production-user-asset-6210df.s3.amazonaws.com/9502341/253217833-fd607601-2b81-48ab-ac5d-e55514a588da.png">
|
||||
</center>
|
||||
|
||||
@@ -347,25 +347,37 @@ The pixels representing the distorted hand and head are obviously not useful in
|
||||
|
||||
If cropping only needs to be done from the bottom, you can use the `--crop-bottom [num]` argument which would be the same as doing `--crop-factor 0.0 [num] 0.0 0.0`
|
||||
|
||||
## 🥽 Render Omni-directional Stereo (360 VR)
|
||||
## 🥽 Render VR Video
|
||||
|
||||
Stereo equirectangular rendering for VR video is supported as omni-directional stereo video and image rendering. This outputs two equirectangular renders vertically stacked, one for each eye. Omni-directional stereo (ODS) is a method to render VR 3D 360 videos, and may introduce slight depth distortions for close objects. For additional information on how ODS works, refer to this [writeup](https://developers.google.com/vr/jump/rendering-ods-content.pdf).
|
||||
Stereo equirectangular rendering for VR video is supported as VR180 and omni-directional stereo (360 VR) Nerfstudio camera types for video and image rendering.
|
||||
|
||||
### Omni-directional Stereo (360 VR)
|
||||
This outputs two equirectangular renders vertically stacked, one for each eye. Omni-directional stereo (ODS) is a method to render VR 3D 360 videos, and may introduce slight depth distortions for close objects. For additional information on how ODS works, refer to this [writeup](https://developers.google.com/vr/jump/rendering-ods-content.pdf).
|
||||
|
||||
<center>
|
||||
<img img width="300" src="https://github-production-user-asset-6210df.s3.amazonaws.com/9502341/240530353-5c05d12b-6e42-42bb-8ff5-f54aa30ca262.jpg">
|
||||
<img img width="300" src="https://github-production-user-asset-6210df.s3.amazonaws.com/9502341/255423390-ff0710f1-29ce-47b2-85f9-922084cab297.jpg">
|
||||
</center>
|
||||
|
||||
To render in ODS it is essential to adjust the NeRF to have an approximately true-to-life real world scale (adjustable in the camera path) to ensure that the scene depth and IPD (distance between the eyes) is appropriate for the render to be viewable in VR. You can adjust the scene scale with the [Nerfstudio Blender Add-on](https://docs.nerf.studio/en/latest/extensions/blender_addon.html) by appropriately scaling a point cloud representation of the NeRF.
|
||||
|
||||
### VR180
|
||||
This outputs two 180 deg equirectangular renders horizontally stacked, one for each eye. VR180 is a video format for VR 3D 180 videos. Unlike in omnidirectional stereo, VR180 content only displays front facing content.
|
||||
|
||||
<center>
|
||||
<img img width="375" src="https://github-production-user-asset-6210df.s3.amazonaws.com/9502341/255379444-b90f5b3c-5021-4659-8732-17725669914e.jpeg">
|
||||
</center>
|
||||
|
||||
### Setup instructions
|
||||
To render for VR video it is essential to adjust the NeRF to have an approximately true-to-life real world scale (adjustable in the camera path) to ensure that the scene depth and IPD (distance between the eyes) is appropriate for the render to be viewable in VR. You can adjust the scene scale with the [Nerfstudio Blender Add-on](https://docs.nerf.studio/en/latest/extensions/blender_addon.html) by appropriately scaling a point cloud representation of the NeRF.
|
||||
Results may be unviewable if the scale is not set appropriately. The IPD is set at 64mm by default but only is accurate when the NeRF scene is true to scale.
|
||||
|
||||
For good quality renders, it is recommended to render at 4096x2048 per eye, or 2048x1024 per eye. Render resolutions must be specified in the camera path in a 2:1 aspect ratio. The final stacked render output will automatically be constructed as 1:1.
|
||||
For good quality renders, it is recommended to render at high resolutions (For ODS: 4096x2048 per eye, or 2048x1024 per eye. For VR180: 4096x4096 per eye or 2048x2048 per eye). Render resolutions for a single eye are specified in the camera path. For VR180, resolutions must be in a 1:1 aspect ratio. For ODS, resolutions must be in a 2:1 aspect ratio. The final stacked render output will automatically be constructed (with aspect ratios for VR180 as 2:1 and ODS as 1:1).
|
||||
|
||||
:::{admonition} Note
|
||||
:class: info
|
||||
If you are rendering an image sequence, it is recommended to render as png instead of jpeg, since the png will appear clearer. However, file sizes can be significantly larger with png.
|
||||
:::
|
||||
|
||||
To render with the ODS camera:
|
||||
To render with the VR videos camera:
|
||||
1. Use the [Nerfstudio Blender Add-on](https://docs.nerf.studio/en/latest/extensions/blender_addon.html) to set the scale of the NeRF scene and create the camera path
|
||||
- Export a point cloud representation of the NeRF
|
||||
- Import the point cloud representation in Blender and enable the Nerfstudio Blender Add-on
|
||||
@@ -374,23 +386,38 @@ To render with the ODS camera:
|
||||
- To place the camera at the correct height from the ground in the scene, you can create a cylinder representing the viewer vertically scaled to the viewer’s height, and place the camera at eye level.
|
||||
- Animate the camera movement as needed
|
||||
- Create the camera path JSON file with the Nerfstudio Blender Add-on
|
||||
|
||||
2. Edit the JSON camera path file
|
||||
- Open the camera path JSON file and specify the `camera_type` as `omnidirectional`
|
||||
- Specify the `render_height` and `render_width` to the resolution of a single eye. The width:height aspect ratio must be 2:1. Recommended resolutions are 4096x2048 or 2048x1024.
|
||||
|
||||
**Omni-directional Stereo (360 VR)**
|
||||
- Open the camera path JSON file and specify the `camera_type` as `omnidirectional`
|
||||
- Specify the `render_height` and `render_width` to the resolution of a single eye. The width:height aspect ratio must be 2:1. Recommended resolutions are 4096x2048 or 2048x1024.
|
||||
<center>
|
||||
<img img width="250" src="https://github-production-user-asset-6210df.s3.amazonaws.com/9502341/240530527-22d14276-ac2c-46a5-a4b0-4785b7413241.png">
|
||||
</center>
|
||||
|
||||
|
||||
**VR180**
|
||||
- Open the camera path JSON file and specify the `camera_type` as `vr180`
|
||||
- Specify the `render_height` and `render_width` to the resolution of a single eye. The width:height aspect ratio must be 1:1. Recommended resolutions are 4096x4096 or 2048x2048.
|
||||
<center>
|
||||
<img img width="190" src="https://github-production-user-asset-6210df.s3.amazonaws.com/9502341/255379889-83b7fd09-ce8f-4868-8838-7be9b63f01b4.png">
|
||||
</center>
|
||||
|
||||
- Save the camera path and render the NeRF
|
||||
<center>
|
||||
<img img width="300" src="https://github-production-user-asset-6210df.s3.amazonaws.com/9502341/240530527-22d14276-ac2c-46a5-a4b0-4785b7413241.png">
|
||||
</center>
|
||||
|
||||
|
||||
:::{admonition} Note
|
||||
:class: info
|
||||
If the depth of the scene is unviewable and looks too close or expanded when viewing the render in VR, the scale of the NeRF may be set too small. If there is almost no discernible depth, the scale of the NeRF may be too large. Getting the right scale may take some experimentation, so it is recommended to either render at a much lower resolution or just one frame to ensure the depth and render is viewable in the VR headset.
|
||||
:::
|
||||
|
||||
### Additional Notes
|
||||
- Rendering with ODS can take significantly longer than traditional renders due to higher resolutions and needing to render a left and right eye view for each frame. Render times may grow exponentially with larger resolutions.
|
||||
- When rendering ODS content, Nerfstudio will first render the left eye, then the right eye, and finally vertically stack the renders. During this process, Nerfstudio will create a temporary folder to store the left and right eye renders and delete this folder once the final renders are stacked.
|
||||
#### Additional Notes
|
||||
- Rendering with VR180 or ODS can take significantly longer than traditional renders due to higher resolutions and needing to render a left and right eye view for each frame. Render times may grow exponentially with larger resolutions.
|
||||
- When rendering VR180 or ODS content, Nerfstudio will first render the left eye, then the right eye, and finally vertically stack the renders. During this process, Nerfstudio will create a temporary folder to store the left and right eye renders and delete this folder once the final renders are stacked.
|
||||
- If rendering content where the camera is stationary for many frames, it is recommended to only render once at that position and extend the time in a video editor since ODS renders can take a lot of time to render.
|
||||
- It is recommended to render a preliminary render at a much lower resolution or frame rate to test and ensure that the depth and camera position look accurate in VR.
|
||||
- The IPD can be modified in the `cameras.py` script as the variable `vr_ipd` (default is 64 mm).
|
||||
- If using the Nerfstudio Blender Add-on to composite Blender objects or NeRFs with the omnidirectional render, change the Stereoscopy mode to "Parallel" set the Interocular Distance to 0.064 m.
|
||||
- Compositing with Blender Objects and VR180 or ODS Renders
|
||||
- Configure the Blender camera as panoramic and equirectangular. For the VR180 Blender camera, set the panoramic longitude min and max to -90 and 90.
|
||||
- Change the Stereoscopy mode to "Parallel" set the Interocular Distance to 0.064 m.
|
||||
@@ -141,6 +141,8 @@ def get_path_from_json(camera_path: Dict[str, Any]) -> Cameras:
|
||||
camera_type = CameraType.EQUIRECTANGULAR
|
||||
elif camera_path["camera_type"].lower() == "omnidirectional":
|
||||
camera_type = CameraType.OMNIDIRECTIONALSTEREO_L
|
||||
elif camera_path["camera_type"].lower() == "vr180":
|
||||
camera_type = CameraType.VR180_L
|
||||
else:
|
||||
camera_type = CameraType.PERSPECTIVE
|
||||
|
||||
@@ -151,11 +153,13 @@ def get_path_from_json(camera_path: Dict[str, Any]) -> Cameras:
|
||||
# pose
|
||||
c2w = torch.tensor(camera["camera_to_world"]).view(4, 4)[:3]
|
||||
c2ws.append(c2w)
|
||||
if (
|
||||
camera_type == CameraType.EQUIRECTANGULAR
|
||||
or camera_type == CameraType.OMNIDIRECTIONALSTEREO_L
|
||||
or camera_type == CameraType.OMNIDIRECTIONALSTEREO_R
|
||||
):
|
||||
if camera_type in [
|
||||
CameraType.EQUIRECTANGULAR,
|
||||
CameraType.OMNIDIRECTIONALSTEREO_L,
|
||||
CameraType.OMNIDIRECTIONALSTEREO_R,
|
||||
CameraType.VR180_L,
|
||||
CameraType.VR180_R,
|
||||
]:
|
||||
fxs.append(image_width / 2)
|
||||
fys.append(image_height)
|
||||
else:
|
||||
|
||||
@@ -46,6 +46,8 @@ class CameraType(Enum):
|
||||
EQUIRECTANGULAR = auto()
|
||||
OMNIDIRECTIONALSTEREO_L = auto()
|
||||
OMNIDIRECTIONALSTEREO_R = auto()
|
||||
VR180_L = auto()
|
||||
VR180_R = auto()
|
||||
|
||||
|
||||
CAMERA_MODEL_TO_TYPE = {
|
||||
@@ -58,6 +60,8 @@ CAMERA_MODEL_TO_TYPE = {
|
||||
"EQUIRECTANGULAR": CameraType.EQUIRECTANGULAR,
|
||||
"OMNIDIRECTIONALSTEREO_L": CameraType.OMNIDIRECTIONALSTEREO_L,
|
||||
"OMNIDIRECTIONALSTEREO_R": CameraType.OMNIDIRECTIONALSTEREO_R,
|
||||
"VR180_L": CameraType.VR180_L,
|
||||
"VR180_R": CameraType.VR180_R,
|
||||
}
|
||||
|
||||
|
||||
@@ -708,6 +712,60 @@ class Cameras(TensorDataclass):
|
||||
|
||||
return ods_origins_circle, directions_stack
|
||||
|
||||
def _compute_rays_for_vr180(
|
||||
eye: Literal["left", "right"]
|
||||
) -> Tuple[Float[Tensor, "num_rays_shape 3"], Float[Tensor, "3 num_rays_shape 3"]]:
|
||||
"""Compute the rays for a VR180 camera
|
||||
|
||||
Args:
|
||||
eye: Which eye to compute rays for.
|
||||
|
||||
Returns:
|
||||
A tuple containing the origins and the directions of the rays.
|
||||
"""
|
||||
# Directions calculated similarly to equirectangular
|
||||
vr180_cam_type = CameraType.VR180_R.value if eye == "right" else CameraType.VR180_L.value
|
||||
mask = (self.camera_type[true_indices] == vr180_cam_type).squeeze(-1)
|
||||
mask = torch.stack([mask, mask, mask], dim=0)
|
||||
|
||||
# adjusting theta range to +/-90 deg
|
||||
theta = -torch.pi * ((x - cx) / (fx * 2))[0]
|
||||
phi = torch.pi * (0.5 - coord_stack[..., 1])
|
||||
|
||||
directions_stack[..., 0][mask] = torch.masked_select(-torch.sin(theta) * torch.sin(phi), mask).float()
|
||||
directions_stack[..., 1][mask] = torch.masked_select(torch.cos(phi), mask).float()
|
||||
directions_stack[..., 2][mask] = torch.masked_select(-torch.cos(theta) * torch.sin(phi), mask).float()
|
||||
|
||||
vr_ipd = 0.064 # IPD in meters (note: scale of NeRF must be true to life and can be adjusted with the Blender add-on)
|
||||
isRightEye = 1 if eye == "right" else -1
|
||||
|
||||
# find VR180 camera position
|
||||
c2w = self.camera_to_worlds[true_indices]
|
||||
assert c2w.shape == num_rays_shape + (3, 4)
|
||||
transposedC2W = c2w[0][0].t()
|
||||
vr180_cam_position = transposedC2W[3].repeat(c2w.shape[1], 1)
|
||||
|
||||
rotation = c2w[..., :3, :3]
|
||||
|
||||
-torch.pi * ((x - cx) / fx)[0]
|
||||
|
||||
# interocular axis of the VR180 camera
|
||||
vr180_x_axis = torch.tensor([1, 0, 0], device=c2w.device)
|
||||
|
||||
# VR180 ray origins of horizontal offset
|
||||
vr180_origins = isRightEye * (vr_ipd / 2.0) * (vr180_x_axis.repeat(c2w.shape[1], 1))
|
||||
|
||||
# rotate origins to match the camera rotation
|
||||
for i in range(vr180_origins.shape[0]):
|
||||
vr180_origins[i] = rotation[0][0] @ vr180_origins[i] + vr180_cam_position[0]
|
||||
|
||||
vr180_origins = vr180_origins.unsqueeze(0).repeat(c2w.shape[0], 1, 1)
|
||||
|
||||
# assign final camera origins
|
||||
c2w[..., :3, 3] = vr180_origins
|
||||
|
||||
return vr180_origins, directions_stack
|
||||
|
||||
for cam in cam_types:
|
||||
if CameraType.PERSPECTIVE.value in cam_types:
|
||||
mask = (self.camera_type[true_indices] == CameraType.PERSPECTIVE.value).squeeze(-1) # (num_rays)
|
||||
@@ -755,6 +813,17 @@ class Cameras(TensorDataclass):
|
||||
ods_origins_circle, directions_stack = _compute_rays_for_omnidirectional_stereo("right")
|
||||
# assign final camera origins
|
||||
c2w[..., :3, 3] = ods_origins_circle
|
||||
|
||||
elif CameraType.VR180_L.value in cam_types:
|
||||
vr180_origins, directions_stack = _compute_rays_for_vr180("left")
|
||||
# assign final camera origins
|
||||
c2w[..., :3, 3] = vr180_origins
|
||||
|
||||
elif CameraType.VR180_R.value in cam_types:
|
||||
vr180_origins, directions_stack = _compute_rays_for_vr180("right")
|
||||
# assign final camera origins
|
||||
c2w[..., :3, 3] = vr180_origins
|
||||
|
||||
else:
|
||||
raise ValueError(f"Camera type {cam} not supported.")
|
||||
|
||||
|
||||
@@ -344,16 +344,23 @@ class RenderCameraPath(BaseRender):
|
||||
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:
|
||||
if (
|
||||
camera_path.camera_type[0] == CameraType.OMNIDIRECTIONALSTEREO_L.value
|
||||
or camera_path.camera_type[0] == CameraType.VR180_L.value
|
||||
):
|
||||
# temp folder for writing left and right view renders
|
||||
temp_folder_path = self.output_path.parent / (self.output_path.stem + "_temp")
|
||||
|
||||
Path(temp_folder_path).mkdir(parents=True, exist_ok=True)
|
||||
left_eye_path = temp_folder_path / "ods_render_Left.mp4"
|
||||
left_eye_path = temp_folder_path / "render_left.mp4"
|
||||
|
||||
self.output_path = left_eye_path
|
||||
|
||||
CONSOLE.print("[bold green]:goggles: Omni-directional Stereo VR :goggles:")
|
||||
if camera_path.camera_type[0] == CameraType.OMNIDIRECTIONALSTEREO_L.value:
|
||||
CONSOLE.print("[bold green]:goggles: Omni-directional Stereo VR :goggles:")
|
||||
else:
|
||||
CONSOLE.print("[bold green]:goggles: VR180 :goggles:")
|
||||
|
||||
CONSOLE.print("Rendering left eye view")
|
||||
|
||||
# add mp4 suffix to video output if none is specified
|
||||
@@ -374,14 +381,21 @@ class RenderCameraPath(BaseRender):
|
||||
colormap_options=self.colormap_options,
|
||||
)
|
||||
|
||||
if camera_path.camera_type[0] == CameraType.OMNIDIRECTIONALSTEREO_L.value:
|
||||
if (
|
||||
camera_path.camera_type[0] == CameraType.OMNIDIRECTIONALSTEREO_L.value
|
||||
or camera_path.camera_type[0] == CameraType.VR180_L.value
|
||||
):
|
||||
# declare paths for left and right renders
|
||||
|
||||
left_eye_path = self.output_path
|
||||
right_eye_path = left_eye_path.parent / "ods_render_Right.mp4"
|
||||
right_eye_path = left_eye_path.parent / "render_right.mp4"
|
||||
|
||||
self.output_path = right_eye_path
|
||||
camera_path.camera_type[0] = CameraType.OMNIDIRECTIONALSTEREO_R.value
|
||||
|
||||
if camera_path.camera_type[0] == CameraType.OMNIDIRECTIONALSTEREO_L.value:
|
||||
camera_path.camera_type[0] = CameraType.OMNIDIRECTIONALSTEREO_R.value
|
||||
else:
|
||||
camera_path.camera_type[0] = CameraType.VR180_R.value
|
||||
|
||||
CONSOLE.print("Rendering right eye view")
|
||||
_render_trajectory_video(
|
||||
@@ -398,26 +412,49 @@ class RenderCameraPath(BaseRender):
|
||||
colormap_options=self.colormap_options,
|
||||
)
|
||||
|
||||
# stack the left and right eye renders for final output
|
||||
self.output_path = Path(str(left_eye_path.parent)[:-5] + ".mp4")
|
||||
ffmpeg_ods_command = ""
|
||||
if self.output_format == "video":
|
||||
ffmpeg_ods_command = f'ffmpeg -y -i "{left_eye_path}" -i "{right_eye_path}" -filter_complex "[0:v]pad=iw:2*ih[int];[int][1:v]overlay=0:h" -c:v libx264 -crf 23 -preset veryfast "{self.output_path}"'
|
||||
run_command(ffmpeg_ods_command, verbose=False)
|
||||
if self.output_format == "images":
|
||||
# create a folder for the stacked renders
|
||||
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.with_suffix("") / "*.png")}" -pattern_type glob -i "{str(right_eye_path.with_suffix("") / "*.png")}" -filter_complex vstack -start_number 0 "{str(self.output_path)+"//%05d.png"}"'
|
||||
elif self.image_format == "jpeg":
|
||||
ffmpeg_ods_command = f'ffmpeg -y -pattern_type glob -i "{str(left_eye_path.with_suffix("") / "*.jpg")}" -pattern_type glob -i "{str(right_eye_path.with_suffix("") / "*.jpg")}" -filter_complex vstack -start_number 0 "{str(self.output_path)+"//%05d.jpg"}"'
|
||||
run_command(ffmpeg_ods_command, verbose=False)
|
||||
|
||||
# remove the temp files directory
|
||||
if str(left_eye_path.parent)[-5:] == "_temp":
|
||||
shutil.rmtree(left_eye_path.parent, ignore_errors=True)
|
||||
CONSOLE.print("[bold green]Final ODS Render Complete")
|
||||
if camera_path.camera_type[0] == CameraType.OMNIDIRECTIONALSTEREO_R.value:
|
||||
# stack the left and right eye renders vertically for ODS final output
|
||||
ffmpeg_ods_command = ""
|
||||
if self.output_format == "video":
|
||||
ffmpeg_ods_command = f'ffmpeg -y -i "{left_eye_path}" -i "{right_eye_path}" -filter_complex "[0:v]pad=iw:2*ih[int];[int][1:v]overlay=0:h" -c:v libx264 -crf 23 -preset veryfast "{self.output_path}"'
|
||||
run_command(ffmpeg_ods_command, verbose=False)
|
||||
if self.output_format == "images":
|
||||
# create a folder for the stacked renders
|
||||
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.with_suffix("") / "*.png")}" -pattern_type glob -i "{str(right_eye_path.with_suffix("") / "*.png")}" -filter_complex vstack -start_number 0 "{str(self.output_path)+"//%05d.png"}"'
|
||||
elif self.image_format == "jpeg":
|
||||
ffmpeg_ods_command = f'ffmpeg -y -pattern_type glob -i "{str(left_eye_path.with_suffix("") / "*.jpg")}" -pattern_type glob -i "{str(right_eye_path.with_suffix("") / "*.jpg")}" -filter_complex vstack -start_number 0 "{str(self.output_path)+"//%05d.jpg"}"'
|
||||
run_command(ffmpeg_ods_command, verbose=False)
|
||||
|
||||
# remove the temp files directory
|
||||
if str(left_eye_path.parent)[-5:] == "_temp":
|
||||
shutil.rmtree(left_eye_path.parent, ignore_errors=True)
|
||||
CONSOLE.print("[bold green]Final ODS Render Complete")
|
||||
else:
|
||||
# stack the left and right eye renders horizontally for VR180 final output
|
||||
self.output_path = Path(str(left_eye_path.parent)[:-5] + ".mp4")
|
||||
ffmpeg_vr180_command = ""
|
||||
if self.output_format == "video":
|
||||
ffmpeg_vr180_command = f'ffmpeg -y -i "{right_eye_path}" -i "{left_eye_path}" -filter_complex "[1:v]hstack=inputs=2" -c:a copy "{self.output_path}"'
|
||||
run_command(ffmpeg_vr180_command, verbose=False)
|
||||
if self.output_format == "images":
|
||||
# create a folder for the stacked renders
|
||||
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_vr180_command = f'ffmpeg -y -pattern_type glob -i "{str(left_eye_path.with_suffix("") / "*.png")}" -pattern_type glob -i "{str(right_eye_path.with_suffix("") / "*.png")}" -filter_complex hstack -start_number 0 "{str(self.output_path)+"//%05d.png"}"'
|
||||
elif self.image_format == "jpeg":
|
||||
ffmpeg_vr180_command = f'ffmpeg -y -pattern_type glob -i "{str(left_eye_path.with_suffix("") / "*.jpg")}" -pattern_type glob -i "{str(right_eye_path.with_suffix("") / "*.jpg")}" -filter_complex hstack -start_number 0 "{str(self.output_path)+"//%05d.jpg"}"'
|
||||
run_command(ffmpeg_vr180_command, verbose=False)
|
||||
|
||||
# remove the temp files directory
|
||||
if str(left_eye_path.parent)[-5:] == "_temp":
|
||||
shutil.rmtree(left_eye_path.parent, ignore_errors=True)
|
||||
CONSOLE.print("[bold green]Final VR180 Render Complete")
|
||||
|
||||
|
||||
@dataclass
|
||||
|
||||
Reference in new issue
Block a user