Skip to content

Commit 896845d

Browse files
Fix head pose in standalone script (fix #48)
1 parent d1ba939 commit 896845d

File tree

2 files changed

+103
-2
lines changed

2 files changed

+103
-2
lines changed

rt_gene/scripts/estimate_gaze_standalone.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from rt_gene.gaze_tools import get_phi_theta_from_euler, limit_yaw
1717
from rt_gene.extract_landmarks_method_base import LandmarkMethodBase
1818
from rt_gene.estimate_gaze_base import GazeEstimatorBase
19+
from rt_gene.gaze_tools_standalone import euler_from_matrix
1920

2021
script_path = os.path.dirname(os.path.realpath(__file__))
2122

@@ -68,8 +69,18 @@ def estimate_gaze(base_name, color_img, dist_coefficients, camera_matrix):
6869
tqdm.write('Not able to extract head pose for subject {}'.format(idx))
6970
continue
7071

71-
roll_pitch_yaw = [-rotation_vector[2], -rotation_vector[0], rotation_vector[1] + np.pi]
72-
roll_pitch_yaw = limit_yaw(np.array(roll_pitch_yaw).flatten().tolist())
72+
_rotation_matrix, _ = cv2.Rodrigues(rotation_vector)
73+
_rotation_matrix = np.matmul(_rotation_matrix, np.array([[0, 1, 0], [0, 0, -1], [-1, 0, 0]]))
74+
_m = np.zeros((4, 4))
75+
_m[:3, :3] = _rotation_matrix
76+
_m[3, 3] = 1
77+
# Go from camera space to ROS space
78+
_camera_to_ros = [[0.0, 0.0, 1.0, 0.0],
79+
[-1.0, 0.0, 0.0, 0.0],
80+
[0.0, -1.0, 0.0, 0.0],
81+
[0.0, 0.0, 0.0, 1.0]]
82+
roll_pitch_yaw = list(euler_from_matrix(np.dot(_camera_to_ros, _m)))
83+
roll_pitch_yaw = limit_yaw(roll_pitch_yaw)
7384

7485
phi_head, theta_head = get_phi_theta_from_euler(roll_pitch_yaw)
7586

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
# Copyright (c) 2006, Christoph Gohlke
2+
# Copyright (c) 2006-2009, The Regents of the University of California
3+
# All rights reserved.
4+
#
5+
# Redistribution and use in source and binary forms, with or without
6+
# modification, are permitted provided that the following conditions are met:
7+
#
8+
# * Redistributions of source code must retain the above copyright
9+
# notice, this list of conditions and the following disclaimer.
10+
# * Redistributions in binary form must reproduce the above copyright
11+
# notice, this list of conditions and the following disclaimer in the
12+
# documentation and/or other materials provided with the distribution.
13+
# * Neither the name of the copyright holders nor the names of any
14+
# contributors may be used to endorse or promote products derived
15+
# from this software without specific prior written permission.
16+
#
17+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20+
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
21+
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22+
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23+
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24+
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25+
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26+
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27+
# POSSIBILITY OF SUCH DAMAGE.
28+
29+
30+
import math
31+
import numpy
32+
33+
# map axes strings to/from tuples of inner axis, parity, repetition, frame
34+
_AXES2TUPLE = {
35+
'sxyz': (0, 0, 0, 0), 'sxyx': (0, 0, 1, 0), 'sxzy': (0, 1, 0, 0),
36+
'sxzx': (0, 1, 1, 0), 'syzx': (1, 0, 0, 0), 'syzy': (1, 0, 1, 0),
37+
'syxz': (1, 1, 0, 0), 'syxy': (1, 1, 1, 0), 'szxy': (2, 0, 0, 0),
38+
'szxz': (2, 0, 1, 0), 'szyx': (2, 1, 0, 0), 'szyz': (2, 1, 1, 0),
39+
'rzyx': (0, 0, 0, 1), 'rxyx': (0, 0, 1, 1), 'ryzx': (0, 1, 0, 1),
40+
'rxzx': (0, 1, 1, 1), 'rxzy': (1, 0, 0, 1), 'ryzy': (1, 0, 1, 1),
41+
'rzxy': (1, 1, 0, 1), 'ryxy': (1, 1, 1, 1), 'ryxz': (2, 0, 0, 1),
42+
'rzxz': (2, 0, 1, 1), 'rxyz': (2, 1, 0, 1), 'rzyz': (2, 1, 1, 1)}
43+
44+
_TUPLE2AXES = dict((v, k) for k, v in _AXES2TUPLE.items())
45+
46+
# axis sequences for Euler angles
47+
_NEXT_AXIS = [1, 2, 0, 1]
48+
49+
# epsilon for testing whether a number is close to zero
50+
_EPS = numpy.finfo(float).eps * 4.0
51+
52+
53+
def euler_from_matrix(matrix, axes='sxyz'):
54+
try:
55+
firstaxis, parity, repetition, frame = _AXES2TUPLE[axes.lower()]
56+
except (AttributeError, KeyError):
57+
_ = _TUPLE2AXES[axes]
58+
firstaxis, parity, repetition, frame = axes
59+
60+
i = firstaxis
61+
j = _NEXT_AXIS[i+parity]
62+
k = _NEXT_AXIS[i-parity+1]
63+
64+
M = numpy.array(matrix, dtype=numpy.float64, copy=False)[:3, :3]
65+
if repetition:
66+
sy = math.sqrt(M[i, j]*M[i, j] + M[i, k]*M[i, k])
67+
if sy > _EPS:
68+
ax = math.atan2( M[i, j], M[i, k])
69+
ay = math.atan2( sy, M[i, i])
70+
az = math.atan2( M[j, i], -M[k, i])
71+
else:
72+
ax = math.atan2(-M[j, k], M[j, j])
73+
ay = math.atan2( sy, M[i, i])
74+
az = 0.0
75+
else:
76+
cy = math.sqrt(M[i, i]*M[i, i] + M[j, i]*M[j, i])
77+
if cy > _EPS:
78+
ax = math.atan2( M[k, j], M[k, k])
79+
ay = math.atan2(-M[k, i], cy)
80+
az = math.atan2( M[j, i], M[i, i])
81+
else:
82+
ax = math.atan2(-M[j, k], M[j, j])
83+
ay = math.atan2(-M[k, i], cy)
84+
az = 0.0
85+
86+
if parity:
87+
ax, ay, az = -ax, -ay, -az
88+
if frame:
89+
ax, az = az, ax
90+
return ax, ay, az

0 commit comments

Comments
 (0)