| |
|
|
| |
| import json |
| import numpy as np |
| from functools import lru_cache |
| from typing import Dict, List, Optional, Tuple |
| import cv2 |
| import torch |
|
|
| from detectron2.utils.file_io import PathManager |
|
|
| from densepose.modeling import build_densepose_embedder |
| from densepose.modeling.cse.utils import get_closest_vertices_mask_from_ES |
|
|
| from ..data.utils import get_class_to_mesh_name_mapping |
| from ..structures import DensePoseEmbeddingPredictorOutput |
| from ..structures.mesh import create_mesh |
| from .base import Boxes, Image, MatrixVisualizer |
| from .densepose_results_textures import get_texture_atlas |
|
|
|
|
| @lru_cache() |
| def get_xyz_vertex_embedding(mesh_name: str, device: torch.device): |
| if mesh_name == "smpl_27554": |
| embed_path = PathManager.get_local_path( |
| "https://dl.fbaipublicfiles.com/densepose/data/cse/mds_d=256.npy" |
| ) |
| embed_map, _ = np.load(embed_path, allow_pickle=True) |
| embed_map = torch.tensor(embed_map).float()[:, 0] |
| embed_map -= embed_map.min() |
| embed_map /= embed_map.max() |
| else: |
| mesh = create_mesh(mesh_name, device) |
| embed_map = mesh.vertices.sum(dim=1) |
| embed_map -= embed_map.min() |
| embed_map /= embed_map.max() |
| embed_map = embed_map**2 |
| return embed_map |
|
|
|
|
| class DensePoseOutputsVertexVisualizer: |
| def __init__( |
| self, |
| cfg, |
| inplace=True, |
| cmap=cv2.COLORMAP_JET, |
| alpha=0.7, |
| |
| device="cpu", |
| default_class=0, |
| **kwargs, |
| ): |
| self.mask_visualizer = MatrixVisualizer( |
| inplace=inplace, cmap=cmap, val_scale=1.0, alpha=alpha |
| ) |
| self.class_to_mesh_name = get_class_to_mesh_name_mapping(cfg) |
| self.embedder = build_densepose_embedder(cfg) |
| self.device = torch.device(device) |
| self.default_class = default_class |
|
|
| self.mesh_vertex_embeddings = { |
| mesh_name: self.embedder(mesh_name).to(self.device) |
| for mesh_name in self.class_to_mesh_name.values() |
| if self.embedder.has_embeddings(mesh_name) |
| } |
|
|
| def visualize( |
| self, |
| image_bgr: Image, |
| outputs_boxes_xywh_classes: Tuple[ |
| Optional[DensePoseEmbeddingPredictorOutput], Optional[Boxes], Optional[List[int]] |
| ], |
| ) -> Image: |
| if outputs_boxes_xywh_classes[0] is None: |
| return image_bgr |
|
|
| S, E, N, bboxes_xywh, pred_classes = self.extract_and_check_outputs_and_boxes( |
| outputs_boxes_xywh_classes |
| ) |
|
|
| for n in range(N): |
| x, y, w, h = bboxes_xywh[n].int().tolist() |
| mesh_name = self.class_to_mesh_name[pred_classes[n]] |
| closest_vertices, mask = get_closest_vertices_mask_from_ES( |
| E[[n]], |
| S[[n]], |
| h, |
| w, |
| self.mesh_vertex_embeddings[mesh_name], |
| self.device, |
| ) |
| embed_map = get_xyz_vertex_embedding(mesh_name, self.device) |
| vis = (embed_map[closest_vertices].clip(0, 1) * 255.0).cpu().numpy() |
| mask_numpy = mask.cpu().numpy().astype(dtype=np.uint8) |
| image_bgr = self.mask_visualizer.visualize(image_bgr, mask_numpy, vis, [x, y, w, h]) |
|
|
| return image_bgr |
|
|
| def extract_and_check_outputs_and_boxes(self, outputs_boxes_xywh_classes): |
|
|
| densepose_output, bboxes_xywh, pred_classes = outputs_boxes_xywh_classes |
|
|
| if pred_classes is None: |
| pred_classes = [self.default_class] * len(bboxes_xywh) |
|
|
| assert isinstance( |
| densepose_output, DensePoseEmbeddingPredictorOutput |
| ), "DensePoseEmbeddingPredictorOutput expected, {} encountered".format( |
| type(densepose_output) |
| ) |
|
|
| S = densepose_output.coarse_segm |
| E = densepose_output.embedding |
| N = S.size(0) |
| assert N == E.size( |
| 0 |
| ), "CSE coarse_segm {} and embeddings {}" " should have equal first dim size".format( |
| S.size(), E.size() |
| ) |
| assert N == len( |
| bboxes_xywh |
| ), "number of bounding boxes {}" " should be equal to first dim size of outputs {}".format( |
| len(bboxes_xywh), N |
| ) |
| assert N == len(pred_classes), ( |
| "number of predicted classes {}" |
| " should be equal to first dim size of outputs {}".format(len(bboxes_xywh), N) |
| ) |
|
|
| return S, E, N, bboxes_xywh, pred_classes |
|
|
|
|
| def get_texture_atlases(json_str: Optional[str]) -> Optional[Dict[str, Optional[np.ndarray]]]: |
| """ |
| json_str is a JSON string representing a mesh_name -> texture_atlas_path dictionary |
| """ |
| if json_str is None: |
| return None |
|
|
| paths = json.loads(json_str) |
| return {mesh_name: get_texture_atlas(path) for mesh_name, path in paths.items()} |
|
|
|
|
| class DensePoseOutputsTextureVisualizer(DensePoseOutputsVertexVisualizer): |
| def __init__( |
| self, |
| cfg, |
| texture_atlases_dict, |
| |
| device="cpu", |
| default_class=0, |
| **kwargs, |
| ): |
| self.embedder = build_densepose_embedder(cfg) |
|
|
| self.texture_image_dict = {} |
| self.alpha_dict = {} |
|
|
| for mesh_name in texture_atlases_dict.keys(): |
| if texture_atlases_dict[mesh_name].shape[-1] == 4: |
| self.alpha_dict[mesh_name] = texture_atlases_dict[mesh_name][:, :, -1] / 255.0 |
| self.texture_image_dict[mesh_name] = texture_atlases_dict[mesh_name][:, :, :3] |
| else: |
| self.alpha_dict[mesh_name] = texture_atlases_dict[mesh_name].sum(axis=-1) > 0 |
| self.texture_image_dict[mesh_name] = texture_atlases_dict[mesh_name] |
|
|
| self.device = torch.device(device) |
| self.class_to_mesh_name = get_class_to_mesh_name_mapping(cfg) |
| self.default_class = default_class |
|
|
| self.mesh_vertex_embeddings = { |
| mesh_name: self.embedder(mesh_name).to(self.device) |
| for mesh_name in self.class_to_mesh_name.values() |
| } |
|
|
| def visualize( |
| self, |
| image_bgr: Image, |
| outputs_boxes_xywh_classes: Tuple[ |
| Optional[DensePoseEmbeddingPredictorOutput], Optional[Boxes], Optional[List[int]] |
| ], |
| ) -> Image: |
| image_target_bgr = image_bgr.copy() |
| if outputs_boxes_xywh_classes[0] is None: |
| return image_target_bgr |
|
|
| S, E, N, bboxes_xywh, pred_classes = self.extract_and_check_outputs_and_boxes( |
| outputs_boxes_xywh_classes |
| ) |
|
|
| meshes = { |
| p: create_mesh(self.class_to_mesh_name[p], self.device) for p in np.unique(pred_classes) |
| } |
|
|
| for n in range(N): |
| x, y, w, h = bboxes_xywh[n].int().cpu().numpy() |
| mesh_name = self.class_to_mesh_name[pred_classes[n]] |
| closest_vertices, mask = get_closest_vertices_mask_from_ES( |
| E[[n]], |
| S[[n]], |
| h, |
| w, |
| self.mesh_vertex_embeddings[mesh_name], |
| self.device, |
| ) |
| uv_array = meshes[pred_classes[n]].texcoords[closest_vertices].permute((2, 0, 1)) |
| uv_array = uv_array.cpu().numpy().clip(0, 1) |
| textured_image = self.generate_image_with_texture( |
| image_target_bgr[y : y + h, x : x + w], |
| uv_array, |
| mask.cpu().numpy(), |
| self.class_to_mesh_name[pred_classes[n]], |
| ) |
| if textured_image is None: |
| continue |
| image_target_bgr[y : y + h, x : x + w] = textured_image |
|
|
| return image_target_bgr |
|
|
| def generate_image_with_texture(self, bbox_image_bgr, uv_array, mask, mesh_name): |
| alpha = self.alpha_dict.get(mesh_name) |
| texture_image = self.texture_image_dict.get(mesh_name) |
| if alpha is None or texture_image is None: |
| return None |
| U, V = uv_array |
| x_index = (U * texture_image.shape[1]).astype(int) |
| y_index = (V * texture_image.shape[0]).astype(int) |
| local_texture = texture_image[y_index, x_index][mask] |
| local_alpha = np.expand_dims(alpha[y_index, x_index][mask], -1) |
| output_image = bbox_image_bgr.copy() |
| output_image[mask] = output_image[mask] * (1 - local_alpha) + local_texture * local_alpha |
| return output_image.astype(np.uint8) |
|
|