Update app.py
Browse files
app.py
CHANGED
|
@@ -36,6 +36,57 @@ def filter_kwargs(cls, kwargs):
|
|
| 36 |
valid_params = set(sig.parameters.keys()) - {'self', 'cls'}
|
| 37 |
return {k: v for k, v in kwargs.items() if k in valid_params}
|
| 38 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 39 |
from huggingface_hub import snapshot_download
|
| 40 |
|
| 41 |
def download_component_subfolder(repo_id, subfolder):
|
|
@@ -128,7 +179,7 @@ def get_prompt(click_state, click_input):
|
|
| 128 |
|
| 129 |
@spaces.GPU
|
| 130 |
# extract frames from upload video
|
| 131 |
-
def get_frames_from_video(video_input, video_state):
|
| 132 |
"""
|
| 133 |
Args:
|
| 134 |
video_path:str
|
|
@@ -348,9 +399,11 @@ def inpaint_video(video_state, *_):
|
|
| 348 |
# import pdb;pdb.set_trace()
|
| 349 |
frames = video_state["origin_images"]
|
| 350 |
masks = video_state["masks"]
|
| 351 |
-
# masks = masks * 255
|
| 352 |
fps = int(video_state["fps"])
|
| 353 |
|
|
|
|
|
|
|
|
|
|
| 354 |
total_frames = len(frames)
|
| 355 |
target_frame_count = (total_frames - 1) // 16 * 16 + 1
|
| 356 |
frames = frames[:target_frame_count]
|
|
@@ -456,7 +509,7 @@ description = r"""
|
|
| 456 |
<center></center>
|
| 457 |
<b>Official Gradio demo</b> for <a href='https://github.com/Kunbyte-AI/ROSE' target='_blank'><b>Remove Objects with Side Effects in Videos</b></a>.<br>
|
| 458 |
🔥 ROSE is a robust inpainting algorithm.<br>
|
| 459 |
-
🤗
|
| 460 |
"""
|
| 461 |
|
| 462 |
css = """
|
|
@@ -518,9 +571,21 @@ with gr.Blocks(theme=gr.themes.Monochrome(), css=css) as iface:
|
|
| 518 |
# input video
|
| 519 |
gr.Markdown("## Step1: Upload video")
|
| 520 |
with gr.Row(equal_height=True):
|
| 521 |
-
with gr.Column(scale=2):
|
| 522 |
-
video_input = gr.Video(elem_classes="video")
|
| 523 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 524 |
with gr.Column(scale=2):
|
| 525 |
run_status = gr.HighlightedText(value=[("",""), ("Try to upload your video and click the Get video info button to get started!", "Normal")],
|
| 526 |
color_map={"Normal": "green", "Error": "red", "Clear clicks": "gray", "Add mask": "green", "Remove mask": "red"})
|
|
@@ -553,20 +618,20 @@ with gr.Blocks(theme=gr.themes.Monochrome(), css=css) as iface:
|
|
| 553 |
mask_dropdown = gr.Dropdown(multiselect=True, value=[], label="Mask selection", info=".", visible=False)
|
| 554 |
|
| 555 |
# output video
|
| 556 |
-
step3_title = gr.Markdown("---\n## Step3:
|
| 557 |
with gr.Row(equal_height=True):
|
| 558 |
with gr.Column(scale=2):
|
| 559 |
tracking_video_output = gr.Video(visible=False, elem_classes="video")
|
| 560 |
-
tracking_video_predict_button = gr.Button(value="1. Tracking", visible=False, elem_classes="margin_center")
|
| 561 |
with gr.Column(scale=2):
|
| 562 |
inpaiting_video_output = gr.Video(visible=False, elem_classes="video")
|
| 563 |
-
inpaint_video_predict_button = gr.Button(value="
|
| 564 |
|
| 565 |
# first step: get the video information
|
| 566 |
extract_frames_button.click(
|
| 567 |
fn=get_frames_from_video,
|
| 568 |
inputs=[
|
| 569 |
-
video_input, video_state
|
| 570 |
],
|
| 571 |
outputs=[video_state, video_info, template_frame,
|
| 572 |
image_selection_slider, track_pause_number_slider,point_prompt, clear_button_click, Add_mask_button, template_frame,
|
|
@@ -608,14 +673,14 @@ with gr.Blocks(theme=gr.themes.Monochrome(), css=css) as iface:
|
|
| 608 |
outputs=[tracking_video_output, video_state, interactive_state, run_status, run_status2]
|
| 609 |
)
|
| 610 |
|
| 611 |
-
# inpaint
|
| 612 |
inpaint_video_predict_button.click(
|
| 613 |
fn=inpaint_video,
|
| 614 |
-
|
| 615 |
-
inputs=[video_state, mask_dropdown],
|
| 616 |
outputs=[inpaiting_video_output, run_status, run_status2]
|
| 617 |
)
|
| 618 |
|
|
|
|
| 619 |
# click to get mask
|
| 620 |
mask_dropdown.change(
|
| 621 |
fn=show_mask,
|
|
|
|
| 36 |
valid_params = set(sig.parameters.keys()) - {'self', 'cls'}
|
| 37 |
return {k: v for k, v in kwargs.items() if k in valid_params}
|
| 38 |
|
| 39 |
+
|
| 40 |
+
def load_mask_video(mask_video_path, target_frame_count, target_size, target_fps):
|
| 41 |
+
"""
|
| 42 |
+
Load a user-supplied mask video.
|
| 43 |
+
|
| 44 |
+
Convention:
|
| 45 |
+
- white / bright pixels = area to inpaint
|
| 46 |
+
- black pixels = keep unchanged
|
| 47 |
+
- grayscale masks are supported
|
| 48 |
+
- the mask video is resized to the source video's resolution
|
| 49 |
+
- if the mask has fewer frames than the source, an error is raised
|
| 50 |
+
"""
|
| 51 |
+
if not mask_video_path:
|
| 52 |
+
return None
|
| 53 |
+
|
| 54 |
+
cap = cv2.VideoCapture(mask_video_path)
|
| 55 |
+
if not cap.isOpened():
|
| 56 |
+
raise ValueError(f"Could not open mask video: {mask_video_path}")
|
| 57 |
+
|
| 58 |
+
masks = []
|
| 59 |
+
while True:
|
| 60 |
+
ret, frame = cap.read()
|
| 61 |
+
if not ret:
|
| 62 |
+
break
|
| 63 |
+
|
| 64 |
+
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
|
| 65 |
+
if (gray.shape[1], gray.shape[0]) != target_size:
|
| 66 |
+
gray = cv2.resize(
|
| 67 |
+
gray,
|
| 68 |
+
target_size,
|
| 69 |
+
interpolation=cv2.INTER_NEAREST
|
| 70 |
+
)
|
| 71 |
+
|
| 72 |
+
# Normalize to a binary mask. Any non-black pixel is treated as masked.
|
| 73 |
+
mask = (gray > 127).astype(np.uint8)
|
| 74 |
+
masks.append(mask)
|
| 75 |
+
|
| 76 |
+
cap.release()
|
| 77 |
+
|
| 78 |
+
if not masks:
|
| 79 |
+
raise ValueError("The supplied mask video contains no readable frames.")
|
| 80 |
+
|
| 81 |
+
if len(masks) < target_frame_count:
|
| 82 |
+
raise ValueError(
|
| 83 |
+
f"Mask video has {len(masks)} frames, but the source video has "
|
| 84 |
+
f"{target_frame_count} frames."
|
| 85 |
+
)
|
| 86 |
+
|
| 87 |
+
# Extra mask frames are ignored so both videos stay aligned.
|
| 88 |
+
return masks[:target_frame_count]
|
| 89 |
+
|
| 90 |
from huggingface_hub import snapshot_download
|
| 91 |
|
| 92 |
def download_component_subfolder(repo_id, subfolder):
|
|
|
|
| 179 |
|
| 180 |
@spaces.GPU
|
| 181 |
# extract frames from upload video
|
| 182 |
+
def get_frames_from_video(video_input, mask_video_input, video_state):
|
| 183 |
"""
|
| 184 |
Args:
|
| 185 |
video_path:str
|
|
|
|
| 399 |
# import pdb;pdb.set_trace()
|
| 400 |
frames = video_state["origin_images"]
|
| 401 |
masks = video_state["masks"]
|
|
|
|
| 402 |
fps = int(video_state["fps"])
|
| 403 |
|
| 404 |
+
if masks is None or len(masks) != len(frames):
|
| 405 |
+
raise gr.Error("Mask video is missing or does not match the source video frame count.")
|
| 406 |
+
|
| 407 |
total_frames = len(frames)
|
| 408 |
target_frame_count = (total_frames - 1) // 16 * 16 + 1
|
| 409 |
frames = frames[:target_frame_count]
|
|
|
|
| 509 |
<center></center>
|
| 510 |
<b>Official Gradio demo</b> for <a href='https://github.com/Kunbyte-AI/ROSE' target='_blank'><b>Remove Objects with Side Effects in Videos</b></a>.<br>
|
| 511 |
🔥 ROSE is a robust inpainting algorithm.<br>
|
| 512 |
+
🤗 Drop your source video and, optionally, a pre-made mask video, then get the inpainting result!<br>
|
| 513 |
"""
|
| 514 |
|
| 515 |
css = """
|
|
|
|
| 571 |
# input video
|
| 572 |
gr.Markdown("## Step1: Upload video")
|
| 573 |
with gr.Row(equal_height=True):
|
| 574 |
+
with gr.Column(scale=2):
|
| 575 |
+
video_input = gr.Video(label="Source video", elem_classes="video")
|
| 576 |
+
mask_video_input = gr.Video(
|
| 577 |
+
label="Mask video (optional)",
|
| 578 |
+
elem_classes="video"
|
| 579 |
+
)
|
| 580 |
+
gr.Markdown(
|
| 581 |
+
"**Mask convention:** white = remove/inpaint, black = keep. "
|
| 582 |
+
"The mask video must be frame-aligned with the source video."
|
| 583 |
+
)
|
| 584 |
+
extract_frames_button = gr.Button(
|
| 585 |
+
value="Get video info",
|
| 586 |
+
interactive=True,
|
| 587 |
+
variant="primary"
|
| 588 |
+
)
|
| 589 |
with gr.Column(scale=2):
|
| 590 |
run_status = gr.HighlightedText(value=[("",""), ("Try to upload your video and click the Get video info button to get started!", "Normal")],
|
| 591 |
color_map={"Normal": "green", "Error": "red", "Clear clicks": "gray", "Add mask": "green", "Remove mask": "red"})
|
|
|
|
| 618 |
mask_dropdown = gr.Dropdown(multiselect=True, value=[], label="Mask selection", info=".", visible=False)
|
| 619 |
|
| 620 |
# output video
|
| 621 |
+
step3_title = gr.Markdown("---\n## Step3: Get the inpainting result", visible=False)
|
| 622 |
with gr.Row(equal_height=True):
|
| 623 |
with gr.Column(scale=2):
|
| 624 |
tracking_video_output = gr.Video(visible=False, elem_classes="video")
|
| 625 |
+
tracking_video_predict_button = gr.Button(value="1. Tracking (optional)", visible=False, elem_classes="margin_center")
|
| 626 |
with gr.Column(scale=2):
|
| 627 |
inpaiting_video_output = gr.Video(visible=False, elem_classes="video")
|
| 628 |
+
inpaint_video_predict_button = gr.Button(value="Inpainting", visible=False, elem_classes="margin_center")
|
| 629 |
|
| 630 |
# first step: get the video information
|
| 631 |
extract_frames_button.click(
|
| 632 |
fn=get_frames_from_video,
|
| 633 |
inputs=[
|
| 634 |
+
video_input, mask_video_input, video_state
|
| 635 |
],
|
| 636 |
outputs=[video_state, video_info, template_frame,
|
| 637 |
image_selection_slider, track_pause_number_slider,point_prompt, clear_button_click, Add_mask_button, template_frame,
|
|
|
|
| 673 |
outputs=[tracking_video_output, video_state, interactive_state, run_status, run_status2]
|
| 674 |
)
|
| 675 |
|
| 676 |
+
# inpaint directly from the supplied mask video
|
| 677 |
inpaint_video_predict_button.click(
|
| 678 |
fn=inpaint_video,
|
| 679 |
+
inputs=[video_state],
|
|
|
|
| 680 |
outputs=[inpaiting_video_output, run_status, run_status2]
|
| 681 |
)
|
| 682 |
|
| 683 |
+
|
| 684 |
# click to get mask
|
| 685 |
mask_dropdown.change(
|
| 686 |
fn=show_mask,
|