123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319 |
- import os
- import sys
- os.environ['PYOPENGL_PLATFORM'] = 'egl'
- import numpy as np
- import trimesh
- from pyrender import PerspectiveCamera,\
- DirectionalLight, SpotLight, PointLight,\
- MetallicRoughnessMaterial,\
- Primitive, Mesh, Node, Scene,\
- OffscreenRenderer
- import tqdm
- from fairmotion.ops import conversions, math, motion as motion_ops
- from fairmotion.utils import utils
- import pyrender
- import matplotlib.pyplot as plt
- import matplotlib.animation as animation
- from fairmotion.ops.conversions import E2R, R2Q
- def _get_cam_rotation(p_cam, p_obj, vup):
- z = p_cam - p_obj
- z /= np.linalg.norm(z)
- x = np.cross(vup, z)
- x /= np.linalg.norm(x)
- y = np.cross(z, x)
- return np.array([x, y, z]).transpose()
- class MocapViewerOffline(animation.FuncAnimation):
- """
- MocapViewerOffline is an extension of the glut_viewer.Viewer class that implements
- requisite callback functions -- render_callback, keyboard_callback,
- idle_callback and overlay_callback.
- ```
- python fairmotion/viz/bvh_visualizer.py \
- --bvh-files $BVH_FILE1
- ```
- To visualize more than 1 motion sequence side by side, append more files
- to the `--bvh-files` argument. Set `--x-offset` to an appropriate float
- value to add space separation between characters in the row.
- ```
- python fairmotion/viz/bvh_visualizer.py \
- --bvh-files $BVH_FILE1 $BVH_FILE2 $BVH_FILE3 \
- --x-offset 2
- ```
- To visualize asfamc motion sequence:
- ```
- python fairmotion/viz/bvh_visualizer.py \
- --asf-files tests/data/11.asf \
- --amc-files tests/data/11_01.amc
- ```
- """
- def __init__(
- self,
- motion,
- cam_pos,
- v_up_str,
- play_speed=1.0,
- scale=1.0,
- thickness=1.0,
- hide_origin=False
- ):
- animation.FuncAnimation.__init__(self, fig=plt.figure(figsize=(5, 5)), func=self.animate,
- frames=len(motion.poses), interval=50, blit=False)
- self.motion = motion
- self.play_speed = play_speed
- self.hide_origin = hide_origin
- self.file_idx = 0
- self.cur_time = 0.0
- self.scale = scale
- self.thickness = thickness
- self.cam_p = np.array(cam_pos)
- self.up_axis = utils.str_to_axis(v_up_str)
- self.ground_node = None
- self.init_pyrender()
- self.pt_pool = []
- self.cap_pool = []
- plt.axis('off')
- self.ims = None
- self.progress = tqdm.tqdm(total=len(motion.poses)) # Initialise
- def render_point(self, at_index, p, scale=1.0, radius=1.0, color=[1.0, 0.0, 0.0]):
- if at_index >= len(self.pt_pool):
- # create enough to allow at_index to work
- for i in range(len(self.pt_pool), at_index + 1): # must include at_index too
- # get primitive first
- sphere_trimesh = trimesh.creation.icosphere(radius=radius, subdivisions=1)
- sphere_face_colors = np.zeros(sphere_trimesh.faces.shape)
- sphere_face_colors[:] = np.array(color)
- sphere_trimesh.visual.face_colors = sphere_face_colors
- sphere_mesh = Mesh.from_trimesh(sphere_trimesh, smooth=False)
- sphere_node = Node(mesh=sphere_mesh,
- name="sphere_" + str(i)) # , translation=np.array([-0.1, -0.10, 0.05]))
- self.scene.add_node(sphere_node)
- self.pt_pool.append(sphere_node)
- # okay now we know the node exists, just change it's position
- self.pt_pool[at_index].scale = [scale] * 3
- self.pt_pool[at_index].translation = p
- def render_capsule(self, at_index, p, Q, length, scale=1.0, color=[1.0, 0.0, 0.0]):
- if at_index >= len(self.cap_pool):
- # create enough to allow at_index to work
- for i in range(len(self.cap_pool), at_index + 1): # must include at_index too
- # get primitive first
- sphere_trimesh = trimesh.creation.capsule(height=1.0, radius=1.0, count=[8, 8])
- sphere_face_colors = np.zeros(sphere_trimesh.faces.shape)
- sphere_face_colors[:] = np.array(color)
- sphere_trimesh.visual.face_colors = sphere_face_colors
- sphere_mesh = Mesh.from_trimesh(sphere_trimesh, smooth=False)
- sphere_node = Node(mesh=sphere_mesh,
- name="capsule_" + str(i)) # , translation=np.array([-0.1, -0.10, 0.05]))
- self.scene.add_node(sphere_node)
- self.cap_pool.append(sphere_node)
- # okay now we know the node exists, just change it's position
- self.cap_pool[at_index].scale = [0.1, 0.1, length]
- self.cap_pool[at_index].translation = p
- self.cap_pool[at_index].rotation = Q
- # print("Q: " + str(Q))
- def _render_pose(self, pose, color):
- skel = pose.skel
- capnum = 0
- for ipt, j in enumerate(skel.joints):
- T = pose.get_transform(j, local=False)
- pos = 0.4 * conversions.T2p(T)
- self.render_point(ipt, pos, radius=0.03 * self.scale, color=color)
- if j.parent_joint is not None:
- # returns X that X dot vec1 = vec2
- pos_parent = 0.5 * conversions.T2p(
- pose.get_transform(j.parent_joint, local=False)
- )
- p = 0.4 * (pos_parent + pos)
- l = np.linalg.norm(pos_parent - pos)
- # l=2.0
- r = 0.1 * self.thickness
- R = math.R_from_vectors(np.array([0, 0, 1]), pos_parent - pos)
- self.render_capsule(capnum,
- p,
- # conversions.p2T(p),
- R2Q(R),
- l / 2.0,
- # r * self.scale,
- 0.1,
- color=color
- )
- capnum += 1
- def _render_characters(self, colors, frame):
- # t = self.cur_time % motion.length()
- skel = self.motion.skel
- # pose = motion.get_pose_by_frame(motion.time_to_frame(t))
- pose = self.motion.get_pose_by_frame(frame)
- # pose = motion.get_pose_by_frame(0)
- color = colors[0 % len(colors)]
- self._render_pose(pose, color)
- def render_ground(self, size=[20.0, 20.0],
- dsize=[1.0, 1.0],
- color=[0.0, 0.0, 0.0, 1.0],
- line_width=1.0,
- axis="y",
- origin=True,
- use_arrow=False,
- lighting=False):
- if self.ground_node is None:
- lx = size[0]
- lz = size[1]
- dx = dsize[0]
- dz = dsize[1]
- nx = int(lx / dx) + 1
- nz = int(lz / dz) + 1
- grid_pts = np.zeros((2 * nx + 2 * nz, 3))
- colors = np.zeros((2 * nx + 2 * nz, 4))
- colors[:] = np.array(color)
- if axis is "x":
- linei = 0
- for i in np.linspace(-0.5 * lx, 0.5 * lx, nx):
- grid_pts[2 * linei] = [0, i, -0.5 * lz]
- grid_pts[2 * linei + 1] = [0, i, 0.5 * lz]
- linei += 1
- for i in np.linspace(-0.5 * lz, 0.5 * lz, nz):
- grid_pts[2 * linei] = [0, -0.5 * lx, i]
- grid_pts[2 * linei + 1] = [0, 0.5 * lx, i]
- linei += 1
- elif axis is "y":
- linei = 0
- for i in np.linspace(-0.5 * lx, 0.5 * lx, nx):
- grid_pts[2 * linei] = [i, 0, -0.5 * lz]
- grid_pts[2 * linei + 1] = [i, 0, 0.5 * lz]
- linei += 1
- for i in np.linspace(-0.5 * lz, 0.5 * lz, nz):
- grid_pts[2 * linei] = [-0.5 * lx, 0, i]
- grid_pts[2 * linei + 1] = [0.5 * lx, 0, i]
- linei += 1
- elif axis is "z":
- linei = 0
- for i in np.linspace(-0.5 * lx, 0.5 * lx, nx):
- grid_pts[2 * linei] = [i, -0.5 * lz, 0.]
- grid_pts[2 * linei + 1] = [i, 0.5 * lz, 0.]
- linei += 1
- for j, i in enumerate(np.linspace(-0.5 * lz, 0.5 * lz, nz)):
- grid_pts[2 * linei] = [-0.5 * lx, i, 0.]
- grid_pts[2 * linei + 1] = [0.5 * lx, i, 0.]
- linei += 1
- grid = pyrender.Primitive(grid_pts, color_0=colors, mode=1) # 1->LINES
- grid = pyrender.Mesh([grid])
- self.ground_node = Node(mesh=grid, name="ground_plane")
- self.scene.add_node(self.ground_node)
- def render_callback(self, frame_num):
- self.render_ground(
- size=[100, 100],
- color=[0.8, 0.8, 0.8, 1.0],
- axis="y", # utils.axis_to_str(self.motions[0].skel.v_up_env),
- origin=not self.hide_origin,
- use_arrow=True,
- )
- colors = [
- np.array([123, 174, 85]) / 255.0, # green
- np.array([255, 255, 0]) / 255.0, # yellow
- np.array([85, 160, 173]) / 255.0, # blue
- ]
- self._render_characters(colors, frame_num)
- color, depth = self.r.render(self.scene)
- return color
- def animate(self, frame_num):
- # print("Rendering frame %d..."%(frame_num))
- self.progress.update(frame_num)
- color = self.render_callback(frame_num)
- if self.ims is None:
- self.ims = plt.imshow(color, animated=True)
- else:
- self.ims.set_array(color)
- return self.ims,
- def idle_callback(self):
- time_elapsed = self.time_checker.get_time(restart=False)
- self.cur_time += self.play_speed * time_elapsed
- self.time_checker.begin()
- def init_pyrender(self):
- # ==============================================================================
- # Light creation
- # ==============================================================================
- self.spot_l = pyrender.SpotLight(color=np.ones(3), intensity=3.0,
- innerConeAngle=np.pi / 16.0,
- outerConeAngle=np.pi / 6.0)
- # ==============================================================================
- # Camera creation
- # ==============================================================================
- cam = PerspectiveCamera(yfov=np.pi / 2.0)
- R = _get_cam_rotation(self.cam_p,
- np.zeros((3)),
- self.up_axis)
- self.cam_pose = conversions.Rp2T(R, self.cam_p)
- # ==============================================================================
- # Scene creation
- # ==============================================================================
- self.scene = Scene(ambient_light=np.array([0.1, 0.1, 0.1, 1.0]))
- self.spot_l_node = self.scene.add(self.spot_l, pose=self.cam_pose, name="spot_light")
- self.cam_node = self.scene.add(cam, pose=self.cam_pose, name="camera")
- self.r = OffscreenRenderer(viewport_width=320, viewport_height=240)
- if __name__ == "__main__":
- from fairmotion.data import bvh
- filename = sys.argv[1]
- v_up_env = utils.str_to_axis('y')
- motion = bvh.load(
- file=filename,
- v_up_skel=v_up_env,
- v_face_skel=utils.str_to_axis('z'),
- v_up_env=v_up_env,
- scale=1.0)
- R = E2R([-np.pi / 2.0, 0.0, 0.0])
- motion = motion_ops.rotate(motion, R)
- motion = motion_ops.translate(motion, [0, -20.0, 0])
- viewer = MocapViewerOffline(
- motion=motion,
- cam_pos=[0.0, 1.1, -2.3],
- v_up_str='y',
- scale=20.33
- )
- viewer.to_html5_video()
|