Update app.py
Browse files
app.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
| 1 |
#!/usr/bin/env python3
|
| 2 |
"""
|
| 3 |
-
BackgroundFX -
|
| 4 |
-
|
| 5 |
"""
|
| 6 |
|
| 7 |
import gradio as gr
|
|
@@ -129,7 +129,7 @@ def download_model(self, model_size, progress_fn=None):
|
|
| 129 |
f.write(chunk)
|
| 130 |
downloaded += len(chunk)
|
| 131 |
if progress_fn and total_size > 0:
|
| 132 |
-
progress = downloaded / total_size * 0.
|
| 133 |
progress_fn(progress, f"Downloading SAM2 {model_size} ({downloaded/1024/1024:.1f}MB/{total_size/1024/1024:.1f}MB)")
|
| 134 |
|
| 135 |
logger.info(f"SAM2 {model_size} downloaded successfully")
|
|
@@ -155,7 +155,7 @@ def load_model(self, model_size, progress_fn=None):
|
|
| 155 |
model_path = self.download_model(model_size, progress_fn)
|
| 156 |
|
| 157 |
if progress_fn:
|
| 158 |
-
progress_fn(0.
|
| 159 |
|
| 160 |
# Build model
|
| 161 |
model_config = self.models[model_size]["config"]
|
|
@@ -170,7 +170,7 @@ def load_model(self, model_size, progress_fn=None):
|
|
| 170 |
self.current_model_size = model_size
|
| 171 |
|
| 172 |
if progress_fn:
|
| 173 |
-
progress_fn(0.
|
| 174 |
|
| 175 |
logger.info(f"SAM2 {model_size} model loaded and ready")
|
| 176 |
return self.predictor
|
|
@@ -229,8 +229,134 @@ def segment_image(self, image, model_size="tiny", progress_fn=None):
|
|
| 229 |
logger.error(f"Segmentation failed: {e}")
|
| 230 |
return None, 0.0
|
| 231 |
|
| 232 |
-
#
|
| 233 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 234 |
|
| 235 |
# Video Validation
|
| 236 |
def validate_video(video_path):
|
|
@@ -342,9 +468,10 @@ def load_background_image(background_img, background_preset, target_width, targe
|
|
| 342 |
logger.error(f"Background loading failed: {e}")
|
| 343 |
return create_gradient_background(target_width, target_height)
|
| 344 |
|
| 345 |
-
#
|
| 346 |
-
def
|
| 347 |
-
|
|
|
|
| 348 |
|
| 349 |
if input_video is None:
|
| 350 |
return None, "β Please upload a video file"
|
|
@@ -388,13 +515,13 @@ def process_video_enhanced(input_video, background_img, background_preset, model
|
|
| 388 |
|
| 389 |
# Processing variables
|
| 390 |
frame_count = 0
|
| 391 |
-
|
| 392 |
processing_start_time = time.time()
|
| 393 |
|
| 394 |
-
#
|
| 395 |
-
def
|
| 396 |
-
# Map
|
| 397 |
-
overall_progress = 0.1 + (progress_val * 0.
|
| 398 |
progress(overall_progress, desc=message)
|
| 399 |
|
| 400 |
# Process frames
|
|
@@ -405,45 +532,47 @@ def sam2_progress(progress_val, message):
|
|
| 405 |
|
| 406 |
frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
|
| 407 |
|
| 408 |
-
#
|
| 409 |
-
|
|
|
|
|
|
|
| 410 |
|
| 411 |
-
if
|
| 412 |
-
|
| 413 |
-
|
| 414 |
else:
|
| 415 |
-
# Use last good
|
| 416 |
-
if
|
| 417 |
-
|
| 418 |
-
logger.warning(f"Frame {frame_count}: Using previous
|
| 419 |
else:
|
| 420 |
-
# Create center-focused fallback
|
| 421 |
-
|
| 422 |
center_x, center_y = width // 2, height // 2
|
| 423 |
y, x = np.ogrid[:height, :width]
|
| 424 |
mask_dist = np.sqrt((x - center_x)**2 + (y - center_y)**2)
|
| 425 |
-
|
| 426 |
-
logger.warning(f"Frame {frame_count}: Using fallback
|
| 427 |
|
| 428 |
# Apply edge smoothing
|
| 429 |
if edge_smoothing > 0:
|
| 430 |
kernel_size = int(edge_smoothing * 2) + 1
|
| 431 |
-
|
| 432 |
|
| 433 |
-
#
|
| 434 |
-
if
|
| 435 |
-
|
| 436 |
else:
|
| 437 |
-
|
| 438 |
|
| 439 |
# Ensure alpha is in correct range
|
| 440 |
-
|
| 441 |
|
| 442 |
foreground = frame_rgb.astype(np.float32)
|
| 443 |
background = background_image.astype(np.float32)
|
| 444 |
|
| 445 |
-
#
|
| 446 |
-
composite = foreground *
|
| 447 |
composite = np.clip(composite, 0, 255).astype(np.uint8)
|
| 448 |
|
| 449 |
# Convert back to BGR for output
|
|
@@ -453,38 +582,42 @@ def sam2_progress(progress_val, message):
|
|
| 453 |
frame_count += 1
|
| 454 |
|
| 455 |
# Update progress
|
| 456 |
-
if frame_count %
|
| 457 |
frame_progress = frame_count / total_frames
|
| 458 |
-
overall_progress = 0.
|
| 459 |
elapsed_time = time.time() - processing_start_time
|
| 460 |
if frame_count > 0:
|
| 461 |
avg_time_per_frame = elapsed_time / frame_count
|
| 462 |
remaining_time = avg_time_per_frame * (total_frames - frame_count)
|
| 463 |
-
|
|
|
|
|
|
|
| 464 |
|
| 465 |
# Memory management
|
| 466 |
-
if frame_count %
|
| 467 |
torch.cuda.empty_cache()
|
| 468 |
|
| 469 |
-
progress(0.98, desc="Finalizing video...")
|
| 470 |
|
| 471 |
# Cleanup
|
| 472 |
cap.release()
|
| 473 |
out.release()
|
| 474 |
|
| 475 |
-
# Clear
|
| 476 |
-
|
| 477 |
|
| 478 |
if CUDA_AVAILABLE:
|
| 479 |
torch.cuda.empty_cache()
|
| 480 |
gc.collect()
|
| 481 |
|
| 482 |
processing_time = time.time() - processing_start_time
|
| 483 |
-
|
|
|
|
|
|
|
| 484 |
|
| 485 |
progress(1.0, desc="Complete!")
|
| 486 |
|
| 487 |
-
return output_path, f"β
|
| 488 |
|
| 489 |
except Exception as e:
|
| 490 |
error_msg = f"β Processing failed: {str(e)}"
|
|
@@ -501,12 +634,12 @@ def sam2_progress(progress_val, message):
|
|
| 501 |
except:
|
| 502 |
pass
|
| 503 |
|
| 504 |
-
|
| 505 |
return None, error_msg
|
| 506 |
|
| 507 |
-
# Gradio Interface
|
| 508 |
-
def
|
| 509 |
-
"""Create the Gradio interface"""
|
| 510 |
|
| 511 |
# Get background presets for dropdown
|
| 512 |
preset_choices = [("Custom (upload image)", "custom")]
|
|
@@ -514,11 +647,11 @@ def create_interface():
|
|
| 514 |
preset_choices.append((name, key))
|
| 515 |
|
| 516 |
with gr.Blocks(
|
| 517 |
-
title="BackgroundFX Pro - SAM2
|
| 518 |
theme=gr.themes.Soft(),
|
| 519 |
css="""
|
| 520 |
.gradio-container {
|
| 521 |
-
max-width:
|
| 522 |
}
|
| 523 |
.main-header {
|
| 524 |
text-align: center;
|
|
@@ -527,15 +660,25 @@ def create_interface():
|
|
| 527 |
-webkit-text-fill-color: transparent;
|
| 528 |
background-clip: text;
|
| 529 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 530 |
"""
|
| 531 |
) as demo:
|
| 532 |
|
| 533 |
gr.Markdown("""
|
| 534 |
-
# π₯ BackgroundFX Pro - SAM2
|
| 535 |
-
**Professional AI video background replacement with
|
|
|
|
|
|
|
| 536 |
|
| 537 |
-
Upload your video and
|
| 538 |
-
Optimized for Hugging Face Spaces with smart memory management and lazy loading.
|
| 539 |
""", elem_classes=["main-header"])
|
| 540 |
|
| 541 |
with gr.Row():
|
|
@@ -548,7 +691,7 @@ def create_interface():
|
|
| 548 |
info="Supported: MP4, AVI, MOV, MKV, WebM (max 5 minutes)"
|
| 549 |
)
|
| 550 |
|
| 551 |
-
with gr.Tab("Background"):
|
| 552 |
background_preset = gr.Dropdown(
|
| 553 |
choices=preset_choices,
|
| 554 |
value="gradient:ocean",
|
|
@@ -563,14 +706,14 @@ def create_interface():
|
|
| 563 |
info="Upload image to override preset"
|
| 564 |
)
|
| 565 |
|
| 566 |
-
with gr.Accordion("
|
| 567 |
model_size = gr.Radio(
|
| 568 |
choices=[
|
| 569 |
("Tiny (38MB) - Fastest", "tiny"),
|
| 570 |
-
("Small (185MB) - Balanced", "small"),
|
| 571 |
("Base (320MB) - Best Quality", "base")
|
| 572 |
],
|
| 573 |
-
value="
|
| 574 |
label="SAM2 Model Size",
|
| 575 |
info="Larger models = better quality but slower processing"
|
| 576 |
)
|
|
@@ -578,21 +721,34 @@ def create_interface():
|
|
| 578 |
edge_smoothing = gr.Slider(
|
| 579 |
minimum=0,
|
| 580 |
maximum=5,
|
| 581 |
-
value=1.
|
| 582 |
step=0.5,
|
| 583 |
label="Edge Smoothing",
|
| 584 |
info="Softens edges around subject (0 = sharp, 5 = very soft)"
|
| 585 |
)
|
| 586 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 587 |
process_btn = gr.Button(
|
| 588 |
-
"π
|
| 589 |
variant="primary",
|
| 590 |
size="lg",
|
| 591 |
scale=2
|
| 592 |
)
|
| 593 |
|
| 594 |
with gr.Column(scale=1):
|
| 595 |
-
gr.Markdown("### π₯ Output")
|
| 596 |
|
| 597 |
video_output = gr.Video(
|
| 598 |
label="Processed Video",
|
|
@@ -607,13 +763,20 @@ def create_interface():
|
|
| 607 |
)
|
| 608 |
|
| 609 |
gr.Markdown("""
|
| 610 |
-
### π‘
|
| 611 |
-
- **Best results
|
| 612 |
-
- **Lighting
|
| 613 |
-
- **Movement
|
| 614 |
-
- **
|
| 615 |
-
- **
|
| 616 |
""")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 617 |
|
| 618 |
# System Information
|
| 619 |
with gr.Row():
|
|
@@ -621,33 +784,51 @@ def create_interface():
|
|
| 621 |
if CUDA_AVAILABLE:
|
| 622 |
gr.Markdown(f"π **GPU Acceleration:** {GPU_NAME} ({GPU_MEMORY:.1f}GB) | Type: {GPU_TYPE}")
|
| 623 |
else:
|
| 624 |
-
gr.Markdown("π» **CPU Mode** (GPU recommended for
|
| 625 |
|
| 626 |
with gr.Column():
|
| 627 |
-
gr.Markdown("π¦ **Storage:** 0MB
|
| 628 |
|
| 629 |
# Processing event
|
| 630 |
process_btn.click(
|
| 631 |
-
fn=
|
| 632 |
inputs=[
|
| 633 |
video_input,
|
| 634 |
background_input,
|
| 635 |
background_preset,
|
| 636 |
model_size,
|
| 637 |
-
edge_smoothing
|
|
|
|
| 638 |
],
|
| 639 |
outputs=[video_output, status_output],
|
| 640 |
show_progress=True
|
| 641 |
)
|
| 642 |
|
| 643 |
-
#
|
| 644 |
with gr.Row():
|
| 645 |
gr.Markdown("""
|
| 646 |
-
### π¬
|
| 647 |
-
-
|
| 648 |
-
-
|
| 649 |
-
-
|
| 650 |
-
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 651 |
""")
|
| 652 |
|
| 653 |
return demo
|
|
@@ -655,17 +836,17 @@ def create_interface():
|
|
| 655 |
# Main execution
|
| 656 |
if __name__ == "__main__":
|
| 657 |
# Setup logging
|
| 658 |
-
logger.info("Starting BackgroundFX Pro...")
|
| 659 |
logger.info(f"Device: {DEVICE}")
|
| 660 |
if CUDA_AVAILABLE:
|
| 661 |
logger.info(f"GPU: {GPU_NAME} ({GPU_MEMORY:.1f}GB)")
|
| 662 |
|
| 663 |
-
# Create and launch interface
|
| 664 |
-
demo =
|
| 665 |
|
| 666 |
demo.queue(
|
| 667 |
-
concurrency_count=
|
| 668 |
-
max_size=
|
| 669 |
api_open=False # Disable API for security
|
| 670 |
).launch(
|
| 671 |
server_name="0.0.0.0",
|
|
|
|
| 1 |
#!/usr/bin/env python3
|
| 2 |
"""
|
| 3 |
+
BackgroundFX Pro - SAM2 + MatAnyone Professional Video Background Replacer
|
| 4 |
+
State-of-the-art video background replacement with professional alpha matting
|
| 5 |
"""
|
| 6 |
|
| 7 |
import gradio as gr
|
|
|
|
| 129 |
f.write(chunk)
|
| 130 |
downloaded += len(chunk)
|
| 131 |
if progress_fn and total_size > 0:
|
| 132 |
+
progress = downloaded / total_size * 0.2 # 20% of total progress
|
| 133 |
progress_fn(progress, f"Downloading SAM2 {model_size} ({downloaded/1024/1024:.1f}MB/{total_size/1024/1024:.1f}MB)")
|
| 134 |
|
| 135 |
logger.info(f"SAM2 {model_size} downloaded successfully")
|
|
|
|
| 155 |
model_path = self.download_model(model_size, progress_fn)
|
| 156 |
|
| 157 |
if progress_fn:
|
| 158 |
+
progress_fn(0.25, f"Loading SAM2 {model_size} model...")
|
| 159 |
|
| 160 |
# Build model
|
| 161 |
model_config = self.models[model_size]["config"]
|
|
|
|
| 170 |
self.current_model_size = model_size
|
| 171 |
|
| 172 |
if progress_fn:
|
| 173 |
+
progress_fn(0.3, f"SAM2 {model_size} loaded successfully!")
|
| 174 |
|
| 175 |
logger.info(f"SAM2 {model_size} model loaded and ready")
|
| 176 |
return self.predictor
|
|
|
|
| 229 |
logger.error(f"Segmentation failed: {e}")
|
| 230 |
return None, 0.0
|
| 231 |
|
| 232 |
+
# MatAnyone Professional Alpha Matting
|
| 233 |
+
class MatAnyoneLazy:
|
| 234 |
+
def __init__(self):
|
| 235 |
+
self.model = None
|
| 236 |
+
self.available = False
|
| 237 |
+
|
| 238 |
+
def load_model(self, progress_fn=None):
|
| 239 |
+
"""Load MatAnyone model lazily"""
|
| 240 |
+
if self.model is not None:
|
| 241 |
+
return self.model
|
| 242 |
+
|
| 243 |
+
try:
|
| 244 |
+
if progress_fn:
|
| 245 |
+
progress_fn(0.35, "Loading MatAnyone professional matting...")
|
| 246 |
+
|
| 247 |
+
# Try to import MatAnyone
|
| 248 |
+
try:
|
| 249 |
+
from matanyone import MatAnyoneModel
|
| 250 |
+
self.model = MatAnyoneModel.from_pretrained(device=DEVICE)
|
| 251 |
+
self.available = True
|
| 252 |
+
|
| 253 |
+
if progress_fn:
|
| 254 |
+
progress_fn(0.45, "MatAnyone loaded successfully!")
|
| 255 |
+
|
| 256 |
+
logger.info("MatAnyone model loaded for professional alpha matting")
|
| 257 |
+
return self.model
|
| 258 |
+
|
| 259 |
+
except ImportError:
|
| 260 |
+
logger.warning("MatAnyone not available, using fallback alpha matting")
|
| 261 |
+
self.available = False
|
| 262 |
+
return None
|
| 263 |
+
|
| 264 |
+
except Exception as e:
|
| 265 |
+
logger.error(f"Failed to load MatAnyone: {e}")
|
| 266 |
+
self.available = False
|
| 267 |
+
return None
|
| 268 |
+
|
| 269 |
+
def refine_mask(self, image, coarse_mask, progress_fn=None):
|
| 270 |
+
"""Refine mask with MatAnyone professional alpha matting"""
|
| 271 |
+
if not self.available:
|
| 272 |
+
return coarse_mask
|
| 273 |
+
|
| 274 |
+
try:
|
| 275 |
+
model = self.load_model(progress_fn)
|
| 276 |
+
if model is None:
|
| 277 |
+
return coarse_mask
|
| 278 |
+
|
| 279 |
+
# Convert to format expected by MatAnyone
|
| 280 |
+
if image.max() <= 1.0:
|
| 281 |
+
image_input = (image * 255).astype(np.uint8)
|
| 282 |
+
else:
|
| 283 |
+
image_input = image.astype(np.uint8)
|
| 284 |
+
|
| 285 |
+
# Run MatAnyone inference
|
| 286 |
+
refined_alpha = model.predict(
|
| 287 |
+
image=image_input,
|
| 288 |
+
coarse_mask=coarse_mask,
|
| 289 |
+
quality='high'
|
| 290 |
+
)
|
| 291 |
+
|
| 292 |
+
# Ensure output is in correct format
|
| 293 |
+
if refined_alpha.max() > 1.0:
|
| 294 |
+
refined_alpha = refined_alpha / 255.0
|
| 295 |
+
|
| 296 |
+
return refined_alpha.astype(np.float32)
|
| 297 |
+
|
| 298 |
+
except Exception as e:
|
| 299 |
+
logger.warning(f"MatAnyone refinement failed, using coarse mask: {e}")
|
| 300 |
+
return coarse_mask
|
| 301 |
+
|
| 302 |
+
def clear_model(self):
|
| 303 |
+
"""Clear MatAnyone model from memory"""
|
| 304 |
+
if self.model:
|
| 305 |
+
del self.model
|
| 306 |
+
self.model = None
|
| 307 |
+
if CUDA_AVAILABLE:
|
| 308 |
+
torch.cuda.empty_cache()
|
| 309 |
+
gc.collect()
|
| 310 |
+
|
| 311 |
+
# Professional SAM2 + MatAnyone Pipeline
|
| 312 |
+
class SAM2MatAnyonePipeline:
|
| 313 |
+
def __init__(self):
|
| 314 |
+
self.sam2_loader = SAM2EnhancedLazy()
|
| 315 |
+
self.matanyone_loader = MatAnyoneLazy()
|
| 316 |
+
|
| 317 |
+
def segment_with_professional_matting(self, image, model_size="tiny", use_matanyone=True, progress_fn=None):
|
| 318 |
+
"""Professional segmentation pipeline with SAM2 + MatAnyone"""
|
| 319 |
+
|
| 320 |
+
# Step 1: SAM2 coarse segmentation
|
| 321 |
+
if progress_fn:
|
| 322 |
+
progress_fn(0.3, "SAM2 segmentation...")
|
| 323 |
+
|
| 324 |
+
coarse_mask, confidence = self.sam2_loader.segment_image(image, model_size, progress_fn)
|
| 325 |
+
|
| 326 |
+
if coarse_mask is None or confidence < 0.3:
|
| 327 |
+
logger.warning(f"SAM2 segmentation failed or low confidence: {confidence:.2f}")
|
| 328 |
+
return coarse_mask, confidence
|
| 329 |
+
|
| 330 |
+
# Step 2: MatAnyone professional refinement (if enabled)
|
| 331 |
+
if use_matanyone and confidence > 0.5:
|
| 332 |
+
if progress_fn:
|
| 333 |
+
progress_fn(0.5, "MatAnyone alpha matting refinement...")
|
| 334 |
+
|
| 335 |
+
try:
|
| 336 |
+
refined_alpha = self.matanyone_loader.refine_mask(image, coarse_mask, progress_fn)
|
| 337 |
+
|
| 338 |
+
if progress_fn:
|
| 339 |
+
progress_fn(0.6, "Professional matting complete!")
|
| 340 |
+
|
| 341 |
+
return refined_alpha, confidence
|
| 342 |
+
|
| 343 |
+
except Exception as e:
|
| 344 |
+
logger.warning(f"MatAnyone failed, using SAM2 only: {e}")
|
| 345 |
+
|
| 346 |
+
return coarse_mask, confidence
|
| 347 |
+
|
| 348 |
+
def clear_models(self):
|
| 349 |
+
"""Clear all models from memory"""
|
| 350 |
+
self.sam2_loader.clear_model()
|
| 351 |
+
self.matanyone_loader.clear_model()
|
| 352 |
+
|
| 353 |
+
if CUDA_AVAILABLE:
|
| 354 |
+
torch.cuda.empty_cache()
|
| 355 |
+
gc.collect()
|
| 356 |
+
logger.info("All models cleared from memory")
|
| 357 |
+
|
| 358 |
+
# Global pipeline
|
| 359 |
+
professional_pipeline = SAM2MatAnyonePipeline()
|
| 360 |
|
| 361 |
# Video Validation
|
| 362 |
def validate_video(video_path):
|
|
|
|
| 468 |
logger.error(f"Background loading failed: {e}")
|
| 469 |
return create_gradient_background(target_width, target_height)
|
| 470 |
|
| 471 |
+
# Professional Video Processing with MatAnyone
|
| 472 |
+
def process_video_professional(input_video, background_img, background_preset, model_size,
|
| 473 |
+
edge_smoothing, use_matanyone, progress=gr.Progress()):
|
| 474 |
+
"""Professional video processing with SAM2 + MatAnyone pipeline"""
|
| 475 |
|
| 476 |
if input_video is None:
|
| 477 |
return None, "β Please upload a video file"
|
|
|
|
| 515 |
|
| 516 |
# Processing variables
|
| 517 |
frame_count = 0
|
| 518 |
+
last_alpha = None
|
| 519 |
processing_start_time = time.time()
|
| 520 |
|
| 521 |
+
# Pipeline progress callback
|
| 522 |
+
def pipeline_progress(progress_val, message):
|
| 523 |
+
# Map pipeline progress to overall progress (10%-60%)
|
| 524 |
+
overall_progress = 0.1 + (progress_val * 0.5)
|
| 525 |
progress(overall_progress, desc=message)
|
| 526 |
|
| 527 |
# Process frames
|
|
|
|
| 532 |
|
| 533 |
frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
|
| 534 |
|
| 535 |
+
# Professional segmentation with SAM2 + MatAnyone
|
| 536 |
+
alpha, confidence = professional_pipeline.segment_with_professional_matting(
|
| 537 |
+
frame_rgb, model_size, use_matanyone, pipeline_progress
|
| 538 |
+
)
|
| 539 |
|
| 540 |
+
if alpha is not None and confidence > 0.3:
|
| 541 |
+
current_alpha = alpha
|
| 542 |
+
last_alpha = current_alpha
|
| 543 |
else:
|
| 544 |
+
# Use last good alpha or create fallback
|
| 545 |
+
if last_alpha is not None:
|
| 546 |
+
current_alpha = last_alpha
|
| 547 |
+
logger.warning(f"Frame {frame_count}: Using previous alpha (confidence: {confidence:.2f})")
|
| 548 |
else:
|
| 549 |
+
# Create center-focused fallback alpha
|
| 550 |
+
current_alpha = np.zeros((height, width), dtype=np.float32)
|
| 551 |
center_x, center_y = width // 2, height // 2
|
| 552 |
y, x = np.ogrid[:height, :width]
|
| 553 |
mask_dist = np.sqrt((x - center_x)**2 + (y - center_y)**2)
|
| 554 |
+
current_alpha = np.clip(1 - mask_dist / (min(width, height) * 0.3), 0, 1)
|
| 555 |
+
logger.warning(f"Frame {frame_count}: Using fallback alpha")
|
| 556 |
|
| 557 |
# Apply edge smoothing
|
| 558 |
if edge_smoothing > 0:
|
| 559 |
kernel_size = int(edge_smoothing * 2) + 1
|
| 560 |
+
current_alpha = cv2.GaussianBlur(current_alpha, (kernel_size, kernel_size), edge_smoothing)
|
| 561 |
|
| 562 |
+
# Professional compositing
|
| 563 |
+
if current_alpha.ndim == 2:
|
| 564 |
+
alpha_channel = np.expand_dims(current_alpha, axis=2)
|
| 565 |
else:
|
| 566 |
+
alpha_channel = current_alpha
|
| 567 |
|
| 568 |
# Ensure alpha is in correct range
|
| 569 |
+
alpha_channel = np.clip(alpha_channel, 0, 1)
|
| 570 |
|
| 571 |
foreground = frame_rgb.astype(np.float32)
|
| 572 |
background = background_image.astype(np.float32)
|
| 573 |
|
| 574 |
+
# Professional alpha compositing
|
| 575 |
+
composite = foreground * alpha_channel + background * (1 - alpha_channel)
|
| 576 |
composite = np.clip(composite, 0, 255).astype(np.uint8)
|
| 577 |
|
| 578 |
# Convert back to BGR for output
|
|
|
|
| 582 |
frame_count += 1
|
| 583 |
|
| 584 |
# Update progress
|
| 585 |
+
if frame_count % 3 == 0: # Update every 3 frames
|
| 586 |
frame_progress = frame_count / total_frames
|
| 587 |
+
overall_progress = 0.6 + (frame_progress * 0.35) # 60%-95%
|
| 588 |
elapsed_time = time.time() - processing_start_time
|
| 589 |
if frame_count > 0:
|
| 590 |
avg_time_per_frame = elapsed_time / frame_count
|
| 591 |
remaining_time = avg_time_per_frame * (total_frames - frame_count)
|
| 592 |
+
|
| 593 |
+
quality_indicator = "Professional" if use_matanyone else "Standard"
|
| 594 |
+
progress(overall_progress, desc=f"{quality_indicator} | Frame {frame_count}/{total_frames} (ETA: {remaining_time:.0f}s)")
|
| 595 |
|
| 596 |
# Memory management
|
| 597 |
+
if frame_count % 20 == 0 and CUDA_AVAILABLE:
|
| 598 |
torch.cuda.empty_cache()
|
| 599 |
|
| 600 |
+
progress(0.98, desc="Finalizing professional video...")
|
| 601 |
|
| 602 |
# Cleanup
|
| 603 |
cap.release()
|
| 604 |
out.release()
|
| 605 |
|
| 606 |
+
# Clear all models to free memory
|
| 607 |
+
professional_pipeline.clear_models()
|
| 608 |
|
| 609 |
if CUDA_AVAILABLE:
|
| 610 |
torch.cuda.empty_cache()
|
| 611 |
gc.collect()
|
| 612 |
|
| 613 |
processing_time = time.time() - processing_start_time
|
| 614 |
+
quality_info = "Professional MatAnyone" if use_matanyone else "Standard SAM2"
|
| 615 |
+
|
| 616 |
+
logger.info(f"Processing completed in {processing_time:.1f}s with {quality_info}")
|
| 617 |
|
| 618 |
progress(1.0, desc="Complete!")
|
| 619 |
|
| 620 |
+
return output_path, f"β
{quality_info} processing: {duration:.1f}s video ({total_frames} frames) in {processing_time:.1f}s"
|
| 621 |
|
| 622 |
except Exception as e:
|
| 623 |
error_msg = f"β Processing failed: {str(e)}"
|
|
|
|
| 634 |
except:
|
| 635 |
pass
|
| 636 |
|
| 637 |
+
professional_pipeline.clear_models()
|
| 638 |
return None, error_msg
|
| 639 |
|
| 640 |
+
# Enhanced Gradio Interface
|
| 641 |
+
def create_professional_interface():
|
| 642 |
+
"""Create the professional Gradio interface with MatAnyone integration"""
|
| 643 |
|
| 644 |
# Get background presets for dropdown
|
| 645 |
preset_choices = [("Custom (upload image)", "custom")]
|
|
|
|
| 647 |
preset_choices.append((name, key))
|
| 648 |
|
| 649 |
with gr.Blocks(
|
| 650 |
+
title="BackgroundFX Pro - SAM2 + MatAnyone",
|
| 651 |
theme=gr.themes.Soft(),
|
| 652 |
css="""
|
| 653 |
.gradio-container {
|
| 654 |
+
max-width: 1400px !important;
|
| 655 |
}
|
| 656 |
.main-header {
|
| 657 |
text-align: center;
|
|
|
|
| 660 |
-webkit-text-fill-color: transparent;
|
| 661 |
background-clip: text;
|
| 662 |
}
|
| 663 |
+
.professional-badge {
|
| 664 |
+
background: linear-gradient(45deg, #FFD700, #FFA500);
|
| 665 |
+
color: black;
|
| 666 |
+
padding: 8px 16px;
|
| 667 |
+
border-radius: 20px;
|
| 668 |
+
font-weight: bold;
|
| 669 |
+
display: inline-block;
|
| 670 |
+
margin: 10px 0;
|
| 671 |
+
}
|
| 672 |
"""
|
| 673 |
) as demo:
|
| 674 |
|
| 675 |
gr.Markdown("""
|
| 676 |
+
# π₯ BackgroundFX Pro - SAM2 + MatAnyone
|
| 677 |
+
**Professional AI video background replacement with state-of-the-art alpha matting**
|
| 678 |
+
|
| 679 |
+
<div class="professional-badge">π Powered by SAM2 + MatAnyone - Professional Grade</div>
|
| 680 |
|
| 681 |
+
Upload your video and experience Hollywood-quality background replacement with advanced segmentation and professional alpha matting.
|
|
|
|
| 682 |
""", elem_classes=["main-header"])
|
| 683 |
|
| 684 |
with gr.Row():
|
|
|
|
| 691 |
info="Supported: MP4, AVI, MOV, MKV, WebM (max 5 minutes)"
|
| 692 |
)
|
| 693 |
|
| 694 |
+
with gr.Tab("π¨ Background"):
|
| 695 |
background_preset = gr.Dropdown(
|
| 696 |
choices=preset_choices,
|
| 697 |
value="gradient:ocean",
|
|
|
|
| 706 |
info="Upload image to override preset"
|
| 707 |
)
|
| 708 |
|
| 709 |
+
with gr.Accordion("π€ AI Settings", open=True):
|
| 710 |
model_size = gr.Radio(
|
| 711 |
choices=[
|
| 712 |
("Tiny (38MB) - Fastest", "tiny"),
|
| 713 |
+
("Small (185MB) - Balanced β", "small"),
|
| 714 |
("Base (320MB) - Best Quality", "base")
|
| 715 |
],
|
| 716 |
+
value="small",
|
| 717 |
label="SAM2 Model Size",
|
| 718 |
info="Larger models = better quality but slower processing"
|
| 719 |
)
|
|
|
|
| 721 |
edge_smoothing = gr.Slider(
|
| 722 |
minimum=0,
|
| 723 |
maximum=5,
|
| 724 |
+
value=1.5,
|
| 725 |
step=0.5,
|
| 726 |
label="Edge Smoothing",
|
| 727 |
info="Softens edges around subject (0 = sharp, 5 = very soft)"
|
| 728 |
)
|
| 729 |
|
| 730 |
+
with gr.Accordion("π Professional Settings", open=True):
|
| 731 |
+
use_matanyone = gr.Checkbox(
|
| 732 |
+
value=True,
|
| 733 |
+
label="MatAnyone Professional Alpha Matting",
|
| 734 |
+
info="π Best quality but slower - Professional Hollywood-grade results"
|
| 735 |
+
)
|
| 736 |
+
|
| 737 |
+
gr.Markdown("""
|
| 738 |
+
**Quality Comparison:**
|
| 739 |
+
- β
**MatAnyone ON**: Professional hair/edge detail, natural compositing
|
| 740 |
+
- β‘ **MatAnyone OFF**: Fast processing, good for previews
|
| 741 |
+
""")
|
| 742 |
+
|
| 743 |
process_btn = gr.Button(
|
| 744 |
+
"π Create Professional Video",
|
| 745 |
variant="primary",
|
| 746 |
size="lg",
|
| 747 |
scale=2
|
| 748 |
)
|
| 749 |
|
| 750 |
with gr.Column(scale=1):
|
| 751 |
+
gr.Markdown("### π₯ Professional Output")
|
| 752 |
|
| 753 |
video_output = gr.Video(
|
| 754 |
label="Processed Video",
|
|
|
|
| 763 |
)
|
| 764 |
|
| 765 |
gr.Markdown("""
|
| 766 |
+
### π‘ Professional Tips
|
| 767 |
+
- **Best results**: Clean subject separation from background
|
| 768 |
+
- **Lighting**: Even lighting eliminates edge artifacts
|
| 769 |
+
- **Movement**: Steady shots for consistent quality
|
| 770 |
+
- **MatAnyone**: Use for final videos, disable for quick previews
|
| 771 |
+
- **Processing**: 60-120s per minute with MatAnyone ON
|
| 772 |
""")
|
| 773 |
+
|
| 774 |
+
# Quality indicators
|
| 775 |
+
with gr.Row():
|
| 776 |
+
gr.Markdown("**π¬ Quality Modes:**")
|
| 777 |
+
with gr.Row():
|
| 778 |
+
gr.Markdown("π **Professional** (MatAnyone): Cinema-quality edges")
|
| 779 |
+
gr.Markdown("β‘ **Standard** (SAM2 only): Fast and clean")
|
| 780 |
|
| 781 |
# System Information
|
| 782 |
with gr.Row():
|
|
|
|
| 784 |
if CUDA_AVAILABLE:
|
| 785 |
gr.Markdown(f"π **GPU Acceleration:** {GPU_NAME} ({GPU_MEMORY:.1f}GB) | Type: {GPU_TYPE}")
|
| 786 |
else:
|
| 787 |
+
gr.Markdown("π» **CPU Mode** (GPU recommended for MatAnyone)")
|
| 788 |
|
| 789 |
with gr.Column():
|
| 790 |
+
gr.Markdown("π§ **AI Models:** SAM2 + MatAnyone | π¦ **Storage:** 0MB (True lazy loading)")
|
| 791 |
|
| 792 |
# Processing event
|
| 793 |
process_btn.click(
|
| 794 |
+
fn=process_video_professional,
|
| 795 |
inputs=[
|
| 796 |
video_input,
|
| 797 |
background_input,
|
| 798 |
background_preset,
|
| 799 |
model_size,
|
| 800 |
+
edge_smoothing,
|
| 801 |
+
use_matanyone
|
| 802 |
],
|
| 803 |
outputs=[video_output, status_output],
|
| 804 |
show_progress=True
|
| 805 |
)
|
| 806 |
|
| 807 |
+
# Professional showcase
|
| 808 |
with gr.Row():
|
| 809 |
gr.Markdown("""
|
| 810 |
+
### π¬ Professional Use Cases
|
| 811 |
+
- **π― Content Creation**: Remove distracting backgrounds for professional videos
|
| 812 |
+
- **πΉ Virtual Production**: Custom backgrounds for video calls and streaming
|
| 813 |
+
- **π Education**: Clean, professional backgrounds for instructional content
|
| 814 |
+
- **π± Social Media**: Eye-catching backgrounds that make content stand out
|
| 815 |
+
- **πͺ Entertainment**: Creative backgrounds for artistic projects
|
| 816 |
+
""")
|
| 817 |
+
|
| 818 |
+
# Technical specs
|
| 819 |
+
with gr.Accordion("π§ Technical Specifications", open=False):
|
| 820 |
+
gr.Markdown("""
|
| 821 |
+
### AI Pipeline
|
| 822 |
+
- **SAM2**: Meta's Segment Anything Model 2 for object detection
|
| 823 |
+
- **MatAnyone**: State-of-the-art alpha matting for professional edges
|
| 824 |
+
- **Processing**: Lazy loading, CUDA optimization, memory management
|
| 825 |
+
|
| 826 |
+
### Performance Guide
|
| 827 |
+
| Hardware | Standard Mode | Professional Mode | Recommended |
|
| 828 |
+
|----------|---------------|-------------------|-------------|
|
| 829 |
+
| CPU | 2-3 min/video min | 4-6 min/video min | Standard only |
|
| 830 |
+
| T4-small | 30-60s/video min | 60-120s/video min | Both modes |
|
| 831 |
+
| T4-medium+ | 20-40s/video min | 40-80s/video min | Professional β |
|
| 832 |
""")
|
| 833 |
|
| 834 |
return demo
|
|
|
|
| 836 |
# Main execution
|
| 837 |
if __name__ == "__main__":
|
| 838 |
# Setup logging
|
| 839 |
+
logger.info("Starting BackgroundFX Pro with SAM2 + MatAnyone...")
|
| 840 |
logger.info(f"Device: {DEVICE}")
|
| 841 |
if CUDA_AVAILABLE:
|
| 842 |
logger.info(f"GPU: {GPU_NAME} ({GPU_MEMORY:.1f}GB)")
|
| 843 |
|
| 844 |
+
# Create and launch professional interface
|
| 845 |
+
demo = create_professional_interface()
|
| 846 |
|
| 847 |
demo.queue(
|
| 848 |
+
concurrency_count=1, # Single user for professional processing
|
| 849 |
+
max_size=5, # Max 5 in queue
|
| 850 |
api_open=False # Disable API for security
|
| 851 |
).launch(
|
| 852 |
server_name="0.0.0.0",
|