MogensR commited on
Commit
d06a12c
Β·
1 Parent(s): 8f536c1

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +119 -88
app.py CHANGED
@@ -1,6 +1,7 @@
1
  """
2
  🍹 Video Background Replacer - IFRAME OPTIMIZED VERSION
3
  Optimized for embedding in MyAvatar Railway app with Claude's recommendations
 
4
  """
5
 
6
  import streamlit as st
@@ -124,43 +125,50 @@ def __init__(self):
124
  """Initialize the background replacer with MatAnyone or MediaPipe"""
125
  self.use_matanyone = False
126
  self.use_mediapipe = False
 
 
 
127
 
128
  print("πŸ”„ Starting AI model initialization...")
129
 
130
- # Try MatAnyone first (best quality)
131
  try:
132
  print("πŸš€ Attempting to load MatAnyone...")
133
- from matanyone import InferenceCore
 
 
134
 
135
  print("πŸ“¦ MatAnyone imported successfully, initializing processor...")
136
- # Initialize MatAnyone with model name (HuggingFace style)
137
- self.matanyone_processor = InferenceCore("PeiqingYang/MatAnyone")
 
 
 
 
 
 
 
 
138
  self.use_matanyone = True
139
  print("βœ… MatAnyone AI loaded successfully!")
140
 
141
  except ImportError as e:
142
  print(f"⚠️ MatAnyone not found ({str(e)}), trying MediaPipe...")
143
- # Fallback to MediaPipe
144
- try:
145
- import mediapipe as mp
146
- self.mp_selfie_segmentation = mp.solutions.selfie_segmentation
147
- self.selfie_segmentation = self.mp_selfie_segmentation.SelfieSegmentation(model_selection=1)
148
- self.use_mediapipe = True
149
- print("βœ… MediaPipe AI loaded successfully!")
150
- except ImportError:
151
- print("πŸ“± Using basic background replacement (install MatAnyone or MediaPipe for better results)")
152
-
153
  except Exception as e:
154
  print(f"⚠️ MatAnyone failed to load ({str(e)}), trying MediaPipe...")
155
- # Fallback to MediaPipe
156
- try:
157
- import mediapipe as mp
158
- self.mp_selfie_segmentation = mp.solutions.selfie_segmentation
159
- self.selfie_segmentation = self.mp_selfie_segmentation.SelfieSegmentation(model_selection=1)
160
- self.use_mediapipe = True
161
- print("βœ… MediaPipe AI loaded successfully!")
162
- except ImportError:
163
- print("πŸ“± Using basic background replacement")
 
 
 
164
 
165
  def create_simple_mask(self, frame):
166
  """Create a simple background mask using color detection"""
@@ -188,31 +196,55 @@ def create_simple_mask(self, frame):
188
 
189
  return mask
190
 
191
- def process_frame(self, frame, background_image):
192
- """Process a single frame with background replacement"""
193
- if self.use_matanyone:
194
- try:
195
- # Convert frame to PIL Image for MatAnyone
196
- frame_pil = Image.fromarray(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))
197
-
198
- # Use MatAnyone for segmentation
199
- result = self.matanyone_processor.infer(frame_pil)
200
-
201
- # Extract the mask from MatAnyone result
202
- if hasattr(result, 'alpha') and result.alpha is not None:
203
- # MatAnyone returns alpha matte
204
- mask = np.array(result.alpha)
205
- if len(mask.shape) == 2:
206
- mask = np.stack([mask] * 3, axis=-1)
207
- mask = mask.astype(np.float32) / 255.0
208
- else:
209
- # Fallback if alpha not available
210
- mask = self.create_simple_mask(frame).astype(np.float32) / 255.0
211
 
212
- except Exception as e:
213
- print(f"MatAnyone processing failed: {e}, using fallback")
214
- mask = self.create_simple_mask(frame).astype(np.float32) / 255.0
215
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
216
  elif self.use_mediapipe:
217
  try:
218
  # Convert BGR to RGB for MediaPipe
@@ -223,17 +255,17 @@ def process_frame(self, frame, background_image):
223
 
224
  if results.segmentation_mask is not None:
225
  # Convert segmentation mask to 3-channel
226
- mask = results.segmentation_mask
227
- mask = np.stack([mask] * 3, axis=-1).astype(np.float32)
228
  else:
229
- mask = self.create_simple_mask(frame).astype(np.float32) / 255.0
230
 
231
  except Exception as e:
232
  print(f"MediaPipe processing failed: {e}, using fallback")
233
- mask = self.create_simple_mask(frame).astype(np.float32) / 255.0
234
  else:
235
  # Simple fallback method
236
- mask = self.create_simple_mask(frame).astype(np.float32) / 255.0
237
 
238
  # Resize background to match frame
239
  bg_resized = cv2.resize(background_image, (frame.shape[1], frame.shape[0]))
@@ -241,7 +273,7 @@ def process_frame(self, frame, background_image):
241
  # Apply background replacement
242
  # mask values close to 1 = keep original (person)
243
  # mask values close to 0 = use background
244
- result = frame * mask + bg_resized * (1 - mask)
245
 
246
  return result.astype(np.uint8)
247
 
@@ -273,8 +305,12 @@ def process_video(self, video_path, background_image, progress_callback=None):
273
  if not ret:
274
  break
275
 
 
 
 
 
276
  # Process frame
277
- processed_frame = self.process_frame(frame, background_image)
278
  out.write(processed_frame)
279
 
280
  frame_count += 1
@@ -335,17 +371,13 @@ def main():
335
  if uploaded_background:
336
  st.image(uploaded_background, caption="Background Preview")
337
 
338
- # Processing section
339
  if uploaded_video and uploaded_background:
340
  st.markdown("---")
341
  st.markdown("### πŸš€ Ready to Process!")
342
 
343
  # Process button (clean and stable)
344
  if st.button("🍹 PROCESS VIDEO", key="process_button", use_container_width=True):
345
- # Clear any previous results first
346
- if 'video_result' in st.session_state:
347
- del st.session_state['video_result']
348
-
349
  # Save uploaded files
350
  with tempfile.NamedTemporaryFile(delete=False, suffix='.mp4') as tmp_video:
351
  tmp_video.write(uploaded_video.read())
@@ -353,10 +385,10 @@ def main():
353
 
354
  background_image = Image.open(uploaded_background)
355
 
356
- # Processing container - clean and professional
357
- processing_container = st.empty()
358
 
359
- with processing_container.container():
360
  st.markdown('<div class="processing-box">', unsafe_allow_html=True)
361
  st.markdown("### πŸ”„ Processing Your Video...")
362
 
@@ -375,16 +407,35 @@ def update_progress(progress, frame_count, total_frames):
375
  video_path, background_image, update_progress
376
  )
377
 
378
- # Clear processing animation
379
- processing_container.empty()
 
 
380
 
381
  # Success message
382
  st.markdown('<div class="success-box">πŸŽ‰ Video Successfully Processed! πŸŽ‰</div>', unsafe_allow_html=True)
383
 
384
- # Store result in session state
385
  if os.path.exists(output_path) and os.path.getsize(output_path) > 0:
386
  with open(output_path, 'rb') as video_file:
387
- st.session_state['video_result'] = video_file.read()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
388
 
389
  # Cleanup temp files
390
  try:
@@ -397,28 +448,9 @@ def update_progress(progress, frame_count, total_frames):
397
  st.error("❌ Output video file is empty or corrupted")
398
 
399
  except Exception as e:
400
- processing_container.empty()
401
  st.error(f"❌ Processing failed: {str(e)}")
402
  st.info("πŸ’‘ Try with a shorter video or different background image")
403
-
404
- # Display results if they exist (separate from processing)
405
- if 'video_result' in st.session_state:
406
- st.markdown("### 🎬 Your Processed Video:")
407
-
408
- # Show the video
409
- st.video(st.session_state['video_result'])
410
-
411
- # Download button
412
- st.download_button(
413
- label="⬇️ Download Processed Video",
414
- data=st.session_state['video_result'],
415
- file_name=f"background_replaced_{int(time.time())}.mp4",
416
- mime="video/mp4",
417
- use_container_width=True,
418
- key="download_button"
419
- )
420
-
421
- st.success("βœ… Video ready for download!")
422
 
423
  else:
424
  st.info("πŸ‘† Upload both a video and background image to start processing!")
@@ -427,10 +459,9 @@ def update_progress(progress, frame_count, total_frames):
427
  st.markdown("---")
428
  st.markdown("""
429
  <div style="text-align: center; color: #666; padding: 10px;">
430
- <p><small>🍹 Powered by MatAnyone and MediaPipe | Optimized for MyAvatar</small></p>
431
  </div>
432
  """, unsafe_allow_html=True)
433
 
434
  if __name__ == "__main__":
435
- main()
436
-
 
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
 
125
  """Initialize the background replacer with MatAnyone or MediaPipe"""
126
  self.use_matanyone = False
127
  self.use_mediapipe = False
128
+ self.matanyone_processor = None
129
+ self.matanyone_model = None
130
+ self.device = None
131
 
132
  print("πŸ”„ Starting AI model initialization...")
133
 
134
+ # Try MatAnyone first (best quality) with CORRECTED API
135
  try:
136
  print("πŸš€ Attempting to load MatAnyone...")
137
+ from matanyone.inference.inference_core import InferenceCore
138
+ from matanyone.utils.get_default_model import get_matanyone_model
139
+ from matanyone.utils.device import get_default_device
140
 
141
  print("πŸ“¦ MatAnyone imported successfully, initializing processor...")
142
+
143
+ # Get device
144
+ self.device = get_default_device()
145
+
146
+ # Load MatAnyone model (auto-downloads if needed)
147
+ self.matanyone_model = get_matanyone_model(None, self.device)
148
+
149
+ # Initialize processor with CORRECT API
150
+ self.matanyone_processor = InferenceCore(self.matanyone_model, cfg=self.matanyone_model.cfg)
151
+
152
  self.use_matanyone = True
153
  print("βœ… MatAnyone AI loaded successfully!")
154
 
155
  except ImportError as e:
156
  print(f"⚠️ MatAnyone not found ({str(e)}), trying MediaPipe...")
157
+ self._init_mediapipe()
 
 
 
 
 
 
 
 
 
158
  except Exception as e:
159
  print(f"⚠️ MatAnyone failed to load ({str(e)}), trying MediaPipe...")
160
+ self._init_mediapipe()
161
+
162
+ def _init_mediapipe(self):
163
+ """Initialize MediaPipe as fallback"""
164
+ try:
165
+ import mediapipe as mp
166
+ self.mp_selfie_segmentation = mp.solutions.selfie_segmentation
167
+ self.selfie_segmentation = self.mp_selfie_segmentation.SelfieSegmentation(model_selection=1)
168
+ self.use_mediapipe = True
169
+ print("βœ… MediaPipe AI loaded successfully!")
170
+ except ImportError:
171
+ print("πŸ“± Using basic background replacement (install MatAnyone or MediaPipe for better results)")
172
 
173
  def create_simple_mask(self, frame):
174
  """Create a simple background mask using color detection"""
 
196
 
197
  return mask
198
 
199
+ def process_frame_matanyone(self, frame, is_first_frame=False, mask=None):
200
+ """Process frame using CORRECTED MatAnyone API"""
201
+ try:
202
+ # Convert frame to tensor format expected by MatAnyone
203
+ # Frame should be in RGB format and normalized to [0,1]
204
+ if len(frame.shape) == 3 and frame.shape[2] == 3:
205
+ # Convert BGR to RGB
206
+ frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
207
+ else:
208
+ frame_rgb = frame
209
+
210
+ # Convert to tensor and normalize
211
+ frame_tensor = torch.from_numpy(frame_rgb).permute(2, 0, 1).float() / 255.0
212
+ frame_tensor = frame_tensor.to(self.device)
213
+
214
+ if is_first_frame and mask is not None:
215
+ # First frame: provide mask
216
+ mask_tensor = torch.from_numpy(mask).float().to(self.device)
217
+ if len(mask_tensor.shape) == 3:
218
+ mask_tensor = mask_tensor[:, :, 0] # Take first channel if 3-channel
219
 
220
+ objects = [1] # Object ID
 
 
221
 
222
+ # CORRECTED API: Use .step() not .infer()
223
+ output_prob = self.matanyone_processor.step(frame_tensor, mask_tensor, objects=objects)
224
+ output_prob = self.matanyone_processor.step(frame_tensor, first_frame_pred=True)
225
+ else:
226
+ # Subsequent frames: no mask needed
227
+ output_prob = self.matanyone_processor.step(frame_tensor)
228
+
229
+ # Convert output to mask
230
+ alpha_mask = self.matanyone_processor.output_prob_to_mask(output_prob)
231
+ alpha_mask = alpha_mask.cpu().numpy()
232
+
233
+ # Ensure mask is 3-channel and proper format
234
+ if len(alpha_mask.shape) == 2:
235
+ alpha_mask = np.stack([alpha_mask] * 3, axis=-1)
236
+
237
+ return alpha_mask.astype(np.float32)
238
+
239
+ except Exception as e:
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:
250
  # Convert BGR to RGB for MediaPipe
 
255
 
256
  if results.segmentation_mask is not None:
257
  # Convert segmentation mask to 3-channel
258
+ alpha_mask = results.segmentation_mask
259
+ alpha_mask = np.stack([alpha_mask] * 3, axis=-1).astype(np.float32)
260
  else:
261
+ alpha_mask = self.create_simple_mask(frame).astype(np.float32) / 255.0
262
 
263
  except Exception as e:
264
  print(f"MediaPipe processing failed: {e}, using fallback")
265
+ alpha_mask = self.create_simple_mask(frame).astype(np.float32) / 255.0
266
  else:
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]))
 
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
 
 
305
  if not ret:
306
  break
307
 
308
+ # For MatAnyone, first frame needs special handling
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
 
371
  if uploaded_background:
372
  st.image(uploaded_background, caption="Background Preview")
373
 
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())
 
385
 
386
  background_image = Image.open(uploaded_background)
387
 
388
+ # Create stable container for processing and results
389
+ result_container = st.container()
390
 
391
+ with result_container:
392
  st.markdown('<div class="processing-box">', unsafe_allow_html=True)
393
  st.markdown("### πŸ”„ Processing Your Video...")
394
 
 
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:
420
  with open(output_path, 'rb') as video_file:
421
+ video_data = video_file.read()
422
+
423
+ st.markdown("### 🎬 Your Processed Video:")
424
+
425
+ # Show the video
426
+ st.video(video_data)
427
+
428
+ # Download button
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
  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!")
 
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
 
466
  if __name__ == "__main__":
467
+ main()