Spaces:
Running on Zero
Running on Zero
Update app.py
Browse files
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 |
-
|
|
|
|
|
|
|
| 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 |
-
|
| 231 |
masks = video_state["masks"]
|
|
|
|
| 232 |
|
| 233 |
-
|
| 234 |
-
|
| 235 |
-
|
| 236 |
-
|
| 237 |
-
|
| 238 |
-
|
| 239 |
-
|
| 240 |
-
|
| 241 |
-
|
| 242 |
-
|
| 243 |
-
|
| 244 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 245 |
|
| 246 |
pipe.to("cuda")
|
| 247 |
with torch.no_grad():
|
| 248 |
out = pipe(
|
| 249 |
-
|
| 250 |
-
|
| 251 |
-
|
| 252 |
-
|
| 253 |
-
|
| 254 |
-
|
| 255 |
-
|
| 256 |
-
|
| 257 |
).frames[0]
|
| 258 |
|
| 259 |
-
|
| 260 |
-
|
|
|
|
|
|
|
| 261 |
|
| 262 |
video_file = f"/tmp/{time.time()}-{random.random()}-removed_output.mp4"
|
| 263 |
-
clip = ImageSequenceClip(
|
| 264 |
-
clip.write_videofile(video_file, codec=
|
| 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)
|