Spaces:
Sleeping
Sleeping
Download app.py from Luminia/dis-bria-rmbg: direct link, hf CLI and curl.
- Browser
- Download file 26 kB
-
https://huggingface.co/spaces/Luminia/dis-bria-rmbg/resolve/main/app.py
- Command line
-
hf download hf://spaces/Luminia/dis-bria-rmbg/app.py
-
curl -L -o app.py https://huggingface.co/spaces/Luminia/dis-bria-rmbg/resolve/main/app.py
26 kB
| """ | |
| Background Removal - Image and Video | |
| - BRIA RMBG 1.4: Fast general-purpose background removal | |
| - BRIA RMBG 2.0: High-quality BiRefNet-based (slower, better quality) | |
| - IS-Net/DIS: Ultra-fine edge detection (hair, fur, transparency) | |
| - Robust Video Matting: Video background removal with ONNX Runtime (2x faster!) | |
| CPU-only HuggingFace Space | |
| """ | |
| import os | |
| import sys | |
| import tempfile | |
| import argparse | |
| from typing import Tuple, Optional | |
| # Set OMP threads to match cpu-basic (2 vCPU) | |
| os.environ["OMP_NUM_THREADS"] = "2" | |
| os.environ["OMP_WAIT_POLICY"] = "ACTIVE" | |
| import gradio as gr | |
| import numpy as np | |
| from PIL import Image | |
| import torch | |
| import torch.nn.functional as F | |
| from torchvision.transforms.functional import normalize | |
| from huggingface_hub import hf_hub_download | |
| import cv2 | |
| import onnxruntime as ort | |
| # Force CPU for PyTorch (image model) | |
| device = torch.device("cpu") | |
| # ============================================================================= | |
| # Image Background Removal (BRIA RMBG 1.4) | |
| # ============================================================================= | |
| class BriaRMBGModel: | |
| """Wrapper for BRIA-RMBG 1.4 model""" | |
| def __init__(self): | |
| self.model = None | |
| self.input_size = [1024, 1024] | |
| def load_model(self): | |
| """Load the BRIA-RMBG 1.4 model from HuggingFace""" | |
| if self.model is not None: | |
| return | |
| print("Loading BRIA-RMBG 1.4 model...") | |
| # Download all required files from BRIA repo | |
| repo_id = "briaai/RMBG-1.4" | |
| model_path = hf_hub_download(repo_id=repo_id, filename="model.pth") | |
| briarmbg_path = hf_hub_download(repo_id=repo_id, filename="briarmbg.py") | |
| myconfig_path = hf_hub_download(repo_id=repo_id, filename="MyConfig.py") | |
| # Setup package directory | |
| briarmbg_dir = os.path.dirname(briarmbg_path) | |
| # Create __init__.py to make it a proper package | |
| init_path = os.path.join(briarmbg_dir, "__init__.py") | |
| if not os.path.exists(init_path): | |
| with open(init_path, "w") as f: | |
| f.write("# Auto-generated for package import\n") | |
| # Add parent directory to path so we can import as package | |
| parent_dir = os.path.dirname(briarmbg_dir) | |
| package_name = os.path.basename(briarmbg_dir) | |
| if parent_dir not in sys.path: | |
| sys.path.insert(0, parent_dir) | |
| # Import using the package structure | |
| import importlib | |
| briarmbg_module = importlib.import_module(f"{package_name}.briarmbg") | |
| BriaRMBG = briarmbg_module.BriaRMBG | |
| self.model = BriaRMBG() | |
| self.model.load_state_dict(torch.load(model_path, map_location=device)) | |
| self.model.to(device) | |
| self.model.eval() | |
| print("BRIA-RMBG model loaded!") | |
| def preprocess(self, image: Image.Image): | |
| """Preprocess image for model input""" | |
| if image.mode != "RGB": | |
| image = image.convert("RGB") | |
| orig_size = image.size | |
| im = np.array(image) | |
| if len(im.shape) < 3: | |
| im = im[:, :, np.newaxis] | |
| if im.shape[2] == 1: | |
| im = np.repeat(im, 3, axis=2) | |
| im_tensor = torch.tensor(im, dtype=torch.float32).permute(2, 0, 1) | |
| im_tensor = F.interpolate( | |
| im_tensor.unsqueeze(0), | |
| self.input_size, | |
| mode="bilinear", | |
| align_corners=False | |
| ) | |
| image_tensor = im_tensor / 255.0 | |
| image_tensor = normalize(image_tensor, [0.5, 0.5, 0.5], [1.0, 1.0, 1.0]) | |
| return image_tensor, orig_size | |
| def postprocess(self, result, orig_size): | |
| """Postprocess model output to mask""" | |
| result = F.interpolate( | |
| result, | |
| size=(orig_size[1], orig_size[0]), | |
| mode="bilinear", | |
| align_corners=False | |
| ) | |
| ma = torch.max(result) | |
| mi = torch.min(result) | |
| result = (result - mi) / (ma - mi + 1e-8) | |
| mask = (result.squeeze().cpu().numpy() * 255).astype(np.uint8) | |
| return mask | |
| def remove_background(self, image: Image.Image): | |
| """Remove background from image""" | |
| self.load_model() | |
| input_tensor, orig_size = self.preprocess(image) | |
| input_tensor = input_tensor.to(device) | |
| result = self.model(input_tensor) | |
| if isinstance(result, (list, tuple)): | |
| result = result[0] | |
| if isinstance(result, (list, tuple)): | |
| result = result[0] | |
| mask = self.postprocess(result, orig_size) | |
| pil_mask = Image.fromarray(mask).convert("L") | |
| if image.mode != "RGB": | |
| image = image.convert("RGB") | |
| output = image.copy() | |
| output.putalpha(pil_mask) | |
| return output, pil_mask | |
| # ============================================================================= | |
| # Image Background Removal (IS-Net/DIS - Ultra-fine edges) - ONNX Runtime | |
| # ============================================================================= | |
| class ISNetModel: | |
| """Wrapper for IS-Net/DIS ONNX model - better for hair, fur, transparency""" | |
| def __init__(self): | |
| self.session = None | |
| self.input_size = [1024, 1024] | |
| def load_model(self): | |
| """Load IS-Net ONNX model""" | |
| if self.session is not None: | |
| return | |
| print("Loading IS-Net/DIS ONNX model...") | |
| model_path = os.path.join(os.path.dirname(__file__), "isnet_dis_fp32.onnx") | |
| # Setup ONNX Runtime with optimizations | |
| sess_options = ort.SessionOptions() | |
| sess_options.intra_op_num_threads = 2 | |
| sess_options.inter_op_num_threads = 1 | |
| sess_options.graph_optimization_level = ort.GraphOptimizationLevel.ORT_ENABLE_ALL | |
| self.session = ort.InferenceSession( | |
| model_path, | |
| sess_options, | |
| providers=['CPUExecutionProvider'] | |
| ) | |
| print("IS-Net/DIS ONNX model loaded!") | |
| def preprocess(self, image: Image.Image): | |
| """Preprocess image for model input""" | |
| if image.mode != "RGB": | |
| image = image.convert("RGB") | |
| orig_size = image.size | |
| # Resize to 1024x1024 | |
| im_resized = image.resize((1024, 1024), Image.BILINEAR) | |
| im = np.array(im_resized).astype(np.float32) | |
| # Normalize: (x/255 - 0.5) / 1.0 = x/255 - 0.5 | |
| im = im / 255.0 | |
| im = (im - 0.5) / 1.0 | |
| # HWC -> NCHW | |
| im = np.transpose(im, (2, 0, 1))[np.newaxis, ...] | |
| return im.astype(np.float32), orig_size | |
| def postprocess(self, result, orig_size): | |
| """Postprocess model output to mask""" | |
| # Result is already sigmoid (0-1), resize to original | |
| mask_1024 = result[0, 0] # 1024x1024 | |
| # Resize to original size | |
| mask_pil = Image.fromarray((mask_1024 * 255).astype(np.uint8)) | |
| mask_pil = mask_pil.resize(orig_size, Image.BILINEAR) | |
| return np.array(mask_pil) | |
| def remove_background(self, image: Image.Image): | |
| """Remove background from image using IS-Net ONNX""" | |
| self.load_model() | |
| input_tensor, orig_size = self.preprocess(image) | |
| # Run ONNX inference - d0 is the main output | |
| outputs = self.session.run(None, {'input': input_tensor}) | |
| result = outputs[0] # d0: main segmentation mask | |
| mask = self.postprocess(result, orig_size) | |
| pil_mask = Image.fromarray(mask).convert("L") | |
| if image.mode != "RGB": | |
| image = image.convert("RGB") | |
| output = image.copy() | |
| output.putalpha(pil_mask) | |
| return output, pil_mask | |
| # ============================================================================= | |
| # Image Background Removal (BRIA RMBG 2.0 - BiRefNet based, high quality) | |
| # ============================================================================= | |
| class RMBG2Model: | |
| """Wrapper for BRIA-RMBG 2.0 model (BiRefNet architecture) - higher quality, slower""" | |
| def __init__(self): | |
| self.model = None | |
| self.input_size = 1024 # Native resolution for best quality | |
| def load_model(self): | |
| """Load RMBG-2.0 model from HuggingFace""" | |
| if self.model is not None: | |
| return | |
| print("Loading BRIA-RMBG 2.0 model (BiRefNet)...") | |
| # Download model files from ungated repo | |
| repo_id = "1038lab/RMBG-2.0" | |
| model_path = hf_hub_download(repo_id=repo_id, filename="model.safetensors") | |
| birefnet_path = hf_hub_download(repo_id=repo_id, filename="birefnet.py") | |
| config_path = hf_hub_download(repo_id=repo_id, filename="BiRefNet_config.py") | |
| # Setup import path | |
| model_dir = os.path.dirname(birefnet_path) | |
| if model_dir not in sys.path: | |
| sys.path.insert(0, model_dir) | |
| # Import model classes | |
| from safetensors.torch import load_file | |
| import importlib.util | |
| # Load BiRefNet_config | |
| spec = importlib.util.spec_from_file_location("BiRefNet_config", config_path) | |
| config_module = importlib.util.module_from_spec(spec) | |
| sys.modules["BiRefNet_config"] = config_module | |
| spec.loader.exec_module(config_module) | |
| # Fix relative import in birefnet.py - read, fix, and exec | |
| with open(birefnet_path, 'r', encoding='utf-8') as f: | |
| birefnet_code = f.read() | |
| # Replace relative import with absolute | |
| birefnet_code = birefnet_code.replace( | |
| 'from .BiRefNet_config import BiRefNetConfig', | |
| 'from BiRefNet_config import BiRefNetConfig' | |
| ) | |
| # Execute the fixed code | |
| birefnet_module = type(sys)("birefnet") | |
| exec(compile(birefnet_code, birefnet_path, 'exec'), birefnet_module.__dict__) | |
| BiRefNet = birefnet_module.BiRefNet | |
| BiRefNetConfig = config_module.BiRefNetConfig | |
| # Create and load model | |
| config = BiRefNetConfig(bb_pretrained=False) | |
| self.model = BiRefNet(config) | |
| state_dict = load_file(model_path) | |
| self.model.load_state_dict(state_dict) | |
| self.model.to(device) | |
| self.model.eval() | |
| print(f"BRIA-RMBG 2.0 loaded! ({sum(p.numel() for p in self.model.parameters())/1e6:.0f}M params)") | |
| def preprocess(self, image: Image.Image): | |
| """Preprocess image for model input""" | |
| if image.mode != "RGB": | |
| image = image.convert("RGB") | |
| orig_size = image.size | |
| # Resize to model input size | |
| im_resized = image.resize((self.input_size, self.input_size), Image.BILINEAR) | |
| im = np.array(im_resized).astype(np.float32) | |
| # Normalize | |
| im = im / 255.0 | |
| im = (im - 0.5) / 1.0 | |
| # HWC -> NCHW | |
| im_tensor = torch.from_numpy(im).permute(2, 0, 1).unsqueeze(0) | |
| return im_tensor.float(), orig_size | |
| def postprocess(self, result, orig_size): | |
| """Postprocess model output to mask""" | |
| # Get final output (BiRefNet returns list for deep supervision) | |
| if isinstance(result, (list, tuple)): | |
| result = result[-1] # Last output is the refined mask | |
| # Sigmoid and resize | |
| mask = torch.sigmoid(result).squeeze().cpu().numpy() | |
| # Resize to original size | |
| mask_pil = Image.fromarray((mask * 255).astype(np.uint8)) | |
| mask_pil = mask_pil.resize(orig_size, Image.BILINEAR) | |
| return np.array(mask_pil) | |
| def remove_background(self, image: Image.Image): | |
| """Remove background from image using RMBG-2.0""" | |
| self.load_model() | |
| input_tensor, orig_size = self.preprocess(image) | |
| input_tensor = input_tensor.to(device) | |
| result = self.model(input_tensor) | |
| mask = self.postprocess(result, orig_size) | |
| pil_mask = Image.fromarray(mask).convert("L") | |
| if image.mode != "RGB": | |
| image = image.convert("RGB") | |
| output = image.copy() | |
| output.putalpha(pil_mask) | |
| return output, pil_mask | |
| # ============================================================================= | |
| # Video Background Removal (Robust Video Matting - ONNX Runtime) | |
| # ============================================================================= | |
| rvm_session = None | |
| def load_rvm_onnx(): | |
| """Load RobustVideoMatting ONNX model for fast CPU inference.""" | |
| global rvm_session | |
| if rvm_session is not None: | |
| return rvm_session | |
| print("Loading Robust Video Matting ONNX model...") | |
| # Load ONNX model from local Space directory | |
| model_path = os.path.join(os.path.dirname(__file__), "rvm_mobilenetv3_fp32.onnx") | |
| # Setup ONNX Runtime with optimizations (2 vCPU on cpu-basic) | |
| sess_options = ort.SessionOptions() | |
| sess_options.intra_op_num_threads = 2 | |
| sess_options.inter_op_num_threads = 1 | |
| sess_options.graph_optimization_level = ort.GraphOptimizationLevel.ORT_ENABLE_ALL | |
| rvm_session = ort.InferenceSession( | |
| model_path, | |
| sess_options, | |
| providers=['CPUExecutionProvider'] | |
| ) | |
| print("RVM ONNX model loaded! (2x faster than PyTorch)") | |
| return rvm_session | |
| # Output resolution options | |
| RESOLUTION_OPTIONS = { | |
| "Original": None, | |
| "1080p": 1080, | |
| "720p": 720, | |
| "480p": 480, | |
| } | |
| def process_video(input_video, output_type="green_screen", max_resolution="720p", downsample_ratio=0.25, progress=None): | |
| """ | |
| Process video to remove background using ONNX Runtime. | |
| Args: | |
| input_video: Path to input video | |
| output_type: "green_screen" or "foreground_only" | |
| max_resolution: Output video resolution (affects speed) | |
| downsample_ratio: Internal model quality (affects edge accuracy) | |
| progress: Gradio Progress tracker (optional, for web UI) | |
| Returns: | |
| Path to processed video | |
| """ | |
| if input_video is None: | |
| return None | |
| # Get max height from resolution option | |
| max_h = RESOLUTION_OPTIONS.get(max_resolution) | |
| session = load_rvm_onnx() | |
| output_names = [o.name for o in session.get_outputs()] | |
| cap = cv2.VideoCapture(input_video) | |
| fps = cap.get(cv2.CAP_PROP_FPS) | |
| orig_width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) | |
| orig_height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) | |
| frame_count = int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) | |
| # Resize based on quality preset | |
| if max_h and orig_height > max_h: | |
| scale = max_h / orig_height | |
| width = int(orig_width * scale) | |
| height = max_h | |
| print(f"Resizing: {orig_width}x{orig_height} -> {width}x{height}") | |
| else: | |
| width, height = orig_width, orig_height | |
| with tempfile.NamedTemporaryFile(suffix=".mp4", delete=False) as tmp: | |
| output_path = tmp.name | |
| fourcc = cv2.VideoWriter_fourcc(*"mp4v") | |
| out = cv2.VideoWriter(output_path, fourcc, fps, (width, height)) | |
| green_bg = np.zeros((height, width, 3), dtype=np.uint8) | |
| green_bg[:] = (0, 255, 0) | |
| # Initialize recurrent states (zeros for first frame) | |
| r1 = np.zeros((1, 1, 1, 1), dtype=np.float32) | |
| r2 = np.zeros((1, 1, 1, 1), dtype=np.float32) | |
| r3 = np.zeros((1, 1, 1, 1), dtype=np.float32) | |
| r4 = np.zeros((1, 1, 1, 1), dtype=np.float32) | |
| frame_idx = 0 | |
| while True: | |
| ret, frame = cap.read() | |
| if not ret: | |
| break | |
| # Resize if needed | |
| if frame.shape[0] != height or frame.shape[1] != width: | |
| frame = cv2.resize(frame, (width, height)) | |
| # Preprocess frame | |
| frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) | |
| src = frame_rgb.astype(np.float32) / 255.0 | |
| src = np.transpose(src, (2, 0, 1))[np.newaxis, ...] # NCHW | |
| # ONNX inference | |
| outputs = session.run(output_names, { | |
| 'src': src, | |
| 'r1i': r1, | |
| 'r2i': r2, | |
| 'r3i': r3, | |
| 'r4i': r4, | |
| 'downsample_ratio': np.array([downsample_ratio], dtype=np.float32) | |
| }) | |
| fgr, pha, r1, r2, r3, r4 = outputs | |
| # Postprocess | |
| fgr_np = np.transpose(fgr[0], (1, 2, 0)) # CHW -> HWC | |
| pha_np = pha[0, 0] | |
| if fgr_np.shape[:2] != (height, width): | |
| fgr_np = cv2.resize(fgr_np, (width, height)) | |
| pha_np = cv2.resize(pha_np, (width, height)) | |
| pha_3ch = np.stack([pha_np] * 3, axis=-1) | |
| if output_type == "green_screen": | |
| result = (fgr_np * pha_3ch + (green_bg / 255.0) * (1 - pha_3ch)) | |
| else: | |
| result = fgr_np * pha_3ch | |
| result_bgr = cv2.cvtColor((result * 255).astype(np.uint8), cv2.COLOR_RGB2BGR) | |
| out.write(result_bgr) | |
| frame_idx += 1 | |
| if frame_count > 0: | |
| if progress is not None: | |
| progress(frame_idx / frame_count, desc=f"Processing frame {frame_idx}/{frame_count}") | |
| elif frame_idx % 30 == 0: | |
| print(f"Processing frame {frame_idx}/{frame_count}") | |
| cap.release() | |
| out.release() | |
| return output_path | |
| # ============================================================================= | |
| # Gradio Interface | |
| # ============================================================================= | |
| # Initialize image models (lazy loading) | |
| bria_model = BriaRMBGModel() | |
| rmbg2_model = RMBG2Model() | |
| isnet_model = ISNetModel() | |
| IMAGE_MODELS = { | |
| "BRIA-RMBG 1.4 (fast, general)": bria_model, | |
| "BRIA-RMBG 2.0 (high quality, slow)": rmbg2_model, | |
| "IS-Net/DIS (fine edges, hair)": isnet_model, | |
| } | |
| IMAGE_EXTENSIONS = {'.jpg', '.jpeg', '.png', '.webp', '.bmp', '.gif', '.tiff'} | |
| VIDEO_EXTENSIONS = {'.mp4', '.avi', '.mov', '.mkv', '.webm', '.flv', '.wmv'} | |
| def detect_file_type(file_path): | |
| """Detect if file is image or video based on extension.""" | |
| if file_path is None: | |
| return None | |
| ext = os.path.splitext(file_path)[1].lower() | |
| if ext in IMAGE_EXTENSIONS: | |
| return "image" | |
| elif ext in VIDEO_EXTENSIONS: | |
| return "video" | |
| return None | |
| def on_file_upload(file_path): | |
| """Handle file upload - detect type and update preview.""" | |
| if file_path is None: | |
| return gr.Image(visible=False), gr.Video(visible=False), None | |
| file_type = detect_file_type(file_path) | |
| if file_type == "image": | |
| return gr.Image(value=file_path, visible=True), gr.Video(visible=False), "image" | |
| elif file_type == "video": | |
| return gr.Image(visible=False), gr.Video(value=file_path, visible=True), "video" | |
| return gr.Image(visible=False), gr.Video(visible=False), None | |
| def process_media(file_path, file_type, image_model_choice, output_type, max_resolution, edge_quality, progress=gr.Progress()): | |
| """Process based on stored file type.""" | |
| if file_path is None: | |
| return None, None | |
| # Auto-detect file type if not provided (for API calls) | |
| if file_type is None: | |
| file_type = detect_file_type(file_path) | |
| if file_type is None: | |
| return None, None | |
| if file_type == "image": | |
| pil_image = Image.open(file_path) | |
| model = IMAGE_MODELS.get(image_model_choice, bria_model) | |
| output_img, mask = model.remove_background(pil_image) | |
| return output_img, None | |
| elif file_type == "video": | |
| output_path = process_video(file_path, output_type, max_resolution, edge_quality, progress) | |
| return None, output_path | |
| return None, None | |
| def build_ui(): | |
| """Build compact unified Gradio interface.""" | |
| with gr.Blocks(title="Background Removal") as demo: | |
| gr.Markdown("## Background Removal\nUpload image or video. Image models: RMBG 1.4 (fast) | RMBG 2.0 (quality) | IS-Net (fine edges) | Video: RVM") | |
| # State to track file type | |
| file_type_state = gr.State(value=None) | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| input_file = gr.File( | |
| label="Upload Image or Video", | |
| file_types=["image", "video"], | |
| type="filepath" | |
| ) | |
| # Preview - only one visible at a time | |
| preview_image = gr.Image(label="Preview", interactive=False, visible=False) | |
| preview_video = gr.Video(label="Preview", interactive=False, visible=False) | |
| # Image model selector | |
| image_model_dropdown = gr.Dropdown( | |
| choices=list(IMAGE_MODELS.keys()), | |
| value="BRIA-RMBG 1.4 (fast, general)", | |
| label="Image Model", | |
| info="1.4: fast | 2.0: quality (slow) | IS-Net: fine edges" | |
| ) | |
| with gr.Accordion("Video Settings", open=False): | |
| output_type = gr.Radio( | |
| choices=["green_screen", "foreground_only"], | |
| value="green_screen", | |
| label="Output Type" | |
| ) | |
| max_res = gr.Dropdown( | |
| choices=list(RESOLUTION_OPTIONS.keys()), | |
| value="720p", | |
| label="Output Resolution" | |
| ) | |
| edge_quality = gr.Slider( | |
| minimum=0.1, | |
| maximum=1.0, | |
| value=0.25, | |
| step=0.05, | |
| label="Edge Quality" | |
| ) | |
| process_btn = gr.Button("Remove Background", variant="primary") | |
| with gr.Column(scale=1): | |
| # Output components | |
| output_image = gr.Image(label="Result (Image)", type="pil") | |
| output_video = gr.Video(label="Result (Video)") | |
| # Wrapper for examples (auto-detects file type) | |
| def process_example(file_path, image_model_choice, output_type, max_resolution, edge_quality): | |
| file_type = detect_file_type(file_path) | |
| return process_media(file_path, file_type, image_model_choice, output_type, max_resolution, edge_quality) | |
| # Example files with cached results | |
| gr.Examples( | |
| examples=[ | |
| ["examples/cat.jpg", "BRIA-RMBG 1.4 (fast, general)", "green_screen", "720p", 0.25], | |
| ], | |
| inputs=[input_file, image_model_dropdown, output_type, max_res, edge_quality], | |
| outputs=[output_image, output_video], | |
| fn=process_example, | |
| cache_examples=True, | |
| cache_mode="lazy", | |
| label="Examples" | |
| ) | |
| # On upload: detect type and show preview | |
| input_file.change( | |
| fn=on_file_upload, | |
| inputs=[input_file], | |
| outputs=[preview_image, preview_video, file_type_state] | |
| ) | |
| # On process: fill the already-visible output | |
| process_btn.click( | |
| fn=process_media, | |
| inputs=[input_file, file_type_state, image_model_dropdown, output_type, max_res, edge_quality], | |
| outputs=[output_image, output_video] | |
| ) | |
| return demo | |
| # ============================================================================= | |
| # CLI Interface | |
| # ============================================================================= | |
| def cli_main(): | |
| """CLI entry point.""" | |
| parser = argparse.ArgumentParser(description="Background Removal - Image and Video") | |
| subparsers = parser.add_subparsers(dest="command", help="Commands") | |
| # Image command | |
| img_parser = subparsers.add_parser("image", help="Remove background from image") | |
| img_parser.add_argument("input", type=str, help="Input image path") | |
| img_parser.add_argument("--output", "-o", type=str, default="output.png", help="Output path") | |
| img_parser.add_argument("--mask", "-m", type=str, help="Save mask to path") | |
| img_parser.add_argument("--model", type=str, default="BRIA-RMBG 1.4 (fast, general)", | |
| choices=list(IMAGE_MODELS.keys()), | |
| help="Image model: BRIA (fast) or IS-Net (fine edges)") | |
| # Video command | |
| vid_parser = subparsers.add_parser("video", help="Remove background from video") | |
| vid_parser.add_argument("input", type=str, help="Input video path") | |
| vid_parser.add_argument("--output", "-o", type=str, default="output.mp4", help="Output path") | |
| vid_parser.add_argument("--type", type=str, default="green_screen", | |
| choices=["green_screen", "foreground_only"]) | |
| vid_parser.add_argument("--downsample", type=float, default=0.25, help="Downsample ratio") | |
| args = parser.parse_args() | |
| if args.command == "image": | |
| print(f"Processing image: {args.input}") | |
| input_img = Image.open(args.input) | |
| # Use selected model (default: BRIA) | |
| model = IMAGE_MODELS.get(args.model, bria_model) | |
| output_img, mask = model.remove_background(input_img) | |
| output_img.save(args.output) | |
| print(f"Saved: {args.output}") | |
| if args.mask: | |
| mask.save(args.mask) | |
| print(f"Saved mask: {args.mask}") | |
| elif args.command == "video": | |
| print(f"Processing video: {args.input}") | |
| output_path = process_video(args.input, args.type, args.downsample) | |
| if output_path: | |
| import shutil | |
| shutil.move(output_path, args.output) | |
| print(f"Saved: {args.output}") | |
| else: | |
| parser.print_help() | |
| if __name__ == "__main__": | |
| if len(sys.argv) > 1 and sys.argv[1] in ["image", "video", "-h", "--help"]: | |
| cli_main() | |
| else: | |
| demo = build_ui() | |
| demo.launch(theme=gr.themes.Soft()) | |