0 前言
?? 優(yōu)質(zhì)競賽項(xiàng)目系列,今天要分享的是
?? 深度學(xué)習(xí)圖像風(fēng)格遷移 - opencv python
該項(xiàng)目較為新穎,適合作為競賽課題方向,學(xué)長非常推薦!
??學(xué)長這里給一個題目綜合評分(每項(xiàng)滿分5分)
- 難度系數(shù):3分
- 工作量:3分
- 創(chuàng)新點(diǎn):4分
?? 更多資料, 項(xiàng)目分享:文章來源:http://www.zghlxwxcb.cn/news/detail-761370.html
https://gitee.com/dancheng-senior/postgraduate文章來源地址http://www.zghlxwxcb.cn/news/detail-761370.html
圖片風(fēng)格遷移指的是將一個圖片的風(fēng)格轉(zhuǎn)換到另一個圖片中,如圖所示:
原圖片經(jīng)過一系列的特征變換,具有了新的紋理特征,這就叫做風(fēng)格遷移。
1 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)確的圖像識別效率,在這里我們使用VGG網(wǎng)絡(luò)來進(jìn)行圖像的風(fēng)格遷移)。
如上圖所示,從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)可以最后得到一個分類結(jié)構(gòu)。
2 風(fēng)格遷移
對一副圖像進(jìn)行風(fēng)格遷移,需要清楚的有兩點(diǎn)。
- 生成的圖像需要具有原圖片的內(nèi)容特征
- 生成的圖像需要具有風(fēng)格圖片的紋理特征
根據(jù)這兩點(diǎn),可以確定,要想實(shí)現(xiàn)風(fēng)格遷移,需要有兩個loss值:
一個是生成圖片的內(nèi)容特征與原圖的內(nèi)容特征的loss,另一個是生成圖片的紋理特征與風(fēng)格圖片的紋理特征的loss。
而對一張圖片進(jìn)行不同的特征(內(nèi)容特征和紋理特征)提取,只需要使用不同的卷積結(jié)構(gòu)進(jìn)行訓(xùn)練即可以得到。這時我們需要用到兩個神經(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ò)的特征提取,再分別針對內(nèi)容和紋理進(jìn)行特征的loss計(jì)算。
如圖,假設(shè)初始化圖像x(Input image)是一張隨機(jī)圖片,我們經(jīng)過fw(image Transform Net)網(wǎng)絡(luò)進(jìn)行生成,生成圖片y。
此時y需要和風(fēng)格圖片ys進(jìn)行特征的計(jì)算得到一個loss_style,與內(nèi)容圖片yc進(jìn)行特征的計(jì)算得到一個loss_content,假設(shè)loss=loss_style+loss_content,便可以對fw的網(wǎng)絡(luò)參數(shù)進(jìn)行訓(xùn)練。
現(xiàn)在就可以看網(wǎng)上很常見的一張圖片了:
相較于我畫的第一張圖,這即對VGG內(nèi)的loss求值過程進(jìn)行了細(xì)化。
細(xì)化的結(jié)果可以分為兩個方面:
- (1)內(nèi)容損失
- (2)風(fēng)格損失
3 內(nèi)容損失
由于上圖中使用的模型是VGG-16,那么即相當(dāng)于在VGG-16的relu3-3處,對兩張圖片求得的特征進(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
# 逐個取出衡量內(nèi)容損失的vgg層名稱及對應(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
# 將損失對層數(shù)取平均
content_loss /= len(content_layers)
return content_loss
4 風(fēng)格損失
風(fēng)格損失由多個特征一同計(jì)算,首先需要計(jì)算Gram Matrix
Gram Matrix實(shí)際上可看做是feature之間的偏心協(xié)方差矩陣(即沒有減去均值的協(xié)方差矩陣),在feature
map中,每一個數(shù)字都來自于一個特定濾波器在特定位置的卷積,因此每個數(shù)字就代表一個特征的強(qiáng)度,而Gram計(jì)算的實(shí)際上是兩兩特征之間的相關(guān)性,哪兩個特征是同時出現(xiàn)的,哪兩個是此消彼長的等等,同時,Gram的對角線元素,還體現(xiàn)了每個特征在圖像中出現(xiàn)的量,因此,Gram有助于把握整個圖像的大體風(fēng)格。有了表示風(fēng)格的Gram
Matrix,要度量兩個圖像風(fēng)格的差異,只需比較他們Gram Matrix的差異即可。 故在計(jì)算損失的時候函數(shù)如下:
在實(shí)際使用時,該loss的層級一般選擇由低到高的多個層,比如VGG16中的第2、4、7、10個卷積層,然后將每一層的style loss相加。
第三個部分不是必須的,被稱為Total Variation
Loss。實(shí)際上是一個平滑項(xiàng)(一個正則化項(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
# 逐個取出衡量風(fēng)格損失的vgg層名稱及對應(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
# 將損失對層數(shù)取平均
style_loss /= len(style_layers)
return style_loss
5 主代碼實(shí)現(xiàn)
代碼實(shí)現(xiàn)主要分為4步:
-
1、隨機(jī)生成圖片
-
2、讀取內(nèi)容和風(fēng)格圖片
-
3、計(jì)算總的loss
-
4、訓(xùn)練修改生成圖片的參數(shù),使得loss最小
* 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)
6 遷移模型實(shí)現(xiàn)
由于在進(jìn)行l(wèi)oss值求解時,需要在多個網(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ò)中重新利用。
下載得到.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)對網(wǎng)絡(luò)重建,VGG-19網(wǎng)絡(luò):
進(jìn)行重建即根據(jù)VGG-19模型的結(jié)構(gòu)重新創(chuàng)建一個結(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ù)據(jù)都不會改變,所以為了節(jié)省訓(xùn)練時間,在訓(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
# 將損失對層數(shù)取平均
style_loss /= len(STYLE_LAUERS)
return style_loss
def content_loss(sess, content_neck, model):
content_loss = 0.0
# 逐個取出衡量內(nèi)容損失的vgg層名稱及對應(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)行累加
# 將損失對層數(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()
7 效果展示
8 最后
?? 更多資料, 項(xiàng)目分享:
https://gitee.com/dancheng-senior/postgraduate
到了這里,關(guān)于深度學(xué)習(xí)圖像風(fēng)格遷移 - opencv python 計(jì)算機(jī)競賽的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!