CNN 風格遷移實戰(附python代碼)

CNN 風格遷移實戰(附python代碼)

在今天的文章中,我們會建立一個很棒的風格遷移網絡。為了做到這一點,我們需要深入地瞭解 CNN 和卷積層的工作原理。在文章結束時,你將會創建一個風格遷移網絡,這個網絡能夠在保留原始圖像的同時將新樣式應用到它上面。

CNN 風格遷移實戰(附python代碼)

波士頓天際線和梵高的繁星之夜混合效果

風格遷移

在開始之前,先明確一下我們的目標。

我們將風格遷移定義為改變圖像風格同時保留它的內容的過程。

給定一張輸入圖像和樣式圖像,我們就可以得到既有原始內容又有新樣式的輸出圖像。在 Leon A. Gaty 的論文 A Neural Algorithm of Artistic Style 中有所描述。

輸入圖像 + 樣式圖像 -> 輸出圖像(風格化)

工作方式

  1. 準備輸入圖像和風格圖像並將它們調整為相同的大小。
  2. 加載預訓練的卷積神經網絡(VGG16)。
  3. 區分負責樣式的卷積(基本形狀,顏色等)和負責內容的卷積(特定於圖像的特徵),將卷積分開可以單獨地處理內容和樣式。
  4. 優化問題,也就是最小化:
  • 內容損失(輸入和輸出圖像之間的距離 - 盡力保留內容)
  • 風格損失(風格和輸出圖像之間的距離 - 盡力應用新風格)
  • 總變差損失(正則化 - 對輸出圖像進行去噪的空間平滑度)

5.最後設置梯度並使用 L-BFGS 算法進行優化。

實現

可以在 Kaggle kernel 或 GitHub找到該項目的完整代碼。

輸入

# 舊金山
san_francisco_image_path = "https://www.economist.com/sites/default/files/images/print-edition/20180602_USP001_0.jpg"
# 輸入可視化
input_image = Image.open(BytesIO(requests.get(san_francisco_image_path).content))
input_image = input_image.resize((IMAGE_WIDTH, IMAGE_HEIGHT))
input_image.save(input_image_path)
input_image

這就是舊金山的天際線

CNN 風格遷移實戰(附python代碼)

風格

然後定義一個風格圖像。

# Tytus Brzozowski
tytus_image_path = "http://meetingbenches.com/wp-content/flagallery/tytus-brzozowski-polish-architect-and-watercolorist-a-fairy-tale-in-warsaw/tytus_brzozowski_13.jpg"

# 風格圖像可視化
style_image = Image.open(BytesIO(requests.get(tytus_image_path).content))
style_image = style_image.resize((IMAGE_WIDTH, IMAGE_HEIGHT))
style_image.save(style_image_path)
style_image

這是Tytus Brzozowski的景色。

CNN 風格遷移實戰(附python代碼)

預處理

接下來對兩個圖像調整大小和均值歸一化。

input_image_array = np.asarray(input_image, dtype="float32")
input_image_array = np.expand_dims(input_image_array, axis=0)
input_image_array[:, :, :, 0] -= IMAGENET_MEAN_RGB_VALUES[2]
input_image_array[:, :, :, 1] -= IMAGENET_MEAN_RGB_VALUES[1]
input_image_array[:, :, :, 2] -= IMAGENET_MEAN_RGB_VALUES[0]
input_image_array = input_image_array[:, :, :, ::-1]

style_image_array = np.asarray(style_image, dtype="float32")
style_image_array = np.expand_dims(style_image_array, axis=0)
style_image_array[:, :, :, 0] -= IMAGENET_MEAN_RGB_VALUES[2]
style_image_array[:, :, :, 1] -= IMAGENET_MEAN_RGB_VALUES[1]
style_image_array[:, :, :, 2] -= IMAGENET_MEAN_RGB_VALUES[0]
style_image_array = style_image_array[:, :, :, ::-1]

CNN模型

隨著圖像準備完成,我們可以繼續建立 CNN 模型。

# 模型
input_image = backend.variable(input_image_array)
style_image = backend.variable(style_image_array)
combination_image = backend.placeholder((1, IMAGE_HEIGHT, IMAGE_SIZE, 3))

input_tensor = backend.concatenate([input_image,style_image,combination_image], axis=0)
model = VGG16(input_tensor=input_tensor, include_top=False)

在這個項目中,我們將使用預先訓練的VGG16模型,如下所示。

CNN 風格遷移實戰(附python代碼)

VGG16架構

我們不使用全連接(藍色)和 softmax (黃色),因為這裡不需要分類器。我們僅使用特徵提取器,即卷積(黑色)和最大池(紅色)。

下面是在ImageNet數據集上訓練的 VGG16 的圖像特徵。

CNN 風格遷移實戰(附python代碼)

VGG16 特徵

我們不會可視化每個CNN,對於內容,我們應該選擇 block2_conv2 ,樣式應該選擇 [block1_conv2,block2_conv2,block3_conv3,block4_conv3,block5_conv3]。

雖然這種組合被證明是有效的,但也可以嘗試不同的卷積層。

內容損失

定義了CNN模型後,還需要定義一個內容損失函數。為了保留圖像原始內容,我們將最小化輸入圖像和輸出圖像之間的距離。

def content_loss(content, combination):
return backend.sum(backend.square(combination - content))

layers = dict([(layer.name, layer.output) for layer in model.layers])

content_layer = "block2_conv2"
layer_features = layers[content_layer]
content_image_features = layer_features[0, :, :, :]
combination_features = layer_features[2, :, :, :]
loss = backend.variable(0.)
loss += CONTENT_WEIGHT * content_loss(content_image_features,
combination_features)

樣式損失

與內容損失類似,樣式損失也被定義為兩個圖像之間的距離。但是,為了應用新風格,樣式損失被定義為風格圖像和輸出圖像之間的距離。

def gram_matrix(x):
features = backend.batch_flatten(backend.permute_dimensions(x, (2, 0, 1)))
gram = backend.dot(features, backend.transpose(features))
return gram

def compute_style_loss(style, combination):
style = gram_matrix(style)

combination = gram_matrix(combination)
size = IMAGE_HEIGHT * IMAGE_WIDTH
return backend.sum(backend.square(style - combination)) / (4. * (CHANNELS ** 2) * (size ** 2))

style_layers = ["block1_conv2", "block2_conv2", "block3_conv3", "block4_conv3", "block5_conv3"]
for layer_name in style_layers:
layer_features = layers[layer_name]
style_features = layer_features[1, :, :, :]
combination_features = layer_features[2, :, :, :]
style_loss = compute_style_loss(style_features, combination_features)
loss += (STYLE_WEIGHT / len(style_layers)) * style_loss

總變化損失

最後定義一個總變化損失,它作為一個空間平滑器來規範圖像並防止去噪。

def total_variation_loss(x):
a = backend.square(x[:, :IMAGE_HEIGHT-1, :IMAGE_WIDTH-1, :] - x[:, 1:, :IMAGE_WIDTH-1, :])
b = backend.square(x[:, :IMAGE_HEIGHT-1, :IMAGE_WIDTH-1, :] - x[:, :IMAGE_HEIGHT-1, 1:, :])
return backend.sum(backend.pow(a + b, TOTAL_VARIATION_LOSS_FACTOR))

loss += TOTAL_VARIATION_WEIGHT * total_variation_loss(combination_image)

優化 - 損失和梯度

設置了內容損失,樣式損失和總變化損失之後,就可以將風格轉移過程轉化為優化問題,最大限度地減少全局損失(內容,風格和總變化損失的組合) 。

在每次迭代中,我們將創建一個輸出圖像,以便最小化相應像素輸出和輸入/樣式之間的距離(差異)。

outputs = [loss]
outputs += backend.gradients(loss, combination_image)

def evaluate_loss_and_gradients(x):
x = x.reshape((1, IMAGE_HEIGHT, IMAGE_WIDTH, CHANNELS))
outs = backend.function([combination_image], outputs)([x])
loss = outs[0]
gradients = outs[1].flatten().astype("float64")
return loss, gradients

class Evaluator:

def loss(self, x):
loss, gradients = evaluate_loss_and_gradients(x)
self._gradients = gradients
return loss

def gradients(self, x):
return self._gradients

evaluator = Evaluator()
CNN 風格遷移實戰(附python代碼)

梯度下降可視化

結果

最後,使用 L-BFGS 算法進行優化並可視化結果。

x = np.random.uniform(0, 255, (1, IMAGE_HEIGHT, IMAGE_WIDTH, 3)) - 128.
for i in range(ITERATIONS):
x, loss, info = fmin_l_bfgs_b(evaluator.loss, x.flatten(), fprime=evaluator.gradients, maxfun=20)
print("Iteration %d completed with loss %d" % (i, loss))

x = x.reshape((IMAGE_HEIGHT, IMAGE_WIDTH, CHANNELS))
x = x[:, :, ::-1]
x[:, :, 0] += IMAGENET_MEAN_RGB_VALUES[2]
x[:, :, 1] += IMAGENET_MEAN_RGB_VALUES[1]
x[:, :, 2] += IMAGENET_MEAN_RGB_VALUES[0]
x = np.clip(x, 0, 255).astype("uint8")
output_image = Image.fromarray(x)
output_image.save(output_image_path)
output_image
CNN 風格遷移實戰(附python代碼)

在1,2和5次迭代後輸出圖像

CNN 風格遷移實戰(附python代碼)

10次​​迭代後結果

將輸入圖像,樣式圖像和輸出圖像放在一起。

CNN 風格遷移實戰(附python代碼)

效果還是非常不錯的。

我們可以清楚地看到,既保留了輸入圖像(舊金山天際線)的原始內容,也成功地將新樣式(Tytus Brzozowski)應用到了新的輸出圖像。

其他一些例子

CNN 風格遷移實戰(附python代碼)

CNN 風格遷移實戰(附python代碼)

CNN 風格遷移實戰(附python代碼)

CNN 風格遷移實戰(附python代碼)


分享到:


相關文章: