Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
tensorflow
GitHub Repository: tensorflow/docs-l10n
Path: blob/master/site/pt-br/tutorials/generative/style_transfer.ipynb
25118 views
Kernel: Python 3
#@title Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License.

Transferência neural de estilo

Este tutorial usa aprendizado profundo para criar uma imagem com o estilo de outra (você já desejou pintar como Picasso ou Van Gogh?). Isso é conhecido como transferência neural de estilo, e uma técnica é descrita em Algoritmo neural de estilo artístico (Gatys et al.).

Observação: este tutorial demonstra o algoritmo de transferência de estilo original, que otimiza o conteúdo da imagem para um estilo específico. Nas estratégias modernas, um modelo é treinado para gerar a imagem estilizada diretamente (similar à CycleGAN). Essa estratégia é muito mais rápida (até 1.000 vezes).

Para ver uma aplicação simples de transferência de estilo com um modelo pré-treinado do TensorFlow Hub, confira o tutorial Transferência rápida de estilo para estilos arbitrários, que usa um modelo arbitrário de estilização de imagem. Para ver um exemplo de transferência de estilo com o TensorFlow Lite, confira Transferência de estilo artístico com o TensorFlow Lite.

A transferência neural de estilo é uma técnica de otimização usada para pegar duas imagens, uma de conteúdo e uma de referência de estilo (como uma obra de arte de um pintor famoso), e combiná-las para que a imagem produzida pareça a imagem de conteúdo, mas esteja “pintada” com o estilo da imagem de referência de estilo.

Essa implementação é feita otimizando-se a imagem produzida para que corresponda às estatísticas de conteúdo da imagem de conteúdo e às estatísticas de estilo da imagem de referência de estilo. Essas estatísticas são extraídas das imagens usando uma rede convolucional.

Por exemplo, vamos pegar uma imagem deste cachorro e a Composição VII de Wassily Kandinsky:

Labrador amarelo olhando para trás, disponível na Wikimedia Commons por Elf. Licença CC BY-SA 3.0

Como essa imagem ficaria se Kandinsky decidisse pintar esse cachorro exclusivamente com esse estilo? Algo assim?

Configuração

Importar e configurar os modelos

import os import tensorflow as tf # Load compressed models from tensorflow_hub os.environ['TFHUB_MODEL_LOAD_FORMAT'] = 'COMPRESSED'
import IPython.display as display import matplotlib.pyplot as plt import matplotlib as mpl mpl.rcParams['figure.figsize'] = (12, 12) mpl.rcParams['axes.grid'] = False import numpy as np import PIL.Image import time import functools
def tensor_to_image(tensor): tensor = tensor*255 tensor = np.array(tensor, dtype=np.uint8) if np.ndim(tensor)>3: assert tensor.shape[0] == 1 tensor = tensor[0] return PIL.Image.fromarray(tensor)

Baixe as imagens e escolha uma imagem de estilo e outra de conteúdo:

content_path = tf.keras.utils.get_file('YellowLabradorLooking_new.jpg', 'https://storage.googleapis.com/download.tensorflow.org/example_images/YellowLabradorLooking_new.jpg') style_path = tf.keras.utils.get_file('kandinsky5.jpg','https://storage.googleapis.com/download.tensorflow.org/example_images/Vassily_Kandinsky%2C_1913_-_Composition_7.jpg')

Visualizar a entrada

Defina uma função para carregar uma imagem e limitar sua dimensão máxima a 512 pixels.

def load_img(path_to_img): max_dim = 512 img = tf.io.read_file(path_to_img) img = tf.image.decode_image(img, channels=3) img = tf.image.convert_image_dtype(img, tf.float32) shape = tf.cast(tf.shape(img)[:-1], tf.float32) long_dim = max(shape) scale = max_dim / long_dim new_shape = tf.cast(shape * scale, tf.int32) img = tf.image.resize(img, new_shape) img = img[tf.newaxis, :] return img

Crie uma função simples para exibir uma imagem:

def imshow(image, title=None): if len(image.shape) > 3: image = tf.squeeze(image, axis=0) plt.imshow(image) if title: plt.title(title)
content_image = load_img(content_path) style_image = load_img(style_path) plt.subplot(1, 2, 1) imshow(content_image, 'Content Image') plt.subplot(1, 2, 2) imshow(style_image, 'Style Image')

Transferência rápida de estilo usando o TF-Hub

Este tutorial demonstra o algoritmo de transferência de estilo original, que otimiza o conteúdo da imagem para um estilo específico. Antes de darmos maiores detalhes, vejamos como o modelo do TensorFlow Hub faz isso:

import tensorflow_hub as hub hub_model = hub.load('https://tfhub.dev/google/magenta/arbitrary-image-stylization-v1-256/2') stylized_image = hub_model(tf.constant(content_image), tf.constant(style_image))[0] tensor_to_image(stylized_image)

Definir as representações de conteúdo e estilo

Use as camadas intermediárias do modelo para obter as representações de conteúdo e estilo da imagem. Começando pela camada de entrada da rede, as primeiras ativações de camada representam as características de baixo nível, como contornos e texturas. À medida que você executa os passos da rede, as camadas finais representam características de alto nível: partes de objetos, como rodas ou olhos. Neste caso, você está usando a arquitetura de rede VGG19, uma rede de classificação de imagens pré-treinada. Essas camadas intermediárias são necessárias para definir a representação do conteúdo e do estilo das imagens. Para uma imagem de entrada, tente compatibilizar as representações correspondentes de estilo e conteúdo alvo nessas camadas intermediárias.

Carregue um VGG19 e execute um teste na imagem para garantir que o uso esteja correto:

x = tf.keras.applications.vgg19.preprocess_input(content_image*255) x = tf.image.resize(x, (224, 224)) vgg = tf.keras.applications.VGG19(include_top=True, weights='imagenet') prediction_probabilities = vgg(x) prediction_probabilities.shape
predicted_top_5 = tf.keras.applications.vgg19.decode_predictions(prediction_probabilities.numpy())[0] [(class_name, prob) for (number, class_name, prob) in predicted_top_5]

Agora, carregue um VGG19 sem o cabeçalho de classificação e liste os nomes das camadas:

vgg = tf.keras.applications.VGG19(include_top=False, weights='imagenet') print() for layer in vgg.layers: print(layer.name)

Escolha camadas intermediárias da rede para representar o estilo e o conteúdo da imagem:

content_layers = ['block5_conv2'] style_layers = ['block1_conv1', 'block2_conv1', 'block3_conv1', 'block4_conv1', 'block5_conv1'] num_content_layers = len(content_layers) num_style_layers = len(style_layers)

Camadas intermediárias para estilo e conteúdo

Por que essas saídas intermediárias dentro da nossa rede de classificação de imagens pré-treinada permite a definição de representações de estilo e classificação?

De forma geral, para que uma rede realize classificação de imagens (que essa rede foi treinada para fazer), ela precisa entender a imagem. Isso requer pegar a imagem bruta como pixels de entrada e criar uma representação interna que converta os pixels da imagem bruta em uma compreensão complexa das características presentes na imagem.

Também é por isso que redes neurais convolucionais conseguem generalizar bem: elas conseguem capturar as invariâncias e características determinantes de classes (por exemplo, gatos versus cachorros), que são agnósticas quanto ao ruído de segundo plano e outros inconvenientes. Portanto, entre a alimentação da imagem bruta no modelo e o rótulo de classificação da saída, o modelo serve como um extrator de características complexas. Ao acessar as camadas intermediárias do modelo, você consegue descrever o conteúdo e o estilo das imagens de entrada.

Criação do modelo

As redes em tf.keras.applications foram criadas para que você possa extrair facilmente os valores de camadas intermediárias usando a API funcional do Keras.

Para definir um modelo usando a API funcional, especifique as entradas e as saídas:

model = Model(inputs, outputs)

A função abaixo cria um modelo VGG19 que retorna uma lista de saídas de camadas intermediárias:

def vgg_layers(layer_names): """ Creates a VGG model that returns a list of intermediate output values.""" # Load our model. Load pretrained VGG, trained on ImageNet data vgg = tf.keras.applications.VGG19(include_top=False, weights='imagenet') vgg.trainable = False outputs = [vgg.get_layer(name).output for name in layer_names] model = tf.keras.Model([vgg.input], outputs) return model

Para criar o modelo:

style_extractor = vgg_layers(style_layers) style_outputs = style_extractor(style_image*255) #Look at the statistics of each layer's output for name, output in zip(style_layers, style_outputs): print(name) print(" shape: ", output.numpy().shape) print(" min: ", output.numpy().min()) print(" max: ", output.numpy().max()) print(" mean: ", output.numpy().mean()) print()

Calcular o estilo

O conteúdo de uma imagem é representado pelos valores dos mapas de características intermediárias.

Então, o estilo de uma imagem pode ser descrito por médias e correlações dos diferentes mapas de características. Calcule uma matriz de Gram que inclua essa informação calculando o produto externo do vetor de características consigo mesmo em cada local e fazendo a média do produto externo de todos os locais. Essa matriz de Gram pode ser calculada para uma camada específica da seguinte forma:

Gcdl=ijFijcl(x)Fijdl(x)IJG^l_{cd} = \frac{\sum_{ij} F^l_{ijc}(x)F^l_{ijd}(x)}{IJ}

Isso pode ser implementado de forma concisa usando a função tf.linalg.einsum:

def gram_matrix(input_tensor): result = tf.linalg.einsum('bijc,bijd->bcd', input_tensor, input_tensor) input_shape = tf.shape(input_tensor) num_locations = tf.cast(input_shape[1]*input_shape[2], tf.float32) return result/(num_locations)

Extrair o estilo e o conteúdo

Crie um modelo que retorne os tensores de estilo e conteúdo.

class StyleContentModel(tf.keras.models.Model): def __init__(self, style_layers, content_layers): super(StyleContentModel, self).__init__() self.vgg = vgg_layers(style_layers + content_layers) self.style_layers = style_layers self.content_layers = content_layers self.num_style_layers = len(style_layers) self.vgg.trainable = False def call(self, inputs): "Expects float input in [0,1]" inputs = inputs*255.0 preprocessed_input = tf.keras.applications.vgg19.preprocess_input(inputs) outputs = self.vgg(preprocessed_input) style_outputs, content_outputs = (outputs[:self.num_style_layers], outputs[self.num_style_layers:]) style_outputs = [gram_matrix(style_output) for style_output in style_outputs] content_dict = {content_name: value for content_name, value in zip(self.content_layers, content_outputs)} style_dict = {style_name: value for style_name, value in zip(self.style_layers, style_outputs)} return {'content': content_dict, 'style': style_dict}

Quando uma imagem faz uma chamada, esse modelo retorna uma matriz de Gram (estilo) das style_layers e de conteúdo das content_layers:

extractor = StyleContentModel(style_layers, content_layers) results = extractor(tf.constant(content_image)) print('Styles:') for name, output in sorted(results['style'].items()): print(" ", name) print(" shape: ", output.numpy().shape) print(" min: ", output.numpy().min()) print(" max: ", output.numpy().max()) print(" mean: ", output.numpy().mean()) print() print("Contents:") for name, output in sorted(results['content'].items()): print(" ", name) print(" shape: ", output.numpy().shape) print(" min: ", output.numpy().min()) print(" max: ", output.numpy().max()) print(" mean: ", output.numpy().mean())

Executar o método do gradiente descendente

Com esse extrator de estilo e conteúdo, agora você pode implementar o algoritmo de transferência de estilo. Faça isso calculando o erro quadrático médio da saída da sua imagem em relação a cada alvo, depois faça a soma ponderada dessas perdas.

Defina seus valores alvo de estilo e conteúdo:

style_targets = extractor(style_image)['style'] content_targets = extractor(content_image)['content']

Defina uma tf.Variable, que conterá a imagem a ser otimizada. Para deixar isso rápido, inicialize-a com a imagem de conteúdo (a tf.Variable precisa ter o mesmo formato que o da imagem de conteúdo):

image = tf.Variable(content_image)

Como essa imagem é um float, defina uma função para manter os valores de pixels entre e 0 e 1:

def clip_0_1(image): return tf.clip_by_value(image, clip_value_min=0.0, clip_value_max=1.0)

Crie um otimizador. O artigo recomenda o LBFGS, mas o Adam também funciona bem:

opt = tf.keras.optimizers.Adam(learning_rate=0.02, beta_1=0.99, epsilon=1e-1)

Para otimizar, use uma combinação ponderada das duas perdas para obter a perda total:

style_weight=1e-2 content_weight=1e4
def style_content_loss(outputs): style_outputs = outputs['style'] content_outputs = outputs['content'] style_loss = tf.add_n([tf.reduce_mean((style_outputs[name]-style_targets[name])**2) for name in style_outputs.keys()]) style_loss *= style_weight / num_style_layers content_loss = tf.add_n([tf.reduce_mean((content_outputs[name]-content_targets[name])**2) for name in content_outputs.keys()]) content_loss *= content_weight / num_content_layers loss = style_loss + content_loss return loss

Use tf.GradientTape para atualizar a imagem.

@tf.function() def train_step(image): with tf.GradientTape() as tape: outputs = extractor(image) loss = style_content_loss(outputs) grad = tape.gradient(loss, image) opt.apply_gradients([(grad, image)]) image.assign(clip_0_1(image))

Agora, execute alguns passos para testar:

train_step(image) train_step(image) train_step(image) tensor_to_image(image)

Como está funcionando, faça uma otimização mais longa:

import time start = time.time() epochs = 10 steps_per_epoch = 100 step = 0 for n in range(epochs): for m in range(steps_per_epoch): step += 1 train_step(image) print(".", end='', flush=True) display.clear_output(wait=True) display.display(tensor_to_image(image)) print("Train step: {}".format(step)) end = time.time() print("Total time: {:.1f}".format(end-start))

Perda de variação total

Uma desvantagem dessa implementação básica é que são produzidos muitos artefatos de alta frequência. Para diminuir a quantidade de artefatos, use um termo de regularização explícita nos componentes de alta frequência da imagem. Na transferência de estilo, geralmente isso é chamado de perda de variação total:

def high_pass_x_y(image): x_var = image[:, :, 1:, :] - image[:, :, :-1, :] y_var = image[:, 1:, :, :] - image[:, :-1, :, :] return x_var, y_var
x_deltas, y_deltas = high_pass_x_y(content_image) plt.figure(figsize=(14, 10)) plt.subplot(2, 2, 1) imshow(clip_0_1(2*y_deltas+0.5), "Horizontal Deltas: Original") plt.subplot(2, 2, 2) imshow(clip_0_1(2*x_deltas+0.5), "Vertical Deltas: Original") x_deltas, y_deltas = high_pass_x_y(image) plt.subplot(2, 2, 3) imshow(clip_0_1(2*y_deltas+0.5), "Horizontal Deltas: Styled") plt.subplot(2, 2, 4) imshow(clip_0_1(2*x_deltas+0.5), "Vertical Deltas: Styled")

Isso mostra como os componentes de alta frequência aumentaram.

Além disso, esse componente de alta frequência é basicamente um detector de contornos. Você consegue uma saída similar com o detector de contornos Sobel. Por exemplo:

plt.figure(figsize=(14, 10)) sobel = tf.image.sobel_edges(content_image) plt.subplot(1, 2, 1) imshow(clip_0_1(sobel[..., 0]/4+0.5), "Horizontal Sobel-edges") plt.subplot(1, 2, 2) imshow(clip_0_1(sobel[..., 1]/4+0.5), "Vertical Sobel-edges")

A perda de regularização associada a isso é a soma dos quadrados dos valores:

def total_variation_loss(image): x_deltas, y_deltas = high_pass_x_y(image) return tf.reduce_sum(tf.abs(x_deltas)) + tf.reduce_sum(tf.abs(y_deltas))
total_variation_loss(image).numpy()

Isso demonstrou o que ele faz. Mas você não precisa implementar, pois o TensorFlow inclui uma implementação padrão:

tf.image.total_variation(image).numpy()

Executar a otimização novamente

Escolha um peso para total_variation_loss:

total_variation_weight=30

Agora, inclua na função train_step:

@tf.function() def train_step(image): with tf.GradientTape() as tape: outputs = extractor(image) loss = style_content_loss(outputs) loss += total_variation_weight*tf.image.total_variation(image) grad = tape.gradient(loss, image) opt.apply_gradients([(grad, image)]) image.assign(clip_0_1(image))

Reinicialize a variável de imagem e o otimizador:

opt = tf.keras.optimizers.Adam(learning_rate=0.02, beta_1=0.99, epsilon=1e-1) image = tf.Variable(content_image)

E execute a otimização:

import time start = time.time() epochs = 10 steps_per_epoch = 100 step = 0 for n in range(epochs): for m in range(steps_per_epoch): step += 1 train_step(image) print(".", end='', flush=True) display.clear_output(wait=True) display.display(tensor_to_image(image)) print("Train step: {}".format(step)) end = time.time() print("Total time: {:.1f}".format(end-start))

Por fim, salve o resultado:

file_name = 'stylized-image.png' tensor_to_image(image).save(file_name) try: from google.colab import files except ImportError: pass else: files.download(file_name)

Saiba mais

Este tutorial demonstra o algoritmo de transferência de estilo original. Para ver uma aplicação simples de transferência de estilo, confira este tutorial para saber mais sobre como usar o modelo arbitrário de transferência de estilo de imagem do TensorFlow Hub.