Upload 2 files
Browse files
app.py
ADDED
@@ -0,0 +1,140 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from __future__ import print_function
|
2 |
+
import torch
|
3 |
+
import torchvision
|
4 |
+
import torch.nn as nn
|
5 |
+
import gradio as gr
|
6 |
+
import os
|
7 |
+
import time
|
8 |
+
import torch.nn.functional as F
|
9 |
+
import torch.optim as optim
|
10 |
+
import matplotlib.pyplot as plt
|
11 |
+
import torchvision.transforms as transforms
|
12 |
+
import copy
|
13 |
+
import torchvision.models as models
|
14 |
+
import torchvision.transforms.functional as TF
|
15 |
+
from PIL import Image
|
16 |
+
import numpy as np
|
17 |
+
from model import ContentLoss, gram_matrix, StyleLoss, image_transform, get_input_optimizer,get_style_model_and_losses
|
18 |
+
|
19 |
+
|
20 |
+
|
21 |
+
|
22 |
+
|
23 |
+
## style_transfer
|
24 |
+
import numpy as np
|
25 |
+
def run_style_transfer(cnn, normalization_mean, normalization_std,
|
26 |
+
content_img, style_img, input_img, num_steps=300,
|
27 |
+
style_weight=1000000, content_weight=1):
|
28 |
+
"""Run the style transfer."""
|
29 |
+
print('Building the style transfer model..')
|
30 |
+
model, style_losses, content_losses = get_style_model_and_losses(cnn,
|
31 |
+
normalization_mean, normalization_std, style_img, content_img)
|
32 |
+
|
33 |
+
# We want to optimize the input and not the model parameters so we
|
34 |
+
# update all the requires_grad fields accordingly
|
35 |
+
input_img.requires_grad_(True)
|
36 |
+
model.requires_grad_(False)
|
37 |
+
|
38 |
+
optimizer = get_input_optimizer(input_img)
|
39 |
+
|
40 |
+
print('Optimizing..')
|
41 |
+
run = [0]
|
42 |
+
while run[0] <= num_steps:
|
43 |
+
|
44 |
+
def closure():
|
45 |
+
# correct the values of updated input image
|
46 |
+
with torch.no_grad():
|
47 |
+
input_img.clamp_(0, 1)
|
48 |
+
|
49 |
+
optimizer.zero_grad()
|
50 |
+
model(input_img)
|
51 |
+
style_score = 0
|
52 |
+
content_score = 0
|
53 |
+
|
54 |
+
for sl in style_losses:
|
55 |
+
style_score += sl.loss
|
56 |
+
for cl in content_losses:
|
57 |
+
content_score += cl.loss
|
58 |
+
|
59 |
+
style_score *= style_weight
|
60 |
+
content_score *= content_weight
|
61 |
+
|
62 |
+
loss = style_score + content_score
|
63 |
+
loss.backward()
|
64 |
+
|
65 |
+
run[0] += 1
|
66 |
+
if run[0] % 50 == 0:
|
67 |
+
print("run {}:".format(run))
|
68 |
+
print('Style Loss : {:4f} Content Loss: {:4f}'.format(
|
69 |
+
style_score.item(), content_score.item()))
|
70 |
+
print()
|
71 |
+
|
72 |
+
return style_score + content_score
|
73 |
+
|
74 |
+
optimizer.step(closure)
|
75 |
+
|
76 |
+
# a last correction...
|
77 |
+
with torch.no_grad():
|
78 |
+
input_img.clamp_(0, 1)
|
79 |
+
|
80 |
+
# Convert output tensor to a NumPy array
|
81 |
+
output_np = input_img.detach().cpu().numpy()[0].transpose(1, 2, 0)
|
82 |
+
|
83 |
+
# Convert NumPy array to PIL Image object
|
84 |
+
output_img = Image.fromarray((output_np * 255).astype(np.uint8))
|
85 |
+
|
86 |
+
return output_img
|
87 |
+
|
88 |
+
|
89 |
+
#Defining the predict function
|
90 |
+
def style_transfer(cont_img,styl_img):
|
91 |
+
|
92 |
+
#Start the timer
|
93 |
+
start_time = time.time()
|
94 |
+
|
95 |
+
|
96 |
+
#transform the input image
|
97 |
+
style_img = image_transform(styl_img)
|
98 |
+
content_img =image_transform(cont_img)
|
99 |
+
|
100 |
+
#getting input image
|
101 |
+
input_img = content_img.clone()
|
102 |
+
|
103 |
+
#running the style transfer
|
104 |
+
output = run_style_transfer(cnn, cnn_normalization_mean, cnn_normalization_std,
|
105 |
+
content_img, style_img, input_img)
|
106 |
+
# output_img = output.detach().cpu().squeeze(0)
|
107 |
+
# output_img = TF.to_pil_image(output_img)
|
108 |
+
end_time=time.time()
|
109 |
+
|
110 |
+
pred_time =round(end_time- start_time, 5)
|
111 |
+
|
112 |
+
return output
|
113 |
+
|
114 |
+
##Gradio App
|
115 |
+
import gradio as gr
|
116 |
+
title= 'Style Transfer'
|
117 |
+
description='A model to transfer the style of one image to another'
|
118 |
+
article = 'Created at Pytorch Model Deployment'
|
119 |
+
|
120 |
+
#example_images
|
121 |
+
example_list = [["examples/" + example] for example in os.listdir("examples")]
|
122 |
+
|
123 |
+
#Create the gradio demo
|
124 |
+
demo = gr.Interface(
|
125 |
+
fn=style_transfer,
|
126 |
+
inputs=[
|
127 |
+
gr.inputs.Image(label="content Image",type='pil'),
|
128 |
+
gr.inputs.Image(label="style_image",type='pil')
|
129 |
+
],
|
130 |
+
examples=example_list,
|
131 |
+
outputs="image",
|
132 |
+
allow_flagging=False,
|
133 |
+
title=title,
|
134 |
+
description=description,
|
135 |
+
article=article
|
136 |
+
)
|
137 |
+
|
138 |
+
# Launch the Gradio interface
|
139 |
+
demo.launch(debug=True)
|
140 |
+
|
model.py
ADDED
@@ -0,0 +1,135 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
|
2 |
+
#Content Loss
|
3 |
+
class ContentLoss(nn.Module):
|
4 |
+
|
5 |
+
def __init__(self, target,):
|
6 |
+
super(ContentLoss, self).__init__()
|
7 |
+
'''
|
8 |
+
we 'detach' the target content from the tree used
|
9 |
+
to dynamically compute the gradient: this is a stated value,
|
10 |
+
not a variable. Otherwise the forward method of the criterion
|
11 |
+
will throw an error.
|
12 |
+
'''
|
13 |
+
self.target = target.detach()
|
14 |
+
|
15 |
+
def forward(self, input):
|
16 |
+
self.loss = F.mse_loss(input, self.target)
|
17 |
+
return input
|
18 |
+
|
19 |
+
#Style Loss
|
20 |
+
def gram_matrix(input):
|
21 |
+
a, b, c, d = input.size() # a=batch size(=1)
|
22 |
+
# b=number of feature maps
|
23 |
+
# (c,d)=dimensions of a f. map (N=c*d)
|
24 |
+
|
25 |
+
features = input.view(a * b, c * d) # resize F_XL into \hat F_XL
|
26 |
+
|
27 |
+
G = torch.mm(features, features.t()) # compute the gram product
|
28 |
+
|
29 |
+
# we 'normalize' the values of the gram matrix
|
30 |
+
# by dividing by the number of element in each feature maps.
|
31 |
+
return G.div(a * b * c * d)
|
32 |
+
|
33 |
+
class StyleLoss(nn.Module):
|
34 |
+
|
35 |
+
def __init__(self, target_feature):
|
36 |
+
super(StyleLoss, self).__init__()
|
37 |
+
self.target = gram_matrix(target_feature).detach()
|
38 |
+
|
39 |
+
def forward(self, input):
|
40 |
+
G = gram_matrix(input)
|
41 |
+
self.loss = F.mse_loss(G, self.target)
|
42 |
+
return input
|
43 |
+
|
44 |
+
|
45 |
+
#Normalization
|
46 |
+
|
47 |
+
cnn_normalization_mean = torch.tensor([0.485, 0.456, 0.406]).to(device)
|
48 |
+
cnn_normalization_std = torch.tensor([0.229, 0.224, 0.225]).to(device)
|
49 |
+
#image transformation
|
50 |
+
def image_transform(image):
|
51 |
+
if isinstance(image, str):
|
52 |
+
# If image is a path to a file, open it using PIL
|
53 |
+
image = Image.open(image).convert('RGB')
|
54 |
+
else:
|
55 |
+
# If image is a NumPy array, convert it to a PIL image
|
56 |
+
image = Image.fromarray(image.astype('uint8'), 'RGB')
|
57 |
+
# Apply the same transformations as before
|
58 |
+
image = transform(image).unsqueeze(0)
|
59 |
+
return image.to(device)
|
60 |
+
|
61 |
+
|
62 |
+
|
63 |
+
#Defining a model
|
64 |
+
cnn = models.vgg19(pretrained=True).features.to(device).eval()
|
65 |
+
|
66 |
+
#getting the input optimizer
|
67 |
+
def get_input_optimizer(input_img):
|
68 |
+
# this line to show that input is a parameter that requires a gradient
|
69 |
+
optimizer = optim.LBFGS([input_img])
|
70 |
+
return optimizer
|
71 |
+
|
72 |
+
# desired depth layers to compute style/content losses :
|
73 |
+
|
74 |
+
|
75 |
+
content_layers_default = ['conv_4']
|
76 |
+
style_layers_default = ['conv_1', 'conv_2', 'conv_3', 'conv_4', 'conv_5']
|
77 |
+
|
78 |
+
def get_style_model_and_losses(cnn, normalization_mean, normalization_std,
|
79 |
+
style_img, content_img,
|
80 |
+
content_layers=content_layers_default,
|
81 |
+
style_layers=style_layers_default):
|
82 |
+
# normalization module
|
83 |
+
normalization = Normalization(normalization_mean, normalization_std).to(device)
|
84 |
+
|
85 |
+
# just in order to have an iterable access to or list of content/style
|
86 |
+
# losses
|
87 |
+
content_losses = []
|
88 |
+
style_losses = []
|
89 |
+
|
90 |
+
# assuming that ``cnn`` is a ``nn.Sequential``, so we make a new ``nn.Sequential``
|
91 |
+
# to put in modules that are supposed to be activated sequentially
|
92 |
+
model = nn.Sequential(normalization)
|
93 |
+
|
94 |
+
i = 0 # increment every time we see a conv
|
95 |
+
for layer in cnn.children():
|
96 |
+
if isinstance(layer, nn.Conv2d):
|
97 |
+
i += 1
|
98 |
+
name = 'conv_{}'.format(i)
|
99 |
+
elif isinstance(layer, nn.ReLU):
|
100 |
+
name = 'relu_{}'.format(i)
|
101 |
+
# The in-place version doesn't play very nicely with the ``ContentLoss``
|
102 |
+
# and ``StyleLoss`` we insert below. So we replace with out-of-place
|
103 |
+
# ones here.
|
104 |
+
layer = nn.ReLU(inplace=False)
|
105 |
+
elif isinstance(layer, nn.MaxPool2d):
|
106 |
+
name = 'pool_{}'.format(i)
|
107 |
+
elif isinstance(layer, nn.BatchNorm2d):
|
108 |
+
name = 'bn_{}'.format(i)
|
109 |
+
else:
|
110 |
+
raise RuntimeError('Unrecognized layer: {}'.format(layer.__class__.__name__))
|
111 |
+
|
112 |
+
model.add_module(name, layer)
|
113 |
+
|
114 |
+
if name in content_layers:
|
115 |
+
# add content loss:
|
116 |
+
target = model(content_img).detach()
|
117 |
+
content_loss = ContentLoss(target)
|
118 |
+
model.add_module("content_loss_{}".format(i), content_loss)
|
119 |
+
content_losses.append(content_loss)
|
120 |
+
|
121 |
+
if name in style_layers:
|
122 |
+
# add style loss:
|
123 |
+
target_feature = model(style_img).detach()
|
124 |
+
style_loss = StyleLoss(target_feature)
|
125 |
+
model.add_module("style_loss_{}".format(i), style_loss)
|
126 |
+
style_losses.append(style_loss)
|
127 |
+
|
128 |
+
# now we trim off the layers after the last content and style losses
|
129 |
+
for i in range(len(model) - 1, -1, -1):
|
130 |
+
if isinstance(model[i], ContentLoss) or isinstance(model[i], StyleLoss):
|
131 |
+
break
|
132 |
+
|
133 |
+
model = model[:(i + 1)]
|
134 |
+
|
135 |
+
return model, style_losses, content_losses
|