RehamAAhmed commited on
Commit
2ba714b
·
verified ·
1 Parent(s): f2b6df7

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +48 -11
app.py CHANGED
@@ -179,6 +179,22 @@ def get_sam3_model_and_processor(model_id="facebook/sam3", hf_token=None):
179
  "and provided a valid access token in the text box."
180
  )
181
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
182
  def analyze_video_sam3(video_path, text_prompts, hf_token, max_duration=8.0, progress=gr.Progress()):
183
  """
184
  Core callback function:
@@ -215,14 +231,14 @@ def analyze_video_sam3(video_path, text_prompts, hf_token, max_duration=8.0, pro
215
 
216
  orig_fps = orig_fps if orig_fps and orig_fps > 0 else 15.0
217
 
218
- # Subsample FPS to run much faster on CPU (target around 5 FPS)
219
- target_fps = 5.0
220
  frame_skip = max(1, int(orig_fps / target_fps))
221
  fps = orig_fps / frame_skip
222
  max_frames = int(max_duration * fps)
223
 
224
  # Resize resolution to speed up CPU inference dramatically
225
- max_dim = 360
226
  scale = 1.0
227
  if max(height, width) > max_dim:
228
  scale = max_dim / max(height, width)
@@ -246,14 +262,33 @@ def analyze_video_sam3(video_path, text_prompts, hf_token, max_duration=8.0, pro
246
  if not frames:
247
  return None, "# ⚠️ Error\nNo frames could be loaded from the video."
248
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
249
  progress(0.15, desc="Initializing SAM 3 video session...")
250
 
251
  device = "cuda" if torch.cuda.is_available() else "cpu"
252
  dtype = torch.float32 if device == "cpu" else torch.bfloat16
253
 
254
- # Initialize video session
255
  session = processor.init_video_session(
256
- video=frames,
257
  inference_device=device,
258
  inference_state_device=device,
259
  processing_device=device,
@@ -270,11 +305,11 @@ def analyze_video_sam3(video_path, text_prompts, hf_token, max_duration=8.0, pro
270
  obj_to_prompt = {}
271
  track_db = defaultdict(set)
272
 
273
- progress(0.20, desc="Running SAM 3 propagation across frames...")
274
 
275
  session = to_device_recursive(session, device)
276
 
277
- frame_count = len(frames)
278
  with torch.no_grad():
279
  for model_outputs in model.propagate_in_video_iterator(
280
  inference_session=session,
@@ -282,7 +317,7 @@ def analyze_video_sam3(video_path, text_prompts, hf_token, max_duration=8.0, pro
282
  max_frame_num_to_track=frame_count,
283
  ):
284
  processed_outputs = processor.postprocess_outputs(session, model_outputs)
285
- frame_idx = model_outputs.frame_idx
286
 
287
  object_ids = processed_outputs["object_ids"]
288
  masks = processed_outputs["masks"]
@@ -305,9 +340,9 @@ def analyze_video_sam3(video_path, text_prompts, hf_token, max_duration=8.0, pro
305
  if mask_2d.ndim == 3:
306
  mask_2d = mask_2d.squeeze()
307
  mask_2d = (mask_2d > 0.0).astype(np.uint8)
308
- masks_by_frame[frame_idx][current_obj_id] = mask_2d
309
 
310
- progress(0.20 + 0.60 * ((frame_idx + 1) / frame_count), desc=f"Propagating: Frame {frame_idx + 1}/{frame_count}...")
311
 
312
  session = to_device_recursive(session, "cpu")
313
 
@@ -322,7 +357,9 @@ def analyze_video_sam3(video_path, text_prompts, hf_token, max_duration=8.0, pro
322
  font = ImageFont.load_default()
323
 
324
  for frame_idx, frame in enumerate(frames):
325
- frame_masks = masks_by_frame.get(frame_idx, {})
 
 
326
  # Overlay masks on frame
327
  annotated_img = overlay_masks_on_frame(frame, frame_masks, color_by_obj, alpha=0.55)
328
 
 
179
  "and provided a valid access token in the text box."
180
  )
181
 
182
+ def is_different_enough(img1, img2, threshold=0.92):
183
+ """Detects if two images are visually different enough based on HSV histogram correlation."""
184
+ # Convert PIL Images to HSV numpy arrays
185
+ arr1 = cv2.cvtColor(np.array(img1), cv2.COLOR_RGB2HSV)
186
+ arr2 = cv2.cvtColor(np.array(img2), cv2.COLOR_RGB2HSV)
187
+
188
+ # Calculate histogram
189
+ hist1 = cv2.calcHist([arr1], [0, 1], None, [30, 32], [0, 180, 0, 256])
190
+ hist2 = cv2.calcHist([arr2], [0, 1], None, [30, 32], [0, 180, 0, 256])
191
+
192
+ cv2.normalize(hist1, hist1, 0, 1, cv2.NORM_MINMAX)
193
+ cv2.normalize(hist2, hist2, 0, 1, cv2.NORM_MINMAX)
194
+
195
+ corr = cv2.compareHist(hist1, hist2, cv2.HISTCMP_CORREL)
196
+ return corr < threshold
197
+
198
  def analyze_video_sam3(video_path, text_prompts, hf_token, max_duration=8.0, progress=gr.Progress()):
199
  """
200
  Core callback function:
 
231
 
232
  orig_fps = orig_fps if orig_fps and orig_fps > 0 else 15.0
233
 
234
+ # Target 8 FPS for smooth output rendering
235
+ target_fps = 8.0
236
  frame_skip = max(1, int(orig_fps / target_fps))
237
  fps = orig_fps / frame_skip
238
  max_frames = int(max_duration * fps)
239
 
240
  # Resize resolution to speed up CPU inference dramatically
241
+ max_dim = 320
242
  scale = 1.0
243
  if max(height, width) > max_dim:
244
  scale = max_dim / max(height, width)
 
262
  if not frames:
263
  return None, "# ⚠️ Error\nNo frames could be loaded from the video."
264
 
265
+ # Extract motion-based keyframes to minimize model run times
266
+ progress(0.12, desc="Extracting keyframes...")
267
+ keyframes = [frames[0]]
268
+ keyframe_indices = [0]
269
+ max_gap = 6 # force a keyframe at least every 6 frames to keep temporal coherence
270
+ min_gap = 2 # wait at least 2 frames between keyframes to save time
271
+
272
+ for i in range(1, len(frames)):
273
+ gap = i - keyframe_indices[-1]
274
+ if gap >= max_gap:
275
+ keyframes.append(frames[i])
276
+ keyframe_indices.append(i)
277
+ elif gap >= min_gap:
278
+ if is_different_enough(frames[i], keyframes[-1], threshold=0.94):
279
+ keyframes.append(frames[i])
280
+ keyframe_indices.append(i)
281
+
282
+ print(f"Total frames: {len(frames)}, Extracted keyframes: {len(keyframes)}")
283
+
284
  progress(0.15, desc="Initializing SAM 3 video session...")
285
 
286
  device = "cuda" if torch.cuda.is_available() else "cpu"
287
  dtype = torch.float32 if device == "cpu" else torch.bfloat16
288
 
289
+ # Initialize video session with keyframes only
290
  session = processor.init_video_session(
291
+ video=keyframes,
292
  inference_device=device,
293
  inference_state_device=device,
294
  processing_device=device,
 
305
  obj_to_prompt = {}
306
  track_db = defaultdict(set)
307
 
308
+ progress(0.20, desc="Running SAM 3 propagation across keyframes...")
309
 
310
  session = to_device_recursive(session, device)
311
 
312
+ frame_count = len(keyframes)
313
  with torch.no_grad():
314
  for model_outputs in model.propagate_in_video_iterator(
315
  inference_session=session,
 
317
  max_frame_num_to_track=frame_count,
318
  ):
319
  processed_outputs = processor.postprocess_outputs(session, model_outputs)
320
+ k_frame_idx = model_outputs.frame_idx # index in keyframes list
321
 
322
  object_ids = processed_outputs["object_ids"]
323
  masks = processed_outputs["masks"]
 
340
  if mask_2d.ndim == 3:
341
  mask_2d = mask_2d.squeeze()
342
  mask_2d = (mask_2d > 0.0).astype(np.uint8)
343
+ masks_by_frame[k_frame_idx][current_obj_id] = mask_2d
344
 
345
+ progress(0.20 + 0.60 * ((k_frame_idx + 1) / frame_count), desc=f"Propagating: Keyframe {k_frame_idx + 1}/{frame_count}...")
346
 
347
  session = to_device_recursive(session, "cpu")
348
 
 
357
  font = ImageFont.load_default()
358
 
359
  for frame_idx, frame in enumerate(frames):
360
+ # Find the closest keyframe index
361
+ closest_k = min(range(len(keyframe_indices)), key=lambda k: abs(keyframe_indices[k] - frame_idx))
362
+ frame_masks = masks_by_frame.get(closest_k, {})
363
  # Overlay masks on frame
364
  annotated_img = overlay_masks_on_frame(frame, frame_masks, color_by_obj, alpha=0.55)
365