博客专栏

EEPW首页 > 博客 > 深度学习教你重建赵丽颖的三维人脸

深度学习教你重建赵丽颖的三维人脸

发布人:AI科技大本营 时间:2021-07-04 来源:工程师 发布文章

近年来,三维人脸重建成为计算机视觉、图像识别等研究领域中的热点问题。三维人脸重建技术分为基于不同视角的多幅图像的重建和基于单幅图像的三维人脸重建。

本项目通过使用PRNet算法,通过训练CNN网络实现对二维图像转变为三维空间图像的转换,其中二维使用UV坐标作为2D的表达,可以实现在UV空间保存完整的人脸3D形状。实现的效果如下图所示:

1.jpg2.gif

基本介绍

针对单幅图像的三维人脸重建,传统方法有基于模型的方法、基于明暗形状恢复的方法等。

随着深度学习被引入到三维人脸重建领域,并取得比传统方法更优异的效果,逐渐成为主流的重建方法。JacksonAS等人提出使用VRN(volumetric regression networks)从单个二维图像直接进行三维面部重建的方法。Feng Y等人设计了一个名为UV位置图的二维表示方法,记录UV空间中完整面部的三维形状,然后训练一个简单的卷积神经网络,从单个二维图像中回归。

ChangFJ等人提出了直接应用于人脸图像强度,回归3D表情系数的29D向量的ExpNet CNN模型。Tu等人针对3D标注训练数据短缺问题,提出一种2D辅助自监督学习方法,利用带嘈杂地标信息的无约束二维人脸图像改善三维人脸模型的学习,在密集人脸对齐和三维人脸重建方面取得了突出的效果。刘成攀等人提出一种基于自监督深度学习的人脸表征及三维重建方法,将二维人脸的特征点信息映射到三维空间实现三维人脸重建。

3.jpg

环境要求

本次环境使用的是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

4.png

人脸生成

检测人脸关键点:

通过调用人脸模型监测人脸并将人脸裁剪分出:

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

5.png

获取人脸点云:

三维点云是一个在三维空间关联系统中包含点的数据库,即事物或空间的精确数字记录,主要关于表面点的集合。与相片不同,三维图像是对一类信息的统称,信息还需要有具体的表现形式。

其表现形式包括:深度图(以灰度表达物体与相机的距离),几何模型(由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))

6.png

生成三维人脸:

首先是获取三角形每个顶点的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)

7.png

代码链接:

https://pan.baidu.com/s/1s3NjgxuuCnB238GpzN-6Pw

提取码:ucqj

*博客内容为网友个人发布,仅代表博主个人观点,如有侵权请联系工作人员删除。



关键词: 深度学习

相关推荐

技术专区

关闭