最新网络游戏排行榜2021前十名,温州seo排名公司,网站开发算固定资产,python一句做网站前言
之前有款换脸软件不是叫ZAO么#xff0c;分析了一下#xff0c;它的实现原理绝对是3D人脸重建#xff0c;而非deepfake方法#xff0c;找了一篇3D重建的论文和源码看看。这里对源码中的部分函数做了自己的理解和改写。
国际惯例#xff0c;参考博客#xff1a;
什…前言
之前有款换脸软件不是叫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],cmapgray)
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_size2):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(projection3d)
ax1.scatter3D(vertices[:,2],vertices[:,0],vertices[:,1], cmapBlues) #绘制散点图
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, interpolationcv2.INTER_NEAREST, borderModecv2.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,dtypenp.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])],dtypenp.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])# xRxnp.array([[1, 0, 0],[0, np.math.cos(x), -np.math.sin(x)],[0, np.math.sin(x), np.math.cos(x)]])# yRynp.array([[ np.math.cos(y), 0, np.math.sin(y)],[ 0, 1, 0],[-np.math.sin(y), 0, np.math.cos(y)]])# zRznp.array([[np.math.cos(z), -np.math.sin(z), 0],[np.math.sin(z), np.math.cos(z), 0],[ 0, 0, 1]])RRz.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,dtypenp.uint8)
mask np.zeros_like(img,dtypenp.uint8)
fill_area0
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])],dtypenp.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博客会同步更新自己的学习内容一个方便电脑看一个方便手机看