README.md 7.5 KB
Newer Older
L
LielinJiang 已提交
1 2 3 4 5 6 7
# 图像风格迁移

图像的风格迁移是卷积神经网络有趣的应用之一。那什么是风格迁移呢?下图第一列左边的图为相机拍摄的一张普通图片,右边的图为梵高的名画星空。那如何让左边的普通图片拥有星空的风格呢。神经网络的风格迁移就可以帮助你生成第二列的这样的图片。

<div align=center>
 <img src="images/markdown/img1.png" width = "600" height = "300"  />
</br>
L
LielinJiang 已提交
8
 <img src="images/markdown/img2.png" width = "300" height = "300"  divalign=center />
L
LielinJiang 已提交
9 10
<div align=left>

L
LielinJiang 已提交
11

L
LielinJiang 已提交
12 13 14 15 16 17
## 基本原理
风格迁移的目标就是使得生成图片的内容与内容图片(content image)尽可能相似。由于在计算机中,我们用一个一个像素点表示图片,所以两个图片的相似程度我们可以用每个像素点的欧式距离来表示。而两个图片的风格相似度,我们采用两个图片在卷积神经网络中相同的一层特征图的gram矩阵的欧式距离来表示。对于一个特征图gram矩阵的计算如下所示:

```python
# tensor shape is [1, c, h, w]
_, c, h, w = tensor.shape
L
LielinJiang 已提交
18
tensor = paddle.reshape(tensor, [c, h * w])
L
LielinJiang 已提交
19
# gram matrix with shape: [c, c]
L
LielinJiang 已提交
20
gram_matrix = paddle.matmul(tensor, paddle.transpose(tensor, [1, 0]))
L
LielinJiang 已提交
21 22 23 24
```

最终风格迁移的问题转化为优化上述的两个欧式距离的问题。这里要注意的是,我们使用一个在imagenet上预训练好的模型vgg16,并且固定参数,优化器只更新输入的生成图像的值。

L
LielinJiang 已提交
25 26 27
## 具体实现
接下来,使用代码一步一步来实现上述图片的风格迁移

L
LielinJiang 已提交
28
```python
L
LielinJiang 已提交
29 30 31 32 33 34
# 导入所需的模块
%matplotlib inline

import numpy as np
import matplotlib.pyplot as plt

L
LielinJiang 已提交
35
import paddle
L
LielinJiang 已提交
36

L
LielinJiang 已提交
37 38
from paddle.vision.models import vgg16
from paddle.vision.transforms import transforms
L
LielinJiang 已提交
39 40 41 42 43 44
from paddle import fluid

import cv2
import copy

# 图像预处理函数,和tensor恢复到自然图像的函数
L
LielinJiang 已提交
45
from .style_transfer import load_image, image_restore
L
LielinJiang 已提交
46 47 48 49 50
```


```python
# 启动动态图模式
L
LielinJiang 已提交
51
paddle.disable_static()
L
LielinJiang 已提交
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
```

```python
# 内容图像,用于风格迁移
content_path = './images/chicago_cropped.jpg'
# 风格图像
style_path = './images/Starry-Night-by-Vincent-Van-Gogh-painting.jpg'
```


```python
# 可视化两个图像
content = load_image(content_path)
style = load_image(style_path, shape=tuple(content.shape[-2:]))

fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(10, 10))
ax1.imshow(image_restore(content))
ax2.imshow(image_restore(style))
```


![png](images/markdown/output_10_1.png)



```python
# 定义风格迁移模型,使用在imagenet上预训练好的vgg16作为基础模型
L
LielinJiang 已提交
79
class StyleTransferModel(paddle.nn.Layer):
L
LielinJiang 已提交
80 81 82 83 84
    def __init__(self):
        super(StyleTransferModel, self).__init__()
        # pretrained设置为true,会自动下载imagenet上的预训练权重并加载
        vgg = vgg16(pretrained=True)
        self.base_model = vgg.features
L
LielinJiang 已提交
85

L
LielinJiang 已提交
86
        for p in self.base_model.parameters():
L
LielinJiang 已提交
87
            p.stop_gradient = True
L
LielinJiang 已提交
88
        self.layers = {
L
LielinJiang 已提交
89 90 91 92 93 94 95
            '0': 'conv1_1',
            '5': 'conv2_1',
            '10': 'conv3_1',
            '17': 'conv4_1',
            '19': 'conv4_2',  ## content representation
            '24': 'conv5_1'
        }
L
LielinJiang 已提交
96 97 98 99 100 101 102 103 104 105 106 107 108

    def forward(self, image):
        outputs = []
        for name, layer in self.base_model.named_sublayers():
            image = layer(image)
            if name in self.layers:
                outputs.append(image)
        return outputs
```


```python
# 定义风格迁移个损失函数
L
LielinJiang 已提交
109 110 111 112 113
class StyleTransferLoss(paddle.nn.Layer):
    def __init__(self,
                 content_loss_weight=1,
                 style_loss_weight=1e5,
                 style_weights=[1.0, 0.8, 0.5, 0.3, 0.1]):
L
LielinJiang 已提交
114 115 116 117 118
        super(StyleTransferLoss, self).__init__()
        self.content_loss_weight = content_loss_weight
        self.style_loss_weight = style_loss_weight
        self.style_weights = style_weights

L
LielinJiang 已提交
119 120 121
    def forward(self, *features):
        outputs = features[:6]
        labels = features[6:]
L
LielinJiang 已提交
122 123 124 125
        content_features = labels[-1]
        style_features = labels[:-1]

        # 计算图像内容相似度的loss
L
LielinJiang 已提交
126
        content_loss = paddle.mean((outputs[-2] - content_features)**2)
L
LielinJiang 已提交
127 128 129

        # 计算风格相似度的loss
        style_loss = 0
L
LielinJiang 已提交
130
        style_grams = [self.gram_matrix(feat) for feat in style_features]
L
LielinJiang 已提交
131 132 133
        style_weights = self.style_weights
        for i, weight in enumerate(style_weights):
            target_gram = self.gram_matrix(outputs[i])
L
LielinJiang 已提交
134 135
            layer_loss = weight * paddle.mean((target_gram - style_grams[
                i])**2)
L
LielinJiang 已提交
136 137 138 139 140 141 142 143
            b, d, h, w = outputs[i].shape
            style_loss += layer_loss / (d * h * w)

        total_loss = self.content_loss_weight * content_loss + self.style_loss_weight * style_loss
        return total_loss

    def gram_matrix(self, A):
        if len(A.shape) == 4:
L
LielinJiang 已提交
144 145 146
            _, c, h, w = A.shape
            A = paddle.reshape(A, (c, h * w))
        GA = paddle.matmul(A, paddle.transpose(A, [1, 0]))
L
LielinJiang 已提交
147 148 149 150 151 152 153

        return GA
```


```python
# 创建模型
L
LielinJiang 已提交
154 155
net = StyleTransferModel()
model = paddle.Model(net)
L
LielinJiang 已提交
156 157 158 159 160 161 162 163 164 165 166
```


```python
# 创建损失函数
style_loss = StyleTransferLoss()
```


```python
# 使用内容图像初始化要生成的图像
L
LielinJiang 已提交
167
target = net.create_parameter(shape=content.shape)
L
LielinJiang 已提交
168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185
target.set_value(content.numpy())
```


```python
# 创建优化器
optimizer = fluid.optimizer.Adam(parameter_list=[target], learning_rate=0.001)
```


```python
# 初始化高级api
model.prepare(optimizer, style_loss)
```


```python
# 使用内容图像和风格图像获取内容特征和风格特征
L
LielinJiang 已提交
186 187
content_fetures = model.test_batch(content)
style_features = model.test_batch(style)
L
LielinJiang 已提交
188 189 190 191 192 193 194 195 196 197 198 199 200
```


```python
# 将两个特征组合,作为损失函数的label传给模型
feats = style_features + [content_fetures[-2]]
```


```python
# 训练5000个step,每500个step画一下生成的图像查看效果
steps = 5000
for i in range(steps):
L
LielinJiang 已提交
201
    outs = model.train_batch(target, feats)
L
LielinJiang 已提交
202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285

    if i % 500 == 0:
        print('iters:', i, 'loss:', outs[0])
        plt.imshow(image_restore(target))
        plt.show()
```

    iters: 0 loss: [8.829961e+09]



![png](images/markdown/output_20_1.png)


    iters: 500 loss: [3.728548e+08]



![png](images/markdown/output_20_3.png)


    iters: 1000 loss: [1.6327214e+08]



![png](images/markdown/output_20_5.png)


    iters: 1500 loss: [1.0806553e+08]



![png](images/markdown/output_20_7.png)


    iters: 2000 loss: [81069480.]



![png](images/markdown/output_20_9.png)


    iters: 2500 loss: [64284104.]



![png](images/markdown/output_20_11.png)


    iters: 3000 loss: [52580884.]



![png](images/markdown/output_20_13.png)


    iters: 3500 loss: [43825304.]



![png](images/markdown/output_20_15.png)


    iters: 4000 loss: [37048400.]



![png](images/markdown/output_20_17.png)


    iters: 4500 loss: [31719670.]



![png](images/markdown/output_20_19.png)



```python
# 风格迁移后的图像
fig, (ax1, ax2, ax3) = plt.subplots(1, 3, figsize=(20, 10))
ax1.imshow(image_restore(content))
ax2.imshow(image_restore(target))
ax3.imshow(image_restore(style))
L
LielinJiang 已提交
286 287 288 289
```



L
LielinJiang 已提交
290 291 292 293 294 295 296 297 298

![png](images/markdown/output_21_1.png)



## 总结
上述可运行的代码都在[style-transfer.ipynb](./style-transfer.ipynb)中, 同时我们提供了[style-transfer.py](./style-transfer.py)脚本,可以直接执行如下命令,实现图片的风格迁移:

```shell
L
LielinJiang 已提交
299
python -u style-transfer.py --content-image /path/to/your-content-image --style-image /path/to/your-style-image --save-dir /path/to/your-output-dir
L
LielinJiang 已提交
300 301
```

L
LielinJiang 已提交
302 303
风格迁移生成的图像保存在```--save-dir```中。

L
LielinJiang 已提交
304
## 参考
L
LielinJiang 已提交
305 306

[A Neural Algorithm of Artistic Style](https://arxiv.org/abs/1508.06576)