上一節我們通過VAE網絡完成了人臉生成效果。VAE網絡一個特性是會把人臉編碼成一個含有200個分量的向量,反過來說在特定分佈範圍內的含有200個分量的向量就對應一張人臉。由於向量之間可以進行運算,這就意味着我們把兩張不同人臉A,B分佈轉換成兩個不同向量z_A,z_B,然後我們使用向量運算例如z_AB = z_A *(1 - alpha) + z_B *alpha,就能將兩個向量以一定比例合成一個新向量,該新向量就會對應一個人臉,而且這個人臉就會同時具有人臉A和B的特點,如果我們增大參數alpha,那麼生成向量對應的人臉特徵就會更像人臉B,如果我們減少alpha的值,生成向量對應的人臉就更像人臉A.
接下來我們看看如何實現人臉的轉變特效,首先我們先出數據圖片中選出具有特定特徵的人臉圖片,例如”戴墨鏡“,然後使用編碼器得出”戴墨鏡“人臉圖片的特徵向量,然後我們再選取不帶墨鏡的人臉圖片,計算其特徵向量,然後使用向量運算將兩個向量結合起來,這樣我們就能把後面不帶墨鏡的人臉轉換成帶墨鏡的模樣。
我們看看相應代碼實現:
def morph_faces(start_image_file, end_image_file):
alpha = np.arange(0, 1, 0.1)
atttribute_specific = att[att['image_id'].isin([start_image_file, end_image_file])]
att_specific = atttribute_specific.reset_index()
data_flow_label = imageLoader.build(att_specific, 2) #加載滿足給定條件的圖片
example_batch = next(data_flow_label)
example_images = example_batch[0]
example_labels = example_batch[1]
z_vectors = vae.encoder.predict(example_images)#獲得人臉圖像的關鍵向量
fig = plt.figure(figsize = (18, 8))
counter = 1
img = example_images[0].squeeze()
sub = fig.add_subplot(1, len(alpha) + 2, counter)
sub.axis('off')
sub.imshow(img)
counter += 1
for factor in alpha: #通過變化參數讓一個向量滑向另一個向量
changed_z_vector = z_vectors[0] * (1 - factor) + z_vectors[1] * factor
changed_image = vae.decoder.predict(np.array([changed_z_vector]))[0] #根據新向量繪製人臉圖像
img = changed_image.squeeze()
sub = fig.add_subplot(1, len(alpha) + 2, counter)
sub.axis('off')
sub.imshow(img)
counter += 1
img = example_images[1]
sub = fig.add_subplot(1, len(alpha) + 2, counter)
sub.axis('off')
sub.imshow(img)
plt.show()
上面函數先將輸入的兩張圖片轉換成各自對應的向量,然後使用向量運算,通過變化中間參數的形式讓一個向量不斷的”滑向“另一個向量,由此形成的效果是,其中一張人臉圖片參數”漸變“效果,隨着向量不斷滑向另一個向量,所生成的人臉圖片越來越具備目標向量的特性,我們調用上面函數看看實現效果:
start_image_file = '000238.jpg'
end_image_file = '000193.jpg' #戴墨鏡人臉
morph_faces(start_image_file, end_image_file)
上面代碼運行後所得結果如下:
處於最左和最右邊的圖像時我們輸入的兩張人臉圖片,中間人臉是將一邊人臉圖片對應的向量滑向另一邊時所產生的人臉,我們注意到中間人臉圖片是左右兩張人臉圖片特徵的混合。回到deepfake或zao這樣的變臉應用,他們的原理就是先將計算原來視頻中人臉變化所對應的不同向量,然後計算用戶的人臉向量,然後將用戶人臉向量”滑向“視頻中人臉當前表情對應向量從而實現用戶人臉展現出視頻中人臉的同樣表情,當前它們的實現比我們這裏介紹的要複雜得多。
接下來我們看如何讓一個沒有帶墨鏡的人臉“自然而然”的帶上墨鏡,首先要做的是計算所有戴墨鏡的人臉圖片對應的向量,將這些向量加總求平均值得向量A,這樣我們就能用向量A表達出”帶墨鏡“這一特徵,然後計算所有沒有帶墨鏡的人臉圖片所對應的向量,將這些向量加總求平均值得向量B,將該向量減去”戴墨鏡“所對應的特徵向量,這樣就得出滑動方向,然後將不帶墨鏡的人臉圖片對應向量慢慢根據給定方向滑動,這樣我們就可以天衣無縫的給原來沒帶墨鏡的人臉帶上墨鏡,實現如下:
def get_vector_from_label(label, batch_size):
data_flow_label = imageLoader.build(att, batch_size, label = label) #讀取所有圖片
origin = np.zeros(shape = vae.z_dim, dtype = 'float32') #原點向量
current_sum_pos = np.zeros(shape = vae.z_dim, dtype = 'float32') #將所有含有給定特徵的圖片向量加總
current_n_pos = 0 #統計當前具備給定特徵的圖片數量
current_mean_pos = np.zeros(shape = vae.z_dim, dtype = 'float32') #求加給定特徵向量加總後的平均值向量
current_sum_neg = np.zeros(shape = vae.z_dim, dtype = 'float32') #不具備給定特徵的向量加總
current_n_neg = 0 #不具備給定特徵的向量個數
current_mean_neg = np.zeros(shape = vae.z_dim, dtype = 'float32') #不具備給定特徵的向量加總後求平均所得向量
current_vector = np.zeros(shape = vae.z_dim, dtype = 'float32')
current_dist = 0 #給定圖片特徵向量與給定特徵向量之間的距離
print('label: ' + label) #label表示給定特徵
print('image : POS move : NEG move : distance : delta distance')
while current_n_pos < 10000:
batch = next(data_flow_label)
im = batch[0]
attribute = batch[1]
z = vae.encoder.predict(np.array(im)) #計算圖片對應的特徵向量
z_pos = z[attribute == 1] #抽取出具有給定特徵圖片所對應的向量
z_neg = z[attribute == -1] #抽取出不具備給定特徵的圖片所對應的向量
if len(z_pos) > 0:
current_sum_pos = current_sum_pos + np.sum(z_pos, axis = 0) #將所有含有給定特徵的向量加總
current_n_pos += len(z_pos)
new_mean_pos = current_sum_pos / current_n_pos #計算平均向量
#隨着圖片加載越多,具有給定特徵的圖片也就越多,於是得到的平均向量在位置上就會發生改變
movement_pos = np.linalg.norm(new_mean_pos - current_mean_pos) #計算特徵向量加總求平均後所得向量的變化量
if len(z_neg) > 0:
current_sum_neg = current_sum_neg + np.sum(z_neg, axis = 0) #將不具備給定特徵的人臉向量加總
current_n_neg += len(z_neg)
new_mean_neg = current_sum_neg / current_n_neg #計算平均向量
#隨着讀入圖片的增多,不具備給定特徵的圖片也會增多,於是平均向量也會發生改變
movement_neg = np.linalg.norm(new_mean_neg - current_mean_neg) #計算平均向量的改變量
current_vector = new_mean_pos - new_mean_neg #獲得連接給定特徵的平均向量和不具備給定特徵的平均向量之間的連接向量
new_dist = np.linalg.norm(current_vector) #獲得兩個向量之間的距離
dist_change = new_dist - current_dist
print(str(current_n_pos) + ' : ' + str(np.round(movement_pos, 3))
+ ' : ' + str(np.round(movement_neg, 3))
+ ' : ' + str(np.round(new_dist, 3))
+ ' : ' + str(np.round(dist_change, 3))
)
current_mean_pos = np.copy(new_mean_pos)
current_mean_neg = np.copy(new_mean_neg)
current_dist = np.copy(new_dist)
if np.sum([movement_pos, movement_neg]) < 0.08: #當向量該變量足夠小時我們結束對平均向量的計算
current_vector = current_vector / current_dist
print("Found the " + label + ' vector')
break
return current_vector
def add_vector_to_images(feature_vec): #將給定圖片的向量滑向給定特徵平均向量,於是讓圖片具備給定特徵
n_to_show = 5
factors = [-4, -3, -2, -1, 0, 1, 2, 3, 4]
example_batch = next(data_flow_generic)
example_images = example_batch[0]
example_labels = example_batch[1]
z_points = vae.encoder.predict(example_images) #獲得圖片對應特徵向量
fig = plt.figure(figsize = (18, 10))
counter = 1
for i in range(n_to_show):
img = example_images[i].squeeze()
sub = fig.add_subplot(n_to_show, len(factors) + 1, counter)
sub.axis('off')
sub.imshow(img) #顯示沒有變化前圖片
counter += 1
for factor in factors: #讓圖片對應向量滑向給定特徵平均向量
changed_z_point = z_points[i] + feature_vec * factor
changed_image = vae.decoder.predict(np.array([changed_z_point]))[0] #根據滑動後向量創建人臉圖片
img = changed_image.squeeze()
sub = fig.add_subplot(n_to_show, len(factors) + 1, counter)
sub.axis('off')
sub.imshow(img)
counter += 1
plt.show()
接着我們隨機採用若干張沒有戴墨鏡的人臉圖片,然後先調用上面代碼計算戴墨鏡這一特徵對應的向量,然後將人類圖片對應向量沿着戴墨鏡所對應特徵向量的方向滑動,這樣就能使得不帶墨鏡的人臉變成戴墨鏡,代碼如下:
BATCH_SIZE = 500
eyeglasses_vec = get_vector_from_label("Eyeglasses", BATCH_SIZE) #先獲得"帶墨鏡"對應的特徵向量
add_vector_to_images(eyeglasses_vec) #讓沒有帶墨鏡的人臉圖片添加上帶墨鏡效果
上面代碼運行後結果如下:
從中我們看到,最左邊對應沒有戴墨鏡的人臉圖片,最右邊的人臉則是戴墨鏡的效果,到這裏我們節介紹完使用VAE網絡實現人臉生成的技術方法
更多技術信息,包括操作系統,編譯器,面試算法,機器學習,人工智能,請關照我的公衆號: