| |
| |
| |
| |
| |
| |
| |
|
|
| |
| |
| |
| |
| |
|
|
| import os |
| import torch |
| import copy |
| import trimesh |
| import numpy as np |
| from PIL import Image |
| from typing import List |
| from DifferentiableRenderer.MeshRender import MeshRender |
| from utils.simplify_mesh_utils import remesh_mesh |
| from utils.multiview_utils import multiviewDiffusionNet |
| from utils.pipeline_utils import ViewProcessor |
| from utils.image_super_utils import imageSuperNet |
| from utils.uvwrap_utils import mesh_uv_wrap |
| from DifferentiableRenderer.mesh_utils import convert_obj_to_glb |
| import warnings |
|
|
| warnings.filterwarnings("ignore") |
| from diffusers.utils import logging as diffusers_logging |
|
|
| diffusers_logging.set_verbosity(50) |
|
|
|
|
| class Hunyuan3DPaintConfig: |
| def __init__(self, max_num_view, resolution): |
| self.device = "cuda" |
|
|
| self.multiview_cfg_path = "cfgs/hunyuan-paint-pbr.yaml" |
| self.custom_pipeline = "hunyuanpaintpbr" |
| self.multiview_pretrained_path = "tencent/Hunyuan3D-2.1" |
| self.dino_ckpt_path = "facebook/dinov2-giant" |
| self.realesrgan_ckpt_path = "ckpt/RealESRGAN_x4plus.pth" |
|
|
| self.raster_mode = "cr" |
| self.bake_mode = "back_sample" |
| self.render_size = 1024 * 2 |
| self.texture_size = 1024 * 4 |
| self.max_selected_view_num = max_num_view |
| self.resolution = resolution |
| self.bake_exp = 4 |
| self.merge_method = "fast" |
|
|
| |
| self.candidate_camera_azims = [0, 90, 180, 270, 0, 180] |
| self.candidate_camera_elevs = [0, 0, 0, 0, 90, -90] |
| self.candidate_view_weights = [1, 0.1, 0.5, 0.1, 0.05, 0.05] |
|
|
| for azim in range(0, 360, 30): |
| self.candidate_camera_azims.append(azim) |
| self.candidate_camera_elevs.append(20) |
| self.candidate_view_weights.append(0.01) |
|
|
| self.candidate_camera_azims.append(azim) |
| self.candidate_camera_elevs.append(-20) |
| self.candidate_view_weights.append(0.01) |
|
|
|
|
| class Hunyuan3DPaintPipeline: |
|
|
| def __init__(self, config=None) -> None: |
| self.config = config if config is not None else Hunyuan3DPaintConfig() |
| self.models = {} |
| self.stats_logs = {} |
| self.render = MeshRender( |
| default_resolution=self.config.render_size, |
| texture_size=self.config.texture_size, |
| bake_mode=self.config.bake_mode, |
| raster_mode=self.config.raster_mode, |
| ) |
| self.view_processor = ViewProcessor(self.config, self.render) |
| self.load_models() |
|
|
| def load_models(self): |
| torch.cuda.empty_cache() |
| self.models["super_model"] = imageSuperNet(self.config) |
| self.models["multiview_model"] = multiviewDiffusionNet(self.config) |
| print("Models Loaded.") |
|
|
| @torch.no_grad() |
| def __call__(self, mesh_path=None, image_path=None, output_mesh_path=None, use_remesh=True, save_glb=True): |
| """Generate texture for 3D mesh using multiview diffusion""" |
| |
| if isinstance(image_path, str): |
| image_prompt = Image.open(image_path) |
| elif isinstance(image_path, Image.Image): |
| image_prompt = image_path |
| if not isinstance(image_prompt, List): |
| image_prompt = [image_prompt] |
| else: |
| image_prompt = image_path |
|
|
| |
| path = os.path.dirname(mesh_path) |
| if use_remesh: |
| processed_mesh_path = os.path.join(path, "white_mesh_remesh.obj") |
| remesh_mesh(mesh_path, processed_mesh_path) |
| else: |
| processed_mesh_path = mesh_path |
|
|
| |
| if output_mesh_path is None: |
| output_mesh_path = os.path.join(path, f"textured_mesh.obj") |
|
|
| |
| mesh = trimesh.load(processed_mesh_path) |
| mesh = mesh_uv_wrap(mesh) |
| self.render.load_mesh(mesh=mesh) |
|
|
| |
| selected_camera_elevs, selected_camera_azims, selected_view_weights = self.view_processor.bake_view_selection( |
| self.config.candidate_camera_elevs, |
| self.config.candidate_camera_azims, |
| self.config.candidate_view_weights, |
| self.config.max_selected_view_num, |
| ) |
|
|
| normal_maps = self.view_processor.render_normal_multiview( |
| selected_camera_elevs, selected_camera_azims, use_abs_coor=True |
| ) |
| position_maps = self.view_processor.render_position_multiview(selected_camera_elevs, selected_camera_azims) |
|
|
| |
| image_caption = "high quality" |
| image_style = [] |
| for image in image_prompt: |
| image = image.resize((512, 512)) |
| if image.mode == "RGBA": |
| white_bg = Image.new("RGB", image.size, (255, 255, 255)) |
| white_bg.paste(image, mask=image.getchannel("A")) |
| image = white_bg |
| image_style.append(image) |
| image_style = [image.convert("RGB") for image in image_style] |
|
|
| |
| multiviews_pbr = self.models["multiview_model"]( |
| image_style, |
| normal_maps + position_maps, |
| prompt=image_caption, |
| custom_view_size=self.config.resolution, |
| resize_input=True, |
| ) |
| |
| enhance_images = {} |
| enhance_images["albedo"] = copy.deepcopy(multiviews_pbr["albedo"]) |
| enhance_images["mr"] = copy.deepcopy(multiviews_pbr["mr"]) |
|
|
| for i in range(len(enhance_images["albedo"])): |
| enhance_images["albedo"][i] = self.models["super_model"](enhance_images["albedo"][i]) |
| enhance_images["mr"][i] = self.models["super_model"](enhance_images["mr"][i]) |
|
|
| |
| for i in range(len(enhance_images)): |
| enhance_images["albedo"][i] = enhance_images["albedo"][i].resize( |
| (self.config.render_size, self.config.render_size) |
| ) |
| enhance_images["mr"][i] = enhance_images["mr"][i].resize((self.config.render_size, self.config.render_size)) |
| texture, mask = self.view_processor.bake_from_multiview( |
| enhance_images["albedo"], selected_camera_elevs, selected_camera_azims, selected_view_weights |
| ) |
| mask_np = (mask.squeeze(-1).cpu().numpy() * 255).astype(np.uint8) |
| texture_mr, mask_mr = self.view_processor.bake_from_multiview( |
| enhance_images["mr"], selected_camera_elevs, selected_camera_azims, selected_view_weights |
| ) |
| mask_mr_np = (mask_mr.squeeze(-1).cpu().numpy() * 255).astype(np.uint8) |
|
|
| |
| texture = self.view_processor.texture_inpaint(texture, mask_np) |
| self.render.set_texture(texture, force_set=True) |
| if "mr" in enhance_images: |
| texture_mr = self.view_processor.texture_inpaint(texture_mr, mask_mr_np) |
| self.render.set_texture_mr(texture_mr) |
|
|
| self.render.save_mesh(output_mesh_path, downsample=True) |
|
|
| if save_glb: |
| convert_obj_to_glb(output_mesh_path, output_mesh_path.replace(".obj", ".glb")) |
| output_glb_path = output_mesh_path.replace(".obj", ".glb") |
|
|
| return output_mesh_path |
|
|