刘云飞 commited on
Commit
89984ef
·
1 Parent(s): e559d8c

add init files

Browse files
README.md CHANGED
@@ -12,3 +12,23 @@ short_description: Official demo of the ICLR 2025 paper
12
  ---
13
 
14
  Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
  ---
13
 
14
  Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
15
+
16
+
17
+ # TEASER: Token Enhanced Spatial Modeling for Expressions Reconstruction
18
+
19
+
20
+ This repository is the official implementation of the ICLR 2025 paper [TEASER: Token Enhanced Spatial Modeling For Expressions Reconstruction](https://arxiv.org/abs/2502.10982).
21
+
22
+ <p align="center">
23
+ <a href='https://arxiv.org/abs/2502.10982' style='padding-left: 0.5rem;'>
24
+ <img src='https://img.shields.io/badge/arXiv-2502.10982-brightgreen' alt='arXiv'>
25
+ </a>
26
+ <a href='https://julia-cherry.github.io/TEASER-PAGE/' style='padding-left: 0.5rem;'>
27
+ <img src='https://img.shields.io/badge/Website-Project Page-blue?style=flat&logo=Google%20chrome&logoColor=blue' alt='Project Page'>
28
+ </a>
29
+ </p>
30
+
31
+ <p align="center">
32
+ <img src="https://github.com/Pixel-Talk/TEASER/blob/main/samples/show.png?raw=true">
33
+ TEASER reconstructs precise 3D facial expression and generates high-fidelity face image through estimating hybrid parameters for 3D facial reconstruction.
34
+ </p>
app.py ADDED
@@ -0,0 +1,324 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import time
3
+ import gradio as gr
4
+ from pathlib import Path
5
+ from functools import partial
6
+ from utils.rprint import rlog as log
7
+
8
+ OUTPUT_DIR = 'results'
9
+ DEVICES = '0'
10
+ IN_IMAGE_DIR = 'assets/demo/images'
11
+ OUTPUT_DIR = 'results/tracking_results'
12
+ EXCHANGE_DIR = 'results/exchanging_results'
13
+
14
+
15
+ def run_cmd(cmd, current_dir):
16
+ log(cmd)
17
+ this_day = time.strftime('%Y-%m-%d')
18
+ cur_log_fp = os.path.join(current_dir, 'logs', f'log-{this_day}.log')
19
+ if not os.path.exists(cur_log_fp):
20
+ os.makedirs(os.path.dirname(cur_log_fp), exist_ok=True)
21
+ with open(cur_log_fp, 'a') as f:
22
+ current_time = time.strftime('%Y-%m-%d %H:%M:%S')
23
+ f.write(f'[{current_time}]' + cmd + '\n')
24
+ os.system(cmd)
25
+
26
+
27
+ def check_process_status(source_image):
28
+ """Check if the processing is complete and return the result video if available"""
29
+ if not OUTPUT_DIR or not os.path.exists(OUTPUT_DIR):
30
+ return "Processing hasn't started yet.", None
31
+
32
+ src_name = os.path.splitext(os.path.basename(source_image))[0]
33
+ current_dir = os.path.dirname(os.path.abspath(__file__))
34
+ output_dir = os.path.join(current_dir, OUTPUT_DIR)
35
+ output_case_dir = os.path.join(output_dir, src_name)
36
+ out_img_fp = os.path.join(output_case_dir, 'reconstruct.jpg')
37
+ out_obj_fp = os.path.join(output_case_dir, 'head.obj')
38
+
39
+ log('Try to find => ' + output_case_dir)
40
+
41
+ if not os.path.exists(out_img_fp) and not os.path.exists(out_obj_fp):
42
+ return "Still processing... You can leave but keep this page open. ⏳", None, None
43
+
44
+ return "Processing completed successfully! 🎉", out_img_fp, out_obj_fp
45
+
46
+
47
+ def process_inference(source_image, progress=gr.Progress()):
48
+
49
+ progress(0.1, desc="Files saved, starting processing...")
50
+
51
+ try:
52
+
53
+ current_dir = os.path.dirname(os.path.abspath(__file__))
54
+ output_dir = os.path.join(current_dir, OUTPUT_DIR)
55
+
56
+ my_run_cmd = partial(run_cmd, current_dir=current_dir)
57
+
58
+ src_name = os.path.splitext(os.path.basename(source_image))[0]
59
+ output_case_dir = os.path.join(output_dir, src_name)
60
+ os.makedirs(output_case_dir, exist_ok=True)
61
+ out_img_fp = os.path.join(output_case_dir, 'reconstruct.jpg')
62
+ out_obj_fp = os.path.join(output_case_dir, 'head.obj')
63
+
64
+ if os.path.exists(out_img_fp) and os.path.exists(out_obj_fp):
65
+ log(f'🐶 Result has been generated, skipping...')
66
+ else:
67
+ src_img_fp = os.path.join(current_dir, source_image)
68
+ my_run_cmd(f'PYTHONPATH=. python cli.py -i {src_img_fp} ' +
69
+ f' -o {output_case_dir}')
70
+ log(f'Done! The result is saved in {output_case_dir}')
71
+
72
+ progress(1.0, desc="🎉 Done! ")
73
+
74
+ return "🎉 Done! ", out_img_fp, out_obj_fp
75
+
76
+ except Exception as e:
77
+ return f"Error occurred: {str(e)}", None, None
78
+
79
+
80
+ def process_image_exp(source_image, driven_image, progress=gr.Progress()):
81
+ log("Processing image...")
82
+ progress(0.1, desc="Processing image...")
83
+
84
+ current_dir = os.path.dirname(os.path.abspath(__file__))
85
+ output_dir = os.path.join(current_dir, EXCHANGE_DIR)
86
+
87
+ my_run_cmd = partial(run_cmd, current_dir=current_dir)
88
+
89
+ src_name = os.path.splitext(os.path.basename(source_image))[0]
90
+ drv_name = os.path.splitext(os.path.basename(driven_image))[0]
91
+ os.makedirs(output_dir, exist_ok=True)
92
+ output_case_fp = os.path.join(output_dir, f'{src_name}__{drv_name}-exp.jpg')
93
+
94
+ if os.path.exists(output_case_fp):
95
+ log(f'Found existing result: {output_case_fp}')
96
+ return "🎉 Done! ", output_case_fp
97
+
98
+ src_img_fp = os.path.join(current_dir, source_image)
99
+ drv_img_fp = os.path.join(current_dir, driven_image)
100
+ my_run_cmd(f'PYTHONPATH=. python cli.py -s {src_img_fp} ' +
101
+ f' -d {drv_img_fp} ' +
102
+ f' -xo {output_case_fp} --type exp')
103
+ progress(1.0, desc="🎉 Done! ")
104
+
105
+ return None, output_case_fp
106
+
107
+
108
+ def process_image_token(source_image, driven_image, progress=gr.Progress()):
109
+ log("Processing image...")
110
+ progress(0.1, desc="Processing image...")
111
+
112
+ current_dir = os.path.dirname(os.path.abspath(__file__))
113
+ output_dir = os.path.join(current_dir, EXCHANGE_DIR)
114
+
115
+ my_run_cmd = partial(run_cmd, current_dir=current_dir)
116
+
117
+ src_name = os.path.splitext(os.path.basename(source_image))[0]
118
+ drv_name = os.path.splitext(os.path.basename(driven_image))[0]
119
+ os.makedirs(output_dir, exist_ok=True)
120
+ output_case_fp = os.path.join(output_dir, f'{src_name}__{drv_name}-token.jpg')
121
+
122
+ if os.path.exists(output_case_fp):
123
+ log(f'Found existing result: {output_case_fp}')
124
+ return "🎉 Done! ", output_case_fp
125
+
126
+ src_img_fp = os.path.join(current_dir, source_image)
127
+ drv_img_fp = os.path.join(current_dir, driven_image)
128
+ my_run_cmd(f'PYTHONPATH=. python cli.py -s {src_img_fp} ' +
129
+ f' -d {drv_img_fp} ' +
130
+ f' -xo {output_case_fp} --type token')
131
+ progress(1.0, desc="🎉 Done! ")
132
+
133
+ return None, output_case_fp
134
+
135
+
136
+ # Create the Gradio interface
137
+ with gr.Blocks(title="TEASER demo", css="""
138
+ .image-container {
139
+ position: relative;
140
+ display: inline-block;
141
+ width: 100%;
142
+ height: 100%;
143
+ }
144
+ .overlay-button {
145
+ position: absolute !important;
146
+ top: 50% !important;
147
+ left: 50% !important;
148
+ transform: translate(-50%, -50%) !important;
149
+ opacity: 0;
150
+ transition: opacity 0.3s;
151
+ background: rgba(0,0,0,0.7) !important;
152
+ color: white !important;
153
+ border: none !important;
154
+ z-index: 1;
155
+ }
156
+ .image-container:hover .overlay-button {
157
+ opacity: 1;
158
+ }
159
+ .gradio-image {
160
+ position: relative !important;
161
+ width: 100% !important;
162
+ height: 100% !important;
163
+ }
164
+ .scrollable-column {
165
+ height: 300px !important;
166
+ overflow-y: auto !important;
167
+ padding-right: 10px;
168
+ }
169
+ .scrollable-column::-webkit-scrollbar {
170
+ width: 8px;
171
+ }
172
+ .scrollable-column::-webkit-scrollbar-track {
173
+ background: #f1f1f1;
174
+ border-radius: 4px;
175
+ }
176
+ .scrollable-column::-webkit-scrollbar-thumb {
177
+ background: #888;
178
+ border-radius: 4px;
179
+ }
180
+ .scrollable-column::-webkit-scrollbar-thumb:hover {
181
+ background: #555;
182
+ }
183
+ .circular-image {
184
+ border-radius: 50% !important;
185
+ overflow: hidden !important;
186
+ width: 200px !important;
187
+ height: 200px !important;
188
+ object-fit: cover !important;
189
+ }
190
+ """) as demo:
191
+
192
+ with gr.Row():
193
+ with gr.Column(scale=1):
194
+ gr.Image("https://github.com/Pixel-Talk/TEASER/blob/main/samples/teaser.png?raw=true", show_label=False, height=170, container=False, interactive=False, elem_classes=["circular-image"])
195
+ with gr.Column(scale=6):
196
+ gr.Markdown("""
197
+ ## TEASER: Token-EnhAanced Spatial Modeling for Expression Reconstruction
198
+ """)
199
+ with gr.Row():
200
+ gr.Markdown("""""")
201
+ gr.Markdown("""
202
+ <div style="text-align: center;">
203
+ <div style="display: inline-block;">
204
+ <a href='https://arxiv.org/abs/2502.10982' style='padding-left: 0.5rem;'>
205
+ <img src='https://img.shields.io/badge/arXiv-2502.10982-brightgreen' alt='arXiv'></a></div><div style="display: inline-block;">
206
+ <a href='https://julia-cherry.github.io/TEASER-PAGE/' style='padding-left: 0.5rem;'>
207
+ <img src='https://img.shields.io/badge/Website-Project Page-blue?style=flat&logo=Google%20chrome&logoColor=blue' alt='Project Page'></a></div></div>
208
+
209
+ > TEASER reconstructs precise 3D facial expression and generates high-fidelity face image through estimating hybrid parameters for 3D facial reconstruction.
210
+
211
+ **Upload a source image we will generate its 3D expressions, we also provide some applications.**
212
+ """)
213
+
214
+ with gr.Tab("3D Expression Reconstruction"):
215
+ with gr.Row():
216
+ with gr.Column():
217
+ source_image = gr.Image(label="Input Image", type="filepath", height=600)
218
+ process_btn = gr.Button("Inference", variant="primary")
219
+
220
+ with gr.Column():
221
+ check_btn = gr.Button("Check Progress 🔄", variant="secondary")
222
+ output_message = gr.Textbox(label="Status")
223
+ output_image = gr.Image(label="Generated results (Rendered Mesh | Generated Image)")
224
+ model_rlt = gr.Model3D(label="Generated 3D expression model")
225
+
226
+ with gr.Row():
227
+ process_btn.click(
228
+ fn=process_inference,
229
+ inputs=[source_image],
230
+ outputs=[output_message, output_image, model_rlt]
231
+ )
232
+
233
+ check_btn.click(
234
+ fn=check_process_status,
235
+ inputs=[source_image],
236
+ outputs=[output_message, output_image, model_rlt]
237
+ )
238
+
239
+ gr.Markdown("---")
240
+ gr.Markdown("### Example Input Images (Click to use)")
241
+ with gr.Row(elem_classes=["scrollable-column"]):
242
+ example_images = [f for f in sorted(os.listdir(IN_IMAGE_DIR)) if f.endswith(('.png', '.jpg', '.jpeg'))]
243
+ for img in example_images:
244
+ img_path = os.path.join(IN_IMAGE_DIR, img)
245
+ with gr.Column(elem_classes=["image-container"]):
246
+ gr.Image(value=img_path, show_label=True, label=img, height=250)
247
+ select_img_btn = gr.Button("Use this image", size="sm", elem_classes=["overlay-button"])
248
+ select_img_btn.click(
249
+ fn=lambda x=img_path: x,
250
+ outputs=[source_image]
251
+ )
252
+
253
+ with gr.Tab("Exchange Token and Expression"):
254
+ gr.Markdown("### Input Image")
255
+ with gr.Row():
256
+ with gr.Column():
257
+ source_image_1 = gr.Image(label="Source Image", type="filepath", height=300)
258
+ driven_image = gr.Image(label="Driven Image", type="filepath", height=300)
259
+ with gr.Row():
260
+ process_x_exp_btn = gr.Button("Exchange Expression", variant="primary")
261
+ process_x_token_btn = gr.Button("Exchange Token", variant="primary")
262
+ with gr.Column():
263
+ output_message_1 = gr.Textbox(label="Status", interactive=False)
264
+ exchange_result = gr.Image(label="Generated results (Rendered Mesh | Generated Image)")
265
+
266
+ process_x_exp_btn.click(fn=process_image_exp, inputs=[source_image_1, driven_image], outputs=[output_message_1, exchange_result])
267
+ process_x_token_btn.click(fn=process_image_token, inputs=[source_image_1, driven_image], outputs=[output_message_1, exchange_result])
268
+
269
+ with gr.Row():
270
+ with gr.Column():
271
+ gr.Markdown("---")
272
+ gr.Markdown("### Example Source Images (Click to use)")
273
+ with gr.Row(elem_classes=["scrollable-column"]):
274
+ example_images = [f for f in sorted(os.listdir(IN_IMAGE_DIR)) if f.endswith(('.png', '.jpg', '.jpeg'))]
275
+ for img in example_images:
276
+ img_path = os.path.join(IN_IMAGE_DIR, img)
277
+ with gr.Column(elem_classes=["image-container"]):
278
+ gr.Image(value=img_path, show_label=True, label=img, height=250)
279
+ select_img_btn = gr.Button("Use this image", size="sm", elem_classes=["overlay-button"])
280
+ select_img_btn.click(
281
+ fn=lambda x=img_path: x,
282
+ outputs=[source_image_1]
283
+ )
284
+ with gr.Column():
285
+ gr.Markdown("---")
286
+ gr.Markdown("### Example Driven Images (Click to use)")
287
+ with gr.Row(elem_classes=["scrollable-column"]):
288
+ example_images = [f for f in sorted(os.listdir(IN_IMAGE_DIR)) if f.endswith(('.png', '.jpg', '.jpeg'))]
289
+ for img in example_images:
290
+ img_path = os.path.join(IN_IMAGE_DIR, img)
291
+ with gr.Column(elem_classes=["image-container"]):
292
+ gr.Image(value=img_path, show_label=True, label=img, height=250)
293
+ select_img_btn = gr.Button("Use this image", size="sm", elem_classes=["overlay-button"])
294
+ select_img_btn.click(
295
+ fn=lambda x=img_path: x,
296
+ outputs=[driven_image]
297
+ )
298
+
299
+ gr.Markdown("""
300
+ ---
301
+ 📝 **Citation**
302
+ <br>
303
+ If our work is useful for your research, please consider citing:
304
+ ```bibtex
305
+ @inproceedings{liu2025TEASER,
306
+ title={TEASER: Token Enhanced Spatial Modeling for Expressions Reconstruction},
307
+ author={Liu, Yunfei and Zhu, Lei and Lin, Lijian and Zhu, Ye and Zhang, Ailing and Li, Yu},
308
+ booktitle={ICLR},
309
+ year={2025}
310
+ }
311
+ ```
312
+ 📧 **Contact**
313
+ <br>
314
+ If you have any questions, please feel free to send a message to <b>liuyunfei.cs@gmail.com</b> or open an issue on the [Github repo](https://github.com/Pixel-Talk/TEASER).
315
+ """)
316
+
317
+ if __name__ == "__main__":
318
+ import argparse
319
+
320
+ parser = argparse.ArgumentParser()
321
+ parser.add_argument("--share", action="store_true", help="Whether to share the app.")
322
+ args = parser.parse_args()
323
+
324
+ demo.launch(allowed_paths=["."], share=args.share)
assets/models/flame.pt ADDED
File without changes
assets/models/teaser.onnx ADDED
File without changes
cli.py ADDED
File without changes
utils/mediapipe_utils.py ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import mediapipe as mp
2
+ from mediapipe.tasks import python
3
+ from mediapipe.tasks.python import vision
4
+ import cv2
5
+ import numpy as np
6
+
7
+ base_options = python.BaseOptions(model_asset_path='assets/face_landmarker.task')
8
+ options = vision.FaceLandmarkerOptions(base_options=base_options,
9
+ output_face_blendshapes=True,
10
+ output_facial_transformation_matrixes=True,
11
+ num_faces=1,
12
+ min_face_detection_confidence=0.1,
13
+ min_face_presence_confidence=0.1
14
+ )
15
+ detector = vision.FaceLandmarker.create_from_options(options)
16
+
17
+
18
+ def run_mediapipe(image):
19
+ # print(image.shape)
20
+ image_numpy = cv2.cvtColor(image,cv2.COLOR_BGR2RGB)
21
+
22
+ # STEP 3: Load the input image.
23
+ image = mp.Image(image_format=mp.ImageFormat.SRGB, data=image_numpy)
24
+
25
+
26
+ # STEP 4: Detect face landmarks from the input image.
27
+ detection_result = detector.detect(image)
28
+
29
+ if len (detection_result.face_landmarks) == 0:
30
+ print('No face detected')
31
+ return None
32
+
33
+ face_landmarks = detection_result.face_landmarks[0]
34
+
35
+ face_landmarks_numpy = np.zeros((478, 3))
36
+
37
+ for i, landmark in enumerate(face_landmarks):
38
+ face_landmarks_numpy[i] = [landmark.x*image.width, landmark.y*image.height, landmark.z]
39
+
40
+ return face_landmarks_numpy
utils/rprint.py ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # coding: utf-8
2
+
3
+ """
4
+ custom print and log functions
5
+ """
6
+
7
+ __all__ = ['rprint', 'rlog']
8
+
9
+ try:
10
+ from rich.console import Console
11
+ console = Console()
12
+ rprint = console.print
13
+ rlog = console.log
14
+ except:
15
+ rprint = print
16
+ rlog = print