IsshikiHugh commited on
Commit
5ac1897
·
1 Parent(s): 19d6f8b

feat: CPU demo

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .gitignore +17 -0
  2. Dockerfile +37 -0
  3. LICENSE +21 -0
  4. README.md +4 -5
  5. configs/.gitignore +0 -0
  6. configs/README.md +31 -0
  7. configs/_hub_/README.md +7 -0
  8. configs/_hub_/datasets.yaml +71 -0
  9. configs/_hub_/models.yaml +57 -0
  10. configs/base.yaml +33 -0
  11. configs/callback/ckpt/all-p1k.yaml +5 -0
  12. configs/callback/ckpt/top1-p1k.yaml +5 -0
  13. configs/callback/skelify-spin/i10kb1.yaml +19 -0
  14. configs/callback/skelify-spin/i230kb1.yaml +16 -0
  15. configs/callback/skelify-spin/i80.yaml +15 -0
  16. configs/callback/skelify-spin/read_only.yaml +18 -0
  17. configs/data/skel-hmr2_fashion.yaml +71 -0
  18. configs/data/skel-hsr_v1_4ds.yaml +84 -0
  19. configs/data/skel-hsr_v1_full.yaml +96 -0
  20. configs/exp/default.yaml +16 -0
  21. configs/exp/hsr/train.yaml +47 -0
  22. configs/exp/skelify-full.yaml +21 -0
  23. configs/exp/skelify-refiner.yaml +21 -0
  24. configs/pipeline/hmr2.yaml +43 -0
  25. configs/pipeline/hsr.yaml +49 -0
  26. configs/pipeline/skelify-full.yaml +98 -0
  27. configs/pipeline/skelify-refiner.yaml +37 -0
  28. configs/policy/README.md +3 -0
  29. configs/policy/default.yaml +5 -0
  30. data_inputs/.gitignore +2 -0
  31. data_inputs/body_models.tar.gz +0 -0
  32. data_inputs/description.md +21 -0
  33. data_inputs/example_imgs/ballerina.png +0 -0
  34. data_inputs/example_imgs/exercise.jpeg +0 -0
  35. data_inputs/example_imgs/jump_high.jpg +0 -0
  36. data_outputs/.gitignore +0 -0
  37. lib/__init__.py +1 -0
  38. lib/body_models/abstract_skeletons.py +123 -0
  39. lib/body_models/common.py +69 -0
  40. lib/body_models/moyo_smplx_wrapper.py +77 -0
  41. lib/body_models/skel/__init__.py +0 -0
  42. lib/body_models/skel/alignment/align_config.py +72 -0
  43. lib/body_models/skel/alignment/align_config_joint.py +72 -0
  44. lib/body_models/skel/alignment/aligner.py +469 -0
  45. lib/body_models/skel/alignment/losses.py +47 -0
  46. lib/body_models/skel/alignment/riggid_parts_mask.pkl +0 -0
  47. lib/body_models/skel/alignment/utils.py +119 -0
  48. lib/body_models/skel/config.py +2 -0
  49. lib/body_models/skel/fit_osim/Fitting_SKEL_to_osim.md +20 -0
  50. lib/body_models/skel/fit_osim/mappings/full_body.yaml +29 -0
.gitignore ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Caches.
2
+ .DS_Store
3
+ __pycache__
4
+ *.egg-info
5
+
6
+ # VEnv.
7
+ .hsmr_env
8
+
9
+ # Hydra dev.
10
+ outputs
11
+
12
+ # Development tools.
13
+ .vscode
14
+ pyrightconfig.json
15
+
16
+ # Logs.
17
+ gpu_monitor.log
Dockerfile ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # read the doc: https://huggingface.co/docs/hub/spaces-sdks-docker
2
+ # you will also find guides on how best to write your Dockerfile
3
+
4
+ FROM python:3.10
5
+
6
+ WORKDIR /code
7
+
8
+ COPY ./requirements_part1.txt /code/requirements_part1.txt
9
+ COPY ./requirements_part2.txt /code/requirements_part2.txt
10
+
11
+ RUN apt-get update -y && \
12
+ apt-get upgrade -y && \
13
+ apt-get install -y libglfw3-dev && \
14
+ apt-get install -y libgles2-mesa-dev && \
15
+ apt-get install -y aria2 && \
16
+ pip install --no-cache-dir --upgrade -r /code/requirements_part1.txt && \
17
+ pip install --no-cache-dir --upgrade -r /code/requirements_part2.txt
18
+
19
+ # Set up a new user named "user" with user ID 1000
20
+ RUN useradd -m -u 1000 user
21
+
22
+ # Switch to the "user" user
23
+ USER user
24
+
25
+
26
+ # Set home to the user's home directory
27
+ ENV HOME=/home/user \
28
+ PATH=/home/user/.local/bin:$PATH
29
+
30
+ # Set the working directory to the user's home directory
31
+ WORKDIR $HOME/app
32
+
33
+ # Copy the current directory contents into the container at $HOME/app setting the owner to the user
34
+ COPY --chown=user . $HOME/app
35
+
36
+
37
+ CMD ["bash", "tools/start.sh"]
LICENSE ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Yan XIA
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.
README.md CHANGED
@@ -1,11 +1,10 @@
1
  ---
2
  title: HSMR
3
  emoji: 💀
4
- colorFrom: purple
5
- colorTo: indigo
6
- sdk: gradio
7
- sdk_version: 5.20.0
8
- app_file: tools/service.py
9
  pinned: true
10
  ---
11
 
 
1
  ---
2
  title: HSMR
3
  emoji: 💀
4
+ colorFrom: blue
5
+ colorTo: pink
6
+ sdk: docker
7
+ app_port: 7860
 
8
  pinned: true
9
  ---
10
 
configs/.gitignore ADDED
File without changes
configs/README.md ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Instructions for the Configuration System
2
+
3
+ The configuration system I used here is based on [Hydra](https://hydra.cc/). However, I made some small 'hack' to achieve some better features, such as `_hub_`. It might be a little bit hard to understand at first, but a comprehensive guidance is provided. Check `README.md` in each directory for more details.
4
+
5
+ ## Philosophy
6
+
7
+ - Easy to modify and maintain.
8
+ - Help you to code with clear structure.
9
+ - Consistency.
10
+ - Easy to trace and identify specific item.
11
+
12
+ ## Some Ideas
13
+
14
+ - Less ListConfig, or ListConfig only for real list data.
15
+ - Dumped list will be unfolded, each element occupies one line, and it's annoying when presenting.
16
+ - List things are not friendly to command line arguments supports.
17
+ - For defaults list, `_self_` must be explicitly specified.
18
+ - Items before `_self_` means 'based on those items'.
19
+ - Items after `_self_` means 'import those items'.
20
+
21
+ ### COMPOSITION OVER INHERITANCE
22
+
23
+ Do not use "overrides" AS MUCH AS POSSIBLE, except when the changes are really tiny. Since it's hard to identify which term is actually used without running the code. Instead, the `default.yaml` serves like a template, you are supposed to copy it and modify it to create a new configuration.
24
+
25
+ ### REFERENCE OVER COPY
26
+
27
+ If you want to use one things for many times (across each experiments or across each components in one experiment), you'd better use `${...}` to reference the Hydra object. So that you only need to modify one place while ensuring the consistency. Think about where to put the source of the object, `_hub_` is recommended but may not be the best choice every time.
28
+
29
+ ### PREPARE EVERYTHING YOU NEED LOCALLY
30
+
31
+ Sometimes you might want to use some configurations outside the class configuration (I mean in the coding process). In that case, I recommend you to reference these things again in local configuration package. It might be too redundant, but it will bring cleaner code.
configs/_hub_/README.md ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ # _hub
2
+
3
+ Configs here shouldn't be used as what Hydra calls a "config group". Configs here serve as a hub for other configs to reference. Most things here usually won't be used in a certain experiment.
4
+
5
+ For example, the details of each datasets can be defined here, and than I only need to reference the `Human3.6M` dataset through `${_hub_.datasets.h36m}` in other configs.
6
+
7
+ Each `.yaml` file here will be loaded separately in `base.yaml`. Check the `base.yaml` to understand how it works.
configs/_hub_/datasets.yaml ADDED
@@ -0,0 +1,71 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ train:
2
+ hsmr: # Fitted according to the SMPL's vertices.
3
+ # Standard 4 datasets.
4
+ mpi_inf_3dhp:
5
+ name: 'HSMR-MPI-INF-3DHP-train-pruned'
6
+ urls: ${_pm_.inputs}/hsmr_training_data/mpi_inf_3dhp-tars/{000000..000012}.tar
7
+ epoch_size: 12_000
8
+ h36m:
9
+ name: 'HSMR-H36M-train'
10
+ urls: ${_pm_.inputs}/hsmr_training_data/h36m-tars/{000000..000312}.tar
11
+ epoch_size: 314_000
12
+ mpii:
13
+ name: 'HSMR-MPII-train'
14
+ urls: ${_pm_.inputs}/hsmr_training_data/mpii-tars/{000000..000009}.tar
15
+ epoch_size: 10_000
16
+ coco14:
17
+ name: 'HSMR-COCO14-train'
18
+ urls: ${_pm_.inputs}/hsmr_training_data/coco14-tars/{000000..000017}.tar
19
+ epoch_size: 18_000
20
+ # The rest full datasets for HMR2.0.
21
+ coco14vit:
22
+ name: 'HSMR-COCO14-vit-train'
23
+ urls: ${_pm_.inputs}/hsmr_training_data/coco14vit-tars/{000000..000044}.tar
24
+ epoch_size: 45_000
25
+ aic:
26
+ name: 'HSMR-AIC-train'
27
+ urls: ${_pm_.inputs}/hsmr_training_data/aic-tars/{000000..000209}.tar
28
+ epoch_size: 210_000
29
+ ava:
30
+ name: 'HSMR-AVA-train'
31
+ urls: ${_pm_.inputs}/hsmr_training_data/ava-tars/{000000..000184}.tar
32
+ epoch_size: 185_000
33
+ insta:
34
+ name: 'HSMR-INSTA-train'
35
+ urls: ${_pm_.inputs}/hsmr_training_data/insta-tars/{000000..003657}.tar
36
+ epoch_size: 3_658_000
37
+
38
+ mocap:
39
+ bioamass_v1:
40
+ dataset_file: ${_pm_.inputs}/datasets/amass_skel/data_v1.npz
41
+ pve_threshold: 0.05
42
+ cmu_mocap:
43
+ dataset_file: ${_pm_.inputs}/hmr2_training_data/cmu_mocap.npz
44
+
45
+ eval:
46
+ h36m_val_p2:
47
+ dataset_file: ${_pm_.inputs}/hmr2_evaluation_data/h36m_val_p2.npz
48
+ img_root: ${_pm_.inputs}/datasets/h36m/images
49
+ kp_list: [25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 43]
50
+ use_hips: true
51
+
52
+ 3dpw_test:
53
+ dataset_file: ${_pm_.inputs}/hmr2_evaluation_data/3dpw_test.npz
54
+ img_root: ${_pm_.inputs}/datasets/3dpw
55
+ kp_list: [25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 43]
56
+ use_hips: false
57
+
58
+ posetrack_val:
59
+ dataset_file: ${_pm_.inputs}/hmr2_evaluation_data/posetrack_2018_val.npz
60
+ img_root: ${_pm_.inputs}/datasets/posetrack/posetrack2018/posetrack_data/
61
+ kp_list: [0] # dummy
62
+
63
+ lsp_extended:
64
+ dataset_file: ${_pm_.inputs}/hmr2_evaluation_data/hr-lspet_train.npz
65
+ img_root: ${_pm_.inputs}/datasets/hr-lspet
66
+ kp_list: [0] # dummy
67
+
68
+ coco_val:
69
+ dataset_file: ${_pm_.inputs}/hmr2_evaluation_data/coco_val.npz
70
+ img_root: ${_pm_.inputs}/datasets/coco
71
+ kp_list: [0] # dummy
configs/_hub_/models.yaml ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ body_models:
2
+
3
+ skel_mix_hsmr:
4
+ _target_: lib.body_models.skel_wrapper.SKELWrapper
5
+ model_path: '${_pm_.inputs}/body_models/skel'
6
+ gender: male # Use male since we don't have neutral model.
7
+ joint_regressor_extra: '${_pm_.inputs}/body_models/SMPL_to_J19.pkl'
8
+ joint_regressor_custom: '${_pm_.inputs}/body_models/J_regressor_SKIL_mix_MALE.pkl'
9
+
10
+ skel_hsmr:
11
+ _target_: lib.body_models.skel_wrapper.SKELWrapper
12
+ model_path: '${_pm_.inputs}/body_models/skel'
13
+ gender: male # Use male since we don't have neutral model.
14
+ joint_regressor_extra: '${_pm_.inputs}/body_models/SMPL_to_J19.pkl'
15
+ # joint_regressor_custom: '${_pm_.inputs}/body_models/J_regressor_SMPL_MALE.pkl'
16
+
17
+ smpl_hsmr:
18
+ _target_: lib.body_models.smpl_wrapper.SMPLWrapper
19
+ model_path: '${_pm_.inputs}/body_models/smpl'
20
+ gender: male # align with skel_hsmr
21
+ num_body_joints: 23
22
+ joint_regressor_extra: '${_pm_.inputs}/body_models/SMPL_to_J19.pkl'
23
+
24
+ smpl_hsmr_neutral:
25
+ _target_: lib.body_models.smpl_wrapper.SMPLWrapper
26
+ model_path: '${_pm_.inputs}/body_models/smpl'
27
+ gender: neutral # align with skel_hsmr
28
+ num_body_joints: 23
29
+ joint_regressor_extra: '${_pm_.inputs}/body_models/SMPL_to_J19.pkl'
30
+
31
+ backbones:
32
+
33
+ vit_b:
34
+ _target_: lib.modeling.networks.backbones.ViT
35
+ img_size: [256, 192]
36
+ patch_size: 16
37
+ embed_dim: 768
38
+ depth: 12
39
+ num_heads: 12
40
+ ratio: 1
41
+ use_checkpoint: False
42
+ mlp_ratio: 4
43
+ qkv_bias: True
44
+ drop_path_rate: 0.3
45
+
46
+ vit_h:
47
+ _target_: lib.modeling.networks.backbones.ViT
48
+ img_size: [256, 192]
49
+ patch_size: 16
50
+ embed_dim: 1280
51
+ depth: 32
52
+ num_heads: 16
53
+ ratio: 1
54
+ use_checkpoint: False
55
+ mlp_ratio: 4
56
+ qkv_bias: True
57
+ drop_path_rate: 0.55
configs/base.yaml ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ defaults:
2
+ # Load each sub-hub.
3
+ - _hub_/datasets@_hub_.datasets
4
+ - _hub_/models@_hub_.models
5
+
6
+ # Set up the template.
7
+ - _self_
8
+
9
+ # Register some defaults.
10
+ - exp: default
11
+ - policy: default
12
+
13
+
14
+ # exp_name: !!null # Name of experiment will determine the output folder.
15
+ exp_name: '${exp_topic}-${exp_tag}'
16
+ exp_topic: !!null # Theme of the experiment.
17
+ exp_tag: 'debug' # Tag of the experiment.
18
+
19
+
20
+ output_dir: ${_pm_.root}/data_outputs/exp/${exp_name} # Output directory for the experiment.
21
+ # output_dir: ${_pm_.root}/data_outputs/exp/${now:%Y-%m-%d}/${exp_name} # Output directory for the experiment.
22
+
23
+ hydra:
24
+ run:
25
+ dir: ${output_dir}
26
+
27
+
28
+ # Information from `PathManager`, which will be automatically set by entrypoint wrapper.
29
+ # The related implementation is in `lib/utils/cfg_utils.py`:`entrypoint_with_args`.
30
+ _pm_:
31
+ root: !!null
32
+ inputs: !!null
33
+ outputs: !!null
configs/callback/ckpt/all-p1k.yaml ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ _target_: pytorch_lightning.callbacks.ModelCheckpoint
2
+ dirpath: '${output_dir}/checkpoints'
3
+ save_last: True
4
+ every_n_train_steps: 1000
5
+ save_top_k: -1 # save all ckpts
configs/callback/ckpt/top1-p1k.yaml ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ _target_: pytorch_lightning.callbacks.ModelCheckpoint
2
+ dirpath: '${output_dir}/checkpoints'
3
+ save_last: True
4
+ every_n_train_steps: 1000
5
+ save_top_k: 1 # save only the last checkpoint
configs/callback/skelify-spin/i10kb1.yaml ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ defaults:
2
+ - _self_
3
+ # Import the SKELify-SPIN callback.
4
+ - /pipeline/skelify-refiner@skelify
5
+
6
+ _target_: lib.modeling.callbacks.SKELifySPIN
7
+
8
+ # This configuration are for frozen backbone exp with batch_size = 24000
9
+
10
+ cfg:
11
+ interval: 10
12
+ batch_size: 24000 # when greater than `interval * dataloader's batch_size`, it's equivalent to that
13
+ max_batches_per_round: 1 # only the latest k * batch_size items are SPINed to save time
14
+ # better_pgt_fn: '${_pm_.inputs}/datasets/skel_training_data/spin/better_pseudo_gt.npz'
15
+ better_pgt_fn: '${output_dir}/better_pseudo_gt.npz'
16
+ skip_warm_up_steps: 10000
17
+ # skip_warm_up_steps: 0
18
+ update_better_pgt: True
19
+ valid_betas_threshold: 2
configs/callback/skelify-spin/i230kb1.yaml ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ defaults:
2
+ - _self_
3
+ # Import the SKELify-SPIN callback.
4
+ - /pipeline/skelify-refiner@skelify
5
+
6
+ _target_: lib.modeling.callbacks.SKELifySPIN
7
+
8
+ cfg:
9
+ interval: 230
10
+ batch_size: 18000 # when greater than `interval * dataloader's batch_size`, it's equivalent to that
11
+ max_batches_per_round: 1 # only the latest k * batch_size items are SPINed to save time
12
+ # better_pgt_fn: '${_pm_.inputs}/datasets/skel_training_data/spin/better_pseudo_gt.npz'
13
+ better_pgt_fn: '${output_dir}/better_pseudo_gt.npz'
14
+ skip_warm_up_steps: 5000
15
+ update_better_pgt: True
16
+ valid_betas_threshold: 2
configs/callback/skelify-spin/i80.yaml ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ defaults:
2
+ - _self_
3
+ # Import the SKELify-SPIN callback.
4
+ - /pipeline/skelify-refiner@skelify
5
+
6
+ _target_: lib.modeling.callbacks.SKELifySPIN
7
+
8
+ cfg:
9
+ interval: 80
10
+ batch_size: 24000 # when greater than `interval * dataloader's batch_size`, it's equivalent to that
11
+ # better_pgt_fn: '${_pm_.inputs}/datasets/skel_training_data/spin/better_pseudo_gt.npz'
12
+ better_pgt_fn: '${output_dir}/better_pseudo_gt.npz'
13
+ skip_warm_up_steps: 5000
14
+ update_better_pgt: True
15
+ valid_betas_threshold: 2
configs/callback/skelify-spin/read_only.yaml ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ defaults:
2
+ - _self_
3
+ # Import the SKELify-SPIN callback.
4
+ - /pipeline/skelify-refiner@skelify
5
+
6
+ _target_: lib.modeling.callbacks.SKELifySPIN
7
+
8
+ # This configuration are for frozen backbone exp with batch_size = 24000
9
+
10
+ cfg:
11
+ interval: 0
12
+ batch_size: 0 # when greater than `interval * dataloader's batch_size`, it's equivalent to that
13
+ max_batches_per_round: 0 # only the latest k * batch_size items are SPINed to save time
14
+ # better_pgt_fn: '${_pm_.inputs}/datasets/skel_training_data/spin/better_pseudo_gt.npz'
15
+ better_pgt_fn: '${output_dir}/better_pseudo_gt.npz'
16
+ skip_warm_up_steps: 0
17
+ update_better_pgt: True
18
+ valid_betas_threshold: 2
configs/data/skel-hmr2_fashion.yaml ADDED
@@ -0,0 +1,71 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ _target_: lib.data.modules.hmr2_fashion.skel_wds.DataModule
2
+
3
+ name: HMR2_fashion_WDS
4
+
5
+ cfg:
6
+
7
+ train:
8
+ shared_ds_opt: # TODO: modify this
9
+ SUPPRESS_KP_CONF_THRESH: 0.3
10
+ FILTER_NUM_KP: 4
11
+ FILTER_NUM_KP_THRESH: 0.0
12
+ FILTER_REPROJ_THRESH: 31000
13
+ SUPPRESS_BETAS_THRESH: 3.0
14
+ SUPPRESS_BAD_POSES: False
15
+ POSES_BETAS_SIMULTANEOUS: True
16
+ FILTER_NO_POSES: False
17
+ BETAS_REG: True
18
+
19
+ datasets:
20
+ - name: 'H36M'
21
+ item: ${_hub_.datasets.train.hsmr.h36m}
22
+ weight: 0.3
23
+ - name: 'MPII'
24
+ item: ${_hub_.datasets.train.hsmr.mpii}
25
+ weight: 0.1
26
+ - name: 'COCO14'
27
+ item: ${_hub_.datasets.train.hsmr.coco14}
28
+ weight: 0.4
29
+ - name: 'MPI-INF-3DHP'
30
+ item: ${_hub_.datasets.train.hsmr.mpi_inf_3dhp}
31
+ weight: 0.2
32
+
33
+ dataloader:
34
+ drop_last: True
35
+ batch_size: 300
36
+ num_workers: 6
37
+ prefetch_factor: 2
38
+
39
+ eval:
40
+ datasets:
41
+ - name: 'LSP-EXTENDED'
42
+ item: ${_hub_.datasets.eval.lsp_extended}
43
+ - name: 'H36M-VAL-P2'
44
+ item: ${_hub_.datasets.eval.h36m_val_p2}
45
+ - name: '3DPW-TEST'
46
+ item: ${_hub_.datasets.eval.3dpw_test}
47
+ - name: 'POSETRACK-VAL'
48
+ item: ${_hub_.datasets.eval.posetrack_val}
49
+ - name: 'COCO-VAL'
50
+ item: ${_hub_.datasets.eval.coco_val}
51
+
52
+
53
+ dataloader:
54
+ shuffle: False
55
+ batch_size: 300
56
+ num_workers: 6
57
+
58
+ policy: ${policy}
59
+
60
+ # TODO: modify this
61
+ augm:
62
+ SCALE_FACTOR: 0.3
63
+ ROT_FACTOR: 30
64
+ TRANS_FACTOR: 0.02
65
+ COLOR_SCALE: 0.2
66
+ ROT_AUG_RATE: 0.6
67
+ TRANS_AUG_RATE: 0.5
68
+ DO_FLIP: True
69
+ FLIP_AUG_RATE: 0.5
70
+ EXTREME_CROP_AUG_RATE: 0.10
71
+ EXTREME_CROP_AUG_LEVEL: 1
configs/data/skel-hsr_v1_4ds.yaml ADDED
@@ -0,0 +1,84 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ _target_: lib.data.modules.hsmr_v1.data_module.DataModule
2
+
3
+ name: SKEL_HSMR_V1
4
+
5
+ cfg:
6
+
7
+ train:
8
+ cfg:
9
+ # Loader settings.
10
+ suppress_pgt_params_pve_max_thresh: 0.06 # Mark PVE-MAX larger than this value as invalid.
11
+ suppress_kp_conf_thresh: 0.3 # Mark key-point confidence smaller than this value as invalid.
12
+ suppress_betas_thresh: 3.0 # Mark betas having components larger than this value as invalid.
13
+ poses_betas_simultaneous: True # Sync poses and betas for the small person.
14
+ filter_insufficient_kp_cnt: 4
15
+ suppress_insufficient_kp_thresh: 0.0
16
+ filter_reproj_err_thresh: 31000
17
+ regularize_invalid_betas: True
18
+ # Others.
19
+ image_augmentation: ${...image_augmentation}
20
+ policy: ${...policy}
21
+
22
+ datasets:
23
+ - name: 'H36M'
24
+ item: ${_hub_.datasets.train.hsmr.h36m}
25
+ weight: 0.3
26
+ - name: 'MPII'
27
+ item: ${_hub_.datasets.train.hsmr.mpii}
28
+ weight: 0.1
29
+ - name: 'COCO14'
30
+ item: ${_hub_.datasets.train.hsmr.coco14}
31
+ weight: 0.4
32
+ - name: 'MPI-INF-3DHP'
33
+ item: ${_hub_.datasets.train.hsmr.mpi_inf_3dhp}
34
+ weight: 0.2
35
+
36
+ dataloader:
37
+ drop_last: True
38
+ batch_size: 300
39
+ num_workers: 6
40
+ prefetch_factor: 2
41
+
42
+ # mocap:
43
+ # cfg: ${_hub_.datasets.mocap.bioamass_v1}
44
+ # dataloader:
45
+ # batch_size: 600 # num_train:2 * batch_size:300 (from HMR2.0's cfg)
46
+ # drop_last: True
47
+ # shuffle: True
48
+ # num_workers: 1
49
+
50
+ eval:
51
+ cfg:
52
+ image_augmentation: ${...image_augmentation}
53
+ policy: ${...policy}
54
+
55
+ datasets:
56
+ - name: 'LSP-EXTENDED'
57
+ item: ${_hub_.datasets.eval.lsp_extended}
58
+ - name: 'H36M-VAL-P2'
59
+ item: ${_hub_.datasets.eval.h36m_val_p2}
60
+ - name: '3DPW-TEST'
61
+ item: ${_hub_.datasets.eval.3dpw_test}
62
+ - name: 'POSETRACK-VAL'
63
+ item: ${_hub_.datasets.eval.posetrack_val}
64
+ - name: 'COCO-VAL'
65
+ item: ${_hub_.datasets.eval.coco_val}
66
+
67
+ dataloader:
68
+ shuffle: False
69
+ batch_size: 300
70
+ num_workers: 6
71
+
72
+ # Augmentation settings.
73
+ image_augmentation:
74
+ trans_factor: 0.02
75
+ bbox_scale_factor: 0.3
76
+ rot_aug_rate: 0.6
77
+ rot_factor: 30
78
+ do_flip: True
79
+ flip_aug_rate: 0.5
80
+ extreme_crop_aug_rate: 0.10
81
+ half_color_scale: 0.2
82
+
83
+ # Others.
84
+ policy: ${policy}
configs/data/skel-hsr_v1_full.yaml ADDED
@@ -0,0 +1,96 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ _target_: lib.data.modules.hsmr_v1.data_module.DataModule
2
+
3
+ name: SKEL_HSMR_V1
4
+
5
+ cfg:
6
+
7
+ train:
8
+ cfg:
9
+ # Loader settings.
10
+ suppress_pgt_params_pve_max_thresh: 0.06 # Mark PVE-MAX larger than this value as invalid.
11
+ suppress_kp_conf_thresh: 0.3 # Mark key-point confidence smaller than this value as invalid.
12
+ suppress_betas_thresh: 3.0 # Mark betas having components larger than this value as invalid.
13
+ poses_betas_simultaneous: True # Sync poses and betas for the small person.
14
+ filter_insufficient_kp_cnt: 4
15
+ suppress_insufficient_kp_thresh: 0.0
16
+ filter_reproj_err_thresh: 31000
17
+ regularize_invalid_betas: True
18
+ # Others.
19
+ image_augmentation: ${...image_augmentation}
20
+ policy: ${...policy}
21
+
22
+ datasets:
23
+ - name: 'H36M'
24
+ item: ${_hub_.datasets.train.hsmr.h36m}
25
+ weight: 0.1
26
+ - name: 'MPII'
27
+ item: ${_hub_.datasets.train.hsmr.mpii}
28
+ weight: 0.1
29
+ - name: 'COCO14'
30
+ item: ${_hub_.datasets.train.hsmr.coco14}
31
+ weight: 0.1
32
+ - name: 'COCO14-ViTPose'
33
+ item: ${_hub_.datasets.train.hsmr.coco14vit}
34
+ weight: 0.1
35
+ - name: 'MPI-INF-3DHP'
36
+ item: ${_hub_.datasets.train.hsmr.mpi_inf_3dhp}
37
+ weight: 0.02
38
+ - name: 'AVA'
39
+ item: ${_hub_.datasets.train.hsmr.ava}
40
+ weight: 0.19
41
+ - name: 'AIC'
42
+ item: ${_hub_.datasets.train.hsmr.aic}
43
+ weight: 0.19
44
+ - name: 'INSTA'
45
+ item: ${_hub_.datasets.train.hsmr.insta}
46
+ weight: 0.2
47
+
48
+ dataloader:
49
+ drop_last: True
50
+ batch_size: 300
51
+ num_workers: 6
52
+ prefetch_factor: 2
53
+
54
+ mocap:
55
+ cfg: ${_hub_.datasets.mocap.bioamass_v1}
56
+ dataloader:
57
+ batch_size: 600 # num_train:2 * batch_size:300 (from HMR2.0's cfg)
58
+ drop_last: True
59
+ shuffle: True
60
+ num_workers: 1
61
+
62
+ eval:
63
+ cfg:
64
+ image_augmentation: ${...image_augmentation}
65
+ policy: ${...policy}
66
+
67
+ datasets:
68
+ - name: 'LSP-EXTENDED'
69
+ item: ${_hub_.datasets.eval.lsp_extended}
70
+ - name: 'H36M-VAL-P2'
71
+ item: ${_hub_.datasets.eval.h36m_val_p2}
72
+ - name: '3DPW-TEST'
73
+ item: ${_hub_.datasets.eval.3dpw_test}
74
+ - name: 'POSETRACK-VAL'
75
+ item: ${_hub_.datasets.eval.posetrack_val}
76
+ - name: 'COCO-VAL'
77
+ item: ${_hub_.datasets.eval.coco_val}
78
+
79
+ dataloader:
80
+ shuffle: False
81
+ batch_size: 300
82
+ num_workers: 6
83
+
84
+ # Augmentation settings.
85
+ image_augmentation:
86
+ trans_factor: 0.02
87
+ bbox_scale_factor: 0.3
88
+ rot_aug_rate: 0.6
89
+ rot_factor: 30
90
+ do_flip: True
91
+ flip_aug_rate: 0.5
92
+ extreme_crop_aug_rate: 0.10
93
+ half_color_scale: 0.2
94
+
95
+ # Others.
96
+ policy: ${policy}
configs/exp/default.yaml ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # @package _global_
2
+ defaults:
3
+ # Use absolute path, cause the root in this file is `configs/exp/` rather than `configs/`.
4
+ # - /data: ...
5
+
6
+ # Configurations in this file should have the highest priority.
7
+ - _self_
8
+
9
+ # ======= Overwrite Section =======
10
+ # (Do not use as much as possible!)
11
+
12
+
13
+
14
+ # ====== Main Section ======
15
+
16
+ exp_topic: !!null # Name of experiment will determine the output folder.
configs/exp/hsr/train.yaml ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # @package _global_
2
+ defaults:
3
+ - /pipeline: hsmr
4
+ - /data: skel-hsmr_v1_full
5
+ # Configurations in this file
6
+ - _self_
7
+ # Import callbacks.
8
+ - /callback/ckpt/[email protected]
9
+ # - /callback/skelify-spin/[email protected]
10
+ - /callback/skelify-spin/[email protected]
11
+
12
+ # ======= Overwrite Section =======
13
+ # (Do not use as much as possible!)
14
+
15
+ pipeline:
16
+ cfg:
17
+ backbone: ${_hub_.models.backbones.vit_h}
18
+ backbone_ckpt: '${_pm_.inputs}/backbone/vitpose_backbone.pth'
19
+ freeze_backbone: False
20
+
21
+ data:
22
+ cfg:
23
+ train:
24
+ dataloader:
25
+ batch_size: 78
26
+
27
+
28
+ # ====== Main Section ======
29
+
30
+ exp_topic: 'HSMR-train-vit_h'
31
+
32
+ enable_time_monitor: False
33
+
34
+ seed: NULL
35
+ ckpt_path: NULL
36
+
37
+ logger:
38
+ interval: 1000
39
+ interval_skelify: 10
40
+ samples_per_record: 5
41
+
42
+ task: 'fit'
43
+ pl_trainer:
44
+ devices: 8
45
+ max_epochs: 100
46
+ deterministic: false
47
+ precision: 16
configs/exp/skelify-full.yaml ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # @package _global_
2
+ defaults:
3
+ - /pipeline: skelify-full
4
+ - /data: skel-hsmr_v1_full
5
+ # Configurations in this file should have the highest priority.
6
+ - _self_
7
+
8
+ # ======= Overwrite Section =======
9
+ # (Do not use as much as possible!)
10
+
11
+
12
+
13
+ # ====== Main Section ======
14
+
15
+ exp_topic: 'SKELify-Full'
16
+
17
+ enable_time_monitor: True
18
+
19
+ logger:
20
+ interval: 1
21
+ samples_per_record: 8
configs/exp/skelify-refiner.yaml ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # @package _global_
2
+ defaults:
3
+ - /pipeline: skelify-refiner
4
+ - /data: skel-hsmr_v1_full
5
+ # Configurations in this file should have the highest priority.
6
+ - _self_
7
+
8
+ # ======= Overwrite Section =======
9
+ # (Do not use as much as possible!)
10
+
11
+
12
+
13
+ # ====== Main Section ======
14
+
15
+ exp_topic: 'SKELify-Refiner'
16
+
17
+ enable_time_monitor: True
18
+
19
+ logger:
20
+ interval: 2
21
+ samples_per_record: 8
configs/pipeline/hmr2.yaml ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ _target_: lib.modeling.pipelines.HMR2Pipeline
2
+
3
+ name: HMR2
4
+
5
+ cfg:
6
+ # Body Models
7
+ SMPL: ${_hub_.models.body_models.smpl_hsmr_neutral}
8
+
9
+ # Backbone and its checkpoint.
10
+ backbone: ${_hub_.models.backbones.vit_h}
11
+ backbone_ckpt: ???
12
+
13
+ # Head to get the parameters.
14
+ head:
15
+ _target_: lib.modeling.networks.heads.SMPLTransformerDecoderHead
16
+ cfg:
17
+ transformer_decoder:
18
+ depth: 6
19
+ heads: 8
20
+ mlp_dim: 1024
21
+ dim_head: 64
22
+ dropout: 0.0
23
+ emb_dropout: 0.0
24
+ norm: 'layer'
25
+ context_dim: ${....backbone.embed_dim}
26
+
27
+ optimizer:
28
+ _target_: torch.optim.AdamW
29
+ lr: 1e-5
30
+ weight_decay: 1e-4
31
+
32
+ # This may be redesigned, e.g., we can add a loss object to maintain the calculation of the loss, or a callback.
33
+ loss_weights:
34
+ kp3d: 0.05
35
+ kp2d: 0.01
36
+ poses_orient: 0.002
37
+ poses_body: 0.001
38
+ betas: 0.0005
39
+ adversarial: 0.0005
40
+ # adversarial: 0.0
41
+
42
+ policy: ${policy}
43
+ logger: ${logger}
configs/pipeline/hsr.yaml ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ _target_: lib.modeling.pipelines.HSMRPipeline
2
+
3
+ name: HSMR
4
+
5
+ cfg:
6
+ pd_poses_repr: 'rotation_6d' # poses representation for prediction, choices: 'euler_angle' | 'rotation_6d'
7
+ sp_poses_repr: 'rotation_matrix' # poses representation for supervision, choices: 'euler_angle' | 'rotation_matrix'
8
+
9
+ # Body Models
10
+ # SKEL: ${_hub_.models.body_models.skel_hsmr}
11
+ SKEL: ${_hub_.models.body_models.skel_mix_hsmr}
12
+
13
+ # Backbone and its checkpoint.
14
+ backbone: ???
15
+ backbone_ckpt: ???
16
+
17
+ # Head to get the parameters.
18
+ head:
19
+ _target_: lib.modeling.networks.heads.SKELTransformerDecoderHead
20
+ cfg:
21
+ pd_poses_repr: ${...pd_poses_repr}
22
+ transformer_decoder:
23
+ depth: 6
24
+ heads: 8
25
+ mlp_dim: 1024
26
+ dim_head: 64
27
+ dropout: 0.0
28
+ emb_dropout: 0.0
29
+ norm: 'layer'
30
+ context_dim: ${....backbone.embed_dim}
31
+
32
+ optimizer:
33
+ _target_: torch.optim.AdamW
34
+ lr: 1e-5
35
+ weight_decay: 1e-4
36
+
37
+ # This may be redesigned, e.g., we can add a loss object to maintain the calculation of the loss, or a callback.
38
+ loss_weights:
39
+ kp3d: 0.05
40
+ kp2d: 0.01
41
+ # prior: 0.0005
42
+ prior: 0.0
43
+ poses_orient: 0.002
44
+ poses_body: 0.001
45
+ betas: 0.0005
46
+ # adversarial: 0.0005
47
+
48
+ policy: ${policy}
49
+ logger: ${logger}
configs/pipeline/skelify-full.yaml ADDED
@@ -0,0 +1,98 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ _target_: lib.modeling.optim.SKELify
2
+
3
+ name: SKELify
4
+
5
+ cfg:
6
+ skel_model: ${_hub_.models.body_models.skel_mix_hsmr}
7
+
8
+ _f_normalize_kp2d: True
9
+ _f_normalize_kp2d_to_mean: False
10
+ _w_angle_prior_scale: 1.7
11
+
12
+ phases:
13
+ # ================================
14
+ # ⛩️ Part 1: Camera initialization.
15
+ # --------------------------------
16
+ STAGE-camera-init:
17
+ max_loop: 30
18
+ params_keys: ['cam_t', 'poses_orient']
19
+ parts: ['torso']
20
+ optimizer: ${...optimizer}
21
+ losses:
22
+ f_normalize_kp2d: ${...._f_normalize_kp2d}
23
+ f_normalize_kp2d_to_mean: ${...._f_normalize_kp2d_to_mean}
24
+ w_depth: 100.0
25
+ w_reprojection: 1.78
26
+ # ================================
27
+
28
+ # ================================
29
+ # ⛩️ Part 2: Overall optimization.
30
+ # --------------------------------
31
+ STAGE-overall-1:
32
+ max_loop: 30
33
+ params_keys: ['cam_t', 'poses_orient', 'poses_body', 'betas']
34
+ parts: ['all']
35
+ optimizer: ${...optimizer}
36
+ losses:
37
+ f_normalize_kp2d: ${...._f_normalize_kp2d}
38
+ f_normalize_kp2d_to_mean: ${...._f_normalize_kp2d_to_mean}
39
+ w_reprojection: 1.0
40
+ w_shape_prior: 100.0
41
+ w_angle_prior: 404.0
42
+ w_angle_prior_scale: ${...._w_angle_prior_scale} # TODO: Finalize it.
43
+ # --------------------------------
44
+ STAGE-overall-2:
45
+ max_loop: 30
46
+ params_keys: ['cam_t', 'poses_orient', 'poses_body', 'betas']
47
+ optimizer: ${...optimizer}
48
+ parts: ['all']
49
+ losses:
50
+ f_normalize_kp2d: ${...._f_normalize_kp2d}
51
+ f_normalize_kp2d_to_mean: ${...._f_normalize_kp2d_to_mean}
52
+ w_reprojection: 1.0
53
+ w_shape_prior: 50.0
54
+ w_angle_prior: 404.0
55
+ w_angle_prior_scale: ${...._w_angle_prior_scale} # TODO: Finalize it.
56
+ # --------------------------------
57
+ STAGE-overall-3:
58
+ max_loop: 30
59
+ params_keys: ['cam_t', 'poses_orient', 'poses_body', 'betas']
60
+ parts: ['all']
61
+ optimizer: ${...optimizer}
62
+ losses:
63
+ f_normalize_kp2d: ${...._f_normalize_kp2d}
64
+ f_normalize_kp2d_to_mean: ${...._f_normalize_kp2d_to_mean}
65
+ w_reprojection: 1.0
66
+ w_shape_prior: 10.0
67
+ w_angle_prior: 57.4
68
+ w_angle_prior_scale: ${...._w_angle_prior_scale} # TODO: Finalize it.
69
+ # --------------------------------
70
+ STAGE-overall-4:
71
+ max_loop: 30
72
+ params_keys: ['cam_t', 'poses_orient', 'poses_body', 'betas']
73
+ parts: ['all']
74
+ optimizer: ${...optimizer}
75
+ losses:
76
+ f_normalize_kp2d: ${...._f_normalize_kp2d}
77
+ f_normalize_kp2d_to_mean: ${...._f_normalize_kp2d_to_mean}
78
+ w_reprojection: 1.0
79
+ w_shape_prior: 5.0
80
+ w_angle_prior: 4.78
81
+ w_angle_prior_scale: ${...._w_angle_prior_scale} # TODO: Finalize it.
82
+ # ================================
83
+
84
+ optimizer:
85
+ _target_: torch.optim.LBFGS
86
+ lr: 1
87
+ line_search_fn: 'strong_wolfe'
88
+ tolerance_grad: ${..early_quit_thresholds.abs}
89
+ tolerance_change: ${..early_quit_thresholds.rel}
90
+
91
+ early_quit_thresholds:
92
+ abs: 1e-9
93
+ rel: 1e-9
94
+
95
+ img_patch_size: ${policy.img_patch_size}
96
+ focal_length: ${policy.focal_length}
97
+
98
+ logger: ${logger}
configs/pipeline/skelify-refiner.yaml ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ _target_: lib.modeling.optim.SKELify
2
+
3
+ name: SKELify-Refiner
4
+
5
+ cfg:
6
+ skel_model: ${_hub_.models.body_models.skel_mix_hsmr}
7
+
8
+ phases:
9
+ STAGE-refine:
10
+ max_loop: 10
11
+ params_keys: ['cam_t', 'poses_orient', 'poses_body', 'betas']
12
+ optimizer: ${...optimizer}
13
+ losses:
14
+ f_normalize_kp2d: True
15
+ f_normalize_kp2d_to_mean: False
16
+ w_reprojection: 1.0
17
+ w_shape_prior: 5.0
18
+ w_angle_prior: 4.78
19
+ w_angle_prior_scale: 0.17
20
+ parts: ['all']
21
+ # ================================
22
+
23
+ optimizer:
24
+ _target_: torch.optim.LBFGS
25
+ lr: 1
26
+ line_search_fn: 'strong_wolfe'
27
+ tolerance_grad: ${..early_quit_thresholds.abs}
28
+ tolerance_change: ${..early_quit_thresholds.rel}
29
+
30
+ early_quit_thresholds:
31
+ abs: 1e-7
32
+ rel: 1e-9
33
+
34
+ img_patch_size: ${policy.img_patch_size}
35
+ focal_length: ${policy.focal_length}
36
+
37
+ logger: ${logger}
configs/policy/README.md ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ # policy
2
+
3
+ Configs here define frequently used values that are shared across multiple sub-modules. They usually cannot belongs to any specific sub-module.
configs/policy/default.yaml ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ img_patch_size: 256
2
+ focal_length: 5000
3
+ img_mean: [0.485, 0.456, 0.406]
4
+ img_std: [0.229, 0.224, 0.225]
5
+ bbox_shape: null
data_inputs/.gitignore ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ body_models
2
+ released_models
data_inputs/body_models.tar.gz ADDED
File without changes
data_inputs/description.md ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <center><b><font size='10'>
2
+ H<font color='CAB6A5'>S</font><font color='98BACB'>M</font>R
3
+ </font></b></center>
4
+ <center><b><font size='6'>
5
+ Reconstructing <font color='98BACB'>Humans</font> with a <br> <font color='CAB6A5'>Biomechanically</font> Accurate Skeleton
6
+ </font></b></center>
7
+ <center><b>
8
+ <a href=#>Project Page</a>
9
+ |
10
+ <a href=#>GitHub Repo</a>
11
+ |
12
+ <a href=#>Paper</a>
13
+ </b></center>
14
+
15
+ > Reconstructing Humans with a Biomechanically Accurate Skeleton <br>
16
+ > [Yan Xia](https://scholar.isshikih.top),
17
+ > [Xiaowei Zhou](https://xzhou.me),
18
+ > [Etienne Vouga](https://www.cs.utexas.edu/~evouga/),
19
+ > [Qixing Huang](https://www.cs.utexas.edu/~huangqx/),
20
+ > [Georgios Pavlakos](https://geopavlakos.github.io/) <br>
21
+ > *CVPR 2025*
data_inputs/example_imgs/ballerina.png ADDED
data_inputs/example_imgs/exercise.jpeg ADDED
data_inputs/example_imgs/jump_high.jpg ADDED
data_outputs/.gitignore ADDED
File without changes
lib/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ from .version import __version__
lib/body_models/abstract_skeletons.py ADDED
@@ -0,0 +1,123 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # "Skeletons" here means virtual "bones" between joints. It is used to draw the skeleton on the image.
2
+
3
+ class Skeleton():
4
+ bones = []
5
+ bone_colors = []
6
+ chains = []
7
+ parent = []
8
+
9
+
10
+ class Skeleton_SMPL24(Skeleton):
11
+ # The joints definition are copied from
12
+ # [ROMP](https://github.com/Arthur151/ROMP/blob/4eebd3647f57d291d26423e51f0d514ff7197cb3/romp/lib/constants.py#L58).
13
+ chains = [
14
+ [0, 1, 4, 7, 10], # left leg
15
+ [0, 2, 5, 8, 11], # right leg
16
+ [0, 3, 6, 9, 12, 15], # spine & head
17
+ [12, 13, 16, 18, 20, 22], # left arm
18
+ [12, 14, 17, 19, 21, 23], # right arm
19
+ ]
20
+ bones = [
21
+ [ 0, 1], [ 1, 4], [ 4, 7], [ 7, 10], # left leg
22
+ [ 0, 2], [ 2, 5], [ 5, 8], [ 8, 11], # right leg
23
+ [ 0, 3], [ 3, 6], [ 6, 9], [ 9, 12], [12, 15], # spine & head
24
+ [12, 13], [13, 16], [16, 18], [18, 20], [20, 22], # left arm
25
+ [12, 14], [14, 17], [17, 19], [19, 21], [21, 23], # right arm
26
+ ]
27
+ bone_colors = [
28
+ [127, 0, 0], [148, 21, 21], [169, 41, 41], [191, 63, 63], # red
29
+ [ 0, 127, 0], [ 21, 148, 21], [ 41, 169, 41], [ 63, 191, 63], # green
30
+ [ 0, 0, 127], [ 15, 15, 143], [ 31, 31, 159], [ 47, 47, 175], [ 63, 63, 191], # blue
31
+ [ 0, 127, 127], [ 15, 143, 143], [ 31, 159, 159], [ 47, 175, 175], [ 63, 191, 191], # cyan
32
+ [127, 0, 127], [143, 15, 143], [159, 31, 159], [175, 47, 175], [191, 63, 191], # magenta
33
+ ]
34
+ parent = [-1, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 12, 12, 13, 14, 16, 17, 18, 19, 20, 21]
35
+
36
+
37
+ class Skeleton_SMPL22(Skeleton):
38
+ chains = [
39
+ [0, 1, 4, 7, 10], # left leg
40
+ [0, 2, 5, 8, 11], # right leg
41
+ [0, 3, 6, 9, 12, 15], # spine & head
42
+ [12, 13, 16, 18, 20], # left arm
43
+ [12, 14, 17, 19, 21], # right arm
44
+ ]
45
+ bones = [
46
+ [ 0, 1], [ 1, 4], [ 4, 7], [ 7, 10], # left leg
47
+ [ 0, 2], [ 2, 5], [ 5, 8], [ 8, 11], # right leg
48
+ [ 0, 3], [ 3, 6], [ 6, 9], [ 9, 12], [12, 15], # spine & head
49
+ [12, 13], [13, 16], [16, 18], [18, 20], # left arm
50
+ [12, 14], [14, 17], [17, 19], [19, 21], # right arm
51
+ ]
52
+ bone_colors = [
53
+ [127, 0, 0], [148, 21, 21], [169, 41, 41], [191, 63, 63], # red
54
+ [ 0, 127, 0], [ 21, 148, 21], [ 41, 169, 41], [ 63, 191, 63], # green
55
+ [ 0, 0, 127], [ 15, 15, 143], [ 31, 31, 159], [ 47, 47, 175], [ 63, 63, 191], # blue
56
+ [ 0, 127, 127], [ 15, 143, 143], [ 31, 159, 159], [ 47, 175, 175], # cyan
57
+ [127, 0, 127], [143, 15, 143], [159, 31, 159], [175, 47, 175], # magenta
58
+ ]
59
+ parent = [-1, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 12, 12, 13, 14, 16, 17, 18, 19]
60
+
61
+
62
+ class Skeleton_SKEL24(Skeleton):
63
+ chains = [
64
+ [ 0, 6, 7, 8, 9, 10], # left leg
65
+ [ 0, 1, 2, 3, 4, 5], # right leg
66
+ [ 0, 11, 12, 13], # spine & head
67
+ [12, 19, 20, 21, 22, 23], # left arm
68
+ [12, 14, 15, 16, 17, 18], # right arm
69
+ ]
70
+ bones = [
71
+ [ 0, 6], [ 6, 7], [ 7, 8], [ 8, 9], [ 9, 10], # left leg
72
+ [ 0, 1], [ 1, 2], [ 2, 3], [ 3, 4], [ 4, 5], # right leg
73
+ [ 0, 11], [11, 12], [12, 13], # spine & head
74
+ [12, 19], [19, 20], [20, 21], [21, 22], [22, 23], # left arm
75
+ [12, 14], [14, 15], [15, 16], [16, 17], [17, 18], # right arm
76
+ ]
77
+ bone_colors = [
78
+ [127, 0, 0], [148, 21, 21], [169, 41, 41], [191, 63, 63], [191, 63, 63], # red
79
+ [ 0, 127, 0], [ 21, 148, 21], [ 41, 169, 41], [ 63, 191, 63], [ 63, 191, 63], # green
80
+ [ 0, 0, 127], [ 31, 31, 159], [ 63, 63, 191], # blue
81
+ [ 0, 127, 127], [ 15, 143, 143], [ 31, 159, 159], [ 47, 175, 175], [ 63, 191, 191], # cyan
82
+ [127, 0, 127], [143, 15, 143], [159, 31, 159], [175, 47, 175], [191, 63, 191], # magenta
83
+ ]
84
+ parent = [-1, 0, 1, 2, 3, 4, 0, 6, 7, 8, 9, 0, 11, 12, 12, 19, 20, 21, 22, 12, 14, 15, 16, 17]
85
+
86
+
87
+ class Skeleton_OpenPose25(Skeleton):
88
+ ''' https://www.researchgate.net/figure/Twenty-five-keypoints-of-the-OpenPose-software-model_fig1_374116819 '''
89
+ chain = [
90
+ [ 8, 12, 13, 14, 19, 20], # left leg
91
+ [14, 21], # left heel
92
+ [ 8, 9, 10, 11, 22, 23], # right leg
93
+ [11, 24], # right heel
94
+ [ 8, 1, 0], # spine & head
95
+ [ 0, 16, 18], # left face
96
+ [ 0, 15, 17], # right face
97
+ [ 1, 5, 6, 7], # left arm
98
+ [ 1, 2, 3, 4], # right arm
99
+ ]
100
+ bones = [
101
+ [ 8, 12], [12, 13], [13, 14], # left leg
102
+ [14, 19], [19, 20], [14, 21], # left foot
103
+ [ 8, 9], [ 9, 10], [10, 11], # right leg
104
+ [11, 22], [22, 23], [11, 24], # right foot
105
+ [ 8, 1], [ 1, 0], # spine & head
106
+ [ 0, 16], [16, 18], # left face
107
+ [ 0, 15], [15, 17], # right face
108
+ [ 1, 5], [ 5, 6], [ 6, 7], # left arm
109
+ [ 1, 2], [ 2, 3], [ 3, 4], # right arm
110
+ ]
111
+ bone_colors = [
112
+ [ 95, 0, 255], [ 79, 0, 255], [ 83, 0, 255], # dark blue
113
+ [ 31, 0, 255], [ 15, 0, 255], [ 0, 0, 255], # dark blue
114
+ [127, 205, 255], [127, 205, 255], [ 95, 205, 255], # light blue
115
+ [ 63, 205, 255], [ 31, 205, 255], [ 0, 205, 255], # light blue
116
+ [255, 0, 0], [255, 0, 0], # red
117
+ [191, 63, 63], [191, 63, 191], # magenta
118
+ [255, 0, 127], [255, 0, 255], # purple
119
+ [127, 255, 0], [ 63, 255, 0], [ 0, 255, 0], # green
120
+ [255, 127, 0], [255, 191, 0], [255, 255, 0], # yellow
121
+
122
+ ]
123
+ parent = [1, 8, 1, 2, 3, 1, 5, 6, -1, 8, 9, 10, 8, 12, 13, 0, 0, 15, 16, 14, 19, 14, 11, 22, 11]
lib/body_models/common.py ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from lib.kits.basic import *
2
+
3
+ from smplx import SMPL
4
+
5
+ from lib.platform import PM
6
+ from lib.body_models.skel_wrapper import SKELWrapper as SKEL
7
+ from lib.body_models.smpl_wrapper import SMPLWrapper
8
+
9
+ def make_SMPL(gender='neutral', device='cuda:0'):
10
+ return SMPL(
11
+ gender = gender,
12
+ model_path = PM.inputs / 'body_models' / 'smpl',
13
+ ).to(device)
14
+
15
+
16
+ def make_SMPL_hmr2(gender='neutral', device='cuda:0'):
17
+ ''' SKEL doesn't have neutral model, so align with SKEL, using male. '''
18
+ return SMPLWrapper(
19
+ gender = gender,
20
+ model_path = PM.inputs / 'body_models' / 'smpl',
21
+ num_body_joints = 23,
22
+ joint_regressor_extra = PM.inputs / 'body_models/SMPL_to_J19.pkl',
23
+ ).to(device)
24
+
25
+
26
+
27
+ def make_SKEL(gender='male', device='cuda:0'):
28
+ ''' We don't have neutral model for SKEL, so use male for now. '''
29
+ return make_SKEL_mix_joints(gender, device)
30
+
31
+
32
+ def make_SKEL_smpl_joints(gender='male', device='cuda:0'):
33
+ ''' We don't have neutral model for SKEL, so use male for now. '''
34
+ return SKEL(
35
+ gender = gender,
36
+ model_path = PM.inputs / 'body_models' / 'skel',
37
+ joint_regressor_extra = PM.inputs / 'body_models' / 'SMPL_to_J19.pkl',
38
+ joint_regressor_custom = PM.inputs / 'body_models' / 'J_regressor_SMPL_MALE.pkl',
39
+ ).to(device)
40
+
41
+
42
+ def make_SKEL_skel_joints(gender='male', device='cuda:0'):
43
+ ''' We don't have neutral model for SKEL, so use male for now. '''
44
+ return SKEL(
45
+ gender = gender,
46
+ model_path = PM.inputs / 'body_models' / 'skel',
47
+ joint_regressor_extra = PM.inputs / 'body_models' / 'SMPL_to_J19.pkl',
48
+ ).to(device)
49
+
50
+
51
+ def make_SKEL_mix_joints(gender='male', device='cuda:0'):
52
+ ''' We don't have neutral model for SKEL, so use male for now. '''
53
+ return SKEL(
54
+ gender = gender,
55
+ model_path = PM.inputs / 'body_models' / 'skel',
56
+ joint_regressor_extra = PM.inputs / 'body_models' / 'SMPL_to_J19.pkl',
57
+ joint_regressor_custom = PM.inputs / 'body_models' / 'J_regressor_SKEL_mix_MALE.pkl',
58
+ ).to(device)
59
+
60
+
61
+ def make_SMPLX_moyo(v_template_path:Union[str, Path], batch_size:int=1, device='cuda:0'):
62
+ from lib.body_models.moyo_smplx_wrapper import MoYoSMPLX
63
+
64
+ return MoYoSMPLX(
65
+ model_path = PM.inputs / 'body_models' / 'smplx',
66
+ v_template_path = v_template_path,
67
+ batch_size = batch_size,
68
+ device = device,
69
+ )
lib/body_models/moyo_smplx_wrapper.py ADDED
@@ -0,0 +1,77 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from lib.kits.basic import *
2
+
3
+ import smplx
4
+ from psbody.mesh import Mesh
5
+
6
+ class MoYoSMPLX(smplx.SMPLX):
7
+
8
+ def __init__(
9
+ self,
10
+ model_path : Union[str, Path],
11
+ v_template_path : Union[str, Path],
12
+ batch_size = 1,
13
+ n_betas = 10,
14
+ device = 'cpu'
15
+ ):
16
+
17
+ if isinstance(v_template_path, Path):
18
+ v_template_path = str(v_template_path)
19
+
20
+ # Load the `v_template`.
21
+ v_template_mesh = Mesh(filename=v_template_path)
22
+ v_template = to_tensor(v_template_mesh.v, device=device)
23
+
24
+ self.n_betas = n_betas
25
+
26
+ # Create the `body_model_params`.
27
+ body_model_params = {
28
+ 'model_path' : model_path,
29
+ 'gender' : 'neutral',
30
+ 'v_template' : v_template.float(),
31
+ 'batch_size' : batch_size,
32
+ 'create_global_orient' : True,
33
+ 'create_body_pose' : True,
34
+ 'create_betas' : True,
35
+ 'num_betas' : self.n_betas, # They actually don't use num_betas.
36
+ 'create_left_hand_pose' : True,
37
+ 'create_right_hand_pose' : True,
38
+ 'create_expression' : True,
39
+ 'create_jaw_pose' : True,
40
+ 'create_leye_pose' : True,
41
+ 'create_reye_pose' : True,
42
+ 'create_transl' : True,
43
+ 'use_pca' : False,
44
+ 'flat_hand_mean' : True,
45
+ 'dtype' : torch.float32,
46
+ }
47
+
48
+ super().__init__(**body_model_params)
49
+ self = self.to(device)
50
+
51
+ def forward(self, **kwargs):
52
+ ''' Only all parameters are passed, the batch_size will be flexible adjusted. '''
53
+ assert 'global_orient' in kwargs, '`global_orient` is required for the forward pass.'
54
+ assert 'body_pose' in kwargs, '`body_pose` is required for the forward pass.'
55
+ B = kwargs['global_orient'].shape[0]
56
+ body_pose = kwargs['body_pose']
57
+
58
+ if 'left_hand_pose' not in kwargs:
59
+ kwargs['left_hand_pose'] = body_pose.new_zeros((B, 45))
60
+ get_logger().warning('`left_hand_pose` is not provided, but it\'s expected, set to zeros.')
61
+ if 'right_hand_pose' not in kwargs:
62
+ kwargs['right_hand_pose'] = body_pose.new_zeros((B, 45))
63
+ get_logger().warning('`left_hand_pose` is not provided, but it\'s expected, set to zeros.')
64
+ if 'transl' not in kwargs:
65
+ kwargs['transl'] = body_pose.new_zeros((B, 3))
66
+ if 'betas' not in kwargs:
67
+ kwargs['betas'] = body_pose.new_zeros((B, self.n_betas))
68
+ if 'expression' not in kwargs:
69
+ kwargs['expression'] = body_pose.new_zeros((B, 10))
70
+ if 'jaw_pose' not in kwargs:
71
+ kwargs['jaw_pose'] = body_pose.new_zeros((B, 3))
72
+ if 'leye_pose' not in kwargs:
73
+ kwargs['leye_pose'] = body_pose.new_zeros((B, 3))
74
+ if 'reye_pose' not in kwargs:
75
+ kwargs['reye_pose'] = body_pose.new_zeros((B, 3))
76
+
77
+ return super().forward(**kwargs)
lib/body_models/skel/__init__.py ADDED
File without changes
lib/body_models/skel/alignment/align_config.py ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """ Optimization config file. In 'optim_steps' we define each optimization steps.
2
+ Each step inherit and overwrite the parameters of the previous step."""
3
+
4
+ config = {
5
+ 'keepalive_meshviewer': False,
6
+ 'optim_steps':
7
+ [
8
+ {
9
+ 'description' : 'Adjust the root orientation and translation',
10
+ 'use_basic_loss': True,
11
+ 'lr': 1,
12
+ 'max_iter': 20,
13
+ 'num_steps': 10,
14
+ 'line_search_fn': 'strong_wolfe', #'strong_wolfe',
15
+ 'tolerance_change': 1e-7,# 1e-4, #0.01
16
+ 'mode' : 'root_only',
17
+
18
+ 'l_verts_loose': 300,
19
+ 'l_time_loss': 0,#5e2,
20
+
21
+ 'l_joint': 0.0,
22
+ 'l_verts': 0,
23
+ 'l_scapula_loss': 0.0,
24
+ 'l_spine_loss': 0.0,
25
+ 'l_pose_loss': 0.0,
26
+
27
+
28
+ },
29
+ # Adjust the upper limbs
30
+ {
31
+ 'description' : 'Adjust the upper limbs pose',
32
+ 'lr': 0.1,
33
+ 'max_iter': 20,
34
+ 'num_steps': 10,
35
+ 'tolerance_change': 1e-7,
36
+ 'mode' : 'fixed_upper_limbs', #'fixed_root',
37
+
38
+ 'l_verts_loose': 600,
39
+ 'l_joint': 1e3,
40
+ 'l_time_loss': 0,# 5e2,
41
+ 'l_pose_loss': 1e-4,
42
+ },
43
+ # Adjust the whole body
44
+ {
45
+ 'description' : 'Adjust the whole body pose with fixed root',
46
+ 'lr': 0.1,
47
+ 'max_iter': 20,
48
+ 'num_steps': 10,
49
+ 'tolerance_change': 1e-7,
50
+ 'mode' : 'fixed_root', #'fixed_root',
51
+
52
+ 'l_verts_loose': 600,
53
+ 'l_joint': 1e3,
54
+ 'l_time_loss': 0,
55
+ 'l_pose_loss': 1e-4,
56
+ },
57
+ #
58
+ {
59
+ 'description' : 'Free optimization',
60
+ 'lr': 0.1,
61
+ 'max_iter': 20,
62
+ 'num_steps': 10,
63
+ 'tolerance_change': 1e-7,
64
+ 'mode' : 'free', #'fixed_root',
65
+
66
+ 'l_verts_loose': 600,
67
+ 'l_joint': 1e3,
68
+ 'l_time_loss':0,
69
+ 'l_pose_loss': 1e-4,
70
+ },
71
+ ]
72
+ }
lib/body_models/skel/alignment/align_config_joint.py ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """ Optimization config file. In 'optim_steps' we define each optimization steps.
2
+ Each step inherit and overwrite the parameters of the previous step."""
3
+
4
+ config = {
5
+ 'keepalive_meshviewer': False,
6
+ 'optim_steps':
7
+ [
8
+ {
9
+ 'description' : 'Adjust the root orientation and translation',
10
+ 'use_basic_loss': True,
11
+ 'lr': 1,
12
+ 'max_iter': 20,
13
+ 'num_steps': 10,
14
+ 'line_search_fn': 'strong_wolfe', #'strong_wolfe',
15
+ 'tolerance_change': 1e-7,# 1e-4, #0.01
16
+ 'mode' : 'root_only',
17
+
18
+ 'l_verts_loose': 300,
19
+ 'l_time_loss': 0,#5e2,
20
+
21
+ 'l_joint': 0.0,
22
+ 'l_verts': 0,
23
+ 'l_scapula_loss': 0.0,
24
+ 'l_spine_loss': 0.0,
25
+ 'l_pose_loss': 0.0,
26
+
27
+
28
+ },
29
+ # Adjust the upper limbs
30
+ {
31
+ 'description' : 'Adjust the upper limbs pose',
32
+ 'lr': 0.1,
33
+ 'max_iter': 20,
34
+ 'num_steps': 10,
35
+ 'tolerance_change': 1e-7,
36
+ 'mode' : 'fixed_upper_limbs', #'fixed_root',
37
+
38
+ 'l_verts_loose': 600,
39
+ 'l_joint': 2e3,
40
+ 'l_time_loss': 0,# 5e2,
41
+ 'l_pose_loss': 1e-3,
42
+ },
43
+ # Adjust the whole body
44
+ {
45
+ 'description' : 'Adjust the whole body pose with fixed root',
46
+ 'lr': 0.1,
47
+ 'max_iter': 20,
48
+ 'num_steps': 10,
49
+ 'tolerance_change': 1e-7,
50
+ 'mode' : 'fixed_root', #'fixed_root',
51
+
52
+ 'l_verts_loose': 600,
53
+ 'l_joint': 1e3,
54
+ 'l_time_loss': 0,
55
+ 'l_pose_loss': 1e-4,
56
+ },
57
+ #
58
+ {
59
+ 'description' : 'Free optimization',
60
+ 'lr': 0.1,
61
+ 'max_iter': 20,
62
+ 'num_steps': 10,
63
+ 'tolerance_change': 1e-7,
64
+ 'mode' : 'free', #'fixed_root',
65
+
66
+ 'l_verts_loose': 300,
67
+ 'l_joint': 2e3,
68
+ 'l_time_loss':0,
69
+ 'l_pose_loss': 1e-4,
70
+ },
71
+ ]
72
+ }
lib/body_models/skel/alignment/aligner.py ADDED
@@ -0,0 +1,469 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ """
3
+ Copyright©2023 Max-Planck-Gesellschaft zur Förderung
4
+ der Wissenschaften e.V. (MPG). acting on behalf of its Max Planck Institute
5
+ for Intelligent Systems. All rights reserved.
6
+
7
+ Author: Soyong Shin, Marilyn Keller
8
+ See https://skel.is.tue.mpg.de/license.html for licensing and contact information.
9
+ """
10
+
11
+ import traceback
12
+ import math
13
+ import os
14
+ import pickle
15
+ import torch
16
+ import smplx
17
+ import omegaconf
18
+ import torch.nn.functional as F
19
+ from psbody.mesh import Mesh, MeshViewer, MeshViewers
20
+ from tqdm import trange
21
+ from pathlib import Path
22
+
23
+ import lib.body_models.skel.config as cg
24
+ from lib.body_models.skel.skel_model import SKEL
25
+ from .losses import compute_anchor_pose, compute_anchor_trans, compute_pose_loss, compute_scapula_loss, compute_spine_loss, compute_time_loss, pretty_loss_print
26
+ from .utils import location_to_spheres, to_numpy, to_params, to_torch
27
+ from .align_config import config
28
+ from .align_config_joint import config as config_joint
29
+
30
+ class SkelFitter(object):
31
+
32
+ def __init__(self, gender, device, num_betas=10, export_meshes=False, joint_optim=False) -> None:
33
+
34
+ self.smpl = smplx.create(cg.smpl_folder, model_type='smpl', gender=gender, num_betas=num_betas, batch_size=1, export_meshes=False).to(device)
35
+ self.skel = SKEL(gender).to(device)
36
+ self.gender = gender
37
+ self.device = device
38
+ self.num_betas = num_betas
39
+ # Instanciate masks used for the vertex to vertex fitting
40
+ fitting_mask_file = Path(__file__).parent / 'riggid_parts_mask.pkl'
41
+ fitting_indices = pickle.load(open(fitting_mask_file, 'rb'))
42
+ fitting_mask = torch.zeros(6890, dtype=torch.bool, device=self.device)
43
+ fitting_mask[fitting_indices] = 1
44
+ self.fitting_mask = fitting_mask.reshape(1, -1, 1).to(self.device) # 1xVx1 to be applied to verts that are BxVx3
45
+
46
+ smpl_torso_joints = [0,3]
47
+ verts_mask = (self.smpl.lbs_weights[:,smpl_torso_joints]>0.5).sum(dim=-1)>0
48
+ self.torso_verts_mask = verts_mask.unsqueeze(0).unsqueeze(-1) # Because verts are of shape BxVx3
49
+
50
+ self.export_meshes = export_meshes
51
+
52
+
53
+ # make the cfg being an object using omegaconf
54
+ if joint_optim:
55
+ self.cfg = omegaconf.OmegaConf.create(config_joint)
56
+ else:
57
+ self.cfg = omegaconf.OmegaConf.create(config)
58
+
59
+ # Instanciate the mesh viewer to visualize the fitting
60
+ if('DISABLE_VIEWER' in os.environ):
61
+ self.mv = None
62
+ print("\n DISABLE_VIEWER flag is set, running in headless mode")
63
+ else:
64
+ self.mv = MeshViewers((1,2), keepalive=self.cfg.keepalive_meshviewer)
65
+
66
+
67
+ def run_fit(self,
68
+ trans_in,
69
+ betas_in,
70
+ poses_in,
71
+ batch_size=20,
72
+ skel_data_init=None,
73
+ force_recompute=False,
74
+ debug=False,
75
+ watch_frame=0,
76
+ freevert_mesh=None,
77
+ opt_sequence=False,
78
+ fix_poses=False,
79
+ variant_exp=''):
80
+ """Align SKEL to a SMPL sequence."""
81
+
82
+ self.nb_frames = poses_in.shape[0]
83
+ self.watch_frame = watch_frame
84
+ self.is_skel_data_init = skel_data_init is not None
85
+ self.force_recompute = force_recompute
86
+
87
+ print('Fitting {} frames'.format(self.nb_frames))
88
+ print('Watching frame: {}'.format(watch_frame))
89
+
90
+ # Initialize SKEL torch params
91
+ body_params = self._init_params(betas_in, poses_in, trans_in, skel_data_init, variant_exp)
92
+
93
+ # We cut the whole sequence in batches for parallel optimization
94
+ if batch_size > self.nb_frames:
95
+ batch_size = self.nb_frames
96
+ print('Batch size is larger than the number of frames. Setting batch size to {}'.format(batch_size))
97
+
98
+ n_batch = math.ceil(self.nb_frames/batch_size)
99
+ pbar = trange(n_batch, desc='Running batch optimization')
100
+
101
+ # Initialize the res dict to store the per frame result skel parameters
102
+ out_keys = ['poses', 'betas', 'trans']
103
+ if self.export_meshes:
104
+ out_keys += ['skel_v', 'skin_v', 'smpl_v']
105
+ res_dict = {key: [] for key in out_keys}
106
+
107
+ res_dict['gender'] = self.gender
108
+ if self.export_meshes:
109
+ res_dict['skel_f'] = self.skel.skel_f.cpu().numpy().copy()
110
+ res_dict['skin_f'] = self.skel.skin_f.cpu().numpy().copy()
111
+ res_dict['smpl_f'] = self.smpl.faces
112
+
113
+ # Iterate over the batches to fit the whole sequence
114
+ for i in pbar:
115
+
116
+ if debug:
117
+ # Only run the first batch to test, ignore the rest
118
+ if i > 1:
119
+ continue
120
+
121
+ # Get batch start and end indices
122
+ i_start = i * batch_size
123
+ i_end = min((i+1) * batch_size, self.nb_frames)
124
+
125
+ # Fit the batch
126
+ betas, poses, trans, verts = self._fit_batch(body_params, i, i_start, i_end, enable_time=opt_sequence, fix_poses=fix_poses)
127
+ # if torch.isnan(betas).any() \
128
+ # or torch.isnan(poses).any() \
129
+ # or torch.isnan(trans).any():
130
+ # print(f'Nan values detected.')
131
+ # raise ValueError('Nan values detected in the output.')
132
+
133
+ # Store ethe results
134
+ res_dict['poses'].append(poses)
135
+ res_dict['betas'].append(betas)
136
+ res_dict['trans'].append(trans)
137
+ if self.export_meshes:
138
+ # Store the meshes vertices
139
+ skel_output = self.skel.forward(poses=poses, betas=betas, trans=trans, poses_type='skel', skelmesh=True)
140
+ res_dict['skel_v'].append(skel_output.skel_verts)
141
+ res_dict['skin_v'].append(skel_output.skin_verts)
142
+ res_dict['smpl_v'].append(verts)
143
+
144
+ if opt_sequence:
145
+ # Initialize the next frames with current frame
146
+ body_params['poses_skel'][i_end:] = poses[-1:].detach()
147
+ body_params['trans_skel'][i_end:] = trans[-1].detach()
148
+ body_params['betas_skel'][i_end:] = betas[-1:].detach()
149
+
150
+ # Concatenate the batches and convert to numpy
151
+ for key, val in res_dict.items():
152
+ if isinstance(val, list):
153
+ res_dict[key] = torch.cat(val, dim=0).detach().cpu().numpy()
154
+
155
+ return res_dict
156
+
157
+ def _init_params(self, betas_smpl, poses_smpl, trans_smpl, skel_data_init=None, variant_exp=''):
158
+ """ Return initial SKEL parameters from SMPL data dictionary and an optional SKEL data dictionary."""
159
+
160
+ if skel_data_init is None or self.force_recompute:
161
+
162
+ poses_skel = torch.zeros((self.nb_frames, self.skel.num_q_params), device=self.device)
163
+ if variant_exp == '' or variant_exp == '_official_old':
164
+ poses_skel[:, :3] = poses_smpl[:, :3] # Global orient are similar between SMPL and SKEL, so init with SMPL angles
165
+ elif variant_exp == '_official_fix':
166
+ # https://github.com/MarilynKeller/SKEL/commit/d1f6ff62235c142ba010158e00e21fd4fe25807f#diff-09188717a56a42e9589e9bd289f9ddb4fb53160e03c81a7ced70b3a84c1d9d0bR157
167
+ pass
168
+ elif variant_exp == '_my_fix':
169
+ gt_orient_aa = poses_smpl[:, :3]
170
+ # IMPORTANT: The alignment comes from `exp/inspect_skel/archive/orientation.py`.
171
+ from lib.utils.geometry.rotation import axis_angle_to_matrix, matrix_to_euler_angles
172
+ gt_orient_mat = axis_angle_to_matrix(gt_orient_aa)
173
+ gt_orient_ea = matrix_to_euler_angles(gt_orient_mat, 'YXZ')
174
+ flip = torch.tensor([-1, 1, 1], device=self.device)
175
+ poses_skel[:, :3] = gt_orient_ea[:, [2, 1, 0]] * flip
176
+ else:
177
+ raise ValueError(f'Unknown variant_exp {variant_exp}')
178
+
179
+ betas_skel = torch.zeros((self.nb_frames, 10), device=self.device)
180
+ betas_skel[:] = betas_smpl[..., :10]
181
+
182
+ trans_skel = trans_smpl # Translation is similar between SMPL and SKEL, so init with SMPL translation
183
+
184
+ else:
185
+ # Load from previous alignment
186
+ betas_skel = to_torch(skel_data_init['betas'], self.device)
187
+ poses_skel = to_torch(skel_data_init['poses'], self.device)
188
+ trans_skel = to_torch(skel_data_init['trans'], self.device)
189
+
190
+ # Make a dictionary out of the necessary body parameters
191
+ body_params = {
192
+ 'betas_skel': betas_skel,
193
+ 'poses_skel': poses_skel,
194
+ 'trans_skel': trans_skel,
195
+ 'betas_smpl': betas_smpl,
196
+ 'poses_smpl': poses_smpl,
197
+ 'trans_smpl': trans_smpl
198
+ }
199
+
200
+ return body_params
201
+
202
+
203
+ def _fit_batch(self, body_params, i, i_start, i_end, enable_time=False, fix_poses=False):
204
+ """ Create parameters for the batch and run the optimization."""
205
+
206
+ # Sample a batch ver
207
+ body_params = { key: val[i_start:i_end] for key, val in body_params.items()}
208
+
209
+ # SMPL params
210
+ betas_smpl = body_params['betas_smpl']
211
+ poses_smpl = body_params['poses_smpl']
212
+ trans_smpl = body_params['trans_smpl']
213
+
214
+ # SKEL params
215
+ betas = to_params(body_params['betas_skel'], device=self.device)
216
+ poses = to_params(body_params['poses_skel'], device=self.device)
217
+ trans = to_params(body_params['trans_skel'], device=self.device)
218
+
219
+ if 'verts' in body_params:
220
+ verts = body_params['verts']
221
+ else:
222
+ # Run a SMPL forward pass to get the SMPL body vertices
223
+ smpl_output = self.smpl(betas=betas_smpl, body_pose=poses_smpl[:,3:], transl=trans_smpl, global_orient=poses_smpl[:,:3])
224
+ verts = smpl_output.vertices
225
+
226
+ # Optimize
227
+ config = self.cfg.optim_steps
228
+ current_cfg = config[0]
229
+
230
+ # from lib.kits.debug import set_trace
231
+ # set_trace()
232
+
233
+ try:
234
+ if fix_poses:
235
+ # for ci, cfg in enumerate(config[1:]):
236
+ for ci, cfg in enumerate([config[-1]]): # To debug, only run the last step
237
+ current_cfg.update(cfg)
238
+ print(f'Step {ci+1}: {current_cfg.description}')
239
+ self._optim([trans,betas], poses, betas, trans, verts, current_cfg, enable_time)
240
+ else:
241
+ if not enable_time or not self.is_skel_data_init:
242
+ # Optimize the global rotation and translation for the initial fitting
243
+ print(f'Step 0: {current_cfg.description}')
244
+ self._optim([trans,poses], poses, betas, trans, verts, current_cfg, enable_time)
245
+
246
+ for ci, cfg in enumerate(config[1:]):
247
+ # for ci, cfg in enumerate([config[-1]]): # To debug, only run the last step
248
+ current_cfg.update(cfg)
249
+ print(f'Step {ci+1}: {current_cfg.description}')
250
+ self._optim([poses], poses, betas, trans, verts, current_cfg, enable_time)
251
+
252
+
253
+ # # Refine by optimizing the whole body
254
+ # cfg.update(self.cfg_optim[])
255
+ # cfg.update({'mode' : 'free', 'tolerance_change': 0.0001, 'l_joint': 0.2e4})
256
+ # self._optim([trans, poses], poses, betas, trans, verts, cfg)
257
+ except Exception as e:
258
+ print(e)
259
+ traceback.print_exc()
260
+ # from lib.kits.debug import set_trace
261
+ # set_trace()
262
+
263
+ return betas, poses, trans, verts
264
+
265
+ def _optim(self,
266
+ params,
267
+ poses,
268
+ betas,
269
+ trans,
270
+ verts,
271
+ cfg,
272
+ enable_time=False,
273
+ ):
274
+
275
+ # regress anatomical joints from SMPL's vertices
276
+ anat_joints = torch.einsum('bik,ji->bjk', [verts, self.skel.J_regressor_osim])
277
+ dJ=torch.zeros((poses.shape[0], 24, 3), device=betas.device)
278
+
279
+ # Create the optimizer
280
+ optimizer = torch.optim.LBFGS(params,
281
+ lr=cfg.lr,
282
+ max_iter=cfg.max_iter,
283
+ line_search_fn=cfg.line_search_fn,
284
+ tolerance_change=cfg.tolerance_change)
285
+
286
+ poses_init = poses.detach().clone()
287
+ trans_init = trans.detach().clone()
288
+
289
+ def closure():
290
+ optimizer.zero_grad()
291
+
292
+ # fi = self.watch_frame #frame of the batch to display
293
+ # output = self.skel.forward(poses=poses[fi:fi+1],
294
+ # betas=betas[fi:fi+1],
295
+ # trans=trans[fi:fi+1],
296
+ # poses_type='skel',
297
+ # dJ=dJ[fi:fi+1],
298
+ # skelmesh=True)
299
+
300
+ # self._fstep_plot(output, cfg, verts[fi:fi+1], anat_joints[fi:fi+1], )
301
+
302
+ loss_dict = self._fitting_loss(poses,
303
+ poses_init,
304
+ betas,
305
+ trans,
306
+ trans_init,
307
+ dJ,
308
+ anat_joints,
309
+ verts,
310
+ cfg,
311
+ enable_time)
312
+
313
+ # print(pretty_loss_print(loss_dict))
314
+
315
+ loss = sum(loss_dict.values())
316
+ loss.backward()
317
+
318
+ return loss
319
+
320
+ for step_i in range(cfg.num_steps):
321
+ loss = optimizer.step(closure).item()
322
+
323
+ def _get_masks(self, cfg):
324
+ pose_mask = torch.ones((self.skel.num_q_params)).to(self.device).unsqueeze(0)
325
+ verts_mask = torch.ones_like(self.fitting_mask)
326
+ joint_mask = torch.ones((self.skel.num_joints, 3)).to(self.device).unsqueeze(0).bool()
327
+
328
+ # Mask vertices
329
+ if cfg.mode=='root_only':
330
+ # Only optimize the global rotation of the body, i.e. the first 3 angles of the pose
331
+ pose_mask[:] = 0 # Only optimize for the global rotation
332
+ pose_mask[:,:3] = 1
333
+ # Only fit the thorax vertices to recover the proper body orientation and translation
334
+ verts_mask = self.torso_verts_mask
335
+
336
+ elif cfg.mode=='fixed_upper_limbs':
337
+ upper_limbs_joints = [0,1,2,3,6,9,12,15,17]
338
+ verts_mask = (self.smpl.lbs_weights[:,upper_limbs_joints]>0.5).sum(dim=-1)>0
339
+ verts_mask = verts_mask.unsqueeze(0).unsqueeze(-1)
340
+
341
+ joint_mask[:, [3,4,5,8,9,10,18,23], :] = 0 # Do not try to match the joints of the upper limbs
342
+
343
+ pose_mask[:] = 1
344
+ pose_mask[:,:3] = 0 # Block the global rotation
345
+ pose_mask[:,19] = 0 # block the lumbar twist
346
+ # pose_mask[:, 36:39] = 0
347
+ # pose_mask[:, 43:46] = 0
348
+ # pose_mask[:, 62:65] = 0
349
+ # pose_mask[:, 62:65] = 0
350
+
351
+ elif cfg.mode=='fixed_root':
352
+ pose_mask[:] = 1
353
+ pose_mask[:,:3] = 0 # Block the global rotation
354
+ # pose_mask[:,19] = 0 # block the lumbar twist
355
+
356
+ # The orientation of the upper limbs is often wrong in SMPL so ignore these vertices for the finale step
357
+ upper_limbs_joints = [1,2,16,17]
358
+ verts_mask = (self.smpl.lbs_weights[:,upper_limbs_joints]>0.5).sum(dim=-1)>0
359
+ verts_mask = torch.logical_not(verts_mask)
360
+ verts_mask = verts_mask.unsqueeze(0).unsqueeze(-1)
361
+
362
+ elif cfg.mode=='free':
363
+ verts_mask = torch.ones_like(self.fitting_mask )
364
+
365
+ joint_mask[:]=0
366
+ joint_mask[:, [19,14], :] = 1 # Only fir the scapula join to avoid collapsing shoulders
367
+
368
+ else:
369
+ raise ValueError(f'Unknown mode {cfg.mode}')
370
+
371
+ return pose_mask, verts_mask, joint_mask
372
+
373
+ def _fitting_loss(self,
374
+ poses,
375
+ poses_init,
376
+ betas,
377
+ trans,
378
+ trans_init,
379
+ dJ,
380
+ anat_joints,
381
+ verts,
382
+ cfg,
383
+ enable_time=False):
384
+
385
+ loss_dict = {}
386
+
387
+
388
+ pose_mask, verts_mask, joint_mask = self._get_masks(cfg)
389
+ poses = poses * pose_mask + poses_init * (1-pose_mask)
390
+
391
+ # Mask joints to not optimize before computing the losses
392
+
393
+ output = self.skel.forward(poses=poses, betas=betas, trans=trans, poses_type='skel', dJ=dJ, skelmesh=False)
394
+
395
+ # Fit the SMPL vertices
396
+ # We know the skinning of the forearm and the neck are not perfect,
397
+ # so we create a mask of the SMPL vertices that are important to fit, like the hands and the head
398
+ loss_dict['verts_loss_loose'] = cfg.l_verts_loose * (verts_mask * (output.skin_verts - verts)**2).sum() / (((verts_mask).sum()*self.nb_frames))
399
+
400
+ # Fit the regressed joints, this avoids collapsing shoulders
401
+ # loss_dict['joint_loss'] = cfg.l_joint * F.mse_loss(output.joints, anat_joints)
402
+ loss_dict['joint_loss'] = cfg.l_joint * (joint_mask * (output.joints - anat_joints)**2).mean()
403
+
404
+ # Time consistancy
405
+ if poses.shape[0] > 1 and enable_time:
406
+ # This avoids unstable hips orientationZ
407
+ loss_dict['time_loss'] = cfg.l_time_loss * F.mse_loss(poses[1:], poses[:-1])
408
+
409
+ loss_dict['pose_loss'] = cfg.l_pose_loss * compute_pose_loss(poses, poses_init)
410
+
411
+ if cfg.use_basic_loss is False:
412
+ # These losses can be used to regularize the optimization but are not always necessary
413
+ loss_dict['anch_rot'] = cfg.l_anch_pose * compute_anchor_pose(poses, poses_init)
414
+ loss_dict['anch_trans'] = cfg.l_anch_trans * compute_anchor_trans(trans, trans_init)
415
+
416
+ loss_dict['verts_loss'] = cfg.l_verts * (verts_mask * self.fitting_mask * (output.skin_verts - verts)**2).sum() / (self.fitting_mask*verts_mask).sum()
417
+
418
+ # Regularize the pose
419
+ loss_dict['scapula_loss'] = cfg.l_scapula_loss * compute_scapula_loss(poses)
420
+ loss_dict['spine_loss'] = cfg.l_spine_loss * compute_spine_loss(poses)
421
+
422
+ # Adjust the losses of all the pose regularizations sub losses with the pose_reg_factor value
423
+ for key in ['scapula_loss', 'spine_loss', 'pose_loss']:
424
+ loss_dict[key] = cfg.pose_reg_factor * loss_dict[key]
425
+
426
+ return loss_dict
427
+
428
+ def _fstep_plot(self, output, cfg, verts, anat_joints):
429
+ "Function to plot each step"
430
+
431
+ if('DISABLE_VIEWER' in os.environ):
432
+ return
433
+
434
+ pose_mask, verts_mask, joint_mask = self._get_masks(cfg)
435
+
436
+ skin_err_value = ((output.skin_verts[0] - verts[0])**2).sum(dim=-1).sqrt()
437
+ skin_err_value = skin_err_value / 0.05
438
+ skin_err_value = to_numpy(skin_err_value)
439
+
440
+ skin_mesh = Mesh(v=to_numpy(output.skin_verts[0]), f=[], vc='white')
441
+ skel_mesh = Mesh(v=to_numpy(output.skel_verts[0]), f=self.skel.skel_f.cpu().numpy(), vc='white')
442
+
443
+ # Display vertex distance on SMPL
444
+ smpl_verts = to_numpy(verts[0])
445
+ smpl_mesh = Mesh(v=smpl_verts, f=self.smpl.faces)
446
+ smpl_mesh.set_vertex_colors_from_weights(skin_err_value, scale_to_range_1=False)
447
+
448
+ smpl_mesh_masked = Mesh(v=smpl_verts[to_numpy(verts_mask[0,:,0])], f=[], vc='green')
449
+ smpl_mesh_pc = Mesh(v=smpl_verts, f=[], vc='green')
450
+
451
+ skin_mesh_err = Mesh(v=to_numpy(output.skin_verts[0]), f=self.skel.skin_f.cpu().numpy(), vc='white')
452
+ skin_mesh_err.set_vertex_colors_from_weights(skin_err_value, scale_to_range_1=False)
453
+ # List the meshes to display
454
+ meshes_left = [skin_mesh_err, smpl_mesh_pc]
455
+ meshes_right = [smpl_mesh_masked, skin_mesh, skel_mesh]
456
+
457
+ if cfg.l_joint > 0:
458
+ # Plot the joints
459
+ meshes_right += location_to_spheres(to_numpy(output.joints[joint_mask[:,:,0]]), color=(1,0,0), radius=0.02)
460
+ meshes_right += location_to_spheres(to_numpy(anat_joints[joint_mask[:,:,0]]), color=(0,1,0), radius=0.02) \
461
+
462
+
463
+ self.mv[0][0].set_dynamic_meshes(meshes_left)
464
+ self.mv[0][1].set_dynamic_meshes(meshes_right)
465
+
466
+ # print(poses[frame_to_watch, :3])
467
+ # print(trans[frame_to_watch])
468
+ # print(betas[frame_to_watch, :3])
469
+ # mv.get_keypress()
lib/body_models/skel/alignment/losses.py ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+
3
+ def compute_scapula_loss(poses):
4
+
5
+ scapula_indices = [26, 27, 28, 36, 37, 38]
6
+
7
+ scapula_poses = poses[:, scapula_indices]
8
+ scapula_loss = torch.linalg.norm(scapula_poses, ord=2)
9
+ return scapula_loss
10
+
11
+ def compute_spine_loss(poses):
12
+
13
+ spine_indices = range(17, 25)
14
+
15
+ spine_poses = poses[:, spine_indices]
16
+ spine_loss = torch.linalg.norm(spine_poses, ord=2)
17
+ return spine_loss
18
+
19
+ def compute_pose_loss(poses, pose_init):
20
+
21
+ pose_loss = torch.linalg.norm(poses[:, 3:], ord=2) # The global rotation should not be constrained
22
+ return pose_loss
23
+
24
+ def compute_anchor_pose(poses, pose_init):
25
+
26
+ pose_loss = torch.nn.functional.mse_loss(poses[:, :3], pose_init[:, :3])
27
+ return pose_loss
28
+
29
+ def compute_anchor_trans(trans, trans_init):
30
+
31
+ trans_loss = torch.nn.functional.mse_loss(trans, trans_init)
32
+ return trans_loss
33
+
34
+ def compute_time_loss(poses):
35
+
36
+ pose_delta = poses[1:] - poses[:-1]
37
+ time_loss = torch.linalg.norm(pose_delta, ord=2)
38
+ return time_loss
39
+
40
+ def pretty_loss_print(loss_dict):
41
+ # Pretty print the loss on the form loss val | loss1 val1 | loss2 val2
42
+ # Start with the total loss
43
+ loss = sum(loss_dict.values())
44
+ pretty_loss = f'{loss:.4f}'
45
+ for key, val in loss_dict.items():
46
+ pretty_loss += f' | {key} {val:.4f}'
47
+ return pretty_loss
lib/body_models/skel/alignment/riggid_parts_mask.pkl ADDED
Binary file (11.9 kB). View file
 
lib/body_models/skel/alignment/utils.py ADDED
@@ -0,0 +1,119 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import os
3
+ import pickle
4
+ import torch
5
+ import numpy as np
6
+ from psbody.mesh.sphere import Sphere
7
+
8
+ # to_params = lambda x: torch.from_numpy(x).float().to(self.device).requires_grad_(True)
9
+ # to_torch = lambda x: torch.from_numpy(x).float().to(self.device)
10
+
11
+ def to_params(x, device):
12
+ return x.to(device).requires_grad_(True)
13
+
14
+ def to_torch(x, device):
15
+ return torch.from_numpy(x).float().to(device)
16
+
17
+ def to_numpy(x):
18
+ return x.detach().cpu().numpy()
19
+
20
+ def load_smpl_seq(smpl_seq_path, gender=None, straighten_hands=False):
21
+
22
+ if not os.path.exists(smpl_seq_path):
23
+ raise Exception('Path does not exist: {}'.format(smpl_seq_path))
24
+
25
+ if smpl_seq_path.endswith('.pkl'):
26
+ data_dict = pickle.load(open(smpl_seq_path, 'rb'))
27
+
28
+ elif smpl_seq_path.endswith('.npz'):
29
+ data_dict = np.load(smpl_seq_path, allow_pickle=True)
30
+
31
+ if data_dict.files == ['pred_smpl_parms', 'verts', 'pred_cam_t']:
32
+ data_dict = data_dict['pred_smpl_parms'].item()# ['global_orient', 'body_pose', 'body_pose_axis_angle', 'global_orient_axis_angle', 'betas']
33
+ else:
34
+ data_dict = {key: data_dict[key] for key in data_dict.keys()} # convert to python dict
35
+ else:
36
+ raise Exception('Unknown file format: {}. Supported formats are .pkl and .npz'.format(smpl_seq_path))
37
+
38
+ # Instanciate a dictionary with the keys expected by the fitter
39
+ data_fixed = {}
40
+
41
+ # Get gender
42
+ if 'gender' not in data_dict:
43
+ assert gender is not None, f"The provided SMPL data dictionary does not contain gender, you need to pass it in command line"
44
+ data_fixed['gender'] = gender
45
+ elif not isinstance(data_dict['gender'], str):
46
+ # In some npz, the gender type happens to be: array('male', dtype='<U4'). So we convert it to string
47
+ data_fixed['gender'] = str(data_dict['gender'])
48
+ else:
49
+ data_fixed['gender'] = gender
50
+
51
+ # convert tensors to numpy arrays
52
+ for key, val in data_dict.items():
53
+ if isinstance(val, torch.Tensor):
54
+ data_dict[key] = val.detach().cpu().numpy()
55
+
56
+ # Get the SMPL pose
57
+ if 'poses' in data_dict:
58
+ poses = data_dict['poses']
59
+ elif 'body_pose_axis_angle' in data_dict and 'global_orient_axis_angle' in data_dict:
60
+ # assert 'global_orient' in data_dict and 'body_pose' in data_dict, f"Could not find poses in {smpl_seq_path}. Available keys: {data_dict.keys()})"
61
+ poses = np.concatenate([data_dict['global_orient_axis_angle'], data_dict['body_pose_axis_angle']], axis=1)
62
+ poses = poses.reshape(-1, 72)
63
+ elif 'body_pose' in data_dict and 'global_orient' in data_dict:
64
+ poses = np.concatenate([data_dict['global_orient_axis_angle'], data_dict['body_pose_axis_angle']], axis=-1)
65
+ else:
66
+ raise Exception(f"Could not find poses in {smpl_seq_path}. Available keys: {data_dict.keys()})")
67
+
68
+ if poses.shape[1] == 156:
69
+ # Those are SMPL+H poses, we remove the hand poses to keep only the body poses
70
+ smpl_poses = np.zeros((poses.shape[0], 72))
71
+ smpl_poses[:, :72-2*3] = poses[:, :72-2*3] # We leave params for SMPL joints 22 and 23 to zero as these DOF are not present in SMPLH
72
+ poses = smpl_poses
73
+
74
+ # Set SMPL joints 22 and 23 to zero as SKEL has rigid hands
75
+ if straighten_hands:
76
+ poses[:, 72-2*3:] = 0
77
+
78
+ data_fixed['poses'] = poses
79
+
80
+ # Translation
81
+ if 'trans' not in data_dict:
82
+ data_fixed['trans'] = np.zeros((poses.shape[0], 3))
83
+ else:
84
+ data_fixed['trans'] = data_dict['trans']
85
+
86
+ # Get betas
87
+ betas = data_dict['betas'][..., :10] # Keep only the 10 first betas
88
+ if len(betas.shape) == 1 and len(poses.shape) == 2:
89
+ betas = betas[None, :] # Add a batch dimension
90
+ data_fixed['betas'] = betas
91
+
92
+ for key in ['trans', 'poses', 'betas', 'gender']:
93
+ assert key in data_fixed.keys(), f'Could not find {key} in {smpl_seq_path}. Available keys: {data_fixed.keys()})'
94
+
95
+ out_dict = {}
96
+ out_dict['trans'] = data_fixed['trans']
97
+ out_dict['poses'] = data_fixed['poses']
98
+ out_dict['betas'] = data_fixed['betas']
99
+ out_dict['gender'] = data_fixed['gender']
100
+
101
+ return out_dict
102
+
103
+
104
+ def location_to_spheres(loc, color=(1,0,0), radius=0.02):
105
+ """Given an array of 3D points, return a list of spheres located at those positions.
106
+
107
+ Args:
108
+ loc (numpy.array): Nx3 array giving 3D positions
109
+ color (tuple, optional): One RGB float color vector to color the spheres. Defaults to (1,0,0).
110
+ radius (float, optional): Radius of the spheres in meters. Defaults to 0.02.
111
+
112
+ Returns:
113
+ list: List of spheres Mesh
114
+ """
115
+
116
+ cL = [Sphere(np.asarray([loc[i, 0], loc[i, 1], loc[i, 2]]), radius).to_mesh() for i in range(loc.shape[0])]
117
+ for spL in cL:
118
+ spL.set_vertex_colors(np.array(color))
119
+ return cL
lib/body_models/skel/config.py ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ skel_folder = '/u/yanxia/workspace/data/body_models/skel_models_v1.1'
2
+ smpl_folder = '/data/yanxia/code/SKEL/models/'
lib/body_models/skel/fit_osim/Fitting_SKEL_to_osim.md ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Fitting SKEL to OpenSim
2
+
3
+ Here we explain how to fit the SKEL model to an OpenSim model sequence to get a surface mesh. We do not provide fitting code yet but if you are interested in this direction, this folder should give you a good starting point.
4
+
5
+ As an example, we will use the OpenSim model from https://simtk.org/projects/full_body.
6
+ Download the model and sample data from (this page)[https://simtk.org/projects/full_body]. You should get the zip folder: `FullBodyModelwSampleSim.-latest.zip` and unzip it. This folder contains a .osim OpenSim model and example .mot motion sequences.
7
+
8
+ You can visualize the sequence with:
9
+
10
+ ```bash
11
+ python examples/load_osim.py --osim /path/to/FullBodyModelwSampleSim.-latest/ModelWithSampleSimulations-4.0/SimulationDataAndSetupFiles-4.0/Rajagopal2015.osim --mot /path/to/FullBodyModelwSampleSim.-latest/ModelWithSampleSimulations-4.0/SimulationDataAndSetupFiles-4.0/IK/results_run/ik_output_run.mot
12
+ ```
13
+
14
+ To fit SKEL to this sequence, we need to map each joint of this OpenSim model to a joint of the SKEL model. For the current model, the mapping is in `SKEL/skel/fit_osim/mappings/full_body.yaml`. If you use another model, you will need to create your own mapping yaml for that model.
15
+
16
+ The following script will then show you how to get the needed joints trajectories to fit SKEL to this OpenSim sequence:
17
+
18
+ ```
19
+ python SKEL/skel/fit_osim/osim_fitter.py
20
+ ```
lib/body_models/skel/fit_osim/mappings/full_body.yaml ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Mapping to SKEL
2
+
3
+ joints_mapping: # For each joint of the OpenSim model, specify the corresponding joint in SKEL
4
+ pelvis : pelvis
5
+ femur_r : femur_r
6
+ tibia_r : tibia_r
7
+ talus_r : talus_r
8
+ calcn_r : calcn_r
9
+ toes_r : toes_r
10
+ femur_l : femur_l
11
+ tibia_l : tibia_l
12
+ talus_l : talus_l
13
+ calcn_l : calcn_l
14
+ toes_l : toes_l
15
+ torso : thorax # This changes
16
+ humerus_r : humerus_r
17
+ ulna_r : ulna_r
18
+ radius_r : radius_r
19
+ hand_r : hand_r
20
+ humerus_l : humerus_l
21
+ ulna_l : ulna_l
22
+ radius_l : radius_l
23
+ hand_l : hand_l
24
+
25
+ markers_mapping: #Given specific markers of the OpenSim model, specify the corresponding vertex index on SMPL/SKEL skin
26
+ RFAradius : 5573 #RWRA
27
+ RFAulna : 5568 #RWRB
28
+ LFAradius : 2112 #LWRA
29
+ LFAulna : 2108 #LWRB