fasd / tddfa /utils /pose.py
ozyman's picture
added depth model
ddadf19
# coding: utf-8
"""
Reference: https://github.com/YadiraF/PRNet/blob/master/utils/estimate_pose.py
Calculating pose from the output 3DMM parameters, you can also try to use solvePnP to perform estimation
"""
__author__ = 'cleardusk'
import cv2
import numpy as np
from math import cos, sin, atan2, asin, sqrt
from .functions import calc_hypotenuse, plot_image
def P2sRt(P):
""" decompositing camera matrix P.
Args:
P: (3, 4). Affine Camera Matrix.
Returns:
s: scale factor.
R: (3, 3). rotation matrix.
t2d: (2,). 2d translation.
"""
t3d = P[:, 3]
R1 = P[0:1, :3]
R2 = P[1:2, :3]
s = (np.linalg.norm(R1) + np.linalg.norm(R2)) / 2.0
r1 = R1 / np.linalg.norm(R1)
r2 = R2 / np.linalg.norm(R2)
r3 = np.cross(r1, r2)
R = np.concatenate((r1, r2, r3), 0)
return s, R, t3d
def matrix2angle(R):
""" compute three Euler angles from a Rotation Matrix. Ref: http://www.gregslabaugh.net/publications/euler.pdf
refined by: https://stackoverflow.com/questions/43364900/rotation-matrix-to-euler-angles-with-opencv
todo: check and debug
Args:
R: (3,3). rotation matrix
Returns:
x: yaw
y: pitch
z: roll
"""
if R[2, 0] > 0.998:
z = 0
x = np.pi / 2
y = z + atan2(-R[0, 1], -R[0, 2])
elif R[2, 0] < -0.998:
z = 0
x = -np.pi / 2
y = -z + atan2(R[0, 1], R[0, 2])
else:
x = asin(R[2, 0])
y = atan2(R[2, 1] / cos(x), R[2, 2] / cos(x))
z = atan2(R[1, 0] / cos(x), R[0, 0] / cos(x))
return x, y, z
def calc_pose(param):
P = param[:12].reshape(3, -1) # camera matrix
s, R, t3d = P2sRt(P)
P = np.concatenate((R, t3d.reshape(3, -1)), axis=1) # without scale
pose = matrix2angle(R)
pose = [p * 180 / np.pi for p in pose]
return P, pose
def build_camera_box(rear_size=90):
point_3d = []
rear_depth = 0
point_3d.append((-rear_size, -rear_size, rear_depth))
point_3d.append((-rear_size, rear_size, rear_depth))
point_3d.append((rear_size, rear_size, rear_depth))
point_3d.append((rear_size, -rear_size, rear_depth))
point_3d.append((-rear_size, -rear_size, rear_depth))
front_size = int(4 / 3 * rear_size)
front_depth = int(4 / 3 * rear_size)
point_3d.append((-front_size, -front_size, front_depth))
point_3d.append((-front_size, front_size, front_depth))
point_3d.append((front_size, front_size, front_depth))
point_3d.append((front_size, -front_size, front_depth))
point_3d.append((-front_size, -front_size, front_depth))
point_3d = np.array(point_3d, dtype=np.float32).reshape(-1, 3)
return point_3d
def plot_pose_box(img, P, ver, color=(40, 255, 0), line_width=2):
""" Draw a 3D box as annotation of pose.
Ref:https://github.com/yinguobing/head-pose-estimation/blob/master/pose_estimator.py
Args:
img: the input image
P: (3, 4). Affine Camera Matrix.
kpt: (2, 68) or (3, 68)
"""
llength = calc_hypotenuse(ver)
point_3d = build_camera_box(llength)
# Map to 2d image points
point_3d_homo = np.hstack((point_3d, np.ones([point_3d.shape[0], 1]))) # n x 4
point_2d = point_3d_homo.dot(P.T)[:, :2]
point_2d[:, 1] = - point_2d[:, 1]
point_2d[:, :2] = point_2d[:, :2] - np.mean(point_2d[:4, :2], 0) + np.mean(ver[:2, :27], 1)
point_2d = np.int32(point_2d.reshape(-1, 2))
# Draw all the lines
cv2.polylines(img, [point_2d], True, color, line_width, cv2.LINE_AA)
cv2.line(img, tuple(point_2d[1]), tuple(
point_2d[6]), color, line_width, cv2.LINE_AA)
cv2.line(img, tuple(point_2d[2]), tuple(
point_2d[7]), color, line_width, cv2.LINE_AA)
cv2.line(img, tuple(point_2d[3]), tuple(
point_2d[8]), color, line_width, cv2.LINE_AA)
return img
def viz_pose(img, param_lst, ver_lst, show_flag=False, wfp=None):
for param, ver in zip(param_lst, ver_lst):
P, pose = calc_pose(param)
img = plot_pose_box(img, P, ver)
# print(P[:, :3])
print(f'yaw: {pose[0]:.1f}, pitch: {pose[1]:.1f}, roll: {pose[2]:.1f}')
if wfp is not None:
cv2.imwrite(wfp, img)
print(f'Save visualization result to {wfp}')
if show_flag:
plot_image(img)
return img