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网络输出的理解相关推荐
- 3D人脸重建--学习笔记
本文旨在学习总结2D到3D人脸重建相关问题,个人水平有限,本人也是刚开始调研3D人脸重建,不足之处望大神指点改进. 文章目录 1 什么是3D人脸重建? 2 重建方法分类 3 通用模型3D人脸重建 4 ...
- CVPR 2022 | 腾讯优图实验室30篇论文入选,含场景文本语义识别、3D人脸重建、目标检测、视频场景分割和视频插帧等领域...
关注公众号,发现CV技术之美 本文转载自腾讯优图 近日,CVPR 2022官方公布了接收论文列表(CVPR 2022 接收论文公布! 总计2067篇!),来自腾讯优图实验室共计30篇论文被CVPR收录 ...
- VR来了,3D人脸重建跟上《三维人脸重建-3DMM》
之前我们写过了<三维人脸重建入门>,接下来,自然就是入门之后的事情.当然了,不管是一个什么项目,方法永远不会是唯一的. 一 引言 To my best of knowledge,如之前所说 ...
- [读论文]弱监督学习的精确 3D 人脸重建:从单个图像到图像集-Accurate 3D Face Reconstruction with Weakly-Supervised Learning:From
论文地址:Accurate 3D Face Reconstruction with Weakly-Supervised Learning:From Single Image to Image Set ...
- 3D人脸重建硕博论文阅读
基于人脸单视图的3D人脸重建方法研究(华南理工) 摘要 基于人脸正视图来开展 3D 人脸重建.在选择人脸正面图像后,采用主动形状模型(Active Shape Model,ASM)算法进行人脸对齐,从 ...
- 3D人脸重建之DECA
论文:Learning an Animatable Detailed 3D Face Model from In-The-Wild Images Github:https://github.com/Y ...
- 基于多视角照片的3D人脸重建
[原文:http://www.sigvc.org/why/book/3dp/chap5.3.2.htm] 5.3.2 基于多视角照片的3D人脸重建 多视角三维重建的技术原理请详见第6章6.3节&quo ...
- 深度学习 3d人脸 重建_深度学习实时3D人脸跟踪
深度学习 3d人脸 重建 Snapchat was made popular by putting funny dog ears on people's head, swapping faces an ...
- ECCV 2022 | 清华腾讯AI Lab提出REALY: 重新思考3D人脸重建的评估方法
点击下方卡片,关注"CVer"公众号 AI/CV重磅干货,第一时间送达 点击进入-> CV 微信技术交流群 本文分享ECCV 2022论文<REALY: Rethink ...
最新文章
- 使用easeui dialog弹出框中使用CKeditor多次加载后无法编辑问题
- python百度云资源-python学习资源--百度云
- HttpHandler
- mysql保存23:59:59时,自动加一秒
- Typora Mermaid 使用指南
- sql2000执行sql2005导出的数据脚本时出现“提示含有超过64K限度的行”(转)
- PyTorch——nn.Conv2d和其中的padding策略,Caffe、Tensorflow的padding策略
- 第三季度编程语言排行榜出炉,它太稳了!
- dbm数据库详解【flask】【dbm.gun解决】
- 【OpenCV笔记】光流法之金字塔Lucas-Kanade
- Contrastive Learning NLP Papers
- 使用ngrok实现内网穿透,免费在本地发布项目
- oracle 分组 最新,Oracle分组查询
- 零基础怎样自学编程?新手如何学习编程?编程学习入门指南
- 计算机制图怎么学,新手学电脑学习画图的方法
- 压力传感器与压力变送器的区别
- 小猫爪:S32K3学习笔记11-S32K3之FCCU
- BadUSB橡皮鸭WinLinux通用下载者
- 新浪博客大赛:你刷我也刷?
- MIPS汇编语言指令类型
热门文章
- ajax从mysql提取数据在html中_提取图片中数据的科研利器
- 该文章为递归寻找目录下目标文件(待完善,但是能用)
- 在PAT上提交Java代码
- 『操作系统』微内核结构的操作系统几何?(优缺点)
- 『ACM-算法-动态规划』初识DP动态规划算法
- 程序员最喜欢用的在线代码编译器,什么?你竟然不知道!可以在网页敲代码,运行调试!
- (原创)eCos驱动分析 之 ISR是如何与硬件中断联系起来的?
- Python函数式编程简介(二)返回函数
- 银行不是很喜欢客户分期吗?为何申请信用卡分期被拒绝了?
- 腾讯信用向全国开放了,据说700分都算低的!