-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathBvhJointHandler.py
265 lines (221 loc) · 9.07 KB
/
BvhJointHandler.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
import json
import numpy as np
import math
from pyquaternion import Quaternion
from typing import List
from bvh import Bvh
from tqdm import tqdm
class JointInfo:
""" Contains information on a certain joint in the DeepMimic human model as
well as some functions in order to convert joint data from BVH to DeepMimic.
"""
def __init__(self, deepMimicName: str, bvhName: str, dimensions: int,
zeroRotVector: List[float], childOffset: List[float]):
self.deepMimicName = deepMimicName
self.bvhName = bvhName
self.dimensions = dimensions
self.zeroRotVector = zeroRotVector
self.childOffset = childOffset
self.offsetQuat = self.calculateOffsetQuat()
def calculateOffsetQuat(self) -> Quaternion:
v1 = np.array(self.zeroRotVector)
v2 = np.array(self.childOffset)
# Normalize vectors
norm1 = np.linalg.norm(v1)
norm2 = np.linalg.norm(v2)
if norm1 > 0:
v1 = v1 / norm1
if norm2 > 0:
v2 = v2 / norm2
# Calculate perpendicular vector
perpvec = np.cross(v1, v2)
perpnorm = np.linalg.norm(perpvec)
if perpnorm > 0:
perpvec = perpvec / perpnorm
angle = math.acos(np.dot(v1, v2))
else:
perpvec = np.array([1, 0, 0])
angle = 0
# Calculate quaternion from angle axis form.
result = Quaternion(axis=perpvec, radians=angle)
# Note that this quaternion is defined in the frame of the BVH file.
return self.quatBvhToDM(result)
@staticmethod
def quatBvhToDM(quaternion: Quaternion) -> Quaternion:
# transform x -> z and z -> -x
return Quaternion(
quaternion[0],
quaternion[3],
quaternion[2],
-quaternion[1]
)
class BvhJointHandler:
""" Handles conversion of BVH files to DeepMimic format.
"""
def __init__(self, mocap: Bvh, rigPath="./Rigs/humanoidRig.json", posLocked=False):
self.mocap = mocap
self.posLocked = posLocked
# get json of humanoidRig
with open(rigPath) as json_data:
self.humanoidRig = json.load(json_data)
# Sets up list of bones used by DeepMimic humanoid
# Order is important
self.deepMimicHumanoidJoints = ["seconds", "hip", "hip", "chest", "neck", "right hip", "right knee", "right ankle",
"right shoulder", "right elbow", "left hip", "left knee", "left ankle", "left shoulder", "left elbow"]
self.jointDimensions = [1, 3, 4, 4, 4, 4, 1, 4, 4, 1, 4, 1, 4, 4, 1]
# Looking directly at the front of the model, X-axis points at you, Y-axis points straight up, Z-axis points left.
# Image of correct deepMimic humanoid bind pose: https://user-images.githubusercontent.com/43953552/61379957-cb485c80-a8a8-11e9-8b78-24f4bf581900.PNG
self.rotVecDict = {
"seconds": [],
"hip": [0, 0, 0],
"hip": [0, 0, 0],
"chest": [0, 1, 0],
"neck": [0, 1, 0],
"right hip": [0, -1, 0],
"right knee": [0, 0, 0],
"right ankle": [0, 0, 0],
"right shoulder": [0, -1, 0],
"right elbow": [0, 0, 0],
"left hip": [0, -1, 0],
"left knee": [0, 0, 0],
"left ankle": [0, 0, 0],
"left shoulder": [0, -1, 0],
"left elbow": [0, 0, 0]
}
self.jointChildDict = {
# TODO: expand BVH parser to allow to get child joints
# Child joints are necessary to be able to compute the offset quaternions correctly.
"Hips": "Hips",
"Spine": "Spine1",
"Neck": "Head",
"RightUpLeg": "RightLeg",
"RightLeg": "RightFoot",
"RightFoot": "RightToeBase",
"RightArm": "RightForeArm",
"RightForeArm": "RightHand",
"LeftUpLeg": "LeftLeg",
"LeftLeg": "LeftFoot",
"LeftFoot": "LeftToeBase",
"LeftArm": "LeftForeArm",
"LeftForeArm": "LeftHand"
}
self.positionChannelNames = ["Xposition", "Yposition", "Zposition"]
self.rotationChannelNames = ["Xrotation", "Yrotation", "Zrotation"]
self.generateJointData()
def generateJointData(self):
assert len(self.deepMimicHumanoidJoints) == len(self.jointDimensions)
self.jointData = []
for i in range(2, len(self.deepMimicHumanoidJoints)):
deepMimicBoneName = self.deepMimicHumanoidJoints[i]
bvhBoneName = self.bvhBoneName(deepMimicBoneName)
jointInfo = JointInfo(
deepMimicBoneName,
bvhBoneName,
self.jointDimensions[i],
self.rotVecDict[deepMimicBoneName],
self.getJointOffset(self.jointChildDict[bvhBoneName])
)
self.jointData.append(jointInfo)
def generateKeyFrame(self, frameNumber: int):
result = []
# Append Time
result.append(self.mocap.frame_time)
# Append hip root pos
if self.posLocked:
result.extend([2, 2, 2])
else:
result.extend(
self.getJointTranslation(frameNumber, self.jointData[0])
)
# Append hip rotation
result.extend(
self.getJointRotation(frameNumber, self.jointData[0])
)
# Append other rotations
for joint in self.jointData[1:]:
result.extend(self.getJointRotation(frameNumber, joint))
return result
def generateKeyFrames(self):
keyFrames = []
for i in tqdm(range(0, self.mocap.nframes)):
keyFrames.append(self.generateKeyFrame(i))
return keyFrames
def bvhBoneName(self, deepMimicBoneName):
return self.humanoidRig[deepMimicBoneName]
def getJointOffset(self, bvhJointName):
return list(self.mocap.joint_offset(bvhJointName))
def getJointTranslation(self, frameNumber: int, jointInfo: JointInfo):
# TODO: get channel names from file or params
channels = self.mocap.joint_channels(jointInfo.bvhName)
result = []
if len(channels) > 3:
for channel in self.positionChannelNames:
result.append(self.mocap.frame_joint_channel(
frameNumber, jointInfo.bvhName, channel))
return result
def getJointRotation(self, frameNumber: int, jointInfo: JointInfo) -> List[float]:
# Get the order of channel names as desribed in BVH file for this joint.
channels = self.mocap.joint_channels(jointInfo.bvhName)
# Only keep the rotation channels
channels = list(
filter(lambda x: x in self.rotationChannelNames, channels))
# Get the X, Y, Z euler angles
eulerAngles = []
for channel in self.rotationChannelNames:
eulerAngles.append(self.mocap.frame_joint_channel(
frameNumber, jointInfo.bvhName, channel))
# Calculate joint rotations
if jointInfo.dimensions > 1:
# 4D (quaternion) DeepMimic joint
rotation = self.eulerToQuat(eulerAngles, channels)
rotation = JointInfo.quatBvhToDM(rotation)
offset = jointInfo.offsetQuat
result = offset * rotation
return result.elements
else:
# 1D DeepMimic joint
assert jointInfo.dimensions == 1
# TODO: calculate this angle correctly (keep all DOF's into account)
return [-math.radians(eulerAngles[0])]
def eulerToQuat(self, angles: List[float], channels: List[str]):
# Convert angles to radians
Xangle = math.radians(angles[0])
Yangle = math.radians(angles[1])
Zangle = math.radians(angles[2])
# Create the rotation matrix for every euler angle
# See: https://en.wikipedia.org/wiki/Rotation_matrix#In_three_dimensions
Xrot = np.array(
[
[1, 0, 0],
[0, math.cos(Xangle), -math.sin(Xangle)],
[0, math.sin(Xangle), math.cos(Xangle)]
]
)
Yrot = np.array(
[
[math.cos(Yangle), 0, math.sin(Yangle)],
[0, 1, 0],
[-math.sin(Yangle), 0, math.cos(Yangle)]
]
)
Zrot = np.array(
[
[math.cos(Zangle), -math.sin(Zangle), 0],
[math.sin(Zangle), math.cos(Zangle), 0],
[0, 0, 1]
]
)
# Connect the rotation channel names to corresponding matrices
rotationDict = {
self.rotationChannelNames[0]: Xrot,
self.rotationChannelNames[1]: Yrot,
self.rotationChannelNames[2]: Zrot
}
# Compute the final rotation matrix in the order as the BVH file describes.
rotationMatrix = rotationDict[channels[2]].dot(
rotationDict[channels[1]].dot(
rotationDict[channels[0]]
)
)
# Return the corresponding quaternion
return Quaternion(matrix=rotationMatrix)