這篇文章是我的《AI繪畫系列》的第三篇,點選此處可以檢視整個系列的文章
本篇文章的原始碼可以在微信公眾號「01二進位制」後臺回覆「影象風格遷移」獲得
簡介
所謂影象風格遷移,是指將一幅內容圖A的內容,和一幅風格圖B的風格融合在一起,從而生成一張具有A圖風格和B圖內容的圖片C的技術。目前這個技術已經得到了比較廣泛的應用,這裡安利一個app——"大畫家",這個軟體可以將使用者的照片自動變換為具有藝術家的風格的圖片。
準備
其實剛開始寫這篇文章的時候我是準備詳細介紹下原理的,但是後來發現公式實在是太多了,就算寫了估計也沒什麼人看,而且這篇文章本來定位的使用者就是隻需要實現功能的新人。此外,有關風格遷移的原理解析的部落格實在是太多了,所以這裡我就把重點放在如何使用 TensorFlow 實現一個快速風格遷移的應用上,原理的解析就一帶而過了。如果只想實現這個效果的可以跳到**"執行"**一節。
第一步我們需要提前安裝好 TensorFlow,如果有GPU的小夥伴可以參考我的這篇文章搭建一個GPU環境:《AI 繪畫第一彈——用GPU為你的訓練過程加速》,如果打算直接用 CPU 執行的話,執行下面一行話就可以了
pip install numpy tensorflow scipy
複製程式碼
原理
本篇文章是基於A Neural Algorithm of Artistic Style一文提出的方法實現的,如果嫌看英文論文太麻煩的也可以檢視我對這篇文章的翻譯【譯】一種有關藝術風格遷移的神經網路演算法。
為了將風格圖的風格和內容圖的內容進行融合,所生成的圖片,在內容上應當儘可能接近內容圖,在風格上應當儘可能接近風格圖,因此需要定義內容損失函式和風格損失函式,經過加權後作為總的損失函式。
預訓練模型
CNN具有抽象和理解影象的能力,因此可以考慮將各個卷積層的輸出作為影象的內容,這裡我們採用了利用VGG19訓練好的模型來進行遷移學習,一般認為,卷積神經網路的訓練是對資料集特徵的一步步抽取的過程,從簡單的特徵,到複雜的特徵。訓練好的模型學習到的是對影象特徵的抽取方法,而該模型就是在imagenet資料集上預訓練的模型,所以理論上來說,也可以直接用於抽取其他影象的特徵,雖然效果可能沒有在原有資料集上訓練出的模型好,但是能夠節省大量的訓練時間,在特定情況下非常有用。
載入預訓練模型
def vggnet(self):
# 讀取預訓練的vgg模型
vgg = scipy.io.loadmat(settings.VGG_MODEL_PATH)
vgg_layers = vgg['layers'][0]
net = {}
# 使用預訓練的模型引數構建vgg網路的卷積層和池化層
# 全連線層不需要
# 注意,除了input之外,這裡引數都為constant,即常量
# 和平時不同,我們並不訓練vgg的引數,它們保持不變
# 需要進行訓練的是input,它即是我們最終生成的影象
net['input'] = tf.Variable(np.zeros([1, settings.IMAGE_HEIGHT, settings.IMAGE_WIDTH, 3]), dtype=tf.float32)
# 引數對應的層數可以參考vgg模型圖
net['conv1_1'] = self.conv_relu(net['input'], self.get_wb(vgg_layers, 0))
net['conv1_2'] = self.conv_relu(net['conv1_1'], self.get_wb(vgg_layers, 2))
net['pool1'] = self.pool(net['conv1_2'])
net['conv2_1'] = self.conv_relu(net['pool1'], self.get_wb(vgg_layers, 5))
net['conv2_2'] = self.conv_relu(net['conv2_1'], self.get_wb(vgg_layers, 7))
net['pool2'] = self.pool(net['conv2_2'])
net['conv3_1'] = self.conv_relu(net['pool2'], self.get_wb(vgg_layers, 10))
net['conv3_2'] = self.conv_relu(net['conv3_1'], self.get_wb(vgg_layers, 12))
net['conv3_3'] = self.conv_relu(net['conv3_2'], self.get_wb(vgg_layers, 14))
net['conv3_4'] = self.conv_relu(net['conv3_3'], self.get_wb(vgg_layers, 16))
net['pool3'] = self.pool(net['conv3_4'])
net['conv4_1'] = self.conv_relu(net['pool3'], self.get_wb(vgg_layers, 19))
net['conv4_2'] = self.conv_relu(net['conv4_1'], self.get_wb(vgg_layers, 21))
net['conv4_3'] = self.conv_relu(net['conv4_2'], self.get_wb(vgg_layers, 23))
net['conv4_4'] = self.conv_relu(net['conv4_3'], self.get_wb(vgg_layers, 25))
net['pool4'] = self.pool(net['conv4_4'])
net['conv5_1'] = self.conv_relu(net['pool4'], self.get_wb(vgg_layers, 28))
net['conv5_2'] = self.conv_relu(net['conv5_1'], self.get_wb(vgg_layers, 30))
net['conv5_3'] = self.conv_relu(net['conv5_2'], self.get_wb(vgg_layers, 32))
net['conv5_4'] = self.conv_relu(net['conv5_3'], self.get_wb(vgg_layers, 34))
net['pool5'] = self.pool(net['conv5_4'])
return net
複製程式碼
訓練思路
我們使用VGG中的一些層的輸出來表示圖片的內容特徵和風格特徵。比如,我使用[‘conv4_2’,’conv5_2’]
表示內容特徵,使用[‘conv1_1’,’conv2_1’,’conv3_1’,’conv4_1’]
表示風格特徵。在settings.py
中進行配置。
# 定義計算內容損失的vgg層名稱及對應權重的列表
CONTENT_LOSS_LAYERS = [('conv4_2', 0.5),('conv5_2',0.5)]
# 定義計算風格損失的vgg層名稱及對應權重的列表
STYLE_LOSS_LAYERS = [('conv1_1', 0.2), ('conv2_1', 0.2), ('conv3_1', 0.2), ('conv4_1', 0.2), ('conv5_1', 0.2)]
複製程式碼
內容損失函式
其中,X是噪聲圖片的特徵矩陣,P是內容圖片的特徵矩陣。M是P的長*寬,N是通道數。最終的內容損失為,每一層的內容損失加權和,再對層數取平均。
我知道很多人一看到數學公式就會頭疼,簡單理解就是這個公式可以讓模型在訓練過程中不斷的抽取圖片的內容。
風格損失函式
計算風格損失。我們使用風格影象在指定層上的特徵矩陣的GRAM矩陣來衡量其風格,風格損失可以定義為風格影象和噪音影象特徵矩陣的格萊姆矩陣的差值的L2範數。
對於每一層的風格損失函式,我們有:
其中M是特徵矩陣的長*寬,N是特徵矩陣的通道數。G為噪音影象特徵的Gram矩陣,A為風格圖片特徵的GRAM矩陣。最終的風格損失為,每一層的風格損失加權和,再對層數取平均。
同樣的,看不懂公式沒關係,你就把它理解為這個公式可以在訓練過程中獲取圖片的風格。
計算總損失函式並訓練模型
最後我們只需要將內容損失函式和風格損失函式帶入剛開始的公式中即可,要做的就是控制一些風格的權重和內容的權重:
# 內容損失權重
ALPHA = 1
# 風格損失權重
BETA = 500
複製程式碼
ALPHA越大,則最後生成的圖片內容資訊越大;同理,BETA越大,則最後生成的圖片風格化更嚴重。
當訓練開始時,我們根據內容圖片和噪聲,生成一張噪聲圖片。並將噪聲圖片餵給網路,計算loss,再根據loss調整噪聲圖片。將調整後的圖片餵給網路,重新計算loss,再調整,再計算…直到達到指定迭代次數,此時,噪聲圖片已兼具內容圖片的內容和風格圖片的風格,進行儲存即可。
執行
如果對上述原理不感興趣的,想直接執行程式碼的可以去微信公眾號「01二進位制」後臺回覆「影象風格遷移」獲得原始碼。接下來我們來說說如何使用這個程式碼跑出自己的繪畫。
首先看下專案結構:
images下有兩張圖片,分別是內容圖和風格圖,output下是訓練過程中產生的檔案,.mat檔案就是預訓練模型,models.py是我們實現的用於讀取預訓練模型的檔案,settings.py是配置檔案,train.py是最終的訓練檔案。
想要執行該專案,我們只需要執行python train.py
即可,想更改風格和內容的的話只要在images檔案中更換原先的圖片即可。當然你也可以在settings.py
中修改路徑:
# 內容圖片路徑
CONTENT_IMAGE = 'images/content.jpg'
# 風格圖片路徑
STYLE_IMAGE = 'images/style.jpg'
# 輸出圖片路徑
OUTPUT_IMAGE = 'output/output'
# 預訓練的vgg模型路徑
VGG_MODEL_PATH = 'imagenet-vgg-verydeep-19.mat'
複製程式碼
我們來看看訓練後的圖片:
最後
雖然通過上述程式碼我們可以實現影象的風格遷移,但是他有一個最大的缺點,就是無法儲存訓練好的模型,每次轉換風格都要重新跑一遍,如果使用CPU跑1000輪的話大約是在30分鐘左右,因此推薦大家使用GPU進行訓練。
但是即使使用上了GPU,訓練時間也無法滿足商業使用的,那有沒有什麼辦法可以儲存訓練好的風格模型,然後直接快速生成目標圖片呢?當然是有的,史丹佛的李飛飛發表過一篇《Perceptual Losses for Real-Time Style Transfer and Super-Resolution》,通過使用perceptual loss來替代per-pixels loss使用pre-trained的vgg model來簡化原先的loss計算,增加一個transform Network,直接生成Content image的style。這裡就不再多說了,感興趣的可以參考我下面給出的兩個連結:
以上就是本篇文章的全部內容,個人做下來感覺還是挺有意思的,因個人能力有限,文中如有紕漏錯誤之處,還請各位大佬指正,萬分感謝!
參考
歡迎關注我的微信公眾號:「01二進位制」,關注後即可獲得博主認真收集的計算機資料