├── .gitignore
├── LICENSE
├── README.md
├── images
├── donelli.jpg
├── groening.jpg
├── lundstroem.jpg
├── margrethe.jpg
├── margrethe_donelli.jpg
├── margrethe_groening.jpg
├── margrethe_lundstroem.jpg
├── margrethe_picasso.jpg
├── margrethe_skrik.jpg
├── picasso.jpg
├── skrik.jpg
├── starry_night.jpg
├── tuebingen-starry_night.jpg
└── tuebingen.jpg
├── matconvnet.py
├── neural_artistic_style.py
└── style_network.py
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 |
5 | # C extensions
6 | *.so
7 |
8 | # Distribution / packaging
9 | .Python
10 | env/
11 | build/
12 | develop-eggs/
13 | dist/
14 | downloads/
15 | eggs/
16 | .eggs/
17 | lib/
18 | lib64/
19 | parts/
20 | sdist/
21 | var/
22 | *.egg-info/
23 | .installed.cfg
24 | *.egg
25 |
26 | # PyInstaller
27 | # Usually these files are written by a python script from a template
28 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
29 | *.manifest
30 | *.spec
31 |
32 | # Installer logs
33 | pip-log.txt
34 | pip-delete-this-directory.txt
35 |
36 | # Unit test / coverage reports
37 | htmlcov/
38 | .tox/
39 | .coverage
40 | .coverage.*
41 | .cache
42 | nosetests.xml
43 | coverage.xml
44 | *,cover
45 |
46 | # Translations
47 | *.mo
48 | *.pot
49 |
50 | # Django stuff:
51 | *.log
52 |
53 | # Sphinx documentation
54 | docs/_build/
55 |
56 | # PyBuilder
57 | target/
58 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Anders Boesen Lindbo Larsen
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## Neural Artistic Style in Python
2 |
3 | Implementation of [A Neural Algorithm of Artistic Style](http://arxiv.org/abs/1508.06576). A method to transfer the style of one image to the subject of another image.
4 |
5 |
6 | ### Requirements
7 | - [DeepPy](http://github.com/andersbll/deeppy), Deep learning in Python.
8 | - [CUDArray](http://github.com/andersbll/cudarray) with [cuDNN](https://developer.nvidia.com/cudnn), CUDA-accelerated NumPy.
9 | - [Pretrained VGG 19 model](http://www.vlfeat.org/matconvnet/pretrained), choose *imagenet-vgg-verydeep-19*.
10 |
11 |
12 | ### Examples
13 | Execute
14 |
15 | python neural_artistic_style.py --subject images/tuebingen.jpg --style images/starry_night.jpg
16 |
17 | The two inputs are
18 |
19 |
20 | Subject:
21 |
22 | Style:
23 |
24 |
25 |
26 | The output becomes:
27 |
28 |
29 |
30 |
31 | We can also choose a (younger version) of HM the Queen of Denmark as subject and paint her using different styles. Click the images to see the full size.
32 |
33 | **Subject**
34 |
35 |
36 |
37 |
38 | **Styles**
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 | **Outputs**
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 | ### Help
58 | List command line options with
59 |
60 | python neural_artistic_style.py --help
61 |
--------------------------------------------------------------------------------
/images/donelli.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/andersbll/neural_artistic_style/94b21acca47db89a06b095c48f0c3c69d424144b/images/donelli.jpg
--------------------------------------------------------------------------------
/images/groening.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/andersbll/neural_artistic_style/94b21acca47db89a06b095c48f0c3c69d424144b/images/groening.jpg
--------------------------------------------------------------------------------
/images/lundstroem.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/andersbll/neural_artistic_style/94b21acca47db89a06b095c48f0c3c69d424144b/images/lundstroem.jpg
--------------------------------------------------------------------------------
/images/margrethe.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/andersbll/neural_artistic_style/94b21acca47db89a06b095c48f0c3c69d424144b/images/margrethe.jpg
--------------------------------------------------------------------------------
/images/margrethe_donelli.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/andersbll/neural_artistic_style/94b21acca47db89a06b095c48f0c3c69d424144b/images/margrethe_donelli.jpg
--------------------------------------------------------------------------------
/images/margrethe_groening.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/andersbll/neural_artistic_style/94b21acca47db89a06b095c48f0c3c69d424144b/images/margrethe_groening.jpg
--------------------------------------------------------------------------------
/images/margrethe_lundstroem.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/andersbll/neural_artistic_style/94b21acca47db89a06b095c48f0c3c69d424144b/images/margrethe_lundstroem.jpg
--------------------------------------------------------------------------------
/images/margrethe_picasso.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/andersbll/neural_artistic_style/94b21acca47db89a06b095c48f0c3c69d424144b/images/margrethe_picasso.jpg
--------------------------------------------------------------------------------
/images/margrethe_skrik.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/andersbll/neural_artistic_style/94b21acca47db89a06b095c48f0c3c69d424144b/images/margrethe_skrik.jpg
--------------------------------------------------------------------------------
/images/picasso.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/andersbll/neural_artistic_style/94b21acca47db89a06b095c48f0c3c69d424144b/images/picasso.jpg
--------------------------------------------------------------------------------
/images/skrik.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/andersbll/neural_artistic_style/94b21acca47db89a06b095c48f0c3c69d424144b/images/skrik.jpg
--------------------------------------------------------------------------------
/images/starry_night.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/andersbll/neural_artistic_style/94b21acca47db89a06b095c48f0c3c69d424144b/images/starry_night.jpg
--------------------------------------------------------------------------------
/images/tuebingen-starry_night.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/andersbll/neural_artistic_style/94b21acca47db89a06b095c48f0c3c69d424144b/images/tuebingen-starry_night.jpg
--------------------------------------------------------------------------------
/images/tuebingen.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/andersbll/neural_artistic_style/94b21acca47db89a06b095c48f0c3c69d424144b/images/tuebingen.jpg
--------------------------------------------------------------------------------
/matconvnet.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | import scipy.io
3 | import deeppy as dp
4 |
5 |
6 | def conv_layer(weights, bias, border_mode):
7 | return dp.Convolution(
8 | n_filters=weights.shape[0],
9 | filter_shape=weights.shape[2:],
10 | border_mode=border_mode,
11 | weights=weights,
12 | bias=bias,
13 | )
14 |
15 |
16 | def pool_layer(pool_method, border_mode):
17 | return dp.Pool(
18 | win_shape=(3, 3),
19 | strides=(2, 2),
20 | method=pool_method,
21 | border_mode=border_mode,
22 | )
23 |
24 |
25 | def vgg_net(path, pool_method='max', border_mode='same'):
26 | matconvnet = scipy.io.loadmat(path)
27 | img_mean = matconvnet['meta'][0][0][2][0][0][2]
28 | vgg_layers = matconvnet['layers'][0]
29 | layers = []
30 | for layer in vgg_layers:
31 | layer = layer[0][0]
32 | layer_type = layer[1][0]
33 | if layer_type == 'conv':
34 | params = layer[2][0]
35 | weights = params[0]
36 | bias = params[1]
37 | weights = np.transpose(weights, (3, 2, 0, 1)).astype(dp.float_)
38 | bias = np.reshape(bias, (1, bias.size, 1, 1)).astype(dp.float_)
39 | layers.append(conv_layer(weights, bias, border_mode))
40 | elif layer_type == 'pool':
41 | layers.append(pool_layer(pool_method, border_mode))
42 | elif layer_type == 'relu':
43 | layers.append(dp.ReLU())
44 | elif layer_type == 'softmax':
45 | pass
46 | else:
47 | raise ValueError('invalid layer type: %s' % layer_type)
48 | return layers, img_mean
49 |
--------------------------------------------------------------------------------
/neural_artistic_style.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | import os
4 | import argparse
5 | import numpy as np
6 | import scipy.misc
7 | import deeppy as dp
8 |
9 | from matconvnet import vgg_net
10 | from style_network import StyleNetwork
11 |
12 |
13 | def weight_tuple(s):
14 | try:
15 | conv_idx, weight = map(float, s.split(','))
16 | return conv_idx, weight
17 | except:
18 | raise argparse.ArgumentTypeError('weights must by "int,float"')
19 |
20 |
21 | def float_range(x):
22 | x = float(x)
23 | if x < 0.0 or x > 1.0:
24 | raise argparse.ArgumentTypeError("%r not in range [0, 1]" % x)
25 | return x
26 |
27 |
28 | def weight_array(weights):
29 | array = np.zeros(19)
30 | for idx, weight in weights:
31 | array[idx] = weight
32 | norm = np.sum(array)
33 | if norm > 0:
34 | array /= norm
35 | return array
36 |
37 |
38 | def imread(path):
39 | return scipy.misc.imread(path).astype(dp.float_)
40 |
41 |
42 | def imsave(path, img):
43 | img = np.clip(img, 0, 255).astype(np.uint8)
44 | scipy.misc.imsave(path, img)
45 |
46 |
47 | def to_bc01(img):
48 | return np.transpose(img, (2, 0, 1))[np.newaxis, ...]
49 |
50 |
51 | def to_rgb(img):
52 | return np.transpose(img[0], (1, 2, 0))
53 |
54 |
55 | def run():
56 | parser = argparse.ArgumentParser(
57 | description='Neural artistic style. Generates an image by combining '
58 | 'the subject from one image and the style from another.',
59 | formatter_class=argparse.ArgumentDefaultsHelpFormatter
60 | )
61 | parser.add_argument('--subject', required=True, type=str,
62 | help='Subject image.')
63 | parser.add_argument('--style', required=True, type=str,
64 | help='Style image.')
65 | parser.add_argument('--output', default='out.png', type=str,
66 | help='Output image.')
67 | parser.add_argument('--init', default=None, type=str,
68 | help='Initial image. Subject is chosen as default.')
69 | parser.add_argument('--init-noise', default=0.0, type=float_range,
70 | help='Weight between [0, 1] to adjust the noise level '
71 | 'in the initial image.')
72 | parser.add_argument('--random-seed', default=None, type=int,
73 | help='Random state.')
74 | parser.add_argument('--animation', default='animation', type=str,
75 | help='Output animation directory.')
76 | parser.add_argument('--iterations', default=500, type=int,
77 | help='Number of iterations to run.')
78 | parser.add_argument('--learn-rate', default=2.0, type=float,
79 | help='Learning rate.')
80 | parser.add_argument('--smoothness', type=float, default=5e-8,
81 | help='Weight of smoothing scheme.')
82 | parser.add_argument('--subject-weights', nargs='*', type=weight_tuple,
83 | default=[(9, 1)],
84 | help='List of subject weights (conv_idx,weight).')
85 | parser.add_argument('--style-weights', nargs='*', type=weight_tuple,
86 | default=[(0, 1), (2, 1), (4, 1), (8, 1), (12, 1)],
87 | help='List of style weights (conv_idx,weight).')
88 | parser.add_argument('--subject-ratio', type=float, default=2e-2,
89 | help='Weight of subject relative to style.')
90 | parser.add_argument('--pool-method', default='avg', type=str,
91 | choices=['max', 'avg'], help='Subsampling scheme.')
92 | parser.add_argument('--network', default='imagenet-vgg-verydeep-19.mat',
93 | type=str, help='Network in MatConvNet format).')
94 | args = parser.parse_args()
95 |
96 | if args.random_seed is not None:
97 | np.random.seed(args.random_seed)
98 |
99 | layers, pixel_mean = vgg_net(args.network, pool_method=args.pool_method)
100 |
101 | # Inputs
102 | style_img = imread(args.style) - pixel_mean
103 | subject_img = imread(args.subject) - pixel_mean
104 | if args.init is None:
105 | init_img = subject_img
106 | else:
107 | init_img = imread(args.init) - pixel_mean
108 | noise = np.random.normal(size=init_img.shape, scale=np.std(init_img)*1e-1)
109 | init_img = init_img * (1 - args.init_noise) + noise * args.init_noise
110 |
111 | # Setup network
112 | subject_weights = weight_array(args.subject_weights) * args.subject_ratio
113 | style_weights = weight_array(args.style_weights)
114 | net = StyleNetwork(layers, to_bc01(init_img), to_bc01(subject_img),
115 | to_bc01(style_img), subject_weights, style_weights,
116 | args.smoothness)
117 |
118 | # Repaint image
119 | def net_img():
120 | return to_rgb(net.image) + pixel_mean
121 |
122 | if not os.path.exists(args.animation):
123 | os.mkdir(args.animation)
124 |
125 | params = net.params
126 | learn_rule = dp.Adam(learn_rate=args.learn_rate)
127 | learn_rule_states = [learn_rule.init_state(p) for p in params]
128 | for i in range(args.iterations):
129 | imsave(os.path.join(args.animation, '%.4d.png' % i), net_img())
130 | cost = np.mean(net.update())
131 | for param, state in zip(params, learn_rule_states):
132 | learn_rule.step(param, state)
133 | print('Iteration: %i, cost: %.4f' % (i, cost))
134 | imsave(args.output, net_img())
135 |
136 |
137 | if __name__ == "__main__":
138 | run()
139 |
--------------------------------------------------------------------------------
/style_network.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | import cudarray as ca
3 | import deeppy as dp
4 | from deeppy.base import Model
5 | from deeppy.parameter import Parameter
6 |
7 |
8 | class Convolution(dp.Convolution):
9 | """ Convolution layer wrapper
10 |
11 | This layer does not propagate gradients to filters. Also, it reduces
12 | memory consumption as it does not store fprop() input for bprop().
13 | """
14 | def __init__(self, layer):
15 | self.layer = layer
16 |
17 | def fprop(self, x):
18 | y = self.conv_op.fprop(x, self.weights.array)
19 | y += self.bias.array
20 | return y
21 |
22 | def bprop(self, y_grad):
23 | # Backprop to input image only
24 | _, x_grad = self.layer.conv_op.bprop(
25 | imgs=None, filters=self.weights.array, convout_d=y_grad,
26 | to_imgs=True, to_filters=False
27 | )
28 | return x_grad
29 |
30 | # Wrap layer methods
31 | def __getattr__(self, attr):
32 | if attr in self.__dict__:
33 | return getattr(self, attr)
34 | return getattr(self.layer, attr)
35 |
36 |
37 | def gram_matrix(img_bc01):
38 | n_channels = img_bc01.shape[1]
39 | feats = ca.reshape(img_bc01, (n_channels, -1))
40 | gram = ca.dot(feats, feats.T)
41 | return gram
42 |
43 |
44 | class StyleNetwork(Model):
45 | """ Artistic style network
46 |
47 | Implementation of [1].
48 |
49 | Differences:
50 | - The gradients for both subject and style are normalized. The original
51 | method uses pre-normalized convolutional features.
52 | - The Gram matrices are scaled wrt. # of pixels. The original method is
53 | sensitive to different image sizes between subject and style.
54 | - Additional smoothing term for visually better results.
55 |
56 | References:
57 | [1]: A Neural Algorithm of Artistic Style; Leon A. Gatys, Alexander S.
58 | Ecker, Matthias Bethge; arXiv:1508.06576; 08/2015
59 | """
60 |
61 | def __init__(self, layers, init_img, subject_img, style_img,
62 | subject_weights, style_weights, smoothness=0.0):
63 |
64 | # Map weights (in convolution indices) to layer indices
65 | self.subject_weights = np.zeros(len(layers))
66 | self.style_weights = np.zeros(len(layers))
67 | layers_len = 0
68 | conv_idx = 0
69 | for l, layer in enumerate(layers):
70 | if isinstance(layer, dp.Activation):
71 | self.subject_weights[l] = subject_weights[conv_idx]
72 | self.style_weights[l] = style_weights[conv_idx]
73 | if subject_weights[conv_idx] > 0 or \
74 | style_weights[conv_idx] > 0:
75 | layers_len = l+1
76 | conv_idx += 1
77 |
78 | # Discard unused layers
79 | layers = layers[:layers_len]
80 |
81 | # Wrap convolution layers for better performance
82 | self.layers = [Convolution(l) if isinstance(l, dp.Convolution) else l
83 | for l in layers]
84 |
85 | # Setup network
86 | x_shape = init_img.shape
87 | self.x = Parameter(init_img)
88 | self.x.setup(x_shape)
89 | for layer in self.layers:
90 | layer.setup(x_shape)
91 | x_shape = layer.y_shape(x_shape)
92 |
93 | # Precompute subject features and style Gram matrices
94 | self.subject_feats = [None]*len(self.layers)
95 | self.style_grams = [None]*len(self.layers)
96 | next_subject = ca.array(subject_img)
97 | next_style = ca.array(style_img)
98 | for l, layer in enumerate(self.layers):
99 | next_subject = layer.fprop(next_subject)
100 | next_style = layer.fprop(next_style)
101 | if self.subject_weights[l] > 0:
102 | self.subject_feats[l] = next_subject
103 | if self.style_weights[l] > 0:
104 | gram = gram_matrix(next_style)
105 | # Scale gram matrix to compensate for different image sizes
106 | n_pixels_subject = np.prod(next_subject.shape[2:])
107 | n_pixels_style = np.prod(next_style.shape[2:])
108 | scale = (n_pixels_subject / float(n_pixels_style))
109 | self.style_grams[l] = gram * scale
110 |
111 | self.tv_weight = smoothness
112 | kernel = np.array([[0, 1, 0], [1, -4, 1], [0, 1, 0]], dtype=dp.float_)
113 | kernel /= np.sum(np.abs(kernel))
114 | self.tv_kernel = ca.array(kernel[np.newaxis, np.newaxis, ...])
115 | self.tv_conv = ca.nnet.ConvBC01((1, 1), (1, 1))
116 |
117 | @property
118 | def image(self):
119 | return np.array(self.x.array)
120 |
121 | @property
122 | def params(self):
123 | return [self.x]
124 |
125 | def update(self):
126 | # Forward propagation
127 | next_x = self.x.array
128 | x_feats = [None]*len(self.layers)
129 | for l, layer in enumerate(self.layers):
130 | next_x = layer.fprop(next_x)
131 | if self.subject_weights[l] > 0 or self.style_weights[l] > 0:
132 | x_feats[l] = next_x
133 |
134 | # Backward propagation
135 | grad = ca.zeros_like(next_x)
136 | loss = ca.zeros(1)
137 | for l, layer in reversed(list(enumerate(self.layers))):
138 | if self.subject_weights[l] > 0:
139 | diff = x_feats[l] - self.subject_feats[l]
140 | norm = ca.sum(ca.fabs(diff)) + 1e-8
141 | weight = float(self.subject_weights[l]) / norm
142 | grad += diff * weight
143 | loss += 0.5*weight*ca.sum(diff**2)
144 | if self.style_weights[l] > 0:
145 | diff = gram_matrix(x_feats[l]) - self.style_grams[l]
146 | n_channels = diff.shape[0]
147 | x_feat = ca.reshape(x_feats[l], (n_channels, -1))
148 | style_grad = ca.reshape(ca.dot(diff, x_feat), x_feats[l].shape)
149 | norm = ca.sum(ca.fabs(style_grad))
150 | weight = float(self.style_weights[l]) / norm
151 | style_grad *= weight
152 | grad += style_grad
153 | loss += 0.25*weight*ca.sum(diff**2)
154 | grad = layer.bprop(grad)
155 |
156 | if self.tv_weight > 0:
157 | x = ca.reshape(self.x.array, (3, 1) + grad.shape[2:])
158 | tv = self.tv_conv.fprop(x, self.tv_kernel)
159 | tv *= self.tv_weight
160 | grad -= ca.reshape(tv, grad.shape)
161 |
162 | ca.copyto(self.x.grad_array, grad)
163 | return loss
164 |
--------------------------------------------------------------------------------