| import gradio as gr |
| import os |
| import cv2 |
| import numpy as np |
| from tqdm import tqdm |
| from moviepy.editor import * |
| import tempfile |
|
|
|
|
| import esim_py |
| from infererence import process_events, Ev2Hands |
| from settings import OUTPUT_HEIGHT, OUTPUT_WIDTH |
|
|
| ev2hands = Ev2Hands() |
|
|
|
|
| def create_video(frames, fps, path): |
| clip = ImageSequenceClip(frames, fps=fps) |
| clip.write_videofile(path, fps=fps) |
| return path |
|
|
|
|
| def get_frames(video_in, trim_in): |
| cap = cv2.VideoCapture(video_in) |
| |
| fps = cap.get(cv2.CAP_PROP_FPS) |
| stop_frame = int(trim_in * fps) |
| |
| print("video fps: " + str(fps)) |
| |
| frames = [] |
| i = 0 |
| while(cap.isOpened()): |
| ret, frame = cap.read() |
| if not ret: |
| break |
|
|
| frame = cv2.resize(frame, (OUTPUT_WIDTH, OUTPUT_HEIGHT)) |
| frames.append(frame) |
|
|
| if i > stop_frame: |
| break |
| |
| i += 1 |
| |
| |
| cap.release() |
| |
| return frames, fps |
|
|
|
|
| def change_model(model_slider, eventframe_files, mesh_files): |
| if mesh_files is None: |
| return None, None, None |
| |
| if model_slider >= len(mesh_files): |
| model_slider = len(mesh_files) |
|
|
| idx = int(model_slider - 1) |
|
|
| event_frame_path = eventframe_files[idx] |
| mesh_path = mesh_files[idx] |
|
|
| return model_slider, event_frame_path, mesh_path |
|
|
|
|
| def infer(video_inp, trim_in, threshold): |
| if video_inp is None: |
| return None, None, None, None |
| |
| frames, fps = get_frames(video_inp, trim_in) |
| ts_s = 1 / fps |
| ts_ns = ts_s * 1e9 |
|
|
| POS_THRESHOLD = NEG_THRESHOLD = threshold |
| REF_PERIOD = 0 |
| |
| print(f'Threshold: {threshold}') |
|
|
| esim = esim_py.EventSimulator(POS_THRESHOLD, NEG_THRESHOLD, REF_PERIOD, 1e-4, True) |
| is_init = False |
|
|
| temp_folder = f'temp/{next(tempfile._get_candidate_names())}' |
| |
| event_frame_folder = f'{temp_folder}/event_frames' |
| mesh_folder = f'{temp_folder}/meshes' |
|
|
| os.makedirs(event_frame_folder, exist_ok=True) |
| os.makedirs(mesh_folder, exist_ok=True) |
|
|
| mesh_paths = list() |
| event_frames = list() |
| for idx, frame in enumerate(tqdm(frames)): |
| frame_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) |
| frame_log = np.log(frame_gray.astype("float32") / 255 + 1e-4) |
| |
| current_ts_ns = idx * ts_ns |
|
|
| if not is_init: |
| esim.init(frame_log, current_ts_ns) |
| is_init = True |
| continue |
|
|
| events = esim.generateEventFromCVImage(frame_log, current_ts_ns) |
| data = process_events(events) |
|
|
| mesh = ev2hands(data) |
| mesh_path = f'{mesh_folder}/{idx}.obj' |
| mesh.export(mesh_path) |
| mesh_paths.append(mesh_path) |
|
|
| event_frame = data['event_frame'].cpu().numpy().astype(dtype=np.uint8) |
| |
| event_frame_path = f'{event_frame_folder}/{idx}.jpg' |
| cv2.imwrite(event_frame_path, event_frame) |
|
|
| event_frames.append(event_frame_path) |
| |
| return event_frames, event_frames[0], mesh_paths, mesh_paths[0] |
| |
|
|
| with gr.Blocks(css='style.css') as demo: |
|
|
| gr.Markdown( |
| """ |
| <div align="center"> |
| <h1>Ev2Hands: 3D Pose Estimation of Two Interacting Hands from a Monocular Event Camera</h1> |
| </div> |
| """ |
| ) |
| gr.Markdown( |
| """ |
| <div align="center"> |
| <h4> |
| Note: The model's performance may be suboptimal as the event stream derived from the input video inadequately reflects the characteristics of an event stream generated by an event camera. 🚫📹 |
| </h4> |
| </div> |
| """ |
| ) |
| gr.Markdown( |
| """ |
| <p align="center"> |
| <a title="Project Page" href="https://4dqv.mpi-inf.mpg.de/Ev2Hands/" target="_blank" rel="noopener noreferrer" style="display: inline-block;"> |
| <img src="https://img.shields.io/badge/Project-Website-5B7493?logo=googlechrome&logoColor=5B7493"> |
| </a> |
| <a title="arXiv" href="https://arxiv.org/abs/2312.14157" target="_blank" rel="noopener noreferrer" style="display: inline-block;"> |
| <img src="https://img.shields.io/badge/arXiv-Paper-b31b1b?logo=arxiv&logoColor=b31b1b"> |
| </a> |
| <a title="GitHub" href="https://github.com/Chris10M/Ev2Hands/" target="_blank" rel="noopener noreferrer" style="display: inline-block;"> |
| <img src="https://img.shields.io/github/stars/Chris10M/Ev2Hands?label=GitHub%20%E2%98%85&&logo=github" alt="badge-github-stars"> |
| </a> |
| <a title="Video" href="https://youtu.be/nvES_c5vRfU" target="_blank" rel="noopener noreferrer" style="display: inline-block;"> |
| <img src="https://img.shields.io/badge/YouTube-Video-red?logo=youtube&logoColor=red"> |
| </a> |
| <a title="Visitor" href="https://hits.seeyoufarm.com" target="_blank" rel="noopener noreferrer" style="display: inline-block;"> |
| <img src="https://hits.seeyoufarm.com/api/count/incr/badge.svg?url=https%3A%2F%2Fhuggingface.co%2Fspaces%2Fchris10%2Fev2hands&count_bg=%2379C83D&title_bg=%23555555&icon=&icon_color=%23E7E7E7&title=hits&edge_flat=false"> |
| </a> |
| </p> |
| """ |
| ) |
|
|
| with gr.Column(elem_id="col-container"): |
| |
| with gr.Row(): |
| with gr.Column(): |
| gr.Markdown("<h3>Input: RGB video. We convert the video into an event stream.✨📹</h3>") |
| video_inp = gr.Video(label="Video source", elem_id="input-vid") |
| with gr.Row(): |
| trim_in = gr.Slider(label="Cut video at (s)", minimum=1, maximum=5, step=1, value=1) |
| threshold = gr.Slider(label="Event Threshold", minimum=0.1, maximum=1, step=0.05, value=0.8) |
| |
| gr.Examples( |
| examples=[os.path.join(os.path.dirname(__file__), "examples/video.mp4")], |
| inputs=video_inp, |
| ) |
|
|
|
|
| with gr.Column(): |
| eventframe_files = gr.Files(visible=False, label='Event frame paths') |
| mesh_files = gr.Files(visible=False, label='3D Mesh Files') |
| |
| event_frame = gr.Image(label="Event Frame") |
| prediction_out = gr.Model3D(clear_color=[0.0, 0.0, 0.0, 0.0], label="Ev2Hands Result") |
| model_slider = gr.Slider(minimum=1, step=1, label="Frame Number") |
|
|
| gr.HTML(""" |
| <a style="display:inline-block" href="https://huggingface.co/spaces/chris10/ev2hands?duplicate=true"><img src="https://img.shields.io/badge/-Duplicate%20Space-blue?labelColor=white&style=flat&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAP5JREFUOE+lk7FqAkEURY+ltunEgFXS2sZGIbXfEPdLlnxJyDdYB62sbbUKpLbVNhyYFzbrrA74YJlh9r079973psed0cvUD4A+4HoCjsA85X0Dfn/RBLBgBDxnQPfAEJgBY+A9gALA4tcbamSzS4xq4FOQAJgCDwV2CPKV8tZAJcAjMMkUe1vX+U+SMhfAJEHasQIWmXNN3abzDwHUrgcRGmYcgKe0bxrblHEB4E/pndMazNpSZGcsZdBlYJcEL9Afo75molJyM2FxmPgmgPqlWNLGfwZGG6UiyEvLzHYDmoPkDDiNm9JR9uboiONcBXrpY1qmgs21x1QwyZcpvxt9NS09PlsPAAAAAElFTkSuQmCC&logoWidth=14" alt="Duplicate Space"></a> |
| work with longer videos / skip the queue: |
| """, elem_id="duplicate-container") |
|
|
| submit_btn = gr.Button("Run Ev2Hands") |
|
|
| inputs = [video_inp, trim_in, threshold] |
| outputs = [eventframe_files, event_frame, mesh_files, prediction_out] |
| |
| submit_btn.click(infer, inputs, outputs) |
| model_slider.change(change_model, [model_slider, eventframe_files, mesh_files], [model_slider, event_frame, prediction_out]) |
|
|
| demo.queue(max_size=12).launch(server_name="0.0.0.0", server_port=7860) |