Files
naku-yh-ComfyUI_Flux2ImageR…/flux2_image_reference_node.py
2026-01-17 11:48:22 +08:00

241 lines
11 KiB
Python

import torch
import os
import comfy.model_management as model_management
class NakuNode_Flux2:
"""
NakuNode Flux2 Image Reference Node for Flux2-based Image Generation
Takes up to 5 images, encodes them with selected VAE into reference latents,
and combines with text conditioning for feature fusion.
"""
def __init__(self):
pass
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"vae": ("VAE",),
"strength": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 2.0, "step": 0.01}),
},
"optional": {
"image1": ("IMAGE",),
"image2": ("IMAGE",),
"image3": ("IMAGE",),
"image4": ("IMAGE",),
"image5": ("IMAGE",),
"conditioning": ("CONDITIONING",),
}
}
RETURN_TYPES = ("CONDITIONING",)
FUNCTION = "apply_reference"
CATEGORY = "NakuNode_Flux2"
def apply_reference(self, vae, strength, image1=None, image2=None, image3=None, image4=None, image5=None, conditioning=None):
# 收集图像
images = [img for img in [image1, image2, image3, image4, image5] if img is not None]
if not images:
raise ValueError("At least one image is required")
# 对每张图像进行VAE编码
reference_latents = []
for i, image in enumerate(images):
# 确保图像维度正确
if len(image.shape) == 3:
image = image.unsqueeze(0)
# 验证图像通道数,确保是RGB或RGBA
if image.shape[-1] < 3:
raise ValueError(f"Image {i+1} has insufficient channels: {image.shape[-1]}")
# 编码图像为latent
# 将图像移动到适当的设备上
device = model_management.get_torch_device()
image = image.to(device)
# 编码图像为latent
# 注意:VAE的encode方法期望输入形状为(batch, height, width, channel),值范围为[0,1]
# 并且通常只取前3个通道(RGB)
image_rgb = image[:, :, :, :3]
latent = vae.encode(image_rgb)
reference_latents.append(latent)
# 如果有原始conditioning,则与参考latent结合
if conditioning is not None:
# 这里需要实现将reference_latents特征注入到conditioning中的逻辑
# 由于ComfyUI的conditioning格式复杂,这里简化处理
# 实际实现可能需要更复杂的attention操作
processed_conditioning = self.inject_features(conditioning, reference_latents, strength)
else:
# 如果没有原始conditioning,返回基于参考latent的新conditioning
processed_conditioning = self.create_conditioning_from_latents(reference_latents, strength)
return (processed_conditioning,)
def inject_features(self, conditioning, reference_latents, strength):
"""
将参考latent的特征注入到原始conditioning中
这是一个简化的实现,实际需要根据具体模型架构调整
"""
# 在Flux2模型中,conditioning通常包含文本嵌入和其他相关信息
# 我们需要将参考latent的信息融入到conditioning中
# 这里我们采用一种简单的方式,将latent的统计信息附加到conditioning中
# 获取device信息
device = model_management.get_torch_device()
# 计算所有参考latents的平均统计信息
combined_latent_stats = []
for latent in reference_latents:
# 计算latent的统计信息,如均值、方差等
# 这些统计信息可以作为视觉特征的表示
latent_mean = latent.mean(dim=[1, 2, 3], keepdim=True) # (batch, channels, 1, 1)
latent_std = latent.std(dim=[1, 2, 3], keepdim=True) # (batch, channels, 1, 1)
# 将统计信息展平以便后续处理
stats = torch.cat([latent_mean.squeeze(-1).squeeze(-1),
latent_std.squeeze(-1).squeeze(-1)], dim=-1) # (batch, 2*channels)
combined_latent_stats.append(stats)
# 计算所有latents的平均统计信息
if len(combined_latent_stats) > 1:
avg_latent_stats = torch.stack(combined_latent_stats, dim=0).mean(dim=0)
else:
avg_latent_stats = combined_latent_stats[0]
# 创建新的conditioning,将latent特征注入到原始conditioning中
new_conditioning = []
for cond in conditioning:
# 复制原始条件,安全地处理可能包含不同数据类型的列表
n = []
for item in cond:
if torch.is_tensor(item):
n.append(torch.clone(item))
else:
# 对于非张量类型,直接复制引用
n.append(item)
# 尝试将latent统计信息注入到conditioning中
# conditioning[0]通常是文本嵌入张量,尝试将其与latent统计信息结合
if len(n) > 0 and torch.is_tensor(n[0]) and n[0].dim() >= 2:
# 获取原始conditioning的形状信息
orig_cond = n[0]
# 调整avg_latent_stats的大小以匹配orig_cond的形状
# 这里假设orig_cond的形状是(batch, seq_len, features)
batch_size = orig_cond.shape[0]
# 重复latent统计信息以匹配批次大小
expanded_latent_stats = avg_latent_stats.repeat(batch_size, 1)
# 为了匹配orig_cond的序列长度维度,我们需要扩展
# 一种方法是将latent_stats附加到每个序列元素
seq_len = orig_cond.shape[1] if orig_cond.dim() > 1 else 1
features_per_token = orig_cond.shape[-1] if orig_cond.dim() > 1 else orig_cond.shape[-1]
# 简单的方法:将latent统计信息添加到原始conditioning中
# 这里我们将其作为一个额外的token或直接相加(取决于模型需求)
# 为简单起见,我们将latent特征添加到现有特征上
if orig_cond.shape[0] == expanded_latent_stats.shape[0]:
# 调整expanded_latent_stats以匹配orig_cond的最后一个维度
if expanded_latent_stats.shape[1] != orig_cond.shape[-1]:
# 如果维度不匹配,尝试截断或填充
target_dim = orig_cond.shape[-1]
if expanded_latent_stats.shape[1] > target_dim:
expanded_latent_stats = expanded_latent_stats[:, :target_dim]
else:
# 填充到目标维度
padding_size = target_dim - expanded_latent_stats.shape[1]
expanded_latent_stats = torch.cat([
expanded_latent_stats,
torch.zeros(expanded_latent_stats.shape[0], padding_size).to(expanded_latent_stats.device)
], dim=1)
# 应用强度参数
injected_cond = orig_cond + strength * expanded_latent_stats.unsqueeze(1)
n[0] = injected_cond
# 在conditioning的字典部分添加reference_latents信息
# 检查cond中是否有字典类型的元素(通常在cond[1]位置)
for i, item in enumerate(cond):
if isinstance(item, dict):
# 找到了字典,向其中添加reference_latents
n[i]['reference_latents'] = reference_latents
break
else:
# 如果没有找到字典,创建一个新的字典并添加到列表中
n.append({'reference_latents': reference_latents})
new_conditioning.append(n)
return new_conditioning
def create_conditioning_from_latents(self, reference_latents, strength):
"""
基于参考latent创建新的conditioning
"""
# 获取device信息
device = model_management.get_torch_device()
# 计算所有参考latents的综合特征表示
combined_latent_features = []
for latent in reference_latents:
# 计算latent的多级统计信息作为特征表示
latent_mean = latent.mean(dim=[2, 3], keepdim=True) # (batch, channels, 1, 1)
latent_std = latent.std(dim=[2, 3], keepdim=True) # (batch, channels, 1, 1)
latent_max = latent.max(dim=2, keepdim=True)[0].max(dim=3, keepdim=True)[0] # (batch, channels, 1, 1)
latent_min = latent.min(dim=2, keepdim=True)[0].min(dim=3, keepdim=True)[0] # (batch, channels, 1, 1)
# 组合多种统计信息
combined_stats = torch.cat([
latent_mean,
latent_std,
latent_max,
latent_min
], dim=1) # (batch, 4*channels, 1, 1)
# 展平并应用强度
flattened_stats = combined_stats.view(latent.shape[0], -1) * strength # (batch, 4*channels)
combined_latent_features.append(flattened_stats)
# 合并所有图像的特征
if len(combined_latent_features) > 1:
# 可以选择平均、拼接或其他融合策略
# 这里使用平均作为融合策略
fused_features = torch.stack(combined_latent_features, dim=0).mean(dim=0)
else:
fused_features = combined_latent_features[0]
# 创建模拟的conditioning结构
# 在实际应用中,这需要根据具体模型的要求进行调整
# 这里我们创建一个与Flux2兼容的conditioning格式
c = []
# 创建一个模拟的文本嵌入,其中包含从图像中提取的视觉特征
# 假设conditioning的基本格式为[嵌入向量, 其他参数字典]
batch_size = fused_features.shape[0]
# 创建一个模拟的嵌入向量,其维度与模型期望的文本嵌入一致
# 由于我们不知道确切的维度,我们创建一个合理的近似
embedding_dim = 768 # 假设使用类似CLIP的768维嵌入
if fused_features.shape[1] > embedding_dim:
# 如果特征过多,截断
simulated_embedding = fused_features[:, :embedding_dim].unsqueeze(1) # (batch, 1, embedding_dim)
elif fused_features.shape[1] < embedding_dim:
# 如果特征不足,填充零
padding_needed = embedding_dim - fused_features.shape[1]
padding = torch.zeros(batch_size, padding_needed).to(device)
padded_features = torch.cat([fused_features, padding], dim=1)
simulated_embedding = padded_features.unsqueeze(1) # (batch, 1, embedding_dim)
else:
simulated_embedding = fused_features.unsqueeze(1) # (batch, 1, embedding_dim)
# 创建conditioning条目,包含reference_latents信息
conditioning_entry = [simulated_embedding, {"pooled_output": fused_features, "reference_latents": reference_latents}] # 添加额外的池化输出和参考latents
c.append(conditioning_entry)
return c