| |
|
|
| import gradio as gr |
| import os |
|
|
| import subprocess |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| from apps.infer import generate_model, generate_video |
|
|
| |
|
|
| title = ''' |
| # Unconstrained & Detailed Clothed Human Digitization (ECON + ControlNet) |
| ### ECON: Explicit Clothed humans Optimized via Normal integration (CVPR 2023, Highlight) |
| ''' |
|
|
| bottom = ''' |
| |
| |
| #### Citation |
| ``` |
| @inproceedings{xiu2023econ, |
| title = {{ECON: Explicit Clothed humans Optimized via Normal integration}}, |
| author = {Xiu, Yuliang and Yang, Jinlong and Cao, Xu and Tzionas, Dimitrios and Black, Michael J.}, |
| booktitle = {Proceedings of the IEEE/CVF Conference on Computer Vision and Pattern Recognition (CVPR)}, |
| month = {June}, |
| year = {2023}, |
| } |
| ``` |
| |
| |
| <details> |
| |
| <summary>More</summary> |
| |
| #### Acknowledgments: |
| - [controlnet-openpose](https://huggingface.co/spaces/diffusers/controlnet-openpose) |
| - [TEXTure](https://huggingface.co/spaces/TEXTurePaper/TEXTure) |
| |
| |
| #### Image Credits |
| |
| * [Pinterest](https://www.pinterest.com/search/pins/?q=parkour&rs=sitelinks_searchbox) |
| |
| #### Related works |
| |
| * [ICON @ MPI-IS](https://icon.is.tue.mpg.de/) |
| * [MonoPort @ USC](https://xiuyuliang.cn/monoport) |
| * [Phorhum @ Google](https://phorhum.github.io/) |
| * [PIFuHD @ Meta](https://shunsukesaito.github.io/PIFuHD/) |
| * [PaMIR @ Tsinghua](http://www.liuyebin.com/pamir/pamir.html) |
| |
| </details> |
| |
| <center> |
| <a href="https://huggingface.co/spaces/Yuliang/ECON?duplicate=true"><img src="https://huggingface.co/datasets/huggingface/badges/raw/main/duplicate-this-space-lg-dark.svg"/></a> |
| <h2> Generate pose & prompt-guided images / Upload photos / Use examples → Submit Image (~3min) → Generate Video (~3min) </h2> |
| <h2><span style="color:red">ECON is only suitable for "humanoid images" and will not work well on cartoons with non-human shapes.</span></h2> |
| </center> |
| ''' |
|
|
| description = ''' |
| <table> |
| <th width="20%"> |
| <ul> |
| <li><strong>Homepage</strong> <a href="https://econ.is.tue.mpg.de/">econ.is.tue.mpg.de</a></li> |
| <li><strong>Code</strong> <a href="https://github.com/YuliangXiu/ECON">YuliangXiu/ECON</a></li> |
| <li><strong>Paper</strong> <a href="https://arxiv.org/abs/2212.07422">arXiv</a>, <a href="https://readpaper.com/paper/4736821012688027649">ReadPaper</a></li> |
| <li><strong>Chatroom</strong> <a href="https://discord.gg/Vqa7KBGRyk">Discord</a></li> |
| </ul> |
| <br> |
| <ul> |
| <li><strong>Colab Notebook</strong> <a href='https://colab.research.google.com/drive/1YRgwoRCZIrSB2e7auEWFyG10Xzjbrbno?usp=sharing'><img style="display: inline-block;" src='https://colab.research.google.com/assets/colab-badge.svg' alt='Google Colab'></a></li> |
| <li><strong>Blender Plugin</strong> <a href='https://carlosedubarreto.gumroad.com/l/CEB_ECON'><img style="display: inline-block;" src='https://img.shields.io/badge/Blender-F6DDCC.svg?logo=Blender' alt='Blender'></a></li> |
| <li><strong>Docker Image</strong> <a href='https://github.com/YuliangXiu/ECON/blob/master/docs/installation-docker.md'><img style="display: inline-block;" src='https://img.shields.io/badge/Docker-9cf.svg?logo=Docker' alt='Docker'></a></li> |
| <li><strong>Windows Setup</strong> <a href="https://github.com/YuliangXiu/ECON/blob/master/docs/installation-windows.md"><img style="display: inline-block;" src='https://img.shields.io/badge/Windows-00a2ed.svg?logo=Windows' akt='Windows'></a></li> |
| </ul> |
| |
| <br> |
| <a href="https://twitter.com/yuliangxiu"><img alt="Twitter Follow" src="https://img.shields.io/twitter/follow/yuliangxiu?style=social"></a><br> |
| <iframe src="https://ghbtns.com/github-btn.html?user=yuliangxiu&repo=ECON&type=star&count=true&v=2&size=small" frameborder="0" scrolling="0" width="100" height="20"></iframe> |
| </th> |
| <th width="40%"> |
| <iframe width="560" height="315" src="https://www.youtube.com/embed/5PEd_p90kS0" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe> |
| </th> |
| <th width="40%"> |
| <iframe width="560" height="315" src="https://www.youtube.com/embed/sbWZbTf6ZYk" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe> |
| </th> |
| </table> |
| ''' |
|
|
| from controlnet_aux import OpenposeDetector |
| from diffusers import StableDiffusionControlNetPipeline, ControlNetModel |
| from diffusers import UniPCMultistepScheduler |
| import gradio as gr |
| import torch |
| import base64 |
| from io import BytesIO |
| from PIL import Image |
|
|
| |
| canvas_html = "<pose-canvas id='canvas-root' style='display:flex;max-width: 500px;margin: 0 auto;'></pose-canvas>" |
| load_js = """ |
| async () => { |
| const url = "https://huggingface.co/datasets/radames/gradio-components/raw/main/pose-gradio.js" |
| fetch(url) |
| .then(res => res.text()) |
| .then(text => { |
| const script = document.createElement('script'); |
| script.type = "module" |
| script.src = URL.createObjectURL(new Blob([text], { type: 'application/javascript' })); |
| document.head.appendChild(script); |
| }); |
| } |
| """ |
| get_js_image = """ |
| async (image_in_img, prompt, image_file_live_opt, live_conditioning) => { |
| const canvasEl = document.getElementById("canvas-root"); |
| const data = canvasEl? canvasEl._data : null; |
| return [image_in_img, prompt, image_file_live_opt, data] |
| } |
| """ |
|
|
| |
| cached = False |
|
|
| |
| pose_model = OpenposeDetector.from_pretrained("lllyasviel/ControlNet") |
| controlnet = ControlNetModel.from_pretrained( |
| "lllyasviel/sd-controlnet-openpose", torch_dtype=torch.float16 |
| ) |
| pipe = StableDiffusionControlNetPipeline.from_pretrained( |
| "runwayml/stable-diffusion-v1-5", |
| controlnet=controlnet, |
| safety_checker=None, |
| torch_dtype=torch.float16 |
| ) |
| pipe.scheduler = UniPCMultistepScheduler.from_config(pipe.scheduler.config) |
|
|
| |
| |
| pipe.enable_model_cpu_offload() |
|
|
| |
| pipe.enable_xformers_memory_efficient_attention() |
|
|
| |
| generator = torch.manual_seed(0) |
|
|
| hint_prompts = ''' |
| <strong>Hints</strong>: <br> |
| best quality, extremely detailed, solid color background, |
| super detail, high detail, edge lighting, soft focus, |
| light and dark contrast, 8k, edge lighting, 3d, c4d, |
| blender, oc renderer, ultra high definition, 3d rendering |
| ''' |
|
|
|
|
| def get_pose(image): |
| return pose_model(image) |
|
|
| import sys |
|
|
| def read_logs(): |
| sys.stdout.flush() |
| with open("output.log", "r") as f: |
| return f.read() |
|
|
|
|
| def generate_images(image, prompt, image_file_live_opt='file', live_conditioning=None): |
| if image is None and 'image' not in live_conditioning: |
| raise gr.Error("Please provide an image") |
| try: |
| if image_file_live_opt == 'file': |
| pose = get_pose(image) |
| elif image_file_live_opt == 'webcam': |
| base64_img = live_conditioning['image'] |
| image_data = base64.b64decode(base64_img.split(',')[1]) |
| pose = Image.open(BytesIO(image_data)).convert('RGB').resize((512, 512)) |
| output = pipe( |
| prompt, |
| pose, |
| generator=generator, |
| num_images_per_prompt=3, |
| num_inference_steps=50, |
| ) |
| all_outputs = [] |
| all_outputs.append(pose) |
| for image in output.images: |
| all_outputs.append(image) |
| return all_outputs, all_outputs |
| except Exception as e: |
| raise gr.Error(str(e)) |
|
|
|
|
| def toggle(choice): |
| if choice == "file": |
| return gr.update(visible=True, value=None), gr.update(visible=False, value=None) |
| elif choice == "webcam": |
| return gr.update(visible=False, value=None), gr.update(visible=True, value=canvas_html) |
|
|
|
|
| examples_pose = 'examples/pose' |
| examples_cloth = 'examples/cloth' |
|
|
| def show_video(): |
| return gr.update(visible=True), gr.update(visible=True) |
|
|
| with gr.Blocks() as demo: |
| |
| gr.Markdown(title) |
| gr.HTML(description) |
| gr.Markdown(bottom) |
|
|
| out_lst = [] |
| with gr.Row(): |
| with gr.Column(): |
| with gr.Row(): |
|
|
| live_conditioning = gr.JSON(value={}, visible=False) |
|
|
| with gr.Column(): |
| image_file_live_opt = gr.Radio(["file", "webcam"], |
| value="file", |
| label="How would you like to upload your image?") |
|
|
| with gr.Row(): |
| image_in_img = gr.Image( |
| visible=True, type="pil", label="Image for Pose" |
| ) |
| canvas = gr.HTML(None, elem_id="canvas_html", visible=False) |
|
|
| image_file_live_opt.change( |
| fn=toggle, |
| inputs=[image_file_live_opt], |
| outputs=[image_in_img, canvas], |
| queue=False |
| ) |
| prompt = gr.Textbox( |
| label="Enter your prompt to synthesise the image", |
| max_lines=10, |
| placeholder="best quality, extremely detailed", |
| ) |
|
|
| gr.Markdown(hint_prompts) |
|
|
| with gr.Column(): |
| gallery = gr.Gallery(label="Generated Images", columns=[2],rows=[2]) |
| gallery_cache = gr.State() |
|
|
| gr.Markdown( |
| ''' |
| <center> |
| <strong>Click the target generated image for Reconstruction.</strong> <br> |
| ↓ |
| </center> |
| ''' |
| ) |
|
|
| inp = gr.Image(type="filepath", label="Input Image for Reconstruction") |
| fitting_step = gr.Slider( |
| 10, |
| 100, |
| step=10, |
| label='Fitting steps (Slower yet Better-aligned SMPL-X)', |
| value=50 |
| ) |
|
|
| with gr.Row(): |
| btn_sample = gr.Button("Generate Image") |
| btn_submit = gr.Button("Submit Image (~3min)") |
|
|
| btn_sample.click( |
| fn=generate_images, |
| inputs=[image_in_img, prompt, image_file_live_opt, live_conditioning], |
| outputs=[gallery, gallery_cache], |
| js=get_js_image |
| ) |
|
|
| def get_select_index(cache, evt: gr.SelectData): |
| return cache[evt.index] |
|
|
| gallery.select( |
| fn=get_select_index, |
| inputs=[gallery_cache], |
| outputs=[inp], |
| ) |
|
|
| with gr.Row(): |
| |
| gr.Examples( |
| examples=examples_pose, |
| inputs=[inp], |
| cache_examples=cached, |
| fn=generate_model, |
| outputs=out_lst, |
| label="Hard Pose Examples" |
| ) |
|
|
| gr.Examples( |
| examples=examples_cloth, |
| inputs=[inp], |
| cache_examples=cached, |
| fn=generate_model, |
| outputs=out_lst, |
| label="Loose Cloth Examples" |
| ) |
|
|
|
|
| with gr.Column(): |
| overlap_inp = gr.Image(type="filepath", label="Image Normal Overlap") |
| out_final = gr.Model3D( |
| clear_color=[0.0, 0.0, 0.0, 0.0], label="Clothed human", elem_id="avatar" |
| ) |
| out_smpl = gr.Model3D( |
| clear_color=[0.0, 0.0, 0.0, 0.0], label="SMPL-X body (via PIXIE)", elem_id="avatar" |
| ) |
|
|
| vis_tensor_path = gr.State() |
| |
|
|
| |
| btn_video = gr.Button("Generate Video (~3min)", visible=False) |
| out_vid = gr.Video(label="Shared on Twitter with #ECON", visible=False) |
|
|
| out_lst = [out_smpl, out_final, overlap_inp, vis_tensor_path] |
|
|
| btn_video.click( |
| fn=generate_video, |
| inputs=[vis_tensor_path], |
| outputs=[out_vid], |
| ) |
|
|
| btn_submit.click(fn=generate_model, inputs=[inp, fitting_step], outputs=out_lst) |
| btn_submit.click(fn=show_video, outputs=[btn_video, out_vid]) |
| |
| demo.load(None, None, None, js=load_js) |
|
|
| if __name__ == "__main__": |
|
|
| demo.queue() |
| demo.launch(max_threads=4) |
| |