Skip to content

Commit beefad8

Browse files
committed
feat: object, scene, animation
1 parent a2bedee commit beefad8

13 files changed

+806
-45
lines changed
712 KB
Binary file not shown.

assets/models/animation_vroid_1.glb

25.1 MB
Binary file not shown.
11.1 MB
Binary file not shown.
Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
import json
2+
import os
3+
from glob import glob
4+
5+
import imageio
6+
import numpy as np
7+
8+
from bpyrenderer import SceneManager
9+
from bpyrenderer.camera import add_camera
10+
from bpyrenderer.camera.layout import get_camera_positions_on_sphere
11+
from bpyrenderer.engine import init_render_engine
12+
from bpyrenderer.environment import set_background_color, set_env_map
13+
from bpyrenderer.importer import load_armature, load_file
14+
from bpyrenderer.render_output import (
15+
enable_color_output,
16+
enable_depth_output,
17+
enable_normals_output,
18+
render_keypoint_map,
19+
)
20+
from bpyrenderer.utils import (
21+
MIXAMO_KEYPOINTS,
22+
VROID_KEYPOINT_MAPS,
23+
convert_depth_to_webp,
24+
convert_normal_to_webp,
25+
)
26+
27+
28+
def process(
29+
model_path: str,
30+
output_dir: str,
31+
process_materials: bool = False,
32+
env_map: str = "../../assets/env_textures/brown_photostudio_02_1k.exr",
33+
):
34+
# 1. Init engine and scene manager
35+
init_render_engine("BLENDER_EEVEE")
36+
scene_manager = SceneManager()
37+
scene_manager.clear()
38+
39+
# 2. Import models
40+
armature = load_armature(model_path, ignore_components=["Icosphere", "polySurface"])
41+
scene_manager.update_scene_frames()
42+
43+
# Optional. check armature template and selete bones to visualize
44+
armature_bones = [bone.name.lower() for bone in armature.pose.bones]
45+
keypoint_names, plot_bones = None, None
46+
# Check mixamo first
47+
# for name in armature_bones:
48+
# if "mixamorig" in name:
49+
# keypoint_names = plot_bones = MIXAMO_KEYPOINTS
50+
# break
51+
# Check vroid
52+
# if keypoint_names is None:
53+
# for vroid_tag in VROID_KEYPOINT_MAPS.keys():
54+
# if vroid_tag in armature_bones:
55+
# keypoint_names = plot_bones = VROID_KEYPOINT_MAPS.get(vroid_tag, None)
56+
# Save all the bones if keypoint_names is None, and visualize all bones if plot_bones is None
57+
if keypoint_names is None and plot_bones is None:
58+
print("No preset skeleton template matched. Will save and visualize all bones.")
59+
60+
# Others. smooth objects and normalize scene
61+
scene_manager.smooth()
62+
scene_manager.normalize_scene(1.0, process_frames=True, use_parent_node=True)
63+
if process_materials:
64+
scene_manager.clear_normal_map()
65+
scene_manager.set_material_transparency(False)
66+
scene_manager.set_materials_opaque() # !!! Important for render normal but may cause render error !!!
67+
68+
# 3. Set environment
69+
set_env_map(env_map)
70+
# set_background_color([1.0, 1.0, 1.0, 1.0])
71+
72+
# 4. Prepare cameras
73+
cam_pos, cam_mats, elevations, azimuths = get_camera_positions_on_sphere(
74+
center=(0, 0, 0), radius=2.7, elevations=[0], azimuths=[-90]
75+
)
76+
cameras = []
77+
for i, camera_mat in enumerate(cam_mats):
78+
camera = add_camera(camera_mat, add_frame=i < len(cam_mats) - 1)
79+
cameras.append(camera)
80+
81+
# 5. Set render outputs and do rendering
82+
width, height, fps = 1280, 720, 24
83+
enable_color_output(width, height, output_dir, file_format="PNG", fps=fps)
84+
enable_depth_output(output_dir)
85+
enable_normals_output(output_dir)
86+
87+
scene_manager.render()
88+
89+
render_keypoint_map(
90+
output_dir,
91+
file_format="PNG",
92+
export_meta=True,
93+
bone_width=5,
94+
keypoint_radius=4,
95+
keypoint_names=keypoint_names, # represent color map if dict, kpt names if list
96+
background_color=(255, 255, 255),
97+
plot_bones=plot_bones,
98+
)
99+
100+
# 6. post-process
101+
# convert depth (.exr) into .png
102+
render_files = sorted(glob(os.path.join(output_dir, "depth_*.exr")))
103+
output_files = [file.replace("exr", "png") for file in render_files]
104+
min_depth, scale = convert_depth_to_webp(render_files, output_files)
105+
for filepath in render_files:
106+
os.remove(filepath)
107+
108+
# convert normal (.exr) into .png
109+
for file in os.listdir(output_dir):
110+
if file.startswith("normal_") and file.endswith(".exr"):
111+
filepath = os.path.join(output_dir, file)
112+
render_filepath = filepath.replace("normal_", "render_")
113+
convert_normal_to_webp(
114+
filepath,
115+
filepath.replace(".exr", ".png"),
116+
render_filepath,
117+
)
118+
os.remove(filepath)
119+
120+
# convert rendered render_*.png (rgba) to a white background video and a mask video
121+
render_files = sorted(glob(os.path.join(output_dir, "render_*.png")))
122+
if render_files:
123+
# Create videos for white background and mask
124+
white_video_path = os.path.join(output_dir, "rgb.mp4")
125+
mask_video_path = os.path.join(output_dir, "mask.mp4")
126+
127+
with imageio.get_writer(
128+
white_video_path, fps=fps
129+
) as white_writer, imageio.get_writer(mask_video_path, fps=fps) as mask_writer:
130+
131+
for file in render_files:
132+
# Read RGBA image
133+
image = imageio.imread(file)
134+
mask = image[:, :, 3]
135+
white_bg = np.ones((height, width, 3), dtype=np.uint8) * 255
136+
137+
alpha = image[:, :, 3:4] / 255.0
138+
white_image = image[:, :, :3] * alpha + white_bg * (1 - alpha)
139+
140+
white_writer.append_data(white_image.astype(np.uint8))
141+
mask_writer.append_data(mask)
142+
os.remove(file)
143+
144+
# convert rendered normal_*.png to a video, and remove original images
145+
normal_files = sorted(glob(os.path.join(output_dir, "normal_*.png")))
146+
if normal_files:
147+
video_path = os.path.join(output_dir, "normal.mp4")
148+
with imageio.get_writer(video_path, fps=fps) as writer:
149+
for file in normal_files:
150+
image = imageio.imread(file)
151+
writer.append_data(image)
152+
os.remove(file)
153+
154+
# convert rendered depth_*.png to a video, and remove original images
155+
depth_files = sorted(glob(os.path.join(output_dir, "depth_*.png")))
156+
if depth_files:
157+
video_path = os.path.join(output_dir, "depth.mp4")
158+
with imageio.get_writer(video_path, fps=fps) as writer:
159+
for file in depth_files:
160+
image = imageio.imread(file)
161+
writer.append_data(image)
162+
os.remove(file)
163+
164+
# convert rendered keypoints_*.png to a video, and remove original images
165+
keypoint_files = sorted(glob(os.path.join(output_dir, "keypoint_*.png")))
166+
if keypoint_files:
167+
video_path = os.path.join(output_dir, "keypoint.mp4")
168+
with imageio.get_writer(video_path, fps=fps) as writer:
169+
for file in keypoint_files:
170+
image = imageio.imread(file)
171+
writer.append_data(image)
172+
os.remove(file)
173+
174+
# save metadata
175+
meta_info = {"width": width, "height": height, "locations": []}
176+
for i in range(len(cam_pos)):
177+
index = "{0:04d}".format(i)
178+
meta_info["locations"].append(
179+
{
180+
"index": index,
181+
# intristic
182+
"projection_type": cameras[i].data.type,
183+
"ortho_scale": cameras[i].data.ortho_scale,
184+
"camera_angle_x": cameras[i].data.angle_x,
185+
# extristic
186+
"elevation": elevations[i],
187+
"azimuth": azimuths[i],
188+
"transform_matrix": cam_mats[i].tolist(),
189+
# depth
190+
"depth_min": float(min_depth),
191+
"depth_scale": float(scale),
192+
}
193+
)
194+
with open(os.path.join(output_dir, "meta.json"), "w") as f:
195+
json.dump(meta_info, f, indent=4)
196+
197+
198+
if __name__ == "__main__":
199+
process("../../assets/models/animation_vroid_1.glb", "outputs")
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import os
2+
import json
3+
import numpy as np
4+
5+
from bpyrenderer.camera import add_camera
6+
from bpyrenderer.engine import init_render_engine
7+
from bpyrenderer.environment import set_background_color, set_env_map
8+
from bpyrenderer.importer import load_file, load_armature
9+
from bpyrenderer.render_output import enable_color_output
10+
from bpyrenderer import SceneManager
11+
from bpyrenderer.camera.layout import get_camera_positions_on_sphere
12+
13+
output_dir = "outputs"
14+
15+
# 1. Init engine and scene manager
16+
init_render_engine("BLENDER_EEVEE")
17+
scene_manager = SceneManager()
18+
scene_manager.clear()
19+
20+
# 2. Import models
21+
load_armature(
22+
"../../assets/models/animation_vroid_1.glb",
23+
ignore_components=["Icosphere", "polySurface"],
24+
)
25+
scene_manager.update_scene_frames()
26+
27+
# Others. smooth objects and normalize scene
28+
scene_manager.smooth()
29+
scene_manager.normalize_scene(1.0, process_frames=True, use_parent_node=True)
30+
31+
# 3. Set environment
32+
set_env_map("../../assets/env_textures/brown_photostudio_02_1k.exr")
33+
# set_background_color([1.0, 1.0, 1.0, 1.0])
34+
35+
# 4. Prepare cameras
36+
cam_pos, cam_mats, elevations, azimuths = get_camera_positions_on_sphere(
37+
center=(0, 0, 0), radius=2.7, elevations=[0], azimuths=[-90]
38+
)
39+
cameras = []
40+
for i, camera_mat in enumerate(cam_mats):
41+
camera = add_camera(camera_mat, add_frame=i < len(cam_mats) - 1)
42+
cameras.append(camera)
43+
44+
# 5. Set render outputs
45+
width, height = 1280, 720
46+
enable_color_output(
47+
width,
48+
height,
49+
output_dir,
50+
mode="VIDEO",
51+
film_transparent=False, # enable rendering result to include background
52+
)
53+
scene_manager.render()
54+
55+
# Optional. save metadata
56+
meta_info = {"width": width, "height": height, "locations": []}
57+
for i in range(len(cam_pos)):
58+
index = "{0:04d}".format(i)
59+
meta_info["locations"].append(
60+
{
61+
"index": index,
62+
"projection_type": cameras[i].data.type,
63+
"ortho_scale": cameras[i].data.ortho_scale,
64+
"camera_angle_x": cameras[i].data.angle_x,
65+
"elevation": elevations[i],
66+
"azimuth": azimuths[i],
67+
"transform_matrix": cam_mats[i].tolist(),
68+
}
69+
)
70+
with open(os.path.join(output_dir, "meta.json"), "w") as f:
71+
json.dump(meta_info, f, indent=4)

0 commit comments

Comments
 (0)