This commit is contained in:
Fuou Marinas
2024-08-13 11:14:57 +07:00
committed by GitHub
parent b438209b10
commit f264247cc3
15 changed files with 1010 additions and 206 deletions
+39 -3
View File
@@ -5,6 +5,8 @@ Click name to jump to workflow
1. [**Neural Neighbor**](#neural-neighbor). Paper: [Neural Neighbor Style Transfer](https://github.com/nkolkin13/NeuralNeighborStyleTransfer)
2. [**CAST**](#cast). Paper: [Domain Enhanced Arbitrary Image Style Transfer via Contrastive Learning](https://github.com/zyxElsa/CAST_pytorch)
3. [**EFDM**](#efdm). Paper: [Exact Feature Distribution Matching for Arbitrary Style Transfer and Domain Generalization](https://github.com/YBZh/EFDM)
More will come soon
@@ -12,9 +14,11 @@ More will come soon
All nodes support batched input (i.e video) but is generally not recommended. One should generate 1 or 2 style frames (start and end), then use [ComfyUI-EbSynth](https://github.com/FuouM/ComfyUI-EbSynth) to propagate the style to the entire video.
| Neural Neighbor (2020-2022) - Slowest | CAST (2022-2023) - Fast | X |
|:-:|:-:|-|
|![neural_neighbor](example_outputs/neural_neighbor.png) | ![cast](example_outputs/cast.png) | X |
| Neural Neighbor (2020-2022) - Slowest | CAST (2022-2023) - Fast |
|:-:|:-:|
|![neural_neighbor](example_outputs/neural_neighbor.png) | ![cast](example_outputs/cast.png) |
| **EFDM (2022) - Fast but OOM on larger resolutions** | X |
| ![efdm](example_outputs/efdm.png) | X |
### Neural Neighbor
@@ -60,6 +64,29 @@ models\
![wf_cast](workflows/wf_cast.png)
### EFDM
Download the `hm_decoder_iter_160000.pth`
in https://github.com/YBZh/EFDM/tree/main/ArbitraryStyleTransfer/models and put it in
`models\hm_decoder_iter_160000.pth`
Download `vgg_normalized.pth` as instructed in [**CAST**](#cast)
Arguments:
- `style_interp_weights`: Weight for each style (only applicable for multiple styles). If empty, each style will have same weight (1/num_styles). Weights are automatically normalized so you can just enter anything
- `model_arch`: `["adain", "adamean", "adastd", "efdm", "hm"]`, defaults to `"efdm"`
- `style_strength`: From 0.00 to 1.00. The higher the stronger the style. Defaults to 1.00
- `do_crop`: Whether to resize and then crop the content and style images to `size x size` or just resize. Defaults to False
- `preserve_color`: Whether to preserve the color of the content image. Defaults to False
- `size`: Size of the height of the images after resizing. The aspect ratio of content is kept, while the styles will be resized to match content's height and width. The higher the `size`, the better the result, but also the more VRAM it will take. You may try `use_cpu` to circumvent OOM.
- `use_cpu`: Whether to use CPU or GPU for this. CPU takes very long! (default workflow = ~21 seconds). Defaults to False.
Important: You should not use batched content image input (i.e video) for this node. It is very easy to get OOM and is not optmized for videos.
[workflow_efdm.json](workflows/workflow_efdm.json)
![wf_efdm](workflows/wf_efdm.png)
## Credits
```
@@ -77,3 +104,12 @@ year = {2022}}
publisher={ACM New York, NY}
}
```
```
@inproceedings{zhang2021exact,
title={Exact Feature Distribution Matching for Arbitrary Style Transfer and Domain Generalization},
author={Zhang, Yabin and Li, Minghan and Li, Ruihuang and Jia, Kui and Zhang, Lei},
booktitle={CVPR},
year={2022}
}
```
+3 -1
View File
@@ -1,13 +1,15 @@
from .run import NeuralNeighbor, CAST
from .run import NeuralNeighbor, CAST, EFDM
NODE_CLASS_MAPPINGS = {
"NeuralNeighbor": NeuralNeighbor,
"CAST": CAST,
"EFDM": EFDM,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"NeuralNeighbor": "Neural Neighbor",
"CAST": "CAST",
"EFDM": "EFDM",
}
+5 -1
View File
@@ -3,4 +3,8 @@ CAST_DEFAULT = "CAST"
CAST_NET_AE_PATH = "latest_net_AE.pth"
CAST_NET_DEC_B_PATH = "latest_net_Dec_B.pth"
CAST_VGG_PATH = "models/vgg_normalised.pth"
CAST_VGG_PATH = "models/vgg_normalised.pth"
EFDM_STYLE_TYPES = ["adain", "adamean", "adastd", "efdm", "hm"]
EFDM_DEFAULT = "efdm"
EFDM_PATH = "models/hm_decoder_iter_160000.pth"
Binary file not shown.

After

Width:  |  Height:  |  Size: 880 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 202 KiB

+3
View File
@@ -37,5 +37,8 @@ def inference_ucast(
with torch.no_grad():
real_A_feat = netAE.forward(real_A, real_B)
fake_B = netDec_B.forward(real_A_feat)
if device.type != "cpu":
torch.cuda.empty_cache()
return fake_B
+154
View File
@@ -0,0 +1,154 @@
import torch
import torch.nn.functional as F
from torchvision import transforms
from . import net
from .function import (
adaptive_instance_normalization,
adaptive_mean_normalization,
adaptive_std_normalization,
coral,
exact_feature_distribution_matching,
histogram_matching,
)
def inference_efdm(
src_img: torch.Tensor,
style_img: torch.Tensor,
device,
decoder_path: str,
vgg_path: str,
alpha: float,
size: int,
style_type: str,
do_crop=False,
preserve_color=False,
style_interpolation_weights: list[int] | None = None,
):
if style_img.shape[0] > 1:
if style_interpolation_weights is None:
style_interpolation_weights = [
1 / style_img.shape[0] for _ in range(style_img.shape[0])
]
else:
style_interpolation_weights = [
w / sum(style_interpolation_weights)
for w in style_interpolation_weights
]
else:
style_interpolation_weights = None
decoder = net.decoder
vgg = net.vgg
decoder.eval()
vgg.eval()
decoder.load_state_dict(torch.load(decoder_path))
vgg.load_state_dict(torch.load(vgg_path))
vgg = torch.nn.Sequential(*list(vgg.children())[:31])
vgg.to(device)
decoder.to(device)
content_tf = test_transform(size, do_crop)
content = content_tf(src_img).to(device)
style = content_tf(style_img).to(device)
style = F.interpolate(
style,
size=(content.shape[2], content.shape[3]),
mode="bilinear",
align_corners=False,
)
content = content.expand_as(style)
print(f"Resized: {content.shape=}")
print(f"Resized: {style.shape=}")
if preserve_color:
tmp_content = content.squeeze()
tmp_styles = [coral(stl, tmp_content).unsqueeze(0) for stl in style]
style = torch.cat(tmp_styles, dim=0)
style = style.to(device)
if device.type != "cpu":
torch.cuda.empty_cache()
output = style_transfer(
vgg,
decoder,
content,
style,
device,
alpha,
style_interpolation_weights,
style_type=style_type,
)
if device.type != "cpu":
torch.cuda.empty_cache()
# [N, C, H, W]
return output
def test_transform(size, crop):
transform_list = []
if size != 0:
transform_list.append(transforms.Resize(size))
if crop:
transform_list.append(transforms.CenterCrop(size))
transform = transforms.Compose(transform_list)
# [B, C, H, W]
return transform
def style_transfer(
vgg,
decoder,
content,
style,
device,
alpha=1.0,
interpolation_weights=None,
style_type="adain",
):
assert 0.0 <= alpha <= 1.0
content_f = vgg(content)
style_f = vgg(style)
if interpolation_weights:
_, C, H, W = content_f.size()
feat = torch.FloatTensor(1, C, H, W).zero_().to(device)
if style_type == "adain":
base_feat = adaptive_instance_normalization(content_f, style_f)
elif style_type == "adamean":
base_feat = adaptive_mean_normalization(content_f, style_f)
elif style_type == "adastd":
base_feat = adaptive_std_normalization(content_f, style_f)
elif style_type == "efdm":
base_feat = exact_feature_distribution_matching(content_f, style_f)
elif style_type == "hm":
feat = histogram_matching(content_f, style_f)
else:
raise NotImplementedError
for i, w in enumerate(interpolation_weights):
feat = feat + w * base_feat[i : i + 1]
content_f = content_f[0:1]
else:
if style_type == "adain":
feat = adaptive_instance_normalization(content_f, style_f)
elif style_type == "adamean":
feat = adaptive_mean_normalization(content_f, style_f)
elif style_type == "adastd":
feat = adaptive_std_normalization(content_f, style_f)
elif style_type == "efdm":
feat = exact_feature_distribution_matching(content_f, style_f)
elif style_type == "hm":
feat = histogram_matching(content_f, style_f)
else:
raise NotImplementedError
feat = feat * alpha + content_f * (1 - alpha)
return decoder(feat)
+140
View File
@@ -0,0 +1,140 @@
import numpy as np
import torch
from skimage.exposure import match_histograms
def calc_mean_std(feat, eps=1e-5):
# eps is a small value added to the variance to avoid divide-by-zero.
size = feat.size()
assert len(size) == 4
N, C = size[:2]
feat_var = feat.view(N, C, -1).var(dim=2) + eps
feat_std = feat_var.sqrt().view(N, C, 1, 1)
feat_mean = feat.view(N, C, -1).mean(dim=2).view(N, C, 1, 1)
return feat_mean, feat_std
def adaptive_instance_normalization(content_feat, style_feat):
assert content_feat.size()[:2] == style_feat.size()[:2]
size = content_feat.size()
style_mean, style_std = calc_mean_std(style_feat)
content_mean, content_std = calc_mean_std(content_feat)
normalized_feat = (content_feat - content_mean.expand(size)) / content_std.expand(
size
)
return normalized_feat * style_std.expand(size) + style_mean.expand(size)
## AdaMean
def adaptive_mean_normalization(content_feat, style_feat):
assert content_feat.size()[:2] == style_feat.size()[:2]
size = content_feat.size()
style_mean, style_std = calc_mean_std(style_feat)
content_mean, content_std = calc_mean_std(content_feat)
normalized_feat = content_feat - content_mean.expand(size)
return normalized_feat + style_mean.expand(size)
## AdaStd
def adaptive_std_normalization(content_feat, style_feat):
assert content_feat.size()[:2] == style_feat.size()[:2]
size = content_feat.size()
style_mean, style_std = calc_mean_std(style_feat)
content_mean, content_std = calc_mean_std(content_feat)
normalized_feat = (content_feat) / content_std.expand(size)
return normalized_feat * style_std.expand(size)
## EFDM
def exact_feature_distribution_matching(content_feat, style_feat):
assert content_feat.size() == style_feat.size()
B, C, W, H = (
content_feat.size(0),
content_feat.size(1),
content_feat.size(2),
content_feat.size(3),
)
value_content, index_content = torch.sort(
content_feat.view(B, C, -1)
) # sort conduct a deep copy here.
value_style, _ = torch.sort(
style_feat.view(B, C, -1)
) # sort conduct a deep copy here.
inverse_index = index_content.argsort(-1)
new_content = content_feat.view(B, C, -1) + (
value_style.gather(-1, inverse_index) - content_feat.view(B, C, -1).detach()
)
return new_content.view(B, C, W, H)
## HM
def histogram_matching(content_feat, style_feat):
assert content_feat.size() == style_feat.size()
B, C, W, H = (
content_feat.size(0),
content_feat.size(1),
content_feat.size(2),
content_feat.size(3),
)
x_view = content_feat.view(-1, W, H)
image1_temp = match_histograms(
np.array(x_view.detach().clone().cpu().float().transpose(0, 2)),
np.array(
style_feat.view(-1, W, H).detach().clone().cpu().float().transpose(0, 2)
),
)
image1_temp = (
torch.from_numpy(image1_temp)
.float()
.to(content_feat.device)
.transpose(0, 2)
.view(B, C, W, H)
)
return content_feat + (image1_temp - content_feat).detach()
def _calc_feat_flatten_mean_std(feat):
# takes 3D feat (C, H, W), return mean and std of array within channels
assert feat.size()[0] == 3
assert isinstance(feat, torch.FloatTensor)
feat_flatten = feat.view(3, -1)
mean = feat_flatten.mean(dim=-1, keepdim=True)
std = feat_flatten.std(dim=-1, keepdim=True)
return feat_flatten, mean, std
def _mat_sqrt(x):
U, D, V = torch.svd(x)
return torch.mm(torch.mm(U, D.pow(0.5).diag()), V.t())
def coral(source: torch.Tensor, target: torch.Tensor):
# assume both source and target are 3D array (C, H, W)
# Note: flatten -> f
source_f, source_f_mean, source_f_std = _calc_feat_flatten_mean_std(source.to('cpu'))
source_f_norm = (
source_f - source_f_mean.expand_as(source_f)
) / source_f_std.expand_as(source_f)
source_f_cov_eye = torch.mm(source_f_norm, source_f_norm.t()) + torch.eye(3)
target_f, target_f_mean, target_f_std = _calc_feat_flatten_mean_std(target.to('cpu'))
target_f_norm = (
target_f - target_f_mean.expand_as(target_f)
) / target_f_std.expand_as(target_f)
target_f_cov_eye = torch.mm(target_f_norm, target_f_norm.t()) + torch.eye(3)
source_f_norm_transfer = torch.mm(
_mat_sqrt(target_f_cov_eye),
torch.mm(torch.inverse(_mat_sqrt(source_f_cov_eye)), source_f_norm),
)
source_f_transfer = source_f_norm_transfer * target_f_std.expand_as(
source_f_norm
) + target_f_mean.expand_as(source_f_norm)
return source_f_transfer.view(source.size())
+214
View File
@@ -0,0 +1,214 @@
import numpy as np
import torch
import torch.nn as nn
from skimage.exposure import match_histograms
from .function import adaptive_instance_normalization as adain
from .function import adaptive_mean_normalization as adamean
from .function import adaptive_std_normalization as adastd
from .function import calc_mean_std
from .function import exact_feature_distribution_matching as efdm
from .function import histogram_matching as hm
decoder = nn.Sequential(
nn.ReflectionPad2d((1, 1, 1, 1)),
nn.Conv2d(512, 256, (3, 3)),
nn.ReLU(),
nn.Upsample(scale_factor=2, mode="nearest"),
nn.ReflectionPad2d((1, 1, 1, 1)),
nn.Conv2d(256, 256, (3, 3)),
nn.ReLU(),
nn.ReflectionPad2d((1, 1, 1, 1)),
nn.Conv2d(256, 256, (3, 3)),
nn.ReLU(),
nn.ReflectionPad2d((1, 1, 1, 1)),
nn.Conv2d(256, 256, (3, 3)),
nn.ReLU(),
nn.ReflectionPad2d((1, 1, 1, 1)),
nn.Conv2d(256, 128, (3, 3)),
nn.ReLU(),
nn.Upsample(scale_factor=2, mode="nearest"),
nn.ReflectionPad2d((1, 1, 1, 1)),
nn.Conv2d(128, 128, (3, 3)),
nn.ReLU(),
nn.ReflectionPad2d((1, 1, 1, 1)),
nn.Conv2d(128, 64, (3, 3)),
nn.ReLU(),
nn.Upsample(scale_factor=2, mode="nearest"),
nn.ReflectionPad2d((1, 1, 1, 1)),
nn.Conv2d(64, 64, (3, 3)),
nn.ReLU(),
nn.ReflectionPad2d((1, 1, 1, 1)),
nn.Conv2d(64, 3, (3, 3)),
)
vgg = nn.Sequential(
nn.Conv2d(3, 3, (1, 1)),
nn.ReflectionPad2d((1, 1, 1, 1)),
nn.Conv2d(3, 64, (3, 3)),
nn.ReLU(), # relu1-1
nn.ReflectionPad2d((1, 1, 1, 1)),
nn.Conv2d(64, 64, (3, 3)),
nn.ReLU(), # relu1-2
nn.MaxPool2d((2, 2), (2, 2), (0, 0), ceil_mode=True),
nn.ReflectionPad2d((1, 1, 1, 1)),
nn.Conv2d(64, 128, (3, 3)),
nn.ReLU(), # relu2-1
nn.ReflectionPad2d((1, 1, 1, 1)),
nn.Conv2d(128, 128, (3, 3)),
nn.ReLU(), # relu2-2
nn.MaxPool2d((2, 2), (2, 2), (0, 0), ceil_mode=True),
nn.ReflectionPad2d((1, 1, 1, 1)),
nn.Conv2d(128, 256, (3, 3)),
nn.ReLU(), # relu3-1
nn.ReflectionPad2d((1, 1, 1, 1)),
nn.Conv2d(256, 256, (3, 3)),
nn.ReLU(), # relu3-2
nn.ReflectionPad2d((1, 1, 1, 1)),
nn.Conv2d(256, 256, (3, 3)),
nn.ReLU(), # relu3-3
nn.ReflectionPad2d((1, 1, 1, 1)),
nn.Conv2d(256, 256, (3, 3)),
nn.ReLU(), # relu3-4
nn.MaxPool2d((2, 2), (2, 2), (0, 0), ceil_mode=True),
nn.ReflectionPad2d((1, 1, 1, 1)),
nn.Conv2d(256, 512, (3, 3)),
nn.ReLU(), # relu4-1, this is the last layer used
nn.ReflectionPad2d((1, 1, 1, 1)),
nn.Conv2d(512, 512, (3, 3)),
nn.ReLU(), # relu4-2
nn.ReflectionPad2d((1, 1, 1, 1)),
nn.Conv2d(512, 512, (3, 3)),
nn.ReLU(), # relu4-3
nn.ReflectionPad2d((1, 1, 1, 1)),
nn.Conv2d(512, 512, (3, 3)),
nn.ReLU(), # relu4-4
nn.MaxPool2d((2, 2), (2, 2), (0, 0), ceil_mode=True),
nn.ReflectionPad2d((1, 1, 1, 1)),
nn.Conv2d(512, 512, (3, 3)),
nn.ReLU(), # relu5-1
nn.ReflectionPad2d((1, 1, 1, 1)),
nn.Conv2d(512, 512, (3, 3)),
nn.ReLU(), # relu5-2
nn.ReflectionPad2d((1, 1, 1, 1)),
nn.Conv2d(512, 512, (3, 3)),
nn.ReLU(), # relu5-3
nn.ReflectionPad2d((1, 1, 1, 1)),
nn.Conv2d(512, 512, (3, 3)),
nn.ReLU(), # relu5-4
)
class Net(nn.Module):
def __init__(self, encoder, decoder, style):
super(Net, self).__init__()
enc_layers = list(encoder.children())
self.enc_1 = nn.Sequential(*enc_layers[:4]) # input -> relu1_1
self.enc_2 = nn.Sequential(*enc_layers[4:11]) # relu1_1 -> relu2_1
self.enc_3 = nn.Sequential(*enc_layers[11:18]) # relu2_1 -> relu3_1
self.enc_4 = nn.Sequential(*enc_layers[18:31]) # relu3_1 -> relu4_1
self.decoder = decoder
self.mse_loss = nn.MSELoss()
self.style = style
# fix the encoder
for name in ["enc_1", "enc_2", "enc_3", "enc_4"]:
for param in getattr(self, name).parameters():
param.requires_grad = False
# extract relu1_1, relu2_1, relu3_1, relu4_1 from input image
def encode_with_intermediate(self, input):
results = [input]
for i in range(4):
func = getattr(self, "enc_{:d}".format(i + 1))
results.append(func(results[-1]))
return results[1:]
# extract relu4_1 from input image
def encode(self, input):
for i in range(4):
input = getattr(self, "enc_{:d}".format(i + 1))(input)
return input
def calc_content_loss(self, input, target):
assert input.size() == target.size()
assert target.requires_grad is False
return self.mse_loss(input, target)
def calc_style_loss(self, input, target):
# ipdb.set_trace()
assert input.size() == target.size()
assert (
target.requires_grad is False
) ## first make sure which one require gradient and which one do not.
# print(input.requires_grad) ## True
input_mean, input_std = calc_mean_std(input)
target_mean, target_std = calc_mean_std(target)
if self.style == "adain":
return self.mse_loss(input_mean, target_mean) + self.mse_loss(
input_std, target_std
)
elif self.style == "adamean":
return self.mse_loss(input_mean, target_mean)
elif self.style == "adastd":
return self.mse_loss(input_std, target_std)
elif self.style == "efdm":
B, C, W, H = input.size(0), input.size(1), input.size(2), input.size(3)
value_content, index_content = torch.sort(input.view(B, C, -1))
value_style, index_style = torch.sort(target.view(B, C, -1))
inverse_index = index_content.argsort(-1)
return self.mse_loss(
input.view(B, C, -1), value_style.gather(-1, inverse_index)
)
elif self.style == "hm":
B, C, W, H = input.size(0), input.size(1), input.size(2), input.size(3)
x_view = input.view(-1, W, H)
image1_temp = match_histograms(
np.array(x_view.detach().clone().cpu().float().transpose(0, 2)),
np.array(
target.view(-1, W, H).detach().clone().cpu().float().transpose(0, 2)
),
multichannel=True,
)
image1_temp = (
torch.from_numpy(image1_temp)
.float()
.to(input.device)
.transpose(0, 2)
.view(B, C, W, H)
)
return self.mse_loss(input.reshape(B, C, -1), image1_temp.reshape(B, C, -1))
else:
raise NotImplementedError
def forward(self, content, style, alpha=1.0):
assert 0 <= alpha <= 1
# ipdb.set_trace()
style_feats = self.encode_with_intermediate(style)
content_feat = self.encode(content)
# print(content_feat.requires_grad) False
# print(style_feats[-1].requires_grad) False
if self.style == "adain":
t = adain(content_feat, style_feats[-1])
elif self.style == "adamean":
t = adamean(content_feat, style_feats[-1])
elif self.style == "adastd":
t = adastd(content_feat, style_feats[-1])
elif self.style == "efdm":
t = efdm(content_feat, style_feats[-1])
elif self.style == "hm":
t = hm(content_feat, style_feats[-1])
else:
raise NotImplementedError
t = alpha * t + (1 - alpha) * content_feat
g_t = self.decoder(t)
g_t_feats = self.encode_with_intermediate(g_t)
loss_c = self.calc_content_loss(
g_t_feats[-1], t
) ### final feature should be the same.
loss_s = self.calc_style_loss(g_t_feats[0], style_feats[0])
for i in range(1, 4):
loss_s += self.calc_style_loss(g_t_feats[i], style_feats[i])
return loss_c, loss_s
+26
View File
@@ -0,0 +1,26 @@
import numpy as np
from torch.utils import data
def InfiniteSampler(n):
# i = 0
i = n - 1
order = np.random.permutation(n)
while True:
yield order[i]
i += 1
if i >= n:
np.random.seed()
order = np.random.permutation(n)
i = 0
class InfiniteSamplerWrapper(data.sampler.Sampler):
def __init__(self, data_source):
self.num_samples = len(data_source)
def __iter__(self):
return iter(InfiniteSampler(self.num_samples))
def __len__(self):
return 2 ** 31
+2 -2
View File
@@ -74,7 +74,7 @@ def produce_stylization(
li = -1
for scl in range(max_scls)[::-1]:
# Get content image and style image from pyramid at current resolution
if device != "cpu":
if device.type != "cpu":
torch.cuda.empty_cache()
style_im_tmp = syn_pyr(style_pyr[scl:])
content_im_tmp = syn_pyr(c_pyr[scl:])
@@ -381,7 +381,7 @@ def optimize_output_im(
pbar.update_absolute(i, max_iter)
if device != "cpu":
if device.type != "cpu":
torch.cuda.empty_cache()
# Update output's pyramid coefficients for current resolution
+2 -1
View File
@@ -1,2 +1,3 @@
torch
torchvision
torchvision
scikit-image
+86 -198
View File
@@ -12,19 +12,20 @@ import torch.nn.functional as F
import tqdm
from comfy.utils import ProgressBar
from .module_cast.cast_model import inference_ucast
from .constants import (
CAST_DEFAULT,
CAST_NET_AE_PATH,
CAST_NET_DEC_B_PATH,
CAST_TYPES,
CAST_VGG_PATH,
EFDM_DEFAULT,
EFDM_PATH,
EFDM_STYLE_TYPES,
)
from .module_cast.cast_model import inference_ucast
from .module_efdm.efdm_model import inference_efdm
from .module_neural_neighbor.neural_neighbor_model import inference_neural_neighbor
base_dir = Path(__file__).resolve().parent
@@ -155,208 +156,95 @@ class CAST:
return (torch.cat(result, dim=0),)
# class RealViFormerSR:
# @classmethod
# def INPUT_TYPES(s):
# return {
# "required": {
# "src_video": ("IMAGE",),
# "interval": (
# "INT",
# {"default": 50, "min": 0, "step": 1},
# ),
# },
# }
class EFDM:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"src_img": ("IMAGE",),
"style_img": ("IMAGE",),
"style_interp_weights": ("STRING", {"default": ""}),
"model_arch": (EFDM_STYLE_TYPES, {"default": EFDM_DEFAULT}),
"style_strength": (
"FLOAT",
{"default": 1.00, "min": 0.00, "max": 1.00, "round": 0.01},
),
"do_crop": (
"BOOLEAN",
{"default": False},
),
"preserve_color": (
"BOOLEAN",
{"default": False},
),
"size": (
"INT",
{"default": 512, "min": 1, "step": 1},
),
"use_cpu": (
"BOOLEAN",
{"default": False},
),
},
}
# RETURN_TYPES = ("IMAGE",)
# RETURN_NAMES = ("res_video",)
# FUNCTION = "todo"
# CATEGORY = "FM_nodes"
RETURN_TYPES = ("IMAGE",)
RETURN_NAMES = ("res_img",)
FUNCTION = "todo"
CATEGORY = "StyleTransferPlus"
# def todo(self, src_video: torch.Tensor, interval: int):
# src_video = src_video.permute(0, 3, 1, 2)
# print(f"{src_video.shape=}")
def todo(
self,
src_img: torch.Tensor,
style_img: torch.Tensor,
style_interp_weights: str,
model_arch: str,
style_strength: float,
do_crop: bool,
preserve_color: bool,
size: int,
use_cpu: bool,
):
print(f"{src_img.shape=}")
print(f"{style_img.shape=}")
device = torch.device(
"cuda" if torch.cuda.is_available() and not use_cpu else "cpu"
)
# device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
# model = RealViformer(
# num_feat=48,
# num_blocks=[2, 3, 4, 1],
# spynet_path=None,
# heads=[1, 2, 4],
# ffn_expansion_factor=2.66,
# merge_head=2,
# bias=False,
# LayerNorm_type="BiasFree",
# ch_compress=True,
# squeeze_factor=[4, 4, 4],
# masked=True,
# )
# model.load_state_dict(
# torch.load(f"{base_dir}/{REALVIFORMER_MODEL_PATH}")["params"], strict=False
# )
# model.eval()
# model = model.to(device)
params = {
"style_img": style_img.permute(0, 3, 1, 2),
"device": device,
"decoder_path": f"{base_dir}/{EFDM_PATH}",
"vgg_path": f"{base_dir}/{CAST_VGG_PATH}",
"alpha": style_strength,
"size": size,
"style_type": model_arch,
"do_crop": do_crop,
"preserve_color": preserve_color,
"style_interpolation_weights": deserialize_floats(style_interp_weights)
or None,
}
# if src_video.shape[0] <= interval:
# out_tensor = inference_realviformer(
# src_video.unsqueeze(0).to(device), model
# )
# out_tensor = out_tensor.squeeze(dim=0).permute(0, 2, 3, 1)
# return (out_tensor,)
print(f"{params['style_interpolation_weights']=}")
# num_imgs = src_video.shape[0]
# outputs: list[torch.Tensor] = []
# pbar = ProgressBar(num_imgs)
num_frames = src_img.size(0)
pbar = ProgressBar(num_frames)
# for idx in tqdm.tqdm(range(0, num_imgs, interval)):
# interval = min(interval, num_imgs - idx)
# imgs = src_video[idx : idx + interval]
# imgs = imgs.unsqueeze(0).to(device) # [b, n, c, h, w]
# outputs.append(inference_realviformer(imgs, model).squeeze(dim=0))
# pbar.update_absolute(idx + interval, num_imgs)
result: list[torch.Tensor] = []
with torch.inference_mode(mode=False):
for i in range(num_frames):
params["src_img"] = src_img[i].unsqueeze(0).permute(0, 3, 1, 2)
res_tensor = inference_efdm(**params)
result.append(res_tensor.permute(0, 2, 3, 1))
print(f"{res_tensor.shape=}")
pbar.update_absolute(i, num_frames)
# out_tensor = torch.cat(outputs, dim=0).permute(0, 2, 3, 1)
# return (out_tensor,)
return (torch.cat(result, dim=0),)
# class ProPIH_Harmonizer:
# @classmethod
# def INPUT_TYPES(s):
# return {
# "required": {
# "composite": ("IMAGE",),
# "background": ("IMAGE",),
# },
# "optional": {"foreground_mask": ("IMAGE",), "foreground_MASK": ("MASK",)},
# }
# RETURN_TYPES = (
# "IMAGE",
# "IMAGE",
# "IMAGE",
# "IMAGE",
# )
# RETURN_NAMES = (
# "out_0",
# "out_1",
# "out_2",
# "out_3",
# )
# FUNCTION = "todo"
# CATEGORY = "FM_nodes"
# def todo(
# self,
# composite: torch.Tensor,
# background: torch.Tensor,
# foreground_mask: torch.Tensor | None = None,
# foreground_MASK: torch.Tensor | None = None,
# ):
# if foreground_mask is None and foreground_MASK is None:
# raise ValueError("Please provide one mask image")
# if foreground_MASK is not None:
# mask = foreground_MASK.unsqueeze(dim=0)
# else:
# mask = img_to_mask(foreground_mask.permute(0, 3, 1, 2))
# composite = composite.permute(0, 3, 1, 2)
# background = background.permute(0, 3, 1, 2)
# propih = VGG19HRNetModel(
# vgg_path=f"{base_dir}/{PROPIH_VGG_MODEL_PATH}",
# g_path=f"{base_dir}/{PROPIH_G_MODEL_PATH}",
# )
# outputs = propih.forward(comp=composite, style=background, mask=mask)
# final_outputs = []
# for ts in outputs:
# final_outputs.append(ts.permute(0, 2, 3, 1))
# return (
# final_outputs[0],
# final_outputs[1],
# final_outputs[2],
# final_outputs[3],
# )
def serialize_floats(lst: list[float]):
return ",".join(map(str, lst))
# class CoLIE_LowLight_Enhance:
# @classmethod
# def INPUT_TYPES(s):
# return {
# "required": {
# "src_img": ("IMAGE",),
# "down_res": (
# "INT",
# {"default": 256, "min": 1, "step": 1},
# ),
# "epochs": (
# "INT",
# {"default": 100, "min": 1, "step": 1},
# ),
# "cxt_window": (
# "INT",
# {"default": 1, "min": 1, "step": 1},
# ),
# "loss_mean": (
# "FLOAT",
# {"default": 0.3, "min": 0.01},
# ),
# "alpha": (
# "FLOAT",
# {"default": 1.0, "min": 0.01},
# ),
# "beta": (
# "FLOAT",
# {"default": 20.0, "min": 0.01},
# ),
# "gamma": (
# "FLOAT",
# {"default": 8.0, "min": 0.01},
# ),
# "delta": (
# "FLOAT",
# {"default": 5.0, "min": 0.01},
# ),
# },
# }
# RETURN_TYPES = ("IMAGE",)
# RETURN_NAMES = ("res_img",)
# FUNCTION = "todo"
# CATEGORY = "FM_nodes"
# def todo(
# self,
# src_img: torch.Tensor,
# down_res: int,
# epochs: int,
# cxt_window: int,
# loss_mean: float,
# alpha: float,
# beta: float,
# gamma: float,
# delta: float,
# ):
# result: list[torch.Tensor] = []
# num_frames = src_img.size(0)
# pbar = ProgressBar(num_frames)
# for i in range(num_frames):
# image = src_img[i].unsqueeze(0)
# res_tensor = run_colie(
# image,
# CoLIE_Config(
# down_res=down_res,
# epochs=epochs,
# cxt_window=cxt_window,
# loss_mean=loss_mean,
# alpha=alpha,
# beta=beta,
# gamma=gamma,
# delta=delta,
# ),
# )
# result.append(res_tensor)
# pbar.update_absolute(i, num_frames)
# return (torch.cat(result, dim=0),)
def deserialize_floats(floats_str: str):
return [float(w) for w in floats_str.split(",") if w.strip()]
Binary file not shown.

After

Width:  |  Height:  |  Size: 726 KiB

+336
View File
@@ -0,0 +1,336 @@
{
"last_node_id": 9,
"last_link_id": 18,
"nodes": [
{
"id": 8,
"type": "LoadImage",
"pos": [
242,
460
],
"size": [
315,
314.00001525878906
],
"flags": {},
"order": 0,
"mode": 0,
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
12
],
"shape": 3,
"slot_index": 0
},
{
"name": "MASK",
"type": "MASK",
"links": null,
"shape": 3
}
],
"properties": {
"Node name for S&R": "LoadImage"
},
"widgets_values": [
"popova.jpg",
"image"
]
},
{
"id": 9,
"type": "Reroute",
"pos": [
461,
279
],
"size": [
75,
26
],
"flags": {},
"order": 4,
"mode": 0,
"inputs": [
{
"name": "",
"type": "*",
"link": 18
}
],
"outputs": [
{
"name": "",
"type": "IMAGE",
"links": [
16
],
"slot_index": 0
}
],
"properties": {
"showOutputText": false,
"horizontal": false
}
},
{
"id": 2,
"type": "LoadImage",
"pos": [
622,
519
],
"size": {
"0": 315,
"1": 314
},
"flags": {},
"order": 1,
"mode": 0,
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
9
],
"shape": 3,
"slot_index": 0
},
{
"name": "MASK",
"type": "MASK",
"links": null,
"shape": 3
}
],
"properties": {
"Node name for S&R": "LoadImage"
},
"widgets_values": [
"006.jpg",
"image"
]
},
{
"id": 4,
"type": "SaveImage",
"pos": [
961,
282
],
"size": {
"0": 315,
"1": 270
},
"flags": {},
"order": 6,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 11
}
],
"properties": {},
"widgets_values": [
"StyleTransfer"
]
},
{
"id": 7,
"type": "ImageBatch",
"pos": [
249,
362
],
"size": {
"0": 210,
"1": 46
},
"flags": {},
"order": 3,
"mode": 0,
"inputs": [
{
"name": "image1",
"type": "IMAGE",
"link": 13
},
{
"name": "image2",
"type": "IMAGE",
"link": 12
}
],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [],
"shape": 3,
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "ImageBatch"
}
},
{
"id": 3,
"type": "LoadImage",
"pos": [
-104,
293
],
"size": {
"0": 315,
"1": 314
},
"flags": {},
"order": 2,
"mode": 0,
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
13,
18
],
"shape": 3,
"slot_index": 0
},
{
"name": "MASK",
"type": "MASK",
"links": null,
"shape": 3
}
],
"properties": {
"Node name for S&R": "LoadImage"
},
"widgets_values": [
"S4.jpg",
"image"
]
},
{
"id": 6,
"type": "EFDM",
"pos": [
623,
275
],
"size": {
"0": 315,
"1": 198
},
"flags": {},
"order": 5,
"mode": 0,
"inputs": [
{
"name": "src_img",
"type": "IMAGE",
"link": 9
},
{
"name": "style_img",
"type": "IMAGE",
"link": 16
}
],
"outputs": [
{
"name": "res_img",
"type": "IMAGE",
"links": [
11
],
"shape": 3,
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "EFDM"
},
"widgets_values": [
"0.7, 0.1",
"efdm",
1,
false,
false,
512
]
}
],
"links": [
[
9,
2,
0,
6,
0,
"IMAGE"
],
[
11,
6,
0,
4,
0,
"IMAGE"
],
[
12,
8,
0,
7,
1,
"IMAGE"
],
[
13,
3,
0,
7,
0,
"IMAGE"
],
[
16,
9,
0,
6,
1,
"IMAGE"
],
[
18,
3,
0,
9,
0,
"*"
]
],
"groups": [],
"config": {},
"extra": {
"ds": {
"scale": 1.1000000000000008,
"offset": [
173.09566993240344,
-50.5718089715092
]
}
},
"version": 0.4
}