Spaces:
Sleeping
Sleeping
gradio-fix (#2)
Browse files- gradio update (ff63d1bb8456f8b2612f9096bc239f48d3220790)
- .gitignore +3 -0
- app.py +44 -40
- clip_styler.py +159 -205
- util.py +1 -2
.gitignore
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
.venv/
|
| 2 |
+
.gradio/
|
| 3 |
+
__pycache__/
|
app.py
CHANGED
|
@@ -4,39 +4,22 @@ import clip
|
|
| 4 |
from torchvision import transforms
|
| 5 |
from style_net import StyleNet
|
| 6 |
from dataset import FlickrStreamer
|
| 7 |
-
from
|
| 8 |
-
from clip_styler import ClipStyler
|
| 9 |
|
| 10 |
# --- CONFIGURATION ---
|
| 11 |
-
|
| 12 |
INDEX_PATH = "flickr_embeddings.pt"
|
| 13 |
SEARCH_LIMIT = 5000 # Must match the indexer limit!
|
| 14 |
# ---------------------
|
| 15 |
|
| 16 |
-
UI_EPOCHS = 100
|
| 17 |
-
STYLE_PRESETS = {
|
| 18 |
-
"Sketch": {
|
| 19 |
-
"prompt": "charcoal sketch style",
|
| 20 |
-
"source": "a Photo",
|
| 21 |
-
},
|
| 22 |
-
"Van Gogh": {
|
| 23 |
-
"prompt": "Van Gogh painting style",
|
| 24 |
-
"source": "a Photo",
|
| 25 |
-
},
|
| 26 |
-
"Cyberpunk": {
|
| 27 |
-
"prompt": "neon cyberpunk style",
|
| 28 |
-
"source": "a Photo",
|
| 29 |
-
},
|
| 30 |
-
}
|
| 31 |
-
|
| 32 |
print(f"System starting on {DEVICE}...")
|
| 33 |
|
| 34 |
# 1. LOAD MODELS
|
| 35 |
-
# A.
|
| 36 |
-
|
| 37 |
|
| 38 |
-
# B.
|
| 39 |
-
|
| 40 |
|
| 41 |
# 2. LOAD SEARCH INDEX
|
| 42 |
print("Loading Search Index...")
|
|
@@ -78,25 +61,39 @@ def search_flickr(text_query):
|
|
| 78 |
|
| 79 |
return results
|
| 80 |
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
if preset is None:
|
| 87 |
-
return image
|
| 88 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 89 |
try:
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
prompt=preset["prompt"],
|
| 93 |
-
source=preset.get("source", "a Photo"),
|
| 94 |
-
num_epochs=UI_EPOCHS,
|
| 95 |
-
log_interval=0,
|
| 96 |
-
)
|
| 97 |
-
except Exception as exc:
|
| 98 |
-
print(f"[edit_image] Failed to stylize image: {exc}")
|
| 99 |
return image
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 100 |
|
| 101 |
# --- THE UI ---
|
| 102 |
with gr.Blocks() as demo:
|
|
@@ -118,11 +115,18 @@ with gr.Blocks() as demo:
|
|
| 118 |
with gr.Row():
|
| 119 |
im_in = gr.Image(type="pil", label="Input Image")
|
| 120 |
style = gr.Dropdown(["Sketch", "Van Gogh", "Cyberpunk"], label="Choose Style")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 121 |
|
| 122 |
btn_edit = gr.Button("Apply")
|
| 123 |
im_out = gr.Image(label="Result")
|
| 124 |
|
| 125 |
-
btn_edit.click(edit_image, inputs=[im_in, style], outputs=im_out)
|
| 126 |
|
| 127 |
if __name__ == "__main__":
|
| 128 |
demo.launch(share=True)
|
|
|
|
| 4 |
from torchvision import transforms
|
| 5 |
from style_net import StyleNet
|
| 6 |
from dataset import FlickrStreamer
|
| 7 |
+
from clip_styler import style_transfer
|
|
|
|
| 8 |
|
| 9 |
# --- CONFIGURATION ---
|
| 10 |
+
DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
|
| 11 |
INDEX_PATH = "flickr_embeddings.pt"
|
| 12 |
SEARCH_LIMIT = 5000 # Must match the indexer limit!
|
| 13 |
# ---------------------
|
| 14 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 15 |
print(f"System starting on {DEVICE}...")
|
| 16 |
|
| 17 |
# 1. LOAD MODELS
|
| 18 |
+
# A. Editing Model
|
| 19 |
+
#style_net = StyleNet().to(DEVICE)
|
| 20 |
|
| 21 |
+
# B. Retrieval Model (CLIP)
|
| 22 |
+
#clip_model, preprocess = clip.load("ViT-B/32", device=DEVICE)
|
| 23 |
|
| 24 |
# 2. LOAD SEARCH INDEX
|
| 25 |
print("Loading Search Index...")
|
|
|
|
| 61 |
|
| 62 |
return results
|
| 63 |
|
| 64 |
+
# --- FUNCTION: EDIT ---
|
| 65 |
+
tf_edit = transforms.Compose([
|
| 66 |
+
transforms.Resize((256, 256)),
|
| 67 |
+
transforms.ToTensor()
|
| 68 |
+
])
|
|
|
|
|
|
|
| 69 |
|
| 70 |
+
def edit_image(image, style_choice, num_steps):
|
| 71 |
+
if image is None: return None
|
| 72 |
+
|
| 73 |
+
weights_map = {
|
| 74 |
+
"Sketch": "model_sketch.pth",
|
| 75 |
+
"Van Gogh": "model_painting.pth",
|
| 76 |
+
"Cyberpunk": "model_cyberpunk.pth"
|
| 77 |
+
}
|
| 78 |
+
detail_prompt = {
|
| 79 |
+
"Sketch": "charcoal sketch",
|
| 80 |
+
"Van Gogh": "Van Gogh painting",
|
| 81 |
+
"Cyberpunk": "neon cyberpunk",
|
| 82 |
+
}
|
| 83 |
+
'''
|
| 84 |
try:
|
| 85 |
+
style_net.load_state_dict(torch.load(weights_map.get(style_choice), map_location=DEVICE))
|
| 86 |
+
except Exception:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 87 |
return image
|
| 88 |
+
|
| 89 |
+
input_tensor = tf_edit(image).unsqueeze(0).to(DEVICE)
|
| 90 |
+
with torch.no_grad():
|
| 91 |
+
output_tensor = style_net(input_tensor)
|
| 92 |
+
|
| 93 |
+
output_img = transforms.ToPILImage()(output_tensor.squeeze().cpu())
|
| 94 |
+
return output_img
|
| 95 |
+
'''
|
| 96 |
+
return style_transfer(image, detail_prompt[style_choice], num_steps=num_steps)
|
| 97 |
|
| 98 |
# --- THE UI ---
|
| 99 |
with gr.Blocks() as demo:
|
|
|
|
| 115 |
with gr.Row():
|
| 116 |
im_in = gr.Image(type="pil", label="Input Image")
|
| 117 |
style = gr.Dropdown(["Sketch", "Van Gogh", "Cyberpunk"], label="Choose Style")
|
| 118 |
+
steps_slider = gr.Slider(
|
| 119 |
+
minimum=50,
|
| 120 |
+
maximum=200,
|
| 121 |
+
value=100,
|
| 122 |
+
step=10,
|
| 123 |
+
label="Number of Steps (more = better quality but slower)"
|
| 124 |
+
)
|
| 125 |
|
| 126 |
btn_edit = gr.Button("Apply")
|
| 127 |
im_out = gr.Image(label="Result")
|
| 128 |
|
| 129 |
+
btn_edit.click(edit_image, inputs=[im_in, style, steps_slider], outputs=im_out)
|
| 130 |
|
| 131 |
if __name__ == "__main__":
|
| 132 |
demo.launch(share=True)
|
clip_styler.py
CHANGED
|
@@ -1,212 +1,166 @@
|
|
| 1 |
-
from __future__ import annotations
|
| 2 |
-
|
| 3 |
-
from typing import Optional
|
| 4 |
-
|
| 5 |
-
from PIL import Image
|
| 6 |
import torch
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
from
|
|
|
|
| 10 |
from transformers import CLIPModel, AutoTokenizer
|
|
|
|
| 11 |
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 35 |
"""
|
| 36 |
-
|
| 37 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 38 |
"""
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
)
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
image: Image.Image,
|
| 79 |
-
prompt: str,
|
| 80 |
-
source: str = "a Photo",
|
| 81 |
-
num_epochs: Optional[int] = None,
|
| 82 |
-
log_interval: int = 20,
|
| 83 |
-
) -> Image.Image:
|
| 84 |
-
"""
|
| 85 |
-
Run the CLIP-guided optimization loop and return the stylized PIL image.
|
| 86 |
-
"""
|
| 87 |
-
if image is None:
|
| 88 |
-
raise ValueError("Image must be provided for stylization.")
|
| 89 |
-
|
| 90 |
-
epochs = num_epochs if num_epochs is not None else self.num_epochs
|
| 91 |
-
base_img = self._pil_to_tensor(image)
|
| 92 |
-
content_features = get_features(normalize(base_img), self.vgg19)
|
| 93 |
-
|
| 94 |
-
cnn_model = UNet(text_dim=512).to(self.device)
|
| 95 |
-
cnn_model.train()
|
| 96 |
-
|
| 97 |
-
optimizer = optim.Adam(cnn_model.parameters(), lr=LR)
|
| 98 |
-
scheduler = torch.optim.lr_scheduler.StepLR(
|
| 99 |
-
optimizer, step_size=100, gamma=0.5
|
| 100 |
-
)
|
| 101 |
-
|
| 102 |
-
with torch.no_grad():
|
| 103 |
-
text_features = self._encode_text(prompt)
|
| 104 |
-
source_features = self._encode_text(source)
|
| 105 |
-
base_img_features = self._image_features(base_img)
|
| 106 |
-
|
| 107 |
-
style_direction = text_features - source_features
|
| 108 |
-
style_direction /= style_direction.norm(dim=-1, keepdim=True)
|
| 109 |
-
|
| 110 |
-
final_target = base_img
|
| 111 |
-
for epoch in range(epochs + 1):
|
| 112 |
-
scheduler.step()
|
| 113 |
-
target = cnn_model(base_img, text_embedding=text_features)
|
| 114 |
-
final_target = target
|
| 115 |
-
|
| 116 |
-
target_features = get_features(normalize(target), self.vgg19)
|
| 117 |
-
content_loss = torch.mean(
|
| 118 |
-
(target_features["conv4_2"] - content_features["conv4_2"]) ** 2
|
| 119 |
-
)
|
| 120 |
-
content_loss += torch.mean(
|
| 121 |
-
(target_features["conv5_2"] - content_features["conv5_2"]) ** 2
|
| 122 |
-
)
|
| 123 |
-
|
| 124 |
-
img_aug = self._augment_crops(target)
|
| 125 |
-
img_aug_features = self.clip_model.get_image_features(
|
| 126 |
-
pixel_values=clip_normalize(img_aug)
|
| 127 |
-
)
|
| 128 |
-
img_aug_features /= img_aug_features.norm(dim=-1, keepdim=True)
|
| 129 |
-
|
| 130 |
-
img_direction = img_aug_features - base_img_features
|
| 131 |
-
img_direction /= img_direction.norm(dim=-1, keepdim=True)
|
| 132 |
-
|
| 133 |
-
patch_text_direction = style_direction.repeat(img_direction.size(0), 1)
|
| 134 |
-
patch_text_direction /= patch_text_direction.norm(dim=-1, keepdim=True)
|
| 135 |
-
|
| 136 |
-
loss_patch = 1 - torch.cosine_similarity(
|
| 137 |
-
img_direction, patch_text_direction, dim=1
|
| 138 |
-
)
|
| 139 |
-
loss_patch = torch.where(
|
| 140 |
-
loss_patch < PATCH_THRESHOLD, torch.zeros_like(loss_patch), loss_patch
|
| 141 |
-
).mean()
|
| 142 |
-
|
| 143 |
-
glob_features = self.clip_model.get_image_features(
|
| 144 |
-
pixel_values=clip_normalize(target)
|
| 145 |
-
)
|
| 146 |
-
glob_features /= glob_features.norm(dim=-1, keepdim=True)
|
| 147 |
-
|
| 148 |
-
glob_direction = glob_features - base_img_features
|
| 149 |
-
glob_direction /= glob_direction.norm(dim=-1, keepdim=True)
|
| 150 |
-
|
| 151 |
-
loss_glob = (1 - torch.cosine_similarity(glob_direction, style_direction, dim=1)).mean()
|
| 152 |
-
|
| 153 |
-
loss_tv = L_TV * get_image_prior_losses(target)
|
| 154 |
-
|
| 155 |
-
total_loss = (
|
| 156 |
-
(L_PATCH * loss_patch)
|
| 157 |
-
+ (L_DIR * loss_glob)
|
| 158 |
-
+ (L_CONTENT * content_loss)
|
| 159 |
-
+ loss_tv
|
| 160 |
-
)
|
| 161 |
-
|
| 162 |
-
optimizer.zero_grad()
|
| 163 |
-
total_loss.backward()
|
| 164 |
-
optimizer.step()
|
| 165 |
-
|
| 166 |
-
'''
|
| 167 |
-
if log_interval and epoch % log_interval == 0:
|
| 168 |
-
print(
|
| 169 |
-
f"[ClipStyler] Epoch {epoch}: total={total_loss.item():.4f}, "
|
| 170 |
-
f"content={content_loss.item():.4f}, patch={loss_patch.item():.4f}, "
|
| 171 |
-
f"dir={loss_glob.item():.4f}, tv={loss_tv.item():.4f}"
|
| 172 |
-
)
|
| 173 |
-
'''
|
| 174 |
-
|
| 175 |
-
return self._tensor_to_pil(final_target)
|
| 176 |
-
|
| 177 |
-
def _pil_to_tensor(self, image: Image.Image) -> torch.Tensor:
|
| 178 |
-
if image.mode != "RGB":
|
| 179 |
-
image = image.convert("RGB")
|
| 180 |
-
tensor = self.image_transform(image).unsqueeze(0)
|
| 181 |
-
return tensor.to(self.device)
|
| 182 |
-
|
| 183 |
-
def _tensor_to_pil(self, tensor: torch.Tensor) -> Image.Image:
|
| 184 |
-
tensor = tensor.detach().clone().cpu().clamp(0, 1).squeeze(0)
|
| 185 |
-
tensor = adjust_contrast(tensor, 1.5)
|
| 186 |
-
return self.to_pil(tensor)
|
| 187 |
-
|
| 188 |
-
def _augment_crops(self, target: torch.Tensor) -> torch.Tensor:
|
| 189 |
-
crops = []
|
| 190 |
-
for _ in range(NUM_CROPS):
|
| 191 |
-
crop = self.crop_transform(target)
|
| 192 |
-
crop = self.augment_transform(crop)
|
| 193 |
-
crops.append(crop)
|
| 194 |
-
return torch.cat(crops, dim=0)
|
| 195 |
-
|
| 196 |
-
@torch.no_grad()
|
| 197 |
-
def _encode_text(self, text: str) -> torch.Tensor:
|
| 198 |
-
prompts = prompt_ensemble(text)
|
| 199 |
-
tokens = self.tokenizer(prompts, padding=True, return_tensors="pt").to(
|
| 200 |
-
self.device
|
| 201 |
-
)
|
| 202 |
-
text_features = self.clip_model.get_text_features(**tokens)
|
| 203 |
-
text_features = text_features.mean(dim=0, keepdim=True)
|
| 204 |
text_features /= text_features.norm(dim=-1, keepdim=True)
|
| 205 |
-
return text_features
|
| 206 |
-
|
| 207 |
-
@torch.no_grad()
|
| 208 |
-
def _image_features(self, tensor: torch.Tensor) -> torch.Tensor:
|
| 209 |
-
feats = self.clip_model.get_image_features(pixel_values=clip_normalize(tensor))
|
| 210 |
-
feats /= feats.norm(dim=-1, keepdim=True)
|
| 211 |
-
return feats
|
| 212 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
import torch
|
| 2 |
+
from PIL import Image
|
| 3 |
+
import torchvision.transforms as transforms
|
| 4 |
+
from util import *
|
| 5 |
+
from cnn1 import UNet
|
| 6 |
from transformers import CLIPModel, AutoTokenizer
|
| 7 |
+
from torchvision.transforms.functional import adjust_contrast
|
| 8 |
|
| 9 |
+
clip_model = None
|
| 10 |
+
tokenizer = None
|
| 11 |
+
vgg19 = None
|
| 12 |
+
cnn_model = None
|
| 13 |
+
|
| 14 |
+
def load_models():
|
| 15 |
+
"""Load models once and cache them"""
|
| 16 |
+
global clip_model, tokenizer, vgg19, cnn_model
|
| 17 |
+
|
| 18 |
+
if clip_model is None:
|
| 19 |
+
clip_model = CLIPModel.from_pretrained("openai/clip-vit-base-patch32").to(DEVICE)
|
| 20 |
+
|
| 21 |
+
if tokenizer is None:
|
| 22 |
+
tokenizer = AutoTokenizer.from_pretrained("openai/clip-vit-base-patch32")
|
| 23 |
+
|
| 24 |
+
if vgg19 is None:
|
| 25 |
+
vgg19 = models.vgg19(pretrained=True).features.to(DEVICE)
|
| 26 |
+
for param in vgg19.parameters():
|
| 27 |
+
param.requires_grad_(False)
|
| 28 |
+
|
| 29 |
+
if cnn_model is None:
|
| 30 |
+
cnn_model = UNet(text_dim=512).to(DEVICE)
|
| 31 |
+
|
| 32 |
+
return clip_model, tokenizer, vgg19, cnn_model
|
| 33 |
+
|
| 34 |
+
def reset_cnn_model():
|
| 35 |
+
"""Reset CNN model weights for each new image"""
|
| 36 |
+
global cnn_model
|
| 37 |
+
if cnn_model is not None:
|
| 38 |
+
# Reinitialize the model
|
| 39 |
+
for layer in cnn_model.modules():
|
| 40 |
+
if isinstance(layer, (torch.nn.Conv2d, torch.nn.Linear)):
|
| 41 |
+
torch.nn.init.xavier_uniform_(layer.weight)
|
| 42 |
+
if layer.bias is not None:
|
| 43 |
+
torch.nn.init.zeros_(layer.bias)
|
| 44 |
+
|
| 45 |
+
def style_transfer(img, prompt, num_steps=NUM_EPOCHS, source="a Photo"):
|
| 46 |
"""
|
| 47 |
+
Apply style transfer to an uploaded image
|
| 48 |
+
|
| 49 |
+
Args:
|
| 50 |
+
image: PIL Image uploaded by user
|
| 51 |
+
prompt: Text style prompt
|
| 52 |
+
num_steps: Number of optimization steps (fewer = faster, more = better quality)
|
| 53 |
+
|
| 54 |
+
Returns:
|
| 55 |
+
Stylized PIL Image
|
| 56 |
"""
|
| 57 |
+
if img is None:
|
| 58 |
+
return None
|
| 59 |
+
|
| 60 |
+
img = load_image(img).to(DEVICE)
|
| 61 |
+
|
| 62 |
+
# Load models
|
| 63 |
+
clip_model, tokenizer, vgg19, cnn_model = load_models()
|
| 64 |
+
|
| 65 |
+
# Reset CNN model for fresh start on each image
|
| 66 |
+
reset_cnn_model()
|
| 67 |
+
|
| 68 |
+
# image features
|
| 69 |
+
features = get_features(normalize(img), vgg19)
|
| 70 |
+
|
| 71 |
+
# cnn model
|
| 72 |
+
#cnn_model = UNet().to(DEVICE)
|
| 73 |
+
|
| 74 |
+
# load ADAM
|
| 75 |
+
optimizer = optim.Adam(cnn_model.parameters(), lr=LR)
|
| 76 |
+
scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=100, gamma=0.5)
|
| 77 |
+
|
| 78 |
+
# data augmentation
|
| 79 |
+
crop = transforms.Compose([transforms.RandomCrop(CROP_SIZE)])
|
| 80 |
+
augment = transforms.Compose([transforms.RandomPerspective(fill=0, p=1, distortion_scale=0.5), transforms.Resize(RESIZE)])
|
| 81 |
+
|
| 82 |
+
# initialize variables
|
| 83 |
+
content_loss_epoch = []
|
| 84 |
+
style_loss_epoch = []
|
| 85 |
+
total_loss_epoch = []
|
| 86 |
+
output_image = img
|
| 87 |
+
mean_img = torch.mean(img, dim=(2, 3), keepdim=False).squeeze(0)
|
| 88 |
+
mean_img = [mean_img[0].item(), mean_img[1].item(), mean_img[2].item()]
|
| 89 |
+
target = img.clone().requires_grad_(True).to(DEVICE)
|
| 90 |
+
|
| 91 |
+
with torch.no_grad():
|
| 92 |
+
edited_text = prompt_ensemble(prompt)
|
| 93 |
+
text_tokens = tokenizer(edited_text, padding=True, return_tensors="pt").to(DEVICE)
|
| 94 |
+
text_features = clip_model.get_text_features(**text_tokens)
|
| 95 |
+
text_features = text_features.mean(axis=0, keepdim=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 96 |
text_features /= text_features.norm(dim=-1, keepdim=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 97 |
|
| 98 |
+
edited_source = prompt_ensemble(source)
|
| 99 |
+
source_tokens = tokenizer(edited_source, padding=True, return_tensors="pt").to(DEVICE)
|
| 100 |
+
source_features = clip_model.get_text_features(**source_tokens)
|
| 101 |
+
source_features = source_features.mean(axis=0, keepdim=True)
|
| 102 |
+
source_features /= source_features.norm(dim=-1, keepdim=True)
|
| 103 |
+
|
| 104 |
+
img_features = clip_model.get_image_features(pixel_values=clip_normalize(img))
|
| 105 |
+
img_features /= (img_features.clone().norm(dim=-1, keepdim=True))
|
| 106 |
+
|
| 107 |
+
for epoch in range(num_steps + 1):
|
| 108 |
+
scheduler.step()
|
| 109 |
+
#target = cnn_model(img).requires_grad_(True).to(DEVICE)
|
| 110 |
+
target = cnn_model(img, text_embedding=text_features).to(DEVICE)
|
| 111 |
+
|
| 112 |
+
target_features = get_features(normalize(target), vgg19)
|
| 113 |
+
|
| 114 |
+
content_loss = 0.0
|
| 115 |
+
content_loss += torch.mean((target_features['conv4_2'] - features['conv4_2']) ** 2)
|
| 116 |
+
content_loss += torch.mean((target_features['conv5_2'] - features['conv5_2']) ** 2)
|
| 117 |
+
|
| 118 |
+
loss_patch = 0
|
| 119 |
+
img_proc = []
|
| 120 |
+
for n in range(NUM_CROPS):
|
| 121 |
+
target_crop = crop(target)
|
| 122 |
+
target_crop = augment(target_crop)
|
| 123 |
+
img_proc.append(target_crop)
|
| 124 |
+
|
| 125 |
+
img_proc = torch.cat(img_proc, dim=0)
|
| 126 |
+
img_aug = img_proc
|
| 127 |
+
|
| 128 |
+
img_aug_features = clip_model.get_image_features(pixel_values=clip_normalize(img_aug))
|
| 129 |
+
img_aug_features /= (img_aug_features.clone().norm(dim=-1, keepdim=True))
|
| 130 |
+
|
| 131 |
+
img_direction = img_aug_features - img_features
|
| 132 |
+
img_direction /= img_direction.clone().norm(dim=-1, keepdim=True)
|
| 133 |
+
|
| 134 |
+
text_direction = (text_features - source_features).repeat(img_aug_features.size(0), 1)
|
| 135 |
+
text_direction /= text_direction.norm(dim=-1, keepdim=True)
|
| 136 |
+
|
| 137 |
+
loss_calc = (1 - torch.cosine_similarity(img_direction, text_direction, dim=1))
|
| 138 |
+
loss_calc[loss_calc < PATCH_THRESHOLD] = 0.0
|
| 139 |
+
loss_patch += loss_calc.mean()
|
| 140 |
+
|
| 141 |
+
glob_features = clip_model.get_image_features(pixel_values=clip_normalize(target))
|
| 142 |
+
glob_features /= (glob_features.clone().norm(dim=-1, keepdim=True))
|
| 143 |
+
glob_direction = glob_features - img_features
|
| 144 |
+
glob_direction /= glob_direction.clone().norm(dim=-1, keepdim=True)
|
| 145 |
+
|
| 146 |
+
loss_glob = (1 - torch.cosine_similarity(glob_direction, text_direction, dim=1)).mean()
|
| 147 |
+
|
| 148 |
+
loss_tv = L_TV * get_image_prior_losses(target)
|
| 149 |
+
|
| 150 |
+
total_loss = (L_PATCH * loss_patch) + (L_DIR * loss_glob) + (L_CONTENT * content_loss) + loss_tv
|
| 151 |
+
total_loss_epoch.append(total_loss)
|
| 152 |
+
|
| 153 |
+
optimizer.zero_grad()
|
| 154 |
+
total_loss.backward()
|
| 155 |
+
optimizer.step()
|
| 156 |
+
|
| 157 |
+
# Post-process output
|
| 158 |
+
output_image = target.clone().detach()
|
| 159 |
+
output_image = torch.clamp(output_image, 0, 1)
|
| 160 |
+
output_image = adjust_contrast(output_image, 1.5)
|
| 161 |
+
|
| 162 |
+
# Convert tensor back to PIL Image
|
| 163 |
+
output_image = output_image.squeeze(0).cpu()
|
| 164 |
+
output_image = transforms.ToPILImage()(output_image)
|
| 165 |
+
|
| 166 |
+
return output_image
|
util.py
CHANGED
|
@@ -129,8 +129,7 @@ def clip_normalize(data):
|
|
| 129 |
norm_data = (resized - mean) / std
|
| 130 |
return norm_data
|
| 131 |
|
| 132 |
-
def load_image(
|
| 133 |
-
image = Image.open(img_path)
|
| 134 |
image = image.resize((IMG_SIZE, IMG_SIZE))
|
| 135 |
transform = transforms.Compose([transforms.ToTensor()])
|
| 136 |
return transform(image)[:3, :, :].unsqueeze(0)
|
|
|
|
| 129 |
norm_data = (resized - mean) / std
|
| 130 |
return norm_data
|
| 131 |
|
| 132 |
+
def load_image(image):
|
|
|
|
| 133 |
image = image.resize((IMG_SIZE, IMG_SIZE))
|
| 134 |
transform = transforms.Compose([transforms.ToTensor()])
|
| 135 |
return transform(image)[:3, :, :].unsqueeze(0)
|