Spaces:
Running
Running
File size: 8,090 Bytes
5aebe57 |
1 2 3 4 5 6 7 8 9 10 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 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 |
import PIL
import torch
import numpy as np
from PIL import Image
from tqdm import tqdm
import torch.nn.functional as F
import torchvision.transforms as T
from diffusers import LMSDiscreteScheduler, DiffusionPipeline
# configurations
torch_device = "cuda" if torch.cuda.is_available() else "mps" if torch.backends.mps.is_available() else "cpu"
height, width = 512, 512
guidance_scale = 8
loss_scale = 200
num_inference_steps = 50
model_path = "CompVis/stable-diffusion-v1-4"
sd_pipeline = DiffusionPipeline.from_pretrained(
model_path,
low_cpu_mem_usage = True,
torch_dtype=torch.float32
).to(torch_device)
sd_pipeline.load_textual_inversion("sd-concepts-library/illustration-style")
sd_pipeline.load_textual_inversion("sd-concepts-library/line-art")
sd_pipeline.load_textual_inversion("sd-concepts-library/hitokomoru-style-nao")
sd_pipeline.load_textual_inversion("sd-concepts-library/style-of-marc-allante")
sd_pipeline.load_textual_inversion("sd-concepts-library/midjourney-style")
sd_pipeline.load_textual_inversion("sd-concepts-library/hanfu-anime-style")
sd_pipeline.load_textual_inversion("sd-concepts-library/birb-style")
styles_mapping = {
"Illustration Style": '<illustration-style>', "Line Art":'<line-art>',
"Hitokomoru Style":'<hitokomoru-style-nao>', "Marc Allante": '<Marc_Allante>',
"Midjourney":'<midjourney-style>', "Hanfu Anime": '<hanfu-anime-style>',
"Birb Style": '<birb-style>'
}
# Define seeds for all the styles
seed_list = [11, 56, 110, 65, 5, 29, 47]
# Loss Function based on Edge Detection
def edge_detection(image):
channels = image.shape[1]
# Define the kernels for Edge Detection
ed_x = torch.tensor([[-1, 0, 1], [-2, 0, 2], [-1, 0, 1]], dtype=torch.float32).unsqueeze(0).unsqueeze(0)
ed_y = torch.tensor([[1, 2, 1], [0, 0, 0], [-1, -2, -1]], dtype=torch.float32).unsqueeze(0).unsqueeze(0)
# Replicate the Edge detection kernels for each channel
ed_x = ed_x.repeat(channels, 1, 1, 1).to(image.device)
ed_y = ed_y.repeat(channels, 1, 1, 1).to(image.device)
# ed_x = ed_x.to(torch.float16)
# ed_y = ed_y.to(torch.float16)
# Convolve the image with the Edge detection kernels
conv_ed_x = F.conv2d(image, ed_x, padding=1, groups=channels)
conv_ed_y = F.conv2d(image, ed_y, padding=1, groups=channels)
# Combine the x and y gradients after convolution
ed_value = torch.sqrt(conv_ed_x**2 + conv_ed_y**2)
return ed_value
def edge_loss(image):
ed_value = edge_detection(image)
ed_capped = (ed_value > 0.5).to(torch.float32)
return F.mse_loss(ed_value, ed_capped)
def compute_loss(original_image, loss_type):
if loss_type == 'blue':
# blue loss
# [:,2] -> all images in batch, only the blue channel
error = torch.abs(original_image[:,2] - 0.9).mean()
elif loss_type == 'edge':
# edge loss
error = edge_loss(original_image)
elif loss_type == 'contrast':
# RGB to Gray loss
transformed_image = T.functional.adjust_contrast(original_image, contrast_factor = 2)
error = torch.abs(transformed_image - original_image).mean()
elif loss_type == 'brightness':
# brightnesss loss
transformed_image = T.functional.adjust_brightness(original_image, brightness_factor = 2)
error = torch.abs(transformed_image - original_image).mean()
elif loss_type == 'sharpness':
# sharpness loss
transformed_image = T.functional.adjust_sharpness(original_image, sharpness_factor = 2)
error = torch.abs(transformed_image - original_image).mean()
elif loss_type == 'saturation':
# saturation loss
transformed_image = T.functional.adjust_saturation(original_image, saturation_factor = 10)
error = torch.abs(transformed_image - original_image).mean()
else:
print("error. Loss not defined")
return error
def get_examples():
examples = [
['A bird sitting on a tree', 'Midjourney', 'edge', 5],
['Cats fighting on the road', 'Marc Allante', 'brightness', 65],
['A mouse with the head of a puppy', 'Hitokomoru Style', 'contrast', 110],
['A woman with a smiling face in front of an Italian Pizza', 'Hanfu Anime', 'brightness', 29],
['A campfire (oil on canvas)', 'Birb Style', 'blue', 47],
]
return(examples)
def latents_to_pil(latents):
# bath of latents -> list of images
latents = (1 / 0.18215) * latents
with torch.no_grad():
image = sd_pipeline.vae.decode(latents).sample
image = (image / 2 + 0.5).clamp(0, 1) # 0 to 1
image = image.detach().cpu().permute(0, 2, 3, 1).numpy()
image = (image * 255).round().astype("uint8")
return Image.fromarray(image[0])
def show_image(prompt, concept, guidance_type, seed):
prompt = f"{prompt} in the style of {styles_mapping[concept]}"
styled_image_without_loss = latents_to_pil(generate_image(seed, prompt, guidance_type, loss_flag=False))
styled_image_with_loss = latents_to_pil(generate_image(seed, prompt, guidance_type, loss_flag=True))
return([styled_image_without_loss, styled_image_with_loss])
def generate_image(seed, prompt, loss_type, loss_flag=False):
generator = torch.manual_seed(seed)
batch_size = 1
# scheduler
scheduler = LMSDiscreteScheduler(beta_start = 0.00085, beta_end = 0.012, beta_schedule = "scaled_linear", num_train_timesteps = 1000)
scheduler.set_timesteps(num_inference_steps)
scheduler.timesteps = scheduler.timesteps.to(torch.float32)
# text embeddings of the prompt
text_input = sd_pipeline.tokenizer(prompt, padding='max_length', max_length = sd_pipeline.tokenizer.model_max_length, truncation= True, return_tensors="pt")
input_ids = text_input.input_ids.to(torch_device)
with torch.no_grad():
text_embeddings = sd_pipeline.text_encoder(text_input.input_ids.to(torch_device))[0]
max_length = text_input.input_ids.shape[-1]
uncond_input = sd_pipeline.tokenizer(
[""] * batch_size, padding="max_length", max_length= max_length, return_tensors="pt"
)
with torch.no_grad():
uncond_embeddings = sd_pipeline.text_encoder(uncond_input.input_ids.to(torch_device))[0]
text_embeddings = torch.cat([uncond_embeddings,text_embeddings]) # shape: 2,77,768
# random latent
latents = torch.randn(
(batch_size, sd_pipeline.unet.config.in_channels, height// 8, width //8),
generator = generator,
) .to(torch.float32)
latents = latents.to(torch_device)
latents = latents * scheduler.init_noise_sigma
for i, t in tqdm(enumerate(scheduler.timesteps), total = len(scheduler.timesteps)):
latent_model_input = torch.cat([latents] * 2)
sigma = scheduler.sigmas[i]
latent_model_input = scheduler.scale_model_input(latent_model_input, t)
with torch.no_grad():
noise_pred = sd_pipeline.unet(latent_model_input.to(torch.float32), t, encoder_hidden_states=text_embeddings)["sample"]
noise_pred_uncond, noise_pred_text = noise_pred.chunk(2)
noise_pred = noise_pred_uncond + guidance_scale * (noise_pred_text - noise_pred_uncond)
if loss_flag and i%5 == 0:
latents = latents.detach().requires_grad_()
# the following line alone does not work, it requires change to reduce step only once
# hence commenting it out
#latents_x0 = scheduler.step(noise_pred,t, latents).pred_original_sample
latents_x0 = latents - sigma * noise_pred
# use vae to decode the image
denoised_images = sd_pipeline.vae.decode((1/ 0.18215) * latents_x0).sample / 2 + 0.5 # range(0,1)
loss = compute_loss(denoised_images, loss_type) * loss_scale
#loss = loss.to(torch.float16)
print(f"{i} loss {loss}")
cond_grad = torch.autograd.grad(loss, latents)[0]
latents = latents.detach() - cond_grad * sigma**2
latents = scheduler.step(noise_pred,t, latents).prev_sample
return latents |