Files
SOLRICKS-comfyui-solricks/VideoTAADLAA.py
T

211 lines
8.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Created By MSXYZ and Claude Opus 4.6
# GPU başında ciddi işler döndürüyoruz. 🚀
# TAA (Temporal Anti-Aliasing) + DLAA (Deep Learning Anti-Aliasing) Adaptation
# v0.1.1
import torch
import torch.nn as nn
import torch.nn.functional as F
import threading
import logging
logger = logging.getLogger("VideoTAADLAA")
if not logger.handlers:
_handler = logging.StreamHandler()
_handler.setFormatter(logging.Formatter("[%(name)s] %(message)s"))
logger.addHandler(_handler)
logger.setLevel(logging.INFO)
# -----------------------------
# Gelişmiş DLAA Ağı ve Buffer Yönetimi
# -----------------------------
class _DLAANet(nn.Module):
def __init__(self):
super().__init__()
# Katmanlar
self.conv = nn.Sequential(
nn.Conv2d(3, 16, 3, padding=1, bias=False),
nn.ReLU(),
nn.Conv2d(16, 16, 3, padding=1, bias=False),
nn.ReLU(),
nn.Conv2d(16, 3, 3, padding=1, bias=False),
)
# KERNEL OPTİMİZASYONU: Sabitleri GPU buffer'ına kaydediyoruz
# Böylece her karede CPU -> GPU transferi yapılmaz.
self.register_buffer("sobel_x", torch.tensor([[-1, 0, 1], [-2, 0, 2], [-1, 0, 1]], dtype=torch.float32).view(1, 1, 3, 3))
self.register_buffer("sobel_y", torch.tensor([[-1, -2, -1], [0, 0, 0], [1, 2, 1]], dtype=torch.float32).view(1, 1, 3, 3))
self.register_buffer("jitter_offsets", torch.tensor([
[0.25, 0.25], [-0.25, -0.25], [-0.25, 0.25], [0.25, -0.25]
], dtype=torch.float32))
self._init_weights()
def _init_weights(self):
# Deterministik Sharpen Init
sharpen = torch.tensor([[0, -1, 0], [-1, 5, -1], [0, -1, 0]], dtype=torch.float32)
convs = [m for m in self.conv if isinstance(m, nn.Conv2d)]
nn.init.dirac_(convs[0].weight)
nn.init.dirac_(convs[1].weight)
nn.init.zeros_(convs[2].weight)
with torch.no_grad():
for i in range(3):
for j in range(3):
convs[2].weight[i, j] = sharpen * 0.1
def forward(self, x):
return torch.clamp(x + self.conv(x) * 0.15, 0.0, 1.0)
# -----------------------------
# Hareket Duyarlı TAA Yönetimi
# -----------------------------
class _TAAState:
def __init__(self):
self.history: torch.Tensor | None = None
self.frame_count: int = 0
def reset(self):
self.history = None
self.frame_count = 0
def update(self, frame: torch.Tensor, alpha: float, motion_threshold: float) -> torch.Tensor:
if self.history is None or self.history.shape != frame.shape:
self.history = frame.clone()
self.frame_count += 1
return frame
# GHOSTING KONTROLÜ (Motion Masking):
# Kareler arası farkı hesapla (L1 norm)
diff = torch.abs(frame - self.history).mean(dim=1, keepdim=True)
# Hareketin olduğu yerlerde alpha'yı (geçmişin etkisini) düşür
# Eğer fark threshold'un üzerindeyse o bölgeyi yeni kareden al (ghosting'i kes)
motion_mask = torch.clamp(diff * motion_threshold * 10.0, 0.0, 1.0)
dynamic_alpha = alpha * (1.0 - motion_mask)
out = dynamic_alpha * self.history + (1.0 - dynamic_alpha) * frame
self.history = out.detach()
self.frame_count += 1
return out
# -----------------------------
# Ana ComfyUI Node
# -----------------------------
class VideoTAADLAA:
def __init__(self):
self._lock = threading.Lock()
self._net_cache: dict[str, _DLAANet] = {}
self._taa = _TAAState()
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"images": ("IMAGE",),
"taa_strength": ("FLOAT", {"default": 0.7, "min": 0.0, "max": 1.0, "step": 0.05}),
"taa_alpha": ("FLOAT", {"default": 0.8, "min": 0.0, "max": 0.99, "step": 0.01}),
"motion_sensitivity": ("FLOAT", {"default": 0.5, "min": 0.0, "max": 1.0, "step": 0.05}),
"jitter_scale": ("FLOAT", {"default": 0.5, "min": 0.0, "max": 2.0, "step": 0.1}),
"dlaa_strength": ("FLOAT", {"default": 0.6, "min": 0.0, "max": 1.0, "step": 0.05}),
"edge_threshold": ("FLOAT", {"default": 0.12, "min": 0.0, "max": 1.0, "step": 0.01}),
"blur_radius": ("INT", {"default": 1, "min": 1, "max": 5, "step": 1}),
"reset_history": ("BOOLEAN", {"default": False}),
"batch_size": ("INT", {"default": 0, "min": 0, "max": 64, "step": 1}),
}
}
RETURN_TYPES = ("IMAGE",)
FUNCTION = "execute"
CATEGORY = "CustomPostProcess"
def _get_net(self, device: torch.device) -> _DLAANet:
key = str(device)
if key not in self._net_cache:
self._net_cache[key] = _DLAANet().to(device).eval()
return self._net_cache[key]
def _jitter(self, x: torch.Tensor, idx: int, scale: float, net: _DLAANet) -> torch.Tensor:
if scale <= 0:
return x
off = net.jitter_offsets[idx % 4]
B, C, H, W = x.shape
theta = torch.eye(2, 3, device=x.device).unsqueeze(0).repeat(B, 1, 1)
theta[:, 0, 2] = off[0] * scale / W
theta[:, 1, 2] = off[1] * scale / H
grid = F.affine_grid(theta, x.shape, align_corners=False)
# BICUBIC JITTER: Bilinear yerine Bicubic kullanarak keskinliği koruyoruz
return F.grid_sample(x, grid, mode="bicubic", padding_mode="reflection", align_corners=False)
def _edge_aa(self, x: torch.Tensor, thr: float, blur: int, net: _DLAANet) -> torch.Tensor:
gray = 0.299 * x[:, 0:1] + 0.587 * x[:, 1:2] + 0.114 * x[:, 2:3]
sx = F.conv2d(gray, net.sobel_x, padding=1)
sy = F.conv2d(gray, net.sobel_y, padding=1)
mask = (torch.sqrt(sx * sx + sy * sy + 1e-6) > thr).float()
if blur > 0:
k = blur * 2 + 1
blurred = F.avg_pool2d(F.pad(x, [blur] * 4, mode="reflect"), k, stride=1)
return x * (1.0 - mask) + blurred * mask
return x
def execute(self, images, taa_strength, taa_alpha, motion_sensitivity, jitter_scale,
dlaa_strength, edge_threshold, blur_radius, reset_history, batch_size):
with self._lock:
if reset_history:
self._taa.reset()
device = images.device
net = self._get_net(device)
if batch_size == 0:
# Maliyet hesabında C=4 (RGBA) ve Bicubic payı eklendi
B, H, W, C = images.shape
cost = H * W * C * 4 * 14 # Bicubic biraz daha fazla VRAM/İşlem ister
free, _ = torch.cuda.mem_get_info() if torch.cuda.is_available() else (0,0)
batch_size = max(1, min(16, int((free * 0.6) // cost))) if free > 0 else 1
images = images.to(device)
B, H, W, C = images.shape
has_alpha = (C == 4)
outputs = []
with torch.no_grad():
for i in range(0, B, batch_size):
chunk = images[i:i + batch_size]
img = chunk.permute(0, 3, 1, 2).float()
img_rgb = img[:, :3]
img_alpha = img[:, 3:4] if has_alpha else None
results_rgb = []
for f_idx in range(img_rgb.shape[0]):
f = img_rgb[f_idx:f_idx + 1]
# 1. Jitter (Bicubic Mode)
f = self._jitter(f, self._taa.frame_count, jitter_scale, net)
# 2. Edge AA (Optimized Kernels)
f = self._edge_aa(f, edge_threshold, blur_radius, net)
# 3. Motion-Adaptive TAA (Anti-Ghosting)
taa_out = self._taa.update(f, taa_alpha, motion_sensitivity)
f = torch.lerp(f, taa_out, taa_strength)
# 4. DLAA
if dlaa_strength > 0.0:
f = torch.lerp(f, net(f), dlaa_strength)
results_rgb.append(f.clamp(0.0, 1.0))
batch_rgb = torch.cat(results_rgb, dim=0)
batch_out = torch.cat([batch_rgb, img_alpha], dim=1) if has_alpha else batch_rgb
outputs.append(batch_out.permute(0, 2, 3, 1).cpu())
logger.info(f"İşlendi: {min(i + batch_size, B)} / {B}")
return (torch.cat(outputs, dim=0),)
NODE_CLASS_MAPPINGS = {"VideoTAADLAA": VideoTAADLAA}
NODE_DISPLAY_NAME_MAPPINGS = {"VideoTAADLAA": "🎮 Video TAA + DLAA (Ultra)"}