文章目錄
- 0 前言
- 1 VGG網絡
- 2 風格遷移
- 3 內容損失
- 4 風格損失
- 5 主代碼實現
- 6 遷移模型實現
- 7 效果展示
- 8 最后
0 前言
🔥 優質競賽項目系列,今天要分享的是
🚩 深度學習圖像風格遷移 - opencv python
該項目較為新穎,適合作為競賽課題方向,學長非常推薦!
🥇學長這里給一個題目綜合評分(每項滿分5分)
- 難度系數:3分
- 工作量:3分
- 創新點:4分
🧿 更多資料, 項目分享:
https://gitee.com/dancheng-senior/postgraduate
圖片風格遷移指的是將一個圖片的風格轉換到另一個圖片中,如圖所示:
原圖片經過一系列的特征變換,具有了新的紋理特征,這就叫做風格遷移。
1 VGG網絡
在實現風格遷移之前,需要先簡單了解一下VGG網絡(由于VGG網絡不斷使用卷積提取特征的網絡結構和準確的圖像識別效率,在這里我們使用VGG網絡來進行圖像的風格遷移)。
如上圖所示,從A-
E的每一列都表示了VGG網絡的結構原理,其分別為:VGG-11,VGG-13,VGG-16,VGG-19,如下圖,一副圖片經過VGG-19網絡結構可以最后得到一個分類結構。
2 風格遷移
對一副圖像進行風格遷移,需要清楚的有兩點。
- 生成的圖像需要具有原圖片的內容特征
- 生成的圖像需要具有風格圖片的紋理特征
根據這兩點,可以確定,要想實現風格遷移,需要有兩個loss值:
一個是生成圖片的內容特征與原圖的內容特征的loss,另一個是生成圖片的紋理特征與風格圖片的紋理特征的loss。
而對一張圖片進行不同的特征(內容特征和紋理特征)提取,只需要使用不同的卷積結構進行訓練即可以得到。這時我們需要用到兩個神經網絡。
再回到VGG網絡上,VGG網絡不斷使用卷積層來提取特征,利用特征將物品進行分類,所以該網絡中提取內容和紋理特征的參數都可以進行遷移使用。故需要將生成的圖片經過VGG網絡的特征提取,再分別針對內容和紋理進行特征的loss計算。
如圖,假設初始化圖像x(Input image)是一張隨機圖片,我們經過fw(image Transform Net)網絡進行生成,生成圖片y。
此時y需要和風格圖片ys進行特征的計算得到一個loss_style,與內容圖片yc進行特征的計算得到一個loss_content,假設loss=loss_style+loss_content,便可以對fw的網絡參數進行訓練。
現在就可以看網上很常見的一張圖片了:
相較于我畫的第一張圖,這即對VGG內的loss求值過程進行了細化。
細化的結果可以分為兩個方面:
- (1)內容損失
- (2)風格損失
3 內容損失
由于上圖中使用的模型是VGG-16,那么即相當于在VGG-16的relu3-3處,對兩張圖片求得的特征進行計算求損失,計算的函數如下:
簡言之,假設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寬xchannelM = 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 風格損失
風格損失由多個特征一同計算,首先需要計算Gram Matrix
Gram Matrix實際上可看做是feature之間的偏心協方差矩陣(即沒有減去均值的協方差矩陣),在feature
map中,每一個數字都來自于一個特定濾波器在特定位置的卷積,因此每個數字就代表一個特征的強度,而Gram計算的實際上是兩兩特征之間的相關性,哪兩個特征是同時出現的,哪兩個是此消彼長的等等,同時,Gram的對角線元素,還體現了每個特征在圖像中出現的量,因此,Gram有助于把握整個圖像的大體風格。有了表示風格的Gram
Matrix,要度量兩個圖像風格的差異,只需比較他們Gram Matrix的差異即可。 故在計算損失的時候函數如下:
在實際使用時,該loss的層級一般選擇由低到高的多個層,比如VGG16中的第2、4、7、10個卷積層,然后將每一層的style loss相加。
第三個部分不是必須的,被稱為Total Variation
Loss。實際上是一個平滑項(一個正則化項),目的是使生成的圖像在局部上盡可能平滑,而它的定義和馬爾科夫隨機場(MRF)中使用的平滑項非常相似。
其中yn+1是yn的相鄰像素。
代碼實現以上函數:
?
# 求gamm矩陣
def gram(x, size, deep):x = tf.reshape(x, (size, deep))g = tf.matmul(tf.transpose(x), x)return gdef 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、計算總的loss
-
4、訓練修改生成圖片的參數,使得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')# 計算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 遷移模型實現
由于在進行loss值求解時,需要在多個網絡層求得特征值,并根據特征值進行帶權求和,所以需要根據已有的VGG網絡,取其參數,重新建立VGG網絡。
注意:在這里使用到的是VGG-19網絡:
在重建的之前,首先應該下載Google已經訓練好的VGG-19網絡,以便提取出已經訓練好的參數,在重建的VGG-19網絡中重新利用。
下載得到.mat文件以后,便可以進行網絡重建了。已知VGG-19網絡的網絡結構如上述圖1中的E網絡,則可以根據E網絡的結構對網絡重建,VGG-19網絡:
進行重建即根據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'] = netfor 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]=netreturn network
由于計算風格特征和內容特征時數據都不會改變,所以為了節省訓練時間,在訓練之前先計算出特征結果(該函數封裝在以下代碼get_neck()函數中)。
總的代碼如下:
?
import tensorflow as tfimport numpy as npimport scipy.ioimport cv2import scipy.miscHEIGHT = 300WIGHT = 450LEARNING_RATE = 1.0NOISE = 0.5ALPHA = 1BETA = 500TRAIN_STEPS = 200OUTPUT_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'] = netfor 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]=netreturn network# 求gamm矩陣def gram(x, size, deep):x = tf.reshape(x, (size, deep))g = tf.matmul(tf.transpose(x), x)return gdef style_loss(sess, style_neck, model):style_loss = 0.0for 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_lossdef 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寬xchannelM = 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_lossdef 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_imgdef 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] = psess.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] = areturn content_neck, style_contentdef 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 最后
🧿 更多資料, 項目分享:
https://gitee.com/dancheng-senior/postgraduate