File size: 5,411 Bytes
8448f43
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
import spaces  # Must precede torch and CUDA-touching project imports.

import logging
import os
from pathlib import Path
import time
from urllib.parse import urlencode

import gradio as gr
import numpy as np
from PIL import Image, ImageOps
import torch
from huggingface_hub import hf_hub_download

from bootstrap import prepare_solver

ROOT = Path(__file__).resolve().parent
MODEL_REVISION = "0a74d1d328ed6f47da5ec3632991c02a0266adca"
prepare_solver()

from engine import ReconstructionEngine


def weight(name: str) -> str:
    return hf_hub_download("LinzhouLi/RealDenseFace", filename=name, revision=MODEL_REVISION, token=False)


def flame_model() -> str:
    token = os.environ.get("FLAME_READ_TOKEN")
    if not token:
        raise RuntimeError("Configure the FLAME_READ_TOKEN Space secret with read access to the private demo assets repository.")
    return hf_hub_download(
        os.environ.get("FLAME_REPO_ID", "LinzhouLi/RealDenseFace-Demo-Assets"),
        filename="flame2023.pkl", revision=os.environ.get("FLAME_REVISION", "main"),
        token=token,
    )


print("[startup] Loading ViT-B and FaceBox", flush=True)
FLAME_MODEL_PATH = flame_model()
ENGINE = ReconstructionEngine(
    ROOT, weight("weights/realdenseface/vitb.pth"), weight("weights/facebox/face_box.pth"),
    weight("weights/flame/flame_assets.npz"), FLAME_MODEL_PATH,
)
print("[startup] Networks loaded; native contexts will be created in GPU requests", flush=True)


@spaces.GPU(duration=20)
def reconstruct(image: Image.Image):
    """Reconstruct a single portrait and return dense priors, overlay and mesh visualizations."""
    started = time.perf_counter()
    if image is None:
        raise gr.Error("Upload a portrait or choose an example first.")
    if image.width * image.height > 40_000_000:
        raise gr.Error("Please use an image with fewer than 40 million pixels.")
    image = ImageOps.exif_transpose(image).convert("RGB")
    image.thumbnail((1280, 1280), Image.Resampling.LANCZOS)
    if min(image.size) < 32:
        raise gr.Error("The image is too small. Please upload a larger portrait.")
    try:
        dense, overlay, mesh, report = ENGINE.reconstruct(np.ascontiguousarray(np.asarray(image)))
    except ValueError as exc:
        raise gr.Error(str(exc)) from exc
    except Exception:
        logging.exception("Reconstruction failed")
        raise gr.Error("Reconstruction failed. Please try a clearer portrait.")
    report["callback_seconds"] = round(time.perf_counter() - started, 3)
    print(f"[request] {report}", flush=True)
    return dense, overlay, mesh, f"Inference: {report['seconds']:.2f} s (excludes queue and transfer)."



with gr.Blocks(title="RealDenseFace", delete_cache=(3600, 3600)) as demo:
    gr.Markdown("""# RealDenseFace

### Real-time Monocular 3D Face Reconstruction from Dense UV-space Priors

[Paper](https://arxiv.org/abs/2608.09238) 路 [Project](https://gapszju.github.io/RealDenseFace/) 路 [Code](https://github.com/LinzhouLi/RealDenseFace) 路 [Models](https://huggingface.co/LinzhouLi/RealDenseFace)



Upload a portrait or select an example, then click **Reconstruct**. Powered by RealDenseFace ViT-B.

""")
    with gr.Row():
        with gr.Column(scale=1, min_width=300):
            with gr.Tabs():
                with gr.Tab("Input"):
                    image_input = gr.Image(type="pil", label="Input portrait", sources=["upload"], height=420, format="webp")
        with gr.Column(scale=1, min_width=300):
            with gr.Tabs():
                with gr.Tab("Mesh overlay"):
                    overlay = gr.Image(label="Mesh overlay", interactive=False, height=420, format="webp")
                with gr.Tab("Reconstructed mesh"):
                    mesh = gr.Image(label="Reconstructed mesh", interactive=False, height=420, format="webp")
                with gr.Tab("Dense priors"):
                    dense = gr.Image(label="Dense priors", interactive=False, height=420, format="webp")
    with gr.Row():
        run = gr.Button("Reconstruct", variant="primary", scale=1)
        status = gr.Markdown(scale=1)
    run.click(reconstruct, inputs=image_input, outputs=[dense, overlay, mesh, status],
              api_name="reconstruct", concurrency_limit=1, concurrency_id="gpu")
    gr.Examples(examples=[[str(p)] for p in sorted((ROOT / "examples").glob("*.jpg"))],
                inputs=[image_input], label="Examples", examples_per_page=10, cache_examples=False)
    gr.Markdown("Please use an image with one clearly visible face. ZeroGPU requests may queue before processing.")
    issue_url = "https://github.com/LinzhouLi/RealDenseFace/issues/new?" + urlencode({
        "title": "[Demo] Poor reconstruction result",
        "body": "### Problem\nPlease describe the problem.\n\n"
                "### Input image\nUpload your input image here.\n\n"
                "### Result image\nUpload your result image here.\n",
    })
    gr.Markdown(f"[Report a poor result]({issue_url})")
    image_input.change(lambda: (None, None, None, ""),
                       inputs=[], outputs=[dense, overlay, mesh, status],
                       queue=False, api_name=False)


if __name__ == "__main__":
    demo.queue(max_size=12).launch(show_error=False, blocked_paths=[FLAME_MODEL_PATH, str(Path(FLAME_MODEL_PATH).resolve())])