Spaces:
Runtime error
Runtime error
kleinhe
commited on
Commit
·
c3d0293
0
Parent(s):
init
Browse filesThis view is limited to 50 files because it contains too many changes.
See raw diff
- .gitattributes +35 -0
- .gitignore +4 -0
- LICENSE +21 -0
- SMPLX/__pycache__/joints2smpl.cpython-310.pyc +0 -0
- SMPLX/__pycache__/joints2smpl.cpython-39.pyc +0 -0
- SMPLX/__pycache__/read_from_npy.cpython-310.pyc +0 -0
- SMPLX/__pycache__/read_from_npy.cpython-311.pyc +0 -0
- SMPLX/__pycache__/read_from_npy.cpython-39.pyc +0 -0
- SMPLX/__pycache__/read_joints_from_pose.cpython-39.pyc +0 -0
- SMPLX/__pycache__/rotation_conversions.cpython-310.pyc +0 -0
- SMPLX/__pycache__/rotation_conversions.cpython-311.pyc +0 -0
- SMPLX/__pycache__/rotation_conversions.cpython-39.pyc +0 -0
- SMPLX/__pycache__/transfer_smpls.cpython-39.pyc +0 -0
- SMPLX/__pycache__/visual_amass.cpython-39.pyc +0 -0
- SMPLX/__pycache__/visualize.cpython-38.pyc +0 -0
- SMPLX/config_files/smpl2smplh.yaml +25 -0
- SMPLX/config_files/smpl2smplx.yaml +26 -0
- SMPLX/config_files/smplh2smpl.yaml +24 -0
- SMPLX/config_files/smplh2smplx.yaml +26 -0
- SMPLX/config_files/smplh2smplx_as.yaml +26 -0
- SMPLX/config_files/smplh2smplx_onepose.yaml +27 -0
- SMPLX/config_files/smplx2smpl.yaml +25 -0
- SMPLX/config_files/smplx2smplh.yaml +27 -0
- SMPLX/joints2smpl.py +59 -0
- SMPLX/read_from_npy.py +108 -0
- SMPLX/read_joints_from_pose.py +110 -0
- SMPLX/rotation_conversions.py +532 -0
- SMPLX/smplx/__init__.py +30 -0
- SMPLX/smplx/__pycache__/__init__.cpython-310.pyc +0 -0
- SMPLX/smplx/__pycache__/__init__.cpython-311.pyc +0 -0
- SMPLX/smplx/__pycache__/__init__.cpython-39.pyc +0 -0
- SMPLX/smplx/__pycache__/body_models.cpython-310.pyc +0 -0
- SMPLX/smplx/__pycache__/body_models.cpython-311.pyc +0 -0
- SMPLX/smplx/__pycache__/body_models.cpython-39.pyc +0 -0
- SMPLX/smplx/__pycache__/joint_names.cpython-39.pyc +0 -0
- SMPLX/smplx/__pycache__/lbs.cpython-310.pyc +0 -0
- SMPLX/smplx/__pycache__/lbs.cpython-311.pyc +0 -0
- SMPLX/smplx/__pycache__/lbs.cpython-39.pyc +0 -0
- SMPLX/smplx/__pycache__/utils.cpython-310.pyc +0 -0
- SMPLX/smplx/__pycache__/utils.cpython-311.pyc +0 -0
- SMPLX/smplx/__pycache__/utils.cpython-39.pyc +0 -0
- SMPLX/smplx/__pycache__/vertex_ids.cpython-310.pyc +0 -0
- SMPLX/smplx/__pycache__/vertex_ids.cpython-311.pyc +0 -0
- SMPLX/smplx/__pycache__/vertex_ids.cpython-39.pyc +0 -0
- SMPLX/smplx/__pycache__/vertex_joint_selector.cpython-310.pyc +0 -0
- SMPLX/smplx/__pycache__/vertex_joint_selector.cpython-311.pyc +0 -0
- SMPLX/smplx/__pycache__/vertex_joint_selector.cpython-39.pyc +0 -0
- SMPLX/smplx/body_models.py +0 -0
- SMPLX/smplx/joint_names.py +320 -0
- SMPLX/smplx/lbs.py +405 -0
.gitattributes
ADDED
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
*.7z filter=lfs diff=lfs merge=lfs -text
|
2 |
+
*.arrow filter=lfs diff=lfs merge=lfs -text
|
3 |
+
*.bin filter=lfs diff=lfs merge=lfs -text
|
4 |
+
*.bz2 filter=lfs diff=lfs merge=lfs -text
|
5 |
+
*.ckpt filter=lfs diff=lfs merge=lfs -text
|
6 |
+
*.ftz filter=lfs diff=lfs merge=lfs -text
|
7 |
+
*.gz filter=lfs diff=lfs merge=lfs -text
|
8 |
+
*.h5 filter=lfs diff=lfs merge=lfs -text
|
9 |
+
*.joblib filter=lfs diff=lfs merge=lfs -text
|
10 |
+
*.lfs.* filter=lfs diff=lfs merge=lfs -text
|
11 |
+
*.mlmodel filter=lfs diff=lfs merge=lfs -text
|
12 |
+
*.model filter=lfs diff=lfs merge=lfs -text
|
13 |
+
*.msgpack filter=lfs diff=lfs merge=lfs -text
|
14 |
+
*.npy filter=lfs diff=lfs merge=lfs -text
|
15 |
+
*.npz filter=lfs diff=lfs merge=lfs -text
|
16 |
+
*.onnx filter=lfs diff=lfs merge=lfs -text
|
17 |
+
*.ot filter=lfs diff=lfs merge=lfs -text
|
18 |
+
*.parquet filter=lfs diff=lfs merge=lfs -text
|
19 |
+
*.pb filter=lfs diff=lfs merge=lfs -text
|
20 |
+
*.pickle filter=lfs diff=lfs merge=lfs -text
|
21 |
+
*.pkl filter=lfs diff=lfs merge=lfs -text
|
22 |
+
*.pt filter=lfs diff=lfs merge=lfs -text
|
23 |
+
*.pth filter=lfs diff=lfs merge=lfs -text
|
24 |
+
*.rar filter=lfs diff=lfs merge=lfs -text
|
25 |
+
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
26 |
+
saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
27 |
+
*.tar.* filter=lfs diff=lfs merge=lfs -text
|
28 |
+
*.tar filter=lfs diff=lfs merge=lfs -text
|
29 |
+
*.tflite filter=lfs diff=lfs merge=lfs -text
|
30 |
+
*.tgz filter=lfs diff=lfs merge=lfs -text
|
31 |
+
*.wasm filter=lfs diff=lfs merge=lfs -text
|
32 |
+
*.xz filter=lfs diff=lfs merge=lfs -text
|
33 |
+
*.zip filter=lfs diff=lfs merge=lfs -text
|
34 |
+
*.zst filter=lfs diff=lfs merge=lfs -text
|
35 |
+
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
.gitignore
ADDED
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
1 |
+
body_models
|
2 |
+
results
|
3 |
+
weights
|
4 |
+
tada-extend
|
LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
MIT License
|
2 |
+
|
3 |
+
Copyright (c) 2023 xin he
|
4 |
+
|
5 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6 |
+
of this software and associated documentation files (the "Software"), to deal
|
7 |
+
in the Software without restriction, including without limitation the rights
|
8 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9 |
+
copies of the Software, and to permit persons to whom the Software is
|
10 |
+
furnished to do so, subject to the following conditions:
|
11 |
+
|
12 |
+
The above copyright notice and this permission notice shall be included in all
|
13 |
+
copies or substantial portions of the Software.
|
14 |
+
|
15 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16 |
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17 |
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18 |
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19 |
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20 |
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21 |
+
SOFTWARE.
|
SMPLX/__pycache__/joints2smpl.cpython-310.pyc
ADDED
Binary file (1.71 kB). View file
|
|
SMPLX/__pycache__/joints2smpl.cpython-39.pyc
ADDED
Binary file (1.71 kB). View file
|
|
SMPLX/__pycache__/read_from_npy.cpython-310.pyc
ADDED
Binary file (2.75 kB). View file
|
|
SMPLX/__pycache__/read_from_npy.cpython-311.pyc
ADDED
Binary file (6.76 kB). View file
|
|
SMPLX/__pycache__/read_from_npy.cpython-39.pyc
ADDED
Binary file (2.76 kB). View file
|
|
SMPLX/__pycache__/read_joints_from_pose.cpython-39.pyc
ADDED
Binary file (4.24 kB). View file
|
|
SMPLX/__pycache__/rotation_conversions.cpython-310.pyc
ADDED
Binary file (16.8 kB). View file
|
|
SMPLX/__pycache__/rotation_conversions.cpython-311.pyc
ADDED
Binary file (24.7 kB). View file
|
|
SMPLX/__pycache__/rotation_conversions.cpython-39.pyc
ADDED
Binary file (16.8 kB). View file
|
|
SMPLX/__pycache__/transfer_smpls.cpython-39.pyc
ADDED
Binary file (4.13 kB). View file
|
|
SMPLX/__pycache__/visual_amass.cpython-39.pyc
ADDED
Binary file (5.6 kB). View file
|
|
SMPLX/__pycache__/visualize.cpython-38.pyc
ADDED
Binary file (3.31 kB). View file
|
|
SMPLX/config_files/smpl2smplh.yaml
ADDED
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
datasets:
|
2 |
+
mesh_folder:
|
3 |
+
data_folder: 'transfer_data/meshes/smpl'
|
4 |
+
deformation_transfer_path: 'transfer_data/smpl2smplh_def_transfer.pkl'
|
5 |
+
mask_ids_fname: ''
|
6 |
+
summary_steps: 100
|
7 |
+
|
8 |
+
edge_fitting:
|
9 |
+
per_part: False
|
10 |
+
|
11 |
+
optim:
|
12 |
+
type: 'trust-ncg'
|
13 |
+
maxiters: 100
|
14 |
+
gtol: 1e-06
|
15 |
+
|
16 |
+
body_model:
|
17 |
+
model_type: "smplh"
|
18 |
+
# SMPL+H has no neutral model, so we have to manually select the gender
|
19 |
+
gender: "female"
|
20 |
+
# gender: "male"
|
21 |
+
folder: "transfer_data/body_models"
|
22 |
+
use_compressed: False
|
23 |
+
smplh:
|
24 |
+
betas:
|
25 |
+
num: 10
|
SMPLX/config_files/smpl2smplx.yaml
ADDED
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
datasets:
|
2 |
+
mesh_folder:
|
3 |
+
data_folder: 'transfer_data/meshes/smpl'
|
4 |
+
deformation_transfer_path: 'transfer_data/smpl2smplx_deftrafo_setup.pkl'
|
5 |
+
mask_ids_fname: 'smplx_mask_ids.npy'
|
6 |
+
summary_steps: 100
|
7 |
+
|
8 |
+
edge_fitting:
|
9 |
+
per_part: False
|
10 |
+
|
11 |
+
optim:
|
12 |
+
type: 'trust-ncg'
|
13 |
+
maxiters: 100
|
14 |
+
gtol: 1e-06
|
15 |
+
|
16 |
+
body_model:
|
17 |
+
model_type: "smplx"
|
18 |
+
gender: "neutral"
|
19 |
+
folder: "transfer_data/body_models"
|
20 |
+
use_compressed: False
|
21 |
+
use_face_contour: True
|
22 |
+
smplx:
|
23 |
+
betas:
|
24 |
+
num: 10
|
25 |
+
expression:
|
26 |
+
num: 10
|
SMPLX/config_files/smplh2smpl.yaml
ADDED
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
datasets:
|
2 |
+
mesh_folder:
|
3 |
+
data_folder: 'transfer_data/meshes/smplh'
|
4 |
+
deformation_transfer_path: 'transfer_data/smplh2smpl_def_transfer.pkl'
|
5 |
+
mask_ids_fname: ''
|
6 |
+
summary_steps: 100
|
7 |
+
|
8 |
+
edge_fitting:
|
9 |
+
per_part: False
|
10 |
+
|
11 |
+
optim:
|
12 |
+
type: 'trust-ncg'
|
13 |
+
maxiters: 100
|
14 |
+
gtol: 1e-06
|
15 |
+
|
16 |
+
body_model:
|
17 |
+
model_type: "smpl"
|
18 |
+
gender: "neutral"
|
19 |
+
folder: "transfer_data/body_models"
|
20 |
+
use_compressed: False
|
21 |
+
use_face_contour: True
|
22 |
+
smpl:
|
23 |
+
betas:
|
24 |
+
num: 10
|
SMPLX/config_files/smplh2smplx.yaml
ADDED
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
datasets:
|
2 |
+
mesh_folder:
|
3 |
+
data_folder: 'transfer_data/meshes/smplh'
|
4 |
+
deformation_transfer_path: 'transfer_data/smplh2smplx_deftrafo_setup.pkl'
|
5 |
+
mask_ids_fname: 'smplx_mask_ids.npy'
|
6 |
+
summary_steps: 100
|
7 |
+
|
8 |
+
edge_fitting:
|
9 |
+
per_part: False
|
10 |
+
|
11 |
+
optim:
|
12 |
+
type: 'trust-ncg'
|
13 |
+
maxiters: 100
|
14 |
+
gtol: 1e-06
|
15 |
+
|
16 |
+
body_model:
|
17 |
+
model_type: "smplx"
|
18 |
+
gender: "neutral"
|
19 |
+
folder: "transfer_data/body_models"
|
20 |
+
use_compressed: False
|
21 |
+
use_face_contour: True
|
22 |
+
smplx:
|
23 |
+
betas:
|
24 |
+
num: 10
|
25 |
+
expression:
|
26 |
+
num: 10
|
SMPLX/config_files/smplh2smplx_as.yaml
ADDED
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
datasets:
|
2 |
+
mesh_folder:
|
3 |
+
data_folder: 'transfer_data/meshes/amass_sample'
|
4 |
+
deformation_transfer_path: 'transfer_data/smplh2smplx_deftrafo_setup.pkl'
|
5 |
+
mask_ids_fname: 'smplx_mask_ids.npy'
|
6 |
+
summary_steps: 100
|
7 |
+
|
8 |
+
edge_fitting:
|
9 |
+
per_part: False
|
10 |
+
|
11 |
+
optim:
|
12 |
+
type: 'trust-ncg'
|
13 |
+
maxiters: 100
|
14 |
+
gtol: 1e-06
|
15 |
+
|
16 |
+
body_model:
|
17 |
+
model_type: "smplx"
|
18 |
+
gender: "male"
|
19 |
+
folder: "/data/TTA/data/body_models"
|
20 |
+
use_compressed: False
|
21 |
+
use_face_contour: True
|
22 |
+
smplx:
|
23 |
+
betas:
|
24 |
+
num: 10
|
25 |
+
expression:
|
26 |
+
num: 10
|
SMPLX/config_files/smplh2smplx_onepose.yaml
ADDED
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
datasets:
|
2 |
+
mesh_folder:
|
3 |
+
data_folder: 'transfer_data/meshes/amass_onepose'
|
4 |
+
deformation_transfer_path: 'transfer_data/smplh2smplx_deftrafo_setup.pkl'
|
5 |
+
mask_ids_fname: 'smplx_mask_ids.npy'
|
6 |
+
summary_steps: 100
|
7 |
+
|
8 |
+
edge_fitting:
|
9 |
+
per_part: False
|
10 |
+
|
11 |
+
optim:
|
12 |
+
type: 'adam'
|
13 |
+
lr: 0.1
|
14 |
+
maxiters: 10000
|
15 |
+
gtol: 1e-06
|
16 |
+
|
17 |
+
body_model:
|
18 |
+
model_type: "smplx"
|
19 |
+
gender: "neutral"
|
20 |
+
folder: "models"
|
21 |
+
use_compressed: False
|
22 |
+
use_face_contour: True
|
23 |
+
smplx:
|
24 |
+
betas:
|
25 |
+
num: 10
|
26 |
+
expression:
|
27 |
+
num: 10
|
SMPLX/config_files/smplx2smpl.yaml
ADDED
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
datasets:
|
2 |
+
mesh_folder:
|
3 |
+
data_folder: 'meshes/smplx'
|
4 |
+
deformation_transfer_path: 'transfer_data/smplx2smpl_deftrafo_setup.pkl'
|
5 |
+
mask_ids_fname: ''
|
6 |
+
summary_steps: 100
|
7 |
+
|
8 |
+
edge_fitting:
|
9 |
+
per_part: False
|
10 |
+
|
11 |
+
optim:
|
12 |
+
type: 'lbfgs'
|
13 |
+
maxiters: 200
|
14 |
+
gtol: 1e-06
|
15 |
+
|
16 |
+
body_model:
|
17 |
+
model_type: "smpl"
|
18 |
+
gender: "neutral"
|
19 |
+
ext: 'pkl'
|
20 |
+
folder: "transfer_data/body_models"
|
21 |
+
use_compressed: False
|
22 |
+
use_face_contour: True
|
23 |
+
smpl:
|
24 |
+
betas:
|
25 |
+
num: 10
|
SMPLX/config_files/smplx2smplh.yaml
ADDED
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
datasets:
|
2 |
+
mesh_folder:
|
3 |
+
data_folder: 'meshes/smplx'
|
4 |
+
deformation_transfer_path: 'transfer_data/smplx2smplh_deftrafo_setup.pkl'
|
5 |
+
mask_ids_fname: ''
|
6 |
+
summary_steps: 100
|
7 |
+
|
8 |
+
edge_fitting:
|
9 |
+
per_part: False
|
10 |
+
|
11 |
+
optim:
|
12 |
+
type: 'lbfgs'
|
13 |
+
maxiters: 200
|
14 |
+
gtol: 1e-06
|
15 |
+
|
16 |
+
body_model:
|
17 |
+
model_type: "smplh"
|
18 |
+
# SMPL+H has no neutral model, so we have to manually select the gender
|
19 |
+
gender: "female"
|
20 |
+
# gender: "male"
|
21 |
+
ext: 'pkl'
|
22 |
+
folder: "transfer_data/body_models"
|
23 |
+
use_compressed: False
|
24 |
+
use_face_contour: True
|
25 |
+
smplh:
|
26 |
+
betas:
|
27 |
+
num: 10
|
SMPLX/joints2smpl.py
ADDED
@@ -0,0 +1,59 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import torch
|
2 |
+
from SMPLX.visualize_joint2smpl.simplify_loc2rot import joints2smpl
|
3 |
+
import argparse
|
4 |
+
import numpy as np
|
5 |
+
import os
|
6 |
+
from tqdm import tqdm
|
7 |
+
|
8 |
+
parser = argparse.ArgumentParser(description='transfer joint3d to smpls')
|
9 |
+
parser.add_argument("--model_path", default="/data/TTA/data/body_models")
|
10 |
+
parser.add_argument('--source_path', default="/data/TTA/data/humanact12/group_000")
|
11 |
+
parser.add_argument("--target_path", default="/data/TTA/data/humanact_smplh/group_000")
|
12 |
+
parser.add_argument("--mode", default="joints", choices=["t2m", "joints"])
|
13 |
+
args = parser.parse_args()
|
14 |
+
device = "cuda"
|
15 |
+
|
16 |
+
if os.path.isdir(args.source_path):
|
17 |
+
os.makedirs(args.target_path, exist_ok=True)
|
18 |
+
files = os.listdir(args.source_path)
|
19 |
+
target_files = files
|
20 |
+
else:
|
21 |
+
files = [args.source_path]
|
22 |
+
args.source_path = ""
|
23 |
+
|
24 |
+
if args.target_path.split(".")[-1] != "npy":
|
25 |
+
os.makedirs(args.target_path)
|
26 |
+
target_files = [files[0].split("/")[-1]]
|
27 |
+
else:
|
28 |
+
target_files = [args.target_path]
|
29 |
+
args.target_path = ""
|
30 |
+
|
31 |
+
for i in range(len(files)):
|
32 |
+
curr_path = os.path.join(args.source_path, files[i])
|
33 |
+
target_path = os.path.join(args.target_path, target_files[i])
|
34 |
+
if os.path.exists(target_path):
|
35 |
+
continue
|
36 |
+
|
37 |
+
curr_file = np.load(curr_path) #### [nframe, 263]
|
38 |
+
curr_file = torch.from_numpy(curr_file)
|
39 |
+
|
40 |
+
if args.mode == "t2m":
|
41 |
+
from dataset.t2m.recover_joints import recover_from_ric
|
42 |
+
motions = recover_from_ric(curr_file, 22) #### [nframes, 22, 3]
|
43 |
+
motions = motions.detach().cpu().numpy()
|
44 |
+
else:
|
45 |
+
motions = curr_file.detach().cpu().numpy()
|
46 |
+
|
47 |
+
frames, njoints, nfeats = motions.shape
|
48 |
+
MINS = motions.min(axis=0).min(axis=0)
|
49 |
+
MAXS = motions.max(axis=0).max(axis=0)
|
50 |
+
height_offset = MINS[1]
|
51 |
+
motions[:, :, 1] -= height_offset
|
52 |
+
model = joints2smpl(frames, 0, True, model_path=args.model_path)
|
53 |
+
target, trans = model.joint2smpl(motions)
|
54 |
+
|
55 |
+
target = np.concatenate([target, trans], axis=1)
|
56 |
+
|
57 |
+
np.save(target_path, target)
|
58 |
+
if i % 10 == 0:
|
59 |
+
print("save %d npys"%(i))
|
SMPLX/read_from_npy.py
ADDED
@@ -0,0 +1,108 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import numpy as np
|
2 |
+
import torch
|
3 |
+
|
4 |
+
def npy2info(motions, num_shapes=10):
|
5 |
+
if isinstance(motions, str):
|
6 |
+
motions = np.load(motions)
|
7 |
+
|
8 |
+
trans = None
|
9 |
+
gnum = 2
|
10 |
+
|
11 |
+
if isinstance(motions, np.ndarray):
|
12 |
+
betas = np.zeros([motions.shape[0], num_shapes]).astype(motions.dtype)
|
13 |
+
else:
|
14 |
+
betas = torch.zeros([motions.shape[0], num_shapes], dtype=motions.dtype)
|
15 |
+
|
16 |
+
if len(motions.shape) == 3:
|
17 |
+
motions = motions.reshape(motions.shape[0], -1)
|
18 |
+
|
19 |
+
if motions.shape[1] in [73, 157, 166]:
|
20 |
+
gnum = motions[:, -1:][0]
|
21 |
+
motions = motions[:, :-1]
|
22 |
+
elif motions.shape[1] in [75, 159, 168]:
|
23 |
+
gnum = 2
|
24 |
+
trans = motions[:, -3::]
|
25 |
+
motions = motions[:, :-3]
|
26 |
+
elif motions.shape[1] in [76, 160, 169]:
|
27 |
+
gnum = motions[:, -1:][0]
|
28 |
+
trans = motions[:, -4:-1:]
|
29 |
+
motions = motions[:, :-4]
|
30 |
+
elif motions.shape[1] in [72 + num_shapes, 156 + num_shapes, 165 + num_shapes]:
|
31 |
+
betas = motions[:, -num_shapes::]
|
32 |
+
gnum = 2
|
33 |
+
motions = motions[:, :-num_shapes]
|
34 |
+
elif motions.shape[1] in [73 + num_shapes, 157 + num_shapes, 166 + num_shapes]:
|
35 |
+
betas = motions[:, -num_shapes::]
|
36 |
+
gnum = motions[:, -num_shapes-1:-num_shapes:][0]
|
37 |
+
motions = motions[:, :-num_shapes-1]
|
38 |
+
elif motions.shape[1] in [75 + num_shapes, 159 + num_shapes, 168 + num_shapes]:
|
39 |
+
betas = motions[:, -num_shapes::]
|
40 |
+
gnum = 2
|
41 |
+
trans = motions[:, -num_shapes-3:-num_shapes:]
|
42 |
+
motions = motions[:, :-num_shapes-3]
|
43 |
+
elif motions.shape[1] in [76 + num_shapes, 160 + num_shapes, 169 + num_shapes]:
|
44 |
+
betas = motions[:, -num_shapes::]
|
45 |
+
gnum = motions[:, -num_shapes-1:-num_shapes:][0]
|
46 |
+
trans = motions[:, -num_shapes-4:-num_shapes-1:]
|
47 |
+
motions = motions[:, :-num_shapes-4]
|
48 |
+
|
49 |
+
if gnum == 0:
|
50 |
+
gender = "female"
|
51 |
+
elif gnum == 1:
|
52 |
+
gender = "male"
|
53 |
+
else:
|
54 |
+
gender = "neutral"
|
55 |
+
|
56 |
+
return motions, trans, gender, betas
|
57 |
+
|
58 |
+
def info2dict(pose, trans=None, betas=None, mode="smpl", device="cuda", index=-1):
|
59 |
+
if isinstance(pose, np.ndarray):
|
60 |
+
pose = torch.from_numpy(pose)
|
61 |
+
|
62 |
+
if trans is not None and isinstance(trans, np.ndarray):
|
63 |
+
trans = torch.from_numpy(trans)
|
64 |
+
|
65 |
+
if betas is not None and isinstance(betas, np.ndarray):
|
66 |
+
betas = torch.from_numpy(betas)
|
67 |
+
elif betas is None:
|
68 |
+
betas = torch.zeros([pose.shape[0], 10])
|
69 |
+
|
70 |
+
if index != -1:
|
71 |
+
pose = pose[index:index+1]
|
72 |
+
|
73 |
+
if trans is not None:
|
74 |
+
trans = trans[index:index+1]
|
75 |
+
|
76 |
+
betas = betas[index:index+1]
|
77 |
+
|
78 |
+
if mode == "smplx":
|
79 |
+
inputs = {
|
80 |
+
"global_orient": pose[:, :3].float().to(device),
|
81 |
+
"body_pose": pose[:, 3:66].float().to(device),
|
82 |
+
"jaw_pose": pose[:, 66:69].float().to(device),
|
83 |
+
"leye_pose": pose[:, 69:72].float().to(device),
|
84 |
+
"reye_pose": pose[:, 72:75].float().to(device),
|
85 |
+
"left_hand_pose":pose[:, 75:120].float().to(device),
|
86 |
+
"right_hand_pose":pose[:, 120:].float().to(device),
|
87 |
+
}
|
88 |
+
elif mode == "smplh":
|
89 |
+
inputs = {
|
90 |
+
"global_orient": pose[:, :3].float().to(device),
|
91 |
+
"body_pose": pose[:, 3:66].float().to(device),
|
92 |
+
"left_hand_pose":pose[:, 66:111].float().to(device),
|
93 |
+
"right_hand_pose":pose[:, 111:].float().to(device),
|
94 |
+
}
|
95 |
+
elif mode == "smpl":
|
96 |
+
inputs = {
|
97 |
+
"global_orient": pose[:, :3].float().to(device),
|
98 |
+
"body_pose": pose[:, 3:].float().to(device),
|
99 |
+
}
|
100 |
+
|
101 |
+
if trans is not None:
|
102 |
+
inputs["transl"] = trans[:, :].float().to(device)
|
103 |
+
else:
|
104 |
+
print("No Translation Information")
|
105 |
+
|
106 |
+
inputs["betas"] = betas[:, :].float().to(device)
|
107 |
+
|
108 |
+
return inputs
|
SMPLX/read_joints_from_pose.py
ADDED
@@ -0,0 +1,110 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import torch
|
2 |
+
import numpy as np
|
3 |
+
from torch import nn
|
4 |
+
import pickle as pkl
|
5 |
+
import torch.nn.functional as F
|
6 |
+
|
7 |
+
class Struct(object):
|
8 |
+
def __init__(self, **kwargs):
|
9 |
+
for key, val in kwargs.items():
|
10 |
+
setattr(self, key, val)
|
11 |
+
|
12 |
+
|
13 |
+
def to_np(array, dtype=np.float32):
|
14 |
+
if 'scipy.sparse' in str(type(array)):
|
15 |
+
array = array.todense()
|
16 |
+
return np.array(array, dtype=dtype)
|
17 |
+
|
18 |
+
|
19 |
+
class Get_Joints(nn.Module):
|
20 |
+
def __init__(self, path, batch_size=300) -> None:
|
21 |
+
super().__init__()
|
22 |
+
self.betas = nn.parameter.Parameter(torch.zeros([batch_size, 10], dtype=torch.float32), requires_grad=False)
|
23 |
+
with open(path, "rb") as f:
|
24 |
+
smpl_prior = pkl.load(f, encoding="latin1")
|
25 |
+
data_struct = Struct(**smpl_prior)
|
26 |
+
|
27 |
+
self.v_template = nn.parameter.Parameter(torch.from_numpy(to_np(data_struct.v_template)), requires_grad=False)
|
28 |
+
self.shapedirs = nn.parameter.Parameter(torch.from_numpy(to_np(data_struct.shapedirs)), requires_grad=False)
|
29 |
+
self.J_regressor = nn.parameter.Parameter(torch.from_numpy(to_np(data_struct.J_regressor)), requires_grad=False)
|
30 |
+
posedirs = torch.from_numpy(to_np(data_struct.posedirs))
|
31 |
+
num_pose_basis = posedirs.shape[-1]
|
32 |
+
posedirs = posedirs.reshape([-1, num_pose_basis]).permute(1, 0)
|
33 |
+
self.posedirs = nn.parameter.Parameter(posedirs, requires_grad=False)
|
34 |
+
self.parents = nn.parameter.Parameter(torch.from_numpy(to_np(data_struct.kintree_table)[0]).long(), requires_grad=False)
|
35 |
+
self.parents[0] = -1
|
36 |
+
|
37 |
+
self.ident = nn.parameter.Parameter(torch.eye(3), requires_grad=False)
|
38 |
+
self.K = nn.parameter.Parameter(torch.zeros([1, 3, 3]), requires_grad=False)
|
39 |
+
self.zeros = nn.parameter.Parameter(torch.zeros([1, 1]), requires_grad=False)
|
40 |
+
|
41 |
+
def blend_shapes(self, betas, shape_disps):
|
42 |
+
blend_shape = torch.einsum('bl,mkl->bmk', [betas, shape_disps])
|
43 |
+
return blend_shape
|
44 |
+
|
45 |
+
def vertices2joints(self, J_regressor, vertices):
|
46 |
+
return torch.einsum('bik,ji->bjk', [vertices, J_regressor])
|
47 |
+
|
48 |
+
def batch_rodrigues(
|
49 |
+
self,
|
50 |
+
rot_vecs,
|
51 |
+
epsilon = 1e-8,
|
52 |
+
):
|
53 |
+
batch_size = rot_vecs.shape[0]
|
54 |
+
angle = torch.norm(rot_vecs + epsilon, dim=1, keepdim=True)
|
55 |
+
rot_dir = rot_vecs / angle
|
56 |
+
cos = torch.unsqueeze(torch.cos(angle), dim=1)
|
57 |
+
sin = torch.unsqueeze(torch.sin(angle), dim=1)
|
58 |
+
# Bx1 arrays
|
59 |
+
rx, ry, rz = torch.split(rot_dir, 1, dim=1)
|
60 |
+
K = self.K.repeat(batch_size, 1, 1)
|
61 |
+
zeros = self.zeros.repeat(batch_size, 1)
|
62 |
+
K = torch.cat([zeros, -rz, ry, rz, zeros, -rx, -ry, rx, zeros], dim=1).view((batch_size, 3, 3))
|
63 |
+
ident = self.ident.unsqueeze(0)
|
64 |
+
rot_mat = ident + sin * K + (1 - cos) * torch.bmm(K, K)
|
65 |
+
return rot_mat
|
66 |
+
|
67 |
+
def transform_mat(self, R, t):
|
68 |
+
return torch.cat([F.pad(R, [0, 0, 0, 1]),
|
69 |
+
F.pad(t, [0, 0, 0, 1], value=1)], dim=2)
|
70 |
+
|
71 |
+
def batch_rigid_transform(
|
72 |
+
self,
|
73 |
+
rot_mats,
|
74 |
+
joints,
|
75 |
+
parents,
|
76 |
+
):
|
77 |
+
joints = torch.unsqueeze(joints, dim=-1)
|
78 |
+
|
79 |
+
rel_joints = joints.clone()
|
80 |
+
rel_joints[:, 1:] -= joints[:, parents[1:]]
|
81 |
+
|
82 |
+
transforms_mat = self.transform_mat(
|
83 |
+
rot_mats.reshape(-1, 3, 3),
|
84 |
+
rel_joints.reshape(-1, 3, 1)).reshape(-1, joints.shape[1], 4, 4)
|
85 |
+
|
86 |
+
transform_chain = [transforms_mat[:, 0]]
|
87 |
+
for i in range(1, parents.shape[0]):
|
88 |
+
# Subtract the joint location at the rest pose
|
89 |
+
# No need for rotation, since it's identity when at rest
|
90 |
+
curr_res = torch.matmul(transform_chain[parents[i]],
|
91 |
+
transforms_mat[:, i])
|
92 |
+
transform_chain.append(curr_res)
|
93 |
+
|
94 |
+
transforms = torch.stack(transform_chain, dim=1)
|
95 |
+
|
96 |
+
# The last column of the transformations contains the posed joints
|
97 |
+
posed_joints = transforms[:, :, :3, 3]
|
98 |
+
return posed_joints
|
99 |
+
|
100 |
+
def forward(self, pose, trans=None):
|
101 |
+
pose = pose.float()
|
102 |
+
batch = pose.shape[0]
|
103 |
+
betas = self.betas[:batch]
|
104 |
+
v_shaped = self.v_template + self.blend_shapes(betas, self.shapedirs)
|
105 |
+
J = self.vertices2joints(self.J_regressor, v_shaped)
|
106 |
+
rot_mats = self.batch_rodrigues(pose.view(-1, 3)).view([batch, -1, 3, 3])
|
107 |
+
J_transformed = self.batch_rigid_transform(rot_mats, J, self.parents)
|
108 |
+
if trans is not None:
|
109 |
+
J_transformed += trans.unsqueeze(dim=1)
|
110 |
+
return J_transformed
|
SMPLX/rotation_conversions.py
ADDED
@@ -0,0 +1,532 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
2 |
+
# Check PYTORCH3D_LICENCE before use
|
3 |
+
|
4 |
+
import functools
|
5 |
+
from typing import Optional
|
6 |
+
|
7 |
+
import torch
|
8 |
+
import torch.nn.functional as F
|
9 |
+
|
10 |
+
|
11 |
+
"""
|
12 |
+
The transformation matrices returned from the functions in this file assume
|
13 |
+
the points on which the transformation will be applied are column vectors.
|
14 |
+
i.e. the R matrix is structured as
|
15 |
+
R = [
|
16 |
+
[Rxx, Rxy, Rxz],
|
17 |
+
[Ryx, Ryy, Ryz],
|
18 |
+
[Rzx, Rzy, Rzz],
|
19 |
+
] # (3, 3)
|
20 |
+
This matrix can be applied to column vectors by post multiplication
|
21 |
+
by the points e.g.
|
22 |
+
points = [[0], [1], [2]] # (3 x 1) xyz coordinates of a point
|
23 |
+
transformed_points = R * points
|
24 |
+
To apply the same matrix to points which are row vectors, the R matrix
|
25 |
+
can be transposed and pre multiplied by the points:
|
26 |
+
e.g.
|
27 |
+
points = [[0, 1, 2]] # (1 x 3) xyz coordinates of a point
|
28 |
+
transformed_points = points * R.transpose(1, 0)
|
29 |
+
"""
|
30 |
+
|
31 |
+
|
32 |
+
def quaternion_to_matrix(quaternions):
|
33 |
+
"""
|
34 |
+
Convert rotations given as quaternions to rotation matrices.
|
35 |
+
Args:
|
36 |
+
quaternions: quaternions with real part first,
|
37 |
+
as tensor of shape (..., 4).
|
38 |
+
Returns:
|
39 |
+
Rotation matrices as tensor of shape (..., 3, 3).
|
40 |
+
"""
|
41 |
+
r, i, j, k = torch.unbind(quaternions, -1)
|
42 |
+
two_s = 2.0 / (quaternions * quaternions).sum(-1)
|
43 |
+
|
44 |
+
o = torch.stack(
|
45 |
+
(
|
46 |
+
1 - two_s * (j * j + k * k),
|
47 |
+
two_s * (i * j - k * r),
|
48 |
+
two_s * (i * k + j * r),
|
49 |
+
two_s * (i * j + k * r),
|
50 |
+
1 - two_s * (i * i + k * k),
|
51 |
+
two_s * (j * k - i * r),
|
52 |
+
two_s * (i * k - j * r),
|
53 |
+
two_s * (j * k + i * r),
|
54 |
+
1 - two_s * (i * i + j * j),
|
55 |
+
),
|
56 |
+
-1,
|
57 |
+
)
|
58 |
+
return o.reshape(quaternions.shape[:-1] + (3, 3))
|
59 |
+
|
60 |
+
|
61 |
+
def _copysign(a, b):
|
62 |
+
"""
|
63 |
+
Return a tensor where each element has the absolute value taken from the,
|
64 |
+
corresponding element of a, with sign taken from the corresponding
|
65 |
+
element of b. This is like the standard copysign floating-point operation,
|
66 |
+
but is not careful about negative 0 and NaN.
|
67 |
+
Args:
|
68 |
+
a: source tensor.
|
69 |
+
b: tensor whose signs will be used, of the same shape as a.
|
70 |
+
Returns:
|
71 |
+
Tensor of the same shape as a with the signs of b.
|
72 |
+
"""
|
73 |
+
signs_differ = (a < 0) != (b < 0)
|
74 |
+
return torch.where(signs_differ, -a, a)
|
75 |
+
|
76 |
+
|
77 |
+
def _sqrt_positive_part(x):
|
78 |
+
"""
|
79 |
+
Returns torch.sqrt(torch.max(0, x))
|
80 |
+
but with a zero subgradient where x is 0.
|
81 |
+
"""
|
82 |
+
ret = torch.zeros_like(x)
|
83 |
+
positive_mask = x > 0
|
84 |
+
ret[positive_mask] = torch.sqrt(x[positive_mask])
|
85 |
+
return ret
|
86 |
+
|
87 |
+
|
88 |
+
def matrix_to_quaternion(matrix):
|
89 |
+
"""
|
90 |
+
Convert rotations given as rotation matrices to quaternions.
|
91 |
+
Args:
|
92 |
+
matrix: Rotation matrices as tensor of shape (..., 3, 3).
|
93 |
+
Returns:
|
94 |
+
quaternions with real part first, as tensor of shape (..., 4).
|
95 |
+
"""
|
96 |
+
if matrix.size(-1) != 3 or matrix.size(-2) != 3:
|
97 |
+
raise ValueError(f"Invalid rotation matrix shape f{matrix.shape}.")
|
98 |
+
m00 = matrix[..., 0, 0]
|
99 |
+
m11 = matrix[..., 1, 1]
|
100 |
+
m22 = matrix[..., 2, 2]
|
101 |
+
o0 = 0.5 * _sqrt_positive_part(1 + m00 + m11 + m22)
|
102 |
+
x = 0.5 * _sqrt_positive_part(1 + m00 - m11 - m22)
|
103 |
+
y = 0.5 * _sqrt_positive_part(1 - m00 + m11 - m22)
|
104 |
+
z = 0.5 * _sqrt_positive_part(1 - m00 - m11 + m22)
|
105 |
+
o1 = _copysign(x, matrix[..., 2, 1] - matrix[..., 1, 2])
|
106 |
+
o2 = _copysign(y, matrix[..., 0, 2] - matrix[..., 2, 0])
|
107 |
+
o3 = _copysign(z, matrix[..., 1, 0] - matrix[..., 0, 1])
|
108 |
+
return torch.stack((o0, o1, o2, o3), -1)
|
109 |
+
|
110 |
+
|
111 |
+
def _axis_angle_rotation(axis: str, angle):
|
112 |
+
"""
|
113 |
+
Return the rotation matrices for one of the rotations about an axis
|
114 |
+
of which Euler angles describe, for each value of the angle given.
|
115 |
+
Args:
|
116 |
+
axis: Axis label "X" or "Y or "Z".
|
117 |
+
angle: any shape tensor of Euler angles in radians
|
118 |
+
Returns:
|
119 |
+
Rotation matrices as tensor of shape (..., 3, 3).
|
120 |
+
"""
|
121 |
+
|
122 |
+
cos = torch.cos(angle)
|
123 |
+
sin = torch.sin(angle)
|
124 |
+
one = torch.ones_like(angle)
|
125 |
+
zero = torch.zeros_like(angle)
|
126 |
+
|
127 |
+
if axis == "X":
|
128 |
+
R_flat = (one, zero, zero, zero, cos, -sin, zero, sin, cos)
|
129 |
+
if axis == "Y":
|
130 |
+
R_flat = (cos, zero, sin, zero, one, zero, -sin, zero, cos)
|
131 |
+
if axis == "Z":
|
132 |
+
R_flat = (cos, -sin, zero, sin, cos, zero, zero, zero, one)
|
133 |
+
|
134 |
+
return torch.stack(R_flat, -1).reshape(angle.shape + (3, 3))
|
135 |
+
|
136 |
+
|
137 |
+
def euler_angles_to_matrix(euler_angles, convention: str):
|
138 |
+
"""
|
139 |
+
Convert rotations given as Euler angles in radians to rotation matrices.
|
140 |
+
Args:
|
141 |
+
euler_angles: Euler angles in radians as tensor of shape (..., 3).
|
142 |
+
convention: Convention string of three uppercase letters from
|
143 |
+
{"X", "Y", and "Z"}.
|
144 |
+
Returns:
|
145 |
+
Rotation matrices as tensor of shape (..., 3, 3).
|
146 |
+
"""
|
147 |
+
if euler_angles.dim() == 0 or euler_angles.shape[-1] != 3:
|
148 |
+
raise ValueError("Invalid input euler angles.")
|
149 |
+
if len(convention) != 3:
|
150 |
+
raise ValueError("Convention must have 3 letters.")
|
151 |
+
if convention[1] in (convention[0], convention[2]):
|
152 |
+
raise ValueError(f"Invalid convention {convention}.")
|
153 |
+
for letter in convention:
|
154 |
+
if letter not in ("X", "Y", "Z"):
|
155 |
+
raise ValueError(f"Invalid letter {letter} in convention string.")
|
156 |
+
matrices = map(_axis_angle_rotation, convention, torch.unbind(euler_angles, -1))
|
157 |
+
return functools.reduce(torch.matmul, matrices)
|
158 |
+
|
159 |
+
|
160 |
+
def _angle_from_tan(
|
161 |
+
axis: str, other_axis: str, data, horizontal: bool, tait_bryan: bool
|
162 |
+
):
|
163 |
+
"""
|
164 |
+
Extract the first or third Euler angle from the two members of
|
165 |
+
the matrix which are positive constant times its sine and cosine.
|
166 |
+
Args:
|
167 |
+
axis: Axis label "X" or "Y or "Z" for the angle we are finding.
|
168 |
+
other_axis: Axis label "X" or "Y or "Z" for the middle axis in the
|
169 |
+
convention.
|
170 |
+
data: Rotation matrices as tensor of shape (..., 3, 3).
|
171 |
+
horizontal: Whether we are looking for the angle for the third axis,
|
172 |
+
which means the relevant entries are in the same row of the
|
173 |
+
rotation matrix. If not, they are in the same column.
|
174 |
+
tait_bryan: Whether the first and third axes in the convention differ.
|
175 |
+
Returns:
|
176 |
+
Euler Angles in radians for each matrix in data as a tensor
|
177 |
+
of shape (...).
|
178 |
+
"""
|
179 |
+
|
180 |
+
i1, i2 = {"X": (2, 1), "Y": (0, 2), "Z": (1, 0)}[axis]
|
181 |
+
if horizontal:
|
182 |
+
i2, i1 = i1, i2
|
183 |
+
even = (axis + other_axis) in ["XY", "YZ", "ZX"]
|
184 |
+
if horizontal == even:
|
185 |
+
return torch.atan2(data[..., i1], data[..., i2])
|
186 |
+
if tait_bryan:
|
187 |
+
return torch.atan2(-data[..., i2], data[..., i1])
|
188 |
+
return torch.atan2(data[..., i2], -data[..., i1])
|
189 |
+
|
190 |
+
|
191 |
+
def _index_from_letter(letter: str):
|
192 |
+
if letter == "X":
|
193 |
+
return 0
|
194 |
+
if letter == "Y":
|
195 |
+
return 1
|
196 |
+
if letter == "Z":
|
197 |
+
return 2
|
198 |
+
|
199 |
+
|
200 |
+
def matrix_to_euler_angles(matrix, convention: str):
|
201 |
+
"""
|
202 |
+
Convert rotations given as rotation matrices to Euler angles in radians.
|
203 |
+
Args:
|
204 |
+
matrix: Rotation matrices as tensor of shape (..., 3, 3).
|
205 |
+
convention: Convention string of three uppercase letters.
|
206 |
+
Returns:
|
207 |
+
Euler angles in radians as tensor of shape (..., 3).
|
208 |
+
"""
|
209 |
+
if len(convention) != 3:
|
210 |
+
raise ValueError("Convention must have 3 letters.")
|
211 |
+
if convention[1] in (convention[0], convention[2]):
|
212 |
+
raise ValueError(f"Invalid convention {convention}.")
|
213 |
+
for letter in convention:
|
214 |
+
if letter not in ("X", "Y", "Z"):
|
215 |
+
raise ValueError(f"Invalid letter {letter} in convention string.")
|
216 |
+
if matrix.size(-1) != 3 or matrix.size(-2) != 3:
|
217 |
+
raise ValueError(f"Invalid rotation matrix shape f{matrix.shape}.")
|
218 |
+
i0 = _index_from_letter(convention[0])
|
219 |
+
i2 = _index_from_letter(convention[2])
|
220 |
+
tait_bryan = i0 != i2
|
221 |
+
if tait_bryan:
|
222 |
+
central_angle = torch.asin(
|
223 |
+
matrix[..., i0, i2] * (-1.0 if i0 - i2 in [-1, 2] else 1.0)
|
224 |
+
)
|
225 |
+
else:
|
226 |
+
central_angle = torch.acos(matrix[..., i0, i0])
|
227 |
+
|
228 |
+
o = (
|
229 |
+
_angle_from_tan(
|
230 |
+
convention[0], convention[1], matrix[..., i2], False, tait_bryan
|
231 |
+
),
|
232 |
+
central_angle,
|
233 |
+
_angle_from_tan(
|
234 |
+
convention[2], convention[1], matrix[..., i0, :], True, tait_bryan
|
235 |
+
),
|
236 |
+
)
|
237 |
+
return torch.stack(o, -1)
|
238 |
+
|
239 |
+
|
240 |
+
def random_quaternions(
|
241 |
+
n: int, dtype: Optional[torch.dtype] = None, device=None, requires_grad=False
|
242 |
+
):
|
243 |
+
"""
|
244 |
+
Generate random quaternions representing rotations,
|
245 |
+
i.e. versors with nonnegative real part.
|
246 |
+
Args:
|
247 |
+
n: Number of quaternions in a batch to return.
|
248 |
+
dtype: Type to return.
|
249 |
+
device: Desired device of returned tensor. Default:
|
250 |
+
uses the current device for the default tensor type.
|
251 |
+
requires_grad: Whether the resulting tensor should have the gradient
|
252 |
+
flag set.
|
253 |
+
Returns:
|
254 |
+
Quaternions as tensor of shape (N, 4).
|
255 |
+
"""
|
256 |
+
o = torch.randn((n, 4), dtype=dtype, device=device, requires_grad=requires_grad)
|
257 |
+
s = (o * o).sum(1)
|
258 |
+
o = o / _copysign(torch.sqrt(s), o[:, 0])[:, None]
|
259 |
+
return o
|
260 |
+
|
261 |
+
|
262 |
+
def random_rotations(
|
263 |
+
n: int, dtype: Optional[torch.dtype] = None, device=None, requires_grad=False
|
264 |
+
):
|
265 |
+
"""
|
266 |
+
Generate random rotations as 3x3 rotation matrices.
|
267 |
+
Args:
|
268 |
+
n: Number of rotation matrices in a batch to return.
|
269 |
+
dtype: Type to return.
|
270 |
+
device: Device of returned tensor. Default: if None,
|
271 |
+
uses the current device for the default tensor type.
|
272 |
+
requires_grad: Whether the resulting tensor should have the gradient
|
273 |
+
flag set.
|
274 |
+
Returns:
|
275 |
+
Rotation matrices as tensor of shape (n, 3, 3).
|
276 |
+
"""
|
277 |
+
quaternions = random_quaternions(
|
278 |
+
n, dtype=dtype, device=device, requires_grad=requires_grad
|
279 |
+
)
|
280 |
+
return quaternion_to_matrix(quaternions)
|
281 |
+
|
282 |
+
|
283 |
+
def random_rotation(
|
284 |
+
dtype: Optional[torch.dtype] = None, device=None, requires_grad=False
|
285 |
+
):
|
286 |
+
"""
|
287 |
+
Generate a single random 3x3 rotation matrix.
|
288 |
+
Args:
|
289 |
+
dtype: Type to return
|
290 |
+
device: Device of returned tensor. Default: if None,
|
291 |
+
uses the current device for the default tensor type
|
292 |
+
requires_grad: Whether the resulting tensor should have the gradient
|
293 |
+
flag set
|
294 |
+
Returns:
|
295 |
+
Rotation matrix as tensor of shape (3, 3).
|
296 |
+
"""
|
297 |
+
return random_rotations(1, dtype, device, requires_grad)[0]
|
298 |
+
|
299 |
+
|
300 |
+
def standardize_quaternion(quaternions):
|
301 |
+
"""
|
302 |
+
Convert a unit quaternion to a standard form: one in which the real
|
303 |
+
part is non negative.
|
304 |
+
Args:
|
305 |
+
quaternions: Quaternions with real part first,
|
306 |
+
as tensor of shape (..., 4).
|
307 |
+
Returns:
|
308 |
+
Standardized quaternions as tensor of shape (..., 4).
|
309 |
+
"""
|
310 |
+
return torch.where(quaternions[..., 0:1] < 0, -quaternions, quaternions)
|
311 |
+
|
312 |
+
|
313 |
+
def quaternion_raw_multiply(a, b):
|
314 |
+
"""
|
315 |
+
Multiply two quaternions.
|
316 |
+
Usual torch rules for broadcasting apply.
|
317 |
+
Args:
|
318 |
+
a: Quaternions as tensor of shape (..., 4), real part first.
|
319 |
+
b: Quaternions as tensor of shape (..., 4), real part first.
|
320 |
+
Returns:
|
321 |
+
The product of a and b, a tensor of quaternions shape (..., 4).
|
322 |
+
"""
|
323 |
+
aw, ax, ay, az = torch.unbind(a, -1)
|
324 |
+
bw, bx, by, bz = torch.unbind(b, -1)
|
325 |
+
ow = aw * bw - ax * bx - ay * by - az * bz
|
326 |
+
ox = aw * bx + ax * bw + ay * bz - az * by
|
327 |
+
oy = aw * by - ax * bz + ay * bw + az * bx
|
328 |
+
oz = aw * bz + ax * by - ay * bx + az * bw
|
329 |
+
return torch.stack((ow, ox, oy, oz), -1)
|
330 |
+
|
331 |
+
|
332 |
+
def quaternion_multiply(a, b):
|
333 |
+
"""
|
334 |
+
Multiply two quaternions representing rotations, returning the quaternion
|
335 |
+
representing their composition, i.e. the versor with nonnegative real part.
|
336 |
+
Usual torch rules for broadcasting apply.
|
337 |
+
Args:
|
338 |
+
a: Quaternions as tensor of shape (..., 4), real part first.
|
339 |
+
b: Quaternions as tensor of shape (..., 4), real part first.
|
340 |
+
Returns:
|
341 |
+
The product of a and b, a tensor of quaternions of shape (..., 4).
|
342 |
+
"""
|
343 |
+
ab = quaternion_raw_multiply(a, b)
|
344 |
+
return standardize_quaternion(ab)
|
345 |
+
|
346 |
+
|
347 |
+
def quaternion_invert(quaternion):
|
348 |
+
"""
|
349 |
+
Given a quaternion representing rotation, get the quaternion representing
|
350 |
+
its inverse.
|
351 |
+
Args:
|
352 |
+
quaternion: Quaternions as tensor of shape (..., 4), with real part
|
353 |
+
first, which must be versors (unit quaternions).
|
354 |
+
Returns:
|
355 |
+
The inverse, a tensor of quaternions of shape (..., 4).
|
356 |
+
"""
|
357 |
+
|
358 |
+
return quaternion * quaternion.new_tensor([1, -1, -1, -1])
|
359 |
+
|
360 |
+
|
361 |
+
def quaternion_apply(quaternion, point):
|
362 |
+
"""
|
363 |
+
Apply the rotation given by a quaternion to a 3D point.
|
364 |
+
Usual torch rules for broadcasting apply.
|
365 |
+
Args:
|
366 |
+
quaternion: Tensor of quaternions, real part first, of shape (..., 4).
|
367 |
+
point: Tensor of 3D points of shape (..., 3).
|
368 |
+
Returns:
|
369 |
+
Tensor of rotated points of shape (..., 3).
|
370 |
+
"""
|
371 |
+
if point.size(-1) != 3:
|
372 |
+
raise ValueError(f"Points are not in 3D, f{point.shape}.")
|
373 |
+
real_parts = point.new_zeros(point.shape[:-1] + (1,))
|
374 |
+
point_as_quaternion = torch.cat((real_parts, point), -1)
|
375 |
+
out = quaternion_raw_multiply(
|
376 |
+
quaternion_raw_multiply(quaternion, point_as_quaternion),
|
377 |
+
quaternion_invert(quaternion),
|
378 |
+
)
|
379 |
+
return out[..., 1:]
|
380 |
+
|
381 |
+
|
382 |
+
def axis_angle_to_matrix(axis_angle):
|
383 |
+
"""
|
384 |
+
Convert rotations given as axis/angle to rotation matrices.
|
385 |
+
Args:
|
386 |
+
axis_angle: Rotations given as a vector in axis angle form,
|
387 |
+
as a tensor of shape (..., 3), where the magnitude is
|
388 |
+
the angle turned anticlockwise in radians around the
|
389 |
+
vector's direction.
|
390 |
+
Returns:
|
391 |
+
Rotation matrices as tensor of shape (..., 3, 3).
|
392 |
+
"""
|
393 |
+
return quaternion_to_matrix(axis_angle_to_quaternion(axis_angle))
|
394 |
+
|
395 |
+
|
396 |
+
def matrix_to_axis_angle(matrix):
|
397 |
+
"""
|
398 |
+
Convert rotations given as rotation matrices to axis/angle.
|
399 |
+
Args:
|
400 |
+
matrix: Rotation matrices as tensor of shape (..., 3, 3).
|
401 |
+
Returns:
|
402 |
+
Rotations given as a vector in axis angle form, as a tensor
|
403 |
+
of shape (..., 3), where the magnitude is the angle
|
404 |
+
turned anticlockwise in radians around the vector's
|
405 |
+
direction.
|
406 |
+
"""
|
407 |
+
return quaternion_to_axis_angle(matrix_to_quaternion(matrix))
|
408 |
+
|
409 |
+
|
410 |
+
def axis_angle_to_quaternion(axis_angle):
|
411 |
+
"""
|
412 |
+
Convert rotations given as axis/angle to quaternions.
|
413 |
+
Args:
|
414 |
+
axis_angle: Rotations given as a vector in axis angle form,
|
415 |
+
as a tensor of shape (..., 3), where the magnitude is
|
416 |
+
the angle turned anticlockwise in radians around the
|
417 |
+
vector's direction.
|
418 |
+
Returns:
|
419 |
+
quaternions with real part first, as tensor of shape (..., 4).
|
420 |
+
"""
|
421 |
+
angles = torch.norm(axis_angle, p=2, dim=-1, keepdim=True)
|
422 |
+
half_angles = 0.5 * angles
|
423 |
+
eps = 1e-6
|
424 |
+
small_angles = angles.abs() < eps
|
425 |
+
sin_half_angles_over_angles = torch.empty_like(angles)
|
426 |
+
sin_half_angles_over_angles[~small_angles] = (
|
427 |
+
torch.sin(half_angles[~small_angles]) / angles[~small_angles]
|
428 |
+
)
|
429 |
+
# for x small, sin(x/2) is about x/2 - (x/2)^3/6
|
430 |
+
# so sin(x/2)/x is about 1/2 - (x*x)/48
|
431 |
+
sin_half_angles_over_angles[small_angles] = (
|
432 |
+
0.5 - (angles[small_angles] * angles[small_angles]) / 48
|
433 |
+
)
|
434 |
+
quaternions = torch.cat(
|
435 |
+
[torch.cos(half_angles), axis_angle * sin_half_angles_over_angles], dim=-1
|
436 |
+
)
|
437 |
+
return quaternions
|
438 |
+
|
439 |
+
|
440 |
+
def quaternion_to_axis_angle(quaternions):
|
441 |
+
"""
|
442 |
+
Convert rotations given as quaternions to axis/angle.
|
443 |
+
Args:
|
444 |
+
quaternions: quaternions with real part first,
|
445 |
+
as tensor of shape (..., 4).
|
446 |
+
Returns:
|
447 |
+
Rotations given as a vector in axis angle form, as a tensor
|
448 |
+
of shape (..., 3), where the magnitude is the angle
|
449 |
+
turned anticlockwise in radians around the vector's
|
450 |
+
direction.
|
451 |
+
"""
|
452 |
+
norms = torch.norm(quaternions[..., 1:], p=2, dim=-1, keepdim=True)
|
453 |
+
half_angles = torch.atan2(norms, quaternions[..., :1])
|
454 |
+
angles = 2 * half_angles
|
455 |
+
eps = 1e-6
|
456 |
+
small_angles = angles.abs() < eps
|
457 |
+
sin_half_angles_over_angles = torch.empty_like(angles)
|
458 |
+
sin_half_angles_over_angles[~small_angles] = (
|
459 |
+
torch.sin(half_angles[~small_angles]) / angles[~small_angles]
|
460 |
+
)
|
461 |
+
# for x small, sin(x/2) is about x/2 - (x/2)^3/6
|
462 |
+
# so sin(x/2)/x is about 1/2 - (x*x)/48
|
463 |
+
sin_half_angles_over_angles[small_angles] = (
|
464 |
+
0.5 - (angles[small_angles] * angles[small_angles]) / 48
|
465 |
+
)
|
466 |
+
return quaternions[..., 1:] / sin_half_angles_over_angles
|
467 |
+
|
468 |
+
|
469 |
+
def rotation_6d_to_matrix(d6: torch.Tensor) -> torch.Tensor:
|
470 |
+
"""
|
471 |
+
Converts 6D rotation representation by Zhou et al. [1] to rotation matrix
|
472 |
+
using Gram--Schmidt orthogonalisation per Section B of [1].
|
473 |
+
Args:
|
474 |
+
d6: 6D rotation representation, of size (*, 6)
|
475 |
+
Returns:
|
476 |
+
batch of rotation matrices of size (*, 3, 3)
|
477 |
+
[1] Zhou, Y., Barnes, C., Lu, J., Yang, J., & Li, H.
|
478 |
+
On the Continuity of Rotation Representations in Neural Networks.
|
479 |
+
IEEE Conference on Computer Vision and Pattern Recognition, 2019.
|
480 |
+
Retrieved from http://arxiv.org/abs/1812.07035
|
481 |
+
"""
|
482 |
+
|
483 |
+
a1, a2 = d6[..., :3], d6[..., 3:]
|
484 |
+
b1 = F.normalize(a1, dim=-1)
|
485 |
+
b2 = a2 - (b1 * a2).sum(-1, keepdim=True) * b1
|
486 |
+
b2 = F.normalize(b2, dim=-1)
|
487 |
+
b3 = torch.cross(b1, b2, dim=-1)
|
488 |
+
return torch.stack((b1, b2, b3), dim=-2)
|
489 |
+
|
490 |
+
|
491 |
+
def matrix_to_rotation_6d(matrix: torch.Tensor) -> torch.Tensor:
|
492 |
+
"""
|
493 |
+
Converts rotation matrices to 6D rotation representation by Zhou et al. [1]
|
494 |
+
by dropping the last row. Note that 6D representation is not unique.
|
495 |
+
Args:
|
496 |
+
matrix: batch of rotation matrices of size (*, 3, 3)
|
497 |
+
Returns:
|
498 |
+
6D rotation representation, of size (*, 6)
|
499 |
+
[1] Zhou, Y., Barnes, C., Lu, J., Yang, J., & Li, H.
|
500 |
+
On the Continuity of Rotation Representations in Neural Networks.
|
501 |
+
IEEE Conference on Computer Vision and Pattern Recognition, 2019.
|
502 |
+
Retrieved from http://arxiv.org/abs/1812.07035
|
503 |
+
"""
|
504 |
+
return matrix[..., :2, :].clone().reshape(*matrix.size()[:-2], 6)
|
505 |
+
|
506 |
+
def canonicalize_smplh(poses, trans = None):
|
507 |
+
bs, nframes, njoints = poses.shape[:3]
|
508 |
+
|
509 |
+
global_orient = poses[:, :, 0]
|
510 |
+
|
511 |
+
# first global rotations
|
512 |
+
rot2d = matrix_to_axis_angle(global_orient[:, 0])
|
513 |
+
#rot2d[:, :2] = 0 # Remove the rotation along the vertical axis
|
514 |
+
rot2d = axis_angle_to_matrix(rot2d)
|
515 |
+
|
516 |
+
# Rotate the global rotation to eliminate Z rotations
|
517 |
+
global_orient = torch.einsum("ikj,imkl->imjl", rot2d, global_orient)
|
518 |
+
|
519 |
+
# Construct canonicalized version of x
|
520 |
+
xc = torch.cat((global_orient[:, :, None], poses[:, :, 1:]), dim=2)
|
521 |
+
|
522 |
+
if trans is not None:
|
523 |
+
vel = trans[:, 1:] - trans[:, :-1]
|
524 |
+
# Turn the translation as well
|
525 |
+
vel = torch.einsum("ikj,ilk->ilj", rot2d, vel)
|
526 |
+
trans = torch.cat((torch.zeros(bs, 1, 3, device=vel.device),
|
527 |
+
torch.cumsum(vel, 1)), 1)
|
528 |
+
return xc, trans
|
529 |
+
else:
|
530 |
+
return xc
|
531 |
+
|
532 |
+
|
SMPLX/smplx/__init__.py
ADDED
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# -*- coding: utf-8 -*-
|
2 |
+
|
3 |
+
# Max-Planck-Gesellschaft zur Förderung der Wissenschaften e.V. (MPG) is
|
4 |
+
# holder of all proprietary rights on this computer program.
|
5 |
+
# You can only use this computer program if you have closed
|
6 |
+
# a license agreement with MPG or you get the right to use the computer
|
7 |
+
# program from someone who is authorized to grant you that right.
|
8 |
+
# Any use of the computer program without a valid license is prohibited and
|
9 |
+
# liable to prosecution.
|
10 |
+
#
|
11 |
+
# Copyright©2019 Max-Planck-Gesellschaft zur Förderung
|
12 |
+
# der Wissenschaften e.V. (MPG). acting on behalf of its Max Planck Institute
|
13 |
+
# for Intelligent Systems. All rights reserved.
|
14 |
+
#
|
15 |
+
# Contact: ps-license@tuebingen.mpg.de
|
16 |
+
|
17 |
+
from .body_models import (
|
18 |
+
create,
|
19 |
+
SMPL,
|
20 |
+
SMPLH,
|
21 |
+
SMPLX,
|
22 |
+
MANO,
|
23 |
+
FLAME,
|
24 |
+
build_layer,
|
25 |
+
SMPLLayer,
|
26 |
+
SMPLHLayer,
|
27 |
+
SMPLXLayer,
|
28 |
+
MANOLayer,
|
29 |
+
FLAMELayer,
|
30 |
+
)
|
SMPLX/smplx/__pycache__/__init__.cpython-310.pyc
ADDED
Binary file (375 Bytes). View file
|
|
SMPLX/smplx/__pycache__/__init__.cpython-311.pyc
ADDED
Binary file (531 Bytes). View file
|
|
SMPLX/smplx/__pycache__/__init__.cpython-39.pyc
ADDED
Binary file (373 Bytes). View file
|
|
SMPLX/smplx/__pycache__/body_models.cpython-310.pyc
ADDED
Binary file (62.7 kB). View file
|
|
SMPLX/smplx/__pycache__/body_models.cpython-311.pyc
ADDED
Binary file (106 kB). View file
|
|
SMPLX/smplx/__pycache__/body_models.cpython-39.pyc
ADDED
Binary file (62.1 kB). View file
|
|
SMPLX/smplx/__pycache__/joint_names.cpython-39.pyc
ADDED
Binary file (4.2 kB). View file
|
|
SMPLX/smplx/__pycache__/lbs.cpython-310.pyc
ADDED
Binary file (11.2 kB). View file
|
|
SMPLX/smplx/__pycache__/lbs.cpython-311.pyc
ADDED
Binary file (17.2 kB). View file
|
|
SMPLX/smplx/__pycache__/lbs.cpython-39.pyc
ADDED
Binary file (11.2 kB). View file
|
|
SMPLX/smplx/__pycache__/utils.cpython-310.pyc
ADDED
Binary file (4.4 kB). View file
|
|
SMPLX/smplx/__pycache__/utils.cpython-311.pyc
ADDED
Binary file (7.36 kB). View file
|
|
SMPLX/smplx/__pycache__/utils.cpython-39.pyc
ADDED
Binary file (4.51 kB). View file
|
|
SMPLX/smplx/__pycache__/vertex_ids.cpython-310.pyc
ADDED
Binary file (1.15 kB). View file
|
|
SMPLX/smplx/__pycache__/vertex_ids.cpython-311.pyc
ADDED
Binary file (1.55 kB). View file
|
|
SMPLX/smplx/__pycache__/vertex_ids.cpython-39.pyc
ADDED
Binary file (934 Bytes). View file
|
|
SMPLX/smplx/__pycache__/vertex_joint_selector.cpython-310.pyc
ADDED
Binary file (1.68 kB). View file
|
|
SMPLX/smplx/__pycache__/vertex_joint_selector.cpython-311.pyc
ADDED
Binary file (3 kB). View file
|
|
SMPLX/smplx/__pycache__/vertex_joint_selector.cpython-39.pyc
ADDED
Binary file (1.67 kB). View file
|
|
SMPLX/smplx/body_models.py
ADDED
The diff for this file is too large to render.
See raw diff
|
|
SMPLX/smplx/joint_names.py
ADDED
@@ -0,0 +1,320 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# -*- coding: utf-8 -*-
|
2 |
+
|
3 |
+
# Max-Planck-Gesellschaft zur Förderung der Wissenschaften e.V. (MPG) is
|
4 |
+
# holder of all proprietary rights on this computer program.
|
5 |
+
# You can only use this computer program if you have closed
|
6 |
+
# a license agreement with MPG or you get the right to use the computer
|
7 |
+
# program from someone who is authorized to grant you that right.
|
8 |
+
# Any use of the computer program without a valid license is prohibited and
|
9 |
+
# liable to prosecution.
|
10 |
+
#
|
11 |
+
# Copyright©2019 Max-Planck-Gesellschaft zur Förderung
|
12 |
+
# der Wissenschaften e.V. (MPG). acting on behalf of its Max Planck Institute
|
13 |
+
# for Intelligent Systems. All rights reserved.
|
14 |
+
#
|
15 |
+
# Contact: ps-license@tuebingen.mpg.de
|
16 |
+
|
17 |
+
import numpy as np
|
18 |
+
|
19 |
+
JOINT_NAMES = [
|
20 |
+
"pelvis",
|
21 |
+
"left_hip",
|
22 |
+
"right_hip",
|
23 |
+
"spine1",
|
24 |
+
"left_knee",
|
25 |
+
"right_knee",
|
26 |
+
"spine2",
|
27 |
+
"left_ankle",
|
28 |
+
"right_ankle",
|
29 |
+
"spine3",
|
30 |
+
"left_foot",
|
31 |
+
"right_foot",
|
32 |
+
"neck",
|
33 |
+
"left_collar",
|
34 |
+
"right_collar",
|
35 |
+
"head",
|
36 |
+
"left_shoulder",
|
37 |
+
"right_shoulder",
|
38 |
+
"left_elbow",
|
39 |
+
"right_elbow",
|
40 |
+
"left_wrist",
|
41 |
+
"right_wrist",
|
42 |
+
"jaw",
|
43 |
+
"left_eye_smplhf",
|
44 |
+
"right_eye_smplhf",
|
45 |
+
"left_index1",
|
46 |
+
"left_index2",
|
47 |
+
"left_index3",
|
48 |
+
"left_middle1",
|
49 |
+
"left_middle2",
|
50 |
+
"left_middle3",
|
51 |
+
"left_pinky1",
|
52 |
+
"left_pinky2",
|
53 |
+
"left_pinky3",
|
54 |
+
"left_ring1",
|
55 |
+
"left_ring2",
|
56 |
+
"left_ring3",
|
57 |
+
"left_thumb1",
|
58 |
+
"left_thumb2",
|
59 |
+
"left_thumb3",
|
60 |
+
"right_index1",
|
61 |
+
"right_index2",
|
62 |
+
"right_index3",
|
63 |
+
"right_middle1",
|
64 |
+
"right_middle2",
|
65 |
+
"right_middle3",
|
66 |
+
"right_pinky1",
|
67 |
+
"right_pinky2",
|
68 |
+
"right_pinky3",
|
69 |
+
"right_ring1",
|
70 |
+
"right_ring2",
|
71 |
+
"right_ring3",
|
72 |
+
"right_thumb1",
|
73 |
+
"right_thumb2",
|
74 |
+
"right_thumb3",
|
75 |
+
"nose",
|
76 |
+
"right_eye",
|
77 |
+
"left_eye",
|
78 |
+
"right_ear",
|
79 |
+
"left_ear",
|
80 |
+
"left_big_toe",
|
81 |
+
"left_small_toe",
|
82 |
+
"left_heel",
|
83 |
+
"right_big_toe",
|
84 |
+
"right_small_toe",
|
85 |
+
"right_heel",
|
86 |
+
"left_thumb",
|
87 |
+
"left_index",
|
88 |
+
"left_middle",
|
89 |
+
"left_ring",
|
90 |
+
"left_pinky",
|
91 |
+
"right_thumb",
|
92 |
+
"right_index",
|
93 |
+
"right_middle",
|
94 |
+
"right_ring",
|
95 |
+
"right_pinky",
|
96 |
+
"right_eye_brow1",
|
97 |
+
"right_eye_brow2",
|
98 |
+
"right_eye_brow3",
|
99 |
+
"right_eye_brow4",
|
100 |
+
"right_eye_brow5",
|
101 |
+
"left_eye_brow5",
|
102 |
+
"left_eye_brow4",
|
103 |
+
"left_eye_brow3",
|
104 |
+
"left_eye_brow2",
|
105 |
+
"left_eye_brow1",
|
106 |
+
"nose1",
|
107 |
+
"nose2",
|
108 |
+
"nose3",
|
109 |
+
"nose4",
|
110 |
+
"right_nose_2",
|
111 |
+
"right_nose_1",
|
112 |
+
"nose_middle",
|
113 |
+
"left_nose_1",
|
114 |
+
"left_nose_2",
|
115 |
+
"right_eye1",
|
116 |
+
"right_eye2",
|
117 |
+
"right_eye3",
|
118 |
+
"right_eye4",
|
119 |
+
"right_eye5",
|
120 |
+
"right_eye6",
|
121 |
+
"left_eye4",
|
122 |
+
"left_eye3",
|
123 |
+
"left_eye2",
|
124 |
+
"left_eye1",
|
125 |
+
"left_eye6",
|
126 |
+
"left_eye5",
|
127 |
+
"right_mouth_1",
|
128 |
+
"right_mouth_2",
|
129 |
+
"right_mouth_3",
|
130 |
+
"mouth_top",
|
131 |
+
"left_mouth_3",
|
132 |
+
"left_mouth_2",
|
133 |
+
"left_mouth_1",
|
134 |
+
"left_mouth_5", # 59 in OpenPose output
|
135 |
+
"left_mouth_4", # 58 in OpenPose output
|
136 |
+
"mouth_bottom",
|
137 |
+
"right_mouth_4",
|
138 |
+
"right_mouth_5",
|
139 |
+
"right_lip_1",
|
140 |
+
"right_lip_2",
|
141 |
+
"lip_top",
|
142 |
+
"left_lip_2",
|
143 |
+
"left_lip_1",
|
144 |
+
"left_lip_3",
|
145 |
+
"lip_bottom",
|
146 |
+
"right_lip_3",
|
147 |
+
# Face contour
|
148 |
+
"right_contour_1",
|
149 |
+
"right_contour_2",
|
150 |
+
"right_contour_3",
|
151 |
+
"right_contour_4",
|
152 |
+
"right_contour_5",
|
153 |
+
"right_contour_6",
|
154 |
+
"right_contour_7",
|
155 |
+
"right_contour_8",
|
156 |
+
"contour_middle",
|
157 |
+
"left_contour_8",
|
158 |
+
"left_contour_7",
|
159 |
+
"left_contour_6",
|
160 |
+
"left_contour_5",
|
161 |
+
"left_contour_4",
|
162 |
+
"left_contour_3",
|
163 |
+
"left_contour_2",
|
164 |
+
"left_contour_1",
|
165 |
+
]
|
166 |
+
|
167 |
+
|
168 |
+
SMPLH_JOINT_NAMES = [
|
169 |
+
"pelvis",
|
170 |
+
"left_hip",
|
171 |
+
"right_hip",
|
172 |
+
"spine1",
|
173 |
+
"left_knee",
|
174 |
+
"right_knee",
|
175 |
+
"spine2",
|
176 |
+
"left_ankle",
|
177 |
+
"right_ankle",
|
178 |
+
"spine3",
|
179 |
+
"left_foot",
|
180 |
+
"right_foot",
|
181 |
+
"neck",
|
182 |
+
"left_collar",
|
183 |
+
"right_collar",
|
184 |
+
"head",
|
185 |
+
"left_shoulder",
|
186 |
+
"right_shoulder",
|
187 |
+
"left_elbow",
|
188 |
+
"right_elbow",
|
189 |
+
"left_wrist",
|
190 |
+
"right_wrist",
|
191 |
+
"left_index1",
|
192 |
+
"left_index2",
|
193 |
+
"left_index3",
|
194 |
+
"left_middle1",
|
195 |
+
"left_middle2",
|
196 |
+
"left_middle3",
|
197 |
+
"left_pinky1",
|
198 |
+
"left_pinky2",
|
199 |
+
"left_pinky3",
|
200 |
+
"left_ring1",
|
201 |
+
"left_ring2",
|
202 |
+
"left_ring3",
|
203 |
+
"left_thumb1",
|
204 |
+
"left_thumb2",
|
205 |
+
"left_thumb3",
|
206 |
+
"right_index1",
|
207 |
+
"right_index2",
|
208 |
+
"right_index3",
|
209 |
+
"right_middle1",
|
210 |
+
"right_middle2",
|
211 |
+
"right_middle3",
|
212 |
+
"right_pinky1",
|
213 |
+
"right_pinky2",
|
214 |
+
"right_pinky3",
|
215 |
+
"right_ring1",
|
216 |
+
"right_ring2",
|
217 |
+
"right_ring3",
|
218 |
+
"right_thumb1",
|
219 |
+
"right_thumb2",
|
220 |
+
"right_thumb3",
|
221 |
+
"nose",
|
222 |
+
"right_eye",
|
223 |
+
"left_eye",
|
224 |
+
"right_ear",
|
225 |
+
"left_ear",
|
226 |
+
"left_big_toe",
|
227 |
+
"left_small_toe",
|
228 |
+
"left_heel",
|
229 |
+
"right_big_toe",
|
230 |
+
"right_small_toe",
|
231 |
+
"right_heel",
|
232 |
+
"left_thumb",
|
233 |
+
"left_index",
|
234 |
+
"left_middle",
|
235 |
+
"left_ring",
|
236 |
+
"left_pinky",
|
237 |
+
"right_thumb",
|
238 |
+
"right_index",
|
239 |
+
"right_middle",
|
240 |
+
"right_ring",
|
241 |
+
"right_pinky",
|
242 |
+
]
|
243 |
+
|
244 |
+
SMPL_JOINT_NAMES = [
|
245 |
+
"pelvis",
|
246 |
+
"left_hip",
|
247 |
+
"right_hip",
|
248 |
+
"spine1",
|
249 |
+
"left_knee",
|
250 |
+
"right_knee",
|
251 |
+
"spine2",
|
252 |
+
"left_ankle",
|
253 |
+
"right_ankle",
|
254 |
+
"spine3",
|
255 |
+
"left_foot",
|
256 |
+
"right_foot",
|
257 |
+
"neck",
|
258 |
+
"left_collar",
|
259 |
+
"right_collar",
|
260 |
+
"head",
|
261 |
+
"left_shoulder",
|
262 |
+
"right_shoulder",
|
263 |
+
"left_elbow",
|
264 |
+
"right_elbow",
|
265 |
+
"left_wrist",
|
266 |
+
"right_wrist",
|
267 |
+
"left_hand",
|
268 |
+
"right_hand",
|
269 |
+
]
|
270 |
+
|
271 |
+
|
272 |
+
class Body:
|
273 |
+
"""
|
274 |
+
Class for storing a single body pose.
|
275 |
+
"""
|
276 |
+
|
277 |
+
def __init__(self, joints, joint_names):
|
278 |
+
assert joints.ndim > 1
|
279 |
+
assert joints.shape[0] == len(joint_names)
|
280 |
+
self.joints = {}
|
281 |
+
for i, j in enumerate(joint_names):
|
282 |
+
self.joints[j] = joints[i]
|
283 |
+
|
284 |
+
@staticmethod
|
285 |
+
def from_smpl(joints):
|
286 |
+
"""
|
287 |
+
Create a Body object from SMPL joints.
|
288 |
+
"""
|
289 |
+
return Body(joints, SMPL_JOINT_NAMES)
|
290 |
+
|
291 |
+
@staticmethod
|
292 |
+
def from_smplh(joints):
|
293 |
+
"""
|
294 |
+
Create a Body object from SMPLH joints.
|
295 |
+
"""
|
296 |
+
return Body(joints, SMPLH_JOINT_NAMES)
|
297 |
+
|
298 |
+
def _as(self, joint_names):
|
299 |
+
"""
|
300 |
+
Return a Body object with the specified joint names.
|
301 |
+
"""
|
302 |
+
joint_list = []
|
303 |
+
for j in joint_names:
|
304 |
+
if j not in self.joints:
|
305 |
+
joint_list.append(np.zeros_like(self.joints["spine1"]))
|
306 |
+
else:
|
307 |
+
joint_list.append(self.joints[j])
|
308 |
+
return np.stack(joint_list, axis=0)
|
309 |
+
|
310 |
+
def as_smpl(self):
|
311 |
+
"""
|
312 |
+
Convert the body to SMPL joints.
|
313 |
+
"""
|
314 |
+
return self._as(SMPL_JOINT_NAMES)
|
315 |
+
|
316 |
+
def as_smplh(self):
|
317 |
+
"""
|
318 |
+
Convert the body to SMPLH joints.
|
319 |
+
"""
|
320 |
+
return self._as(SMPLH_JOINT_NAMES)
|
SMPLX/smplx/lbs.py
ADDED
@@ -0,0 +1,405 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# -*- coding: utf-8 -*-
|
2 |
+
|
3 |
+
# Max-Planck-Gesellschaft zur Förderung der Wissenschaften e.V. (MPG) is
|
4 |
+
# holder of all proprietary rights on this computer program.
|
5 |
+
# You can only use this computer program if you have closed
|
6 |
+
# a license agreement with MPG or you get the right to use the computer
|
7 |
+
# program from someone who is authorized to grant you that right.
|
8 |
+
# Any use of the computer program without a valid license is prohibited and
|
9 |
+
# liable to prosecution.
|
10 |
+
#
|
11 |
+
# Copyright©2019 Max-Planck-Gesellschaft zur Förderung
|
12 |
+
# der Wissenschaften e.V. (MPG). acting on behalf of its Max Planck Institute
|
13 |
+
# for Intelligent Systems. All rights reserved.
|
14 |
+
#
|
15 |
+
# Contact: ps-license@tuebingen.mpg.de
|
16 |
+
|
17 |
+
from __future__ import absolute_import
|
18 |
+
from __future__ import print_function
|
19 |
+
from __future__ import division
|
20 |
+
|
21 |
+
from typing import Tuple, List
|
22 |
+
import numpy as np
|
23 |
+
|
24 |
+
import torch
|
25 |
+
import torch.nn.functional as F
|
26 |
+
|
27 |
+
from .utils import rot_mat_to_euler, Tensor
|
28 |
+
|
29 |
+
|
30 |
+
def find_dynamic_lmk_idx_and_bcoords(
|
31 |
+
vertices: Tensor,
|
32 |
+
pose: Tensor,
|
33 |
+
dynamic_lmk_faces_idx: Tensor,
|
34 |
+
dynamic_lmk_b_coords: Tensor,
|
35 |
+
neck_kin_chain: List[int],
|
36 |
+
pose2rot: bool = True,
|
37 |
+
) -> Tuple[Tensor, Tensor]:
|
38 |
+
''' Compute the faces, barycentric coordinates for the dynamic landmarks
|
39 |
+
|
40 |
+
|
41 |
+
To do so, we first compute the rotation of the neck around the y-axis
|
42 |
+
and then use a pre-computed look-up table to find the faces and the
|
43 |
+
barycentric coordinates that will be used.
|
44 |
+
|
45 |
+
Special thanks to Soubhik Sanyal (soubhik.sanyal@tuebingen.mpg.de)
|
46 |
+
for providing the original TensorFlow implementation and for the LUT.
|
47 |
+
|
48 |
+
Parameters
|
49 |
+
----------
|
50 |
+
vertices: torch.tensor BxVx3, dtype = torch.float32
|
51 |
+
The tensor of input vertices
|
52 |
+
pose: torch.tensor Bx(Jx3), dtype = torch.float32
|
53 |
+
The current pose of the body model
|
54 |
+
dynamic_lmk_faces_idx: torch.tensor L, dtype = torch.long
|
55 |
+
The look-up table from neck rotation to faces
|
56 |
+
dynamic_lmk_b_coords: torch.tensor Lx3, dtype = torch.float32
|
57 |
+
The look-up table from neck rotation to barycentric coordinates
|
58 |
+
neck_kin_chain: list
|
59 |
+
A python list that contains the indices of the joints that form the
|
60 |
+
kinematic chain of the neck.
|
61 |
+
dtype: torch.dtype, optional
|
62 |
+
|
63 |
+
Returns
|
64 |
+
-------
|
65 |
+
dyn_lmk_faces_idx: torch.tensor, dtype = torch.long
|
66 |
+
A tensor of size BxL that contains the indices of the faces that
|
67 |
+
will be used to compute the current dynamic landmarks.
|
68 |
+
dyn_lmk_b_coords: torch.tensor, dtype = torch.float32
|
69 |
+
A tensor of size BxL that contains the indices of the faces that
|
70 |
+
will be used to compute the current dynamic landmarks.
|
71 |
+
'''
|
72 |
+
|
73 |
+
dtype = vertices.dtype
|
74 |
+
batch_size = vertices.shape[0]
|
75 |
+
|
76 |
+
if pose2rot:
|
77 |
+
aa_pose = torch.index_select(pose.view(batch_size, -1, 3), 1,
|
78 |
+
neck_kin_chain)
|
79 |
+
rot_mats = batch_rodrigues(
|
80 |
+
aa_pose.view(-1, 3)).view(batch_size, -1, 3, 3)
|
81 |
+
else:
|
82 |
+
rot_mats = torch.index_select(
|
83 |
+
pose.view(batch_size, -1, 3, 3), 1, neck_kin_chain)
|
84 |
+
|
85 |
+
rel_rot_mat = torch.eye(
|
86 |
+
3, device=vertices.device, dtype=dtype).unsqueeze_(dim=0).repeat(
|
87 |
+
batch_size, 1, 1)
|
88 |
+
for idx in range(len(neck_kin_chain)):
|
89 |
+
rel_rot_mat = torch.bmm(rot_mats[:, idx], rel_rot_mat)
|
90 |
+
|
91 |
+
y_rot_angle = torch.round(
|
92 |
+
torch.clamp(-rot_mat_to_euler(rel_rot_mat) * 180.0 / np.pi,
|
93 |
+
max=39)).to(dtype=torch.long)
|
94 |
+
neg_mask = y_rot_angle.lt(0).to(dtype=torch.long)
|
95 |
+
mask = y_rot_angle.lt(-39).to(dtype=torch.long)
|
96 |
+
neg_vals = mask * 78 + (1 - mask) * (39 - y_rot_angle)
|
97 |
+
y_rot_angle = (neg_mask * neg_vals +
|
98 |
+
(1 - neg_mask) * y_rot_angle)
|
99 |
+
|
100 |
+
dyn_lmk_faces_idx = torch.index_select(dynamic_lmk_faces_idx,
|
101 |
+
0, y_rot_angle)
|
102 |
+
dyn_lmk_b_coords = torch.index_select(dynamic_lmk_b_coords,
|
103 |
+
0, y_rot_angle)
|
104 |
+
|
105 |
+
return dyn_lmk_faces_idx, dyn_lmk_b_coords
|
106 |
+
|
107 |
+
|
108 |
+
def vertices2landmarks(
|
109 |
+
vertices: Tensor,
|
110 |
+
faces: Tensor,
|
111 |
+
lmk_faces_idx: Tensor,
|
112 |
+
lmk_bary_coords: Tensor
|
113 |
+
) -> Tensor:
|
114 |
+
''' Calculates landmarks by barycentric interpolation
|
115 |
+
|
116 |
+
Parameters
|
117 |
+
----------
|
118 |
+
vertices: torch.tensor BxVx3, dtype = torch.float32
|
119 |
+
The tensor of input vertices
|
120 |
+
faces: torch.tensor Fx3, dtype = torch.long
|
121 |
+
The faces of the mesh
|
122 |
+
lmk_faces_idx: torch.tensor L, dtype = torch.long
|
123 |
+
The tensor with the indices of the faces used to calculate the
|
124 |
+
landmarks.
|
125 |
+
lmk_bary_coords: torch.tensor Lx3, dtype = torch.float32
|
126 |
+
The tensor of barycentric coordinates that are used to interpolate
|
127 |
+
the landmarks
|
128 |
+
|
129 |
+
Returns
|
130 |
+
-------
|
131 |
+
landmarks: torch.tensor BxLx3, dtype = torch.float32
|
132 |
+
The coordinates of the landmarks for each mesh in the batch
|
133 |
+
'''
|
134 |
+
# Extract the indices of the vertices for each face
|
135 |
+
# BxLx3
|
136 |
+
batch_size, num_verts = vertices.shape[:2]
|
137 |
+
device = vertices.device
|
138 |
+
|
139 |
+
lmk_faces = torch.index_select(faces, 0, lmk_faces_idx.view(-1).to(torch.long)).view(
|
140 |
+
batch_size, -1, 3)
|
141 |
+
#The '.to(torch.long)'.
|
142 |
+
# added to make the trace work in c++,
|
143 |
+
# otherwise you get a runtime error in c++:
|
144 |
+
# 'index_select(): Expected dtype int32 or int64 for index'
|
145 |
+
|
146 |
+
lmk_faces += torch.arange(
|
147 |
+
batch_size, dtype=torch.long, device=device).view(-1, 1, 1) * num_verts
|
148 |
+
|
149 |
+
lmk_vertices = vertices.view(-1, 3)[lmk_faces].view(
|
150 |
+
batch_size, -1, 3, 3)
|
151 |
+
|
152 |
+
landmarks = torch.einsum('blfi,blf->bli', [lmk_vertices, lmk_bary_coords])
|
153 |
+
return landmarks
|
154 |
+
|
155 |
+
|
156 |
+
def lbs(
|
157 |
+
betas: Tensor,
|
158 |
+
pose: Tensor,
|
159 |
+
v_template: Tensor,
|
160 |
+
shapedirs: Tensor,
|
161 |
+
posedirs: Tensor,
|
162 |
+
J_regressor: Tensor,
|
163 |
+
parents: Tensor,
|
164 |
+
lbs_weights: Tensor,
|
165 |
+
pose2rot: bool = True,
|
166 |
+
) -> Tuple[Tensor, Tensor]:
|
167 |
+
''' Performs Linear Blend Skinning with the given shape and pose parameters
|
168 |
+
|
169 |
+
Parameters
|
170 |
+
----------
|
171 |
+
betas : torch.tensor BxNB
|
172 |
+
The tensor of shape parameters
|
173 |
+
pose : torch.tensor Bx(J + 1) * 3
|
174 |
+
The pose parameters in axis-angle format
|
175 |
+
v_template torch.tensor BxVx3
|
176 |
+
The template mesh that will be deformed
|
177 |
+
shapedirs : torch.tensor 1xNB
|
178 |
+
The tensor of PCA shape displacements
|
179 |
+
posedirs : torch.tensor Px(V * 3)
|
180 |
+
The pose PCA coefficients
|
181 |
+
J_regressor : torch.tensor JxV
|
182 |
+
The regressor array that is used to calculate the joints from
|
183 |
+
the position of the vertices
|
184 |
+
parents: torch.tensor J
|
185 |
+
The array that describes the kinematic tree for the model
|
186 |
+
lbs_weights: torch.tensor N x V x (J + 1)
|
187 |
+
The linear blend skinning weights that represent how much the
|
188 |
+
rotation matrix of each part affects each vertex
|
189 |
+
pose2rot: bool, optional
|
190 |
+
Flag on whether to convert the input pose tensor to rotation
|
191 |
+
matrices. The default value is True. If False, then the pose tensor
|
192 |
+
should already contain rotation matrices and have a size of
|
193 |
+
Bx(J + 1)x9
|
194 |
+
dtype: torch.dtype, optional
|
195 |
+
|
196 |
+
Returns
|
197 |
+
-------
|
198 |
+
verts: torch.tensor BxVx3
|
199 |
+
The vertices of the mesh after applying the shape and pose
|
200 |
+
displacements.
|
201 |
+
joints: torch.tensor BxJx3
|
202 |
+
The joints of the model
|
203 |
+
'''
|
204 |
+
|
205 |
+
batch_size = max(betas.shape[0], pose.shape[0])
|
206 |
+
device, dtype = betas.device, betas.dtype
|
207 |
+
|
208 |
+
# Add shape contribution
|
209 |
+
v_shaped = v_template + blend_shapes(betas, shapedirs)
|
210 |
+
|
211 |
+
# Get the joints
|
212 |
+
# NxJx3 array
|
213 |
+
J = vertices2joints(J_regressor, v_shaped)
|
214 |
+
|
215 |
+
# 3. Add pose blend shapes
|
216 |
+
# N x J x 3 x 3
|
217 |
+
ident = torch.eye(3, dtype=dtype, device=device)
|
218 |
+
if pose2rot:
|
219 |
+
rot_mats = batch_rodrigues(pose.view(-1, 3)).view(
|
220 |
+
[batch_size, -1, 3, 3])
|
221 |
+
|
222 |
+
pose_feature = (rot_mats[:, 1:, :, :] - ident).view([batch_size, -1])
|
223 |
+
# (N x P) x (P, V * 3) -> N x V x 3
|
224 |
+
pose_offsets = torch.matmul(
|
225 |
+
pose_feature, posedirs).view(batch_size, -1, 3)
|
226 |
+
else:
|
227 |
+
pose_feature = pose[:, 1:].view(batch_size, -1, 3, 3) - ident
|
228 |
+
rot_mats = pose.view(batch_size, -1, 3, 3)
|
229 |
+
|
230 |
+
pose_offsets = torch.matmul(pose_feature.view(batch_size, -1),
|
231 |
+
posedirs).view(batch_size, -1, 3)
|
232 |
+
|
233 |
+
v_posed = pose_offsets + v_shaped
|
234 |
+
# 4. Get the global joint location
|
235 |
+
J_transformed, A = batch_rigid_transform(rot_mats, J, parents, dtype=dtype)
|
236 |
+
|
237 |
+
# 5. Do skinning:
|
238 |
+
# W is N x V x (J + 1)
|
239 |
+
W = lbs_weights.unsqueeze(dim=0).expand([batch_size, -1, -1])
|
240 |
+
# (N x V x (J + 1)) x (N x (J + 1) x 16)
|
241 |
+
num_joints = J_regressor.shape[0]
|
242 |
+
T = torch.matmul(W, A.view(batch_size, num_joints, 16)) \
|
243 |
+
.view(batch_size, -1, 4, 4)
|
244 |
+
|
245 |
+
homogen_coord = torch.ones([batch_size, v_posed.shape[1], 1],
|
246 |
+
dtype=dtype, device=device)
|
247 |
+
v_posed_homo = torch.cat([v_posed, homogen_coord], dim=2)
|
248 |
+
v_homo = torch.matmul(T, torch.unsqueeze(v_posed_homo, dim=-1))
|
249 |
+
|
250 |
+
verts = v_homo[:, :, :3, 0]
|
251 |
+
|
252 |
+
return verts, J_transformed
|
253 |
+
|
254 |
+
|
255 |
+
def vertices2joints(J_regressor: Tensor, vertices: Tensor) -> Tensor:
|
256 |
+
''' Calculates the 3D joint locations from the vertices
|
257 |
+
|
258 |
+
Parameters
|
259 |
+
----------
|
260 |
+
J_regressor : torch.tensor JxV
|
261 |
+
The regressor array that is used to calculate the joints from the
|
262 |
+
position of the vertices
|
263 |
+
vertices : torch.tensor BxVx3
|
264 |
+
The tensor of mesh vertices
|
265 |
+
|
266 |
+
Returns
|
267 |
+
-------
|
268 |
+
torch.tensor BxJx3
|
269 |
+
The location of the joints
|
270 |
+
'''
|
271 |
+
|
272 |
+
return torch.einsum('bik,ji->bjk', [vertices, J_regressor])
|
273 |
+
|
274 |
+
|
275 |
+
def blend_shapes(betas: Tensor, shape_disps: Tensor) -> Tensor:
|
276 |
+
''' Calculates the per vertex displacement due to the blend shapes
|
277 |
+
|
278 |
+
|
279 |
+
Parameters
|
280 |
+
----------
|
281 |
+
betas : torch.tensor Bx(num_betas)
|
282 |
+
Blend shape coefficients
|
283 |
+
shape_disps: torch.tensor Vx3x(num_betas)
|
284 |
+
Blend shapes
|
285 |
+
|
286 |
+
Returns
|
287 |
+
-------
|
288 |
+
torch.tensor BxVx3
|
289 |
+
The per-vertex displacement due to shape deformation
|
290 |
+
'''
|
291 |
+
|
292 |
+
# Displacement[b, m, k] = sum_{l} betas[b, l] * shape_disps[m, k, l]
|
293 |
+
# i.e. Multiply each shape displacement by its corresponding beta and
|
294 |
+
# then sum them.
|
295 |
+
blend_shape = torch.einsum('bl,mkl->bmk', [betas, shape_disps])
|
296 |
+
return blend_shape
|
297 |
+
|
298 |
+
|
299 |
+
def batch_rodrigues(
|
300 |
+
rot_vecs: Tensor,
|
301 |
+
epsilon: float = 1e-8,
|
302 |
+
) -> Tensor:
|
303 |
+
''' Calculates the rotation matrices for a batch of rotation vectors
|
304 |
+
Parameters
|
305 |
+
----------
|
306 |
+
rot_vecs: torch.tensor Nx3
|
307 |
+
array of N axis-angle vectors
|
308 |
+
Returns
|
309 |
+
-------
|
310 |
+
R: torch.tensor Nx3x3
|
311 |
+
The rotation matrices for the given axis-angle parameters
|
312 |
+
'''
|
313 |
+
|
314 |
+
batch_size = rot_vecs.shape[0]
|
315 |
+
device, dtype = rot_vecs.device, rot_vecs.dtype
|
316 |
+
|
317 |
+
angle = torch.norm(rot_vecs + 1e-8, dim=1, keepdim=True)
|
318 |
+
rot_dir = rot_vecs / angle
|
319 |
+
|
320 |
+
cos = torch.unsqueeze(torch.cos(angle), dim=1)
|
321 |
+
sin = torch.unsqueeze(torch.sin(angle), dim=1)
|
322 |
+
|
323 |
+
# Bx1 arrays
|
324 |
+
rx, ry, rz = torch.split(rot_dir, 1, dim=1)
|
325 |
+
K = torch.zeros((batch_size, 3, 3), dtype=dtype, device=device)
|
326 |
+
|
327 |
+
zeros = torch.zeros((batch_size, 1), dtype=dtype, device=device)
|
328 |
+
K = torch.cat([zeros, -rz, ry, rz, zeros, -rx, -ry, rx, zeros], dim=1) \
|
329 |
+
.view((batch_size, 3, 3))
|
330 |
+
|
331 |
+
ident = torch.eye(3, dtype=dtype, device=device).unsqueeze(dim=0)
|
332 |
+
rot_mat = ident + sin * K + (1 - cos) * torch.bmm(K, K)
|
333 |
+
return rot_mat
|
334 |
+
|
335 |
+
|
336 |
+
def transform_mat(R: Tensor, t: Tensor) -> Tensor:
|
337 |
+
''' Creates a batch of transformation matrices
|
338 |
+
Args:
|
339 |
+
- R: Bx3x3 array of a batch of rotation matrices
|
340 |
+
- t: Bx3x1 array of a batch of translation vectors
|
341 |
+
Returns:
|
342 |
+
- T: Bx4x4 Transformation matrix
|
343 |
+
'''
|
344 |
+
# No padding left or right, only add an extra row
|
345 |
+
return torch.cat([F.pad(R, [0, 0, 0, 1]),
|
346 |
+
F.pad(t, [0, 0, 0, 1], value=1)], dim=2)
|
347 |
+
|
348 |
+
|
349 |
+
def batch_rigid_transform(
|
350 |
+
rot_mats: Tensor,
|
351 |
+
joints: Tensor,
|
352 |
+
parents: Tensor,
|
353 |
+
dtype=torch.float32
|
354 |
+
) -> Tensor:
|
355 |
+
"""
|
356 |
+
Applies a batch of rigid transformations to the joints
|
357 |
+
|
358 |
+
Parameters
|
359 |
+
----------
|
360 |
+
rot_mats : torch.tensor BxNx3x3
|
361 |
+
Tensor of rotation matrices
|
362 |
+
joints : torch.tensor BxNx3
|
363 |
+
Locations of joints
|
364 |
+
parents : torch.tensor BxN
|
365 |
+
The kinematic tree of each object
|
366 |
+
dtype : torch.dtype, optional:
|
367 |
+
The data type of the created tensors, the default is torch.float32
|
368 |
+
|
369 |
+
Returns
|
370 |
+
-------
|
371 |
+
posed_joints : torch.tensor BxNx3
|
372 |
+
The locations of the joints after applying the pose rotations
|
373 |
+
rel_transforms : torch.tensor BxNx4x4
|
374 |
+
The relative (with respect to the root joint) rigid transformations
|
375 |
+
for all the joints
|
376 |
+
"""
|
377 |
+
|
378 |
+
joints = torch.unsqueeze(joints, dim=-1)
|
379 |
+
|
380 |
+
rel_joints = joints.clone()
|
381 |
+
rel_joints[:, 1:] -= joints[:, parents[1:]]
|
382 |
+
|
383 |
+
transforms_mat = transform_mat(
|
384 |
+
rot_mats.reshape(-1, 3, 3),
|
385 |
+
rel_joints.reshape(-1, 3, 1)).reshape(-1, joints.shape[1], 4, 4)
|
386 |
+
|
387 |
+
transform_chain = [transforms_mat[:, 0]]
|
388 |
+
for i in range(1, parents.shape[0]):
|
389 |
+
# Subtract the joint location at the rest pose
|
390 |
+
# No need for rotation, since it's identity when at rest
|
391 |
+
curr_res = torch.matmul(transform_chain[parents[i]],
|
392 |
+
transforms_mat[:, i])
|
393 |
+
transform_chain.append(curr_res)
|
394 |
+
|
395 |
+
transforms = torch.stack(transform_chain, dim=1)
|
396 |
+
|
397 |
+
# The last column of the transformations contains the posed joints
|
398 |
+
posed_joints = transforms[:, :, :3, 3]
|
399 |
+
|
400 |
+
joints_homogen = F.pad(joints, [0, 0, 0, 1])
|
401 |
+
|
402 |
+
rel_transforms = transforms - F.pad(
|
403 |
+
torch.matmul(transforms, joints_homogen), [3, 0, 0, 0, 0, 0, 0, 0])
|
404 |
+
|
405 |
+
return posed_joints, rel_transforms
|