MogensR commited on
Commit
86a214f
Β·
1 Parent(s): d06a12c

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +109 -32
app.py CHANGED
@@ -1,7 +1,8 @@
1
  """
2
- 🍹 Video Background Replacer - IFRAME OPTIMIZED VERSION
3
  Optimized for embedding in MyAvatar Railway app with Claude's recommendations
4
  FIXED: Corrected MatAnyone API usage (processor.step() instead of processor.infer())
 
5
  """
6
 
7
  import streamlit as st
@@ -102,6 +103,16 @@
102
  margin: 20px 0;
103
  }
104
 
 
 
 
 
 
 
 
 
 
 
105
  /* Iframe-specific optimizations */
106
  .block-container {
107
  padding-top: 1rem;
@@ -240,10 +251,10 @@ def process_frame_matanyone(self, frame, is_first_frame=False, mask=None):
240
  print(f"MatAnyone processing failed: {e}, using fallback")
241
  return self.create_simple_mask(frame).astype(np.float32) / 255.0
242
 
243
- def process_frame(self, frame, background_image, is_first_frame=False, mask=None):
244
- """Process a single frame with background replacement"""
245
  if self.use_matanyone:
246
- # Use CORRECTED MatAnyone API
247
  alpha_mask = self.process_frame_matanyone(frame, is_first_frame, mask)
248
  elif self.use_mediapipe:
249
  try:
@@ -267,18 +278,66 @@ def process_frame(self, frame, background_image, is_first_frame=False, mask=None
267
  # Simple fallback method
268
  alpha_mask = self.create_simple_mask(frame).astype(np.float32) / 255.0
269
 
270
- # Resize background to match frame
271
- bg_resized = cv2.resize(background_image, (frame.shape[1], frame.shape[0]))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
272
 
273
- # Apply background replacement
274
- # mask values close to 1 = keep original (person)
275
- # mask values close to 0 = use background
276
- result = frame * alpha_mask + bg_resized * (1 - alpha_mask)
277
 
278
- return result.astype(np.uint8)
279
 
280
  def process_video(self, video_path, background_image, progress_callback=None):
281
- """Process entire video with background replacement"""
282
  # Convert PIL background to OpenCV format
283
  if isinstance(background_image, Image.Image):
284
  background_image = cv2.cvtColor(np.array(background_image), cv2.COLOR_RGB2BGR)
@@ -309,8 +368,8 @@ def process_video(self, video_path, background_image, progress_callback=None):
309
  is_first_frame = (frame_count == 0)
310
  mask = None # Could add automatic mask detection here if needed
311
 
312
- # Process frame
313
- processed_frame = self.process_frame(frame, background_image, is_first_frame, mask)
314
  out.write(processed_frame)
315
 
316
  frame_count += 1
@@ -318,7 +377,8 @@ def process_video(self, video_path, background_image, progress_callback=None):
318
  # Update progress
319
  if progress_callback:
320
  progress = frame_count / total_frames
321
- progress_callback(progress, frame_count, total_frames)
 
322
 
323
  finally:
324
  cap.release()
@@ -329,20 +389,30 @@ def process_video(self, video_path, background_image, progress_callback=None):
329
  def main():
330
  # Compact header for iframe
331
  st.markdown('<h1 class="main-header">🍹 Video Background Replacer</h1>', unsafe_allow_html=True)
332
- st.markdown('<p style="text-align: center; font-size: 1.1rem; color: #666; margin-bottom: 2rem;">Replace your video background with AI!</p>', unsafe_allow_html=True)
333
 
334
  # Initialize the background replacer with loading feedback
335
  if 'replacer' not in st.session_state:
336
- with st.spinner('πŸ”„ Loading AI Processing Interface...'):
337
  st.session_state.replacer = BackgroundReplacer()
338
 
339
  # Show initialization status
340
  if hasattr(st.session_state.replacer, 'use_matanyone') and st.session_state.replacer.use_matanyone:
341
- st.success('πŸš€ MatAnyone AI Ready - Ultimate Quality!')
342
  elif hasattr(st.session_state.replacer, 'use_mediapipe') and st.session_state.replacer.use_mediapipe:
343
- st.success('🎯 MediaPipe AI Ready - Good Quality!')
344
  else:
345
- st.info('πŸ“± Basic Processing Ready')
 
 
 
 
 
 
 
 
 
 
346
 
347
  # Compact upload section
348
  col1, col2 = st.columns(2)
@@ -374,10 +444,10 @@ def main():
374
  # Processing section - SIMPLIFIED to avoid DOM manipulation issues
375
  if uploaded_video and uploaded_background:
376
  st.markdown("---")
377
- st.markdown("### πŸš€ Ready to Process!")
378
 
379
  # Process button (clean and stable)
380
- if st.button("🍹 PROCESS VIDEO", key="process_button", use_container_width=True):
381
  # Save uploaded files
382
  with tempfile.NamedTemporaryFile(delete=False, suffix='.mp4') as tmp_video:
383
  tmp_video.write(uploaded_video.read())
@@ -390,30 +460,37 @@ def main():
390
 
391
  with result_container:
392
  st.markdown('<div class="processing-box">', unsafe_allow_html=True)
393
- st.markdown("### πŸ”„ Processing Your Video...")
394
 
395
  # Progress tracking
396
  progress_bar = st.progress(0)
397
  status_text = st.empty()
 
398
 
399
- def update_progress(progress, frame_count, total_frames):
400
  progress_bar.progress(progress)
401
  status_text.text(f"Processing frame {frame_count}/{total_frames} ({progress*100:.1f}%)")
 
 
 
 
 
402
 
403
  try:
404
  # Process the video
405
- st.info("πŸš€ Starting video processing...")
406
  output_path = st.session_state.replacer.process_video(
407
  video_path, background_image, update_progress
408
  )
409
 
410
  # Complete progress
411
  progress_bar.progress(1.0)
412
- status_text.text("βœ… Processing complete!")
 
413
  st.markdown('</div>', unsafe_allow_html=True)
414
 
415
  # Success message
416
- st.markdown('<div class="success-box">πŸŽ‰ Video Successfully Processed! πŸŽ‰</div>', unsafe_allow_html=True)
417
 
418
  # Display result immediately in same container
419
  if os.path.exists(output_path) and os.path.getsize(output_path) > 0:
@@ -429,13 +506,13 @@ def update_progress(progress, frame_count, total_frames):
429
  st.download_button(
430
  label="⬇️ Download Processed Video",
431
  data=video_data,
432
- file_name=f"background_replaced_{int(time.time())}.mp4",
433
  mime="video/mp4",
434
  use_container_width=True,
435
  key=f"download_button_{int(time.time())}"
436
  )
437
 
438
- st.success("βœ… Video ready for download!")
439
 
440
  # Cleanup temp files
441
  try:
@@ -448,18 +525,18 @@ def update_progress(progress, frame_count, total_frames):
448
  st.error("❌ Output video file is empty or corrupted")
449
 
450
  except Exception as e:
451
- status_text.text("❌ Processing failed")
452
  st.error(f"❌ Processing failed: {str(e)}")
453
  st.info("πŸ’‘ Try with a shorter video or different background image")
454
 
455
  else:
456
- st.info("πŸ‘† Upload both a video and background image to start processing!")
457
 
458
  # Compact footer
459
  st.markdown("---")
460
  st.markdown("""
461
  <div style="text-align: center; color: #666; padding: 10px;">
462
- <p><small>🍹 Powered by MatAnyone and MediaPipe | Optimized for MyAvatar | API Fixed</small></p>
463
  </div>
464
  """, unsafe_allow_html=True)
465
 
 
1
  """
2
+ 🍹 Video Background Replacer - IFRAME OPTIMIZED VERSION WITH 2-STAGE PIPELINE
3
  Optimized for embedding in MyAvatar Railway app with Claude's recommendations
4
  FIXED: Corrected MatAnyone API usage (processor.step() instead of processor.infer())
5
+ RESTORED: 2-Stage Pipeline - MatAnyone β†’ Green Screen β†’ Custom Background
6
  """
7
 
8
  import streamlit as st
 
103
  margin: 20px 0;
104
  }
105
 
106
+ .stage-indicator {
107
+ background: linear-gradient(45deg, #FF6B6B, #4ECDC4);
108
+ color: white;
109
+ padding: 10px;
110
+ border-radius: 8px;
111
+ margin: 10px 0;
112
+ text-align: center;
113
+ font-weight: bold;
114
+ }
115
+
116
  /* Iframe-specific optimizations */
117
  .block-container {
118
  padding-top: 1rem;
 
251
  print(f"MatAnyone processing failed: {e}, using fallback")
252
  return self.create_simple_mask(frame).astype(np.float32) / 255.0
253
 
254
+ def stage1_create_green_screen(self, frame, is_first_frame=False, mask=None):
255
+ """STAGE 1: Create green screen video using AI segmentation"""
256
  if self.use_matanyone:
257
+ # Use MatAnyone for person segmentation
258
  alpha_mask = self.process_frame_matanyone(frame, is_first_frame, mask)
259
  elif self.use_mediapipe:
260
  try:
 
278
  # Simple fallback method
279
  alpha_mask = self.create_simple_mask(frame).astype(np.float32) / 255.0
280
 
281
+ # Create pure green screen background
282
+ green_screen = np.full_like(frame, [0, 255, 0], dtype=np.uint8) # Pure green (BGR)
283
+
284
+ # Composite person on green screen
285
+ # alpha_mask values close to 1 = keep original (person)
286
+ # alpha_mask values close to 0 = use green screen (background)
287
+ green_screen_frame = frame * alpha_mask + green_screen * (1 - alpha_mask)
288
+
289
+ return green_screen_frame.astype(np.uint8)
290
+
291
+ def stage2_chroma_key_replace(self, green_screen_frame, background_image):
292
+ """STAGE 2: Replace green screen with custom background using chroma key"""
293
+ try:
294
+ # Convert to HSV for better green screen detection
295
+ hsv = cv2.cvtColor(green_screen_frame, cv2.COLOR_BGR2HSV)
296
+
297
+ # Define green screen range (optimized for pure green)
298
+ lower_green = np.array([35, 40, 40]) # Lower bound for green
299
+ upper_green = np.array([85, 255, 255]) # Upper bound for green
300
+
301
+ # Create mask for green screen areas
302
+ green_mask = cv2.inRange(hsv, lower_green, upper_green)
303
+
304
+ # Smooth the mask to avoid harsh edges
305
+ kernel = np.ones((3,3), np.uint8)
306
+ green_mask = cv2.morphologyEx(green_mask, cv2.MORPH_CLOSE, kernel)
307
+ green_mask = cv2.morphologyEx(green_mask, cv2.MORPH_OPEN, kernel)
308
+
309
+ # Apply Gaussian blur for smoother edges
310
+ green_mask = cv2.GaussianBlur(green_mask, (5, 5), 0)
311
+
312
+ # Convert mask to 3-channel and normalize
313
+ green_mask_3ch = cv2.cvtColor(green_mask, cv2.COLOR_GRAY2BGR).astype(np.float32) / 255.0
314
+
315
+ # Resize background to match frame
316
+ bg_resized = cv2.resize(background_image, (green_screen_frame.shape[1], green_screen_frame.shape[0]))
317
+
318
+ # Replace green areas with background
319
+ # green_mask_3ch = 1 where green (use background), 0 where not green (keep original)
320
+ result = green_screen_frame * (1 - green_mask_3ch) + bg_resized * green_mask_3ch
321
+
322
+ return result.astype(np.uint8)
323
+
324
+ except Exception as e:
325
+ print(f"Stage 2 chroma key failed: {e}")
326
+ # Fallback: return original frame
327
+ return green_screen_frame
328
+
329
+ def process_frame_2stage(self, frame, background_image, is_first_frame=False, mask=None):
330
+ """Process a single frame with 2-stage background replacement"""
331
+ # STAGE 1: Create green screen
332
+ green_screen_frame = self.stage1_create_green_screen(frame, is_first_frame, mask)
333
 
334
+ # STAGE 2: Replace green screen with custom background
335
+ final_frame = self.stage2_chroma_key_replace(green_screen_frame, background_image)
 
 
336
 
337
+ return final_frame
338
 
339
  def process_video(self, video_path, background_image, progress_callback=None):
340
+ """Process entire video with 2-stage background replacement"""
341
  # Convert PIL background to OpenCV format
342
  if isinstance(background_image, Image.Image):
343
  background_image = cv2.cvtColor(np.array(background_image), cv2.COLOR_RGB2BGR)
 
368
  is_first_frame = (frame_count == 0)
369
  mask = None # Could add automatic mask detection here if needed
370
 
371
+ # Process frame with 2-stage pipeline
372
+ processed_frame = self.process_frame_2stage(frame, background_image, is_first_frame, mask)
373
  out.write(processed_frame)
374
 
375
  frame_count += 1
 
377
  # Update progress
378
  if progress_callback:
379
  progress = frame_count / total_frames
380
+ stage = "stage1" if progress < 0.5 else "stage2"
381
+ progress_callback(stage, progress, frame_count, total_frames)
382
 
383
  finally:
384
  cap.release()
 
389
  def main():
390
  # Compact header for iframe
391
  st.markdown('<h1 class="main-header">🍹 Video Background Replacer</h1>', unsafe_allow_html=True)
392
+ st.markdown('<p style="text-align: center; font-size: 1.1rem; color: #666; margin-bottom: 2rem;">2-Stage AI Processing: Person Segmentation β†’ Green Screen β†’ Custom Background</p>', unsafe_allow_html=True)
393
 
394
  # Initialize the background replacer with loading feedback
395
  if 'replacer' not in st.session_state:
396
+ with st.spinner('πŸ”„ Loading 2-Stage AI Processing Interface...'):
397
  st.session_state.replacer = BackgroundReplacer()
398
 
399
  # Show initialization status
400
  if hasattr(st.session_state.replacer, 'use_matanyone') and st.session_state.replacer.use_matanyone:
401
+ st.success('πŸš€ MatAnyone AI Ready - 2-Stage Ultimate Quality!')
402
  elif hasattr(st.session_state.replacer, 'use_mediapipe') and st.session_state.replacer.use_mediapipe:
403
+ st.success('🎯 MediaPipe AI Ready - 2-Stage Good Quality!')
404
  else:
405
+ st.info('πŸ“± Basic 2-Stage Processing Ready')
406
+
407
+ # Show 2-stage process explanation
408
+ st.markdown("""
409
+ <div class="processing-box">
410
+ <h4>πŸ”„ 2-Stage Processing Pipeline:</h4>
411
+ <p><strong>Stage 1:</strong> AI extracts person from video β†’ Creates clean green screen video</p>
412
+ <p><strong>Stage 2:</strong> Chroma key replacement β†’ Green screen becomes your custom background</p>
413
+ <p><em>This approach ensures the highest quality and most reliable results!</em></p>
414
+ </div>
415
+ """, unsafe_allow_html=True)
416
 
417
  # Compact upload section
418
  col1, col2 = st.columns(2)
 
444
  # Processing section - SIMPLIFIED to avoid DOM manipulation issues
445
  if uploaded_video and uploaded_background:
446
  st.markdown("---")
447
+ st.markdown("### πŸš€ Ready for 2-Stage Processing!")
448
 
449
  # Process button (clean and stable)
450
+ if st.button("🍹 START 2-STAGE PROCESSING", key="process_button", use_container_width=True):
451
  # Save uploaded files
452
  with tempfile.NamedTemporaryFile(delete=False, suffix='.mp4') as tmp_video:
453
  tmp_video.write(uploaded_video.read())
 
460
 
461
  with result_container:
462
  st.markdown('<div class="processing-box">', unsafe_allow_html=True)
463
+ st.markdown("### πŸ”„ 2-Stage Processing Your Video...")
464
 
465
  # Progress tracking
466
  progress_bar = st.progress(0)
467
  status_text = st.empty()
468
+ stage_indicator = st.empty()
469
 
470
+ def update_progress(stage, progress, frame_count, total_frames):
471
  progress_bar.progress(progress)
472
  status_text.text(f"Processing frame {frame_count}/{total_frames} ({progress*100:.1f}%)")
473
+
474
+ if stage == "stage1":
475
+ stage_indicator.markdown('<div class="stage-indicator">🎬 STAGE 1: Creating Green Screen Video</div>', unsafe_allow_html=True)
476
+ elif stage == "stage2":
477
+ stage_indicator.markdown('<div class="stage-indicator">πŸ–ΌοΈ STAGE 2: Applying Custom Background</div>', unsafe_allow_html=True)
478
 
479
  try:
480
  # Process the video
481
+ st.info("πŸš€ Starting 2-stage video processing...")
482
  output_path = st.session_state.replacer.process_video(
483
  video_path, background_image, update_progress
484
  )
485
 
486
  # Complete progress
487
  progress_bar.progress(1.0)
488
+ status_text.text("βœ… 2-Stage Processing complete!")
489
+ stage_indicator.markdown('<div class="stage-indicator">πŸŽ‰ BOTH STAGES COMPLETED!</div>', unsafe_allow_html=True)
490
  st.markdown('</div>', unsafe_allow_html=True)
491
 
492
  # Success message
493
+ st.markdown('<div class="success-box">πŸŽ‰ Video Successfully Processed with 2-Stage Pipeline! πŸŽ‰</div>', unsafe_allow_html=True)
494
 
495
  # Display result immediately in same container
496
  if os.path.exists(output_path) and os.path.getsize(output_path) > 0:
 
506
  st.download_button(
507
  label="⬇️ Download Processed Video",
508
  data=video_data,
509
+ file_name=f"2stage_background_replaced_{int(time.time())}.mp4",
510
  mime="video/mp4",
511
  use_container_width=True,
512
  key=f"download_button_{int(time.time())}"
513
  )
514
 
515
+ st.success("βœ… 2-Stage processed video ready for download!")
516
 
517
  # Cleanup temp files
518
  try:
 
525
  st.error("❌ Output video file is empty or corrupted")
526
 
527
  except Exception as e:
528
+ status_text.text("❌ 2-Stage processing failed")
529
  st.error(f"❌ Processing failed: {str(e)}")
530
  st.info("πŸ’‘ Try with a shorter video or different background image")
531
 
532
  else:
533
+ st.info("πŸ‘† Upload both a video and background image to start 2-stage processing!")
534
 
535
  # Compact footer
536
  st.markdown("---")
537
  st.markdown("""
538
  <div style="text-align: center; color: #666; padding: 10px;">
539
+ <p><small>🍹 2-Stage Pipeline: MatAnyone β†’ Green Screen β†’ Custom Background | Optimized for MyAvatar</small></p>
540
  </div>
541
  """, unsafe_allow_html=True)
542