Bishe 深層学習画像スタイル転送

0 はじめに

今日は先輩がマシンビジョンのプロジェクトを紹介します

深層学習畳み込みニューラル ネットワークに基づく花認識

ピクチャ スタイルの移行とは、図に示すように、あるピクチャのスタイルを別のピクチャに変換することです。

ここに画像の説明を挿入
一連の機能変換の後、元の画像にはスタイル トランスファーと呼ばれる新しいテクスチャ機能があります。

1 VGG ネットワーク

スタイルの移行を実装する前に、VGG ネットワークについて簡単に理解する必要があります (VGG ネットワークは畳み込み抽出機能と正確な画像認識効率のネットワーク構造を引き続き使用するため、ここでは VGG ネットワークを使用して画像スタイルの移行を実行します)。

ここに画像の説明を挿入
上の図に示すように、AE の各列は VGG ネットワークの構造原理を表します。以下の図に示すように、VGG-11、VGG-13、VGG-16、VGG-19 です。 VGG-19 ネットワーク 構造は、最終的に分類構造になる可能性があります。

ここに画像の説明を挿入

2 スタイルの移行

イメージのスタイルを伝えるには、明確にする必要があることが 2 つあります。

  • 生成された画像には、元の画像のコンテンツ特性が必要です
  • 生成された画像には、スタイル ピクチャのテクスチャ特性が必要です。

これらの 2 つの点によると、スタイル トランスファーを達成するためには、2 つの損失値が必要であると判断できます
。生成された画像のテクスチャ機能とスタイル画像のテクスチャ機能の損失です。

画像からさまざまな特徴 (コンテンツの特徴とテクスチャの特徴) を抽出するには、トレーニングにさまざまな畳み込み構造を使用するだけで済みます。この時点で、2 つのニューラル ネットワークを使用する必要があります。

VGG ネットワークに戻ると、VGG ネットワークは継続的に畳み込み層を使用して特徴を抽出し、特徴を使用してアイテムを分類するため、ネットワーク内の抽出されたコンテンツとテクスチャの特徴のパラメーターを移行できます。したがって、VGG ネットワークを介して生成された画像の特徴を抽出し、コンテンツとテクスチャの特徴の損失をそれぞれ計算する必要があります。

ここに画像の説明を挿入
図に示すように、初期化画像 x (入力画像) をランダム画像と仮定し、fw (image Transform Net) ネットワークを介して生成し、画像 y を生成します。
このとき、y は、スタイル イメージ ys の特性を計算して loss_style を取得し、コンテンツ イメージ yc の特性を計算して loss_content を取得する必要があり、loss=loss_style+loss_content と仮定すると、fw のネットワーク パラメータをトレーニングできます。

これで、インターネット上で非常に一般的な写真を見ることができます。

ここに画像の説明を挿入
最初に描いた絵と比べると、これはVGGでの損失評価プロセスの改良です。

改良の結果は、次の 2 つの側面に分けることができます。

  • (1)コンテンツロス
  • (2) スタイルロス

3 コンテンツの喪失

上の図で使用されているモデルは VGG-16 であるため、VGG-16 の relu3-3 で 2 つの写真から得られた特徴の損失を計算することと等価です. 計算関数は次のとおりです。

ここに画像の説明を挿入

つまり、yc によって得られる特徴行列を φ(y) とすると、画像を生成して得られる特徴行列は φ(y^) となり、c=φ.channel、w=φ.weight、h=φ.height となります。 、次にあります:

ここに画像の説明を挿入

コード:

def content_loss(content_img, rand_img):
    content_layers = [('relu3_3', 1.0)]
    content_loss = 0.0
    # 逐个取出衡量内容损失的vgg层名称及对应权重
    for layer_name, weight in content_layers:

        # 计算特征矩阵
        p = get_vgg(content_img, layer_name)
        x = get_vgg(rand_img, layer_name)
        # 长x宽xchannel
        M = p.shape[1] * p.shape[2] * p.shape[3]

        # 根据公式计算损失,并进行累加
        content_loss += (1.0 / M) * tf.reduce_sum(tf.pow(p - x, 2)) * weight

    # 将损失对层数取平均
    content_loss /= len(content_layers)
    return content_loss

4 スタイルロス

スタイルの損失は複数の機能によって計算されます。まず、グラム マトリックスを計算する必要があります。

ここに画像の説明を挿入
グラム行列は、実際には特徴間の偏心共分散行列 (つまり、平均を差し引かない共分散行列) と見なすことができます. 特徴マップでは、各数値は特定の位置での特定のフィルターの畳み込みから得られます. したがって、各数値は、は特徴の強さを表し、グラムが実際に計算するのは、どの 2 つの特徴が同時に現れるか、どの 2 つが干満であるかなど、2 つの特徴間の相関関係です。同時に、グラムの対角要素も反映されます。画像に現れる各特徴の量したがって、グラムは画像全体の一般的なスタイルを把握するのに役立ちます。スタイルを表すグラム マトリックスを使用して、2 つの画像スタイルの違いを測定するには、それらのグラム マトリックスの違いを比較するだけで済みます。したがって、損失を計算する場合、関数は次のようになります。

ここに画像の説明を挿入
実際の使用では、損失のレベルは一般的に、VGG16 の 2、4、7、10 番目の畳み込み層など、低い層から高い層まで複数の層を選択し、各層のスタイル損失を追加します。

ここに画像の説明を挿入
3 番目の部分は必須ではなく、総変動損失と呼ばれます。これは実際には平滑化項 (正則化項) であり、その目的は、生成された画像を局所的に可能な限り滑らかにすることです。その定義は、マルコフ確率場 (MRF) で使用される平滑化項と非常によく似ています。ここで、yn+1 は yn の隣接ピクセルです。

コードは上記の機能を実装します。

# 求gamm矩阵
def gram(x, size, deep):
    x = tf.reshape(x, (size, deep))
    g = tf.matmul(tf.transpose(x), x)
    return g

def style_loss(style_img, rand_img):
    style_layers = [('relu1_2', 0.25), ('relu2_2', 0.25), ('relu3_3', 0.25), ('reluv4_3', 0.25)]
    style_loss = 0.0
    # 逐个取出衡量风格损失的vgg层名称及对应权重
    for layer_name, weight in style_layers:

        # 计算特征矩阵
        a = get_vgg(style_img, layer_name)
        x = get_vgg(rand_img, layer_name)

        # 长x宽
        M = a.shape[1] * a.shape[2]
        N = a.shape[3]

        # 计算gram矩阵
        A = gram(a, M, N)
        G = gram(x, M, N)

        # 根据公式计算损失,并进行累加
        style_loss += (1.0 / (4 * M * M * N * N)) * tf.reduce_sum(tf.pow(G - A, 2)) * weight
    # 将损失对层数取平均
    style_loss /= len(style_layers)
    return style_loss

5つのメインコードの実装

コードの実装は、主に 4 つのステップに分かれています。

  • 1. 画像をランダムに生成する
  • 2. 内容とスタイルの写真を読む
  • 3. 総損失を計算する
  • 4.生成された画像のパラメーターをトレーニングおよび変更して、損失を最小限に抑えます
def main():
    # 生成图片
    rand_img = tf.Variable(random_img(WIGHT, HEIGHT), dtype=tf.float32)
    with tf.Session() as sess:

        content_img = cv2.imread('content.jpg')
        style_img = cv2.imread('style.jpg')

        # 计算loss值
        cost = ALPHA * content_loss(content_img, rand_img) + BETA * style_loss(style_img, rand_img)
        optimizer = tf.train.AdamOptimizer(LEARNING_RATE).minimize(cost)

        sess.run(tf.global_variables_initializer())
        
        for step in range(TRAIN_STEPS):
            # 训练
            sess.run([optimizer,  rand_img])

            if step % 50 == 0:
                img = sess.run(rand_img)
                img = np.clip(img, 0, 255).astype(np.uint8)
                name = OUTPUT_IMAGE + "//" + str(step) + ".jpg"
                cv2.imwrite(name, img)

6 移行モデルの実装

損失値を解く際には、複数のネットワーク層で固有値を取得し、固有値に従って加重加算する必要があるため、既存の VGG ネットワークとそのパラメーターに基づいて VGG ネットワークを再構築する必要があります。
注: VGG-19 ネットワークはここで使用されます:

再構築する前に、まず Google がトレーニングした VGG-19 ネットワークをダウンロードして、トレーニング済みのパラメーターを抽出し、再構築された VGG-19 ネットワークでそれらを再利用する必要があります。

ここに画像の説明を挿入
.mat ファイルをダウンロードしたら、ネットワークを再構築できます。VGG-19 ネットワークのネットワーク構造は、上の図 1 の E ネットワークのようなものであり、E ネットワークの構造に従ってネットワークを再構築できることが知られています。

ここに画像の説明を挿入
再構築とは、VGG-19 モデルの構造に合わせて同じ構造のニューラル ネットワークを再構築し、学習済みのパラメータを新しいネットワークのパラメータとして抽出し、変更できない定数として設定することです。

def vgg19():
    layers=(
        'conv1_1','relu1_1','conv1_2','relu1_2','pool1',
        'conv2_1','relu2_1','conv2_2','relu2_2','pool2',
        'conv3_1','relu3_1','conv3_2','relu3_2','conv3_3','relu3_3','conv3_4','relu3_4','pool3',
        'conv4_1','relu4_1','conv4_2','relu4_2','conv4_3','relu4_3','conv4_4','relu4_4','pool4',
        'conv5_1','relu5_1','conv5_2','relu5_2','conv5_3','relu5_3','conv5_4','relu5_4','pool5'
    )
    vgg = scipy.io.loadmat('D://python//imagenet-vgg-verydeep-19.mat')
    weights = vgg['layers'][0]

    network={
    
    }
    net = tf.Variable(np.zeros([1, 300, 450, 3]), dtype=tf.float32)
    network['input'] = net
    for i,name in enumerate(layers):
        layer_type=name[:4]
        if layer_type=='conv':
            kernels = weights[i][0][0][0][0][0]
            bias = weights[i][0][0][0][0][1]
            conv=tf.nn.conv2d(net,tf.constant(kernels),strides=(1,1,1,1),padding='SAME',name=name)
            net=tf.nn.relu(conv + bias)
        elif layer_type=='pool':
            net=tf.nn.max_pool(net,ksize=(1,2,2,1),strides=(1,2,2,1),padding='SAME')
        network[name]=net
    return network

スタイルの特徴とコンテンツの特徴を計算するときにデータが変更されないため、トレーニング時間を節約するために、トレーニングの前に特徴の結果が計算されます (この関数は次のコード get_neck() 関数にカプセル化されています)。

全体的なコードは次のとおりです。

import tensorflow as tf
import numpy as np
import scipy.io
import cv2
import scipy.misc

HEIGHT = 300
WIGHT = 450
LEARNING_RATE = 1.0
NOISE = 0.5
ALPHA = 1
BETA = 500

TRAIN_STEPS = 200

OUTPUT_IMAGE = "D://python//img"
STYLE_LAUERS = [('conv1_1', 0.2), ('conv2_1', 0.2), ('conv3_1', 0.2), ('conv4_1', 0.2), ('conv5_1', 0.2)]
CONTENT_LAYERS = [('conv4_2', 0.5), ('conv5_2',0.5)]


def vgg19():
    layers=(
        'conv1_1','relu1_1','conv1_2','relu1_2','pool1',
        'conv2_1','relu2_1','conv2_2','relu2_2','pool2',
        'conv3_1','relu3_1','conv3_2','relu3_2','conv3_3','relu3_3','conv3_4','relu3_4','pool3',
        'conv4_1','relu4_1','conv4_2','relu4_2','conv4_3','relu4_3','conv4_4','relu4_4','pool4',
        'conv5_1','relu5_1','conv5_2','relu5_2','conv5_3','relu5_3','conv5_4','relu5_4','pool5'
    )
    vgg = scipy.io.loadmat('D://python//imagenet-vgg-verydeep-19.mat')
    weights = vgg['layers'][0]

    network={
    
    }
    net = tf.Variable(np.zeros([1, 300, 450, 3]), dtype=tf.float32)
    network['input'] = net
    for i,name in enumerate(layers):
        layer_type=name[:4]
        if layer_type=='conv':
            kernels = weights[i][0][0][0][0][0]
            bias = weights[i][0][0][0][0][1]
            conv=tf.nn.conv2d(net,tf.constant(kernels),strides=(1,1,1,1),padding='SAME',name=name)
            net=tf.nn.relu(conv + bias)
        elif layer_type=='pool':
            net=tf.nn.max_pool(net,ksize=(1,2,2,1),strides=(1,2,2,1),padding='SAME')
        network[name]=net
    return network


# 求gamm矩阵
def gram(x, size, deep):
    x = tf.reshape(x, (size, deep))
    g = tf.matmul(tf.transpose(x), x)
    return g


def style_loss(sess, style_neck, model):
    style_loss = 0.0
    for layer_name, weight in STYLE_LAUERS:
        # 计算特征矩阵
        a = style_neck[layer_name]
        x = model[layer_name]
        # 长x宽
        M = a.shape[1] * a.shape[2]
        N = a.shape[3]

        # 计算gram矩阵
        A = gram(a, M, N)
        G = gram(x, M, N)

        # 根据公式计算损失,并进行累加
        style_loss += (1.0 / (4 * M * M * N * N)) * tf.reduce_sum(tf.pow(G - A, 2)) * weight
        # 将损失对层数取平均
    style_loss /= len(STYLE_LAUERS)
    return style_loss


def content_loss(sess, content_neck, model):
    content_loss = 0.0
    # 逐个取出衡量内容损失的vgg层名称及对应权重

    for layer_name, weight in CONTENT_LAYERS:
        # 计算特征矩阵
        p = content_neck[layer_name]
        x = model[layer_name]
        # 长x宽xchannel

        M = p.shape[1] * p.shape[2]
        N = p.shape[3]

        lss = 1.0 / (M * N)
        content_loss += lss * tf.reduce_sum(tf.pow(p - x, 2)) * weight
        # 根据公式计算损失,并进行累加

    # 将损失对层数取平均
    content_loss /= len(CONTENT_LAYERS)
    return content_loss


def random_img(height, weight, content_img):
    noise_image = np.random.uniform(-20, 20, [1, height, weight, 3])
    random_img = noise_image * NOISE + content_img * (1 - NOISE)
    return random_img


def get_neck(sess, model, content_img, style_img):
    sess.run(tf.assign(model['input'], content_img))
    content_neck = {
    
    }
    for layer_name, weight in CONTENT_LAYERS:
        # 计算特征矩阵
        p = sess.run(model[layer_name])
        content_neck[layer_name] = p
    sess.run(tf.assign(model['input'], style_img))
    style_content = {
    
    }
    for layer_name, weight in STYLE_LAUERS:
        # 计算特征矩阵
        a = sess.run(model[layer_name])
        style_content[layer_name] = a
    return content_neck, style_content


def main():
    model = vgg19()
    content_img = cv2.imread('D://a//content1.jpg')
    content_img = cv2.resize(content_img, (450, 300))
    content_img = np.reshape(content_img, (1, 300, 450, 3)) - [128.0, 128.2, 128.0]
    style_img = cv2.imread('D://a//style1.jpg')
    style_img = cv2.resize(style_img, (450, 300))
    style_img = np.reshape(style_img, (1, 300, 450, 3)) - [128.0, 128.2, 128.0]

    # 生成图片
    rand_img = random_img(HEIGHT, WIGHT, content_img)

    with tf.Session() as sess:
        # 计算loss值
        content_neck, style_neck = get_neck(sess, model, content_img, style_img)
        cost = ALPHA * content_loss(sess, content_neck, model) + BETA * style_loss(sess, style_neck, model)
        optimizer = tf.train.AdamOptimizer(LEARNING_RATE).minimize(cost)

        sess.run(tf.global_variables_initializer())
        sess.run(tf.assign(model['input'], rand_img))
        for step in range(TRAIN_STEPS):
            print(step)
            # 训练
            sess.run(optimizer)

            if step % 10 == 0:
                img = sess.run(model['input'])
                img += [128, 128, 128]
                img = np.clip(img, 0, 255).astype(np.uint8)
                name = OUTPUT_IMAGE + "//" + str(step) + ".jpg"
                img = img[0]
                cv2.imwrite(name, img)

        img = sess.run(model['input'])
        img += [128, 128, 128]
        img = np.clip(img, 0, 255).astype(np.uint8)
        cv2.imwrite("D://end.jpg", img[0])

main()

7 エフェクト表示

ここに画像の説明を挿入

ラスト8

おすすめ

転載: blog.csdn.net/HUXINY/article/details/129459909