深度學習教你重建趙麗穎的三維人臉
近年來,三維人臉重建成為計算機視覺、圖像識別等研究領域中的熱點問題。三維人臉重建技術分為基于不同視角的多幅圖像的重建和基于單幅圖像的三維人臉重建。
本項目通過使用PRNet算法,通過訓練CNN網絡實現對二維圖像轉變為三維空間圖像的轉換,其中二維使用UV坐標作為2D的表達,可以實現在UV空間保存完整的人臉3D形狀。實現的效果如下圖所示:


基本介紹
針對單幅圖像的三維人臉重建,傳統方法有基于模型的方法、基于明暗形狀恢復的方法等。
隨著深度學習被引入到三維人臉重建領域,并取得比傳統方法更優異的效果,逐漸成為主流的重建方法。JacksonAS等人提出使用VRN(volumetric regression networks)從單個二維圖像直接進行三維面部重建的方法。Feng Y等人設計了一個名為UV位置圖的二維表示方法,記錄UV空間中完整面部的三維形狀,然后訓練一個簡單的卷積神經網絡,從單個二維圖像中回歸。
ChangFJ等人提出了直接應用于人臉圖像強度,回歸3D表情系數的29D向量的ExpNet CNN模型。Tu等人針對3D標注訓練數據短缺問題,提出一種2D輔助自監督學習方法,利用帶嘈雜地標信息的無約束二維人臉圖像改善三維人臉模型的學習,在密集人臉對齊和三維人臉重建方面取得了突出的效果。劉成攀等人提出一種基于自監督深度學習的人臉表征及三維重建方法,將二維人臉的特征點信息映射到三維空間實現三維人臉重建。

環境要求
本次環境使用的是Python3.6.5+Windows平臺。主要用的庫有:
skimage模塊:skimage包的全稱是scikit-imageSciKit,它對scipy.ndimage進行擴展,提供更多的圖片處理功能。
opencv-python模塊:opencv-python是Python的綁定庫,解決計算機視覺問題。其使用Numpy,這是一個高度優化的數據庫操作庫,具有MATLAB風格的語法。所有Opencv數組結構都轉換為Numpy數組。這也使得與使用Numpy的其他庫(如Scipy和Matplotlib)集成更容易。
Numpy模塊:Numpy是應用Python進行科學計算時的基礎模塊。它是一個提供多維數組對象的Python庫,包含多種衍生的對象以及一系列的為快速計算數組而生的例程,如數學運算、邏輯運算、形狀操作、排序、選擇、I/O、離散傅里葉變換、基本線性代數、基本統計運算、隨機模擬等。
Matplotlib模塊:Matplotlib是Python的2D繪圖庫,以各種硬拷貝格式和跨平臺的交互式環境生成出版質量級別的圖形。通過Matplotlib,開發者僅用幾行代碼便可生成繪圖。Matplot使用Numpy進行數組運算,調用其他Python庫來實現硬件交互。
模型介紹
模型使用的是端到端的PRN多任務方法,能完成稠密人臉對齊和3D人臉形狀重建。主要體現在以下幾個方面:
1、以端到端方式,實現了高分辨率的3D人臉重建和稠密對齊;
2、設計了UV位置圖,來記錄人臉的3D位置信息;
3、設計了權重掩模用于loss計算,loss中每個點權重不同,可明顯提高網絡性能
4、CNN采用輕量級模式,單張人臉任務可達到100FPS
5、在AFLW200-3D和Florence數據集上可達到25%的性能提升
PRNet通過輸入一張圖片,直接使用神經網絡輸出一張稱為UV position map的 UV 位置映射圖。其中UV映射圖維度是一個三位矩陣,前面兩個維度上輸出的紋理圖的維度,最后一個維度表示紋理圖每個像素在 3D 空間中的位置信息。
其中網絡模型代碼如下:
class resfcn256(object): def __init__(self, resolution_inp = 256, resolution_op = 256, channel = 3, name = 'resfcn256'): self.name = name self.channel = channel self.resolution_inp = resolution_inp self.resolution_op = resolution_op def __call__(self, x, is_training = True): with tf.variable_scope(self.name) as scope: with arg_scope([tcl.batch_norm], is_training=is_training, scale=True): with arg_scope([tcl.conv2d,tcl.conv2d_transpose], activation_fn=tf.nn.relu, normalizer_fn=tcl.batch_norm, biases_initializer=None, padding='SAME', weights_regularizer=tcl.l2_regularizer(0.0002)): size = 16 # x: s x s x 3 se = tcl.conv2d(x, num_outputs=size, kernel_size=4, stride=1) # 256 x 256 x 16 se = resBlock(se, num_outputs=size * 2, kernel_size=4, stride=2) # 128 x 128 x 32 se = resBlock(se, num_outputs=size * 2, kernel_size=4, stride=1) # 128 x 128 x 32 se = resBlock(se, num_outputs=size * 4, kernel_size=4, stride=2) # 64 x 64 x 64 se = resBlock(se, num_outputs=size * 4, kernel_size=4, stride=1) # 64 x 64 x 64 se = resBlock(se, num_outputs=size * 8, kernel_size=4, stride=2) # 32 x 32 x 128 se = resBlock(se, num_outputs=size * 8, kernel_size=4, stride=1) # 32 x 32 x 128 se = resBlock(se, num_outputs=size * 16, kernel_size=4, stride=2) # 16 x 16 x 256 se = resBlock(se, num_outputs=size * 16, kernel_size=4, stride=1) # 16 x 16 x 256 se = resBlock(se, num_outputs=size * 32, kernel_size=4, stride=2) # 8 x 8 x 512 se = resBlock(se, num_outputs=size * 32, kernel_size=4, stride=1) # 8 x 8 x 512 pd =tcl.conv2d_transpose(se, size * 32, 4, stride=1) # 8 x 8 x 512 pd =tcl.conv2d_transpose(pd, size * 16, 4, stride=2) # 16 x 16 x 256 pd =tcl.conv2d_transpose(pd, size * 16, 4, stride=1) # 16 x 16 x 256 pd =tcl.conv2d_transpose(pd, size * 16, 4, stride=1) # 16 x 16 x 256 pd =tcl.conv2d_transpose(pd, size * 8, 4, stride=2) # 32 x 32 x 128 pd =tcl.conv2d_transpose(pd, size * 8, 4, stride=1) # 32 x 32 x 128 pd =tcl.conv2d_transpose(pd, size * 8, 4, stride=1) # 32 x 32 x 128 pd = tcl.conv2d_transpose(pd, size * 4, 4, stride=2) # 64 x 64 x 64 pd =tcl.conv2d_transpose(pd, size * 4, 4, stride=1) # 64 x 64 x 64 pd =tcl.conv2d_transpose(pd, size * 4, 4, stride=1) # 64 x 64 x 64 pd =tcl.conv2d_transpose(pd, size * 2, 4, stride=2) # 128 x 128 x 32 pd =tcl.conv2d_transpose(pd, size * 2, 4, stride=1) # 128 x 128 x 32 pd =tcl.conv2d_transpose(pd, size, 4, stride=2) # 256 x 256 x 16 pd =tcl.conv2d_transpose(pd, size, 4, stride=1) # 256 x 256 x 16 pd =tcl.conv2d_transpose(pd, 3, 4, stride=1) # 256 x 256 x 3 pd =tcl.conv2d_transpose(pd, 3, 4, stride=1) # 256 x 256 x 3 pos =tcl.conv2d_transpose(pd, 3, 4, stride=1, activation_fn = tf.nn.sigmoid)#, padding='SAME', weights_initializer=tf.random_normal_initializer(0,0.02)) return pos

人臉生成
檢測人臉關鍵點:
通過調用人臉模型監測人臉并將人臉裁剪分出:
cas =cv2.CascadeClassifier('./Data/cv-data/haarcascade_frontalface_alt2.xml')
img = plt.imread('./images/zly.jpg')
img_gray= cv2.cvtColor(img,cv2.COLOR_BGR2RGB)
faces = cas.detectMultiScale(img_gray,2,3,0,(30,30))
bbox = np.array([faces[0,0],faces[0,1],faces[0,0]+faces[0,2],faces[0,1]+faces[0,3]])
plt.imshow(cv2.rectangle(img.copy(),(bbox[0],bbox[1]),(bbox[2],bbox[3]),(0,255,0),2))
plt.axis('off')
plt.show()
left = bbox[0]; top = bbox[1]; right = bbox[2]; bottom = bbox[3]
old_size = (right - left + bottom - top)/2
center = np.array([right- (right - left) / 2.0, bottom - (bottom -top) / 2.0])
size = int(old_size*1.6)
src_pts = np.array([[center[0]-size/2, center[1]-size/2],
[center[0] - size/2, center[1]+size/2],
[center[0]+size/2, center[1]-size/2]])
DST_PTS = np.array([[0,0], [0,255], [255, 0]]) #圖像大小256*256
tform =estimate_transform('similarity', src_pts, DST_PTS)
img = img/255.
cropped_img =warp(img, tform.inverse, output_shape=(256, 256))

獲取人臉點云:
三維點云是一個在三維空間關聯系統中包含點的數據庫,即事物或空間的精確數字記錄,主要關于表面點的集合。與相片不同,三維圖像是對一類信息的統稱,信息還需要有具體的表現形式。
其表現形式包括:深度圖(以灰度表達物體與相機的距離),幾何模型(由CAD軟件建立),點云模型(所有逆向工程設備都將物體采樣成點云)。可見,點云數據是最為常見也是最基礎的三維模型。點云模型往往由測量直接得到每個點對應一個測量點,未經過其他處理手段,故包含了最大的信息量。
對應代碼如下:
face_ind =np.loadtxt('./Data/uv-data/face_ind.txt').astype(np.int32)
all_vertices = np.reshape(pos, [256*256, -1])
vertices = all_vertices[face_ind, :]
plt.figure(figsize=(8,8))
plt.imshow(draw_kps(img.copy(),vertices[:,:2],1))
plt.axis('off')
plt.show()
texture = cv2.remap(img, pos[:,:,:2].astype(np.float32), None, interpolation=cv2.INTER_NEAREST, borderMode=cv2.BORDER_CONSTANT,borderValue=(0))
生成三維人臉:
首先是獲取三角形每個頂點的depth,平均值作為三角形高度。接著獲取三角形每個頂點的color,平均值作為三角形顏色。對應代碼如下:
tri_depth = (vertices[triangles[:,0],2 ] +vertices[triangles[:,1],2] +vertices[triangles[:,2],2])/3. # tri_tex =(colors[triangles[:,0] ,:] +colors[triangles[:,1],:] +colors[triangles[:,2],:])/3. tri_tex = tri_tex*255 img_3D =np.zeros_like(img,dtype=np.uint8) for i in range(triangles.shape[0]): cnt =np.array([(vertices[triangles[i,0],0],vertices[triangles[i,0],1]), (vertices[triangles[i,1],0],vertices[triangles[i,1],1]), (vertices[triangles[i,2],0],vertices[triangles[i,2],1])],dtype=np.int32) img_3D =cv2.drawContours(img_3D,[cnt],0,tri_tex[i],-1)

代碼鏈接:
https://pan.baidu.com/s/1s3NjgxuuCnB238GpzN-6Pw
提取碼:ucqj
*博客內容為網友個人發布,僅代表博主個人觀點,如有侵權請聯系工作人員刪除。







