遷移網(wǎng)絡(luò)的應(yīng)用-圖像風(fēng)格遷移

圖片風(fēng)格遷移指的是將一個(gè)圖片的風(fēng)格轉(zhuǎn)換到另一個(gè)圖片中西采,如圖所示:

原圖片經(jīng)過一系列的特征變換美尸,具有了新的紋理特征,這就叫做風(fēng)格遷移骄瓣。

VGG網(wǎng)絡(luò)

在實(shí)現(xiàn)風(fēng)格遷移之前停巷,需要先簡單了解一下VGG網(wǎng)絡(luò)(由于VGG網(wǎng)絡(luò)不斷使用卷積提取特征的網(wǎng)絡(luò)結(jié)構(gòu)和準(zhǔn)確的圖像識(shí)別效率,在這里我們使用VGG網(wǎng)絡(luò)來進(jìn)行圖像的風(fēng)格遷移)榕栏。

圖1

如上圖所示畔勤,從A-E的每一列都表示了VGG網(wǎng)絡(luò)的結(jié)構(gòu)原理,其分別為:VGG-11扒磁,VGG-13庆揪,VGG-16,VGG-19渗磅,如下圖,一副圖片經(jīng)過VGG-19網(wǎng)絡(luò)結(jié)構(gòu)可以最后得到一個(gè)分類結(jié)構(gòu)检访。

風(fēng)格遷移

對(duì)一副圖像進(jìn)行風(fēng)格遷移始鱼,需要清楚的有兩點(diǎn)。

  1. 生成的圖像需要具有原圖片的內(nèi)容特征
  2. 生成的圖像需要具有風(fēng)格圖片的紋理特征

根據(jù)這兩點(diǎn)脆贵,可以確定医清,要想實(shí)現(xiàn)風(fēng)格遷移,需要有兩個(gè)loss值:
一個(gè)是生成圖片的內(nèi)容特征與原圖的內(nèi)容特征的loss卖氨,另一個(gè)是生成圖片的紋理特征與風(fēng)格圖片的紋理特征的loss会烙。

而對(duì)一張圖片進(jìn)行不同的特征(內(nèi)容特征和紋理特征)提取,只需要使用不同的卷積結(jié)構(gòu)進(jìn)行訓(xùn)練即可以得到筒捺。這時(shí)我們需要用到兩個(gè)神經(jīng)網(wǎng)絡(luò)柏腻。

再回到VGG網(wǎng)絡(luò)上,VGG網(wǎng)絡(luò)不斷使用卷積層來提取特征系吭,利用特征將物品進(jìn)行分類五嫂,所以該網(wǎng)絡(luò)中提取內(nèi)容和紋理特征的參數(shù)都可以進(jìn)行遷移使用。故需要將生成的圖片經(jīng)過VGG網(wǎng)絡(luò)的特征提取,再分別針對(duì)內(nèi)容和紋理進(jìn)行特征的loss計(jì)算沃缘。

如圖躯枢,假設(shè)初始化圖像x(Input image)是一張隨機(jī)圖片,我們經(jīng)過fw(image Transform Net)網(wǎng)絡(luò)進(jìn)行生成槐臀,生成圖片y锄蹂。
此時(shí)y需要和風(fēng)格圖片ys進(jìn)行特征的計(jì)算得到一個(gè)loss_style,與內(nèi)容圖片yc進(jìn)行特征的計(jì)算得到一個(gè)loss_content水慨,假設(shè)loss=loss_style+loss_content得糜,便可以對(duì)fw的網(wǎng)絡(luò)參數(shù)進(jìn)行訓(xùn)練。

現(xiàn)在就可以看網(wǎng)上很常見的一張圖片了:

圖1

相較于我畫的第一張圖讥巡,這即對(duì)VGG內(nèi)的loss求值過程進(jìn)行了細(xì)化掀亩。細(xì)化的結(jié)果可以分為兩個(gè)方面:(1)內(nèi)容損失 (2)風(fēng)格損失

內(nèi)容損失

由于圖1中使用的模型是VGG-16,那么即相當(dāng)于在VGG-16的relu3-3處欢顷,對(duì)兩張圖片求得的特征進(jìn)行計(jì)算求損失槽棍,計(jì)算的函數(shù)如下:

簡言之,假設(shè)yc求得的特征矩陣是φ(y)抬驴,生成圖片求得的特征矩陣為φ(y^)炼七,且

c=φ.channel,w=φ.weight布持,h=φ.height豌拙,則有:


可以簡單使用代碼實(shí)現(xiàn):

def content_loss(content_img, rand_img):
    content_layers = [('relu3_3', 1.0)]
    content_loss = 0.0
    # 逐個(gè)取出衡量內(nèi)容損失的vgg層名稱及對(duì)應(yīng)權(quán)重
    for layer_name, weight in content_layers:

        # 計(jì)算特征矩陣
        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]

        # 根據(jù)公式計(jì)算損失,并進(jìn)行累加
        content_loss += (1.0 / M) * tf.reduce_sum(tf.pow(p - x, 2)) * weight

    # 將損失對(duì)層數(shù)取平均
    content_loss /= len(content_layers)
    return content_loss

風(fēng)格損失

風(fēng)格損失由多個(gè)特征一同計(jì)算题暖,首先需要計(jì)算Gram Matrix

Gram Matrix實(shí)際上可看做是feature之間的偏心協(xié)方差矩陣(即沒有減去均值的協(xié)方差矩陣)按傅,在feature map中,每一個(gè)數(shù)字都來自于一個(gè)特定濾波器在特定位置的卷積胧卤,因此每個(gè)數(shù)字就代表一個(gè)特征的強(qiáng)度唯绍,而Gram計(jì)算的實(shí)際上是兩兩特征之間的相關(guān)性,哪兩個(gè)特征是同時(shí)出現(xiàn)的枝誊,哪兩個(gè)是此消彼長的等等况芒,同時(shí),Gram的對(duì)角線元素叶撒,還體現(xiàn)了每個(gè)特征在圖像中出現(xiàn)的量绝骚,因此,Gram有助于把握整個(gè)圖像的大體風(fēng)格祠够。有了表示風(fēng)格的Gram Matrix压汪,要度量兩個(gè)圖像風(fēng)格的差異,只需比較他們Gram Matrix的差異即可古瓤。 故在計(jì)算損失的時(shí)候函數(shù)如下:

在實(shí)際使用時(shí)蛾魄,該loss的層級(jí)一般選擇由低到高的多個(gè)層,比如VGG16中的第2、4滴须、7舌狗、10個(gè)卷積層,然后將每一層的style loss相加扔水。

第三個(gè)部分不是必須的痛侍,被稱為Total Variation Loss。實(shí)際上是一個(gè)平滑項(xiàng)(一個(gè)正則化項(xiàng))魔市,目的是使生成的圖像在局部上盡可能平滑主届,而它的定義和馬爾科夫隨機(jī)場(MRF)中使用的平滑項(xiàng)非常相似。 其中yn+1是yn的相鄰像素待德。

代碼實(shí)現(xiàn)以上函數(shù):

# 求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
    # 逐個(gè)取出衡量風(fēng)格損失的vgg層名稱及對(duì)應(yīng)權(quán)重
    for layer_name, weight in style_layers:

        # 計(jì)算特征矩陣
        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]

        # 計(jì)算gram矩陣
        A = gram(a, M, N)
        G = gram(x, M, N)

        # 根據(jù)公式計(jì)算損失君丁,并進(jìn)行累加
        style_loss += (1.0 / (4 * M * M * N * N)) * tf.reduce_sum(tf.pow(G - A, 2)) * weight
    # 將損失對(duì)層數(shù)取平均
    style_loss /= len(style_layers)
    return style_loss

主代碼實(shí)現(xiàn)

代碼實(shí)現(xiàn)主要分為4步:

1、隨機(jī)生成圖片
2将宪、讀取內(nèi)容和風(fēng)格圖片
3绘闷、計(jì)算總的loss
4、訓(xùn)練修改生成圖片的參數(shù)较坛,使得loss最小

簡單實(shí)現(xiàn)主要函數(shù)

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')

        # 計(jì)算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):
            # 訓(xùn)練
            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 + "http://" + str(step) + ".jpg"
                cv2.imwrite(name, img)

遷移模型實(shí)現(xiàn)

由于在進(jìn)行l(wèi)oss值求解時(shí)印蔗,需要在多個(gè)網(wǎng)絡(luò)層求得特征值,并根據(jù)特征值進(jìn)行帶權(quán)求和丑勤,所以需要根據(jù)已有的VGG網(wǎng)絡(luò)华嘹,取其參數(shù),重新建立VGG網(wǎng)絡(luò)法竞。
注意:在這里使用到的是VGG-19網(wǎng)絡(luò):

在重建的之前耙厚,首先應(yīng)該下載Google已經(jīng)訓(xùn)練好的VGG-19網(wǎng)絡(luò),以便提取出已經(jīng)訓(xùn)練好的參數(shù)岔霸,在重建的VGG-19網(wǎng)絡(luò)中重新利用薛躬。
下載地址:http://www.vlfeat.org/matconvnet/models/beta16/imagenet-vgg-verydeep-19.mat

下載得到.mat文件以后,便可以進(jìn)行網(wǎng)絡(luò)重建了秉剑。已知VGG-19網(wǎng)絡(luò)的網(wǎng)絡(luò)結(jié)構(gòu)如上述圖1中的E網(wǎng)絡(luò)泛豪,則可以根據(jù)E網(wǎng)絡(luò)的結(jié)構(gòu)對(duì)網(wǎng)絡(luò)重建稠诲,VGG-19網(wǎng)絡(luò):

進(jìn)行重建即根據(jù)VGG-19模型的結(jié)構(gòu)重新創(chuàng)建一個(gè)結(jié)構(gòu)相同的神經(jīng)網(wǎng)絡(luò)侦鹏,提取出已經(jīng)訓(xùn)練好的參數(shù)作為新的網(wǎng)絡(luò)的參數(shù),設(shè)置為不可改變的常量即可臀叙。

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

由于計(jì)算風(fēng)格特征和內(nèi)容特征時(shí)數(shù)據(jù)都不會(huì)改變略水,所以為了節(jié)省訓(xùn)練時(shí)間,在訓(xùn)練之前先計(jì)算出特征結(jié)果(該函數(shù)封裝在以下代碼get_neck()函數(shù)中)劝萤。
總的代碼如下:

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:
        # 計(jì)算特征矩陣
        a = style_neck[layer_name]
        x = model[layer_name]
        # 長x寬
        M = a.shape[1] * a.shape[2]
        N = a.shape[3]

        # 計(jì)算gram矩陣
        A = gram(a, M, N)
        G = gram(x, M, N)

        # 根據(jù)公式計(jì)算損失渊涝,并進(jìn)行累加
        style_loss += (1.0 / (4 * M * M * N * N)) * tf.reduce_sum(tf.pow(G - A, 2)) * weight
        # 將損失對(duì)層數(shù)取平均
    style_loss /= len(STYLE_LAUERS)
    return style_loss


def content_loss(sess, content_neck, model):
    content_loss = 0.0
    # 逐個(gè)取出衡量內(nèi)容損失的vgg層名稱及對(duì)應(yīng)權(quán)重

    for layer_name, weight in CONTENT_LAYERS:
        # 計(jì)算特征矩陣
        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
        # 根據(jù)公式計(jì)算損失,并進(jìn)行累加

    # 將損失對(duì)層數(shù)取平均
    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:
        # 計(jì)算特征矩陣
        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:
        # 計(jì)算特征矩陣
        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:
        # 計(jì)算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)
            # 訓(xùn)練
            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 + "http://" + 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()

由于時(shí)間原因,我訓(xùn)練了200次后得出的結(jié)果為:

可以增加訓(xùn)練迭代次數(shù)跨释,獲得效果更佳的風(fēng)格遷移圖像胸私。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市鳖谈,隨后出現(xiàn)的幾起案子岁疼,更是在濱河造成了極大的恐慌,老刑警劉巖缆娃,帶你破解...
    沈念sama閱讀 217,277評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件捷绒,死亡現(xiàn)場離奇詭異,居然都是意外死亡贯要,警方通過查閱死者的電腦和手機(jī)暖侨,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來崇渗,“玉大人字逗,你說我怎么就攤上這事∠匝海” “怎么了扳肛?”我有些...
    開封第一講書人閱讀 163,624評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長乘碑。 經(jīng)常有香客問我挖息,道長,這世上最難降的妖魔是什么兽肤? 我笑而不...
    開封第一講書人閱讀 58,356評(píng)論 1 293
  • 正文 為了忘掉前任套腹,我火速辦了婚禮,結(jié)果婚禮上资铡,老公的妹妹穿的比我還像新娘电禀。我一直安慰自己,他們只是感情好笤休,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評(píng)論 6 392
  • 文/花漫 我一把揭開白布尖飞。 她就那樣靜靜地躺著,像睡著了一般店雅。 火紅的嫁衣襯著肌膚如雪政基。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,292評(píng)論 1 301
  • 那天闹啦,我揣著相機(jī)與錄音沮明,去河邊找鬼。 笑死窍奋,一個(gè)胖子當(dāng)著我的面吹牛荐健,可吹牛的內(nèi)容都是我干的酱畅。 我是一名探鬼主播,決...
    沈念sama閱讀 40,135評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼江场,長吁一口氣:“原來是場噩夢啊……” “哼纺酸!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起址否,我...
    開封第一講書人閱讀 38,992評(píng)論 0 275
  • 序言:老撾萬榮一對(duì)情侶失蹤吁峻,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后在张,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體用含,經(jīng)...
    沈念sama閱讀 45,429評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評(píng)論 3 334
  • 正文 我和宋清朗相戀三年帮匾,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了啄骇。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,785評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡瘟斜,死狀恐怖缸夹,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情螺句,我是刑警寧澤虽惭,帶...
    沈念sama閱讀 35,492評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站蛇尚,受9級(jí)特大地震影響芽唇,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜取劫,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評(píng)論 3 328
  • 文/蒙蒙 一匆笤、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧谱邪,春花似錦炮捧、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至扯俱,卻和暖如春书蚪,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背蘸吓。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評(píng)論 1 269
  • 我被黑心中介騙來泰國打工善炫, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留撩幽,地道東北人库继。 一個(gè)月前我還...
    沈念sama閱讀 47,891評(píng)論 2 370
  • 正文 我出身青樓箩艺,卻偏偏與公主長得像,于是被迫代替她去往敵國和親宪萄。 傳聞我的和親對(duì)象是個(gè)殘疾皇子艺谆,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容