天天看點

【Python】人工智能的味道 - 圖像風格遷移

作者:海洋餅幹叔叔

22. 人工智能的味道 - 圖像風格遷移

Python是人工智能程式設計的首選語言,至少當作者在鍵盤上敲下這行話時,這是事實。作為一本Python基礎入門性質的教科書,本書無法就Python在人工智能與深度學習中的應用展開深入讨論。要了解深度學習的内部細節,需要複雜的數學知識。不過作為應用層面的開發者,讀者或者不需要了解深度學習複雜的數學細節,簡單借助于開源的工具包和模型,也可以享受到人工智能的益處。本章通過圖像風格遷移這個示例,讓讀者嘗嘗人工智能的味道。

知識産權協定

允許以教育/教育訓練為目的向學生或閱聽人進行免費引用,展示或者講述,無須取得作者同意。

不允許以電子/紙質出版為目的進行摘抄或改編。

22.1 圖像風格遷移

【Python】人工智能的味道 - 圖像風格遷移

将上圖中左上角圖像的“風格”提取出來,應用到左下角的“原圖”上,生成右方的圖像,就是圖像風格遷移的研究内容。藝術風格是什麼,即便在藝術家的視角看,也是見人見智的。顯然不太可能從數學上準确地定義并求出圖像的風格。到底怎麼把一個缺乏明确數學定義的概念變成可以執行的程式,是困擾圖像風格遷移的研究者的主要問題。

在将人工神經網絡和深度學習應用于圖像風格遷移之前,人們的主要思路就是分析某一種風格的圖像,比如把畢加索畫的全部畫作進行統計分析,如小波分析,通過建立統計模型,然後疊代改變要做遷移的圖像,讓它更好地符合統計模型。這種方法有一個很大的缺點:一個程式隻能做某一種風格或場景的圖像風格遷移。

2015年,Gatsys等人在論文A Neural Algorithm for Artistic Style中首次将人工神經網絡用于圖像風格遷移,斯坦福大學的李飛飛和她的學生Justin Johnson等人于2016年進一步改進了算法,提高了圖像風格遷移的速度。本章示例程式中圖像風格遷移就是以Justin Johnson等人訓練的深度神經網絡模型為基礎的。

22.2 深度神經網絡

人工智能是一個研究了很多年的課題。它有衆多的學派,其中符号學派将機器學習看作逆向演繹,聯結學派對大腦進行逆向分析和模拟,進化學派在計算機上模拟生物進化,貝葉斯學派認為學習是一種機率推理形式,理論根基在于統計學。其中,聯結學派的主要工具就是人工神經網絡,人工神經網絡模拟了大腦神經元和神經元間突觸連接配接的結構和工作方式。而深度神經網絡又是人工神經網絡的一種。

【Python】人工智能的味道 - 圖像風格遷移

上圖展示了一個深度神經網絡(Deep Neural Network) - DNN的示意圖。圖中的節點稱之為神經元,神經元之間的連線模拟了人類大腦中腦細胞之間的突觸連接配接。可以簡單地認為,每個神經元具備計算或者是信号處理的功能,它通過突觸從别的神經元擷取輸入,經過計算/處理後再通過突觸向其它神經元輸出信号。我們已知,大腦細胞之間的每個突觸連接配接,其離子通道的導電能力是有差異的。與之對應,神經網絡神經元之間的連接配接權重參數用來表征該連接配接的重要程度。

神經元的層次,數量,神經元之間的連接配接關系可以稱為神經網絡的結構。突觸連接配接的權重、偏倚等資訊則稱為神經網絡的參數。為滿足特定任務的需要,比如在各種圖檔或者視訊中識别出貓,工程師會設計特定的神經網絡結構,并将大量經過标注的資料圖檔(圖檔中有貓的位置被人工标記)輸入神經網絡的輸入層,通過神經網絡的計算,從輸出層獲得識别結果,然後根據識别錯誤反向修改神經網絡内部的參數,經過多次疊代後,神經網絡的内部參數被修正到即便對于标注資料外的圖檔,神經網絡也能識别出貓的程度。這一過程稱為神經網絡的訓練。訓練的結果稱為模型,它包括了神經網絡的結構以及内部參數。

貓長什麼模樣,在數學上很難精确定義。我們可以認為,上述神經網絡的訓練過程有點類似于人類學習的過程,經過訓練的神經網絡模型包括了“何種模式的圖像是貓”的知識。這種知識雖然說不清道不明,但在實踐中卻非常有效。

同理,将梵高的星空等相似風格的畫作作為資料集,也可以對深度神經網絡進行訓練,訓練所得的模型包含了這類畫作的”風格是什麼“以及"如何把另一幅圖檔變成這種風格"的知識。本章的示例程式就是借助于Justin Johnson等人訓練好的圖像風格遷移網絡模型,對圖檔進行風格應用的。在調入模型,建立好深度神經網絡後,将圖檔”輸入“給圖像風格遷移網絡,該網絡會進行一系列的疊代計算,其輸出就是應用了對應風格的被修改過的圖檔。

22.3 程式解讀

本章的示例程式包含在目錄C22_StyleTransfer當中,主程式為StyleTransfer.py。子目錄images用于存儲風格遷移的輸入圖檔,models子目錄用于存儲風格圖檔及對應的圖像風格遷移網絡模型檔案。其中,模型檔案需要讀者自行下載下傳,詳情請見Readme.txt。

該程式需要用到opencv-python以及matplotlib庫。opencv是著名的C++語言編寫的計算機視覺及圖像處理工具包,本程式中,主要用它來讀取、轉換圖檔以及進行圖像風格遷移網絡的計算。matplotlib在本程式中用于互動和圖檔顯示。

22.3.1 程式的使用

先下載下傳并将模型檔案(擴充名為.t7)存入models子目錄。在Visual Source Code中打開StyleTransfer目錄,打開StyleTransfer.py并運作即可。按上下方向鍵可以切換圖檔,按左右方向鍵則可以切換風格模型。如本章開始的樣圖所示,左上角是風格圖檔,左下角是原圖,右邊則是風格遷移的結果圖檔。由于風格遷移網絡的計算量很大,是以切換圖檔或者風格時反應較慢。

22.3.2 資料結構

class App:
    def __init__(self):
        self.idxImage = 0   #目前被風格遷移的圖檔在images清單中的下标
        self.idxModel = 0   #目前使用的模型檔案在models清單中的下标
        self.images = glob.glob("images/*.jpg")  
        self.paintings,self.models = [],[] 
        t = glob.glob("models/*.jpg")
        for x in t:
            m = x[:-3] + "t7"       #模型檔案的擴充名為.t7, 基本名與對應的風格圖檔相同
            if os.path.exists(m):   #僅在風格圖檔及模型檔案同時存在時,将二者加入清單
                self.paintings.append(x)
                self.models.append(m)           

為了避免過多的全部變量污染名字空間,作者把相關資訊組織在App類型中。

資料成員
  • - images: 包含images子目錄内全部jpg圖檔檔案路徑的清單,形如['images\1.jpg', 'images\2.jpg', 'images\3.jpg']。
  • - paintings: 包含models子目錄風格圖檔路徑的清單,形如['models\candy.jpg', 'models\composition_vii.jpg',...]。
  • - models: 包含models子目錄内模型檔案路徑的清單,形如['models\candy.t7', 'models\composition_vii.t7',...]。paintings與models清單内的風格圖檔與模型是一一對應的,且檔案基本名相同,僅擴充名不同。
  • - idxImage: 目前被風絡遷移的内容圖檔在images清單中的下标。
  • - idxModel: 目前使用的模型檔案在models清單中的下标。

glob.glob("images/*.jpg") 周遊images子目錄,找出其中的全部jpg檔案,生成一個包含全部jpg檔案路徑字元串的清單。上述圖像風格遷移網絡的模型檔案的擴充名為.t7,這是PyTorch的資料儲存格式。而PyTorch是Facebook的深度學習開源工具包,具體一點可以把PyTorch看做加入了GPU 支援的numpy,同時也可以看成一個擁有自動求導功能的強大的深度神經網絡。

22.3.3 風格遷移

import cv2 as cv    #導入opencv庫
class App:
    ...
    def styleTransfer(self):
        net = cv.dnn.readNetFromTorch(self.models[self.idxModel]) #cv2.dnn_Net
        net.setPreferableBackend(cv.dnn.DNN_BACKEND_OPENCV) #設定計算背景類型
        inImg = cv.imread(self.images[self.idxImage]) #讀取輸入的内容圖檔
        inp = cv.dnn.blobFromImage(inImg, 1.0, (inImg.shape[1],inImg.shape[0]),
                              (103.939, 116.779, 123.68), swapRB=False, crop=False)
        net.setInput(inp)
        outImg = net.forward()
        outImg = outImg.reshape(3, outImg.shape[2], outImg.shape[3])
        outImg[0] += 103.939
        outImg[1] += 116.779
        outImg[2] += 123.68
        outImg /= 255
        outImg = outImg.transpose(1, 2, 0)
        return inImg, outImg           

類App的styleTransfer()函數應用目前的圖像風格遷移網絡模型将目前内容圖檔風格遷移成輸出圖檔。函數傳回的inImg即為輸入的内容圖檔,outImg則為輸出圖檔,兩者都是numpy三維數組。

讀者可能會感到詫異,如此複雜的功能居然隻有這麼少的代碼。是的,當你站在前人的肩膀上,隻是調庫(調用别人開發好的庫)時,就是這麼簡單。

代碼說明
  • - net = cv.dnn.readNetFromTorch() - 加載Torch格式的配置網絡及其參數。執行後,net的類型為cv2.dnn_net,是一個深度神經網絡。
  • - cv.imread()函數用于讀取圖像檔案,其傳回一個numpy的三維數組,其次元依次為圖像的像素高,圖像的像素寬,像素顔色通道數(通常為3)。
  • - 接下來,blobFromImage将inImg進行轉換,變成深度神經網絡所接受的資料格式;net.setupInput(inp)則把轉換後的資料設定為神經網絡的輸入; net.forward()則應用神經網絡進行計算,并在其輸出層得到輸出圖像outImg;outImg也是一個numpy多元數組,其次元與資料格式與正常的圖像有所不同;是以後續代碼對其進行了一些格式變換。
  • - 最後,函數傳回inImg-輸入圖像, outImg-輸出圖像兩個用于表示圖像的多元數組。

22.3.4 圖像顯示與重新整理

class App:
    ...
    def refresh(self,fig,axStyle,axIn,axOut):
        print("Style tranfering...")
        self.idxImage = self.idxImage % len(self.images)
        self.idxModel = self.idxModel % len(self.models)
        inImg, outImg = self.styleTransfer()
        print("Rendering...")
        styleImg = cv.imread(self.paintings[self.idxModel])
        axStyle.imshow(cv.cvtColor(styleImg,cv.COLOR_BGR2RGB))
        axIn.imshow(cv.cvtColor(inImg, cv.COLOR_BGR2RGB))
        axOut.imshow(cv.cvtColor(outImg, cv.COLOR_BGR2RGB))
        fig.canvas.draw()           

函數refresh()負責對目前内容圖像進行風格遷移運算,并将風格風像,内容圖像,遷移後的輸出圖像顯示在對應的子圖(Axes)中。其中,axStyle用于顯示風格圖像,axIn顯示内容圖像,axOut用于顯示輸出圖像。

self.idxImage和self.idxModel分别對len(self.images)和len(self.models)進行求模,目的是防止清單的越界通路(後續代碼中,切換圖檔和風格時沒有進行越界限制)。

axStyle/axIn/axOut.imshow()負責将圖像顯示在子圖中。請注意,在顯示之前,還應用cv.cvtColor()函數進行了一次圖像格式轉換,這是因為OpenCV中的圖像其顔色通道順序為BGR,即三維數組第三維中下标0對應藍色,下标1對應綠色,下标2對應紅色;而Matplotlib中,圖像的顔色通道順序為RGB,為了能正确顯示,交換一下顔色通道順序。

...
fig = plt.figure(figsize=(12,6))
axStyle = plt.subplot(231)          
axIn = plt.subplot(234)
axOut = plt.subplot2grid((2,3),(0,1),rowspan=2,colspan=2)
plt.subplots_adjust(0,0,1,1,0,0)    #設定子圖間的間距為0
for ax in (axStyle,axIn,axOut):
    ax.set_axis_off()               #子圖不顯示坐标軸
app.refresh(fig,axStyle,axIn,axOut) #重新整理顯示 
fig.canvas.mpl_connect('key_release_event',on_key_release)  #連接配接鍵盤事件響應函數
plt.show()           

我們在matplotlib中建立1個圖和3個子圖。plt.subplot(231)将圖的可視區域等分成2行3列,然後在第1個區域建立一個子圖。同理,plt.subplot(234)将圖的可視區域等分成2行3列,然後在第4個區域(即第2行的第1個區域)建立子圖。上述第1個區域,第4個區域是從1開始計數的。

plt.subplot2grid()也是将圖的可視區域分成2行3列,然後在第0行的第1列開始建立子圖,子圖橫向占2列,縱向占兩行。這裡的第0行,第1列從0開始計數,顯然,這裡的計數方式跟subplot()函數不同。讀者不必為此感到困惑,這沒有為什麼。作者将此差異視作matplotlib設計者犯下的一個小錯誤,如果全部保持相同的計數規則,對于使用者而言,可能可容易了解一些。

fig.canvas.connect()函數連接配接鍵盤松開事件至on_key_release()函數, 當讀者松開(release)鍵盤按鍵時,on_key_release()函數将被調用執行。

22.3.5 互動響應

def on_key_release(event):
    if event.key == 'up':
        app.idxImage-=1
    elif event.key == 'down':
        app.idxImage+=1
    elif event.key == 'left':
        app.idxModel-=1
    elif event.key == 'right':
        app.idxModel+=1
    else:
        return
    app.refresh(fig,axStyle,axIn,axOut)           

on_key_release()函數響應按鍵松開事件。如果是上-up, 下-down鍵,修改idxImage切換内容圖檔;如果是左(left),右(right)鍵,修改idxModel切換圖像風格遷移網絡模型。最後,調用app.refresh()函數進行風格遷移和界面重新整理。

22.4 小結

下圖是重慶大學虎溪校區圖書館及其在雲湖上的倒影經過風格遷移後的效果。

【Python】人工智能的味道 - 圖像風格遷移

現實中很多類似的計算問題,人類無法對相關概念進行準确的數學定義。比如:什麼是圖像的風格?何種形式的圖像模式是一隻貓?在乳腺X片裡,什麼樣的圖像可能是惡性的結節?在這種情況下,使用經過标注的資料集來訓練神經網絡,經過訓練的神經網絡從資料集中學習了相關的知識。這些知識包含在神經網絡的内部參數中。即便這種知識的表達和真正的數學含義有些說不清,道不明,但我們仍然可以應用這些經過訓練的神經網絡來解決各種現實問題。

本案例節選自作者編寫的教材及配套實驗指導書。

《C++程式設計基礎及應用》(高等教育出版社,出版過程中)

《Python程式設計基礎及應用》,高等教育出版社

《Python程式設計基礎及應用實驗教程》,高等教育出版社

【Python】人工智能的味道 - 圖像風格遷移

高校教師同行如果期望索取樣書,教學支援資料,加群,請私信作者,聯系時請提供學校及個人姓名為盼,各高校在讀學生勿擾為謝。

青少年讀者們如果期望系統性地學習Python及C/C++程式設計語言,歡迎嘗試下述今日頭條(西瓜)免費視訊課程。

C/C++從入門到放棄(重慶大學現場版)

Python程式設計基礎及應用(重慶大學現場版)

【Python】人工智能的味道 - 圖像風格遷移