memory optimization and missing mat anyone 2 py
This commit is contained in:
@@ -18,7 +18,9 @@ checkpoint/
|
||||
|
||||
## Workflow
|
||||
|
||||
[workflow/workflow_mat_anyone.json](workflow/workflow_mat_anyone.json)
|
||||
The extension now includes the `MatAnyone2Video` node, which runs the improved MatAnyoneV2 model for higher-quality and more robust video matting.
|
||||
|
||||
**Workflow**: [workflow/workflow_mat_anyone.json](workflow/workflow_mat_anyone.json)
|
||||
|
||||

|
||||
(Not a workflow-embedded image)
|
||||
@@ -31,18 +33,16 @@ Inputs:
|
||||
- `mask_frame`: The input mask's index (defaults to 0). Support first (0), last and middle frame.
|
||||
- `n_warmup`: Number of iterations to warm up the model. Defaults to 10.
|
||||
|
||||
**Memory Management Inputs**:
|
||||
|
||||
- `max_internal_size` (optional): Resizes the internal processing resolution to save memory (e.g., 360, 480). Default is -1 (full resolution).
|
||||
- `max_mem_frames` (optional): The number of key frames kept in high-resolution working memory. Default is 5.
|
||||
- `use_long_term` (optional): Limits active memory scaling by compressing older frames into long-term prototype memories, preventing OOM over time. Default is False. (Useful for long videos)
|
||||
|
||||
Your input mask won't actually be in the final matte. Instead, the warmup process generate a new input mask, which is then propagated throughout the video.
|
||||
|
||||
### MatAnyone 2 Support
|
||||
|
||||
The extension now includes the `MatAnyone2Video` node, which runs the improved MatAnyoneV2 model for higher-quality and more robust video matting.
|
||||
|
||||
**Workflow V2**: [workflow/workflow_mat_anyone_v2.json](workflow/workflow_mat_anyone_v2.json)
|
||||
|
||||

|
||||
(Not a workflow-embedded image)
|
||||
|
||||
Additional Inputs for V2:
|
||||
|
||||
- `r_erode` (optional): The radius for morphological erosion applied to the `foreground_mask` before processing (defaults to 0). Useful for refining rough masks.
|
||||
- `r_dilate` (optional): The radius for morphological dilation applied to the `foreground_mask` before processing (defaults to 0).
|
||||
|
||||
|
||||
+19
-16
@@ -66,7 +66,6 @@ def inference_matanyone(
|
||||
n_warmup=10,
|
||||
):
|
||||
mask = preprocess_mask(mask)
|
||||
vframes = vframes.to(device)
|
||||
mask = mask.to(device)
|
||||
length = vframes.shape[0]
|
||||
phas = [None] * length # initialize output list
|
||||
@@ -80,13 +79,13 @@ def inference_matanyone(
|
||||
repeated_frames = repeated_frames.to(device)
|
||||
|
||||
output_prob = warming_up(mask, processor, n_warmup, repeated_frames)
|
||||
phas[0] = processor.output_prob_to_mask(output_prob).unsqueeze(0)
|
||||
phas[0] = processor.output_prob_to_mask(output_prob).unsqueeze(0).cpu()
|
||||
|
||||
pbar = ProgressBar(length - 1)
|
||||
for ti in tqdm(range(1, length), desc="Forward Propagation"):
|
||||
image = vframes[ti]
|
||||
image = vframes[ti].to(device)
|
||||
output_prob = processor.step(image)
|
||||
phas[ti] = processor.output_prob_to_mask(output_prob).unsqueeze(0)
|
||||
phas[ti] = processor.output_prob_to_mask(output_prob).unsqueeze(0).cpu()
|
||||
pbar.update_absolute(ti - 1, length - 1)
|
||||
|
||||
elif frame_index == length - 1:
|
||||
@@ -98,15 +97,17 @@ def inference_matanyone(
|
||||
reversed_mask = mask # mask for the last frame
|
||||
|
||||
output_prob = warming_up(reversed_mask, processor, n_warmup, repeated_frames)
|
||||
phas[frame_index] = processor.output_prob_to_mask(output_prob).unsqueeze(0)
|
||||
phas[frame_index] = (
|
||||
processor.output_prob_to_mask(output_prob).unsqueeze(0).cpu()
|
||||
)
|
||||
|
||||
pbar = ProgressBar(length - 1)
|
||||
for ti in tqdm(range(1, length), desc="Backward Propagation"):
|
||||
image = reversed_vframes[ti]
|
||||
image = reversed_vframes[ti].to(device)
|
||||
output_prob = processor.step(image)
|
||||
phas[length - 1 - ti] = processor.output_prob_to_mask(
|
||||
output_prob
|
||||
).unsqueeze(0) # reverse index
|
||||
phas[length - 1 - ti] = (
|
||||
processor.output_prob_to_mask(output_prob).unsqueeze(0).cpu()
|
||||
) # reverse index
|
||||
pbar.update_absolute(ti - 1, length - 1)
|
||||
|
||||
elif 0 < frame_index < length - 1:
|
||||
@@ -117,14 +118,16 @@ def inference_matanyone(
|
||||
|
||||
# Warm up at frame_index
|
||||
output_prob = warming_up(mask, processor, n_warmup, repeated_frames)
|
||||
phas[frame_index] = processor.output_prob_to_mask(output_prob).unsqueeze(0)
|
||||
phas[frame_index] = (
|
||||
processor.output_prob_to_mask(output_prob).unsqueeze(0).cpu()
|
||||
)
|
||||
|
||||
# Forward Propagation (from frame_index + 1 to end)
|
||||
pbar_forward = ProgressBar(length - 1 - frame_index)
|
||||
for ti in tqdm(range(frame_index + 1, length), desc="Forward Propagation"):
|
||||
image = vframes[ti]
|
||||
image = vframes[ti].to(device)
|
||||
output_prob = processor.step(image)
|
||||
phas[ti] = processor.output_prob_to_mask(output_prob).unsqueeze(0)
|
||||
phas[ti] = processor.output_prob_to_mask(output_prob).unsqueeze(0).cpu()
|
||||
pbar_forward.update_absolute(
|
||||
ti - (frame_index + 1), length - 1 - frame_index
|
||||
)
|
||||
@@ -134,11 +137,11 @@ def inference_matanyone(
|
||||
reversed_vframes_backward = torch.flip(vframes[:frame_index], dims=[0])
|
||||
pbar_backward = ProgressBar(frame_index)
|
||||
for ti in tqdm(range(frame_index), desc="Backward Propagation"):
|
||||
image = reversed_vframes_backward[ti]
|
||||
image = reversed_vframes_backward[ti].to(device)
|
||||
output_prob = processor.step(image)
|
||||
phas[frame_index - 1 - ti] = processor.output_prob_to_mask(
|
||||
output_prob
|
||||
).unsqueeze(0) # reverse index
|
||||
phas[frame_index - 1 - ti] = (
|
||||
processor.output_prob_to_mask(output_prob).unsqueeze(0).cpu()
|
||||
) # reverse index
|
||||
pbar_backward.update_absolute(ti, frame_index)
|
||||
|
||||
return phas
|
||||
|
||||
+182
@@ -0,0 +1,182 @@
|
||||
"""
|
||||
MatAnyone2 inference wrapper for ComfyUI.
|
||||
"""
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
|
||||
from comfy.utils import ProgressBar
|
||||
|
||||
from .constants import ckpt_path_matanyone2
|
||||
from .src.matanyone2.inference.inference_core import InferenceCore
|
||||
from .src.matanyone2.utils.get_default_model import get_matanyone2_model
|
||||
from .src.matanyone2.utils.inference_utils import gen_dilate, gen_erosion
|
||||
|
||||
base_dir = Path(__file__).resolve().parent
|
||||
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
||||
|
||||
|
||||
def get_matanyone2_model_cached():
|
||||
"""Loads MatAnyone2 model from checkpoint (user must place matanyone2.pth in checkpoint/)."""
|
||||
ckpt_path = base_dir / ckpt_path_matanyone2
|
||||
if not ckpt_path.exists():
|
||||
raise FileNotFoundError(
|
||||
"MatAnyone2 checkpoint not found at %s. "
|
||||
"Download matanyone2.pth and place it in the checkpoint folder." % ckpt_path
|
||||
)
|
||||
return get_matanyone2_model(str(ckpt_path), device)
|
||||
|
||||
|
||||
def warming_up2(
|
||||
mask_t, processor, n_warmup, repeated_frames, pbar=None, pbar_start=0, total_len=0
|
||||
):
|
||||
output_prob = None
|
||||
objects = [1]
|
||||
for ti in range(n_warmup):
|
||||
image = repeated_frames[ti]
|
||||
if ti == 0:
|
||||
output_prob = processor.step(image, mask_t, objects=objects)
|
||||
output_prob = processor.step(image, first_frame_pred=True)
|
||||
else:
|
||||
output_prob = processor.step(image, first_frame_pred=True)
|
||||
if pbar is not None:
|
||||
pbar.update_absolute(pbar_start + ti, total_len)
|
||||
return output_prob
|
||||
|
||||
|
||||
def inference_matanyone2(
|
||||
vframes: torch.Tensor,
|
||||
mask: torch.Tensor,
|
||||
processor: InferenceCore,
|
||||
mask_frame: int = 0,
|
||||
n_warmup: int = 10,
|
||||
r_erode: int = 0,
|
||||
r_dilate: int = 0,
|
||||
):
|
||||
"""
|
||||
Runs MatAnyone2 inference on video frames.
|
||||
|
||||
Args:
|
||||
vframes: (T, C, H, W) RGB, float [0, 1]
|
||||
mask: (H, W) or (1, H, W) float [0, 1] or int
|
||||
processor: InferenceCore instance
|
||||
mask_frame: Frame index for mask
|
||||
n_warmup: Warmup iterations
|
||||
r_erode: Erosion kernel size
|
||||
r_dilate: Dilation kernel size
|
||||
|
||||
Returns:
|
||||
List of alpha tensors (1, 1, H, W) per frame
|
||||
"""
|
||||
mask = mask.squeeze().float()
|
||||
if mask.max() <= 1.0:
|
||||
mask = (mask * 255).clamp(0, 255)
|
||||
mask_np = mask.cpu().numpy().astype(np.uint8)
|
||||
|
||||
if r_dilate > 0:
|
||||
mask_np = gen_dilate(mask_np, r_dilate, r_dilate)
|
||||
if r_erode > 0:
|
||||
mask_np = gen_erosion(mask_np, r_erode, r_erode)
|
||||
|
||||
mask_t = torch.from_numpy(mask_np).float().to(device)
|
||||
length = vframes.shape[0]
|
||||
|
||||
if mask_frame >= length:
|
||||
raise ValueError(f"Expected 0 <= x < {length}, got {mask_frame}")
|
||||
|
||||
phas = [None] * length
|
||||
|
||||
if mask_frame == 0:
|
||||
total_len = n_warmup + length
|
||||
pbar = ProgressBar(total_len)
|
||||
repeated_frames = vframes[0].unsqueeze(0).repeat(n_warmup, 1, 1, 1).to(device)
|
||||
|
||||
output_prob = warming_up2(
|
||||
mask_t, processor, n_warmup, repeated_frames, pbar, 0, total_len
|
||||
)
|
||||
mask_out = processor.output_prob_to_mask(output_prob)
|
||||
phas[0] = mask_out.unsqueeze(0).unsqueeze(0)
|
||||
|
||||
for ti in range(1, length):
|
||||
image = vframes[ti].to(device)
|
||||
output_prob = processor.step(image)
|
||||
mask_out = processor.output_prob_to_mask(output_prob)
|
||||
phas[ti] = mask_out.unsqueeze(0).unsqueeze(0).cpu()
|
||||
pbar.update_absolute(n_warmup + ti, total_len)
|
||||
|
||||
elif mask_frame == length - 1:
|
||||
total_len = n_warmup + length
|
||||
pbar = ProgressBar(total_len)
|
||||
reversed_vframes = torch.flip(vframes, dims=[0])
|
||||
repeated_frames = (
|
||||
reversed_vframes[0].unsqueeze(0).repeat(n_warmup, 1, 1, 1).to(device)
|
||||
)
|
||||
|
||||
output_prob = warming_up2(
|
||||
mask_t, processor, n_warmup, repeated_frames, pbar, 0, total_len
|
||||
)
|
||||
mask_out = processor.output_prob_to_mask(output_prob)
|
||||
phas[mask_frame] = mask_out.unsqueeze(0).unsqueeze(0).cpu()
|
||||
|
||||
for ti in range(1, length):
|
||||
image = reversed_vframes[ti].to(device)
|
||||
output_prob = processor.step(image)
|
||||
mask_out = processor.output_prob_to_mask(output_prob)
|
||||
phas[length - 1 - ti] = mask_out.unsqueeze(0).unsqueeze(0).cpu()
|
||||
pbar.update_absolute(n_warmup + ti, total_len)
|
||||
|
||||
elif 0 < mask_frame < length - 1:
|
||||
# Two passes: forward then backward
|
||||
total_len = n_warmup + (length - mask_frame) + n_warmup + mask_frame
|
||||
pbar = ProgressBar(total_len)
|
||||
current_step = 0
|
||||
|
||||
# Pass 1: Forward
|
||||
repeated_frames = (
|
||||
vframes[mask_frame].unsqueeze(0).repeat(n_warmup, 1, 1, 1).to(device)
|
||||
)
|
||||
output_prob = warming_up2(
|
||||
mask_t, processor, n_warmup, repeated_frames, pbar, current_step, total_len
|
||||
)
|
||||
current_step += n_warmup
|
||||
mask_out = processor.output_prob_to_mask(output_prob)
|
||||
phas[mask_frame] = mask_out.unsqueeze(0).unsqueeze(0).cpu()
|
||||
|
||||
for ti in range(mask_frame + 1, length):
|
||||
image = vframes[ti].to(device)
|
||||
output_prob = processor.step(image)
|
||||
mask_out = processor.output_prob_to_mask(output_prob)
|
||||
phas[ti] = mask_out.unsqueeze(0).unsqueeze(0).cpu()
|
||||
pbar.update_absolute(current_step, total_len)
|
||||
current_step += 1
|
||||
|
||||
# Pass 2: Backward
|
||||
processor = type(processor)(processor.network, cfg=processor.network.cfg)
|
||||
repeated_frames_back = (
|
||||
vframes[mask_frame].unsqueeze(0).repeat(n_warmup, 1, 1, 1).to(device)
|
||||
)
|
||||
|
||||
output_prob = warming_up2(
|
||||
mask_t,
|
||||
processor,
|
||||
n_warmup,
|
||||
repeated_frames_back,
|
||||
pbar,
|
||||
current_step,
|
||||
total_len,
|
||||
)
|
||||
current_step += n_warmup
|
||||
|
||||
reversed_vframes_backward = torch.flip(vframes[:mask_frame], dims=[0])
|
||||
|
||||
for ti in range(mask_frame):
|
||||
image = reversed_vframes_backward[ti].to(device)
|
||||
output_prob = processor.step(image)
|
||||
mask_out = processor.output_prob_to_mask(output_prob)
|
||||
phas[mask_frame - 1 - ti] = mask_out.unsqueeze(0).unsqueeze(0).cpu()
|
||||
pbar.update_absolute(current_step, total_len)
|
||||
current_step += 1
|
||||
|
||||
return phas
|
||||
@@ -45,6 +45,18 @@ class MatAnyoneVideo:
|
||||
"foreground_mask": ("IMAGE",),
|
||||
"foreground_MASK": ("MASK",),
|
||||
"solid_color": ("IMAGE",),
|
||||
"max_internal_size": (
|
||||
"INT",
|
||||
{"default": -1, "min": -1, "step": 1},
|
||||
),
|
||||
"max_mem_frames": (
|
||||
"INT",
|
||||
{"default": 5, "min": 1, "step": 1},
|
||||
),
|
||||
"use_long_term": (
|
||||
"BOOLEAN",
|
||||
{"default": False},
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -67,12 +79,19 @@ class MatAnyoneVideo:
|
||||
foreground_mask: torch.Tensor | None = None,
|
||||
foreground_MASK: torch.Tensor | None = None,
|
||||
solid_color: torch.Tensor | None = None,
|
||||
max_internal_size: int = -1,
|
||||
max_mem_frames: int = 5,
|
||||
use_long_term: bool = False,
|
||||
):
|
||||
mask = get_mask(foreground_mask, foreground_MASK)
|
||||
src_video = src_video.permute(0, 3, 1, 2) # T CHW RGB
|
||||
|
||||
# load MatAnyone model
|
||||
matanyone = get_matanyone_model(f"{base_dir}/{ckpt_path}")
|
||||
matanyone.cfg.max_internal_size = max_internal_size
|
||||
matanyone.cfg.max_mem_frames = max_mem_frames
|
||||
matanyone.cfg.use_long_term = use_long_term
|
||||
|
||||
processor = InferenceCore(matanyone, cfg=matanyone.cfg)
|
||||
phas = inference_matanyone(src_video, mask, processor, mask_frame, n_warmup)
|
||||
out_mask = torch.cat(phas).unsqueeze(1).permute(0, 2, 3, 1)
|
||||
@@ -123,6 +142,18 @@ class MatAnyone2Video:
|
||||
"INT",
|
||||
{"default": 0, "min": 0, "step": 1},
|
||||
),
|
||||
"max_internal_size": (
|
||||
"INT",
|
||||
{"default": -1, "min": -1, "step": 1},
|
||||
),
|
||||
"max_mem_frames": (
|
||||
"INT",
|
||||
{"default": 5, "min": 1, "step": 1},
|
||||
),
|
||||
"use_long_term": (
|
||||
"BOOLEAN",
|
||||
{"default": False},
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -147,11 +178,18 @@ class MatAnyone2Video:
|
||||
solid_color: torch.Tensor | None = None,
|
||||
r_erode: int = 0,
|
||||
r_dilate: int = 0,
|
||||
max_internal_size: int = -1,
|
||||
max_mem_frames: int = 5,
|
||||
use_long_term: bool = False,
|
||||
):
|
||||
mask = get_mask(foreground_mask, foreground_MASK)
|
||||
src_video = src_video.permute(0, 3, 1, 2) # T C H W RGB, [0, 1]
|
||||
|
||||
matanyone2 = get_matanyone2_model_cached()
|
||||
matanyone2.cfg.max_internal_size = max_internal_size
|
||||
matanyone2.cfg.max_mem_frames = max_mem_frames
|
||||
matanyone2.cfg.use_long_term = use_long_term
|
||||
|
||||
processor = InferenceCore2(matanyone2, cfg=matanyone2.cfg)
|
||||
phas = inference_matanyone2(
|
||||
src_video,
|
||||
|
||||
@@ -234,12 +234,20 @@ class InferenceCore:
|
||||
.long()
|
||||
)
|
||||
else:
|
||||
mask = F.interpolate(
|
||||
mask.unsqueeze(0),
|
||||
size=(new_h, new_w),
|
||||
mode="bilinear",
|
||||
align_corners=False,
|
||||
)[0]
|
||||
if mask.dim() == 2:
|
||||
mask = F.interpolate(
|
||||
mask.unsqueeze(0).unsqueeze(0).float(),
|
||||
size=(new_h, new_w),
|
||||
mode="bilinear",
|
||||
align_corners=False,
|
||||
)[0, 0]
|
||||
else:
|
||||
mask = F.interpolate(
|
||||
mask.unsqueeze(0).float(),
|
||||
size=(new_h, new_w),
|
||||
mode="bilinear",
|
||||
align_corners=False,
|
||||
)[0]
|
||||
|
||||
self.curr_ti += 1
|
||||
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 332 KiB After Width: | Height: | Size: 260 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 269 KiB |
+1422
-617
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user