Fit an SMPL body model (BM) to a given scan and view the optimization process in a plotly dashboard. Fitting supported:
- π§ββοΈ fit the body model parameters (shape, pose, translation, scale)
- π€Ή fit the vertices to the scan
The code supports fitting a single scan π€ or a whole dataset π₯.
smpl_fitting_dashboard.mp4
You can use a docker container to facilitate running the code. After cloning the repo, run in terminal:
cd docker
sh build.sh
sh docker_run.sh CODE_PATHby adjusting the CODE_PATH to the SMPL-Fitting directory location. This creates a smpl-fitting-container container. You can attach to it by running:
docker exec -it smpl-fitting-container /bin/bashπ§ If you do not want to use docker, you can install the docker/requirements.txt into your own environment. π§
Next, initialize the chamfer distance submodule by running:
git submodule update --init --recursiveNecessary files:
- put the
SMPL_{GENDER}.pkl(MALE, FEMALE and NEUTRAL) models into thedata/body_models/smplfolder. You can obtain the files here. - put the
gmm_08.pklprior into thedata/priorfolder. You can obtain the files here. - [OPTIONAL] We provide a demo for fitting the whole FAUST dataset. To do that, download the FAUST dataset here and put the
FAUST/training/scansandFAUST/training/registrationsfolders into thedata/FAUST/trainingfolder in this repository. We already provided the landmarks for fitting indata/FAUST/training/landmarks.
The configuration files for the fitting are stored in the configs folder:
config.yamlstores general variables and optimization-specific variablesloss_weight_configs.yamlstores the loss weight strategy for the fitting process defined asiteration: dict of loss weightspairs. For example,4: {"data": 1, "smooth": 150, "landmark": 100}means that at iteration 4, the data loss will be multiplied by 1, smoothnesss loss will be multiplied by 150, etc.
The general configuration variables are listed below. The specific variables for fitting the body model are given here, and the specific variables for fitting the vertices are given here.
General variables:
verbose- (bool) printout losses and variable values at each stepdefault_dtype- (torch.dtype) define the shape, pose, etc. tensor data typespause_script_after_fitting- (bool) pause the script after the fitting is done so you can visualize in peaceexperiment_name- (string) name your experiment
Visualization variables:
socket_type- (string) type of socket, only zmq supportedsocket_port- (int) port for visualizations, localhost:socket_porterror_curves_logscale- (bool) visualize loss curves in log scalevisualize- (bool) visualize or not the fittingvisualize_steps- (list / range) iterations to visualize, can be defined as summed ranges and lists, ex.range(0, 500, 50)+[10,30,499]
Path variables:
body_models_path- (string) path to SMPL,SMPLX,.. body modelsprior_path- (string) path to the gmm prior loss .pkl filesave_path- (string) path to save the results
Dataset variables for FAUST (these are dataset specific for each dataset you implement):
data_dir- (string) path to FAUST datasetload_gt- (bool) load ground truth SMPL fitting or not
Optimize the body model parameters of shape and pose (including translation and scale) that best fit the given scan. Check notes on losses to see the losses used.
The optimization-specific configurations to fit a BM to a scan are set under fit_body_model_optimization in config.yaml with the following variables:
iterations- (int) number of iterationslr- (float) learning ratestart_lr_decay_iteration- (int) iteration when to start the learning rate decay calculated aslr *(iterations-current iteration)/iterationsbody_model- (string) which BM to use (smpl, smplx,..). See Notes for supported modelsuse_landmarks- (string / list) which body landmarks to use for fitting. Can beAllto use all possible landmarks,{BM}_INDEX_LANDMARKSdefined in landmarks.py or list of landmark names e.g.["Lt. 10th Rib", "Lt. Dactylion",..]defined in landmarks.pyloss_weight_option- the strategy for the loss weights, defined inloss_weights_configs.yamlunderfit_bm_loss_weight_strategy
The default variables already set should work well for the fitting process.
python fit_body_model.py onto_scan --scan_path {path-to-scan} --landmark_path {path-to-landmarks}Check Notes to see the supported scan and landmark file extensions.
python fit_body_model.py onto_dataset --dataset_name {dataset-to-fit}The dataset you want to fit needs to be defined in datasets.py as a torch dataset. Check notes on datasets for more details. We already provide the FAUST dataset in datasets.py.
Optimize the vertices of a BM (or mesh) that best fit the given scan. Check notes on losses to see the losses used.
The optimization-specific configuration to fit the vertices to a scan is set under fit_vertices_optimization in config.yaml with the following variables:
max_iterations- (int) number of maximal iterationsstop_at_loss_value- (float) stop fitting if loss under this thresholdstop_at_loss_difference- (float) stop fitting if difference of loss at iterationi-1and iterationiis less this thresholduse_landmarks- (string / list) which body landmarks to use for fitting. Can benulto not use landmarks,Allto use all possible landmarks,{BM}_INDEX_LANDMARKSdefined in landmarks.py, or list of landmark names e.g.["Lt. 10th Rib", "Lt. Dactylion",..]defined in landmarks.pyrandom_init_A- (bool) random initialization of vertices transformationseed- (float) seed for random initialization of vertices transformationuse_losses- (list) losses to use. The complete list of losses is["data","smooth","landmark","normal","partial_data"]. Check notes on losses.loss_weight_option- (string) the strategy for the loss weights, defined inloss_weights_configs.yamlunderfit_verts_loss_weight_strategylr- (float) learning ratenormal_threshold_angle- (float) used if normal loss included inuse_losses. Penalizes knn points only if angle is lower than this threshold. Otherwise points are ignorednormal_threshold_distance- (float) used if normal loss included inuse_losses. Penalizes knn points only if the distance is lower than this threshold. Otherwise points are ignoredpartial_data_threshold- (float) used if partial_data loss included inuse_losses. Chamfer distance from BM to scan for points that are closer than this threshold. Otherwise points are ignored
python fit_vertices.py onto_scan --scan_path {path-to-scan} --landmark_path {path-to-landmarks} --start_from_previous_results {path-to-YYYY_MM_DD_HH_MM_SS-folder}Check Notes to see the supported scan and landmark file extensions. You can either use --start_from_previous_results to fit the vertices of the previously fitted BM with the fit_body_model.py script ( .npz is located) or use --start_from_body_model to start fitting a BM with zero shape and pose to the scan (
python fit_vertices.py onto_dataset --dataset_name {dataset-name} --start_from_previous_results {path-to-previously-fitted-bm-results}You can either use --start_from_previous_results to fit the vertices of the previously fitted BM with the fit_body_model.py script (.npz are located) or use --start_from_body_model to start fitting a BM with zero shape and pose to the scan (datasets.py as a torch dataset. Check notes on datasets for more details. We already provide the FAUST dataset in datasets.py.
If you already have body model parameters (pose, shape, translation and scale) given, but they are not ideal, you can refine them.
The optimization-specific configuration to refine the parmaeters is set under refine_bm_fitting in config.yaml with the following variables:
iterations- (int) number of iterationsstart_lr_decay_iteration- (int) iteration when to start the learning rate decay calculated aslr *(iterations-current iteration)/iterationsbody_model- (string) which BM to use (smpl, smplx,..). See Notes for supported modelsuse_landmarks- (string / list) which body landmarks to use for fitting. Can benulto not use landmarks,Allto use all possible landmarks,{BM}_INDEX_LANDMARKSdefined in landmarks.py, or list of landmark names e.g.["Lt. 10th Rib", "Lt. Dactylion",..]defined in landmarks.pyrefine_params- (list of strings) of parameters you want to refine, can contiain: pose, shape, transl, scaleuse_losses- (list) losses to use. The complete list of losses is["data","smooth","landmark","normal","partial_data"]. Check notes on losses.loss_weight_option- (string) the strategy for the loss weights, defined inloss_weights_configs.yamlunderfit_verts_loss_weight_strategyprior_folder- (string) path to the gmm prior loss .pkl filenum_gaussians- (float) number of gaussians to use for the priorlr- (float) learning ratenormal_threshold_angle- (float) used if normal loss included inuse_losses. Penalizes knn points only if angle is lower than this threshold. Otherwise points are ignored
python refine_fitting.py onto_dataset --dataset_name {dataset-name}Use the evaluate_fitting.py script to evaluate the fitting.
Evaluate the per vertex error (pve) which is the average euclidean distance between the given ground truth BM to the fitted BM.
python evaluate_fitting.py pve -F {path-to-results}The pve unit is determined by the data. For the FAUST dataset the unit is given in meters.
You can use:
-V- to visualize the pve for each example--select_examples- (list) to select a subset of examples to evaluate (only if evaluating fitting to dataset)--ground_truth_path- (string) to set the path to the ground truth body model (only if evaluating fitting to scan)
Evaluate the (various definitions of) chamfer distance (CD) from the estimated body model to the scan with:
python evaluate_fitting.py chamfer -F {path-to-results}where the different definitions are:
Chamfer standardis (mean(dists_bm2scan) + mean(dists_scan2bm))Chamfer bidirectionalis mean(concatenation(dists_bm2scan,dists_scan2bm))Chamfer from body model to scanis mean(dists_bm2scan)Chamfer from scan to body modelis mean(dists_scan2bm)
and are averaged over the examples. The unit of these metrics is determined by the data. For the FAUST dataset the unit is given in meters.
You can use:
--select_examplesto select a subset of examples to evaluate (only if evaluating fitting to dataset)--deviceto set gpu for running a faster chamfer distance (usecuda:{gpu-index})--scan_path- (string) to set the path to the scan you are evaluating (only if evaluating fitting to scan and not whole dataset)
-
Visualize SMPL landmarks with:
python visualization.py visualize_smpl_landmarks
-
Visualize scan landmarks with:
python visualization.py visualize_scan_landmarks --scan_path {path-to-scan} --landmark_path {path-to-landmarks}Check Notes section to find out the possible landmark definitions.
-
Visualize fitting:
python visualization.py visualize_fitting --scan_path {path-to-scan} --fit_paths {path-to-.npz-file}where the
.npzis obtained with the fitting scripts.
The list of available landmarks for each BM are listed in landmarks.py.
The supported ways of loading landmarks for a scan are:
.txtextension has two optionsx y z landmark_namelandmark_index landmark_name
.jsonextension has two options{landmark_name: [x,y,z]}{landmark_name: landmark_index}
where x y z indicate the coordinates of the landmark and landmark_index indicates the index of the scan vertex representing the landmark.
Losses for fitting the BM:
data loss- chamfer distance between BM and scanlandmark loss- L2 distance between BM landmarks and scan landmarksprior shape loss- L2 norm of BM shape parametersprior pose loss- gmm prior loss from [1]
Losses for fitting the vertices:
data loss- directional chamfer distance from BM to scansmoothness loss- difference between transformations of neighboring BM verticeslandmark loss- L2 distance between BM landmarks and scan landmarksnormal loss- L2 distance between points with normals within angle threshold
The dataset you want to fit needs to be defined in datasets.py as a torch dataset with the following variables:
name- (string) name of the scanvertices- (np.ndarray) vertices of the scanfaces- (np.ndarray) faces of the scan (set toNoneif no faces)landmarks- (dict) of (landmark_name: landmark_coords) pairs where landmark_coords is list of 3 floats
If you additionally want to evaluate the per vertex error (pve) after fitting (check βοΈ Evaluate) which compares the mean L2 between the fitted BM and the ground truth BM, you need to provide the ground truth BM as:
vertices_gt- (np.ndarray) ground truth vertices of the BMfaces_gt- (np.ndarray) ground truth faces of the BM
If you want to refine the parameters that have already been fitted, the dataset needs to additionally return:
pose- (torch.tensor) fitted pose parameters dim 1 x 72shape- (torch.tensor) fitted shape parameters dim 1 x 10trans- (torch.tensor) fitted translation dim 1 x 3gender- (str) gender of the body model
We provide the FAUST and CAESAR and 4DHumanOutfit dataset implementations in datasets.py. You can obtain the datasets from here, here and here.
Currently, we support the SMPL body model. If you want to add another BM, you can follow these steps:
- Add the body models into
data/body_models - Implement the body model in
body_models.py - Implement the body model parameters in
body_parameters.py - Implement the body landmarks in
landmarks.py
Fit body model onto scan:
python fit_body_model.py onto_scan --scan_path data/demo/tr_scan_000.ply --landmark_path data/demo/tr_scan_000_landmarks.jsonFit body model onto dataset (π§ you need to provide the FAUST dataset files as mentioned above π§):
python fit_body_model.py onto_dataset -D FAUSTFit the vertices of the previously fitted BM onto the scan even further:
python fit_vertices.py onto_scan --scan_path data/FAUST/training/scans/tr_scan_000.ply --landmark_path data/FAUST/training/landmarks/tr_scan_000_landmarks.json --start_from_previous_results data/demoFit the vertices of the previously fitted BM onto FAUST dataset further:
python fit_vertices.py onto_dataset --dataset_name FAUST --start_from_previous_results data/demoπ§ We provide only the fitted paths for scans tr_scan_000 and tr_scan_001. Therefore the rest of the scans are going to be skipped π§
Evaluate PVE of fitted scan for the two provided fittings:
python evaluate_fitting.py pve -F data/demo -G data/demoEvaluate chamfer of fitted scan for the two provided fittings:
python evaluate_fitting.py chamfer -F data/demoVisualize SMPL landmarks:
python visualization.py visualize_smpl_landmarksVisualize FAUST scan landmarks:
python visualization.py visualize_scan_landmarks --scan_path data/demo/tr_scan_000.ply --landmark_path data/demo/tr_scan_000_landmarks.jsonVisualize the fitted vertices of the BM onto the FAUST scan:
python visualization.py visualize_fitting --scan_path data/demo/tr_scan_000.ply --fit_paths data/demo/tr_scan_000.npzPlease cite our work and leave a star β if you find the repository useful.
@misc{SMPL-Fitting,
author = {Bojani\'{c}, D.},
title = {SMPL-Fitting},
year = {2024},
publisher = {GitHub},
journal = {GitHub repository},
howpublished = {\url{https://github.com/DavidBoja/SMPL-Fitting}},
}- Implement SMPLx body model
[1] Keep it SMPL: Automatic Estimation of 3D Human Pose and Shape from a Single Image
