|
82 | 82 |
|
83 | 83 | import logging |
84 | 84 | from functools import cached_property, lru_cache |
85 | | -from typing import Optional |
| 85 | +from typing import Optional, Any |
86 | 86 |
|
87 | 87 | import numpy as np |
88 | 88 |
|
@@ -322,9 +322,9 @@ def convert_smpl2mhr( |
322 | 322 | return ConversionResult( |
323 | 323 | result_meshes=result_meshes if return_mhr_meshes else None, |
324 | 324 | result_vertices=mhr_vertices if return_mhr_vertices else None, |
325 | | - result_parameters=None |
326 | | - if not return_mhr_parameters |
327 | | - else fitting_parameter_results, |
| 325 | + result_parameters=( |
| 326 | + None if not return_mhr_parameters else fitting_parameter_results |
| 327 | + ), |
328 | 328 | result_errors=errors if return_fitting_errors else None, |
329 | 329 | ) |
330 | 330 |
|
@@ -401,12 +401,132 @@ def convert_mhr2smpl( |
401 | 401 | return ConversionResult( |
402 | 402 | result_meshes=result_meshes if return_smpl_meshes else None, |
403 | 403 | result_vertices=smpl_vertices if return_smpl_vertices else None, |
404 | | - result_parameters=fitting_parameter_results |
405 | | - if return_smpl_parameters |
406 | | - else None, |
| 404 | + result_parameters=( |
| 405 | + fitting_parameter_results if return_smpl_parameters else None |
| 406 | + ), |
407 | 407 | result_errors=errors if return_fitting_errors else None, |
408 | 408 | ) |
409 | 409 |
|
| 410 | + def convert_sam3d_output_to_smpl( |
| 411 | + self, |
| 412 | + sam3d_outputs: list[dict[str, Any]], |
| 413 | + return_smpl_meshes: bool = False, |
| 414 | + return_smpl_parameters: bool = True, |
| 415 | + return_smpl_vertices: bool = False, |
| 416 | + return_fitting_errors: bool = True, |
| 417 | + batch_size: int = 256, |
| 418 | + ) -> ConversionResult: |
| 419 | + """ |
| 420 | + Convert SAM3D output to SMPL model parameters. |
| 421 | +
|
| 422 | + Usage: |
| 423 | + After initializing a coverter object from Conversion class, |
| 424 | + sam3d_outputs = SAM3DBodyEstimator.process_one_image(...) |
| 425 | + conversion_results = coverter.convert_sam3d_output_to_smpl(sam3d_outputs) |
| 426 | +
|
| 427 | + Args: |
| 428 | + sam3d_outputs: List of dictionaries containing the output of the SAM3D model. |
| 429 | + return_smpl_meshes: Whether to return the SMPL meshes. If True, the function will return a list |
| 430 | + of SMPL meshes. |
| 431 | + return_smpl_parameters: Whether to return the SMPL parameters. If True, the function will return a |
| 432 | + dictionary of SMPL parameters. |
| 433 | + return_smpl_vertices: Whether to return the SMPL vertices. If True, the function will return a numpy |
| 434 | + array of SMPL vertices. |
| 435 | + return_fitting_errors: Whether to return fitting errors. If True, the function will return errors for |
| 436 | + each frame. |
| 437 | + batch_size: Number of frames to process in each batch. |
| 438 | +
|
| 439 | + Returns: |
| 440 | + ConversionResult containing: |
| 441 | + - result_meshes: List of SMPL meshes |
| 442 | + - result_vertices: Numpy array of SMPL vertices |
| 443 | + - result_parameters: Dictionary of SMPL parameters |
| 444 | + - result_errors: Numpy array of fitting errors for each frame |
| 445 | + """ |
| 446 | + |
| 447 | + # These are the keys that are expected in the sam3d_outputs, from the first three we can extract the MHR vertices. The last one are the MHR vertices. |
| 448 | + SAM3D_OUTPUT_VARIABLES = ( |
| 449 | + "mhr_model_params", # Translation is not included in the model params, but in "pred_cam_t". |
| 450 | + "shape_params", |
| 451 | + "expr_params", |
| 452 | + "pred_vertices", # Translation is not included in the vertices, but in "pred_cam_t". |
| 453 | + "pred_cam_t", # This is the camera translation w.r.t. each person. |
| 454 | + ) |
| 455 | + |
| 456 | + # The result of SAM3DBodyEstimator.process_one_image() is a list of dictionaries, each dictionary contains the outputs of the SAM3D model for one person. |
| 457 | + # We concatenate all person's outputs into one dictionary, so that we can convert them to SMPL in batch. |
| 458 | + concatenated_sam3d_outputs = {} |
| 459 | + for k in SAM3D_OUTPUT_VARIABLES: |
| 460 | + concatenated_sam3d_outputs[k] = np.stack( |
| 461 | + [ |
| 462 | + sam3d_output[k] |
| 463 | + for sam3d_output in sam3d_outputs |
| 464 | + if k in sam3d_output |
| 465 | + ], |
| 466 | + axis=0, |
| 467 | + ) |
| 468 | + concatenated_sam3d_outputs[k] = self._to_tensor( |
| 469 | + concatenated_sam3d_outputs[k] |
| 470 | + ) |
| 471 | + |
| 472 | + # If MHR vertices are available, use them to compute the SMPL parameters. If not, use the MHR model, shape, and expressions to compute the SMPL parameters. |
| 473 | + num_people = len(sam3d_outputs) |
| 474 | + mhr_vertices = None |
| 475 | + if ( |
| 476 | + "pred_vertices" in concatenated_sam3d_outputs |
| 477 | + and concatenated_sam3d_outputs["pred_vertices"].shape[0] == num_people |
| 478 | + ): |
| 479 | + # If pred_vertices is available, use it to compute the target vertices |
| 480 | + # Note: SAM3D outputs the vertices in meters, so we need to convert them to centimeters before passing them to the conversion function |
| 481 | + mhr_vertices = ( |
| 482 | + 100.0 * concatenated_sam3d_outputs["pred_vertices"] |
| 483 | + + 100.0 * concatenated_sam3d_outputs["pred_cam_t"][:, None, :] |
| 484 | + ) |
| 485 | + elif ( |
| 486 | + "mhr_model_params" in concatenated_sam3d_outputs |
| 487 | + and concatenated_sam3d_outputs["mhr_model_params"].shape[0] == num_people |
| 488 | + ): |
| 489 | + # If pred_vertices is not available, use the mhr_model_params to compute the target vertices |
| 490 | + mhr_parameters = {} |
| 491 | + mhr_parameters["lbs_model_params"] = concatenated_sam3d_outputs[ |
| 492 | + "mhr_model_params" |
| 493 | + ] |
| 494 | + |
| 495 | + assert ( |
| 496 | + "shape_params" in concatenated_sam3d_outputs |
| 497 | + and concatenated_sam3d_outputs["shape_params"].shape[0] == num_people |
| 498 | + ) |
| 499 | + mhr_parameters["identity_coeffs"] = concatenated_sam3d_outputs[ |
| 500 | + "shape_params" |
| 501 | + ] |
| 502 | + assert ( |
| 503 | + "expr_params" in concatenated_sam3d_outputs |
| 504 | + and concatenated_sam3d_outputs["expr_params"].shape[0] == num_people |
| 505 | + ) |
| 506 | + mhr_parameters["face_expr_coeffs"] = concatenated_sam3d_outputs[ |
| 507 | + "expr_params" |
| 508 | + ] |
| 509 | + _, mhr_vertices = self._mhr_para2mesh(mhr_parameters, return_mesh=False) |
| 510 | + mhr_vertices = self._to_tensor(mhr_vertices) |
| 511 | + mhr_vertices[..., [1, 2]] *= -1 # Camera system difference in SAM3D-Body |
| 512 | + mhr_vertices += 100.0 * concatenated_sam3d_outputs["pred_cam_t"][:, None, :] |
| 513 | + else: |
| 514 | + raise ValueError( |
| 515 | + "Either pred_vertices or mhr_model_params must be available in the SAM3D output." |
| 516 | + ) |
| 517 | + |
| 518 | + conversion_results = self.convert_mhr2smpl( |
| 519 | + mhr_vertices=mhr_vertices, |
| 520 | + single_identity=False, |
| 521 | + is_tracking=False, |
| 522 | + return_smpl_meshes=return_smpl_meshes, |
| 523 | + return_smpl_parameters=return_smpl_parameters, |
| 524 | + return_smpl_vertices=return_smpl_vertices, |
| 525 | + return_fitting_errors=return_fitting_errors, |
| 526 | + batch_size=batch_size, |
| 527 | + ) |
| 528 | + return conversion_results |
| 529 | + |
410 | 530 | def _to_tensor(self, data: torch.Tensor | np.ndarray) -> torch.Tensor: |
411 | 531 | """Convert input data to tensor on the appropriate device.""" |
412 | 532 | if isinstance(data, torch.Tensor): |
|
0 commit comments