MogensR commited on
Commit
7a78d2f
·
1 Parent(s): 2adcda8

Update utilities.py

Browse files
Files changed (1) hide show
  1. utilities.py +159 -410
utilities.py CHANGED
@@ -159,26 +159,26 @@
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)
@@ -186,12 +186,36 @@ def download_and_setup_models():
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
- # Method 1: Try direct import (requirements.txt installation)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
195
  try:
196
  logger.info("🔄 SAM2 Method 1: Direct import from requirements...")
197
  from sam2.build_sam import build_sam2
@@ -200,13 +224,12 @@ def download_and_setup_models():
200
  logger.info("✅ SAM2 imported directly from installed package")
201
  except ImportError as e:
202
  logger.info(f"❌ SAM2 Method 1 failed: {e}")
203
-
204
- # Method 2: Clone and properly setup SAM2
205
  if not sam2_loaded:
206
  try:
207
  logger.info("🔄 SAM2 Method 2: Cloning and setting up repository...")
208
  sam2_dir = "/tmp/segment-anything-2"
209
-
210
  if not os.path.exists(sam2_dir):
211
  logger.info("📥 Cloning SAM2 repository...")
212
  clone_cmd = f"git clone --depth 1 https://github.com/facebookresearch/segment-anything-2.git {sam2_dir}"
@@ -215,38 +238,31 @@ def download_and_setup_models():
215
  logger.info("✅ SAM2 repository cloned successfully")
216
  else:
217
  raise Exception("Git clone failed")
218
-
219
  # Add to path
220
  if sam2_dir not in sys.path:
221
  sys.path.insert(0, sam2_dir)
222
-
223
  # Install SAM2 dependencies if needed
224
  try:
225
  import hydra
226
  except ImportError:
227
  logger.info("Installing Hydra-core for SAM2 configs...")
228
  os.system("pip install hydra-core --quiet")
229
-
230
  from sam2.build_sam import build_sam2
231
  from sam2.sam2_image_predictor import SAM2ImagePredictor
232
  sam2_loaded = True
233
  logger.info("✅ SAM2 imported after cloning")
234
  except Exception as e:
235
  logger.info(f"❌ SAM2 Method 2 failed: {e}")
236
-
237
- # Method 3: Use simplified SAM2 loading without Hydra configs
238
  if not sam2_loaded:
239
  try:
240
  logger.info("🔄 SAM2 Method 3: Simplified loading without Hydra...")
241
-
242
  # Download checkpoint first
243
  cache_dir = os.path.expanduser("~/.cache/sam2")
244
  os.makedirs(cache_dir, exist_ok=True)
245
-
246
- # Use tiny model for better compatibility
247
  checkpoint_url = "https://dl.fbaipublicfiles.com/segment_anything_2/072824/sam2_hiera_tiny.pt"
248
  sam2_checkpoint = os.path.join(cache_dir, "sam2_hiera_tiny.pt")
249
-
250
  if not os.path.exists(sam2_checkpoint):
251
  logger.info("📥 Downloading SAM2 checkpoint...")
252
  response = requests.get(checkpoint_url, stream=True)
@@ -255,93 +271,63 @@ def download_and_setup_models():
255
  if chunk:
256
  f.write(chunk)
257
  logger.info("✅ Checkpoint downloaded")
258
-
259
- # Try to load the model directly without configs
260
- # This is a simplified approach that may work in some environments
261
  checkpoint = torch.load(sam2_checkpoint, map_location=device)
262
-
263
- # Create a simple predictor wrapper
264
  class SimpleSAM2Predictor:
265
  def __init__(self, checkpoint_path, device):
266
  self.device = device
267
  self.checkpoint_path = checkpoint_path
268
  self.image = None
269
  logger.info("Using simplified SAM2 predictor")
270
-
271
  def set_image(self, image):
272
  self.image = image.copy()
273
-
274
  def predict(self, point_coords, point_labels, multimask_output=True):
275
- # Simplified mask prediction
276
  if self.image is None:
277
  raise ValueError("No image set")
278
-
279
  h, w = self.image.shape[:2]
280
  mask = np.zeros((h, w), dtype=np.uint8)
281
-
282
- # Create mask based on points
283
  for point in point_coords:
284
  x, y = int(point[0]), int(point[1])
285
- # Create circular mask around point
286
  cv2.circle(mask, (x, y), min(w, h)//4, 255, -1)
287
-
288
- # Refine mask with GrabCut
289
  try:
290
  mask_3d = np.zeros((h, w), dtype=np.uint8)
291
  mask_3d[mask > 0] = cv2.GC_PR_FGD
292
  mask_3d[mask == 0] = cv2.GC_PR_BGD
293
-
294
  bgd_model = np.zeros((1, 65), np.float64)
295
  fgd_model = np.zeros((1, 65), np.float64)
296
-
297
  cv2.grabCut(self.image, mask_3d, None, bgd_model, fgd_model, 5, cv2.GC_INIT_WITH_MASK)
298
-
299
  mask = np.where((mask_3d == cv2.GC_FGD) | (mask_3d == cv2.GC_PR_FGD), 255, 0).astype('uint8')
300
  except:
301
  pass
302
-
303
  return [mask], [1.0], None
304
-
305
  sam2_predictor = SimpleSAM2Predictor(sam2_checkpoint, device)
306
  sam2_loaded = True
307
  logger.info("✅ Using simplified SAM2 predictor")
308
-
309
  except Exception as e:
310
  logger.info(f"❌ SAM2 Method 3 failed: {e}")
311
-
312
- # Method 4: Install via pip and try again
313
  if not sam2_loaded:
314
  try:
315
  logger.info("🔄 SAM2 Method 4: Installing via pip...")
316
-
317
- # Install dependencies first
318
  os.system("pip install hydra-core omegaconf --quiet")
319
-
320
- # Clone and install SAM2
321
  sam2_dir = "/tmp/sam2_install"
322
  if os.path.exists(sam2_dir):
323
  shutil.rmtree(sam2_dir)
324
-
325
  clone_cmd = f"git clone https://github.com/facebookresearch/segment-anything-2.git {sam2_dir}"
326
  os.system(clone_cmd)
327
-
328
- # Install SAM2
329
  install_cmd = f"cd {sam2_dir} && pip install -e . --quiet"
330
  os.system(install_cmd)
331
-
332
- # Try import again
333
  from sam2.build_sam import build_sam2
334
  from sam2.sam2_image_predictor import SAM2ImagePredictor
335
  sam2_loaded = True
336
  logger.info("✅ SAM2 installed and imported via pip")
337
  except Exception as e:
338
  logger.info(f"❌ SAM2 Method 4 failed: {e}")
339
-
340
  if not sam2_loaded:
341
  logger.warning("❌ All SAM2 loading methods failed, using OpenCV fallback")
342
  sam2_predictor = create_opencv_segmentation_fallback()
343
  else:
344
- # If SAM2 is loaded properly, initialize it
345
  if not isinstance(sam2_predictor, object) or sam2_predictor is None:
346
  try:
347
  # Choose model size based on environment and resources
@@ -353,19 +339,16 @@ def predict(self, point_coords, point_labels, multimask_output=True):
353
  model_name = "sam2_hiera_large"
354
  checkpoint_url = "https://dl.fbaipublicfiles.com/segment_anything_2/072824/sam2_hiera_large.pt"
355
  logger.info("🔧 Using SAM2 Large for maximum quality")
356
-
357
- # Download checkpoint with progress tracking and caching
358
  cache_dir = os.path.expanduser("~/.cache/sam2")
359
  os.makedirs(cache_dir, exist_ok=True)
360
  sam2_checkpoint = os.path.join(cache_dir, f"{model_name}.pt")
361
-
362
  if not os.path.exists(sam2_checkpoint):
363
  logger.info(f"📥 Downloading {model_name} checkpoint...")
364
  try:
365
  response = requests.get(checkpoint_url, stream=True)
366
  total_size = int(response.headers.get('content-length', 0))
367
  downloaded = 0
368
-
369
  with open(sam2_checkpoint, 'wb') as f:
370
  for chunk in response.iter_content(chunk_size=8192):
371
  if chunk:
@@ -374,38 +357,28 @@ def predict(self, point_coords, point_labels, multimask_output=True):
374
  if total_size > 0 and downloaded % (total_size // 20) < 8192:
375
  percent = (downloaded / total_size) * 100
376
  logger.info(f"📥 Download progress: {percent:.1f}%")
377
-
378
  logger.info(f"✅ {model_name} downloaded successfully")
379
  except Exception as e:
380
  logger.error(f"❌ Download failed: {e}")
381
  raise
382
  else:
383
  logger.info(f"✅ Using cached {model_name}")
384
-
385
  # Load SAM2 model - use the config name without .yaml extension
386
  try:
387
  logger.info(f"🚀 Loading SAM2 {model_name} on {device}...")
388
-
389
- # The config should be just the model name, not the full filename
390
- model_cfg = model_name # Use "sam2_hiera_tiny" not "sam2_hiera_tiny.yaml"
391
-
392
- # Memory optimization for limited resources
393
  if device == "cpu" or is_hf_space:
394
  torch.set_num_threads(min(4, os.cpu_count() or 1))
395
  if torch.cuda.is_available():
396
  torch.cuda.empty_cache()
397
-
398
- # Try loading on specified device
399
  sam2_model = build_sam2(model_cfg, sam2_checkpoint, device=device)
400
  sam2_predictor = SAM2ImagePredictor(sam2_model)
401
  logger.info(f"✅ SAM2 model loaded successfully on {device}")
402
-
403
  except Exception as e:
404
  if device == "cuda":
405
  logger.warning(f"❌ GPU loading failed: {e}")
406
  logger.info("🔄 Trying CPU fallback...")
407
  try:
408
- # Force CPU loading
409
  sam2_model = build_sam2(model_cfg, sam2_checkpoint, device="cpu")
410
  sam2_predictor = SAM2ImagePredictor(sam2_model)
411
  device = "cpu"
@@ -418,14 +391,12 @@ def predict(self, point_coords, point_labels, multimask_output=True):
418
  logger.error(f"❌ SAM2 loading failed: {e}")
419
  logger.info("🔄 Using OpenCV segmentation fallback")
420
  sam2_predictor = create_opencv_segmentation_fallback()
421
-
422
  except Exception as e:
423
  logger.error(f"❌ SAM2 initialization failed: {e}")
424
  sam2_predictor = create_opencv_segmentation_fallback()
425
-
426
  # === ENHANCED MATANYONE LOADING WITH MULTIPLE METHODS ===
427
  matanyone_loaded = False
428
-
429
  # Method 1: Try HuggingFace Hub integration
430
  try:
431
  logger.info("🔄 MatAnyone Method 1: HuggingFace Hub...")
@@ -436,7 +407,6 @@ def predict(self, point_coords, point_labels, multimask_output=True):
436
  logger.info("✅ MatAnyone loaded via HuggingFace Hub")
437
  except Exception as e:
438
  logger.info(f"❌ MatAnyone Method 1 failed: {e}")
439
-
440
  # Method 2: Try direct import
441
  if not matanyone_loaded:
442
  try:
@@ -447,19 +417,16 @@ def predict(self, point_coords, point_labels, multimask_output=True):
447
  '/content/MatAnyone',
448
  '/kaggle/working/MatAnyone'
449
  ]
450
-
451
  for path in matanyone_paths:
452
  if os.path.exists(path):
453
  sys.path.append(path)
454
  break
455
-
456
  from inference import MatAnyoneInference
457
  matanyone_model = MatAnyoneInference()
458
  matanyone_loaded = True
459
  logger.info("✅ MatAnyone loaded via direct import")
460
  except Exception as e:
461
  logger.info(f"❌ MatAnyone Method 2 failed: {e}")
462
-
463
  # Method 3: Try GitHub installation
464
  if not matanyone_loaded:
465
  try:
@@ -475,18 +442,16 @@ def predict(self, point_coords, point_labels, multimask_output=True):
475
  raise Exception("GitHub install failed")
476
  except Exception as e:
477
  logger.info(f"❌ MatAnyone Method 3 failed: {e}")
478
-
479
  # Method 4: Enhanced OpenCV fallback (CINEMA QUALITY)
480
  if not matanyone_loaded:
481
  logger.info("🎨 Using ENHANCED OpenCV fallback for cinema-quality matting...")
482
  matanyone_model = create_enhanced_matting_fallback()
483
  matanyone_loaded = True
484
-
485
  # Memory cleanup
486
  gc.collect()
487
  if torch.cuda.is_available():
488
  torch.cuda.empty_cache()
489
-
490
  models_loaded = True
491
  gpu_info = ""
492
  if torch.cuda.is_available() and device == "cuda":
@@ -496,120 +461,85 @@ def predict(self, point_coords, point_labels, multimask_output=True):
496
  gpu_info = " (GPU)"
497
  else:
498
  gpu_info = " (CPU)"
499
-
500
  success_msg = f"✅ ENHANCED high-quality models loaded successfully!{gpu_info}"
501
  logger.info(success_msg)
502
  return success_msg
503
-
504
  except Exception as e:
505
  error_msg = f"❌ Enhanced loading failed: {str(e)}"
506
  logger.error(error_msg)
507
  logger.error(f"Full traceback: {traceback.format_exc()}")
508
  return error_msg
509
 
 
 
510
  def create_opencv_segmentation_fallback():
511
  """Create comprehensive OpenCV-based segmentation fallback"""
512
  class OpenCVSegmentationFallback:
513
  def __init__(self):
514
  logger.info("🔧 Initializing OpenCV segmentation fallback")
515
- # Initialize background subtractor for better segmentation
516
  self.bg_subtractor = cv2.createBackgroundSubtractorMOG2(detectShadows=True)
517
  self.image = None
518
-
519
  def set_image(self, image):
520
  self.image = image.copy()
521
-
522
  def predict(self, point_coords, point_labels, multimask_output=True):
523
  """Advanced OpenCV-based person segmentation with multiple techniques"""
524
  if self.image is None:
525
  raise ValueError("No image set")
526
-
527
  h, w = self.image.shape[:2]
528
-
529
  try:
530
- # Multi-method segmentation approach
531
  masks = []
532
-
533
- # Method 1: Skin tone detection
534
  hsv = cv2.cvtColor(self.image, cv2.COLOR_BGR2HSV)
535
-
536
- # Enhanced skin tone ranges
537
  lower_skin1 = np.array([0, 20, 70], dtype=np.uint8)
538
  upper_skin1 = np.array([20, 255, 255], dtype=np.uint8)
539
  lower_skin2 = np.array([0, 20, 70], dtype=np.uint8)
540
  upper_skin2 = np.array([25, 255, 255], dtype=np.uint8)
541
-
542
  skin_mask1 = cv2.inRange(hsv, lower_skin1, upper_skin1)
543
  skin_mask2 = cv2.inRange(hsv, lower_skin2, upper_skin2)
544
  skin_mask = cv2.bitwise_or(skin_mask1, skin_mask2)
545
-
546
- # Method 2: Edge detection for person outline
547
  gray = cv2.cvtColor(self.image, cv2.COLOR_BGR2GRAY)
548
  edges = cv2.Canny(gray, 50, 150)
549
-
550
- # Method 3: Color-based segmentation
551
- lab = cv2.cvtColor(self.image, cv2.COLOR_BGR2LAB)
552
-
553
- # Method 4: Focus on center region with point guidance
554
  center_x, center_y = w//2, h//2
555
  if len(point_coords) > 0:
556
- # Use provided points as guidance
557
  center_x = int(np.mean(point_coords[:, 0]))
558
  center_y = int(np.mean(point_coords[:, 1]))
559
-
560
- # Create center-biased mask
561
  center_mask = np.zeros((h, w), dtype=np.uint8)
562
  roi_width = w // 3
563
  roi_height = h // 2
564
  cv2.ellipse(center_mask, (center_x, center_y), (roi_width, roi_height), 0, 0, 360, 255, -1)
565
-
566
- # Combine different segmentation methods
567
  combined_mask = cv2.bitwise_or(skin_mask, edges // 4)
568
  combined_mask = cv2.bitwise_and(combined_mask, center_mask)
569
-
570
- # Morphological operations for cleanup
571
  kernel_close = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (7, 7))
572
  kernel_open = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5))
573
-
574
  combined_mask = cv2.morphologyEx(combined_mask, cv2.MORPH_CLOSE, kernel_close)
575
  combined_mask = cv2.morphologyEx(combined_mask, cv2.MORPH_OPEN, kernel_open)
576
-
577
- # Fill holes using contour detection
578
  contours, _ = cv2.findContours(combined_mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
579
-
580
  if contours:
581
- # Find largest contour (likely person)
582
  largest_contour = max(contours, key=cv2.contourArea)
583
-
584
- # Create mask from largest contour
585
  mask = np.zeros((h, w), dtype=np.uint8)
586
  cv2.fillPoly(mask, [largest_contour], 255)
587
-
588
- # Smooth the mask
589
  mask = cv2.GaussianBlur(mask, (5, 5), 2.0)
590
  mask = (mask > 127).astype(np.uint8)
591
  else:
592
- # Fallback: use center region
593
  mask = center_mask
594
-
595
- # Additional refinement
596
  mask = cv2.medianBlur(mask, 5)
597
-
598
- # Return in SAM2-compatible format
599
  masks.append(mask)
600
  scores = [1.0]
601
-
602
  return masks, scores, None
603
-
604
  except Exception as e:
605
  logger.warning(f"OpenCV segmentation error: {e}")
606
- # Ultimate fallback: center rectangle
607
  mask = np.zeros((h, w), dtype=np.uint8)
608
  x1, y1 = w//4, h//6
609
  x2, y2 = 3*w//4, 5*h//6
610
  mask[y1:y2, x1:x2] = 255
611
  return [mask], [1.0], None
612
-
613
  return OpenCVSegmentationFallback()
614
 
615
  def create_enhanced_matting_fallback():
@@ -617,66 +547,36 @@ def create_enhanced_matting_fallback():
617
  class EnhancedMattingFallback:
618
  def __init__(self):
619
  logger.info("🎨 Initializing enhanced matting fallback")
620
-
621
  def infer(self, image, mask):
622
  """Enhanced mask refinement using advanced OpenCV techniques"""
623
  try:
624
- # Ensure proper format
625
  if len(mask.shape) == 3:
626
  mask = cv2.cvtColor(mask, cv2.COLOR_BGR2GRAY)
627
-
628
- # Multi-stage refinement process
629
-
630
- # Stage 1: Bilateral filter for edge-preserving smoothing
631
  refined_mask = cv2.bilateralFilter(mask, 9, 75, 75)
632
-
633
- # Stage 2: Morphological operations for structure cleanup
634
  kernel_ellipse = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3))
635
  refined_mask = cv2.morphologyEx(refined_mask, cv2.MORPH_CLOSE, kernel_ellipse)
636
  refined_mask = cv2.morphologyEx(refined_mask, cv2.MORPH_OPEN, kernel_ellipse)
637
-
638
- # Stage 3: Gaussian blur for smooth edges
639
  refined_mask = cv2.GaussianBlur(refined_mask, (3, 3), 1.0)
640
-
641
- # Stage 4: Edge enhancement for cinema quality
642
  edges = cv2.Canny(refined_mask, 50, 150)
643
  edge_enhancement = cv2.dilate(edges, np.ones((2, 2), np.uint8), iterations=1)
644
  refined_mask = cv2.bitwise_or(refined_mask, edge_enhancement // 4)
645
-
646
- # Stage 5: Distance transform for smooth transitions
647
  dist_transform = cv2.distanceTransform(refined_mask, cv2.DIST_L2, 5)
648
  dist_transform = cv2.normalize(dist_transform, None, 0, 255, cv2.NORM_MINMAX, dtype=cv2.CV_8U)
649
-
650
- # Combine distance transform with original mask
651
  alpha = 0.7
652
  refined_mask = cv2.addWeighted(refined_mask, alpha, dist_transform, 1-alpha, 0)
653
-
654
- # Stage 6: Final smoothing and cleanup
655
  refined_mask = cv2.medianBlur(refined_mask, 3)
656
-
657
- # Stage 7: Ensure smooth gradients at edges
658
  refined_mask = cv2.GaussianBlur(refined_mask, (1, 1), 0.5)
659
-
660
  return refined_mask
661
-
662
  except Exception as e:
663
  logger.warning(f"Enhanced matting error: {e}")
664
  return mask if len(mask.shape) == 2 else cv2.cvtColor(mask, cv2.COLOR_BGR2GRAY)
665
-
666
  return EnhancedMattingFallback()
667
 
668
- # Keep all other functions unchanged (segment_person_hq, refine_mask_hq, etc.)
669
- # ... [Include all the remaining functions from the original file here]
670
-
671
  def segment_person_hq(image):
672
  """High-quality person segmentation using SAM2 or fallback with optimized points"""
673
  try:
674
- # Set image for segmentation
675
  sam2_predictor.set_image(image)
676
-
677
  h, w = image.shape[:2]
678
-
679
- # Enhanced point selection (covers head, torso, limbs, and edges)
680
  points = np.array([
681
  [w//2, h//4], # Top-center (head)
682
  [w//2, h//2], # Center (torso)
@@ -686,39 +586,24 @@ def segment_person_hq(image):
686
  [w//5, h//5], # Top-left (hair/accessories)
687
  [4*w//5, h//5] # Top-right (hair/accessories)
688
  ])
689
- labels = np.ones(len(points)) # All positive points
690
-
691
- # Predict with high quality settings
692
  masks, scores, _ = sam2_predictor.predict(
693
  point_coords=points,
694
  point_labels=labels,
695
  multimask_output=True
696
  )
697
-
698
- # Select best mask based on score and size
699
  best_idx = np.argmax(scores)
700
  best_mask = masks[best_idx]
701
-
702
- # Post-processing for better quality
703
  if len(best_mask.shape) > 2:
704
  best_mask = best_mask.squeeze()
705
-
706
- # Ensure binary mask
707
  if best_mask.dtype != np.uint8:
708
  best_mask = (best_mask * 255).astype(np.uint8)
709
-
710
- # Sharper edges (reduced blur)
711
  kernel = np.ones((3, 3), np.uint8)
712
  best_mask = cv2.morphologyEx(best_mask, cv2.MORPH_CLOSE, kernel)
713
-
714
- # Apply reduced Gaussian smoothing for sharper edges
715
- best_mask = cv2.GaussianBlur(best_mask.astype(np.float32), (3, 3), 0.8) # Reduced from 1.0
716
-
717
  return (best_mask * 255).astype(np.uint8) if best_mask.max() <= 1.0 else best_mask.astype(np.uint8)
718
-
719
  except Exception as e:
720
  logger.error(f"Segmentation error: {e}")
721
- # Return center region as fallback
722
  h, w = image.shape[:2]
723
  fallback_mask = np.zeros((h, w), dtype=np.uint8)
724
  x1, y1 = w//4, h//6
@@ -729,57 +614,38 @@ def segment_person_hq(image):
729
  def refine_mask_hq(image, mask):
730
  """Cinema-quality mask refinement with stronger edge preservation"""
731
  try:
732
- # Apply pre-processing to image for better matting
733
- image_filtered = cv2.bilateralFilter(image, 10, 75, 75) # Increased from 9 to 10
734
-
735
- # Use MatAnyone or fallback for professional matting
736
  refined_mask = matanyone_model.infer(image_filtered, mask)
737
-
738
- # Ensure proper format
739
  if len(refined_mask.shape) == 3:
740
  refined_mask = cv2.cvtColor(refined_mask, cv2.COLOR_BGR2GRAY)
741
-
742
- # Stronger edge preservation with bilateral filter
743
- refined_mask = cv2.bilateralFilter(refined_mask, 10, 75, 75) # Increased from default
744
-
745
- # Post-process for smooth edges
746
  refined_mask = cv2.medianBlur(refined_mask, 3)
747
-
748
  return refined_mask
749
-
750
  except Exception as e:
751
  logger.error(f"Mask refinement error: {e}")
752
- # Return original mask if refinement fails
753
  return mask if len(mask.shape) == 2 else cv2.cvtColor(mask, cv2.COLOR_BGR2GRAY)
754
 
755
  def create_green_screen_background(frame):
756
  """Create green screen background (Stage 1 of two-stage process)"""
757
  h, w = frame.shape[:2]
758
- green_screen = np.full((h, w, 3), (0, 177, 64), dtype=np.uint8) # Professional green screen color
759
  return green_screen
760
 
761
  def create_professional_background(bg_config, width, height):
762
  """Create professional background based on configuration"""
763
  try:
764
  if bg_config["type"] == "color":
765
- # Solid color background
766
  color_hex = bg_config["colors"][0].lstrip('#')
767
  color_rgb = tuple(int(color_hex[i:i+2], 16) for i in (0, 2, 4))
768
- color_bgr = color_rgb[::-1] # Convert RGB to BGR
769
  background = np.full((height, width, 3), color_bgr, dtype=np.uint8)
770
-
771
  elif bg_config["type"] == "gradient":
772
  background = create_gradient_background(bg_config, width, height)
773
-
774
  else:
775
- # Fallback to solid color
776
  background = np.full((height, width, 3), (128, 128, 128), dtype=np.uint8)
777
-
778
  return background
779
-
780
  except Exception as e:
781
  logger.error(f"Background creation error: {e}")
782
- # Return neutral gray background as fallback
783
  return np.full((height, width, 3), (128, 128, 128), dtype=np.uint8)
784
 
785
  def create_gradient_background(bg_config, width, height):
@@ -787,8 +653,7 @@ def create_gradient_background(bg_config, width, height):
787
  try:
788
  colors = bg_config["colors"]
789
  direction = bg_config.get("direction", "vertical")
790
-
791
- # Convert hex colors to RGB
792
  rgb_colors = []
793
  for color_hex in colors:
794
  color_hex = color_hex.lstrip('#')
@@ -796,17 +661,11 @@ def create_gradient_background(bg_config, width, height):
796
  rgb = tuple(int(color_hex[i:i+2], 16) for i in (0, 2, 4))
797
  rgb_colors.append(rgb)
798
  except ValueError:
799
- # Fallback for invalid color
800
  rgb_colors.append((128, 128, 128))
801
-
802
  if not rgb_colors:
803
- rgb_colors = [(128, 128, 128)] # Fallback color
804
-
805
- # Create PIL image for high-quality gradients
806
  pil_img = Image.new('RGB', (width, height))
807
  draw = ImageDraw.Draw(pil_img)
808
-
809
- # Helper function for color interpolation
810
  def interpolate_color(colors, progress):
811
  if len(colors) == 1:
812
  return colors[0]
@@ -816,11 +675,9 @@ def interpolate_color(colors, progress):
816
  b = int(colors[0][2] + (colors[1][2] - colors[0][2]) * progress)
817
  return (r, g, b)
818
  else:
819
- # Multi-color gradient
820
  segment = progress * (len(colors) - 1)
821
  idx = int(segment)
822
  local_progress = segment - idx
823
-
824
  if idx >= len(colors) - 1:
825
  return colors[-1]
826
  else:
@@ -829,23 +686,17 @@ def interpolate_color(colors, progress):
829
  g = int(c1[1] + (c2[1] - c1[1]) * local_progress)
830
  b = int(c1[2] + (c2[2] - c1[2]) * local_progress)
831
  return (r, g, b)
832
-
833
  if direction == "vertical":
834
- # Vertical gradient - optimized line drawing
835
  for y in range(height):
836
  progress = y / height if height > 0 else 0
837
  color = interpolate_color(rgb_colors, progress)
838
  draw.line([(0, y), (width, y)], fill=color)
839
-
840
  elif direction == "horizontal":
841
- # Horizontal gradient - optimized line drawing
842
  for x in range(width):
843
  progress = x / width if width > 0 else 0
844
  color = interpolate_color(rgb_colors, progress)
845
  draw.line([(x, 0), (x, height)], fill=color)
846
-
847
  elif direction == "diagonal":
848
- # Diagonal gradient - optimized pixel setting
849
  max_distance = width + height
850
  for y in range(height):
851
  for x in range(width):
@@ -853,38 +704,27 @@ def interpolate_color(colors, progress):
853
  progress = min(1.0, progress)
854
  color = interpolate_color(rgb_colors, progress)
855
  pil_img.putpixel((x, y), color)
856
-
857
  elif direction in ["radial", "soft_radial"]:
858
- # Radial gradient - optimized with center calculation
859
  center_x, center_y = width // 2, height // 2
860
  max_distance = np.sqrt(center_x**2 + center_y**2)
861
-
862
  for y in range(height):
863
  for x in range(width):
864
  distance = np.sqrt((x - center_x)**2 + (y - center_y)**2)
865
  progress = distance / max_distance if max_distance > 0 else 0
866
  progress = min(1.0, progress)
867
-
868
  if direction == "soft_radial":
869
- progress = progress**0.7 # Softer falloff
870
-
871
  color = interpolate_color(rgb_colors, progress)
872
  pil_img.putpixel((x, y), color)
873
-
874
  else:
875
- # Default to vertical gradient for unknown directions
876
  for y in range(height):
877
  progress = y / height if height > 0 else 0
878
  color = interpolate_color(rgb_colors, progress)
879
  draw.line([(0, y), (width, y)], fill=color)
880
-
881
- # Convert PIL to OpenCV format (RGB to BGR)
882
  background = cv2.cvtColor(np.array(pil_img), cv2.COLOR_RGB2BGR)
883
  return background
884
-
885
  except Exception as e:
886
  logger.error(f"Gradient creation error: {e}")
887
- # Return simple gradient fallback
888
  background = np.zeros((height, width, 3), dtype=np.uint8)
889
  for y in range(height):
890
  intensity = int(255 * (y / height)) if height > 0 else 128
@@ -894,53 +734,31 @@ def interpolate_color(colors, progress):
894
  def replace_background_hq(frame, mask, background):
895
  """High-quality background replacement with advanced compositing"""
896
  try:
897
- # Resize background to match frame exactly with high-quality interpolation
898
- background = cv2.resize(background, (frame.shape[1], frame.shape[0]),
899
- interpolation=cv2.INTER_LANCZOS4)
900
-
901
- # Ensure mask is single channel
902
  if len(mask.shape) == 3:
903
  mask = cv2.cvtColor(mask, cv2.COLOR_BGR2GRAY)
904
-
905
- # Convert mask to float and normalize
906
  mask_float = mask.astype(np.float32) / 255.0
907
-
908
- # Apply edge feathering for smooth transitions
909
  feather_radius = 3
910
  kernel_size = feather_radius * 2 + 1
911
  mask_feathered = cv2.GaussianBlur(mask_float, (kernel_size, kernel_size), feather_radius/3)
912
-
913
- # Create 3-channel mask
914
  mask_3channel = np.stack([mask_feathered] * 3, axis=2)
915
-
916
- # High-quality compositing with gamma correction for realistic lighting
917
  frame_linear = np.power(frame.astype(np.float32) / 255.0, 2.2)
918
  background_linear = np.power(background.astype(np.float32) / 255.0, 2.2)
919
-
920
- # Composite in linear color space for accurate blending
921
  result_linear = frame_linear * mask_3channel + background_linear * (1 - mask_3channel)
922
-
923
- # Convert back to sRGB color space
924
  result = np.power(result_linear, 1/2.2) * 255.0
925
  result = np.clip(result, 0, 255).astype(np.uint8)
926
-
927
  return result
928
-
929
  except Exception as e:
930
  logger.error(f"Background replacement error: {e}")
931
- # Simple fallback compositing
932
  try:
933
  if len(mask.shape) == 3:
934
  mask = cv2.cvtColor(mask, cv2.COLOR_BGR2GRAY)
935
-
936
  background = cv2.resize(background, (frame.shape[1], frame.shape[0]))
937
  mask_normalized = mask.astype(np.float32) / 255.0
938
  mask_3channel = np.stack([mask_normalized] * 3, axis=2)
939
-
940
  result = frame * mask_3channel + background * (1 - mask_3channel)
941
  return result.astype(np.uint8)
942
  except:
943
- # Ultimate fallback - return original frame
944
  return frame
945
 
946
  def get_model_status():
@@ -957,20 +775,107 @@ def get_model_status():
957
  gpu_info = " (GPU Available)"
958
  else:
959
  gpu_info = " (CPU Mode)"
960
-
961
  return f"✅ ENHANCED high-quality models loaded{gpu_info}"
962
  except:
963
  return "✅ ENHANCED high-quality models loaded"
964
  else:
965
  return "⏳ Models not loaded. Click 'Load Models' for ENHANCED cinema-quality processing."
966
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
967
  def create_procedural_background(prompt, style, width, height):
968
  """Create procedural background based on text prompt and style"""
969
  try:
970
- # Analyze prompt for colors and patterns
971
  prompt_lower = prompt.lower()
972
-
973
- # Color mapping based on prompt keywords
974
  color_map = {
975
  'blue': ['#1e3c72', '#2a5298', '#3498db'],
976
  'ocean': ['#74b9ff', '#0984e3', '#00cec9'],
@@ -994,15 +899,12 @@ def create_procedural_background(prompt, style, width, height):
994
  'minimal': ['#ffffff', '#f1f2f6', '#ddd'],
995
  'abstract': ['#6c5ce7', '#a29bfe', '#fd79a8']
996
  }
997
-
998
- # Find matching colors
999
- selected_colors = ['#3498db', '#2ecc71', '#e74c3c'] # Default
1000
  for keyword, colors in color_map.items():
1001
  if keyword in prompt_lower:
1002
  selected_colors = colors
1003
  break
1004
-
1005
- # Create background based on style
1006
  if style == "abstract":
1007
  return create_abstract_background(selected_colors, width, height)
1008
  elif style == "minimalist":
@@ -1014,14 +916,12 @@ def create_procedural_background(prompt, style, width, height):
1014
  elif style == "artistic":
1015
  return create_artistic_background(selected_colors, width, height)
1016
  else:
1017
- # Default: photorealistic gradient
1018
  bg_config = {
1019
  "type": "gradient",
1020
  "colors": selected_colors[:2],
1021
  "direction": "diagonal"
1022
  }
1023
  return create_gradient_background(bg_config, width, height)
1024
-
1025
  except Exception as e:
1026
  logger.error(f"Procedural background creation failed: {e}")
1027
  return None
@@ -1030,16 +930,12 @@ def create_abstract_background(colors, width, height):
1030
  """Create abstract geometric background"""
1031
  try:
1032
  background = np.zeros((height, width, 3), dtype=np.uint8)
1033
-
1034
- # Convert hex colors to BGR
1035
  bgr_colors = []
1036
  for color in colors:
1037
  hex_color = color.lstrip('#')
1038
  rgb = tuple(int(hex_color[i:i+2], 16) for i in (0, 2, 4))
1039
  bgr = rgb[::-1]
1040
  bgr_colors.append(bgr)
1041
-
1042
- # Base gradient
1043
  for y in range(height):
1044
  progress = y / height
1045
  color = [
@@ -1047,23 +943,17 @@ def create_abstract_background(colors, width, height):
1047
  for i in range(3)
1048
  ]
1049
  background[y, :] = color
1050
-
1051
- # Add geometric shapes
1052
  import random
1053
- random.seed(42) # Reproducible
1054
-
1055
  for _ in range(8):
1056
  center_x = random.randint(width//4, 3*width//4)
1057
  center_y = random.randint(height//4, 3*height//4)
1058
  radius = random.randint(width//20, width//8)
1059
  color = bgr_colors[random.randint(0, len(bgr_colors)-1)]
1060
-
1061
  overlay = background.copy()
1062
  cv2.circle(overlay, (center_x, center_y), radius, color, -1)
1063
  cv2.addWeighted(background, 0.7, overlay, 0.3, 0, background)
1064
-
1065
  return background
1066
-
1067
  except Exception as e:
1068
  logger.error(f"Abstract background creation failed: {e}")
1069
  return None
@@ -1077,20 +967,14 @@ def create_minimalist_background(colors, width, height):
1077
  "direction": "soft_radial"
1078
  }
1079
  background = create_gradient_background(bg_config, width, height)
1080
-
1081
- # Add subtle element
1082
  overlay = background.copy()
1083
  center_x, center_y = width//2, height//2
1084
-
1085
  hex_color = colors[0].lstrip('#')
1086
  rgb = tuple(int(hex_color[i:i+2], 16) for i in (0, 2, 4))
1087
  bgr = rgb[::-1]
1088
-
1089
  cv2.circle(overlay, (center_x, center_y), min(width, height)//3, bgr, -1)
1090
  cv2.addWeighted(background, 0.95, overlay, 0.05, 0, background)
1091
-
1092
  return background
1093
-
1094
  except Exception as e:
1095
  logger.error(f"Minimalist background creation failed: {e}")
1096
  return None
@@ -1104,20 +988,14 @@ def create_corporate_background(colors, width, height):
1104
  "direction": "diagonal"
1105
  }
1106
  background = create_gradient_background(bg_config, width, height)
1107
-
1108
- # Add subtle grid
1109
  grid_color = (80, 80, 80)
1110
  grid_spacing = width // 20
1111
-
1112
  for x in range(0, width, grid_spacing):
1113
  cv2.line(background, (x, 0), (x, height), grid_color, 1)
1114
-
1115
  for y in range(0, height, grid_spacing):
1116
  cv2.line(background, (0, y), (width, y), grid_color, 1)
1117
-
1118
  background = cv2.GaussianBlur(background, (3, 3), 1.0)
1119
  return background
1120
-
1121
  except Exception as e:
1122
  logger.error(f"Corporate background creation failed: {e}")
1123
  return None
@@ -1131,29 +1009,20 @@ def create_nature_background(colors, width, height):
1131
  "direction": "vertical"
1132
  }
1133
  background = create_gradient_background(bg_config, width, height)
1134
-
1135
- # Add organic shapes
1136
  import random
1137
  random.seed(42)
1138
-
1139
  overlay = background.copy()
1140
-
1141
  for _ in range(5):
1142
  center_x = random.randint(width//6, 5*width//6)
1143
  center_y = random.randint(height//6, 5*height//6)
1144
-
1145
  axes_x = random.randint(width//20, width//6)
1146
  axes_y = random.randint(height//20, height//6)
1147
  angle = random.randint(0, 180)
1148
-
1149
  color = (random.randint(40, 80), random.randint(120, 160), random.randint(30, 70))
1150
  cv2.ellipse(overlay, (center_x, center_y), (axes_x, axes_y), angle, 0, 360, color, -1)
1151
-
1152
  cv2.addWeighted(background, 0.8, overlay, 0.2, 0, background)
1153
  background = cv2.GaussianBlur(background, (5, 5), 2.0)
1154
-
1155
  return background
1156
-
1157
  except Exception as e:
1158
  logger.error(f"Nature background creation failed: {e}")
1159
  return None
@@ -1161,154 +1030,34 @@ def create_nature_background(colors, width, height):
1161
  def create_artistic_background(colors, width, height):
1162
  """Create artistic background with creative elements"""
1163
  try:
1164
- # Start with base gradient
1165
  bg_config = {
1166
  "type": "gradient",
1167
  "colors": colors,
1168
  "direction": "diagonal"
1169
  }
1170
  background = create_gradient_background(bg_config, width, height)
1171
-
1172
- # Add artistic elements
1173
  import random
1174
  random.seed(42)
1175
-
1176
- # Convert colors to BGR
1177
  bgr_colors = []
1178
  for color in colors:
1179
  hex_color = color.lstrip('#')
1180
  rgb = tuple(int(hex_color[i:i+2], 16) for i in (0, 2, 4))
1181
  bgr_colors.append(rgb[::-1])
1182
-
1183
  overlay = background.copy()
1184
-
1185
- # Add flowing curves
1186
  for i in range(3):
1187
  pts = []
1188
  for x in range(0, width, width//10):
1189
  y = int(height//2 + (height//4) * np.sin(2 * np.pi * x / width + i))
1190
  pts.append([x, y])
1191
-
1192
  pts = np.array(pts, np.int32)
1193
  color = bgr_colors[i % len(bgr_colors)]
1194
  cv2.polylines(overlay, [pts], False, color, thickness=width//50)
1195
-
1196
- # Blend with base
1197
  cv2.addWeighted(background, 0.7, overlay, 0.3, 0, background)
1198
-
1199
- # Add texture
1200
  background = cv2.GaussianBlur(background, (3, 3), 1.0)
1201
-
1202
  return background
1203
-
1204
  except Exception as e:
1205
  logger.error(f"Artistic background creation failed: {e}")
1206
  return None
1207
 
1208
- # Utility functions for validation and cleanup
1209
- def validate_video_file(video_path):
1210
- """Validate video file format and basic properties"""
1211
- if not video_path or not os.path.exists(video_path):
1212
- return False, "Video file not found"
1213
-
1214
- try:
1215
- cap = cv2.VideoCapture(video_path)
1216
- if not cap.isOpened():
1217
- return False, "Cannot open video file"
1218
-
1219
- frame_count = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
1220
- if frame_count == 0:
1221
- return False, "Video appears to be empty"
1222
-
1223
- cap.release()
1224
- return True, "Video file valid"
1225
- except Exception as e:
1226
- return False, f"Error validating video: {str(e)}"
1227
-
1228
- def cleanup_temp_files():
1229
- """Clean up temporary files to free disk space"""
1230
- try:
1231
- temp_patterns = [
1232
- "/tmp/processed_video_*.mp4",
1233
- "/tmp/final_output_*.mp4",
1234
- "/tmp/greenscreen_*.mp4",
1235
- "/tmp/gradient_*.png",
1236
- "/tmp/*.pt", # Model checkpoints
1237
- ]
1238
-
1239
- import glob
1240
- for pattern in temp_patterns:
1241
- for file_path in glob.glob(pattern):
1242
- try:
1243
- if os.path.exists(file_path):
1244
- # Only delete files older than 1 hour
1245
- if time.time() - os.path.getmtime(file_path) > 3600:
1246
- os.remove(file_path)
1247
- logger.info(f"Cleaned up: {file_path}")
1248
- except Exception as e:
1249
- logger.warning(f"Could not clean up {file_path}: {e}")
1250
- except Exception as e:
1251
- logger.warning(f"Cleanup error: {e}")
1252
-
1253
- def get_available_backgrounds():
1254
- """Get list of available professional backgrounds"""
1255
- return {key: config["name"] for key, config in PROFESSIONAL_BACKGROUNDS.items()}
1256
-
1257
- def create_custom_gradient(colors, direction="vertical", width=1920, height=1080):
1258
- """
1259
- Create a custom gradient background
1260
-
1261
- Args:
1262
- colors: List of hex colors (e.g., ["#ff0000", "#00ff00"])
1263
- direction: "vertical", "horizontal", "diagonal", "radial"
1264
- width, height: Dimensions
1265
-
1266
- Returns:
1267
- numpy array of the generated background
1268
- """
1269
- try:
1270
- bg_config = {
1271
- "type": "gradient",
1272
- "colors": colors,
1273
- "direction": direction
1274
- }
1275
- return create_gradient_background(bg_config, width, height)
1276
- except Exception as e:
1277
- logger.error(f"Error creating custom gradient: {e}")
1278
- return None
1279
-
1280
- def create_directories():
1281
- """Create necessary directories for the application"""
1282
- try:
1283
- directories = [
1284
- "/tmp/MyAvatar",
1285
- "/tmp/MyAvatar/My_Videos",
1286
- os.path.expanduser("~/.cache/sam2"),
1287
- ]
1288
-
1289
- for directory in directories:
1290
- os.makedirs(directory, exist_ok=True)
1291
- logger.info(f"📁 Created/verified directory: {directory}")
1292
-
1293
- return True
1294
- except Exception as e:
1295
- logger.error(f"Error creating directories: {e}")
1296
- return False
1297
 
1298
- def optimize_memory_usage():
1299
- """Optimize memory usage by cleaning up unused resources"""
1300
- try:
1301
- # Clear PyTorch cache
1302
- if torch.cuda.is_available():
1303
- torch.cuda.empty_cache()
1304
-
1305
- # Run garbage collector
1306
- gc.collect()
1307
-
1308
- # Clear OpenCV cache
1309
- cv2.ocl.setUseOpenCL(False)
1310
-
1311
- return True
1312
- except Exception as e:
1313
- logger.warning(f"Memory optimization failed: {e}")
1314
- return False
 
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)
 
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
 
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}"
 
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)
 
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
 
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:
 
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"
 
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...")
 
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:
 
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:
 
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":
 
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():
 
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)
 
586
  [w//5, h//5], # Top-left (hair/accessories)
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
 
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:
637
  if bg_config["type"] == "color":
 
638
  color_hex = bg_config["colors"][0].lstrip('#')
639
  color_rgb = tuple(int(color_hex[i:i+2], 16) for i in (0, 2, 4))
640
+ color_bgr = color_rgb[::-1]
641
  background = np.full((height, width, 3), color_bgr, dtype=np.uint8)
 
642
  elif bg_config["type"] == "gradient":
643
  background = create_gradient_background(bg_config, width, height)
 
644
  else:
 
645
  background = np.full((height, width, 3), (128, 128, 128), dtype=np.uint8)
 
646
  return background
 
647
  except Exception as e:
648
  logger.error(f"Background creation error: {e}")
 
649
  return np.full((height, width, 3), (128, 128, 128), dtype=np.uint8)
650
 
651
  def create_gradient_background(bg_config, width, height):
 
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:
659
  color_hex = color_hex.lstrip('#')
 
661
  rgb = tuple(int(color_hex[i:i+2], 16) for i in (0, 2, 4))
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]
 
675
  b = int(colors[0][2] + (colors[1][2] - colors[0][2]) * progress)
676
  return (r, g, b)
677
  else:
 
678
  segment = progress * (len(colors) - 1)
679
  idx = int(segment)
680
  local_progress = segment - idx
 
681
  if idx >= len(colors) - 1:
682
  return colors[-1]
683
  else:
 
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
692
  color = interpolate_color(rgb_colors, progress)
693
  draw.line([(0, y), (width, y)], fill=color)
 
694
  elif direction == "horizontal":
 
695
  for x in range(width):
696
  progress = x / width if width > 0 else 0
697
  color = interpolate_color(rgb_colors, progress)
698
  draw.line([(x, 0), (x, height)], fill=color)
 
699
  elif direction == "diagonal":
 
700
  max_distance = width + height
701
  for y in range(height):
702
  for x in range(width):
 
704
  progress = min(1.0, progress)
705
  color = interpolate_color(rgb_colors, progress)
706
  pil_img.putpixel((x, y), color)
 
707
  elif direction in ["radial", "soft_radial"]:
 
708
  center_x, center_y = width // 2, height // 2
709
  max_distance = np.sqrt(center_x**2 + center_y**2)
 
710
  for y in range(height):
711
  for x in range(width):
712
  distance = np.sqrt((x - center_x)**2 + (y - center_y)**2)
713
  progress = distance / max_distance if max_distance > 0 else 0
714
  progress = min(1.0, progress)
 
715
  if direction == "soft_radial":
716
+ progress = progress**0.7
 
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
 
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():
 
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
  '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
  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
  """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
  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
 
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
  "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
 
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
 
1030
  def create_artistic_background(colors, width, height):
1031
  """Create artistic background with creative elements"""
1032
  try:
 
1033
  bg_config = {
1034
  "type": "gradient",
1035
  "colors": colors,
1036
  "direction": "diagonal"
1037
  }
1038
  background = create_gradient_background(bg_config, width, height)
 
 
1039
  import random
1040
  random.seed(42)
 
 
1041
  bgr_colors = []
1042
  for color in colors:
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 = []
1049
  for x in range(0, width, width//10):
1050
  y = int(height//2 + (height//4) * np.sin(2 * np.pi * x / width + i))
1051
  pts.append([x, y])
 
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