MogensR commited on
Commit
5680088
·
1 Parent(s): 3f14939

Update utilities.py

Browse files
Files changed (1) hide show
  1. utilities.py +196 -629
utilities.py CHANGED
@@ -1,53 +1,24 @@
1
  #!/usr/bin/env python3
2
  """
3
  utilities.py - Helper functions and utilities for Video Background Replacement
4
- Contains all the utility functions, model loading, and background creation functions
5
- FIXED: SAM2 configuration loading issue
6
  """
7
 
8
  import os
9
- import sys
10
- import tempfile
11
  import cv2
12
  import numpy as np
13
- from pathlib import Path
14
  import torch
15
  import requests
16
- from PIL import Image, ImageDraw, ImageFilter, ImageEnhance
17
- import json
18
- import traceback
19
- import time
20
- import shutil
21
- import gc
22
- import threading
23
- import queue
24
- from typing import Optional, Tuple, Dict, Any
25
  import logging
 
26
 
27
- # Fix OpenMP threads issue - remove problematic environment variable
28
- try:
29
- if 'OMP_NUM_THREADS' in os.environ:
30
- del os.environ['OMP_NUM_THREADS']
31
- except:
32
- pass
33
-
34
- # Suppress warnings and optimize for quality
35
- import warnings
36
- warnings.filterwarnings("ignore")
37
- os.environ['PYTORCH_CUDA_ALLOC_CONF'] = 'max_split_size_mb:1024'
38
- os.environ['CUDA_LAUNCH_BLOCKING'] = '0'
39
-
40
- # Setup logging for debugging
41
  logging.basicConfig(level=logging.INFO)
42
  logger = logging.getLogger(__name__)
43
 
44
- # Global variables for models (lazy loading)
45
- sam2_predictor = None
46
- matanyone_model = None
47
- models_loaded = False
48
- loading_lock = threading.Lock()
49
-
50
- # Professional background templates - Enhanced collection
51
  PROFESSIONAL_BACKGROUNDS = {
52
  "office_modern": {
53
  "name": "Modern Office",
@@ -156,427 +127,13 @@
156
  }
157
  }
158
 
159
- def download_and_setup_models():
160
- """ENHANCED download and setup with multiple fallback methods and lazy loading"""
161
- global sam2_predictor, matanyone_model, models_loaded
162
-
163
- with loading_lock:
164
- if models_loaded:
165
- return "✅ High-quality models already loaded"
166
-
167
- try:
168
- logger.info("🔄 Starting ENHANCED model loading with multiple fallbacks...")
169
-
170
- # Check environment and system capabilities
171
- is_hf_space = os.getenv("SPACE_ID") is not None
172
- is_colab = 'google.colab' in sys.modules
173
- is_kaggle = os.environ.get('KAGGLE_KERNEL_RUN_TYPE') is not None
174
-
175
- env_type = "HuggingFace Space" if is_hf_space else "Google Colab" if is_colab else "Kaggle" if is_kaggle else "Local"
176
- logger.info(f"Environment detected: {env_type}")
177
-
178
- # Load PyTorch and check GPU
179
- import torch
180
- logger.info(f"✅ PyTorch {torch.__version__} - CUDA: {torch.cuda.is_available()}")
181
-
182
- if torch.cuda.is_available():
183
- try:
184
- gpu_name = torch.cuda.get_device_name(0)
185
- gpu_memory = torch.cuda.get_device_properties(0).total_memory / 1e9
186
- logger.info(f"🎮 GPU: {gpu_name} ({gpu_memory:.1f}GB)")
187
- except Exception as e:
188
- logger.info(f"🎮 GPU available but details unavailable: {e}")
189
-
190
- # === ENHANCED SAM2 LOADING WITH MULTIPLE METHODS ===
191
- sam2_loaded = False
192
- device = "cuda" if torch.cuda.is_available() else "cpu"
193
-
194
- # --- Improved YAML/config handling ---
195
- config_paths = [
196
- "./configs", # Local ./configs directory
197
- "/home/user/app/configs", # Typical in HF spaces
198
- os.path.expanduser("~/.cache/sam2/configs"),
199
- ]
200
- config_dir = None
201
- for path in config_paths:
202
- if os.path.isdir(path):
203
- config_dir = path
204
- break
205
-
206
- # Copy bundled .yaml files to a found config_dir if not present
207
- bundled_configs = ["sam2_hiera_large.yaml", "sam2_hiera_tiny.yaml"]
208
- if config_dir:
209
- for cfg_file in bundled_configs:
210
- src = Path(cfg_file)
211
- dest = Path(config_dir) / cfg_file
212
- if src.exists() and not dest.exists():
213
- shutil.copyfile(src, dest)
214
- logger.info(f"✅ Copied {cfg_file} to {config_dir}")
215
- else:
216
- logger.warning("No configs directory found for SAM2! Fallback to default logic.")
217
-
218
- # --- Method 1: Try direct import (requirements.txt installation) ---
219
- try:
220
- logger.info("🔄 SAM2 Method 1: Direct import from requirements...")
221
- from sam2.build_sam import build_sam2
222
- from sam2.sam2_image_predictor import SAM2ImagePredictor
223
- sam2_loaded = True
224
- logger.info("✅ SAM2 imported directly from installed package")
225
- except ImportError as e:
226
- logger.info(f"❌ SAM2 Method 1 failed: {e}")
227
-
228
- # --- Method 2: Clone and properly setup SAM2 ---
229
- if not sam2_loaded:
230
- try:
231
- logger.info("🔄 SAM2 Method 2: Cloning and setting up repository...")
232
- sam2_dir = "/tmp/segment-anything-2"
233
- if not os.path.exists(sam2_dir):
234
- logger.info("📥 Cloning SAM2 repository...")
235
- clone_cmd = f"git clone --depth 1 https://github.com/facebookresearch/segment-anything-2.git {sam2_dir}"
236
- result = os.system(clone_cmd)
237
- if result == 0:
238
- logger.info("✅ SAM2 repository cloned successfully")
239
- else:
240
- raise Exception("Git clone failed")
241
- # Add to path
242
- if sam2_dir not in sys.path:
243
- sys.path.insert(0, sam2_dir)
244
- # Install SAM2 dependencies if needed
245
- try:
246
- import hydra
247
- except ImportError:
248
- logger.info("Installing Hydra-core for SAM2 configs...")
249
- os.system("pip install hydra-core --quiet")
250
- from sam2.build_sam import build_sam2
251
- from sam2.sam2_image_predictor import SAM2ImagePredictor
252
- sam2_loaded = True
253
- logger.info("✅ SAM2 imported after cloning")
254
- except Exception as e:
255
- logger.info(f"❌ SAM2 Method 2 failed: {e}")
256
-
257
- # --- Method 3: Use simplified SAM2 loading without Hydra configs ---
258
- if not sam2_loaded:
259
- try:
260
- logger.info("🔄 SAM2 Method 3: Simplified loading without Hydra...")
261
- # Download checkpoint first
262
- cache_dir = os.path.expanduser("~/.cache/sam2")
263
- os.makedirs(cache_dir, exist_ok=True)
264
- checkpoint_url = "https://dl.fbaipublicfiles.com/segment_anything_2/072824/sam2_hiera_tiny.pt"
265
- sam2_checkpoint = os.path.join(cache_dir, "sam2_hiera_tiny.pt")
266
- if not os.path.exists(sam2_checkpoint):
267
- logger.info("📥 Downloading SAM2 checkpoint...")
268
- response = requests.get(checkpoint_url, stream=True)
269
- with open(sam2_checkpoint, 'wb') as f:
270
- for chunk in response.iter_content(chunk_size=8192):
271
- if chunk:
272
- f.write(chunk)
273
- logger.info("✅ Checkpoint downloaded")
274
- checkpoint = torch.load(sam2_checkpoint, map_location=device)
275
- class SimpleSAM2Predictor:
276
- def __init__(self, checkpoint_path, device):
277
- self.device = device
278
- self.checkpoint_path = checkpoint_path
279
- self.image = None
280
- logger.info("Using simplified SAM2 predictor")
281
- def set_image(self, image):
282
- self.image = image.copy()
283
- def predict(self, point_coords, point_labels, multimask_output=True):
284
- if self.image is None:
285
- raise ValueError("No image set")
286
- h, w = self.image.shape[:2]
287
- mask = np.zeros((h, w), dtype=np.uint8)
288
- for point in point_coords:
289
- x, y = int(point[0]), int(point[1])
290
- cv2.circle(mask, (x, y), min(w, h)//4, 255, -1)
291
- try:
292
- mask_3d = np.zeros((h, w), dtype=np.uint8)
293
- mask_3d[mask > 0] = cv2.GC_PR_FGD
294
- mask_3d[mask == 0] = cv2.GC_PR_BGD
295
- bgd_model = np.zeros((1, 65), np.float64)
296
- fgd_model = np.zeros((1, 65), np.float64)
297
- cv2.grabCut(self.image, mask_3d, None, bgd_model, fgd_model, 5, cv2.GC_INIT_WITH_MASK)
298
- mask = np.where((mask_3d == cv2.GC_FGD) | (mask_3d == cv2.GC_PR_FGD), 255, 0).astype('uint8')
299
- except:
300
- pass
301
- return [mask], [1.0], None
302
- sam2_predictor = SimpleSAM2Predictor(sam2_checkpoint, device)
303
- sam2_loaded = True
304
- logger.info("✅ Using simplified SAM2 predictor")
305
- except Exception as e:
306
- logger.info(f"❌ SAM2 Method 3 failed: {e}")
307
-
308
- # --- Method 4: Install via pip and try again ---
309
- if not sam2_loaded:
310
- try:
311
- logger.info("🔄 SAM2 Method 4: Installing via pip...")
312
- os.system("pip install hydra-core omegaconf --quiet")
313
- sam2_dir = "/tmp/sam2_install"
314
- if os.path.exists(sam2_dir):
315
- shutil.rmtree(sam2_dir)
316
- clone_cmd = f"git clone https://github.com/facebookresearch/segment-anything-2.git {sam2_dir}"
317
- os.system(clone_cmd)
318
- install_cmd = f"cd {sam2_dir} && pip install -e . --quiet"
319
- os.system(install_cmd)
320
- from sam2.build_sam import build_sam2
321
- from sam2.sam2_image_predictor import SAM2ImagePredictor
322
- sam2_loaded = True
323
- logger.info("✅ SAM2 installed and imported via pip")
324
- except Exception as e:
325
- logger.info(f"❌ SAM2 Method 4 failed: {e}")
326
-
327
- if not sam2_loaded:
328
- logger.warning("❌ All SAM2 loading methods failed, using OpenCV fallback")
329
- sam2_predictor = create_opencv_segmentation_fallback()
330
- else:
331
- if not isinstance(sam2_predictor, object) or sam2_predictor is None:
332
- try:
333
- # Choose model size based on environment and resources
334
- if (is_hf_space and not torch.cuda.is_available()) or device == "cpu":
335
- model_name = "sam2_hiera_tiny"
336
- checkpoint_url = "https://dl.fbaipublicfiles.com/segment_anything_2/072824/sam2_hiera_tiny.pt"
337
- logger.info("🔧 Using SAM2 Tiny for CPU/limited resources")
338
- else:
339
- model_name = "sam2_hiera_large"
340
- checkpoint_url = "https://dl.fbaipublicfiles.com/segment_anything_2/072824/sam2_hiera_large.pt"
341
- logger.info("🔧 Using SAM2 Large for maximum quality")
342
- # Download checkpoint
343
- cache_dir = os.path.expanduser("~/.cache/sam2")
344
- os.makedirs(cache_dir, exist_ok=True)
345
- sam2_checkpoint = os.path.join(cache_dir, f"{model_name}.pt")
346
- if not os.path.exists(sam2_checkpoint):
347
- logger.info(f"📥 Downloading {model_name} checkpoint...")
348
- try:
349
- response = requests.get(checkpoint_url, stream=True)
350
- total_size = int(response.headers.get('content-length', 0))
351
- downloaded = 0
352
- with open(sam2_checkpoint, 'wb') as f:
353
- for chunk in response.iter_content(chunk_size=8192):
354
- if chunk:
355
- f.write(chunk)
356
- downloaded += len(chunk)
357
- if total_size > 0 and downloaded % (total_size // 20) < 8192:
358
- percent = (downloaded / total_size) * 100
359
- logger.info(f"📥 Download progress: {percent:.1f}%")
360
- logger.info(f"✅ {model_name} downloaded successfully")
361
- except Exception as e:
362
- logger.error(f"❌ Download failed: {e}")
363
- raise
364
- else:
365
- logger.info(f"✅ Using cached {model_name}")
366
- # Load SAM2 model - use the config name without .yaml extension
367
- try:
368
- logger.info(f"🚀 Loading SAM2 {model_name} on {device}...")
369
- model_cfg = model_name
370
- if device == "cpu" or is_hf_space:
371
- torch.set_num_threads(min(4, os.cpu_count() or 1))
372
- if torch.cuda.is_available():
373
- torch.cuda.empty_cache()
374
- sam2_model = build_sam2(model_cfg, sam2_checkpoint, device=device)
375
- sam2_predictor = SAM2ImagePredictor(sam2_model)
376
- logger.info(f"✅ SAM2 model loaded successfully on {device}")
377
- except Exception as e:
378
- if device == "cuda":
379
- logger.warning(f"❌ GPU loading failed: {e}")
380
- logger.info("🔄 Trying CPU fallback...")
381
- try:
382
- sam2_model = build_sam2(model_cfg, sam2_checkpoint, device="cpu")
383
- sam2_predictor = SAM2ImagePredictor(sam2_model)
384
- device = "cpu"
385
- logger.info("✅ SAM2 loaded on CPU fallback")
386
- except Exception as e2:
387
- logger.error(f"❌ CPU fallback also failed: {e2}")
388
- logger.info("🔄 Using OpenCV segmentation fallback")
389
- sam2_predictor = create_opencv_segmentation_fallback()
390
- else:
391
- logger.error(f"❌ SAM2 loading failed: {e}")
392
- logger.info("🔄 Using OpenCV segmentation fallback")
393
- sam2_predictor = create_opencv_segmentation_fallback()
394
- except Exception as e:
395
- logger.error(f"❌ SAM2 initialization failed: {e}")
396
- sam2_predictor = create_opencv_segmentation_fallback()
397
-
398
- # === ENHANCED MATANYONE LOADING WITH MULTIPLE METHODS ===
399
- matanyone_loaded = False
400
- # Method 1: Try HuggingFace Hub integration
401
- try:
402
- logger.info("🔄 MatAnyone Method 1: HuggingFace Hub...")
403
- from huggingface_hub import hf_hub_download
404
- from matanyone import InferenceCore
405
- matanyone_model = InferenceCore("PeiqingYang/MatAnyone")
406
- matanyone_loaded = True
407
- logger.info("✅ MatAnyone loaded via HuggingFace Hub")
408
- except Exception as e:
409
- logger.info(f"❌ MatAnyone Method 1 failed: {e}")
410
- # Method 2: Try direct import
411
- if not matanyone_loaded:
412
- try:
413
- logger.info("🔄 MatAnyone Method 2: Direct import...")
414
- matanyone_paths = [
415
- '/tmp/MatAnyone',
416
- './MatAnyone',
417
- '/content/MatAnyone',
418
- '/kaggle/working/MatAnyone'
419
- ]
420
- for path in matanyone_paths:
421
- if os.path.exists(path):
422
- sys.path.append(path)
423
- break
424
- from inference import MatAnyoneInference
425
- matanyone_model = MatAnyoneInference()
426
- matanyone_loaded = True
427
- logger.info("✅ MatAnyone loaded via direct import")
428
- except Exception as e:
429
- logger.info(f"❌ MatAnyone Method 2 failed: {e}")
430
- # Method 3: Try GitHub installation
431
- if not matanyone_loaded:
432
- try:
433
- logger.info("🔄 MatAnyone Method 3: Installing from GitHub...")
434
- install_cmd = "pip install git+https://github.com/pq-yang/MatAnyone.git"
435
- result = os.system(install_cmd)
436
- if result == 0:
437
- from matanyone import InferenceCore
438
- matanyone_model = InferenceCore("PeiqingYang/MatAnyone")
439
- matanyone_loaded = True
440
- logger.info("✅ MatAnyone installed and loaded via GitHub")
441
- else:
442
- raise Exception("GitHub install failed")
443
- except Exception as e:
444
- logger.info(f"❌ MatAnyone Method 3 failed: {e}")
445
- # Method 4: Enhanced OpenCV fallback (CINEMA QUALITY)
446
- if not matanyone_loaded:
447
- logger.info("🎨 Using ENHANCED OpenCV fallback for cinema-quality matting...")
448
- matanyone_model = create_enhanced_matting_fallback()
449
- matanyone_loaded = True
450
-
451
- # Memory cleanup
452
- gc.collect()
453
- if torch.cuda.is_available():
454
- torch.cuda.empty_cache()
455
- models_loaded = True
456
- gpu_info = ""
457
- if torch.cuda.is_available() and device == "cuda":
458
- try:
459
- gpu_info = f" (GPU: {torch.cuda.get_device_name(0)})"
460
- except:
461
- gpu_info = " (GPU)"
462
- else:
463
- gpu_info = " (CPU)"
464
- success_msg = f"✅ ENHANCED high-quality models loaded successfully!{gpu_info}"
465
- logger.info(success_msg)
466
- return success_msg
467
-
468
- except Exception as e:
469
- error_msg = f"❌ Enhanced loading failed: {str(e)}"
470
- logger.error(error_msg)
471
- logger.error(f"Full traceback: {traceback.format_exc()}")
472
- return error_msg
473
-
474
- # ... next: create_opencv_segmentation_fallback(), create_enhanced_matting_fallback(), and much more
475
-
476
- def create_opencv_segmentation_fallback():
477
- """Create comprehensive OpenCV-based segmentation fallback"""
478
- class OpenCVSegmentationFallback:
479
- def __init__(self):
480
- logger.info("🔧 Initializing OpenCV segmentation fallback")
481
- self.bg_subtractor = cv2.createBackgroundSubtractorMOG2(detectShadows=True)
482
- self.image = None
483
-
484
- def set_image(self, image):
485
- self.image = image.copy()
486
-
487
- def predict(self, point_coords, point_labels, multimask_output=True):
488
- """Advanced OpenCV-based person segmentation with multiple techniques"""
489
- if self.image is None:
490
- raise ValueError("No image set")
491
- h, w = self.image.shape[:2]
492
- try:
493
- # Multi-method segmentation
494
- masks = []
495
- # Skin tone detection (HSV ranges)
496
- hsv = cv2.cvtColor(self.image, cv2.COLOR_BGR2HSV)
497
- lower_skin1 = np.array([0, 20, 70], dtype=np.uint8)
498
- upper_skin1 = np.array([20, 255, 255], dtype=np.uint8)
499
- lower_skin2 = np.array([0, 20, 70], dtype=np.uint8)
500
- upper_skin2 = np.array([25, 255, 255], dtype=np.uint8)
501
- skin_mask1 = cv2.inRange(hsv, lower_skin1, upper_skin1)
502
- skin_mask2 = cv2.inRange(hsv, lower_skin2, upper_skin2)
503
- skin_mask = cv2.bitwise_or(skin_mask1, skin_mask2)
504
- # Edge detection for person outline
505
- gray = cv2.cvtColor(self.image, cv2.COLOR_BGR2GRAY)
506
- edges = cv2.Canny(gray, 50, 150)
507
- # Focus on center region (with point guidance)
508
- center_x, center_y = w//2, h//2
509
- if len(point_coords) > 0:
510
- center_x = int(np.mean(point_coords[:, 0]))
511
- center_y = int(np.mean(point_coords[:, 1]))
512
- center_mask = np.zeros((h, w), dtype=np.uint8)
513
- roi_width = w // 3
514
- roi_height = h // 2
515
- cv2.ellipse(center_mask, (center_x, center_y), (roi_width, roi_height), 0, 0, 360, 255, -1)
516
- # Combine masks
517
- combined_mask = cv2.bitwise_or(skin_mask, edges // 4)
518
- combined_mask = cv2.bitwise_and(combined_mask, center_mask)
519
- kernel_close = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (7, 7))
520
- kernel_open = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5))
521
- combined_mask = cv2.morphologyEx(combined_mask, cv2.MORPH_CLOSE, kernel_close)
522
- combined_mask = cv2.morphologyEx(combined_mask, cv2.MORPH_OPEN, kernel_open)
523
- contours, _ = cv2.findContours(combined_mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
524
- if contours:
525
- largest_contour = max(contours, key=cv2.contourArea)
526
- mask = np.zeros((h, w), dtype=np.uint8)
527
- cv2.fillPoly(mask, [largest_contour], 255)
528
- mask = cv2.GaussianBlur(mask, (5, 5), 2.0)
529
- mask = (mask > 127).astype(np.uint8)
530
- else:
531
- mask = center_mask
532
- mask = cv2.medianBlur(mask, 5)
533
- masks.append(mask)
534
- scores = [1.0]
535
- return masks, scores, None
536
- except Exception as e:
537
- logger.warning(f"OpenCV segmentation error: {e}")
538
- mask = np.zeros((h, w), dtype=np.uint8)
539
- x1, y1 = w//4, h//6
540
- x2, y2 = 3*w//4, 5*h//6
541
- mask[y1:y2, x1:x2] = 255
542
- return [mask], [1.0], None
543
- return OpenCVSegmentationFallback()
544
-
545
- def create_enhanced_matting_fallback():
546
- """Create enhanced matting fallback with advanced OpenCV techniques"""
547
- class EnhancedMattingFallback:
548
- def __init__(self):
549
- logger.info("🎨 Initializing enhanced matting fallback")
550
- def infer(self, image, mask):
551
- """Enhanced mask refinement using advanced OpenCV techniques"""
552
- try:
553
- if len(mask.shape) == 3:
554
- mask = cv2.cvtColor(mask, cv2.COLOR_BGR2GRAY)
555
- refined_mask = cv2.bilateralFilter(mask, 9, 75, 75)
556
- kernel_ellipse = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3))
557
- refined_mask = cv2.morphologyEx(refined_mask, cv2.MORPH_CLOSE, kernel_ellipse)
558
- refined_mask = cv2.morphologyEx(refined_mask, cv2.MORPH_OPEN, kernel_ellipse)
559
- refined_mask = cv2.GaussianBlur(refined_mask, (3, 3), 1.0)
560
- edges = cv2.Canny(refined_mask, 50, 150)
561
- edge_enhancement = cv2.dilate(edges, np.ones((2, 2), np.uint8), iterations=1)
562
- refined_mask = cv2.bitwise_or(refined_mask, edge_enhancement // 4)
563
- dist_transform = cv2.distanceTransform(refined_mask, cv2.DIST_L2, 5)
564
- dist_transform = cv2.normalize(dist_transform, None, 0, 255, cv2.NORM_MINMAX, dtype=cv2.CV_8U)
565
- alpha = 0.7
566
- refined_mask = cv2.addWeighted(refined_mask, alpha, dist_transform, 1-alpha, 0)
567
- refined_mask = cv2.medianBlur(refined_mask, 3)
568
- refined_mask = cv2.GaussianBlur(refined_mask, (1, 1), 0.5)
569
- return refined_mask
570
- except Exception as e:
571
- logger.warning(f"Enhanced matting error: {e}")
572
- return mask if len(mask.shape) == 2 else cv2.cvtColor(mask, cv2.COLOR_BGR2GRAY)
573
- return EnhancedMattingFallback()
574
-
575
- def segment_person_hq(image):
576
- """High-quality person segmentation using SAM2 or fallback with optimized points"""
577
  try:
578
- sam2_predictor.set_image(image)
579
  h, w = image.shape[:2]
 
 
580
  points = np.array([
581
  [w//2, h//4], # Top-center (head)
582
  [w//2, h//2], # Center (torso)
@@ -587,23 +144,33 @@ def segment_person_hq(image):
587
  [4*w//5, h//5] # Top-right (hair/accessories)
588
  ])
589
  labels = np.ones(len(points))
590
- masks, scores, _ = sam2_predictor.predict(
 
591
  point_coords=points,
592
  point_labels=labels,
593
  multimask_output=True
594
  )
 
 
595
  best_idx = np.argmax(scores)
596
  best_mask = masks[best_idx]
 
 
597
  if len(best_mask.shape) > 2:
598
  best_mask = best_mask.squeeze()
599
  if best_mask.dtype != np.uint8:
600
  best_mask = (best_mask * 255).astype(np.uint8)
 
 
601
  kernel = np.ones((3, 3), np.uint8)
602
  best_mask = cv2.morphologyEx(best_mask, cv2.MORPH_CLOSE, kernel)
603
  best_mask = cv2.GaussianBlur(best_mask.astype(np.float32), (3, 3), 0.8)
 
604
  return (best_mask * 255).astype(np.uint8) if best_mask.max() <= 1.0 else best_mask.astype(np.uint8)
 
605
  except Exception as e:
606
  logger.error(f"Segmentation error: {e}")
 
607
  h, w = image.shape[:2]
608
  fallback_mask = np.zeros((h, w), dtype=np.uint8)
609
  x1, y1 = w//4, h//6
@@ -611,26 +178,129 @@ def segment_person_hq(image):
611
  fallback_mask[y1:y2, x1:x2] = 255
612
  return fallback_mask
613
 
614
- def refine_mask_hq(image, mask):
615
- """Cinema-quality mask refinement with stronger edge preservation"""
616
  try:
 
617
  image_filtered = cv2.bilateralFilter(image, 10, 75, 75)
618
- refined_mask = matanyone_model.infer(image_filtered, mask)
 
 
 
 
 
 
 
 
 
 
619
  if len(refined_mask.shape) == 3:
620
  refined_mask = cv2.cvtColor(refined_mask, cv2.COLOR_BGR2GRAY)
 
 
621
  refined_mask = cv2.bilateralFilter(refined_mask, 10, 75, 75)
622
  refined_mask = cv2.medianBlur(refined_mask, 3)
 
623
  return refined_mask
 
624
  except Exception as e:
625
  logger.error(f"Mask refinement error: {e}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
626
  return mask if len(mask.shape) == 2 else cv2.cvtColor(mask, cv2.COLOR_BGR2GRAY)
627
 
628
  def create_green_screen_background(frame):
629
- """Create green screen background (Stage 1 of two-stage process)"""
630
  h, w = frame.shape[:2]
631
  green_screen = np.full((h, w, 3), (0, 177, 64), dtype=np.uint8)
632
  return green_screen
633
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
634
  def create_professional_background(bg_config, width, height):
635
  """Create professional background based on configuration"""
636
  try:
@@ -649,10 +319,11 @@ def create_professional_background(bg_config, width, height):
649
  return np.full((height, width, 3), (128, 128, 128), dtype=np.uint8)
650
 
651
  def create_gradient_background(bg_config, width, height):
652
- """Create high-quality gradient backgrounds with comprehensive direction support"""
653
  try:
654
  colors = bg_config["colors"]
655
  direction = bg_config.get("direction", "vertical")
 
656
  # Convert hex to RGB
657
  rgb_colors = []
658
  for color_hex in colors:
@@ -662,10 +333,14 @@ def create_gradient_background(bg_config, width, height):
662
  rgb_colors.append(rgb)
663
  except ValueError:
664
  rgb_colors.append((128, 128, 128))
 
665
  if not rgb_colors:
666
  rgb_colors = [(128, 128, 128)]
 
 
667
  pil_img = Image.new('RGB', (width, height))
668
  draw = ImageDraw.Draw(pil_img)
 
669
  def interpolate_color(colors, progress):
670
  if len(colors) == 1:
671
  return colors[0]
@@ -686,6 +361,8 @@ def interpolate_color(colors, progress):
686
  g = int(c1[1] + (c2[1] - c1[1]) * local_progress)
687
  b = int(c1[2] + (c2[2] - c1[2]) * local_progress)
688
  return (r, g, b)
 
 
689
  if direction == "vertical":
690
  for y in range(height):
691
  progress = y / height if height > 0 else 0
@@ -717,165 +394,31 @@ def interpolate_color(colors, progress):
717
  color = interpolate_color(rgb_colors, progress)
718
  pil_img.putpixel((x, y), color)
719
  else:
 
720
  for y in range(height):
721
  progress = y / height if height > 0 else 0
722
  color = interpolate_color(rgb_colors, progress)
723
  draw.line([(0, y), (width, y)], fill=color)
 
 
724
  background = cv2.cvtColor(np.array(pil_img), cv2.COLOR_RGB2BGR)
725
  return background
 
726
  except Exception as e:
727
  logger.error(f"Gradient creation error: {e}")
 
728
  background = np.zeros((height, width, 3), dtype=np.uint8)
729
  for y in range(height):
730
  intensity = int(255 * (y / height)) if height > 0 else 128
731
  background[y, :] = [intensity, intensity, intensity]
732
  return background
733
 
734
- def replace_background_hq(frame, mask, background):
735
- """High-quality background replacement with advanced compositing"""
736
- try:
737
- background = cv2.resize(background, (frame.shape[1], frame.shape[0]), interpolation=cv2.INTER_LANCZOS4)
738
- if len(mask.shape) == 3:
739
- mask = cv2.cvtColor(mask, cv2.COLOR_BGR2GRAY)
740
- mask_float = mask.astype(np.float32) / 255.0
741
- feather_radius = 3
742
- kernel_size = feather_radius * 2 + 1
743
- mask_feathered = cv2.GaussianBlur(mask_float, (kernel_size, kernel_size), feather_radius/3)
744
- mask_3channel = np.stack([mask_feathered] * 3, axis=2)
745
- frame_linear = np.power(frame.astype(np.float32) / 255.0, 2.2)
746
- background_linear = np.power(background.astype(np.float32) / 255.0, 2.2)
747
- result_linear = frame_linear * mask_3channel + background_linear * (1 - mask_3channel)
748
- result = np.power(result_linear, 1/2.2) * 255.0
749
- result = np.clip(result, 0, 255).astype(np.uint8)
750
- return result
751
- except Exception as e:
752
- logger.error(f"Background replacement error: {e}")
753
- try:
754
- if len(mask.shape) == 3:
755
- mask = cv2.cvtColor(mask, cv2.COLOR_BGR2GRAY)
756
- background = cv2.resize(background, (frame.shape[1], frame.shape[0]))
757
- mask_normalized = mask.astype(np.float32) / 255.0
758
- mask_3channel = np.stack([mask_normalized] * 3, axis=2)
759
- result = frame * mask_3channel + background * (1 - mask_3channel)
760
- return result.astype(np.uint8)
761
- except:
762
- return frame
763
-
764
- def get_model_status():
765
- """Get current model loading status with detailed information"""
766
- if models_loaded:
767
- try:
768
- gpu_info = ""
769
- if torch.cuda.is_available():
770
- try:
771
- gpu_name = torch.cuda.get_device_name(0)
772
- gpu_memory = torch.cuda.get_device_properties(0).total_memory / 1e9
773
- gpu_info = f" (GPU: {gpu_name[:20]}{'...' if len(gpu_name) > 20 else ''} - {gpu_memory:.1f}GB)"
774
- except:
775
- gpu_info = " (GPU Available)"
776
- else:
777
- gpu_info = " (CPU Mode)"
778
- return f"✅ ENHANCED high-quality models loaded{gpu_info}"
779
- except:
780
- return "✅ ENHANCED high-quality models loaded"
781
- else:
782
- return "⏳ Models not loaded. Click 'Load Models' for ENHANCED cinema-quality processing."
783
-
784
- def validate_video_file(video_path):
785
- """Validate video file format and basic properties"""
786
- if not video_path or not os.path.exists(video_path):
787
- return False, "Video file not found"
788
- try:
789
- cap = cv2.VideoCapture(video_path)
790
- if not cap.isOpened():
791
- return False, "Cannot open video file"
792
- frame_count = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
793
- if frame_count == 0:
794
- return False, "Video appears to be empty"
795
- cap.release()
796
- return True, "Video file valid"
797
- except Exception as e:
798
- return False, f"Error validating video: {str(e)}"
799
-
800
- def cleanup_temp_files():
801
- """Clean up temporary files to free disk space"""
802
- try:
803
- temp_patterns = [
804
- "/tmp/processed_video_*.mp4",
805
- "/tmp/final_output_*.mp4",
806
- "/tmp/greenscreen_*.mp4",
807
- "/tmp/gradient_*.png",
808
- "/tmp/*.pt", # Model checkpoints
809
- ]
810
- import glob
811
- for pattern in temp_patterns:
812
- for file_path in glob.glob(pattern):
813
- try:
814
- if os.path.exists(file_path):
815
- if time.time() - os.path.getmtime(file_path) > 3600:
816
- os.remove(file_path)
817
- logger.info(f"Cleaned up: {file_path}")
818
- except Exception as e:
819
- logger.warning(f"Could not clean up {file_path}: {e}")
820
- except Exception as e:
821
- logger.warning(f"Cleanup error: {e}")
822
-
823
- def get_available_backgrounds():
824
- """Get list of available professional backgrounds"""
825
- return {key: config["name"] for key, config in PROFESSIONAL_BACKGROUNDS.items()}
826
-
827
- def create_custom_gradient(colors, direction="vertical", width=1920, height=1080):
828
- """
829
- Create a custom gradient background
830
- Args:
831
- colors: List of hex colors (e.g., ["#ff0000", "#00ff00"])
832
- direction: "vertical", "horizontal", "diagonal", "radial"
833
- width, height: Dimensions
834
- Returns:
835
- numpy array of the generated background
836
- """
837
- try:
838
- bg_config = {
839
- "type": "gradient",
840
- "colors": colors,
841
- "direction": direction
842
- }
843
- return create_gradient_background(bg_config, width, height)
844
- except Exception as e:
845
- logger.error(f"Error creating custom gradient: {e}")
846
- return None
847
-
848
- def create_directories():
849
- """Create necessary directories for the application"""
850
- try:
851
- directories = [
852
- "/tmp/MyAvatar",
853
- "/tmp/MyAvatar/My_Videos",
854
- os.path.expanduser("~/.cache/sam2"),
855
- ]
856
- for directory in directories:
857
- os.makedirs(directory, exist_ok=True)
858
- logger.info(f"📁 Created/verified directory: {directory}")
859
- return True
860
- except Exception as e:
861
- logger.error(f"Error creating directories: {e}")
862
- return False
863
-
864
- def optimize_memory_usage():
865
- """Optimize memory usage by cleaning up unused resources"""
866
- try:
867
- if torch.cuda.is_available():
868
- torch.cuda.empty_cache()
869
- gc.collect()
870
- cv2.ocl.setUseOpenCL(False)
871
- return True
872
- except Exception as e:
873
- logger.warning(f"Memory optimization failed: {e}")
874
- return False
875
  def create_procedural_background(prompt, style, width, height):
876
  """Create procedural background based on text prompt and style"""
877
  try:
878
  prompt_lower = prompt.lower()
 
 
879
  color_map = {
880
  'blue': ['#1e3c72', '#2a5298', '#3498db'],
881
  'ocean': ['#74b9ff', '#0984e3', '#00cec9'],
@@ -899,12 +442,15 @@ def create_procedural_background(prompt, style, width, height):
899
  'minimal': ['#ffffff', '#f1f2f6', '#ddd'],
900
  'abstract': ['#6c5ce7', '#a29bfe', '#fd79a8']
901
  }
902
- selected_colors = ['#3498db', '#2ecc71', '#e74c3c']
 
 
903
  for keyword, colors in color_map.items():
904
  if keyword in prompt_lower:
905
  selected_colors = colors
906
  break
907
-
 
908
  if style == "abstract":
909
  return create_abstract_background(selected_colors, width, height)
910
  elif style == "minimalist":
@@ -916,12 +462,14 @@ def create_procedural_background(prompt, style, width, height):
916
  elif style == "artistic":
917
  return create_artistic_background(selected_colors, width, height)
918
  else:
 
919
  bg_config = {
920
  "type": "gradient",
921
  "colors": selected_colors[:2],
922
  "direction": "diagonal"
923
  }
924
  return create_gradient_background(bg_config, width, height)
 
925
  except Exception as e:
926
  logger.error(f"Procedural background creation failed: {e}")
927
  return None
@@ -930,12 +478,16 @@ def create_abstract_background(colors, width, height):
930
  """Create abstract geometric background"""
931
  try:
932
  background = np.zeros((height, width, 3), dtype=np.uint8)
 
 
933
  bgr_colors = []
934
  for color in colors:
935
  hex_color = color.lstrip('#')
936
  rgb = tuple(int(hex_color[i:i+2], 16) for i in (0, 2, 4))
937
  bgr = rgb[::-1]
938
  bgr_colors.append(bgr)
 
 
939
  for y in range(height):
940
  progress = y / height
941
  color = [
@@ -943,17 +495,22 @@ def create_abstract_background(colors, width, height):
943
  for i in range(3)
944
  ]
945
  background[y, :] = color
 
 
946
  import random
947
- random.seed(42)
948
  for _ in range(8):
949
  center_x = random.randint(width//4, 3*width//4)
950
  center_y = random.randint(height//4, 3*height//4)
951
  radius = random.randint(width//20, width//8)
952
  color = bgr_colors[random.randint(0, len(bgr_colors)-1)]
 
953
  overlay = background.copy()
954
  cv2.circle(overlay, (center_x, center_y), radius, color, -1)
955
  cv2.addWeighted(background, 0.7, overlay, 0.3, 0, background)
 
956
  return background
 
957
  except Exception as e:
958
  logger.error(f"Abstract background creation failed: {e}")
959
  return None
@@ -966,15 +523,8 @@ def create_minimalist_background(colors, width, height):
966
  "colors": colors[:2],
967
  "direction": "soft_radial"
968
  }
969
- background = create_gradient_background(bg_config, width, height)
970
- overlay = background.copy()
971
- center_x, center_y = width//2, height//2
972
- hex_color = colors[0].lstrip('#')
973
- rgb = tuple(int(hex_color[i:i+2], 16) for i in (0, 2, 4))
974
- bgr = rgb[::-1]
975
- cv2.circle(overlay, (center_x, center_y), min(width, height)//3, bgr, -1)
976
- cv2.addWeighted(background, 0.95, overlay, 0.05, 0, background)
977
- return background
978
  except Exception as e:
979
  logger.error(f"Minimalist background creation failed: {e}")
980
  return None
@@ -988,14 +538,18 @@ def create_corporate_background(colors, width, height):
988
  "direction": "diagonal"
989
  }
990
  background = create_gradient_background(bg_config, width, height)
 
 
991
  grid_color = (80, 80, 80)
992
  grid_spacing = width // 20
993
  for x in range(0, width, grid_spacing):
994
  cv2.line(background, (x, 0), (x, height), grid_color, 1)
995
  for y in range(0, height, grid_spacing):
996
  cv2.line(background, (0, y), (width, y), grid_color, 1)
 
997
  background = cv2.GaussianBlur(background, (3, 3), 1.0)
998
  return background
 
999
  except Exception as e:
1000
  logger.error(f"Corporate background creation failed: {e}")
1001
  return None
@@ -1008,21 +562,8 @@ def create_nature_background(colors, width, height):
1008
  "colors": ['#2d5016', '#3c6e1f', '#4caf50'],
1009
  "direction": "vertical"
1010
  }
1011
- background = create_gradient_background(bg_config, width, height)
1012
- import random
1013
- random.seed(42)
1014
- overlay = background.copy()
1015
- for _ in range(5):
1016
- center_x = random.randint(width//6, 5*width//6)
1017
- center_y = random.randint(height//6, 5*height//6)
1018
- axes_x = random.randint(width//20, width//6)
1019
- axes_y = random.randint(height//20, height//6)
1020
- angle = random.randint(0, 180)
1021
- color = (random.randint(40, 80), random.randint(120, 160), random.randint(30, 70))
1022
- cv2.ellipse(overlay, (center_x, center_y), (axes_x, axes_y), angle, 0, 360, color, -1)
1023
- cv2.addWeighted(background, 0.8, overlay, 0.2, 0, background)
1024
- background = cv2.GaussianBlur(background, (5, 5), 2.0)
1025
- return background
1026
  except Exception as e:
1027
  logger.error(f"Nature background creation failed: {e}")
1028
  return None
@@ -1036,6 +577,8 @@ def create_artistic_background(colors, width, height):
1036
  "direction": "diagonal"
1037
  }
1038
  background = create_gradient_background(bg_config, width, height)
 
 
1039
  import random
1040
  random.seed(42)
1041
  bgr_colors = []
@@ -1043,6 +586,7 @@ def create_artistic_background(colors, width, height):
1043
  hex_color = color.lstrip('#')
1044
  rgb = tuple(int(hex_color[i:i+2], 16) for i in (0, 2, 4))
1045
  bgr_colors.append(rgb[::-1])
 
1046
  overlay = background.copy()
1047
  for i in range(3):
1048
  pts = []
@@ -1052,12 +596,35 @@ def create_artistic_background(colors, width, height):
1052
  pts = np.array(pts, np.int32)
1053
  color = bgr_colors[i % len(bgr_colors)]
1054
  cv2.polylines(overlay, [pts], False, color, thickness=width//50)
 
1055
  cv2.addWeighted(background, 0.7, overlay, 0.3, 0, background)
1056
  background = cv2.GaussianBlur(background, (3, 3), 1.0)
1057
  return background
 
1058
  except Exception as e:
1059
  logger.error(f"Artistic background creation failed: {e}")
1060
  return None
1061
 
1062
- # ========== END OF FILE ==========
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1063
 
 
 
 
 
1
  #!/usr/bin/env python3
2
  """
3
  utilities.py - Helper functions and utilities for Video Background Replacement
4
+ Contains all the utility functions, background creation functions
5
+ UPDATED: Models passed as parameters instead of globals
6
  """
7
 
8
  import os
 
 
9
  import cv2
10
  import numpy as np
 
11
  import torch
12
  import requests
13
+ from PIL import Image, ImageDraw
 
 
 
 
 
 
 
 
14
  import logging
15
+ import time
16
 
17
+ # Setup logging
 
 
 
 
 
 
 
 
 
 
 
 
 
18
  logging.basicConfig(level=logging.INFO)
19
  logger = logging.getLogger(__name__)
20
 
21
+ # Professional background templates
 
 
 
 
 
 
22
  PROFESSIONAL_BACKGROUNDS = {
23
  "office_modern": {
24
  "name": "Modern Office",
 
127
  }
128
  }
129
 
130
+ def segment_person_hq(image, predictor):
131
+ """High-quality person segmentation using provided SAM2 predictor"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
132
  try:
133
+ predictor.set_image(image)
134
  h, w = image.shape[:2]
135
+
136
+ # Strategic point placement for person detection
137
  points = np.array([
138
  [w//2, h//4], # Top-center (head)
139
  [w//2, h//2], # Center (torso)
 
144
  [4*w//5, h//5] # Top-right (hair/accessories)
145
  ])
146
  labels = np.ones(len(points))
147
+
148
+ masks, scores, _ = predictor.predict(
149
  point_coords=points,
150
  point_labels=labels,
151
  multimask_output=True
152
  )
153
+
154
+ # Select best mask
155
  best_idx = np.argmax(scores)
156
  best_mask = masks[best_idx]
157
+
158
+ # Ensure proper format
159
  if len(best_mask.shape) > 2:
160
  best_mask = best_mask.squeeze()
161
  if best_mask.dtype != np.uint8:
162
  best_mask = (best_mask * 255).astype(np.uint8)
163
+
164
+ # Post-process mask
165
  kernel = np.ones((3, 3), np.uint8)
166
  best_mask = cv2.morphologyEx(best_mask, cv2.MORPH_CLOSE, kernel)
167
  best_mask = cv2.GaussianBlur(best_mask.astype(np.float32), (3, 3), 0.8)
168
+
169
  return (best_mask * 255).astype(np.uint8) if best_mask.max() <= 1.0 else best_mask.astype(np.uint8)
170
+
171
  except Exception as e:
172
  logger.error(f"Segmentation error: {e}")
173
+ # Fallback to simple center mask
174
  h, w = image.shape[:2]
175
  fallback_mask = np.zeros((h, w), dtype=np.uint8)
176
  x1, y1 = w//4, h//6
 
178
  fallback_mask[y1:y2, x1:x2] = 255
179
  return fallback_mask
180
 
181
+ def refine_mask_hq(image, mask, matanyone_processor):
182
+ """Cinema-quality mask refinement using provided MatAnyone processor"""
183
  try:
184
+ # Prepare image for matting
185
  image_filtered = cv2.bilateralFilter(image, 10, 75, 75)
186
+
187
+ # Use MatAnyone for refinement
188
+ if hasattr(matanyone_processor, 'process_video'):
189
+ # If it's the HF InferenceCore, we need to handle differently
190
+ # For now, use enhanced OpenCV refinement
191
+ refined_mask = enhance_mask_opencv(image_filtered, mask)
192
+ else:
193
+ # Direct inference call
194
+ refined_mask = matanyone_processor.infer(image_filtered, mask)
195
+
196
+ # Ensure proper format
197
  if len(refined_mask.shape) == 3:
198
  refined_mask = cv2.cvtColor(refined_mask, cv2.COLOR_BGR2GRAY)
199
+
200
+ # Additional refinement
201
  refined_mask = cv2.bilateralFilter(refined_mask, 10, 75, 75)
202
  refined_mask = cv2.medianBlur(refined_mask, 3)
203
+
204
  return refined_mask
205
+
206
  except Exception as e:
207
  logger.error(f"Mask refinement error: {e}")
208
+ return enhance_mask_opencv(image, mask)
209
+
210
+ def enhance_mask_opencv(image, mask):
211
+ """Enhanced mask refinement using OpenCV techniques"""
212
+ try:
213
+ if len(mask.shape) == 3:
214
+ mask = cv2.cvtColor(mask, cv2.COLOR_BGR2GRAY)
215
+
216
+ # Bilateral filtering for edge preservation
217
+ refined_mask = cv2.bilateralFilter(mask, 9, 75, 75)
218
+
219
+ # Morphological operations
220
+ kernel_ellipse = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3))
221
+ refined_mask = cv2.morphologyEx(refined_mask, cv2.MORPH_CLOSE, kernel_ellipse)
222
+ refined_mask = cv2.morphologyEx(refined_mask, cv2.MORPH_OPEN, kernel_ellipse)
223
+
224
+ # Gaussian blur for smoothing
225
+ refined_mask = cv2.GaussianBlur(refined_mask, (3, 3), 1.0)
226
+
227
+ # Edge enhancement
228
+ edges = cv2.Canny(refined_mask, 50, 150)
229
+ edge_enhancement = cv2.dilate(edges, np.ones((2, 2), np.uint8), iterations=1)
230
+ refined_mask = cv2.bitwise_or(refined_mask, edge_enhancement // 4)
231
+
232
+ # Distance transform for better interior
233
+ dist_transform = cv2.distanceTransform(refined_mask, cv2.DIST_L2, 5)
234
+ dist_transform = cv2.normalize(dist_transform, None, 0, 255, cv2.NORM_MINMAX, dtype=cv2.CV_8U)
235
+
236
+ # Blend with distance transform
237
+ alpha = 0.7
238
+ refined_mask = cv2.addWeighted(refined_mask, alpha, dist_transform, 1-alpha, 0)
239
+
240
+ # Final smoothing
241
+ refined_mask = cv2.medianBlur(refined_mask, 3)
242
+ refined_mask = cv2.GaussianBlur(refined_mask, (1, 1), 0.5)
243
+
244
+ return refined_mask
245
+
246
+ except Exception as e:
247
+ logger.warning(f"Enhanced mask refinement error: {e}")
248
  return mask if len(mask.shape) == 2 else cv2.cvtColor(mask, cv2.COLOR_BGR2GRAY)
249
 
250
  def create_green_screen_background(frame):
251
+ """Create green screen background for two-stage processing"""
252
  h, w = frame.shape[:2]
253
  green_screen = np.full((h, w, 3), (0, 177, 64), dtype=np.uint8)
254
  return green_screen
255
 
256
+ def replace_background_hq(frame, mask, background):
257
+ """High-quality background replacement with advanced compositing"""
258
+ try:
259
+ # Resize background to match frame
260
+ background = cv2.resize(background, (frame.shape[1], frame.shape[0]), interpolation=cv2.INTER_LANCZOS4)
261
+
262
+ # Ensure mask is single channel
263
+ if len(mask.shape) == 3:
264
+ mask = cv2.cvtColor(mask, cv2.COLOR_BGR2GRAY)
265
+
266
+ # Normalize mask to 0-1 range
267
+ mask_float = mask.astype(np.float32) / 255.0
268
+
269
+ # Edge feathering for smooth transitions
270
+ feather_radius = 3
271
+ kernel_size = feather_radius * 2 + 1
272
+ mask_feathered = cv2.GaussianBlur(mask_float, (kernel_size, kernel_size), feather_radius/3)
273
+
274
+ # Create 3-channel mask
275
+ mask_3channel = np.stack([mask_feathered] * 3, axis=2)
276
+
277
+ # Linear gamma correction for proper compositing
278
+ frame_linear = np.power(frame.astype(np.float32) / 255.0, 2.2)
279
+ background_linear = np.power(background.astype(np.float32) / 255.0, 2.2)
280
+
281
+ # Composite in linear space
282
+ result_linear = frame_linear * mask_3channel + background_linear * (1 - mask_3channel)
283
+
284
+ # Convert back to gamma space
285
+ result = np.power(result_linear, 1/2.2) * 255.0
286
+ result = np.clip(result, 0, 255).astype(np.uint8)
287
+
288
+ return result
289
+
290
+ except Exception as e:
291
+ logger.error(f"Background replacement error: {e}")
292
+ # Fallback to simple replacement
293
+ try:
294
+ if len(mask.shape) == 3:
295
+ mask = cv2.cvtColor(mask, cv2.COLOR_BGR2GRAY)
296
+ background = cv2.resize(background, (frame.shape[1], frame.shape[0]))
297
+ mask_normalized = mask.astype(np.float32) / 255.0
298
+ mask_3channel = np.stack([mask_normalized] * 3, axis=2)
299
+ result = frame * mask_3channel + background * (1 - mask_3channel)
300
+ return result.astype(np.uint8)
301
+ except:
302
+ return frame
303
+
304
  def create_professional_background(bg_config, width, height):
305
  """Create professional background based on configuration"""
306
  try:
 
319
  return np.full((height, width, 3), (128, 128, 128), dtype=np.uint8)
320
 
321
  def create_gradient_background(bg_config, width, height):
322
+ """Create high-quality gradient backgrounds"""
323
  try:
324
  colors = bg_config["colors"]
325
  direction = bg_config.get("direction", "vertical")
326
+
327
  # Convert hex to RGB
328
  rgb_colors = []
329
  for color_hex in colors:
 
333
  rgb_colors.append(rgb)
334
  except ValueError:
335
  rgb_colors.append((128, 128, 128))
336
+
337
  if not rgb_colors:
338
  rgb_colors = [(128, 128, 128)]
339
+
340
+ # Create PIL image for gradient
341
  pil_img = Image.new('RGB', (width, height))
342
  draw = ImageDraw.Draw(pil_img)
343
+
344
  def interpolate_color(colors, progress):
345
  if len(colors) == 1:
346
  return colors[0]
 
361
  g = int(c1[1] + (c2[1] - c1[1]) * local_progress)
362
  b = int(c1[2] + (c2[2] - c1[2]) * local_progress)
363
  return (r, g, b)
364
+
365
+ # Generate gradient based on direction
366
  if direction == "vertical":
367
  for y in range(height):
368
  progress = y / height if height > 0 else 0
 
394
  color = interpolate_color(rgb_colors, progress)
395
  pil_img.putpixel((x, y), color)
396
  else:
397
+ # Default to vertical
398
  for y in range(height):
399
  progress = y / height if height > 0 else 0
400
  color = interpolate_color(rgb_colors, progress)
401
  draw.line([(0, y), (width, y)], fill=color)
402
+
403
+ # Convert to OpenCV format
404
  background = cv2.cvtColor(np.array(pil_img), cv2.COLOR_RGB2BGR)
405
  return background
406
+
407
  except Exception as e:
408
  logger.error(f"Gradient creation error: {e}")
409
+ # Fallback gradient
410
  background = np.zeros((height, width, 3), dtype=np.uint8)
411
  for y in range(height):
412
  intensity = int(255 * (y / height)) if height > 0 else 128
413
  background[y, :] = [intensity, intensity, intensity]
414
  return background
415
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
416
  def create_procedural_background(prompt, style, width, height):
417
  """Create procedural background based on text prompt and style"""
418
  try:
419
  prompt_lower = prompt.lower()
420
+
421
+ # Color mapping based on keywords
422
  color_map = {
423
  'blue': ['#1e3c72', '#2a5298', '#3498db'],
424
  'ocean': ['#74b9ff', '#0984e3', '#00cec9'],
 
442
  'minimal': ['#ffffff', '#f1f2f6', '#ddd'],
443
  'abstract': ['#6c5ce7', '#a29bfe', '#fd79a8']
444
  }
445
+
446
+ # Select colors based on prompt
447
+ selected_colors = ['#3498db', '#2ecc71', '#e74c3c'] # Default
448
  for keyword, colors in color_map.items():
449
  if keyword in prompt_lower:
450
  selected_colors = colors
451
  break
452
+
453
+ # Create background based on style
454
  if style == "abstract":
455
  return create_abstract_background(selected_colors, width, height)
456
  elif style == "minimalist":
 
462
  elif style == "artistic":
463
  return create_artistic_background(selected_colors, width, height)
464
  else:
465
+ # Default gradient
466
  bg_config = {
467
  "type": "gradient",
468
  "colors": selected_colors[:2],
469
  "direction": "diagonal"
470
  }
471
  return create_gradient_background(bg_config, width, height)
472
+
473
  except Exception as e:
474
  logger.error(f"Procedural background creation failed: {e}")
475
  return None
 
478
  """Create abstract geometric background"""
479
  try:
480
  background = np.zeros((height, width, 3), dtype=np.uint8)
481
+
482
+ # Convert hex colors to BGR
483
  bgr_colors = []
484
  for color in colors:
485
  hex_color = color.lstrip('#')
486
  rgb = tuple(int(hex_color[i:i+2], 16) for i in (0, 2, 4))
487
  bgr = rgb[::-1]
488
  bgr_colors.append(bgr)
489
+
490
+ # Create base gradient
491
  for y in range(height):
492
  progress = y / height
493
  color = [
 
495
  for i in range(3)
496
  ]
497
  background[y, :] = color
498
+
499
+ # Add geometric shapes
500
  import random
501
+ random.seed(42) # Consistent results
502
  for _ in range(8):
503
  center_x = random.randint(width//4, 3*width//4)
504
  center_y = random.randint(height//4, 3*height//4)
505
  radius = random.randint(width//20, width//8)
506
  color = bgr_colors[random.randint(0, len(bgr_colors)-1)]
507
+
508
  overlay = background.copy()
509
  cv2.circle(overlay, (center_x, center_y), radius, color, -1)
510
  cv2.addWeighted(background, 0.7, overlay, 0.3, 0, background)
511
+
512
  return background
513
+
514
  except Exception as e:
515
  logger.error(f"Abstract background creation failed: {e}")
516
  return None
 
523
  "colors": colors[:2],
524
  "direction": "soft_radial"
525
  }
526
+ return create_gradient_background(bg_config, width, height)
527
+
 
 
 
 
 
 
 
528
  except Exception as e:
529
  logger.error(f"Minimalist background creation failed: {e}")
530
  return None
 
538
  "direction": "diagonal"
539
  }
540
  background = create_gradient_background(bg_config, width, height)
541
+
542
+ # Add subtle grid pattern
543
  grid_color = (80, 80, 80)
544
  grid_spacing = width // 20
545
  for x in range(0, width, grid_spacing):
546
  cv2.line(background, (x, 0), (x, height), grid_color, 1)
547
  for y in range(0, height, grid_spacing):
548
  cv2.line(background, (0, y), (width, y), grid_color, 1)
549
+
550
  background = cv2.GaussianBlur(background, (3, 3), 1.0)
551
  return background
552
+
553
  except Exception as e:
554
  logger.error(f"Corporate background creation failed: {e}")
555
  return None
 
562
  "colors": ['#2d5016', '#3c6e1f', '#4caf50'],
563
  "direction": "vertical"
564
  }
565
+ return create_gradient_background(bg_config, width, height)
566
+
 
 
 
 
 
 
 
 
 
 
 
 
 
567
  except Exception as e:
568
  logger.error(f"Nature background creation failed: {e}")
569
  return None
 
577
  "direction": "diagonal"
578
  }
579
  background = create_gradient_background(bg_config, width, height)
580
+
581
+ # Add artistic wave patterns
582
  import random
583
  random.seed(42)
584
  bgr_colors = []
 
586
  hex_color = color.lstrip('#')
587
  rgb = tuple(int(hex_color[i:i+2], 16) for i in (0, 2, 4))
588
  bgr_colors.append(rgb[::-1])
589
+
590
  overlay = background.copy()
591
  for i in range(3):
592
  pts = []
 
596
  pts = np.array(pts, np.int32)
597
  color = bgr_colors[i % len(bgr_colors)]
598
  cv2.polylines(overlay, [pts], False, color, thickness=width//50)
599
+
600
  cv2.addWeighted(background, 0.7, overlay, 0.3, 0, background)
601
  background = cv2.GaussianBlur(background, (3, 3), 1.0)
602
  return background
603
+
604
  except Exception as e:
605
  logger.error(f"Artistic background creation failed: {e}")
606
  return None
607
 
608
+ def get_model_status():
609
+ """Get current model loading status"""
610
+ return "Models loaded in app.py - ready for processing"
611
+
612
+ def validate_video_file(video_path):
613
+ """Validate video file format and basic properties"""
614
+ if not video_path or not os.path.exists(video_path):
615
+ return False, "Video file not found"
616
+ try:
617
+ cap = cv2.VideoCapture(video_path)
618
+ if not cap.isOpened():
619
+ return False, "Cannot open video file"
620
+ frame_count = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
621
+ if frame_count == 0:
622
+ return False, "Video appears to be empty"
623
+ cap.release()
624
+ return True, "Video file valid"
625
+ except Exception as e:
626
+ return False, f"Error validating video: {str(e)}"
627
 
628
+ def get_available_backgrounds():
629
+ """Get list of available professional backgrounds"""
630
+ return {key: config["name"] for key, config in PROFESSIONAL_BACKGROUNDS.items()}