日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 人工智能 > pytorch >内容正文

pytorch

3D人脸重建——PRNet网络输出的理解

發布時間:2023/12/13 pytorch 45 豆豆
生活随笔 收集整理的這篇文章主要介紹了 3D人脸重建——PRNet网络输出的理解 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

前言

之前有款換臉軟件不是叫ZAO么,分析了一下,它的實現原理絕對是3D人臉重建,而非deepfake方法,找了一篇3D重建的論文和源碼看看。這里對源碼中的部分函數做了自己的理解和改寫。

國際慣例,參考博客:

什么是uv貼圖?

PRNet論文

PRNet代碼

本博客主要是對PRNet的輸出進行理解。

理論簡介

這篇博客比較系統的介紹了3D人臉重建的方法,就我個人淺顯的理解,分為兩個流派:1.通過算法估算3DMM的參數,3DMM的思想是有一個平均臉,基于這個平均臉進行變形,就能得到任意的人臉,算法就需要計算這個變形所需要的參數;2. 直接擺脫平均臉的約束,直接使用神經網絡去估算人臉的3D參數。

PRNet就是屬于第二種流派,輸入一張圖片,直接使用神經網絡輸出一張稱為UV position map的UV位置映射圖。本博客就是為了對這個輸出進行充分理解。先簡短說一下,他的維度是(256,256,3)(256,256,3)(256,256,3)的三維矩陣,前面兩個維度上輸出的紋理圖的維度,最后一個維度表示紋理圖每個像素在3D空間中的位置信息。

任何的3D人臉重建,包括3DMM,都需要得到頂點圖和紋理圖,這個在圖形學里面很常見,比如我們看到的游戲角色就包括骨骼信息和紋理信息。

代碼理解

首先引入必要的庫:

import numpy as np import os from skimage.transform import estimate_transform, warp import cv2 from predictor import PosPrediction import matplotlib.pyplot as plt

這里有個額外的predictor庫,是PRNet的網絡結構,直接去這里下載。

還有一個文件夾需要下載,戳這里,這里面定義了UV圖的人臉關鍵點信息uv_kpt_ind,預定義的人臉頂點信息face_ind,三角網格信息triangles。下面會分析他倆的作用。

人臉裁剪

因為源碼使用dlib檢測人臉關鍵點,其實目的是找到人臉框,然后裁剪人臉。由于在Mac上安裝dlib有點難度,而前面的換臉博客剛好玩過用opencv檢測人臉關鍵點。檢測人臉框的代碼如下:

## 預檢測人臉框或者關鍵點,目的是裁剪人臉 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')

裁剪人臉

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))

可視化看看

plt.imshow(cropped_img) plt.axis('off')

網絡推斷

載入網絡結構

pos_predictor = PosPrediction(256, 256) pos_predictor.restore('./Data/net-data/256_256_resfcn256_weight')

直接把裁剪后的圖片輸入到網絡中,推導UV位置映射圖

cropped_pos = pos_predictor.predict(cropped_img) #網絡推斷

因為這個結果是裁剪過的圖的重建,所以在重新調整一下,縮放到之前的圖大小:

#將裁剪圖的結果重新調整 cropped_vertices = np.reshape(cropped_pos, [-1, 3]).T z = cropped_vertices[2,:].copy()/tform.params[0,0] cropped_vertices[2,:] = 1 vertices = np.dot(np.linalg.inv(tform.params), cropped_vertices) vertices = np.vstack((vertices[:2,:], z)) pos = np.reshape(vertices.T, [256, 256, 3])

這里不太好可視化,只看看這個深度信息,也就是第三個通道:

plt.imshow(pos[...,2],cmap='gray') plt.axis('off')

很明顯,這個是能看出來臉部的不同位置,顏色深淺不同,鼻子的高度最高,所以比較白一點。

人臉關鍵點

需要注意的是,論文所生成的所有人臉的texture都符合uv_face.png所有器官位置,比如鼻子一定會在texutre的鼻子那里,不管你是側臉還是正臉,uv_kpt_ind.txt這里面定義的就是texture的人臉關鍵點位置,是固定的。

uv_kpt_ind = np.loadtxt('./Data/uv-data/uv_kpt_ind.txt').astype(np.int32) uv_face = plt.imread('./Data/uv-data/uv_face.png') plt.imshow(draw_kps(uv_face,uv_kpt_ind.T)) plt.axis('off')

記住,所有的人臉texture都滿足這個布局,所有器官一定出現在上圖的對應位置。至于怎么獲取texture,后面會介紹。

前面說了,網絡輸出的UV位置映射圖,前面兩個(256,256)(256,256)(256,256)是texture的位置,最后一個維度上texutre在3D圖上的位置。所以根據uv_kpt_ind和UV位置映射圖能找到人臉圖(非紋理圖)上的關鍵點

def draw_kps(img,kps,point_size=2):img = np.array(img*255,np.uint8)for i in range(kps.shape[0]):cv2.circle(img,(int(kps[i,0]),int(kps[i,1])),point_size,(0,255,0),-1)return img face_kps = pos[uv_kpt_ind[1,:],uv_kpt_ind[0,:],:]

可視化看看

plt.imshow(draw_kps(img.copy(),face_kps)) plt.axis('off')

人臉點云

可視化了人臉關鍵點,順帶將face_ind里面定義的所有頂點全可視化一下。

直接從face_ind讀到所有需要的頂點信息

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, :]

根據texture上定義的位置信息,可視化原人臉圖信息:

plt.figure(figsize=(8,8)) plt.imshow(draw_kps(img.copy(),vertices[:,:2],1)) plt.axis('off')

順便也可以看看3D圖

from mpl_toolkits.mplot3d import Axes3D fig = plt.figure() ax1 = plt.axes(projection='3d') ax1.scatter3D(vertices[:,2],vertices[:,0],vertices[:,1], cmap='Blues') #繪制散點圖 ax1.set_xlabel('X Label') ax1.set_ylabel('Y Label') ax1.set_zlabel('Z Label')

都糊一起了,但是能大概看出來人臉模型。

提取紋理圖

上面說了,所有的人臉經過網絡得到的texture都滿足uv_face.png中的器官位置。

怎么根據UV位置映射圖獲取texture呢?一個函數remap:

texture = cv2.remap(img, pos[:,:,:2].astype(np.float32), None, interpolation=cv2.INTER_NEAREST, borderMode=cv2.BORDER_CONSTANT,borderValue=(0))

可視化texture和固定的uv_kpt_ind看看:

plt.imshow(draw_kps(texture,uv_kpt_ind.T)) plt.axis('off')

因為使用的圖片上趙麗穎的正臉,所以側面的texture不清晰,但是正臉的五官位置的確如所料,在固定的位置上出現。

渲染紋理圖/3D人臉

能用一句話把紋理圖獲取到,那么我們就能根據texture和頂點位置將紋理圖重建為3D圖。原理就是利用triangles.txt定義的網格信息,獲取每個網格的顏色,再把顏色貼到對應的3D位置。

首先從texture中找到每個頂點的膚色:

#找到每個三角形每個頂點的膚色 triangles = np.loadtxt('./Data/uv-data/triangles.txt').astype(np.int32) all_colors = np.reshape(texture, [256*256, -1]) colors = all_colors[face_ind, :]print(vertices.shape) # texutre每個像素對應的3D坐標 print(triangles.shape) #每個三角網格對應的像素索引 print(colors.shape) #每個三角形的顏色 ''' (43867, 3) (86906, 3) (43867, 3) '''

獲取每個三角網格的3D位置和貼圖顏色:

#獲取三角形每個頂點的depth,平均值作為三角形高度 tri_depth = (vertices[triangles[:,0],2 ] + vertices[triangles[:,1],2] + vertices[triangles[:,2],2])/3. #獲取三角形每個頂點的color,平均值作為三角形顏色 tri_tex = (colors[triangles[:,0] ,:] + colors[triangles[:,1],:] + colors[triangles[:,2],:])/3. tri_tex = tri_tex*255

接下來對每個三角網格進行貼圖,這里和源碼不同,我用了opencv的畫圖函數來填充三角網格的顏色

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) plt.imshow(img_3D/255.0)

旋轉人臉

既然我們獲取的是3D人臉,當然可以對他進行旋轉操作咯,可以繞x、y、z三個坐標軸分別旋轉,原理就是旋轉所有頂點的定義的3D信息,也就是UV位置映射的最后一個維度定義的坐標。

通過旋轉角度計算旋轉矩陣的方法是:

# 找到旋轉矩陣,參考https://github.com/YadiraF/face3d def angle2matrix(angles):x, y, z = np.deg2rad(angles[0]), np.deg2rad(angles[1]), np.deg2rad(angles[2])# xRx=np.array([[1, 0, 0],[0, np.math.cos(x), -np.math.sin(x)],[0, np.math.sin(x), np.math.cos(x)]])# yRy=np.array([[ np.math.cos(y), 0, np.math.sin(y)],[ 0, 1, 0],[-np.math.sin(y), 0, np.math.cos(y)]])# zRz=np.array([[np.math.cos(z), -np.math.sin(z), 0],[np.math.sin(z), np.math.cos(z), 0],[ 0, 0, 1]])R=Rz.dot(Ry.dot(Rx))return R.astype(np.float32)

繞垂直方向旋轉30度,調用方法就是

trans_mat = angle2matrix((0,30,0))

旋轉頂點位置

# 旋轉坐標 rotated_vertices = vertices.dot(trans_mat.T)

因為是繞遠點旋轉,搞不好會旋轉出去,所以要矯正一下位置

# 把圖像拉到畫布上 ori_x = np.min(vertices[:,0]) ori_y = np.min(vertices[:,1]) rot_x = np.min(rotated_vertices[:,0]) rot_y = np.min(rotated_vertices[:,1]) shift_x = ori_x-rot_x shift_y = ori_y-rot_y rotated_vertices[:,0]=rotated_vertices[:,0]+shift_x rotated_vertices[:,1]=rotated_vertices[:,1]+shift_y

老樣子把texture可視化:

img_3D = np.zeros_like(img,dtype=np.uint8) mask = np.zeros_like(img,dtype=np.uint8) fill_area=0 for i in range(triangles.shape[0]):cnt = np.array([(rotated_vertices[triangles[i,0],0],rotated_vertices[triangles[i,0],1]),(rotated_vertices[triangles[i,1],0],rotated_vertices[triangles[i,1],1]),(rotated_vertices[triangles[i,2],0],rotated_vertices[triangles[i,2],1])],dtype=np.int32)mask = cv2.drawContours(mask,[cnt],0,(255,255,255),-1)if(np.sum(mask[...,0])>fill_area):fill_area = np.sum(mask[...,0])img_3D = cv2.drawContours(img_3D,[cnt],0,tri_tex[i],-1) plt.imshow(img_3D)

從視覺效果上的確是旋轉過了。

關于換臉的方法、流程和代碼可以關注文末的公眾號,這里貼一下效果圖

后記

本博客主要是驗證了PRNet網絡輸出的各種信息代表什么意思。

后面的研究可能會分為:

  • 網絡結構的研究
  • 換臉

當然,博客源碼

鏈接: https://pan.baidu.com/s/18z2b6Sut6qFecOpGqNc8YA

提取碼: ad77

對博客內容有興趣的,可以關注下面公眾號,公眾號與csdn博客會同步更新自己的學習內容,一個方便電腦看,一個方便手機看

總結

以上是生活随笔為你收集整理的3D人脸重建——PRNet网络输出的理解的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。