John2J commited on
Commit
c83b94d
·
verified ·
1 Parent(s): 6d07f10

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +67 -36
app.py CHANGED
@@ -202,6 +202,13 @@ def load_uploaded_mask_video(video_path, mask_video_path, n_frames, video_state)
202
  preview = np.uint8(np.clip(painted * 255, 0, 255))
203
  return video_state, Image.fromarray(preview)
204
 
 
 
 
 
 
 
 
205
  def preprocess_for_removal(images, masks):
206
  out_images = []
207
  out_masks = []
@@ -223,45 +230,69 @@ def preprocess_for_removal(images, masks):
223
  arr_masks = np.stack(out_masks)
224
  return torch.from_numpy(arr_images).half(), torch.from_numpy(arr_masks).half()
225
 
226
- @spaces.GPU(duration=50)
 
 
227
  def inference_and_return_video(dilation_iterations, num_inference_steps, video_state):
228
  if video_state["origin_images"] is None or video_state["masks"] is None:
229
  return None
230
- images = video_state["origin_images"]
231
  masks = video_state["masks"]
 
232
 
233
- images = np.array(images)
234
- masks = np.array(masks)
235
- img_tensor, mask_tensor = preprocess_for_removal(images, masks)
236
- img_tensor=img_tensor.to("cuda")
237
- mask_tensor = mask_tensor[:,:,:,:1].to("cuda")
238
-
239
- if mask_tensor.shape[1] < mask_tensor.shape[2]:
240
- height = 480
241
- width = 832
242
- else:
243
- height = 832
244
- width = 480
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
245
 
246
  pipe.to("cuda")
247
  with torch.no_grad():
248
  out = pipe(
249
- images=img_tensor,
250
- masks=mask_tensor,
251
- num_frames=mask_tensor.shape[0],
252
- height=height,
253
- width=width,
254
- num_inference_steps=int(num_inference_steps),
255
- generator=torch.Generator(device=device).manual_seed(random_seed),
256
- iterations=int(dilation_iterations)
257
  ).frames[0]
258
 
259
- out = np.uint8(out * 255)
260
- output_frames = [img for img in out]
 
 
261
 
262
  video_file = f"/tmp/{time.time()}-{random.random()}-removed_output.mp4"
263
- clip = ImageSequenceClip(output_frames, fps=15)
264
- clip.write_videofile(video_file, codec='libx264', audio=False, verbose=False, logger=None)
265
  return video_file
266
 
267
  @spaces.GPU(duration=40)
@@ -436,10 +467,19 @@ with gr.Blocks() as demo:
436
  track_btn = gr.Button("Tracking")
437
  video_output = gr.Video(label="Tracking Result", elem_id="my-video")
438
 
 
 
 
 
 
 
 
 
 
439
  with gr.Column(elem_id="my-btn"):
440
  dilation_slider = gr.Slider(minimum=1, maximum=20, value=6, step=1, label="Mask Dilation")
441
  inference_steps_slider = gr.Slider(minimum=1, maximum=100, value=6, step=1, label="Num Inference Steps")
442
-
443
  remove_btn = gr.Button("Remove", elem_id="my-btn")
444
  remove_video = gr.Video(label="Remove Results", elem_id="my-video")
445
  remove_btn.click(
@@ -447,15 +487,6 @@ with gr.Blocks() as demo:
447
  inputs=[dilation_slider, inference_steps_slider, video_state],
448
  outputs=remove_video
449
  )
450
-
451
- mask_video_input = gr.Video(label="(Optional) Upload Mask Video — white = object to remove")
452
- use_mask_btn = gr.Button("Use Uploaded Mask (skips clicking & tracking)", elem_id="my-btn")
453
-
454
- use_mask_btn.click(
455
- load_uploaded_mask_video,
456
- inputs=[video_input, mask_video_input, n_frames_slider, video_state],
457
- outputs=[video_state, image_output],
458
- )
459
 
460
  get_info_btn.click(get_video_info, inputs=[video_input, video_state], \
461
  outputs=image_output)
 
202
  preview = np.uint8(np.clip(painted * 255, 0, 255))
203
  return video_state, Image.fromarray(preview)
204
 
205
+ def get_source_info(video_state):
206
+ vr = VideoReader(video_state["video_path"], ctx=cpu(0))
207
+ fps = float(vr.get_avg_fps())
208
+ h, w = vr[0].shape[:2]
209
+ del vr
210
+ return (fps if fps and fps > 0 else 15.0), (w, h)
211
+
212
  def preprocess_for_removal(images, masks):
213
  out_images = []
214
  out_masks = []
 
230
  arr_masks = np.stack(out_masks)
231
  return torch.from_numpy(arr_images).half(), torch.from_numpy(arr_masks).half()
232
 
233
+ MAX_PROC_SIDE = None # None = native resolution; set e.g. 1280 if you hit OOM
234
+
235
+ @spaces.GPU(duration=300) # 1080p is heavy — give it time
236
  def inference_and_return_video(dilation_iterations, num_inference_steps, video_state):
237
  if video_state["origin_images"] is None or video_state["masks"] is None:
238
  return None
239
+
240
  masks = video_state["masks"]
241
+ fps, (orig_w, orig_h) = get_source_info(video_state) # helper from before
242
 
243
+ # --- load full-res frames straight from the source video ---
244
+ vr = VideoReader(video_state["video_path"], ctx=cpu(0))
245
+ images = [vr[i].asnumpy() for i in range(min(len(masks), len(vr)))]
246
+ del vr
247
+ n_frames = len(images)
248
+
249
+ # --- processing size: native (optionally capped), rounded UP to a multiple of 16 ---
250
+ proc_w, proc_h = orig_w, orig_h
251
+ if MAX_PROC_SIDE and max(proc_w, proc_h) > MAX_PROC_SIDE:
252
+ s = MAX_PROC_SIDE / max(proc_w, proc_h)
253
+ proc_w, proc_h = round(proc_w * s), round(proc_h * s)
254
+ W16 = (proc_w + 15) // 16 * 16 # 1920 -> 1920
255
+ H16 = (proc_h + 15) // 16 * 16 # 1080 -> 1088
256
+ pad_w, pad_h = W16 - proc_w, H16 - proc_h
257
+
258
+ proc_images, proc_masks = [], []
259
+ for img, msk in zip(images, masks):
260
+ img = cv2.resize(img, (proc_w, proc_h), interpolation=cv2.INTER_LINEAR)
261
+ msk = cv2.resize(msk, (proc_w, proc_h), interpolation=cv2.INTER_NEAREST)
262
+ img = img.astype(np.float32) / 127.5 - 1.0 # [-1, 1]
263
+ msk = (msk > 0.5).astype(np.float32)
264
+ if msk.ndim == 2:
265
+ msk = msk[..., None]
266
+ if pad_h or pad_w: # reflect-pad image, zero-pad mask (background)
267
+ img = cv2.copyMakeBorder(img, 0, pad_h, 0, pad_w, cv2.BORDER_REFLECT)
268
+ msk = cv2.copyMakeBorder(msk, 0, pad_h, 0, pad_w, cv2.BORDER_CONSTANT, value=0)
269
+ proc_images.append(img)
270
+ proc_masks.append(msk)
271
+
272
+ img_tensor = torch.from_numpy(np.stack(proc_images)).half().to("cuda")
273
+ mask_tensor = torch.from_numpy(np.stack(proc_masks)).half()[..., :1].to("cuda")
274
 
275
  pipe.to("cuda")
276
  with torch.no_grad():
277
  out = pipe(
278
+ images=img_tensor,
279
+ masks=mask_tensor,
280
+ num_frames=n_frames,
281
+ height=H16,
282
+ width=W16,
283
+ num_inference_steps=int(num_inference_steps),
284
+ generator=torch.Generator(device=device).manual_seed(random_seed),
285
+ iterations=int(dilation_iterations),
286
  ).frames[0]
287
 
288
+ out = np.uint8(out * 255)
289
+ out = out[:, :proc_h, :proc_w] # strip the padding back off
290
+ if (proc_w, proc_h) != (orig_w, orig_h): # only if MAX_PROC_SIDE kicked in
291
+ out = [cv2.resize(f, (orig_w, orig_h), interpolation=cv2.INTER_CUBIC) for f in out]
292
 
293
  video_file = f"/tmp/{time.time()}-{random.random()}-removed_output.mp4"
294
+ clip = ImageSequenceClip(list(out), fps=fps)
295
+ clip.write_videofile(video_file, codec="libx264", audio=False, verbose=False, logger=None)
296
  return video_file
297
 
298
  @spaces.GPU(duration=40)
 
467
  track_btn = gr.Button("Tracking")
468
  video_output = gr.Video(label="Tracking Result", elem_id="my-video")
469
 
470
+ mask_video_input = gr.Video(label="(Optional) Upload Mask Video — white = object to remove")
471
+ use_mask_btn = gr.Button("Use Uploaded Mask (skips clicking & tracking)", elem_id="my-btn")
472
+
473
+ use_mask_btn.click(
474
+ load_uploaded_mask_video,
475
+ inputs=[video_input, mask_video_input, n_frames_slider, video_state],
476
+ outputs=[video_state, image_output],
477
+ )
478
+
479
  with gr.Column(elem_id="my-btn"):
480
  dilation_slider = gr.Slider(minimum=1, maximum=20, value=6, step=1, label="Mask Dilation")
481
  inference_steps_slider = gr.Slider(minimum=1, maximum=100, value=6, step=1, label="Num Inference Steps")
482
+
483
  remove_btn = gr.Button("Remove", elem_id="my-btn")
484
  remove_video = gr.Video(label="Remove Results", elem_id="my-video")
485
  remove_btn.click(
 
487
  inputs=[dilation_slider, inference_steps_slider, video_state],
488
  outputs=remove_video
489
  )
 
 
 
 
 
 
 
 
 
490
 
491
  get_info_btn.click(get_video_info, inputs=[video_input, video_state], \
492
  outputs=image_output)