EFDM (#1)
This commit is contained in:
@@ -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 |
|
||||
|:-:|:-:|-|
|
||||
| |  | X |
|
||||
| Neural Neighbor (2020-2022) - Slowest | CAST (2022-2023) - Fast |
|
||||
|:-:|:-:|
|
||||
| |  |
|
||||
| **EFDM (2022) - Fast but OOM on larger resolutions** | X |
|
||||
|  | X |
|
||||
|
||||
### Neural Neighbor
|
||||
|
||||
@@ -60,6 +64,29 @@ models\
|
||||
|
||||

|
||||
|
||||
### 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)
|
||||
|
||||

|
||||
|
||||
## 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
@@ -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
@@ -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 |
@@ -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
|
||||
|
||||
@@ -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)
|
||||
@@ -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())
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
@@ -1,2 +1,3 @@
|
||||
torch
|
||||
torchvision
|
||||
torchvision
|
||||
scikit-image
|
||||
@@ -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 |
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user