smplfitter 0.2.10__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. smplfitter-0.2.10/.github/workflows/python-publish.yml +35 -0
  2. smplfitter-0.2.10/.gitignore +131 -0
  3. smplfitter-0.2.10/.readthedocs.yaml +13 -0
  4. smplfitter-0.2.10/LICENSE +21 -0
  5. smplfitter-0.2.10/PKG-INFO +265 -0
  6. smplfitter-0.2.10/README.md +206 -0
  7. smplfitter-0.2.10/docs/_static/images/example.gif +0 -0
  8. smplfitter-0.2.10/docs/_static/images/image.png +0 -0
  9. smplfitter-0.2.10/docs/_static/images/image_example.jpg +0 -0
  10. smplfitter-0.2.10/docs/_static/images/on_image.png +0 -0
  11. smplfitter-0.2.10/docs/_static/images/on_image_fit.png +0 -0
  12. smplfitter-0.2.10/docs/_static/images/overlay_after_rot1.png +0 -0
  13. smplfitter-0.2.10/docs/_static/images/overlay_after_rot1_shape.png +0 -0
  14. smplfitter-0.2.10/docs/_static/images/overlay_after_rot2.png +0 -0
  15. smplfitter-0.2.10/docs/_static/images/overlay_after_rot2_shape.png +0 -0
  16. smplfitter-0.2.10/docs/_static/images/overlay_after_rot3.png +0 -0
  17. smplfitter-0.2.10/docs/_static/images/parts_overlay1.png +0 -0
  18. smplfitter-0.2.10/docs/_static/images/parts_overlay1_rot.png +0 -0
  19. smplfitter-0.2.10/docs/_static/images/parts_overlay2.png +0 -0
  20. smplfitter-0.2.10/docs/_static/images/parts_overlay2_rot.png +0 -0
  21. smplfitter-0.2.10/docs/_static/images/pose3d.png +0 -0
  22. smplfitter-0.2.10/docs/_static/images/pose3d_parts_color.png +0 -0
  23. smplfitter-0.2.10/docs/_static/images/pose3d_parts_color_blown.png +0 -0
  24. smplfitter-0.2.10/docs/_static/images/tmean_parts.png +0 -0
  25. smplfitter-0.2.10/docs/_static/images/tmean_parts_blown.png +0 -0
  26. smplfitter-0.2.10/docs/_static/styles/my_theme.css +76 -0
  27. smplfitter-0.2.10/docs/_templates/autoapi/index.rst +9 -0
  28. smplfitter-0.2.10/docs/_templates/autoapi/python/attribute.rst +1 -0
  29. smplfitter-0.2.10/docs/_templates/autoapi/python/class.rst +150 -0
  30. smplfitter-0.2.10/docs/_templates/autoapi/python/data.rst +42 -0
  31. smplfitter-0.2.10/docs/_templates/autoapi/python/exception.rst +1 -0
  32. smplfitter-0.2.10/docs/_templates/autoapi/python/function.rst +25 -0
  33. smplfitter-0.2.10/docs/_templates/autoapi/python/method.rst +25 -0
  34. smplfitter-0.2.10/docs/_templates/autoapi/python/module.rst +160 -0
  35. smplfitter-0.2.10/docs/_templates/autoapi/python/package.rst +1 -0
  36. smplfitter-0.2.10/docs/_templates/autoapi/python/property.rst +25 -0
  37. smplfitter-0.2.10/docs/_templates/copyright.html +12 -0
  38. smplfitter-0.2.10/docs/abbrev_long.bib +207 -0
  39. smplfitter-0.2.10/docs/conf.py +208 -0
  40. smplfitter-0.2.10/docs/index.rst +132 -0
  41. smplfitter-0.2.10/docs/modules.rst +10 -0
  42. smplfitter-0.2.10/docs/references.bib +62 -0
  43. smplfitter-0.2.10/pyproject.toml +69 -0
  44. smplfitter-0.2.10/setup.cfg +4 -0
  45. smplfitter-0.2.10/src/smplfitter/__init__.py +7 -0
  46. smplfitter-0.2.10/src/smplfitter/_version.py +21 -0
  47. smplfitter-0.2.10/src/smplfitter/common.py +166 -0
  48. smplfitter-0.2.10/src/smplfitter/decimation/__init__.py +0 -0
  49. smplfitter-0.2.10/src/smplfitter/decimation/decimate_body_models.py +54 -0
  50. smplfitter-0.2.10/src/smplfitter/decimation/make_post_lbs_joint_regressors.py +289 -0
  51. smplfitter-0.2.10/src/smplfitter/decimation/make_post_lbs_joint_regressors_tf.py +269 -0
  52. smplfitter-0.2.10/src/smplfitter/np/__init__.py +20 -0
  53. smplfitter-0.2.10/src/smplfitter/np/bodyfitter.py +625 -0
  54. smplfitter-0.2.10/src/smplfitter/np/bodymodel.py +279 -0
  55. smplfitter-0.2.10/src/smplfitter/np/lstsq.py +62 -0
  56. smplfitter-0.2.10/src/smplfitter/np/rotation.py +77 -0
  57. smplfitter-0.2.10/src/smplfitter/np/util.py +17 -0
  58. smplfitter-0.2.10/src/smplfitter/pt/__init__.py +94 -0
  59. smplfitter-0.2.10/src/smplfitter/pt/bodyconverter.py +168 -0
  60. smplfitter-0.2.10/src/smplfitter/pt/bodyfitter.py +1087 -0
  61. smplfitter-0.2.10/src/smplfitter/pt/bodyflipper.py +169 -0
  62. smplfitter-0.2.10/src/smplfitter/pt/bodymodel.py +387 -0
  63. smplfitter-0.2.10/src/smplfitter/pt/handreplacer.py +78 -0
  64. smplfitter-0.2.10/src/smplfitter/pt/lstsq.py +88 -0
  65. smplfitter-0.2.10/src/smplfitter/pt/rotation.py +66 -0
  66. smplfitter-0.2.10/src/smplfitter/tf/__init__.py +107 -0
  67. smplfitter-0.2.10/src/smplfitter/tf/bodyfitter.py +655 -0
  68. smplfitter-0.2.10/src/smplfitter/tf/bodymodel.py +303 -0
  69. smplfitter-0.2.10/src/smplfitter/tf/lstsq.py +67 -0
  70. smplfitter-0.2.10/src/smplfitter/tf/rotation.py +65 -0
  71. smplfitter-0.2.10/src/smplfitter/tf/util.py +11 -0
  72. smplfitter-0.2.10/src/smplfitter.egg-info/PKG-INFO +265 -0
  73. smplfitter-0.2.10/src/smplfitter.egg-info/SOURCES.txt +83 -0
  74. smplfitter-0.2.10/src/smplfitter.egg-info/dependency_links.txt +1 -0
  75. smplfitter-0.2.10/src/smplfitter.egg-info/requires.txt +26 -0
  76. smplfitter-0.2.10/src/smplfitter.egg-info/top_level.txt +1 -0
  77. smplfitter-0.2.10/tests/np/__init__.py +0 -0
  78. smplfitter-0.2.10/tests/np/test_bodymodel.py +85 -0
  79. smplfitter-0.2.10/tests/np/test_fitter.py +29 -0
  80. smplfitter-0.2.10/tests/pt/__init__.py +0 -0
  81. smplfitter-0.2.10/tests/pt/test_bodymodel.py +78 -0
  82. smplfitter-0.2.10/tests/pt/test_converter.py +38 -0
  83. smplfitter-0.2.10/tests/pt/test_fitter.py +36 -0
  84. smplfitter-0.2.10/tests/pt/test_flipper.py +36 -0
  85. smplfitter-0.2.10/tests/test_forward.py +152 -0
@@ -0,0 +1,35 @@
1
+ name: Upload Python Package
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+
7
+ permissions:
8
+ contents: read
9
+
10
+ jobs:
11
+ pypi-publish:
12
+ name: Upload release to PyPI
13
+ runs-on: ubuntu-latest
14
+ environment: pypi
15
+ permissions:
16
+ id-token: write
17
+ steps:
18
+ - name: Check out repository
19
+ uses: actions/checkout@v4
20
+ with:
21
+ fetch-depth: 0
22
+
23
+ - name: Set up Python
24
+ uses: actions/setup-python@v5
25
+ with:
26
+ python-version: "3.x"
27
+
28
+ - name: Install build dependencies
29
+ run: python -m pip install --upgrade build
30
+
31
+ - name: Build package distribution
32
+ run: python -m build --sdist
33
+
34
+ - name: Publish package distributions to PyPI
35
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,131 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ pip-wheel-metadata/
24
+ share/python-wheels/
25
+ *.egg-info/
26
+ .installed.cfg
27
+ *.egg
28
+ MANIFEST
29
+
30
+ # PyInstaller
31
+ # Usually these files are written by a python script from a template
32
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
33
+ *.manifest
34
+ *.spec
35
+
36
+ # Installer logs
37
+ pip-log.txt
38
+ pip-delete-this-directory.txt
39
+
40
+ # Unit test / coverage reports
41
+ htmlcov/
42
+ .tox/
43
+ .nox/
44
+ .coverage
45
+ .coverage.*
46
+ .cache
47
+ nosetests.xml
48
+ coverage.xml
49
+ *.cover
50
+ *.py,cover
51
+ .hypothesis/
52
+ .pytest_cache/
53
+
54
+ # Translations
55
+ *.mo
56
+ *.pot
57
+
58
+ # Django stuff:
59
+ *.log
60
+ local_settings.py
61
+ db.sqlite3
62
+ db.sqlite3-journal
63
+
64
+ # Flask stuff:
65
+ instance/
66
+ .webassets-cache
67
+
68
+ # Scrapy stuff:
69
+ .scrapy
70
+
71
+ # Sphinx documentation
72
+ docs_/_build/
73
+
74
+ # PyBuilder
75
+ target/
76
+
77
+ # Jupyter Notebook
78
+ .ipynb_checkpoints
79
+
80
+ # IPython
81
+ profile_default/
82
+ ipython_config.py
83
+
84
+ # pyenv
85
+ .python-version
86
+
87
+ # pipenv
88
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
89
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
90
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
91
+ # install all needed dependencies.
92
+ #Pipfile.lock
93
+
94
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow
95
+ __pypackages__/
96
+
97
+ # Celery stuff
98
+ celerybeat-schedule
99
+ celerybeat.pid
100
+
101
+ # SageMath parsed files
102
+ *.sage.py
103
+
104
+ # Environments
105
+ .env
106
+ .venv
107
+ env/
108
+ venv/
109
+ ENV/
110
+ env.bak/
111
+ venv.bak/
112
+
113
+ # Spyder project settings
114
+ .spyderproject
115
+ .spyproject
116
+
117
+ # Rope project settings
118
+ .ropeproject
119
+
120
+ # mkdocs documentation
121
+ /site
122
+
123
+ # mypy
124
+ .mypy_cache/
125
+ .dmypy.json
126
+ dmypy.json
127
+
128
+ # Pyre type checker
129
+ .pyre/
130
+
131
+ docs/_build/
@@ -0,0 +1,13 @@
1
+ version: 2
2
+
3
+ build:
4
+ os: ubuntu-24.04
5
+ tools:
6
+ python: "3.10"
7
+ commands:
8
+ - python -m pip install .
9
+ - python -m pip install --no-cache-dir -r docs/requirements.txt
10
+ - python -m sphinx -E -b html docs $READTHEDOCS_OUTPUT/html
11
+
12
+ sphinx:
13
+ configuration: docs/conf.py
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 István Sárándi
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.
@@ -0,0 +1,265 @@
1
+ Metadata-Version: 2.4
2
+ Name: smplfitter
3
+ Version: 0.2.10
4
+ Summary: Fast inverse kinematics and shape fitting for SMPL-family models in NumPy, PyTorch, and TensorFlow
5
+ Author-email: István Sárándi <istvan.sarandi@uni-tuebingen.de>
6
+ License: MIT License
7
+
8
+ Copyright (c) 2024 István Sárándi
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
27
+
28
+ Project-URL: Homepage, https://github.com/isarandi/smplfitter
29
+ Project-URL: Repository, https://github.com/isarandi/smplfitter
30
+ Project-URL: Issues, https://github.com/isarandi/smplfitter/issues
31
+ Project-URL: Author, https://istvansarandi.com
32
+ Requires-Python: >=3.6
33
+ Description-Content-Type: text/markdown
34
+ License-File: LICENSE
35
+ Requires-Dist: numpy
36
+ Requires-Dist: scipy
37
+ Requires-Dist: chumpy
38
+ Provides-Extra: tensorflow
39
+ Requires-Dist: tensorflow; extra == "tensorflow"
40
+ Provides-Extra: pytorch
41
+ Requires-Dist: torch; extra == "pytorch"
42
+ Provides-Extra: keywords
43
+ Requires-Dist: smpl; extra == "keywords"
44
+ Requires-Dist: smplx; extra == "keywords"
45
+ Requires-Dist: smplh; extra == "keywords"
46
+ Requires-Dist: 3d-human; extra == "keywords"
47
+ Requires-Dist: body-model; extra == "keywords"
48
+ Requires-Dist: ik; extra == "keywords"
49
+ Requires-Dist: inverse-kinematics; extra == "keywords"
50
+ Requires-Dist: parametric-model; extra == "keywords"
51
+ Requires-Dist: pose-estimation; extra == "keywords"
52
+ Requires-Dist: differentiable; extra == "keywords"
53
+ Requires-Dist: gpu; extra == "keywords"
54
+ Requires-Dist: tensorflow; extra == "keywords"
55
+ Requires-Dist: pytorch; extra == "keywords"
56
+ Requires-Dist: numpy; extra == "keywords"
57
+ Requires-Dist: batch-processing; extra == "keywords"
58
+ Dynamic: license-file
59
+
60
+ # SMPLFitter: The Fast Way From Vertices to Parametric 3D Humans
61
+
62
+ <img src="docs/_static/images/example.gif" alt="on_image" width="500"/>
63
+
64
+ This repository contains code for efficiently fitting parametric SMPL/SMPL+H/SMPL-X human body models to nonparametric 3D vertex and joint locations. The input needs to be in correspondence with the body template - this code does not handle unordered input point clouds.
65
+
66
+ Example use cases:
67
+ * You extracted nonparametric vertex and joint estimates from an RGB image, e.g. using [Neural Localizer Fields (NLF)](https://virtualhumans.mpi-inf.mpg.de/nlf), and want to express this estimate in parametric form, for example to feed it to another model that expects body model parameters.
68
+ * You want to convert between body models. For example, you have SMPL parameters from some dataset but need SMPL-X parameters as input to some pretrained model (or vice versa).
69
+
70
+ We provide the implementation in **PyTorch, TensorFlow and NumPy**.
71
+
72
+ The algorithm is **fast**, optimized for **batch** processing, can run on the **GPU** and is **differentiable**. There are no learnable parameters here, nor sensitivity to initialization. Just solving equation systems.
73
+
74
+ It can fit a batch of 4096 instances in 423 ms on a single RTX 3090 GPU giving a throughput of **9481 fits per second**. At the small batch size regime (batch size 32), the throughput is still 1839 fits/second. When using a subset of 1024 vertices (which still allows high-quality fits), one can fit a batch of 16384 instances in 440 ms. For 25 fps videos, this means you can fit SMPL params to every frame of 10 minutes of nonparametric motion data in less than half a second.
75
+
76
+
77
+ ## Installation
78
+
79
+ ```bash
80
+ pip install git+https://github.com/isarandi/smplfitter.git
81
+ ```
82
+
83
+ (Packaging for PyPI is planned for later.)
84
+
85
+ ### Download Body Model Files
86
+
87
+ You need to download the body model data files from the corresponding websites for this code to work. You only need the ones that you plan to use. There should be a `DATA_ROOT` environment variable under which a `body_models` directory should look like this:
88
+
89
+ ```
90
+ $DATA_ROOT/body_models
91
+ ├── smpl
92
+ │ ├── basicmodel_f_lbs_10_207_0_v1.1.0.pkl
93
+ │ ├── basicmodel_m_lbs_10_207_0_v1.1.0.pkl
94
+ │ ├── basicmodel_neutral_lbs_10_207_0_v1.1.0.pkl
95
+ │ └── kid_template.npy
96
+ ├── smplx
97
+ │ ├── kid_template.npy
98
+ │ ├── SMPLX_FEMALE.npz
99
+ │ ├── SMPLX_MALE.npz
100
+ │ └── SMPLX_NEUTRAL.npz
101
+ ├── smplh
102
+ │ ├── kid_template.npy
103
+ │ ├── SMPLH_FEMALE.pkl
104
+ │ └── SMPLH_MALE.pkl
105
+ ├── smplh16
106
+ │ ├── kid_template.npy
107
+ │ ├── female/model.npz
108
+ │ ├── male/model.npz
109
+ │ └── neutral/model.npz
110
+ ├── smpl2smplx_deftrafo_setup.pkl
111
+ └── smplx2smpl_deftrafo_setup.pkl
112
+ ```
113
+
114
+ You can refer to the relevant [script](https://github.com/isarandi/PosePile/tree/main/posepile/get_body_models.sh) in the PosePile repo about how to download these files.
115
+
116
+ ## Usage Examples
117
+
118
+ ### Basic Fitting
119
+
120
+ ```python
121
+ import torch
122
+ from smplfitter.pt import BodyModel, BodyFitter
123
+
124
+ body_model = BodyModel('smpl', 'neutral').cuda() # create the body model to be fitted
125
+ fitter = BodyFitter(body_model, num_betas=10).cuda() # create the fitter
126
+ fitter = torch.jit.script(fitter) # optional: compile the fitter for faster execution
127
+
128
+ # Obtain a batch of nonparametric vertex and joint locations (here we use dummy random data)
129
+ batch_size = 30
130
+ vertices = torch.rand((batch_size, 6890, 3)).cuda()
131
+ joints = torch.rand((batch_size, 24, 3)).cuda()
132
+
133
+ # Do the fitting!
134
+ fit_res = fitter.fit(vertices, joints, n_iter=3, beta_regularizer=1)
135
+ fit_res['pose_rotvecs'], fit_res['shape_betas'], fit_res['trans']
136
+ ```
137
+
138
+ ### Body Model Conversion (Transfer)
139
+
140
+ ```python
141
+ import torch
142
+ from smplfitter.pt import BodyConverter
143
+
144
+ bm_in = BodyModel('smpl', 'neutral')
145
+ bm_out = BodyModel('smplx', 'neutral')
146
+ smpl2smplx = BodyConverter(bm_in, bm_out).cuda()
147
+ smpl2smplx = torch.jit.script(smpl2smplx) # optional: compile the converter for faster execution
148
+
149
+ batch_size = 30
150
+ pose_rotvecs_in = torch.rand((batch_size, 72)).cuda()
151
+ shape_betas_in = torch.rand((batch_size, 10)).cuda()
152
+ trans_in = torch.rand((batch_size, 3)).cuda()
153
+
154
+ out = smpl2smplx.convert(pose_rotvecs_in, shape_betas_in, trans_in)
155
+ out['pose_rotvecs'], out['shape_betas'], out['trans']
156
+ ```
157
+
158
+
159
+ ## The Algorithm
160
+
161
+ SMPL(-X/+H) is a parametric body model that takes body part orientations $\theta$ and body shape vector $\beta$ as inputs and yields vertex and joint locations as outputs. SMPLfit approximates the **inverse operation**: it takes vertex and joint locations as inputs and yields orientations $\theta$ and shape $\beta$ as outputs.
162
+
163
+ Our algorithm alternates between fitting orientations and fitting shape. A good result can be obtained already with 1-3 iterations.
164
+
165
+ We illustrate the steps with the following example. Given the depicted RGB image, we used [Neural Localizer Fields (NLF)](https://virtualhumans.mpi-inf.mpg.de/nlf) to obtain nonparametric vertex and joint locations as follows:
166
+
167
+ <img src="docs/_static/images/image.png" alt="on_image" width="300"/>
168
+ <img src="docs/_static/images/on_image.png" alt="on_image" width="300"/>
169
+ <img src="docs/_static/images/pose3d.png" alt="on_image" width="300"/>
170
+
171
+ ### Fitting Body Part Orientations
172
+
173
+ We first **partition the body** based on the linear blend skinning (LBS) weights provided with the SMPL model, and then **independently fit the global orientation of each body part**.
174
+
175
+ We illustrate the partitioning on both the target and the template reference:
176
+
177
+ <img src="docs/_static/images/pose3d_parts_color.png" alt="tmean_parts" width="450"/>
178
+ <img src="docs/_static/images/tmean_parts.png" alt="tmean_parts" width="450"/>
179
+
180
+ We can separate the body parts for better visualization:
181
+
182
+ <img src="docs/_static/images/pose3d_parts_color_blown.png" alt="tmean_parts" width="450"/>
183
+ <img src="docs/_static/images/tmean_parts_blown.png" alt="tmean_parts" width="450"/>
184
+
185
+
186
+ For each body part, we have a set of target vertices from the input and a set of reference vertices, initially from the default SMPL template mesh. We apply the **Kabsch algorithm** to get the least-squares optimal rotation between the target and the reference vertices of each body part.
187
+
188
+ The first figure below shows the body parts of the target (blue) and reference (red) pairwise aligned at the centroid. The second figure shows the result of the Kabsch algorithm, with each body part independently and optimally rotated. In the third figure we show the SMPL mesh with the estimated orientations applied (red), compared with the target mesh (blue). (Note how the body shape and size do not align well yet.)
189
+
190
+ <img src="docs/_static/images/parts_overlay1.png" alt="tmean_parts" width="300"/>
191
+ <img src="docs/_static/images/parts_overlay1_rot.png" alt="tmean_parts" width="300"/>
192
+
193
+ <img src="docs/_static/images/overlay_after_rot1.png" alt="tmean_parts" width="300"/>
194
+
195
+
196
+ In subsequent iterations, the reference is no longer the default template, but the SMPL mesh posed and shaped according to the current parametric estimate.
197
+
198
+ We found it important to **weight the joints much higher than the vertices** when solving for the orientations. This ensures that the independently estimated orientations correctly "link up" without error accumulation, since neighboring body parts share a joint.
199
+
200
+ ### Fitting Body Shape
201
+
202
+ In SMPL, the L stands for linear, and so SMPL has a nice property: as long as the body part orientations $\theta$ are fixed, the **mapping from shape vector to vertex locations is linear** (the same applies to the joints), so solving for the shape boils down to a linear least squares problem.
203
+
204
+ SMPL is linear because going from shape vector to vertex location only involves matrix multiplications and adding constants: multiplying by the blend shapes, adding the template, adding pose-dependent blendshapes (which are constant for a fixed pose), multiplying by rotation matrices and multiplying by the linear blend skinning weights. (Technically, it's affine, not linear, but it boils down to the same thing.)
205
+
206
+ This means that for each vertex index $i$ there is a matrix $A_i(\theta)\in \mathbb{R}^{3\times 10}$ and vector $b_i(\theta)\in \mathbb{R}^3$ such that the vertex location can be written as
207
+
208
+ $$v_i(\theta, \beta) = A_i(\theta) \beta + b_i(\theta).$$
209
+
210
+ Substituting $\beta=0$ shows that $b_i(\theta)=v_i(\theta, 0)$, in other words, calculating $b_i$ is the same as applying the forward function of SMPL with the zero shape vector. Calculating $A_i$ boils down to the same SMPL forward computation, but extended with forward-mode automatic differentiation. That is, in each step of the forward computation, we track the Jacobian alongside the values.
211
+
212
+ Once we have the matrices $A_i$ and vectors $b_i$, we can **concatenate them** into a single large matrix $A$ and vector $b$, and solve the combined linear system $A\beta = v - b$ for the shape vector $\beta$ by linear least squares. Specifically, we do this via Cholesky decomposition.
213
+
214
+ The shape vector can be regularized with an L2 penalty, which is useful when the input is noisy. We found that it is best not to regularize the first two shape parameters, which correspond to the global scaling (body size and BMI) of the body.
215
+
216
+ Below we show the overlay of target and reference before and after shape fitting (observe how the silhouette is much better matched after shape fitting):
217
+
218
+ <img src="docs/_static/images/overlay_after_rot1.png" alt="tmean_parts" width="450"/>
219
+ <img src="docs/_static/images/overlay_after_rot1_shape.png" alt="tmean_parts" width="450"/>
220
+
221
+ ### Final Orientation Adjustment
222
+
223
+ In the orientation estimation step described above, we fit the orientations of the body parts independently, using the centroid as the rotation anchor point (following the Kabsch algorithm). However, even if we perfectly estimate these independent orientations, we may have translational errors due to bone length mismatches.
224
+
225
+ Therefore, we optionally perform a final adjustment of the orientations, where we sequentially (i.e. non-independently) adjust the orientation of each body part along the kinematic tree, starting from the root joint. Different from the previously described orientation fitting, we now use the position of the proximal joint of the body part as the anchor (instead of the centroid of the body part). This way a modified orientation can compensate for the translational error and bring the distal body parts closer to the target, preventing one joint error from impacting all subsequent parts in the chain.
226
+
227
+ Below we illustrate the effect of this step. The first image shows the situation after two iterations of orientation and shape fitting, but before the final adjustment. The second one is after the adjustment.
228
+
229
+ <img src="docs/_static/images/overlay_after_rot2_shape.png" alt="tmean_parts" width="450"/>
230
+
231
+ <img src="docs/_static/images/overlay_after_rot3.png" alt="tmean_parts" width="450"/>
232
+
233
+ The difference is visually subtle, but can be seen around the upper right arm and armpit area, as well as the heel of the right foot.
234
+
235
+ ### Handling of Incomplete Input
236
+
237
+ The algorithm can also fit to a **subset of the vertices**. However, the subset should properly cover the whole body including several vertices on each body part, otherwise the orientation fitting will be underdetermined and fail.
238
+
239
+ The input target joint locations are also optional. If they are not given, they are calculated from the input vertices using the SMPL joint regressor. (If you want to use a subset of vertices as the target and don't want to provide joints, then you need to provide a joint regressor $\mathcal{J}$ of size $n_\text{joints}\times n_\text{subset}$ that works for that subset of vertices - a simple way to get such a regressor could be natural neighbor interpolation weights based on the template mesh, while a more complicated way would be to learn it from a dataset like AMASS).
240
+
241
+ ### Weights
242
+
243
+ Our algorithm supports **vertex and joint weights**. The weights are used in both the least-squares orientation and shape fitting. The idea is to downweight noisy or unreliable parts of the input. For example, if your nonparametric vertices and joints come with uncertainty estimates, such as in the case of NLF, you can use the inverse of the uncertainty (or some power of thereof) as the weight.
244
+
245
+ ### Sharing Shape Parameters Across Instances
246
+
247
+ If you have several nonparametric body estimates for the same person in different poses (such as from a video), you can fit all of them together, estimating one shared shape vector for all instances of the batch, while not sharing the orientation parameters. This way the body shape will be more accurately estimated, as there are multiple observations to estimate it from and the output will be more consistent over time.
248
+
249
+ ### Scaling (experimental)
250
+ If you don't trust your input to have the correct metric scale, the shape fitting step can be extended by a scale fitting as well. There are two ways to do this: 1) we estimate a scale factor for the target body (your input), or 2) we estimate a scale factor for the reference body (the resulting output). Neither is really satisfactory, hence experimental. The former will bias the estimation towards smaller bodies as the vertex-to-vertex error that is being minimized scales with body size, and so the solver will favor scaling down the target and producing a beta vector for a smaller person. Estimating the scale for the reference body causes an issue if we want to share the betas among many instances, since the problem becomes quadratic (the beta variables get multiplied by the scale factor variable). For just one instance, this is doable, though messes with the L2 regularization strength. Tentatively, we recommend using the first option, but accounting for the scale bias by e.g. dividing by the mean estimated scale for a longer video sequence.
251
+
252
+
253
+ ## Citation
254
+
255
+ If you find this code useful, please consider citing our work.
256
+ This algorithm was developed for and described in the following paper:
257
+
258
+ ```
259
+ @article{sarandi24nlf,
260
+ title = {Neural Localizer Fields for Continuous 3D Human Pose and Shape Estimation},
261
+ author = {Sárándi, István and Pons-Moll, Gerard},
262
+ journal = {Advances in Neural Information Processing Systems (NeurIPS)},
263
+ year = {2024},
264
+ }
265
+ ```