Files
SOLRICKS-comfyui-solricks/VideoTAADLAA.py
T

252 lines
11 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 DL-style Sharpening Pipeline
# v0.1.1 - Temporal stability improvements and ghosting reduction
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")
# =========================
# DLAA CORE (Uzamsal Filtre & Keskinleştirme)
# =========================
class _DLAANet(nn.Module):
"""
Lightweight CNN-based sharpening module used as a DL-inspired post-process.
Not:
Bu yapı gerçek bir eğitilmiş DLAA modeli değildir.
Sadece inference-time çalışan küçük bir CNN ile
detay keskinleştirme ve aliasing azaltma hedeflenmiştir.
Gelecekte pretrained super-resolution / restoration modelleri
ile değiştirilebilir şekilde tasarlanmıştır.
"""
def __init__(self):
super().__init__()
# Basit 3 katmanlı CNN tabanlı post-process filtre.
# Amaç: görüntü detaylarını hafif şekilde keskinleştirmek ve yumuşak aliasing azaltımı sağlamak.
# 1. Katman: Feature extraction
# 2. Katman: Non-linear mapping
# 3. Katman: Reconstruction / sharpening
self.conv = nn.Sequential(
nn.Conv2d(3, 16, 3, padding=1, bias=False),
nn.ReLU(inplace=True),
nn.Conv2d(16, 16, 3, padding=1, bias=False),
nn.ReLU(inplace=True),
nn.Conv2d(16, 3, 3, padding=1, bias=False),
)
# Edge Detection (Kenar Tespiti) işlemleri için kullanılacak X ve Y yönlü Sobel filtreleri
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))
# Sub-pixel kaydırma (Jitter) için 4 yönlü offset (kayma) koordinatları
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()
def _init(self):
convs = [m for m in self.conv if isinstance(m, nn.Conv2d)]
# 1. Katman: Identity initialization (RGB kanalları korunur)
# Not: Kaiming initialization yerine deterministik başlangıç kullanılmıştır.
with torch.no_grad():
convs[0].weight.zero_()
for c in range(3):
convs[0].weight[c, c, 1, 1] = 1.0 # RGB Identity
# 2. Katman: Identity initialization (kanal bazlı doğrusal aktarım)
with torch.no_grad():
convs[1].weight.zero_()
for c in range(min(convs[1].weight.shape[0], convs[1].weight.shape[1])):
convs[1].weight[c, c, 1, 1] = 1.0
# 3. Katman: Hafif sharpening kernel (predefined edge enhancement)
nn.init.zeros_(convs[2].weight)
sharpen = torch.tensor([[0.0, -1.0, 0.0], [-1.0, 5.0, -1.0], [0.0, -1.0, 0.0]], dtype=torch.float32)
with torch.no_grad():
for out_c in range(3):
# Girdinin sadece kendisine ait kanalını keskinleştirir (Cross-channel karışıklığını önler)
convs[2].weight[out_c, out_c] = sharpen * 0.1
def forward(self, x):
# Residual connection: Orijinal görüntüye, ağdan çıkan keskinleştirilmiş detayları ekliyoruz
return torch.clamp(x + self.conv(x) * 0.12, 0.0, 1.0)
# =========================
# TEMPORAL STATE (ZAMANSAL DURUM & ANTİ-GHOSTİNG)
# =========================
class _TAAState:
def __init__(self):
self.history = None # Önceki kareyi hafızada tutar
self.frame_id = 0 # Jitter döngüsü için kare sayacı
def reset(self):
# Video değiştiğinde veya kullanıcı sıfırlamak istediğinde geçmişi siler
self.history = None
self.frame_id = 0
def update(self, frame, alpha, sensitivity):
# İlk kareyse veya çözünürlük değiştiyse direkt mevcut kareyi geçmiş olarak kaydet
if self.history is None or self.history.shape != frame.shape:
self.history = frame.clone()
self.frame_id += 1
return frame
# Ghosting azaltma: neighborhood clamping ile temporal stabilizasyon
# 3x3 komşulukta min/max sınırlarını alıyoruz.
# Min değeri direkt bulmak yerine max_pool2d'yi ters işaretle kullanmak daha pratik oluyor.
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)
# Geçmiş kareyi mevcut frame’in renk sınırları içine çekiyoruz.
# Temporal clamping: geçmiş frame değerlerini mevcut frame'in lokal min/max aralığına sınırlar
# Bu yöntem ghosting artefact'larını azaltmayı hedefler
history_clipped = torch.clamp(self.history, local_min, local_max)
# Hareket maskesi: hareketli bölgelerde temporal blending etkisini azaltır
# Mevcut kare ile kırpılmış geçmiş kare arasındaki farkı bulur.
diff = torch.abs(frame - history_clipped).mean(dim=1, keepdim=True)
# Hassasiyet eşiğine göre hareketli bölgelerde TAA etkisini azaltmak için bir maske oluşturur (0.0 - 1.0 arası)
motion_mask = torch.sigmoid((diff - sensitivity) * 20.0)
# Hareketin çok olduğu yerlerde geçmişin ağırlığını (alpha) düşürür
dynamic_alpha = alpha * (1.0 - motion_mask)
# Mevcut kare ile geçmiş kareyi dinamik alpha değerine göre harmanlar (Blend)
out = torch.lerp(frame, history_clipped, dynamic_alpha)
# Yeni geçmişi hafızaya kaydet ve sayacı artır (OOM yememek için detach() önemli)
self.history = out.detach()
self.frame_id += 1
return out
# =========================
# MAIN NODE (COMFYUI ARAYÜZÜ)
# =========================
class VideoTAADLAA:
def __init__(self):
self.net_cache = {} # Device-specific model cache (CPU/GPU separation support)
self.taa = _TAAState()
@classmethod
def INPUT_TYPES(cls):
# ComfyUI arayüzünde görünecek ayarlar
return {
"required": {
"images": ("IMAGE",),
"taa_strength": ("FLOAT", {"default": 0.7, "min": 0, "max": 1, "step": 0.05}),
"taa_alpha": ("FLOAT", {"default": 0.6, "min": 0, "max": 0.95, "step": 0.01}),
"motion_sensitivity": ("FLOAT", {"default": 0.05, "min": 0.01, "max": 0.5, "step": 0.01}),
"jitter_scale": ("FLOAT", {"default": 0.1, "min": 0, "max": 1, "step": 0.01}),
"dlaa_strength": ("FLOAT", {"default": 0.6, "min": 0, "max": 1, "step": 0.05}),
"edge_threshold": ("FLOAT", {"default": 0.25, "min": 0, "max": 1, "step": 0.01}),
"blur_radius": ("INT", {"default": 1, "min": 0, "max": 5, "step": 1}),
"reset_history": ("BOOLEAN", {"default": True}),
}
}
RETURN_TYPES = ("IMAGE",)
FUNCTION = "execute"
CATEGORY = "CustomPostProcess"
def _net(self, device):
# Ağı ilgili cihaza (CPU/GPU) yükler ve bellekte tutar
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):
# Sub-pixel jittering: temporal sampling çeşitliliği sağlayarak aliasing azaltmayı hedefler
if scale < 1e-5: return x
off = net.jitter_offsets[idx % 4]
B, C, H, W = x.shape
# Affine dönüşüm matrisi oluşturuluyor
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
# Bilinear interpolasyon ile görüntüyü kaydırır (Kaba piksel kaydırmasından çok daha kalitelidir)
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):
# Edge-aware anti-aliasing: kenar bölgelerinde adaptif blur uygular
if blur <= 0: return x
# Görüntüyü gri tonlamaya (Grayscale) çevirir (İnsan gözünün Luma algısına göre ağırlıklandırılmış)
gray = 0.299*x[:, 0:1] + 0.587*x[:, 1:2] + 0.114*x[:, 2:3]
# Sobel filtreleri ile X ve Y eksenindeki farklılıklar (kenarlar) tespit edilir
sx, sy = F.conv2d(gray, net.sobel_x, padding=1), F.conv2d(gray, net.sobel_y, padding=1)
# Hipotenüs hesabı ile net kenar şiddeti bulunur
# NaN oluşumunu önlemek için küçük epsilon eklenir
edge = torch.sqrt(sx*sx + sy*sy + 1e-6)
# Belirlenen eşik (threshold) değerine göre maske oluşturulur
mask = torch.sigmoid((edge - thr) * 12.0)
# Yansıtmalı dolgu (reflection pad) ile bulanıklaştırma yapılır
blurred = F.avg_pool2d(F.pad(x, [blur]*4, mode="reflect"), blur*2+1, stride=1)
# Sadece kenar olan yerlere bulanıklaştırılmış versiyon, diğer yerlere orijinal görüntü verilir
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=True):
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():
# Frame-by-frame processing: GPU memory kullanımını azaltmak için batch yerine iteratif işleme
for i in range(B):
# ComfyUI [B, H, W, C] formatını PyTorch [B, C, H, W] formatına çevir
img = images[i:i+1].to(device).permute(0, 3, 1, 2).float()
rgb = img[:, :3]
# 1. Stage: spatial preprocessing (jitter + edge-aware filtering)
rgb = self.jitter(rgb, self.taa.frame_id, jitter_scale, net)
rgb = self.edge_aa(rgb, edge_threshold, blur_radius, net)
# 2. Stage: Temporal Anti-Aliasing (history-based blending with motion masking)
taa_out = self.taa.update(rgb, taa_alpha, motion_sensitivity)
rgb = torch.lerp(rgb, taa_out, taa_strength)
# 3. Stage: CNN-based sharpening (DL-inspired post-process)
if dlaa_strength > 0:
rgb = torch.lerp(rgb, net(rgb), dlaa_strength)
# İşlenen kareyi RAM'e geri al ve tekrar ComfyUI formatına [B, H, W, C] çevir
out_list.append(rgb.permute(0, 2, 3, 1).cpu())
# Her 10 karede bir GPU belleğini temizleyerek kararlılığı artır
if i % 10 == 0: mm.soft_empty_cache()
# İşlenen tüm kareleri tek bir batch halinde ComfyUI'a döndür
return (torch.cat(out_list, dim=0),)
# ComfyUI Node Kayıt İşlemleri
NODE_CLASS_MAPPINGS = {
"VideoTAADLAA": VideoTAADLAA
}
NODE_DISPLAY_NAME_MAPPINGS = {
"VideoTAADLAA": "🎮 Video TAA + DLAA Anti-Aliasing"
}