mirror of
https://github.com/gosticks/body-pose-animation.git
synced 2025-10-16 11:45:42 +00:00
add video renderer
This commit is contained in:
parent
36f617899e
commit
e9aeffb199
@ -1,4 +1,5 @@
|
|||||||
# library imports
|
# library imports
|
||||||
|
from utils.render import make_video
|
||||||
import torch
|
import torch
|
||||||
import matplotlib.pyplot as plt
|
import matplotlib.pyplot as plt
|
||||||
|
|
||||||
@ -21,7 +22,8 @@ init_keypoints, init_joints, keypoints, conf, est_scale, r, img_path = setup_tra
|
|||||||
model=model,
|
model=model,
|
||||||
renderer=True,
|
renderer=True,
|
||||||
dataset=dataset,
|
dataset=dataset,
|
||||||
sample_index=sample_index
|
sample_index=sample_index,
|
||||||
|
offscreen=True
|
||||||
)
|
)
|
||||||
|
|
||||||
# configure PyTorch device and format
|
# configure PyTorch device and format
|
||||||
@ -45,7 +47,7 @@ camera.setup_visualization(r.init_keypoints, r.keypoints)
|
|||||||
|
|
||||||
|
|
||||||
# train for pose
|
# train for pose
|
||||||
result, best, train_loss = train_pose_with_conf(
|
result, best, train_loss, step_imgs = train_pose_with_conf(
|
||||||
config=config,
|
config=config,
|
||||||
model=model,
|
model=model,
|
||||||
keypoints=keypoints,
|
keypoints=keypoints,
|
||||||
@ -55,10 +57,17 @@ result, best, train_loss = train_pose_with_conf(
|
|||||||
device=device,
|
device=device,
|
||||||
)
|
)
|
||||||
|
|
||||||
fig, ax = plt.subplots()
|
|
||||||
name = getfilename_from_conf(config=config, index=sample_index)
|
make_video(step_imgs, "test.avi")
|
||||||
ax.plot(train_loss[1::], label='sgd')
|
|
||||||
ax.set(xlabel="Training iteration", ylabel="Loss", title='Training loss')
|
# color = r.get_snapshot()
|
||||||
fig.savefig("results/" + name + ".png")
|
# plt.imshow(color)
|
||||||
ax.legend()
|
# plt.show()
|
||||||
plt.show()
|
|
||||||
|
# fig, ax = plt.subplots()
|
||||||
|
# name = getfilename_from_conf(config=config, index=sample_index)
|
||||||
|
# ax.plot(train_loss[1::], label='sgd')
|
||||||
|
# ax.set(xlabel="Training iteration", ylabel="Loss", title='Training loss')
|
||||||
|
# fig.savefig("results/" + name + ".png")
|
||||||
|
# ax.legend()
|
||||||
|
# plt.show()
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
import pickle
|
import pickle
|
||||||
import time
|
import time
|
||||||
|
from utils.render import make_video
|
||||||
import torch
|
import torch
|
||||||
|
from tqdm.std import tqdm
|
||||||
|
|
||||||
from dataset import SMPLyDataset
|
from dataset import SMPLyDataset
|
||||||
from model import *
|
from model import *
|
||||||
@ -17,6 +19,7 @@ FINISH_IDX = 200 # choose a big number to optimize for all frames in samples d
|
|||||||
RUN_OPTIMIZATION = False
|
RUN_OPTIMIZATION = False
|
||||||
|
|
||||||
final_poses = [] # optimized poses array that is saved for playing the animation
|
final_poses = [] # optimized poses array that is saved for playing the animation
|
||||||
|
result_image = []
|
||||||
idx = START_IDX
|
idx = START_IDX
|
||||||
|
|
||||||
|
|
||||||
@ -27,15 +30,15 @@ def get_next_frame(idx):
|
|||||||
:param idx: index of the frame
|
:param idx: index of the frame
|
||||||
:return: tuple of keypoints, conf and image path
|
:return: tuple of keypoints, conf and image path
|
||||||
"""
|
"""
|
||||||
keypoints = dataset[idx]
|
keypoints, keypoints_conf = dataset[idx]
|
||||||
if keypoints is None:
|
if keypoints is None:
|
||||||
return
|
return
|
||||||
image_path = dataset.get_image_path(idx)
|
image_path = dataset.get_image_path(idx)
|
||||||
return keypoints[0], keypoints[1], image_path
|
return keypoints, keypoints_conf, image_path
|
||||||
|
|
||||||
|
|
||||||
device = torch.device('cpu')
|
device = torch.device('cpu')
|
||||||
dtype = torch.float
|
dtype = torch.float32
|
||||||
|
|
||||||
config = load_config()
|
config = load_config()
|
||||||
dataset = SMPLyDataset.from_config(config)
|
dataset = SMPLyDataset.from_config(config)
|
||||||
@ -57,16 +60,15 @@ joints = model_out.joints.detach().cpu().numpy().squeeze()
|
|||||||
Optimization part without visualization
|
Optimization part without visualization
|
||||||
'''
|
'''
|
||||||
if RUN_OPTIMIZATION:
|
if RUN_OPTIMIZATION:
|
||||||
while get_next_frame(idx) is not None and idx <= FINISH_IDX:
|
for idx in dataset:
|
||||||
keypoints, confidence, img_path = get_next_frame(idx)
|
|
||||||
|
|
||||||
est_scale = estimate_scale(joints, keypoints)
|
init_keypoints, init_joints, keypoints, conf, est_scale, r, img_path = setup_training(
|
||||||
|
model=model,
|
||||||
# apply scaling to keypoints
|
renderer=True,
|
||||||
keypoints = keypoints * est_scale
|
offscreen=True,
|
||||||
|
dataset=dataset,
|
||||||
init_joints = get_torso(joints)
|
sample_index=idx
|
||||||
init_keypoints = get_torso(keypoints)
|
)
|
||||||
|
|
||||||
camera = TorchCameraEstimate(
|
camera = TorchCameraEstimate(
|
||||||
model,
|
model,
|
||||||
@ -86,9 +88,9 @@ if RUN_OPTIMIZATION:
|
|||||||
config=config,
|
config=config,
|
||||||
model=model,
|
model=model,
|
||||||
keypoints=keypoints,
|
keypoints=keypoints,
|
||||||
keypoint_conf=confidence,
|
keypoint_conf=conf,
|
||||||
camera=camera,
|
camera=camera,
|
||||||
renderer=None,
|
renderer=r,
|
||||||
device=device
|
device=device
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -142,6 +144,31 @@ def replay_animation(file, start_frame=0, end_frame=None, with_background=False,
|
|||||||
time.sleep(1 / fps)
|
time.sleep(1 / fps)
|
||||||
|
|
||||||
|
|
||||||
|
def video_from_pkl(filename, video_name):
|
||||||
|
with open(filename, "rb") as fp:
|
||||||
|
final_poses = pickle.load(fp)
|
||||||
|
|
||||||
|
save_to_video(final_poses, video_name)
|
||||||
|
|
||||||
|
|
||||||
|
def save_to_video(poses, video_name, fps=30):
|
||||||
|
r = DefaultRenderer(
|
||||||
|
offscreen=True
|
||||||
|
)
|
||||||
|
r.start()
|
||||||
|
|
||||||
|
model_anim = SMPLyModel.model_from_conf(config)
|
||||||
|
|
||||||
|
frames = []
|
||||||
|
|
||||||
|
for body_pose, cam_trans in tqdm(poses):
|
||||||
|
r.render_model_with_tfs(model_anim, body_pose, keep_pose=True,
|
||||||
|
render_joints=False, transforms=cam_trans)
|
||||||
|
frames.append(r.get_snapshot(False))
|
||||||
|
|
||||||
|
make_video(frames, video_name, fps)
|
||||||
|
|
||||||
|
|
||||||
'''
|
'''
|
||||||
Play the animation.
|
Play the animation.
|
||||||
'''
|
'''
|
||||||
|
|||||||
54
renderer.py
54
renderer.py
@ -14,9 +14,12 @@ class Renderer:
|
|||||||
camera_pose=None,
|
camera_pose=None,
|
||||||
width=1920,
|
width=1920,
|
||||||
height=1080,
|
height=1080,
|
||||||
light_color=[0.3, 0.3, 0.3, 1.0]
|
light_color=[0.3, 0.3, 0.3, 1.0],
|
||||||
|
offscreen=False
|
||||||
) -> None:
|
) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
|
self.use_offscreen = offscreen
|
||||||
self.run_in_thread = False
|
self.run_in_thread = False
|
||||||
self.scene = pyrender.Scene(
|
self.scene = pyrender.Scene(
|
||||||
ambient_light=light_color
|
ambient_light=light_color
|
||||||
@ -50,12 +53,22 @@ class Renderer:
|
|||||||
viewport_size = (self.width, self.height)
|
viewport_size = (self.width, self.height)
|
||||||
|
|
||||||
self.run_in_thread = run_in_thread
|
self.run_in_thread = run_in_thread
|
||||||
self.viewer = pyrender.Viewer(
|
|
||||||
self.scene,
|
if self.use_offscreen:
|
||||||
run_in_thread=run_in_thread,
|
self.offscreen = pyrender.OffscreenRenderer(
|
||||||
use_reymond_lighting=use_reymond_lighting,
|
# self.scene,
|
||||||
viewport_size=tuple(d // 2 for d in viewport_size)
|
# run_in_thread=run_in_thread,
|
||||||
)
|
# use_reymond_lighting=use_reymond_lighting,
|
||||||
|
viewport_width=(viewport_size[0]),
|
||||||
|
viewport_height=(viewport_size[1])
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
self.viewer = pyrender.Viewer(
|
||||||
|
self.scene,
|
||||||
|
run_in_thread=run_in_thread,
|
||||||
|
use_reymond_lighting=use_reymond_lighting,
|
||||||
|
viewport_size=tuple(d // 2 for d in viewport_size)
|
||||||
|
)
|
||||||
|
|
||||||
def stop(self):
|
def stop(self):
|
||||||
self.viewer.close_external()
|
self.viewer.close_external()
|
||||||
@ -63,7 +76,7 @@ class Renderer:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
def requires_lock(self):
|
def requires_lock(self):
|
||||||
return self.run_in_thread and self.viewer
|
return not self.use_offscreen and self.run_in_thread and self.viewer
|
||||||
|
|
||||||
def release(self):
|
def release(self):
|
||||||
if self.requires_lock():
|
if self.requires_lock():
|
||||||
@ -212,8 +225,8 @@ class Renderer:
|
|||||||
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
|
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
|
||||||
self.render_image(img, scale, name)
|
self.render_image(img, scale, name)
|
||||||
|
|
||||||
def render_image(self, image, scale, name=None):
|
def render_image(self, image, scale, name="source_img"):
|
||||||
_ = render_image_plane(self.scene, image, scale, name)
|
self.image = render_image_plane(self.scene, image, scale, name)
|
||||||
|
|
||||||
def set_homog_group_transform(self, group_name, rotation, translation):
|
def set_homog_group_transform(self, group_name, rotation, translation):
|
||||||
# create pose matrix
|
# create pose matrix
|
||||||
@ -281,6 +294,27 @@ class Renderer:
|
|||||||
if self.requires_lock():
|
if self.requires_lock():
|
||||||
self.viewer.render_lock.release()
|
self.viewer.render_lock.release()
|
||||||
|
|
||||||
|
def get_snapshot(self, show_image=False, camera_pose=None):
|
||||||
|
"""get snapshot of the current renderer, only works in offscreen mode
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not self.use_offscreen:
|
||||||
|
print("[error] get_snapshot only works when used with offscreen renderer")
|
||||||
|
return None, None
|
||||||
|
|
||||||
|
if not show_image:
|
||||||
|
self.scene.remove_node(self.image)
|
||||||
|
|
||||||
|
color, depth = self.offscreen.render(
|
||||||
|
self.scene
|
||||||
|
)
|
||||||
|
|
||||||
|
# revert renderer changes
|
||||||
|
if not show_image:
|
||||||
|
self.scene.add_node(self.image)
|
||||||
|
|
||||||
|
return color
|
||||||
|
|
||||||
|
|
||||||
class DefaultRenderer(Renderer):
|
class DefaultRenderer(Renderer):
|
||||||
"""Utility class for easier default renderer setup
|
"""Utility class for easier default renderer setup
|
||||||
|
|||||||
@ -16,32 +16,51 @@ from renderer import Renderer
|
|||||||
|
|
||||||
def train_pose(
|
def train_pose(
|
||||||
model: smplx.SMPL,
|
model: smplx.SMPL,
|
||||||
|
# current datapoints
|
||||||
keypoints,
|
keypoints,
|
||||||
keypoint_conf,
|
keypoint_conf,
|
||||||
|
# 3D to 2D camera layer
|
||||||
camera: SimpleCamera,
|
camera: SimpleCamera,
|
||||||
|
|
||||||
|
# model type
|
||||||
model_type="smplx",
|
model_type="smplx",
|
||||||
learning_rate=1e-3,
|
|
||||||
|
# pytorch config
|
||||||
device=torch.device('cuda'),
|
device=torch.device('cuda'),
|
||||||
dtype=torch.float32,
|
dtype=torch.float32,
|
||||||
renderer: Renderer = None,
|
|
||||||
|
# optimizer settings
|
||||||
optimizer=None,
|
optimizer=None,
|
||||||
optimizer_type="LBFGS",
|
optimizer_type="LBFGS",
|
||||||
|
learning_rate=1e-3,
|
||||||
iterations=60,
|
iterations=60,
|
||||||
|
patience=10,
|
||||||
|
# configure loss function
|
||||||
useBodyPrior=False,
|
useBodyPrior=False,
|
||||||
|
body_prior_weight=2,
|
||||||
|
|
||||||
useAnglePrior=False,
|
useAnglePrior=False,
|
||||||
useConfWeights=False,
|
angle_prior_weight=0.5,
|
||||||
|
|
||||||
use_angle_sum_loss=False,
|
use_angle_sum_loss=False,
|
||||||
angle_sum_weight=0.1,
|
angle_sum_weight=0.1,
|
||||||
patience=10,
|
|
||||||
body_prior_weight=2,
|
|
||||||
angle_prior_weight=0.5,
|
|
||||||
body_mean_loss=False,
|
body_mean_loss=False,
|
||||||
body_mean_weight=0.01
|
body_mean_weight=0.01,
|
||||||
|
|
||||||
|
useConfWeights=False,
|
||||||
|
|
||||||
|
# renderer options
|
||||||
|
renderer: Renderer = None,
|
||||||
|
render_steps=True,
|
||||||
|
render_offscreen=True
|
||||||
):
|
):
|
||||||
|
|
||||||
print("[pose] starting training")
|
print("[pose] starting training")
|
||||||
print("[pose] dtype=", dtype)
|
print("[pose] dtype=", dtype)
|
||||||
|
|
||||||
|
offscreen_step_output = []
|
||||||
|
|
||||||
loss_layer = torch.nn.MSELoss().to(device=device, dtype=dtype) # MSELoss()
|
loss_layer = torch.nn.MSELoss().to(device=device, dtype=dtype) # MSELoss()
|
||||||
|
|
||||||
clip_loss_layer = AngleClipper().to(device=device, dtype=dtype)
|
clip_loss_layer = AngleClipper().to(device=device, dtype=dtype)
|
||||||
@ -52,7 +71,7 @@ def train_pose(
|
|||||||
# setup keypoint data
|
# setup keypoint data
|
||||||
keypoints = torch.tensor(keypoints).to(device=device, dtype=dtype)
|
keypoints = torch.tensor(keypoints).to(device=device, dtype=dtype)
|
||||||
# get a list of openpose conf values
|
# get a list of openpose conf values
|
||||||
keypoints_conf = torch.tensor(keypoint_conf).to(device=device, dtype=dtype)
|
# keypoints_conf = torch.tensor(keypoint_conf).to(device=device, dtype=dtype)
|
||||||
|
|
||||||
# create filter layer to ignore unused joints, keypoints during optimization
|
# create filter layer to ignore unused joints, keypoints during optimization
|
||||||
filter_layer = JointFilter(
|
filter_layer = JointFilter(
|
||||||
@ -89,7 +108,7 @@ def train_pose(
|
|||||||
pbar = tqdm(total=iterations)
|
pbar = tqdm(total=iterations)
|
||||||
|
|
||||||
def predict():
|
def predict():
|
||||||
pose_extra = None
|
# pose_extra = None
|
||||||
|
|
||||||
# if useBodyPrior:
|
# if useBodyPrior:
|
||||||
# body = vposer_layer()
|
# body = vposer_layer()
|
||||||
@ -120,7 +139,7 @@ def train_pose(
|
|||||||
body_prior_loss = 0.0
|
body_prior_loss = 0.0
|
||||||
if useBodyPrior:
|
if useBodyPrior:
|
||||||
# apply pose prior loss.
|
# apply pose prior loss.
|
||||||
body_prior_loss = latent_body.pow(
|
body_prior_loss = latent_pose.pow(
|
||||||
2).sum() * body_prior_weight
|
2).sum() * body_prior_weight
|
||||||
|
|
||||||
angle_prior_loss = 0.0
|
angle_prior_loss = 0.0
|
||||||
@ -162,9 +181,6 @@ def train_pose(
|
|||||||
pred = predict()
|
pred = predict()
|
||||||
loss = optim_closure()
|
loss = optim_closure()
|
||||||
|
|
||||||
# if t % 5 == 0:
|
|
||||||
# time.sleep(5)
|
|
||||||
|
|
||||||
# compute loss
|
# compute loss
|
||||||
cur_loss = loss.item()
|
cur_loss = loss.item()
|
||||||
|
|
||||||
@ -184,15 +200,18 @@ def train_pose(
|
|||||||
pbar.set_description("Error %f" % cur_loss)
|
pbar.set_description("Error %f" % cur_loss)
|
||||||
pbar.update(1)
|
pbar.update(1)
|
||||||
|
|
||||||
if renderer is not None:
|
if renderer is not None and render_steps:
|
||||||
R = camera.trans.detach().cpu().numpy().squeeze()
|
R = camera.trans.detach().cpu().numpy().squeeze()
|
||||||
renderer.render_model_with_tfs(
|
renderer.render_model_with_tfs(
|
||||||
model, pose_layer.cur_out, keep_pose=True, transforms=R)
|
model, pose_layer.cur_out, keep_pose=True, transforms=R)
|
||||||
|
|
||||||
|
if render_offscreen:
|
||||||
|
offscreen_step_output.append(renderer.get_snapshot())
|
||||||
# renderer.set_group_pose("body", R)
|
# renderer.set_group_pose("body", R)
|
||||||
|
|
||||||
pbar.close()
|
pbar.close()
|
||||||
print("Final result:", loss.item())
|
print("Final result:", loss.item())
|
||||||
return pose_layer.cur_out, best_pose, loss_history
|
return pose_layer.cur_out, best_pose, loss_history, offscreen_step_output
|
||||||
|
|
||||||
|
|
||||||
def train_pose_with_conf(
|
def train_pose_with_conf(
|
||||||
@ -204,6 +223,7 @@ def train_pose_with_conf(
|
|||||||
device=torch.device('cpu'),
|
device=torch.device('cpu'),
|
||||||
dtype=torch.float32,
|
dtype=torch.float32,
|
||||||
renderer: Renderer = None,
|
renderer: Renderer = None,
|
||||||
|
render_steps=True
|
||||||
):
|
):
|
||||||
|
|
||||||
# configure PyTorch device and format
|
# configure PyTorch device and format
|
||||||
@ -244,5 +264,6 @@ def train_pose_with_conf(
|
|||||||
body_mean_loss=config['pose']['bodyMeanLoss']['enabled'],
|
body_mean_loss=config['pose']['bodyMeanLoss']['enabled'],
|
||||||
body_mean_weight=config['pose']['bodyMeanLoss']['weight'],
|
body_mean_weight=config['pose']['bodyMeanLoss']['weight'],
|
||||||
use_angle_sum_loss=config['pose']['angleSumLoss']['enabled'],
|
use_angle_sum_loss=config['pose']['angleSumLoss']['enabled'],
|
||||||
angle_sum_weight=config['pose']['angleSumLoss']['weight']
|
angle_sum_weight=config['pose']['angleSumLoss']['weight'],
|
||||||
|
render_steps=render_steps
|
||||||
)
|
)
|
||||||
|
|||||||
@ -152,7 +152,7 @@ def get_new_filename():
|
|||||||
return result_prefix + str(num + 1) + ".pkl"
|
return result_prefix + str(num + 1) + ".pkl"
|
||||||
|
|
||||||
|
|
||||||
def setup_training(model, dataset, sample_index, renderer=True):
|
def setup_training(model, dataset, sample_index, renderer=True, offscreen=False):
|
||||||
keypoints, conf = dataset[sample_index]
|
keypoints, conf = dataset[sample_index]
|
||||||
img_path = dataset.get_image_path(sample_index)
|
img_path = dataset.get_image_path(sample_index)
|
||||||
|
|
||||||
@ -179,7 +179,7 @@ def setup_training(model, dataset, sample_index, renderer=True):
|
|||||||
|
|
||||||
if renderer:
|
if renderer:
|
||||||
# setup renderer
|
# setup renderer
|
||||||
r = DefaultRenderer()
|
r = DefaultRenderer(offscreen=offscreen)
|
||||||
r.setup(
|
r.setup(
|
||||||
model=model,
|
model=model,
|
||||||
model_out=model_out,
|
model_out=model_out,
|
||||||
|
|||||||
@ -2,6 +2,28 @@ from typing import List, Set, Dict, Tuple, Optional
|
|||||||
import numpy as np
|
import numpy as np
|
||||||
import trimesh
|
import trimesh
|
||||||
import pyrender
|
import pyrender
|
||||||
|
import cv2
|
||||||
|
|
||||||
|
from tqdm import tqdm
|
||||||
|
|
||||||
|
|
||||||
|
def make_video(images, video_name: str, fps=5):
|
||||||
|
|
||||||
|
images = np.array(images)
|
||||||
|
print(images.shape)
|
||||||
|
width = images.shape[2]
|
||||||
|
height = images.shape[1]
|
||||||
|
video = cv2.VideoWriter(
|
||||||
|
video_name, 0, fps, (width, height), True)
|
||||||
|
|
||||||
|
print("creating video with size", width, height)
|
||||||
|
|
||||||
|
for idx in tqdm(range(len(images))):
|
||||||
|
img = images[idx]
|
||||||
|
im_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
|
||||||
|
video.write(im_rgb)
|
||||||
|
|
||||||
|
video.release()
|
||||||
|
|
||||||
|
|
||||||
def render_model(
|
def render_model(
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user