解密Deepfake(深度換臉)-基於自編碼器的(Pytorch程式碼)換臉技術

OLDPAN發表於2019-01-23

前言

還記得在2018月3月份火爆reddit的deepfake嗎?將視訊中的頭換成另一個人的頭像,雖然可能有些粗糙和模糊,但是在解析度不要求很高的情況下可以達到以假亂真的效果。

舉個栗子,如下圖中將希拉蕊換成川普的一段演講視訊。

1fca0bd71f9e49038fd19ffff705f7e8

另外還有實現川普和尼古拉臉相換:

deep-fake

當然這只是DeepFake的冰山一角,Deepfake當初火起來的原因可以說是廣大擁有宅男心態的程式設計師們一起奮鬥的結果。那就是,呃,可以將你想要的某張臉換到AV中去。當然這裡就不進行演示了,並且相關的reddit論壇已經被禁止。所以這裡就不多進行討論啦。

本文為 github.com/Oldpan/Face… 程式碼的搭配教程(填一下之前埋得坑),這裡簡單對此進行講解下,填補一下之前的空缺。

相關研究

其實有關深度學習的換臉相關的研究已經很普及了,有基於GAN的也有基於Glow的,但本質上都是生成模型,只是換了一種實現方式,而這個DeepFake呢,使用的是機器學習中的自編碼器,擁有與神經網路類似的結構,魯棒性較好,我們可以通過學習它來對生成網路有一個大概的瞭解,這樣之後碰到相似的網路或者構造就好上手了。

技術講解

人臉互換是計算機視覺領域中一個比較熱門的應用,人臉互換一般可以用於視訊合成、提供隱私服務、肖像更換或者其他有創新性的應用。最早之前,實現人臉互換是通過分別分析兩者人臉的相似資訊來實現換臉,也就是通過特徵點匹配來提取一張臉中例如眉毛、眼睛等特徵資訊然後匹配到另一張人臉上。這種實現不需要進行訓練,不需要的資料集,但是實現的比較差,無法自己修改人臉中的表情。

而在最近發展的深度學習技術中,我們可以通過深度神經網路提取輸入影象的深層資訊,從而讀取出其中隱含的深層特徵來實現一些新奇的任務,比如風格遷移(style transfer)就是通過讀取訓練好的模型提取影象中的深層資訊來實現風格互換。

也有使用神經網路進行人臉互換(face-swap),其中使用VGG網路來進行特徵提取並實現人臉互換。這裡我們通過特殊的自編碼器結構來實現人臉互換,並且達到不錯的效果。

基礎背景:自編碼器

自編碼器類似於神經網路,可以說是神經網路的一種,經過訓練後能夠嘗試將輸入複製到輸出。自編碼器和神經網路一樣,有著隱含層

h,可以將輸入解析成編碼序列,從而復現輸入。自編碼器內部有一個函式
h=f(x)可以進行編碼,同時也有一個函式r=g(h)實現解碼,如下圖所示。

TIM截圖20181227172834

(自編碼器的結構,x為輸入,f為編碼函式,將x編碼為隱含特徵變數h,而g為解碼網路,通過將隱變數h進行重構得到輸出結果r,我們也可以看到數字2被放入編碼器之後得到其隱含層的編碼也就是Compressed representation,之後通過解碼器重新生成出來)

現代的自編碼器從巨集觀上也可以理解為隨機對映

P_encoder​(h/x)和P_decoder​(x/h)。之前自編碼器一般用於資料降維或者影象去噪。但是近年來由於神經網路的發展,更多的潛變數被研究,自編碼器也被帶到了生成式建模的前沿,可以用於影象生成等方面。

關於更多自編碼器的知識:理解深度學習:與神經網路相似的網路-自編碼器(上)

網路構架

那麼應該如何通過自編碼器實現我們的換臉技術呢?

在之前我們已經知道了自編碼器可以學習輸入影象的資訊從而對輸入影象資訊進行編碼並將編碼資訊存到隱含層中,而解碼器則利用學習到的隱含層的資訊重新生成之前輸入的影象,但是如果我們直接將兩個不同個體影象集的影象輸入到自編碼器當中會發生什麼呢?

TIM截圖20181227173028

如上圖,假如我們僅僅是簡單地將兩張不同的臉的集合扔到自編碼網路中,然後挑選一個損失函式去訓練,但這樣去訓練我們是什麼也得不到的,因此我們需要重新設計一下我們的網路。

怎麼設計呢?

既然我們想要將兩張臉互換,那麼我們可以設計兩個不同的解碼網路,也就是使用一個編碼網路去學習兩張不同人臉的共同特徵,而使用兩個解碼器去分別生成他們。

TIM截圖20181227173317

如上圖,也就是我們設計一個輸入端或者說一個編碼器(分別輸入兩個不同的臉),然後兩個輸出端或者說兩個解碼器,這樣我們就可以通過隱含層來分別生成兩張不同的人臉了。

具體構架

這樣如此的話,我們的具體構架如下:

TIM截圖20181227173606

如上圖,可以看到這個自編碼器有一個input端而有兩個output端,在input端分別輸入兩個不同的人臉集(尼古拉和川普),然後在輸出端再重新生成之前的人臉,注意,這裡的每個解碼器分別負責生成不同個體的臉。而在隱含層則學習到了兩個不同個體的共同資訊,從而兩個不同的解碼器可以根據學習到的共同資訊去還原之前輸入的影象。

再具體點就是這樣:

TIM截圖20181227191710

如上圖,我們將兩個不同個體的影象集分別進行進行輸入,此時的Encoder是同一個編碼器,隨後將使用同一個Encoder生成的共同的隱含資訊。再利用兩個不同個體影象的解碼器A、B重新生成各自的影象進行損失標準(criterion)進行比較(這裡損失採用L1損失函式),通過Adam優化演算法進行梯度下降。

之後我們利用解碼器A、B分別去重新生成由輸入影象B、A後隱含層學習到的資訊,如下公式:

TIM截圖20181227192314

網路結構

下面則是之前提到的結構的具體的網路設計,我們可以看到網路結構有一個輸入端和兩個輸出端,輸入端由卷積層和全連線層構成,而輸出端則同樣由卷積層構成,但是需要注意這裡的輸入端是下采樣卷積,而輸出端則是上取樣卷積,也就是影象的解析度是先變低再慢慢升高。

可以看下Pytorch中網路設計的程式碼:

class Autoencoder(nn.Module):
    def __init__(self):
        super(Autoencoder, self).__init__()

        self.encoder = nn.Sequential(   # 編碼網路
            _ConvLayer(3, 128),     # 3 64 64
            _ConvLayer(128, 256),   # 32/2=16
            _ConvLayer(256, 512),   # 16/2=8
            _ConvLayer(512, 1024),  # 8/2=4
            Flatten(),
            nn.Linear(1024 * 4 * 4, 1024),
            nn.Linear(1024, 1024 * 4 * 4),
            Reshape(),
            _UpScale(1024, 512),
        )

        self.decoder_A = nn.Sequential(     # 解碼網路
            _UpScale(512, 256),
            _UpScale(256, 128),
            _UpScale(128, 64),
            Conv2d(64, 3, kernel_size=5, padding=1),
            nn.Sigmoid(),
        )

        self.decoder_B = nn.Sequential(
            _UpScale(512, 256),
            _UpScale(256, 128),
            _UpScale(128, 64),
            Conv2d(64, 3, kernel_size=5, padding=1),
            nn.Sigmoid(),
        )

    def forward(self, x, select='A'):
        if select == 'A':
            out = self.encoder(x)
            out = self.decoder_A(out)
        else:
            out = self.encoder(x)
            out = self.decoder_B(out)
        return out
複製程式碼

上述程式碼網路可以由下圖的圖例所表示:

TIM截圖20181227193143

但是這些結構都是可以變化的,其中卷積核的大小可以按照需求調整,而全連線這種可以打亂空間結構的網路我們也可以尋找類似的結構去代替。另外,在解碼器階段的最後一層還採用了Sub-pixel的上取樣卷積技術(在本部落格的 一邊Upsample一邊Convolve:Efficient Sub-pixel-convolutional-layers詳解 一文中有對此技術的詳細講解)可以快速並且較好地生成影象的細節。

總之,我們想實現換臉的操作,在整體結構不變的基礎上,需要滿足以下幾點:

TIM截圖20181227193921

如上圖,也就是類似於VGG的編碼網路、還要可以打亂空間結構結構的全連線網路、以及可以快速且較好地上取樣影象的Sub-Pixel網路。我們進行了額外的測試,發現作為編碼網路的話,傳統的VGG形式的網路結構的效果最好,可以使損失函式降到最低。但是如果採用其他較為先進的網路,其效果並沒有傳統的VGG構架好,這也是為什麼風格遷移和影象生成使用VGG網路格式更多一些。

TIM截圖20181227194302

同樣,如果我們將全連線網路從編碼器中去掉或者使用卷積網路代替,那麼影象是無法正常生成的,也就是編碼器學習不到任何有用的知識,但是我們可以使用1x1的卷積網路去代替全連線網路,1x1網路如:

nn.Conv2d(1024, 128, 1, 1, 0, bias=False),
nn.Conv2d(128, 1024, 1, 1, 0, bias=False),
複製程式碼

同樣也擁有打亂空間結構的特性,優點是比全連線網路執行更快,但是效果並沒有全連線網路好。

至此,我們簡單說明了基本構架以及網路層的選擇。

影象預處理

當然訓練的時候是有很多小技巧的,因為我們需要隱含層學習到兩個不同個體的共同特徵,我們可以採取一些小Tricks來讓我們的訓練過程更快更平滑,類似於之前談到的 淺談深度學習訓練中資料規範化(Normalization)的重要性 ,我們要做的就是使訓練的影象的分佈資訊儘可能相近:

TIM截圖20181227173928

如上圖,我們可以將影象A(川普)集加上兩者影象集的平均差值(RGB三通道差值)來使兩個輸入影象影象的分佈儘可以相近,這樣我們的損失函式曲線下降會更快些。

拿程式碼表示則為:

images_A += images_B.mean(axis=(0, 1, 2)) - images_A.mean(axis=(0, 1, 2))
複製程式碼

影象增強

當然還有影象增強技術,這個技術就不必多說了,基本是萬能的神器了。我們可以旋轉、縮放、翻轉訓練影象從而使影象的數量翻倍進而增加訓練的效果。

TIM截圖20181227195611

如上圖,對於人臉來說,使用扭曲的增強技術可以有效地降低訓練過程中的損失值,當然增強也是有限度的,不要太過,物極必反。哦對了,還忘了說,我們訓練的時候使用的臉部影象只從原圖擷取了包含臉部的部分,比如上圖右上角的aligned image,我們使用OpenCV庫截圖臉部影象作為訓練素材。

影象後處理

至於影象後處理,當然是將我們生成的臉部影象重新拼回去了:

TIM截圖20181227200107

這裡使用了泊松融合以及Mask邊緣融合的方法,我們可以很容易地看出融合的效果。

總結

總得來說,這個換臉技術是一個結構簡單但是知識點豐富的一個小專案,其結構簡單易於使用以及修改,並且可以生成不錯的效果,但是因為其擁有較多的引數,其執行速度並不是很快(當然我們可以通過改變編碼層和解碼層結構加快訓練生成的速度),並且對於臉部有異物的影象可能會生成不真實的效果。

這是因為自編碼器網路並沒有針對影象中需要學習的部位進行學習而是全部進行了學習,當然會有很多雜質,這可以通過注意力的機制適當改善。

TIM截圖20181227200242

就說這些吧~

文章來源於OLDPAN部落格,歡迎來訪:Oldpan部落格

歡迎關注Oldpan部落格公眾號,持續醞釀深度學習質量文:

解密Deepfake(深度換臉)-基於自編碼器的(Pytorch程式碼)換臉技術


相關文章