Files
SOLRICKS-comfyui-solricks/VideoTAADLAA.py
T

169 lines
6.7 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 (AI-assisted)
# Temporal Anti-Aliasing (TAA) + Lightweight DLAA-style Sharpening
# v0.1.1 - DLAANet with residual learning and orthogonal weights
import torch
import torch.nn as nn
import torch.nn.functional as F
import logging
import comfy.model_management as mm
logger = logging.getLogger("VideoTAADLAA")
class _DLAANet(nn.Module):
def __init__(self):
super().__init__()
# Sobel ve Jitter tanımları
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))
# Detay ayrıştırıcı
self.extract_feature = nn.Conv2d(3, 16, 3, padding=1, bias=False)
# Doku koruyucu
self.refiner = nn.Conv2d(16, 16, 3, padding=1, bias=False)
# Artifactleri temizle
self.reconstructor = nn.Conv2d(16, 3, 3, padding=1, bias=False)
# DLAANet with residual learning and orthogonal weights
self._init_weights()
def _init_weights(self):
nn.init.orthogonal_(self.extract_feature.weight)
nn.init.orthogonal_(self.refiner.weight)
nn.init.dirac_(self.reconstructor.weight)
def forward(self, x):
features = F.leaky_relu(self.extract_feature(x), 0.2)
refined = F.leaky_relu(self.refiner(features), 0.2)
residual = self.reconstructor(refined)
return x + residual
class _TAAState:
def __init__(self):
self.history = None
self.frame_id = 0
def reset(self):
self.history = None
self.frame_id = 0
def update(self, frame, alpha, sensitivity):
if self.history is None or self.history.shape != frame.shape:
self.history = frame.clone()
self.frame_id += 1
return frame
# Neighborhood clamping for stability
local_min = -F.max_pool2d(-frame, kernel_size=3, stride=1, padding=1)
local_max = F.max_pool2d(frame, kernel_size=3, stride=1, padding=1)
history_clipped = torch.clamp(self.history, local_min, local_max)
# Motion masking logic
diff = torch.abs(frame - history_clipped).mean(dim=1, keepdim=True)
motion_mask = torch.sigmoid((diff - sensitivity) * 45.0)
dynamic_alpha = alpha * (1.0 - motion_mask)
out = torch.lerp(frame, history_clipped, dynamic_alpha)
self.history = out.detach()
self.frame_id += 1
return out
class VideoTAADLAA:
def __init__(self):
self.net_cache = {}
self.taa = _TAAState()
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"images": ("IMAGE",),
"taa_strength": ("FLOAT", {"default": 0.65, "min": 0, "max": 1, "step": 0.05}),
"taa_alpha": ("FLOAT", {"default": 0.40, "min": 0, "max": 0.9, "step": 0.01}),
"motion_sensitivity": ("FLOAT", {"default": 0.05, "min": 0.01, "max": 0.5, "step": 0.01}),
"jitter_scale": ("FLOAT", {"default": 0.02, "min": 0, "max": 0.08, "step": 0.01}),
"dlaa_strength": ("FLOAT", {"default": 0.35, "min": 0, "max": 1, "step": 0.05}),
"edge_threshold": ("FLOAT", {"default": 0.25, "min": 0, "max": 0.5, "step": 0.01}),
"blur_radius": ("INT", {"default": 0, "min": 0, "max": 5, "step": 1}),
"reset_history": ("BOOLEAN", {"default": False}),
}
}
RETURN_TYPES = ("IMAGE",)
FUNCTION = "execute"
CATEGORY = "CustomPostProcess"
def _net(self, device):
if device not in self.net_cache:
self.net_cache[device] = _DLAANet().to(device).eval()
return self.net_cache[device]
def jitter(self, x, idx, scale, net):
if scale < 1e-5: 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], theta[:, 1, 2] = off[0] * scale / W, off[1] * scale / H
grid = F.affine_grid(theta, x.shape, align_corners=False)
return F.grid_sample(x, grid, mode="bilinear", padding_mode="reflection", align_corners=False)
def edge_aa(self, x, thr, blur, net):
if blur <= 0: return x
# Grayscale for edge detection
gray = 0.299*x[:, 0:1] + 0.587*x[:, 1:2] + 0.114*x[:, 2:3]
sx, sy = F.conv2d(gray, net.sobel_x, padding=1), F.conv2d(gray, net.sobel_y, padding=1)
edge = torch.sqrt(sx*sx + sy*sy + 1e-6)
mask = torch.sigmoid((edge - thr) * 15.0)
blurred = F.avg_pool2d(F.pad(x, [blur]*4, mode="reflect"), blur*2+1, stride=1)
return x*(1-mask) + blurred*mask
def execute(self, images, taa_strength, taa_alpha, motion_sensitivity, jitter_scale, dlaa_strength, edge_threshold, blur_radius, reset_history=False):
device = mm.get_torch_device()
if reset_history: self.taa.reset()
net = self._net(device)
B, H, W, C = images.shape
out_list = []
with torch.no_grad():
for i in range(B):
# NCHW conversion
img = images[i:i+1].to(device).permute(0, 3, 1, 2).float()
rgb = img[:, :3]
# 1. Spatial Preprocessing
rgb = self.jitter(rgb, self.taa.frame_id, jitter_scale, net)
rgb = self.edge_aa(rgb, edge_threshold, blur_radius, net)
# 2. Temporal Pass
taa_out = self.taa.update(rgb, taa_alpha, motion_sensitivity)
rgb = torch.lerp(rgb, taa_out, taa_strength)
# 3. Post-Sharpen
if dlaa_strength > 0:
# Modelin etkisini dlaa_strength ile sınırla
refined_output = net(rgb)
rgb = rgb + (refined_output - rgb) * dlaa_strength
# Kontrast/clarity artırımı
luma = 0.299 * rgb[:, 0:1, :, :] + 0.587 * rgb[:, 1:2, :, :] + 0.114 * rgb[:, 2:3, :, :]
mean_luma = torch.mean(luma, dim=(1, 2, 3), keepdim=True)
rgb = (rgb - mean_luma) * 1.02 + mean_luma
rgb = torch.clamp(rgb, 0.0, 1.0)
out_list.append(rgb.permute(0, 2, 3, 1).cpu())
if i % 10 == 0: mm.soft_empty_cache()
return (torch.cat(out_list, dim=0),)
NODE_CLASS_MAPPINGS = {"VideoTAADLAA": VideoTAADLAA}
NODE_DISPLAY_NAME_MAPPINGS = {"VideoTAADLAA": "🎮 Video TAA + DLAA Anti-Aliasing"}