John6666's picture
Update app.py
9144295 verified
import gradio as gr
from loadimg import load_img
import spaces
from transformers import AutoModelForImageSegmentation
import torch
from torchvision import transforms
import moviepy.editor as mp
from pydub import AudioSegment
from PIL import Image
import numpy as np
import os
import tempfile
import uuid
import time
from concurrent.futures import ThreadPoolExecutor
from PIL import Image, ImageSequence
import base64
import io
import numpy as np
import tempfile
from gradio_imageslider import ImageSlider
torch.set_float32_matmul_precision(["high", "highest"][0])
device = "cuda" if torch.cuda.is_available() else "cpu"
# Maximum image size
Image.MAX_IMAGE_PIXELS = None
# Load both BiRefNet models
birefnet = AutoModelForImageSegmentation.from_pretrained(
"ZhengPeng7/BiRefNet", trust_remote_code=True
)
birefnet.to(device)
birefnet_lite = AutoModelForImageSegmentation.from_pretrained(
"ZhengPeng7/BiRefNet_lite", trust_remote_code=True
)
birefnet_lite.to(device)
transform_image = transforms.Compose(
[
transforms.Resize((1024, 1024)),
transforms.ToTensor(),
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]),
]
)
# Video processing
# Function to process a single frame
def process_frame(
frame, bg_type, bg, fast_mode, bg_frame_index, background_frames, color
):
try:
pil_image = Image.fromarray(frame)
if bg_type == "Color":
processed_image = process(pil_image, color, fast_mode)
elif bg_type == "Image":
processed_image = process(pil_image, bg, fast_mode)
elif bg_type == "Video":
background_frame = background_frames[
bg_frame_index
] # Access the correct background frame
bg_frame_index += 1
background_image = Image.fromarray(background_frame)
processed_image = process(pil_image, background_image, fast_mode)
else:
processed_image = (
pil_image # Default to original image if no background is selected
)
return np.array(processed_image), bg_frame_index
except Exception as e:
print(f"Error processing frame: {e}")
return frame, bg_frame_index
@spaces.GPU
def remove_bg_video(
vid,
bg_type="Color",
bg_image=None,
bg_video=None,
color="#00FF00",
fps=0,
video_handling="slow_down",
fast_mode=True,
max_workers=6,
):
try:
start_time = time.time() # Start the timer
video = mp.VideoFileClip(vid)
if fps == 0:
fps = video.fps
audio = video.audio
frames = list(video.iter_frames(fps=fps))
processed_frames = []
yield gr.update(visible=True), gr.update(
visible=False
), f"Processing started... Elapsed time: 0 seconds"
if bg_type == "Video":
background_video = mp.VideoFileClip(bg_video)
if background_video.duration < video.duration:
if video_handling == "slow_down":
background_video = background_video.fx(
mp.vfx.speedx, factor=video.duration / background_video.duration
)
else: # video_handling == "loop"
background_video = mp.concatenate_videoclips(
[background_video]
* int(video.duration / background_video.duration + 1)
)
background_frames = list(background_video.iter_frames(fps=fps))
else:
background_frames = None
bg_frame_index = 0 # Initialize background frame index
with ThreadPoolExecutor(max_workers=max_workers) as executor:
# Pass bg_frame_index as part of the function arguments
futures = [
executor.submit(
process_frame,
frames[i],
bg_type,
bg_image,
fast_mode,
bg_frame_index + i,
background_frames,
color,
)
for i in range(len(frames))
]
for i, future in enumerate(futures):
result, _ = future.result() # No need to update bg_frame_index here
processed_frames.append(result)
elapsed_time = time.time() - start_time
yield result, None, f"Processing frame {i+1}/{len(frames)}... Elapsed time: {elapsed_time:.2f} seconds"
processed_video = mp.ImageSequenceClip(processed_frames, fps=fps)
processed_video = processed_video.set_audio(audio)
with tempfile.NamedTemporaryFile(suffix=".mp4", delete=False) as temp_file:
temp_filepath = temp_file.name
processed_video.write_videofile(temp_filepath, codec="libx264")
elapsed_time = time.time() - start_time
yield gr.update(visible=False), gr.update(
visible=True
), f"Processing complete! Elapsed time: {elapsed_time:.2f} seconds"
yield processed_frames[
-1
], temp_filepath, f"Processing complete! Elapsed time: {elapsed_time:.2f} seconds"
except Exception as e:
print(f"Error: {e}")
elapsed_time = time.time() - start_time
yield gr.update(visible=False), gr.update(
visible=True
), f"Error processing video: {e}. Elapsed time: {elapsed_time:.2f} seconds"
yield None, f"Error processing video: {e}", f"Error processing video: {e}. Elapsed time: {elapsed_time:.2f} seconds"
def process(image, bg, fast_mode=False):
image_size = image.size
input_images = transform_image(image).unsqueeze(0).to(device)
model = birefnet_lite if fast_mode else birefnet
with torch.no_grad():
preds = model(input_images)[-1].sigmoid().cpu()
pred = preds[0].squeeze()
pred_pil = transforms.ToPILImage()(pred)
mask = pred_pil.resize(image_size)
if isinstance(bg, str) and bg.startswith("#"):
color_rgb = tuple(int(bg[i : i + 2], 16) for i in (1, 3, 5))
background = Image.new("RGBA", image_size, color_rgb + (255,))
elif isinstance(bg, Image.Image):
background = bg.convert("RGBA").resize(image_size)
else:
background = Image.open(bg).convert("RGBA").resize(image_size)
image = Image.composite(image, background, mask)
return image
# Image processing
# Function to remove background from an image
def remove_bg_fn(image):
im = load_img(image, output_type="pil")
im = im.convert("RGB")
origin = im.copy()
if im.format == "GIF":
frames = []
for frame in ImageSequence.Iterator(im):
frame = frame.convert("RGBA")
processed_frame = process_image(frame)
frames.append(processed_frame)
processed_image = frames[0]
processed_image.save(
io.BytesIO(),
format="GIF",
save_all=True,
append_images=frames[1:],
loop=0,
)
else:
processed_image = process_image(im)
return (processed_image, origin)
@spaces.GPU
def process_image(image):
image_size = image.size
input_images = transform_image(image).unsqueeze(0).to(device)
# Prediction
with torch.no_grad():
preds = birefnet(input_images)[-1].sigmoid().cpu()
pred = preds[0].squeeze()
pred_pil = transforms.ToPILImage()(pred)
mask = pred_pil.resize(image_size)
image.putalpha(mask)
return image
# Function to apply background to an image
@spaces.GPU
def apply_background(image, background):
if background.mode != "RGBA":
background = background.convert("RGBA")
image = image.convert("RGBA")
combined = Image.alpha_composite(background, image)
return combined
# Function to convert hex color to RGBA
def hex_to_rgba(hex_color):
hex_color = hex_color.lstrip("#")
lv = len(hex_color)
return tuple(int(hex_color[i : i + lv // 3], 16) for i in range(0, lv, lv // 3)) + (
255,
)
def apply_bg_image(image, background_file=None, background_color=None, bg_type="Color"):
try:
image_data = image.read()
input_image = Image.open(io.BytesIO(image_data))
origin = input_image.copy()
color_profile = input_image.info.get("icc_profile")
if background_file is not None:
background_image = Image.open(io.BytesIO(background_file.read()))
else:
background_image = None
if bg_type == "Color":
background_image = Image.new("RGBA", input_image.size, hex_to_rgba(background_color))
elif bg_type == "Image" and background_image is not None:
if background_image.size != input_image.size:
background_image = background_image.resize(input_image.size)
if input_image.format == "GIF":
frames = []
for frame in ImageSequence.Iterator(input_image):
frame = frame.convert("RGBA")
output_frame = apply_background(frame, background_image)
frames.append(output_frame)
output_image = io.BytesIO()
frames[0].save(
output_image,
format="GIF",
save_all=True,
append_images=frames[1:],
loop=0,
icc_profile=color_profile,
)
output_image_base64 = base64.b64encode(output_image.getvalue()).decode("utf-8")
else:
output_image = apply_background(input_image, background_image)
buffered = io.BytesIO()
output_image.save(buffered, format="PNG", optimize=True, icc_profile=color_profile)
output_image_base64 = base64.b64encode(buffered.getvalue()).decode("utf-8")
output_image_data = base64.b64decode(output_image_base64)
return (Image.open(io.BytesIO(output_image_data)), origin)
except Exception as e:
return str(e)
# Gradio interface
with gr.Blocks(theme=gr.themes.Ocean()) as demo:
gr.Markdown("# Image and Video Background Remover & Changer\n\nRemove or apply background to images and videos.")
with gr.Tab("Remove Image Background"):
with gr.Row():
image_input = gr.Image(label="Upload Image", interactive=True)
slider = ImageSlider(label="Processed Image", type="pil")
remove_button = gr.Button("Remove Image Background", interactive=True)
examples = gr.Examples(
[
load_img(
"https://images.rawpixel.com/image_800/cHJpdmF0ZS9sci9pbWFnZXMvd2Vic2l0ZS8yMDIzLTA4L3Jhd3BpeGVsX29mZmljZV8yX3Bob3RvX29mX2FfbGlvbl9pc29sYXRlZF9vbl9jb2xvcl9iYWNrZ3JvdW5kXzJhNzgwMjM1LWRlYTgtNDMyOS04OWVjLTY3ZWMwNjcxZDhiMV8xLmpwZw.jpg",
output_type="pil",
)
],
inputs=image_input,
fn=remove_bg_fn,
outputs=slider,
cache_examples=True,
cache_mode="eager",
)
remove_button.click(remove_bg_fn, inputs=image_input, outputs=slider)
with gr.Tab("Apply Background to Image"):
with gr.Row():
image_input = gr.Image(label="Upload Image", interactive=True)
slider = ImageSlider(label="Processed Image", type="pil")
apply_button = gr.Button("Apply Background", interactive=True)
with gr.Row():
bg_type = gr.Radio(
["Color", "Image"],
label="Background Type",
value="Color",
interactive=True,
)
color_picker = gr.ColorPicker(
label="Background Color",
value="#00FF00",
visible=True,
interactive=True,
)
bg_image = gr.Image(
label="Background Image",
type="filepath",
visible=False,
interactive=True,
)
def update_visibility(bg_type):
if bg_type == "Color":
return (
gr.update(visible=True),
gr.update(visible=False),
)
elif bg_type == "Image":
return (
gr.update(visible=False),
gr.update(visible=True),
)
else:
return (
gr.update(visible=False),
gr.update(visible=False),
)
bg_type.change(
update_visibility,
inputs=bg_type,
outputs=[color_picker, bg_image],
)
examples = gr.Examples(
[
["https://pngimg.com/d/mario_PNG125.png", None, "#0cfa38", "Color"],
[
"https://pngimg.com/d/mario_PNG125.png",
"https://cdn.photoroom.com/v2/image-cache?path=gs://background-7ef44.appspot.com/backgrounds_v3/black/47_-_black.jpg",
None,
"Image",
],
],
inputs=[image_input, bg_image, color_picker, bg_type],
fn=apply_bg_image,
outputs=slider,
cache_examples=True,
cache_mode="eager",
)
apply_button.click(
apply_bg_image,
inputs=[image_input, bg_image, color_picker, bg_type],
outputs= slider,
)
with gr.Tab("Remove Video Background"):
with gr.Row():
in_video = gr.Video(label="Input Video", interactive=True)
stream_image = gr.Image(label="Streaming Output", visible=False)
out_video = gr.Video(label="Final Output Video")
submit_button = gr.Button("Change Background", interactive=True)
with gr.Row():
fps_slider = gr.Slider(
minimum=0,
maximum=60,
step=1,
value=0,
label="Output FPS (0 will inherit the original fps value)",
interactive=True,
)
bg_type = gr.Radio(
["Color", "Image", "Video"],
label="Background Type",
value="Color",
interactive=True,
)
color_picker = gr.ColorPicker(
label="Background Color",
value="#00FF00",
visible=True,
interactive=True,
)
bg_image = gr.Image(
label="Background Image",
type="filepath",
visible=False,
interactive=True,
)
bg_video = gr.Video(
label="Background Video", visible=False, interactive=True
)
with gr.Column(visible=False) as video_handling_options:
video_handling_radio = gr.Radio(
["slow_down", "loop"],
label="Video Handling",
value="slow_down",
interactive=True,
)
fast_mode_checkbox = gr.Checkbox(
label="Fast Mode (Use BiRefNet_lite)", value=True, interactive=True
)
max_workers_slider = gr.Slider(
minimum=1,
maximum=32,
step=1,
value=6,
label="Max Workers",
info="Determines how many frames to process in parallel",
interactive=True,
)
time_textbox = gr.Textbox(label="Time Elapsed", interactive=False)
def update_visibility(bg_type):
if bg_type == "Color":
return (
gr.update(visible=True),
gr.update(visible=False),
gr.update(visible=False),
)
elif bg_type == "Image":
return (
gr.update(visible=False),
gr.update(visible=True),
gr.update(visible=False),
gr.update(visible=False),
)
elif bg_type == "Video":
return (
gr.update(visible=False),
gr.update(visible=False),
gr.update(visible=True),
)
else:
return (
gr.update(visible=False),
gr.update(visible=False),
gr.update(visible=False),
)
bg_type.change(
update_visibility,
inputs=bg_type,
outputs=[color_picker, bg_image, bg_video, video_handling_options],
)
examples = gr.Examples(
[
[
"https://www.w3schools.com/html/mov_bbb.mp4",
"Video",
None,
"https://www.w3schools.com/howto/rain.mp4",
],
[
"https://www.w3schools.com/html/mov_bbb.mp4",
"Image",
"https://cdn.photoroom.com/v2/image-cache?path=gs://background-7ef44.appspot.com/backgrounds_v3/black/47_-_black.jpg",
None,
],
["https://www.w3schools.com/html/mov_bbb.mp4", "Color", None, None],
],
inputs=[in_video, bg_type, bg_image, bg_video],
outputs=[stream_image, out_video, time_textbox],
fn=remove_bg_video,
cache_examples=True,
cache_mode="eager",
)
submit_button.click(
remove_bg_video,
inputs=[
in_video,
bg_type,
bg_image,
bg_video,
color_picker,
fps_slider,
video_handling_radio,
fast_mode_checkbox,
max_workers_slider,
],
outputs=[stream_image, out_video, time_textbox],
)
if __name__ == "__main__":
demo.launch(show_error=True, ssr_mode=False)