|
import cv2 |
|
import math |
|
import numpy as np |
|
import matplotlib.pyplot as plt |
|
import dearpygui.dearpygui as dpg |
|
from scipy.spatial.transform import Rotation as R |
|
from utils.commons.hparams import set_hparams, hparams |
|
from data_util.face3d_helper import Face3DHelper |
|
|
|
face3d_helper = Face3DHelper(use_gpu=False) |
|
|
|
|
|
set_hparams("egs/datasets/videos/May/radnerf_torso.yaml") |
|
|
|
from tasks.radnerfs.dataset_utils import RADNeRFDataset |
|
dataset = RADNeRFDataset("val") |
|
idexp_lm3d_mean = dataset.idexp_lm3d_mean.reshape([68,3]) |
|
lm3d_mean = idexp_lm3d_mean / 10 + face3d_helper.key_mean_shape |
|
lm3d_mean /= 1.5 |
|
|
|
class Landmark3D: |
|
|
|
def __init__(self): |
|
|
|
|
|
self.points3D = np.concatenate([lm3d_mean.numpy(), np.ones([68,1])],axis=1).reshape([68,4]) |
|
|
|
|
|
self.lines = [ |
|
|
|
[0, 1], [1, 2], [2, 3], [3, 4], [4, 5], [5,6], [6,7], [7,8], [8,9], [9,10], [10,11], [11,12], [12,13], [13,14], [14,15], [15,16], |
|
|
|
[17,18], [18,19], [19,20], [20,21], |
|
|
|
[22, 23], [23,24], [24,25], [25,26], |
|
|
|
[27,28], [28,29], [29,30], [31,32], [32,33], [33,34], [34,35], |
|
|
|
[36,37], [37,38], [38,39], [39,40], [40,41], [41,36], |
|
|
|
[42,43], [43,44], [44,45], [45,46], [46,47], [47,42], |
|
|
|
[48, 49], [49,50], [50,51], [51,52], [52,53], [53,54], [54,55], [55,56], [56,57], [57,58], [58,59],[59,48], |
|
[48, 60], [60,61], [61,62], [62,63], [63,64], [64,65], [65,66], [66,67], [67,60], [54,64] |
|
] |
|
|
|
|
|
|
|
|
|
|
|
self.colors = [[0,0,255] for _ in range(36)] + [[0,255,0] for _ in range(12)]+ [[255,0,0] for _ in range(20)] |
|
self.line_colors = [[0,0,255] for _ in range(31)] + [[0,255,0] for _ in range(12)]+ [[255,0,0] for _ in range(22)] |
|
|
|
def draw(self, mvp, H, W): |
|
|
|
|
|
canvas = np.zeros((H, W, 3), dtype=np.uint8) |
|
|
|
points2D = self.points3D @ mvp.T |
|
points2D = points2D[:, :3] / points2D[:, 3:] |
|
|
|
xs = (points2D[:, 0] + 1) / 2 * H |
|
ys = (points2D[:, 1] + 1) / 2 * W |
|
|
|
|
|
for i in range(len(self.points3D)): |
|
cv2.circle(canvas, (int(xs[i]), int(ys[i])), 4, self.colors[i], thickness=-1) |
|
|
|
|
|
for i in range(len(self.lines)): |
|
cur_canvas = canvas.copy() |
|
X = xs[self.lines[i]] |
|
Y = ys[self.lines[i]] |
|
mY = np.mean(Y) |
|
mX = np.mean(X) |
|
length = ((Y[0] - Y[1]) ** 2 + (X[0] - X[1]) ** 2) ** 0.5 |
|
angle = math.degrees(math.atan2(Y[0] - Y[1], X[0] - X[1])) |
|
polygon = cv2.ellipse2Poly((int(mX), int(mY)), (int(length / 2), 4), int(angle), 0, 360, 1) |
|
|
|
cv2.fillConvexPoly(cur_canvas, polygon, self.line_colors[i]) |
|
|
|
canvas = cv2.addWeighted(canvas, 0.4, cur_canvas, 0.6, 0) |
|
|
|
canvas = canvas.astype(np.float32) / 255 |
|
return canvas, np.stack([xs, ys], axis=1) |
|
|
|
|
|
class OrbitCamera: |
|
def __init__(self, W, H, r=2, fovy=60, near=0.01, far=100): |
|
self.W = W |
|
self.H = H |
|
self.radius = r |
|
self.fovy = fovy |
|
self.near = near |
|
self.far = far |
|
self.center = np.array([0, 0, 0], dtype=np.float32) |
|
self.rot = R.from_matrix(np.eye(3)) |
|
self.up = np.array([0, 1, 0], dtype=np.float32) |
|
|
|
|
|
@property |
|
def pose(self): |
|
|
|
res = np.eye(4, dtype=np.float32) |
|
res[2, 3] = self.radius |
|
|
|
rot = np.eye(4, dtype=np.float32) |
|
rot[:3, :3] = self.rot.as_matrix() |
|
res = rot @ res |
|
|
|
res[:3, 3] -= self.center |
|
return res |
|
|
|
|
|
@property |
|
def view(self): |
|
return np.linalg.inv(self.pose) |
|
|
|
|
|
@property |
|
def intrinsics(self): |
|
focal = self.H / (2 * np.tan(np.radians(self.fovy) / 2)) |
|
return np.array([focal, focal, self.W // 2, self.H // 2], dtype=np.float32) |
|
|
|
|
|
@property |
|
def perspective(self): |
|
y = np.tan(np.radians(self.fovy) / 2) |
|
aspect = self.W / self.H |
|
return np.array([[1/(y*aspect), 0, 0, 0], |
|
[ 0, -1/y, 0, 0], |
|
[ 0, 0, -(self.far+self.near)/(self.far-self.near), -(2*self.far*self.near)/(self.far-self.near)], |
|
[ 0, 0, -1, 0]], dtype=np.float32) |
|
|
|
|
|
def orbit(self, dx, dy): |
|
|
|
side = self.rot.as_matrix()[:3, 0] |
|
rotvec_x = self.up * np.radians(-0.05 * dx) |
|
rotvec_y = side * np.radians(-0.05 * dy) |
|
self.rot = R.from_rotvec(rotvec_x) * R.from_rotvec(rotvec_y) * self.rot |
|
|
|
def scale(self, delta): |
|
self.radius *= 1.1 ** (-delta) |
|
|
|
def pan(self, dx, dy, dz=0): |
|
|
|
self.center += 0.0005 * self.rot.as_matrix()[:3, :3] @ np.array([dx, -dy, dz]) |
|
|
|
|
|
class GUI: |
|
def __init__(self, opt): |
|
self.opt = opt |
|
self.W = opt.W |
|
self.H = opt.H |
|
self.cam = OrbitCamera(opt.W, opt.H, r=opt.radius, fovy=opt.fovy) |
|
|
|
self.skel = Landmark3D() |
|
|
|
self.render_buffer = np.zeros((self.W, self.H, 3), dtype=np.float32) |
|
self.need_update = True |
|
|
|
self.save_path = 'pose.png' |
|
self.mouse_loc = np.array([0, 0]) |
|
self.points2D = None |
|
self.point_idx = 0 |
|
|
|
dpg.create_context() |
|
self.register_dpg() |
|
self.step() |
|
|
|
|
|
def __del__(self): |
|
dpg.destroy_context() |
|
|
|
|
|
def step(self): |
|
|
|
if self.need_update: |
|
|
|
|
|
mv = self.cam.view |
|
proj = self.cam.perspective |
|
mvp = proj @ mv |
|
|
|
|
|
self.render_buffer, self.points2D = self.skel.draw(mvp, self.H, self.W) |
|
|
|
self.need_update = False |
|
|
|
dpg.set_value("_texture", self.render_buffer) |
|
|
|
|
|
def register_dpg(self): |
|
|
|
|
|
|
|
with dpg.texture_registry(show=False): |
|
dpg.add_raw_texture(self.W, self.H, self.render_buffer, format=dpg.mvFormat_Float_rgb, tag="_texture") |
|
|
|
|
|
|
|
|
|
with dpg.window(label="Viewer", tag="_primary_window", width=self.W, height=self.H): |
|
dpg.add_image("_texture") |
|
|
|
dpg.set_primary_window("_primary_window", True) |
|
|
|
|
|
with dpg.window(label="Control", tag="_control_window", width=-1, height=-1): |
|
|
|
|
|
with dpg.theme() as theme_button: |
|
with dpg.theme_component(dpg.mvButton): |
|
dpg.add_theme_color(dpg.mvThemeCol_Button, (23, 3, 18)) |
|
dpg.add_theme_color(dpg.mvThemeCol_ButtonHovered, (51, 3, 47)) |
|
dpg.add_theme_color(dpg.mvThemeCol_ButtonActive, (83, 18, 83)) |
|
dpg.add_theme_style(dpg.mvStyleVar_FrameRounding, 5) |
|
dpg.add_theme_style(dpg.mvStyleVar_FramePadding, 3, 3) |
|
|
|
def callback_save(sender, app_data): |
|
image = (self.render_buffer * 255).astype(np.uint8) |
|
image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR) |
|
cv2.imwrite(self.save_path, image) |
|
print(f'[INFO] write image to {self.save_path}') |
|
|
|
def callback_set_save_path(sender, app_data): |
|
self.save_path = app_data |
|
|
|
with dpg.group(horizontal=True): |
|
dpg.add_button(label="save image", tag="_button_save", callback=callback_save) |
|
dpg.bind_item_theme("_button_save", theme_button) |
|
|
|
dpg.add_input_text(label="", default_value=self.save_path, callback=callback_set_save_path) |
|
|
|
|
|
def callback_set_fovy(sender, app_data): |
|
self.cam.fovy = app_data |
|
self.need_update = True |
|
|
|
dpg.add_slider_int(label="FoV (vertical)", min_value=1, max_value=120, format="%d deg", default_value=self.cam.fovy, callback=callback_set_fovy) |
|
|
|
|
|
|
|
|
|
def callback_camera_drag_rotate(sender, app_data): |
|
|
|
if not dpg.is_item_focused("_primary_window"): |
|
return |
|
|
|
|
|
|
|
|
|
|
|
self.need_update = True |
|
|
|
|
|
def callback_camera_wheel_scale(sender, app_data): |
|
|
|
if not dpg.is_item_focused("_primary_window"): |
|
return |
|
|
|
delta = app_data |
|
|
|
self.cam.scale(delta) |
|
self.need_update = True |
|
|
|
|
|
def callback_camera_drag_pan(sender, app_data): |
|
|
|
if not dpg.is_item_focused("_primary_window"): |
|
return |
|
|
|
dx = app_data[1] |
|
dy = app_data[2] |
|
|
|
self.cam.pan(dx, dy) |
|
self.need_update = True |
|
|
|
def callback_set_mouse_loc(sender, app_data): |
|
|
|
if not dpg.is_item_focused("_primary_window"): |
|
return |
|
|
|
|
|
self.mouse_loc = np.array(app_data) |
|
|
|
def callback_skel_select(sender, app_data): |
|
|
|
if not dpg.is_item_focused("_primary_window"): |
|
return |
|
|
|
|
|
if self.points2D is None: return |
|
|
|
dist = np.linalg.norm(self.points2D - self.mouse_loc, axis=1) |
|
self.point_idx = np.argmin(dist) |
|
|
|
|
|
def callback_skel_drag(sender, app_data): |
|
|
|
if not dpg.is_item_focused("_primary_window"): |
|
return |
|
|
|
|
|
dx = app_data[1] |
|
dy = app_data[2] |
|
|
|
self.skel.points3D[self.point_idx, :3] += 0.0002 * self.cam.rot.as_matrix()[:3, :3] @ np.array([dx, -dy, 0]) |
|
self.need_update = True |
|
|
|
|
|
with dpg.handler_registry(): |
|
dpg.add_mouse_drag_handler(button=dpg.mvMouseButton_Left, callback=callback_camera_drag_rotate) |
|
dpg.add_mouse_wheel_handler(callback=callback_camera_wheel_scale) |
|
dpg.add_mouse_drag_handler(button=dpg.mvMouseButton_Middle, callback=callback_camera_drag_pan) |
|
|
|
|
|
dpg.add_mouse_move_handler(callback=callback_set_mouse_loc) |
|
dpg.add_mouse_click_handler(button=dpg.mvMouseButton_Right, callback=callback_skel_select) |
|
dpg.add_mouse_drag_handler(button=dpg.mvMouseButton_Right, callback=callback_skel_drag) |
|
|
|
|
|
dpg.create_viewport(title='pose viewer', resizable=False, width=self.W, height=self.H) |
|
|
|
|
|
with dpg.theme() as theme_no_padding: |
|
with dpg.theme_component(dpg.mvAll): |
|
|
|
dpg.add_theme_style(dpg.mvStyleVar_WindowPadding, 0, 0, category=dpg.mvThemeCat_Core) |
|
dpg.add_theme_style(dpg.mvStyleVar_FramePadding, 0, 0, category=dpg.mvThemeCat_Core) |
|
dpg.add_theme_style(dpg.mvStyleVar_CellPadding, 0, 0, category=dpg.mvThemeCat_Core) |
|
|
|
dpg.bind_item_theme("_primary_window", theme_no_padding) |
|
dpg.focus_item("_primary_window") |
|
|
|
dpg.setup_dearpygui() |
|
|
|
|
|
|
|
dpg.show_viewport() |
|
|
|
|
|
def render(self): |
|
|
|
while dpg.is_dearpygui_running(): |
|
self.step() |
|
dpg.render_dearpygui_frame() |
|
|
|
|
|
if __name__ == '__main__': |
|
|
|
import argparse |
|
|
|
parser = argparse.ArgumentParser() |
|
parser.add_argument('--W', type=int, default=512, help="GUI width") |
|
parser.add_argument('--H', type=int, default=512, help="GUI height") |
|
parser.add_argument('--radius', type=float, default=3, help="default GUI camera radius from center") |
|
parser.add_argument('--fovy', type=float, default=25, help="default GUI camera fovy") |
|
|
|
opt = parser.parse_args() |
|
|
|
gui = GUI(opt) |
|
gui.render() |