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博客会同步更新自己的学习内容,一个方便电脑看,一个方便手机看
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/246579.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

tensorflow官方posenet模型解析

前言 tensorflow官方有个姿态估计项目,这个输入和openpose还有点不一样,这里写个单人情况下的模型输出解析方案。 国际惯例,参考博客: 博客: 使用 TensorFlow.js 在浏览器端上实现实时人体姿势检测 tensorflow中posnet的IOS代…

tensorflow2安装时候的一个dll找不到的错误

电脑环境: vs2015python3.7.6,使用anaconda安装的CUDA 10.1cuDnn 7.6.5tensorflow2.1.0 错误内容 File "C:\Users\zb116\anaconda3\lib\imp.py", line 242, in load_modulereturn load_dynamic(name, filename, file)File "C:\Users\z…

PCA、SVD、ZCA白化理论与实现

简介 在UFLDL中介绍了主成分分析这一块的知识,而且当时学机器学习的时候,老师是将PCA和SVD联系起来将的,同时UFLDL也讲到了使用PCA做数据白化whitening处理,这个词经常在论文里面看到。 国际惯例,参考博客&#xff1…

OpenCV使用Tensorflow2-Keras模型

前言 最近工作上需要在C上快速集成Tensorflow/Keras训练好的模型,做算法验证。首先想到的就是opencv里面的dnn模块了,但是它需要的格式文件比较郁闷,是pb格式的模型,但是keras通常保存的是h5文件,查阅了很多资料&…

3D人脸表情驱动——基于eos库

前言 之前出过三篇换脸的博文,遇到一个问题是表情那一块不好处理,可行方法是直接基于2D人脸关键点做网格变形,强行将表情矫正到目标人脸,还有就是使用PRNet的思想,使用目标人脸的顶点模型配合源人脸的纹理&#xff0c…

3D姿态估计——ThreeDPose项目简单易用的模型解析

前言 之前写过tensorflow官方的posenet模型解析,用起来比较简单,但是缺点是只有2D关键点,本着易用性的原则,当然要再来个简单易用的3D姿态估计。偶然看见了ThreeDPose的项目,感觉很强大的,所以把模型扒下来…

简易的素描图片转换流程与实现

前言 之前经常在网上看到用PS实现真实图片到素描图片的转换,但是流程都大同小异,身为一只程序猿,必须来个一键转化额。 国际惯例,参考博客: Photoshop基础教程:混合模式原理篇 颜色减淡的原理讲解以及应…

一个简单好用的磨皮祛斑算法理论和python实现

前言 最近看了一个磨皮算法祛斑感觉效果不错,效果图看文末就行,个人觉得效果非常不错滴。 国际惯例,参考博客: 磨皮算法的源码:YUCIHighPassSkinSmoothing How To Smooth And Soften Skin With Photoshop 图像算法…

OpenVINO——配置与道路分割案例

前言 最近看到了一个深度学习库OpenVINO,专门用于Intel硬件上部署深度学习模型,其内置了非常非常多使用的预训练模型,比如道路分割、人脸提取、3D姿态估计等等。但是配置和调用有点小恶心,这里以道路分割为例,展示如何…

图像颜色迁移《color transfer between images》

前言 前段时间,在深度学习领域不是有个比较火的方向叫风格迁移的嘛,对于我这种不喜欢深度学习那种不稳定结果的人来说,还是想看看传统图像处理领域有什么类似的技术,发现了一个颜色迁移的算法,很久前的论文了。 国际…

ColorSpace颜色空间简介

前言 如果看过之前的介绍的图像颜色迁移《color transfer between images》和颜色协调模型Color Harmoniztion就会发现,大部分图像处理算法虽然输入输出是RGB像素值,但是中间进行算法处理时很少直接更改RGB值,而是转换到其它空间&#xff0c…

Ogre共享骨骼与两种骨骼驱动方法

前言 最近业务中用到Ogre做基于3D关键点虚拟角色骨骼驱动,但是遇到两个问题: 身体、头、眼睛、衣服等mesh的骨骼是分开的,但是骨骼结构都是一样的,需要设置共享骨骼驱动的时候可以直接修改骨骼旋转量,或者将旋转量存…

仿射变换和透视变换

前言 在前面做换脸的博客中提到了使用仿射变换和透视变换将两张不同的人脸基于关键点进行对齐,保证一张人脸贴到另一张人脸时,大小完全一致;所以有必要理解一下这两个概念的区别,由于以实用性为目的,所以所有的图像算…

obj格式解析

前言 最近处理一些网格渲染的时候,需要解析Obj文件,从Free3D上随便找了个免费的人体obj模型解析测试一波 国际惯例,参考博客: 本文所使用的从Free3D下载的模型 .obj文件格式与.mtl文件格式 详解3D中的obj文件格式 3D中OBJ文…

Flask服务部署与简单内网穿透

前言 最近学习部署的时候,想到深度学习里面通常用的部署方法是flask做服务端,然后使用nginx做负载均衡,貌似也能做内网穿透。不过我不太懂负载均衡,只想利用本地电脑搭建一个简单的服务器,实现外部调用API服务的功能。…

OpenCV学习——轮廓检测

前言 轮廓检测是传统视觉中非常常用的功能,这里简单记录一下opencv中的轮廓检测算法使用方法,至于理论,后续有机会再去细品。 国际惯例: OpenCV官方的轮廓检测教程python版 OpenCV中的二值化方法教程 OpenCV轮廓层级官方文档…

RBF神经网络理论与实现

前言 最近发现有挺多人喜欢径向基函数(Radial Basis Function,RBF)神经网络,其实它就是将RBF作为神经网络层间的一种连接方式而已。这里做一个简单的描述和找了个代码解读。 之前也写过一篇,不过排版不好看,可以戳这里跳转 国际惯例&#x…

基于python和unity交互的卡通角色肢体和表情驱动(深度学习)

前言 最近看到了好多卡通角色的肢体驱动的东东,感觉是时候发挥一下读研时候学的东西了,而且虽然现在不炼丹了,但是还是得保持吃丹的技能。这个项目找了很多很多代码进行测试,最终集成了一个3D姿态估计和人脸关键点提取的代码。 …

OpenCV学习——形态学

前言 继续学习图像里面的形态学知识——结构元、腐蚀、膨胀、开运算、闭运算、击中/不击中变换。以及部分基本形态学算法,包括边界提取、空洞填充、连通分量的提取、凸壳、细化、粗化、骨架、裁剪、形态学重建。 其实就是对冈萨雷斯的《数字图像处理》中第9章节《…

径向基函数RBF三维网格变形

前言 之前写过径向基函数(RBF)神经网络做分类或者拟合。然后挖了个坑说在《Phase-Functioned Neural Networks for Character Control》里面提到了用于做地形编辑,所以这篇博客就是解析一下如何用RBF做网格编辑系统。 参考博客: Noe’s tutorial on d…