Added Dockerfile and refined the code

This commit is contained in:
Justin John
2024-03-07 21:33:09 +05:30
parent 2ac83fa562
commit 2931258e50
4 changed files with 178 additions and 146 deletions
+75 -129
View File
@@ -1,156 +1,102 @@
import argparse
import logging
import os
import sys
import time
from PIL import Image
import numpy as np
import rembg
import torch
from PIL import Image
from tsr.system import TSR
from tsr.utils import remove_background, resize_foreground, save_video
# Setup logging configuration
logging.basicConfig(format="%(asctime)s - %(levelname)s - %(message)s", level=logging.INFO)
# Print environment and GPU information
def print_environment_info():
logging.info("Environment variables:")
for key, value in os.environ.items():
logging.info(f"{key}: {value}")
logging.info('========================================')
logging.info(f'Python interpreter: {sys.executable}')
logging.info(f'PyTorch version: {torch.__version__}')
logging.info(f'CUDA Available: {torch.cuda.is_available()}')
logging.info(f'CUDA Device count: {torch.cuda.device_count()}')
logging.info(f'CUDA threads for parallelizing CPU operations: {torch.get_num_threads()}')
logging.info(f'CUDA architectures library was compiled for: {torch.cuda.get_arch_list()}')
logging.info('========================================')
print_environment_info()
class Timer:
def __init__(self):
self.items = {}
self.time_scale = 1000.0 # ms
self.time_unit = "ms"
self.start_times = {}
def start(self, name: str) -> None:
if torch.cuda.is_available():
torch.cuda.synchronize()
self.items[name] = time.time()
logging.info(f"{name} ...")
def end(self, name: str) -> float:
if name not in self.items:
return
if torch.cuda.is_available():
torch.cuda.synchronize()
start_time = self.items.pop(name)
delta = time.time() - start_time
t = delta * self.time_scale
logging.info(f"{name} finished in {t:.2f}{self.time_unit}.")
def start(self, name: str):
self.start_times[name] = time.time()
def end(self, name: str):
if name in self.start_times:
elapsed_time = time.time() - self.start_times[name]
logging.info(f"{name} finished in {elapsed_time * 1000:.2f}ms.")
del self.start_times[name]
timer = Timer()
def process_image(image_path, remove_bg=True, foreground_ratio=0.85):
image = Image.open(image_path).convert("RGB")
if remove_bg:
rembg_session = rembg.new_session()
image = remove_background(image, rembg_session)
image = resize_foreground(image, foreground_ratio)
image = np.array(image).astype(np.float32) / 255.0
image = image[:, :, :3] * image[:, :, 3:4] + (1 - image[:, :, 3:4]) * 0.5 # Blend with grey background
return Image.fromarray((image * 255.0).astype(np.uint8))
logging.basicConfig(
format="%(asctime)s - %(levelname)s - %(message)s", level=logging.INFO
)
parser = argparse.ArgumentParser()
parser.add_argument("image", type=str, nargs="+", help="Path to input image(s).")
parser.add_argument(
"--device",
default="cuda:0",
type=str,
help="Device to use. If no CUDA-compatible device is found, will fallback to 'cpu'. Default: 'cuda:0'",
)
parser.add_argument(
"--pretrained-model-name-or-path",
default="stabilityai/TripoSR",
type=str,
help="Path to the pretrained model. Could be either a huggingface model id is or a local path. Default: 'stabilityai/TripoSR'",
)
parser.add_argument(
"--chunk-size",
default=8192,
type=int,
help="Evaluation chunk size for surface extraction and rendering. Smaller chunk size reduces VRAM usage but increases computation time. 0 for no chunking. Default: 8192",
)
parser.add_argument(
"--no-remove-bg",
action="store_true",
help="If specified, the background will NOT be automatically removed from the input image, and the input image should be an RGB image with gray background and properly-sized foreground. Default: false",
)
parser.add_argument(
"--foreground-ratio",
default=0.85,
type=float,
help="Ratio of the foreground size to the image size. Only used when --no-remove-bg is not specified. Default: 0.85",
)
parser.add_argument(
"--output-dir",
default="output/",
type=str,
help="Output directory to save the results. Default: 'output/'",
)
parser.add_argument(
"--model-save-format",
default="obj",
type=str,
choices=["obj", "glb"],
help="Format to save the extracted mesh. Default: 'obj'",
)
parser.add_argument(
"--render",
action="store_true",
help="If specified, save a NeRF-rendered video. Default: false",
)
args = parser.parse_args()
def main(args):
output_dir = args.output_dir
os.makedirs(output_dir, exist_ok=True)
output_dir = args.output_dir
os.makedirs(output_dir, exist_ok=True)
device = args.device if torch.cuda.is_available() else "cpu"
device = args.device
if not torch.cuda.is_available():
device = "cpu"
timer.start("Initializing model")
model = TSR.from_pretrained(args.pretrained_model_name_or_path, config_name="config.yaml", weight_name="model.ckpt")
model.renderer.set_chunk_size(args.chunk_size)
model.to(device)
timer.end("Initializing model")
timer.start("Initializing model")
model = TSR.from_pretrained(
args.pretrained_model_name_or_path,
config_name="config.yaml",
weight_name="model.ckpt",
)
model.renderer.set_chunk_size(args.chunk_size)
model.to(device)
timer.end("Initializing model")
for i, image_path in enumerate(args.image):
timer.start(f"Processing image {i+1}")
processed_image = process_image(image_path, not args.no_remove_bg, args.foreground_ratio)
processed_image_path = os.path.join(output_dir, f"processed_{i}.png")
processed_image.save(processed_image_path)
timer.end(f"Processing image {i+1}")
timer.start("Processing images")
images = []
timer.start(f"Running model on image {i+1}")
with torch.no_grad():
scene_codes = model([np.array(processed_image).astype(np.float32) / 255.0], device=device)
timer.end(f"Running model on image {i+1}")
if args.no_remove_bg:
rembg_session = None
else:
rembg_session = rembg.new_session()
timer.start(f"Exporting mesh for image {i+1}")
meshes = model.extract_mesh(scene_codes, resolution=args.mc_resolution)
mesh_export_path = os.path.join(output_dir, f"mesh_{i}.{args.model_save_format}")
meshes[0].export(mesh_export_path, file_type=args.model_save_format)
timer.end(f"Exporting mesh for image {i+1}")
for i, image_path in enumerate(args.image):
if args.no_remove_bg:
image = np.array(Image.open(image_path).convert("RGB"))
else:
image = remove_background(Image.open(image_path), rembg_session)
image = resize_foreground(image, args.foreground_ratio)
image = np.array(image).astype(np.float32) / 255.0
image = image[:, :, :3] * image[:, :, 3:4] + (1 - image[:, :, 3:4]) * 0.5
image = Image.fromarray((image * 255.0).astype(np.uint8))
if not os.path.exists(os.path.join(output_dir, str(i))):
os.makedirs(os.path.join(output_dir, str(i)))
image.save(os.path.join(output_dir, str(i), f"input.png"))
images.append(image)
timer.end("Processing images")
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Run TSR model on input images.")
parser.add_argument("image", type=str, nargs="+", help="Path to input image(s).")
parser.add_argument("--device", default="cuda:0", type=str, help="Device to use. Defaults to 'cuda:0', falls back to 'cpu' if CUDA is not available.")
parser.add_argument("--pretrained-model-name-or-path", default="stabilityai/TripoSR", type=str, help="Pretrained model identifier or file path.")
parser.add_argument("--chunk-size", default=8192, type=int, help="Evaluation chunk size for rendering. Defaults to 8192.")
parser.add_argument("--mc-resolution", default=256, type=int, help="Resolution for marching cubes algorithm. Defaults to 256.")
parser.add_argument("--no-remove-bg", action="store_true", help="Don't remove background from input images.")
parser.add_argument("--foreground-ratio", default=0.85, type=float, help="Foreground ratio for resizing foreground. Defaults to 0.85.")
parser.add_argument("--output-dir", default="output/", type=str, help="Directory to save the output. Defaults to 'output/'.")
parser.add_argument("--model-save-format", default="obj", choices=["obj", "glb"], type=str, help="Format for saving the model. Supports 'obj' and 'glb'. Defaults to 'obj'.")
args = parser.parse_args()
for i, image in enumerate(images):
logging.info(f"Running image {i + 1}/{len(images)} ...")
timer.start("Running model")
with torch.no_grad():
scene_codes = model([image], device=device)
timer.end("Running model")
if args.render:
timer.start("Rendering")
render_images = model.render(scene_codes, n_views=30, return_type="pil")
for ri, render_image in enumerate(render_images[0]):
render_image.save(os.path.join(output_dir, str(i), f"render_{ri:03d}.png"))
save_video(
render_images[0], os.path.join(output_dir, str(i), f"render.mp4"), fps=30
)
timer.end("Rendering")
timer.start("Exporting mesh")
meshes = model.extract_mesh(scene_codes)
meshes[0].export(os.path.join(output_dir, str(i), f"mesh.{args.model_save_format}"))
timer.end("Exporting mesh")
main(args)