对于图片转3d人脸方面的研究

1.一个开源的可以运行的项目(face3d/README.md at master · yfeng95/face3d · GitHub

在配置好环境后,让我们一个一个py文件运行它(我将给出中文注释)

1)1_pipeline.py 将一个3d头像的mat文件转换为jpg文件

import os

import sys

import numpy as np

import scipy.io as sio

from skimage import io

import matplotlib.pyplot as plt

将项目路径添加到系统路径中

sys.path.append('..')

import face3d

from face3d import mesh

1. 加载网格数据

C = sio.loadmat('Data/example1.mat')

vertices = C['vertices']

colors = C['colors']

triangles = C['triangles']

colors = colors / np.max(colors) # 归一化颜色值

2. 变换顶点(缩放、旋转、平移)

缩放,目标大小为180

s = 180 / (np.max(vertices[:, 1]) - np.min(vertices[:, 1]))

旋转30度

R = mesh.transform.angle2matrix([0, 30, 0])

平移,中心为[0, 0]

t = [0, 0, 0]

transformed_vertices = mesh.transform.similarity_transform(vertices, s, R, t)

3. 添加光照

设置光源

light_positions = np.array([[-128, -128, 300]])

light_intensities = np.array([[1, 1, 1]])

lit_colors = mesh.light.add_light(transformed_vertices, triangles, colors, light_positions, light_intensities)

4. 变换顶点(相机变换和投影)

从世界坐标系转换到相机坐标系

camera_vertices = mesh.transform.lookat_camera(transformed_vertices, eye=[0, 0, 200], at=np.array([0, 0, 0]), up=None)

正交投影

projected_vertices = mesh.transform.orthographic_project(camera_vertices)

5. 渲染(生成2D图像)

设置图像高度和宽度

h = w = 256

转换为图像坐标

image_vertices = mesh.transform.to_image(projected_vertices, h, w)

渲染

rendering = mesh.render.render_colors(image_vertices, triangles, lit_colors, h, w)

保存渲染结果

save_folder = 'results/pipeline'

if not os.path.exists(save_folder):

os.mkdir(save_folder)

io.imsave(f'{save_folder}/rendering2222.jpg', rendering)

显示网格(可选)

mesh.vis.plot_mesh(camera_vertices, triangles)

plt.show()

#这里的mat文件就是特定示例的数据的MATLAB文件,在这里是一个人头像的mat文件

注释和说明:

  • 步骤 1:加载网格数据

    • 使用 scipy.io.loadmat 加载来自 Data/example1.mat 文件的顶点、颜色和三角形数据。
  • 步骤 2:变换顶点

    • 计算缩放比例 s,使得顶点在 y 轴上的范围缩放到目标大小 180。
    • 根据角度 [0, 30, 0] 创建旋转矩阵 R
    • 应用 similarity_transform 函数对顶点进行缩放、旋转和平移变换。
  • 步骤 3:添加光照

    • 定义单个光源的位置和强度。
    • 使用 add_light 函数为顶点添加光照效果,生成 lit_colors
  • 步骤 4:变换顶点(相机变换和投影)

    • 使用 lookat_camera 函数将顶点从世界坐标系转换到相机坐标系。
    • 使用 orthographic_project 函数对相机坐标系中的顶点进行正交投影。
  • 步骤 5:渲染(生成2D图像)

    • 设定图像的高度和宽度为 256 像素。
    • 将投影后的顶点转换为图像坐标。
    • 使用 render_colors 函数基于三角形网格和光照处理后的颜色数据生成最终的渲染图像 rendering

2)2_3dmm.py

''' 3D可变形模型示例

3DMM参数 --> 网格

拟合: 2D图像 + 3DMM -> 3D人脸

'''

import os, sys

import subprocess

import numpy as np

import scipy.io as sio

from skimage import io

from time import time

import matplotlib.pyplot as plt

添加上一级目录到系统路径,以便导入face3d库

sys.path.append('..')

import face3d

from face3d import mesh

from face3d.morphable_model import MorphabelModel

--------------------- 前向传播:参数(形状,表情,姿势) --> 3D对象 --> 2D图像 ---------------

--- 1. 加载模型

bfm = MorphabelModel('Data/BFM/Out/BFM.mat')

print('初始化BFM模型成功')

--- 2. 生成人脸网格:顶点(表示形状)和颜色(表示纹理)

sp = bfm.get_shape_para('random') # 获取随机形状参数

ep = bfm.get_exp_para('random') # 获取随机表情参数

vertices = bfm.generate_vertices(sp, ep) # 生成顶点

tp = bfm.get_tex_para('random') # 获取随机纹理参数

colors = bfm.generate_colors(tp) # 生成颜色

colors = np.minimum(np.maximum(colors, 0), 1) # 将颜色限制在0到1之间

--- 3. 将顶点转换到合适的位置

s = 8e-04 # 缩放系数

angles = [10, 30, 20] # 旋转角度

t = [0, 0, 0] # 平移向量

transformed_vertices = bfm.transform(vertices, s, angles, t) # 变换顶点

projected_vertices = transformed_vertices.copy() # 使用标准相机和正交投影

--- 4. 渲染(3D对象 --> 2D图像)

设置渲染属性

h = w = 256; c = 3 # 图像高度、宽度和通道数

image_vertices = mesh.transform.to_image(projected_vertices, h, w) # 将顶点转换到图像坐标系

image = mesh.render.render_colors(image_vertices, bfm.triangles, colors, h, w) # 渲染颜色图像

-------------------- 反向传播: 2D图像点和对应的3D顶点索引 --> 参数(姿势, 形状, 表情) ------

只使用68个关键点进行拟合

x = projected_vertices[bfm.kpt_ind, :2] # 2D关键点,可以从图像中检测到

X_ind = bfm.kpt_ind # 3DMM中关键点的索引,固定的

拟合

fitted_sp, fitted_ep, fitted_s, fitted_angles, fitted_t = bfm.fit(x, X_ind, max_iter = 3) # 拟合形状、表情、缩放、角度和平移参数

验证拟合参数

fitted_vertices = bfm.generate_vertices(fitted_sp, fitted_ep) # 生成拟合后的顶点

transformed_vertices = bfm.transform(fitted_vertices, fitted_s, fitted_angles, fitted_t) # 变换顶点

image_vertices = mesh.transform.to_image(transformed_vertices, h, w) # 将顶点转换到图像坐标系

fitted_image = mesh.render.render_colors(image_vertices, bfm.triangles, colors, h, w) # 渲染拟合后的图像

------------- 打印和显示结果

print('姿势,真实值: \n', s, angles[0], angles[1], angles[2], t[0], t[1])

print('姿势,拟合值: \n', fitted_s, fitted_angles[0], fitted_angles[1], fitted_angles[2], fitted_t[0], fitted_t[1])

保存结果图像

save_folder = 'results/3dmm'

if not os.path.exists(save_folder):

os.mkdir(save_folder)

io.imsave('{}/generated.jpg'.format(save_folder), image) # 保存生成的图像

io.imsave('{}/fitted.jpg'.format(save_folder), fitted_image) # 保存拟合的图像

----------------- 可视化拟合过程

拟合

fitted_sp, fitted_ep, fitted_s, fitted_angles, fitted_t = bfm.fit(x, X_ind, max_iter = 3, isShow = True) # 拟合并显示过程

验证拟合参数

for i in range(fitted_sp.shape[0]):

fitted_vertices = bfm.generate_vertices(fitted_sp[i], fitted_ep[i]) # 生成拟合后的顶点

transformed_vertices = bfm.transform(fitted_vertices, fitted_s[i], fitted_angles[i], fitted_t[i]) # 变换顶点

image_vertices = mesh.transform.to_image(transformed_vertices, h, w) # 将顶点转换到图像坐标系

fitted_image = mesh.render.render_colors(image_vertices, bfm.triangles, colors, h, w) # 渲染拟合后的图像

io.imsave('{}/show_{:0>2d}.jpg'.format(save_folder, i), fitted_image) # 保存每一步的图像

生成GIF动画

options = '-delay 20 -loop 0 -layers optimize' # GIF动画选项,需要ImageMagick

subprocess.call('convert {} {}/show_*.jpg {}'.format(options, save_folder, save_folder + '/3dmm.gif'), shell=True) # 调用ImageMagick生成GIF

subprocess.call('rm {}/show_*.jpg'.format(save_folder), shell=True) # 删除临时图像文件

这段代码是一个关于3D可变形模型(3DMM)的示例,它展示了如何利用该模型从参数(形状、表情、姿势)生成3D人脸,并将其渲染成2D图像。它与之前提到的 1_pipeline.py 文件是相关联的,因为它们都涉及到了使用 face3d 库中的模型和函数来处理和渲染3D人脸数据。

3DMM(3D Morphable Model)是一种用于生成和表示3D人脸模型的统计模型。它基于统计学原理,通过对大量3D人脸数据进行分析和学习,构建出一个能够表示多种不同人脸形状、表情和纹理的参数化模型。

主要步骤和功能解析:

  1. 导入和设置

    • 导入必要的库和模块,包括 ossyssubprocessnumpyscipy.ioskimage.iomatplotlib.pyplot
    • 将上级目录添加到系统路径,以便能够导入 face3d 库和其子模块。
  2. 初始化和加载模型

    • 使用 MorphableModel 类加载预训练的3DMM模型文件 BFM.mat
    • 通过随机参数获取初始的形状参数 (sp)、表情参数 (ep) 和纹理参数 (tp)。
  3. 生成人脸网格

    • 使用获取的随机参数生成顶点 (vertices) 和颜色信息 (colors)。
    • 将颜色值限制在 [0, 1] 范围内。
  4. 顶点变换和投影

    • 定义缩放系数 s 和旋转角度 angles
    • 使用 transform 方法对顶点进行变换,然后复制到 projected_vertices,表示标准相机和正交投影后的顶点位置。
  5. 渲染生成的3D人脸

    • 设定图像的大小为 256x256 像素。
    • 将变换后的顶点投影到图像坐标系。
    • 使用 render_colors 方法根据三角形网格和颜色信息生成渲染图像 image
  6. 拟合过程

    • 从生成的2D图像中提取关键点坐标 x 和对应的3DMM关键点索引 X_ind
    • 使用 fit 方法对关键点进行拟合,得到拟合后的形状参数 (fitted_sp)、表情参数 (fitted_ep)、缩放、旋转角度和平移参数。
  7. 验证拟合参数

    • 根据拟合后的参数生成新的顶点并进行变换。
    • 将变换后的顶点投影到图像坐标系并渲染成图像,保存为 fitted_image
  8. 结果保存和可视化

    • 打印并比较真实的姿势参数和拟合后的参数。
    • 将生成的图像和拟合后的图像保存到指定的文件夹 results/3dmm 中。
  9. 可视化拟合过程

    • 如果需要,可以将拟合过程中每一步的图像保存下来,生成一个 GIF 动画展示拟合的过程。

3)3_transform.py

''' 3D对象变换和相机模型示例

'''

import os, sys

import numpy as np

import math

import scipy.io as sio

from skimage import io

from time import time

import subprocess

添加上一级目录到系统路径,以便导入face3d库

sys.path.append('..')

import face3d

from face3d import mesh

定义变换测试函数

def transform_test(vertices, obj, camera, h=256, w=256):

'''

Args:

vertices: 顶点数组

obj: 包含对象变换参数的字典

camera: 包含相机参数的字典

h: 图像高度

w: 图像宽度

'''

获取旋转矩阵

R = mesh.transform.angle2matrix(obj['angles'])

相似变换(旋转、缩放、平移)

transformed_vertices = mesh.transform.similarity_transform(vertices, obj['s'], R, obj['t'])

如果是正交投影

if camera['proj_type'] == 'orthographic':

projected_vertices = transformed_vertices

image_vertices = mesh.transform.to_image(projected_vertices, h, w)

else:

世界坐标系转换到相机坐标系

camera_vertices = mesh.transform.lookat_camera(transformed_vertices, camera['eye'], camera['at'], camera['up'])

相机坐标系转换到图像坐标系(投影)

projected_vertices = mesh.transform.perspective_project(camera_vertices, camera['fovy'], near=camera['near'], far=camera['far'])

转换为图像坐标

image_vertices = mesh.transform.to_image(projected_vertices, h, w, True)

渲染图像

rendering = mesh.render.render_colors(image_vertices, triangles, colors, h, w)

rendering = np.minimum(np.maximum(rendering, 0), 1)

return rendering

加载网格数据

C = sio.loadmat('Data/example1.mat')

vertices = C['vertices']

colors = C['colors']

triangles = C['triangles']

colors = colors / np.max(colors)

将顶点的中心移动到[0, 0, 0]

vertices = vertices - np.mean(vertices, 0)[np.newaxis, :]

创建保存文件夹

save_folder = 'results/transform'

if not os.path.exists(save_folder):

os.mkdir(save_folder)

options = '-delay 10 -loop 0 -layers optimize' # GIF选项,需要安装ImageMagick

开始处理

obj = {}

camera = {}

将面部模型缩放到真实大小,约18cm

scale_init = 180 / (np.max(vertices[:, 1]) - np.min(vertices[:, 1]))

1. 固定相机模型(标准相机和正交投影),改变对象位置

camera['proj_type'] = 'orthographic'

缩放

for factor in np.arange(0.5, 1.2, 0.1):

obj['s'] = scale_init * factor

obj['angles'] = [0, 0, 0]

obj['t'] = [0, 0, 0]

image = transform_test(vertices, obj, camera)

io.imsave('{}/1_1_{:.2f}.jpg'.format(save_folder, factor), image)

旋转角度

for i in range(3):

for angle in np.arange(-50, 51, 10):

obj['s'] = scale_init

obj['angles'] = [0, 0, 0]

obj['angles'][i] = angle

obj['t'] = [0, 0, 0]

image = transform_test(vertices, obj, camera)

io.imsave('{}/1_2_{}_{}.jpg'.format(save_folder, i, angle), image)

使用ImageMagick将图片转换为GIF

subprocess.call('convert {} {}/1_*.jpg {}'.format(options, save_folder, save_folder + '/obj.gif'), shell=True)

2. 固定对象位置(中心为[0,0,0],正面姿态),改变相机位置和方向,使用透视投影(固定视场角)

obj['s'] = scale_init

obj['angles'] = [0, 0, 0]

obj['t'] = [0, 0, 0]

camera['proj_type'] = 'perspective'

camera['at'] = [0, 0, 0]

camera['near'] = 1000

camera['far'] = -100

camera['fovy'] = 30

camera['up'] = [0, 1, 0]

沿Z轴移动相机,从远到近,始终看向面部中心

for p in np.arange(500, 250-1, -40):

camera['eye'] = [0, 0, p]

image = transform_test(vertices, obj, camera)

io.imsave('{}/2_eye_1_{}.jpg'.format(save_folder, 1000-p), image)

沿Y轴移动相机,从下到上,始终看向面部中心

for p in np.arange(-300, 301, 60):

camera['eye'] = [0, p, 250]

image = transform_test(vertices, obj, camera)

io.imsave('{}/2_eye_2_{}.jpg'.format(save_folder, p/6), image)

沿X轴移动相机,从左到右,始终看向面部中心

for p in np.arange(-300, 301, 60):

camera['eye'] = [p, 0, 250]

image = transform_test(vertices, obj, camera)

io.imsave('{}/2_eye_3_{}.jpg'.format(save_folder, -p/6), image)

调整相机的上方向

camera['eye'] = [0, 0, 250]

for p in np.arange(-50, 51, 10):

world_up = np.array([0, 1, 0]) # 默认方向

z = np.deg2rad(p)

Rz = np.array([[math.cos(z), -math.sin(z), 0], [math.sin(z), math.cos(z), 0], [0, 0, 1]])

up = Rz.dot(world_up[:, np.newaxis])

camera['up'] = np.squeeze(up)

image = transform_test(vertices, obj, camera)

io.imsave('{}/2_eye_4_{}.jpg'.format(save_folder, -p), image)

使用ImageMagick将图片转换为GIF

subprocess.call('convert {} {}/2_*.jpg {}'.format(options, save_folder, save_folder + '/camera.gif'), shell=True)

删除生成的JPG文件

print('GIF已经生成,现在删除JPG文件')

subprocess.call('rm {}/*.jpg'.format(save_folder), shell=True)

该代码展示了如何通过改变3D对象的变换参数(如缩放、旋转)和相机的参数(如位置、方向、投影类型),生成对应的2D图像,并将这些图像合成为GIF动画。这些步骤对于理解3D对象在不同变换下的表现,以及相机模型的应用非常有帮助。

4_light.py

import os, sys

import numpy as np

import scipy.io as sio

from skimage import io

from time import time

import subprocess

sys.path.append('..')

import face3d

from face3d import mesh

def light_test(vertices, light_positions, light_intensities, h = 256, w = 256):

添加光源并渲染

lit_colors = mesh.light.add_light(vertices, triangles, colors, light_positions, light_intensities)

image_vertices = mesh.transform.to_image(vertices, h, w)

rendering = mesh.render.render_colors(image_vertices, triangles, lit_colors, h, w)

rendering = np.minimum((np.maximum(rendering, 0)), 1)

return rendering

--------- 加载网格数据

C = sio.loadmat('Data/example1.mat')

vertices = C['vertices']

global colors

global triangles

colors = C['colors']

triangles = C['triangles']

colors = colors / np.max(colors) # 归一化颜色

将顶点中心移动到[0,0,0]

vertices = vertices - np.mean(vertices, 0)[np.newaxis, :]

缩放因子使人脸模型高度为18cm

s = 180 / (np.max(vertices[:, 1]) - np.min(vertices[:, 1]))

R = mesh.transform.angle2matrix([0, 0, 0])

t = [0, 0, 0]

vertices = mesh.transform.similarity_transform(vertices, s, R, t) # 变换后的顶点

保存设置

save_folder = 'results/light'

if not os.path.exists(save_folder):

os.mkdir(save_folder)

options = '-delay 12 -loop 0 -layers optimize' # GIF选项,需要ImageMagick

---- 开始

1. 固定光源强度,改变光源位置

x轴:光从左到右

light_intensities = np.array([[1, 1, 1]])

for i, p in enumerate(range(-200, 201, 40)):

light_positions = np.array([[p, 0, 300]])

image = light_test(vertices, light_positions, light_intensities)

io.imsave('{}/1_1_{:0>2d}.jpg'.format(save_folder, i), image)

y轴:光从上到下

for i, p in enumerate(range(200, -201, -40)):

light_positions = np.array([[0, p, 300]])

image = light_test(vertices, light_positions, light_intensities)

io.imsave('{}/1_2_{:0>2d}.jpg'.format(save_folder, i), image)

z轴:光从近到远

for i, p in enumerate(range(100, 461, 40)):

light_positions = np.array([[0, 0, p]])

image = light_test(vertices, light_positions, light_intensities)

io.imsave('{}/1_3_{:0>2d}.jpg'.format(save_folder, i), image)

subprocess.call('convert {} {}/1_*.jpg {}'.format(options, save_folder, save_folder + '/position.gif'), shell=True)

2. 固定光源位置,改变光源强度

light_positions = np.array([[0, 0, 300]])

for k in range(3):

for i, p in enumerate(np.arange(0.4, 1.1, 0.2)):

light_intensities = np.array([[0, 0, 0]], dtype=np.float32)

light_intensities[0, k] = p

image = light_test(vertices, light_positions, light_intensities)

io.imsave('{}/2_{}_{:0>2d}.jpg'.format(save_folder, k, i), image)

subprocess.call('convert {} {}/2_*.jpg {}'.format(options, save_folder, save_folder + '/intensity.gif'), shell=True)

-- 删除jpg文件

print('GIF已生成,现在删除jpg文件')

subprocess.call('rm {}/*.jpg'.format(save_folder), shell=True)
5_render.py

''' 测试渲染速度。

'''

import os, sys

import numpy as np

import scipy.io as sio

from skimage import io

from time import time

import matplotlib.pyplot as plt

np.set_printoptions(suppress=True)

sys.path.append('..')

import face3d

from face3d import mesh

from face3d import mesh_numpy

from face3d.morphable_model import MorphabelModel

加载数据

C = sio.loadmat('Data/example1.mat')

vertices = C['vertices']; colors = C['colors']; triangles = C['triangles']

colors = colors / np.max(colors)

将顶点中心移动到[0,0,0]

vertices = vertices - np.mean(vertices, 0)[np.newaxis, :]

缩放因子使人脸模型高度为18cm

s = 180 / (np.max(vertices[:,1]) - np.min(vertices[:,1]))

R = mesh.transform.angle2matrix([0, 0, 0])

t = [0, 0, 0]

vertices = mesh.transform.similarity_transform(vertices, s, R, t)

渲染的图像大小

h = w = 256

image_vertices = mesh.transform.to_image(vertices, h, w)

----------------------------------------- 渲染颜色

使用Python numpy渲染颜色

st = time()

rendering_cp = mesh_numpy.render.render_colors(image_vertices, triangles, colors, h, w)

print('----------colors python: ', time() - st)

使用Python numpy Rasterize方式渲染颜色

st = time()

rendering_cpr = mesh_numpy.render.render_colors_ras(image_vertices, triangles, colors, h, w)

print('----------colors python ras: ', time() - st)

使用C++渲染颜色

st = time()

rendering_cc = mesh.render.render_colors(image_vertices, triangles, colors, h, w)

print('----------colors c++: ', time() - st)

----------------------------------------- 渲染纹理

当面部纹理保存为纹理映射时,推荐使用此方法。

加载纹理

texture = io.imread('Data/uv_texture_map.jpg') / 255.

tex_h, tex_w, _ = texture.shape

加载纹理坐标(uv坐标)

uv_coords = face3d.morphable_model.load.load_uv_coords('Data/BFM/Out/BFM_UV.mat') # 一般的UV坐标范围为[0-1]

转换到纹理大小

texcoord = np.zeros_like(uv_coords)

texcoord[:, 0] = uv_coords[:, 0] * (tex_h - 1)

texcoord[:, 1] = uv_coords[:, 1] * (tex_w - 1)

texcoord[:, 1] = tex_w - texcoord[:, 1] - 1

texcoord = np.hstack((texcoord, np.zeros((texcoord.shape[0], 1)))) # 添加z坐标

tex_triangles = triangles

使用Python numpy渲染纹理

st = time()

rendering_tp = face3d.mesh_numpy.render.render_texture(image_vertices, triangles, texture, texcoord, tex_triangles, h, w)

print('----------texture python: ', time() - st)

使用C++渲染纹理

st = time()

rendering_tc = face3d.mesh.render.render_texture(image_vertices, triangles, texture, texcoord, tex_triangles, h, w)

print('----------texture c++: ', time() - st)

绘图(如果需要查看效果,取消注释以下代码)

plt.subplot(2, 2, 1)

plt.imshow(rendering_cp)

plt.subplot(2, 2, 2)

plt.imshow(rendering_cc)

plt.subplot(2, 2, 3)

plt.imshow(rendering_tp)

plt.subplot(2, 2, 4)

plt.imshow(rendering_tc)

plt.show()

--------------------------- 写入OBJ文件

save_folder = os.path.join('results', 'io')

if not os.path.exists(save_folder):

os.mkdir(save_folder)

将顶点、颜色和纹理坐标转换为浮点数格式

vertices, colors, uv_coords = image_vertices.astype(np.float32).copy(), colors.astype(np.float32).copy(), uv_coords.astype(np.float32).copy()

使用Python numpy写入OBJ文件

st = time()

mesh_numpy.io.write_obj_with_colors_texture(os.path.join(save_folder, 'numpy.obj'), vertices, triangles, colors, texture, uv_coords)

print('----------write obj numpy: ', time() - st)

使用Cython写入OBJ文件

st = time()

mesh.io.write_obj_with_colors_texture(os.path.join(save_folder, 'cython.obj'), vertices, triangles, colors, texture, uv_coords)

print('----------write obj cython: ', time() - st)
6_generate_image_map.py

'''

生成表示不同属性(颜色、深度、PNCC等)的2D图像映射

: 将属性渲染到图像空间。

'''

import os, sys

import numpy as np

import scipy.io as sio

from skimage import io

from time import time

import matplotlib.pyplot as plt

sys.path.append('..')

import face3d

from face3d import mesh

------------------------------ 加载网格数据

C = sio.loadmat('Data/example1.mat')

vertices = C['vertices']; colors = C['colors']; triangles = C['triangles']

colors = colors / np.max(colors)

------------------------------ 修改顶点位置(变换:改变物体的位置)

缩放。目标大小为200,例如

s = 180 / (np.max(vertices[:,1]) - np.min(vertices[:,1]))

旋转30度,例如

R = mesh.transform.angle2matrix([0, 30, 0])

不进行平移。物体中心为[0,0]

t = [0, 0, 0]

transformed_vertices = mesh.transform.similarity_transform(vertices, s, R, t)

------------------------------ 渲染设置(转换为2D图像)

设置渲染的高度和宽度

h = w = 256

转换为图像坐标进行渲染

image_vertices = mesh.transform.to_image(transformed_vertices, h, w)

--- 开始生成图像

save_folder = 'results/image_map'

if not os.path.exists(save_folder):

os.mkdir(save_folder)

0. 颜色映射

attribute = colors

color_image = mesh.render.render_colors(image_vertices, triangles, attribute, h, w, c=3)

io.imsave('{}/color.jpg'.format(save_folder), np.squeeze(color_image))

1. 深度图

z = image_vertices[:, 2:]

z = z - np.min(z)

z = z / np.max(z)

attribute = z

depth_image = mesh.render.render_colors(image_vertices, triangles, attribute, h, w, c=1)

io.imsave('{}/depth.jpg'.format(save_folder), np.squeeze(depth_image))

2. PNCC(局部法线一致性约束编码)图像

pncc = face3d.morphable_model.load.load_pncc_code('Data/BFM/Out/pncc_code.mat')

attribute = pncc

pncc_image = mesh.render.render_colors(image_vertices, triangles, attribute, h, w, c=3)

io.imsave('{}/pncc.jpg'.format(save_folder), np.squeeze(pncc_image))

3. UV坐标图像(用于密集对应)

uv_coords = face3d.morphable_model.load.load_uv_coords('Data/BFM/Out/BFM_UV.mat') #

attribute = uv_coords # 注意:原始论文使用量化的坐标,这里未进行量化

uv_coords_image = mesh.render.render_colors(image_vertices, triangles, attribute, h, w, c=2) # 两个通道:u, v

添加一个通道以便显示

uv_coords_image = np.concatenate((np.zeros((h, w, 1)), uv_coords_image), 2)

io.imsave('{}/uv_coords.jpg'.format(save_folder), np.squeeze(uv_coords_image))
7_generate_uv_map.py

'''

生成表示不同属性(颜色、深度、图像位置等)的2D UV映射图

: 将属性渲染到UV空间。

'''

import os, sys

import numpy as np

import scipy.io as sio

from skimage import io

import skimage.transform

from time import time

import matplotlib.pyplot as plt

sys.path.append('..')

import face3d

from face3d import mesh

from face3d.morphable_model import MorphabelModel

def process_uv(uv_coords, uv_h=256, uv_w=256):

uv_coords[:, 0] = uv_coords[:, 0] * (uv_w - 1)

uv_coords[:, 1] = uv_coords[:, 1] * (uv_h - 1)

uv_coords[:, 1] = uv_h - uv_coords[:, 1] - 1

uv_coords = np.hstack((uv_coords, np.zeros((uv_coords.shape[0], 1)))) # 添加z坐标

return uv_coords

-- 加载网格数据

C = sio.loadmat('Data/example1.mat')

vertices = C['vertices']; colors = C['colors']; triangles = C['full_triangles']

colors = colors / np.max(colors)

-- 修改顶点位置(进行变换,改变物体的位置)

s = 180 / (np.max(vertices[:, 1]) - np.min(vertices[:, 1]))

R = mesh.transform.angle2matrix([-10, -35, 20])

t = [0, 0, 0]

transformed_vertices = mesh.transform.similarity_transform(vertices, s, R, t)

-- 加载UV坐标

uv_coords = face3d.morphable_model.load.load_uv_coords('Data/BFM/Out/BFM_UV.mat')

-- 开始生成图像

save_folder = 'results/uv_map'

if not os.path.exists(save_folder):

os.mkdir(save_folder)

uv_h = uv_w = 256

image_h = image_w = 256

uv_coords = process_uv(uv_coords, uv_h, uv_w)

#-- 1. UV纹理映射

attribute = colors

uv_texture_map = mesh.render.render_colors(uv_coords, triangles, attribute, uv_h, uv_w, c=3)

io.imsave('{}/uv_texture_map.jpg'.format(save_folder), np.squeeze(uv_texture_map))

#-- 2. UV位置图(用于面部重建和对齐的密集对应)

对于面部重建和对齐,UV位置图记录了顶点在UV坐标系中的位置信息

注意:UV位置图依赖于对应的渲染图像

projected_vertices = transformed_vertices.copy() # 使用标准相机和正交投影

image_vertices = mesh.transform.to_image(projected_vertices, image_h, image_w)

position = image_vertices.copy()

position[:, 2] = position[:, 2] - np.min(position[:, 2]) # 将Z坐标平移到非负数

attribute = position

image = mesh.render.render_colors(image_vertices, triangles, colors, image_h, image_w, c=3)

uv_position_map = mesh.render.render_colors(uv_coords, triangles, attribute, uv_h, uv_w, c=3)

io.imsave('{}/image.jpg'.format(save_folder), np.squeeze(image))

np.save('{}/uv_position_map.npy'.format(save_folder), uv_position_map)

io.imsave('{}/uv_position_map.jpg'.format(save_folder), (uv_position_map) / max(image_h, image_w)) # 仅用于显示

-- 3. 一般的几何图像。属性为顶点或转换后的顶点

TODO

-- 4. 法线属性

TODO

8_generate_posmap_300WLP.py

'''

生成300W_LP数据集的UV位置映射图

'''

import os, sys

import numpy as np

import scipy.io as sio

from skimage import io

import skimage.transform

from time import time

import matplotlib.pyplot as plt

sys.path.append('..')

import face3d

from face3d import mesh

from face3d.morphable_model import MorphabelModel

def process_uv(uv_coords, uv_h=256, uv_w=256):

uv_coords[:, 0] = uv_coords[:, 0] * (uv_w - 1)

uv_coords[:, 1] = uv_coords[:, 1] * (uv_h - 1)

uv_coords[:, 1] = uv_h - uv_coords[:, 1] - 1

uv_coords = np.hstack((uv_coords, np.zeros((uv_coords.shape[0], 1)))) # 添加z坐标

return uv_coords

def run_posmap_300W_LP(bfm, image_path, mat_path, save_folder, uv_h=256, uv_w=256, image_h=256, image_w=256):

1. 加载图像和拟合参数

image_name = image_path.strip().split('/')[-1]

image = io.imread(image_path) / 255.

[h, w, c] = image.shape

info = sio.loadmat(mat_path)

pose_para = info['Pose_Para'].T.astype(np.float32)

shape_para = info['Shape_Para'].astype(np.float32)

exp_para = info['Exp_Para'].astype(np.float32)

2. 生成网格模型

生成形状

vertices = bfm.generate_vertices(shape_para, exp_para)

变换网格

s = pose_para[-1, 0]

angles = pose_para[:3, 0]

t = pose_para[3:6, 0]

transformed_vertices = bfm.transform_3ddfa(vertices, s, angles, t)

projected_vertices = transformed_vertices.copy() # 使用标准相机和正交投影

image_vertices = projected_vertices.copy()

image_vertices[:, 1] = h - image_vertices[:, 1] - 1

3. 使用关键点裁剪图像

kpt = image_vertices[bfm.kpt_ind, :].astype(np.int32)

left = np.min(kpt[:, 0])

right = np.max(kpt[:, 0])

top = np.min(kpt[:, 1])

bottom = np.max(kpt[:, 1])

center = np.array([right - (right - left) / 2.0,

bottom - (bottom - top) / 2.0])

old_size = (right - left + bottom - top) / 2

size = int(old_size * 1.5)

随机扰动,可以根据需要调整这些参数

marg = old_size * 0.1

t_x = np.random.rand() * marg * 2 - marg

t_y = np.random.rand() * marg * 2 - marg

center[0] = center[0] + t_x;

center[1] = center[1] + t_y

size = size * (np.random.rand() * 0.2 + 0.9)

裁剪并记录变换参数

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, image_h - 1], [image_w - 1, 0]])

tform = skimage.transform.estimate_transform('similarity', src_pts, DST_PTS)

cropped_image = skimage.transform.warp(image, tform.inverse, output_shape=(image_h, image_w))

将面部位置(图像顶点)转换为UV空间

position = image_vertices.copy()

position[:, 2] = 1

position = np.dot(position, tform.params.T)

position[:, 2] = image_vertices[:, 2] * tform.params[0, 0] # 缩放Z坐标

position[:, 2] = position[:, 2] - np.min(position[:, 2]) # 平移Z坐标

4. 生成UV位置映射图

uv_position_map = mesh.render.render_colors(uv_coords, bfm.full_triangles, position, uv_h, uv_w, c=3)

5. 保存文件

io.imsave('{}/{}'.format(save_folder, image_name), np.squeeze(cropped_image))

np.save('{}/{}'.format(save_folder, image_name.replace('jpg', 'npy')), uv_position_map)

io.imsave('{}/{}'.format(save_folder, image_name.replace('.jpg', '_posmap.jpg')),

(uv_position_map) / max(image_h, image_w)) # 仅用于显示

-- 验证

import cv2

uv_texture_map_rec = cv2.remap(cropped_image, uv_position_map[:,:,:2].astype(np.float32), None, interpolation=cv2.INTER_LINEAR, borderMode=cv2.BORDER_CONSTANT,borderValue=(0))

io.imsave('{}/{}'.format(save_folder, image_name.replace('.jpg', '_tex.jpg')), np.squeeze(uv_texture_map_rec))

if name == 'main':

save_folder = 'results/posmap_300WLP'

if not os.path.exists(save_folder):

os.mkdir(save_folder)

设置参数

uv_h = uv_w = 256

image_h = image_w = 256

加载UV坐标

uv_coords = face3d.morphable_model.load.load_uv_coords('Data/BFM/Out/BFM_UV.mat')

uv_coords = process_uv(uv_coords, uv_h, uv_w)

加载BFM模型

bfm = MorphabelModel('Data/BFM/Out/BFM.mat')

运行生成UV位置映射图

image_path = 'Data/IBUG_image_008_1_0.jpg'

mat_path = 'Data/IBUG_image_008_1_0.mat'

run_posmap_300W_LP(bfm, image_path, mat_path, save_folder)

2. 链接:https://pan.baidu.com/s/1s3NjgxuuCnB238GpzN-6Pw 提取码:ucqj

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

# 将图像从浮点数转换为无符号8位整数,将关键点绘制为绿色的圆圈,并返回绘制了关键点的图像。
def draw_kps(img, kps, point_size=2):
    # 将图像从浮点数(范围[0,1])转换为无符号8位整数(范围[0,255])
    img = np.array(img * 255, np.uint8)

    # 遍历每个关键点
    for i in range(kps.shape[0]):
        # 获取当前关键点的坐标(x, y)
        x, y = int(kps[i, 0]), int(kps[i, 1])
        
        # 在图像上绘制圆形(关键点),圆心为 (x, y),颜色为绿色 (0, 255, 0),半径为 point_size
        cv2.circle(img, (x, y), point_size, (0, 255, 0), -1)
    
    # 返回绘制了关键点的图像
    return img


# 找到旋转矩阵,参考https://github.com/YadiraF/face3d
def angle2matrix(angles):
    x, y, z = np.deg2rad(angles[0]), np.deg2rad(angles[1]), np.deg2rad(angles[2])
    # x
    Rx=np.array([[1,              0,                0],
                 [0, np.math.cos(x),  -np.math.sin(x)],
                 [0, np.math.sin(x),   np.math.cos(x)]])
    # y
    Ry=np.array([[ np.math.cos(y), 0, np.math.sin(y)],
                 [              0, 1,              0],
                 [-np.math.sin(y), 0, np.math.cos(y)]])
    # z
    Rz=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)


# 主要功能代码
# 加载预训练的 Haar 特征级联分类器用于人脸检测
cas = cv2.CascadeClassifier('./Data/cv-data/haarcascade_frontalface_alt2.xml')
#加载测试图像
img = plt.imread('./images/test.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
#进行仿射变换:使用 skimage.transform.estimate_transform 函数进行相似性仿射变换,将原始图像中的人脸区域调整到目标大小(256x256像素)
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')
plt.show()

#使用一个预训练的神经网络模型来预测裁剪后人脸图像的关键点位置
#初始化关键点预测器
pos_predictor = PosPrediction(256, 256)
#加载预训练的模型权重
pos_predictor.restore('./Data/net-data/256_256_resfcn256_weight')
#对裁剪后的人脸图像进行关键点预测
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')
plt.show()

#人脸关键点
#加载关键点索引和UV映射图
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')
#绘制UV映射图中的关键点
plt.imshow(draw_kps(uv_face,uv_kpt_ind.T))
plt.axis('off')
plt.show()
#从三维坐标中提取人脸关键点
face_kps = pos[uv_kpt_ind[1,:],uv_kpt_ind[0,:],:]
plt.imshow(draw_kps(img.copy(),face_kps))
plt.axis('off')
plt.show()
#加载人脸点云索引和绘制
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))
plt.imshow(draw_kps(texture,uv_kpt_ind.T))
plt.axis('off')
plt.show()
#加载三角形索引和纹理
triangles = np.loadtxt('./Data/uv-data/triangles.txt').astype(np.int32)
all_colors = np.reshape(texture, [256*256, -1])
colors = all_colors[face_ind, :]
#计算三角形的深度和纹理
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)
plt.imshow(img_3D/255.0)
plt.show()

'''
函数左右
旋转坐标:使用旋转矩阵 trans_mat 将原始顶点 vertices 进行旋转,得到 rotated_vertices。
拉到画布上:计算旋转后顶点的平移量,将顶点平移至画布中心。
生成图像:创建空白的 img_3D 和 mask,用于绘制旋转后的三维纹理贴图。
循环每个三角形:遍历所有三角形 (triangles.shape[0]),并绘制其轮廓。根据轮廓面积选择填充颜色,使得图像具有最大填充面积。
保存和显示:保存旋转后的图像,并显示在屏幕上。'''
def trans_angle(p, trans_mat):
    # 旋转坐标
    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

    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)
    cv2.imwrite("img/"+str(p)+".jpg", img_3D)
    cv2.imwrite("img/"+str(60-p) + ".jpg", img_3D)
    plt.imshow(img_3D)
    plt.show()
#第一个 for 循环旋转角度从 0 到 29,每次增加 2*i 度。对每个角度调用 trans_angle 函数生成旋转后的图像。
for i in range(30):
    print(i)
    trans_mat = angle2matrix((0,2*i,0))
    trans_angle(i, trans_mat)
#第二个 for 循环旋转角度从 -30 到 -1,每次减小 2*i 度。对每个角度调用 trans_angle 函数生成旋转后的图像,并以 60-p 的格式保存。
for i in range(-30,0):
    i = -31 - i
    p = abs(i) + 60
    print(p)
    trans_mat = angle2matrix((0,2*i,0))
    trans_angle(p, trans_mat)
  • 人脸检测与裁剪:使用 Haar 特征级联分类器检测人脸,并裁剪出检测到的第一个人脸区域。
  • 图像仿射变换:将裁剪后的人脸图像进行仿射变换,调整到固定大小(256x256)的图像。
  • 人脸关键点预测:使用神经网络预测人脸关键点位置。
  • 三维重建
    • 将预测得到的关键点位置映射到三维空间。
    • 使用三角网格和颜色信息对人脸进行纹理贴图和渲染。
    • 最后生成旋转后不同角度的人脸图像。
相关推荐
唐·柯里昂7986 分钟前
[3D打印]拓竹切片软件Bambu Studio使用
经验分享·笔记·3d
摇曳的树4 小时前
【3D目标检测】激光雷达和相机联合标定(一)——ROS同步解包
数码相机·目标检测·3d
摩尔线程8 小时前
使用MTVerseXR SDK实现VR串流
3d·xr·图形渲染·vr·摩尔线程
GIS数据转换器1 天前
城市空间设计对居民生活质量的影响:构建宜居城市的蓝图
大数据·人工智能·3d·gis·生活·智慧城市
qq_15321452641 天前
【2022工业3D异常检测文献】AST: 基于归一化流的双射性产生不对称学生-教师异常检测方法
图像处理·深度学习·神经网络·机器学习·计算机视觉·3d·视觉检测
qq_15321452641 天前
【2023工业3D异常检测文献】CPMF: 基于手工制作PCD描述符和深度学习IAD结合的AD方法
图像处理·深度学习·神经网络·机器学习·计算机视觉·3d·视觉检测
qq_15321452643 天前
【2023工业3D异常检测文献】M3DM: 基于混合融合的多模态工业异常检测方法
图像处理·深度学习·神经网络·机器学习·计算机视觉·3d·视觉检测
小彭努力中3 天前
50. GLTF格式简介 (Web3D领域JPG)
前端·3d·webgl
小彭努力中4 天前
52. OrbitControls辅助设置相机参数
前端·3d·webgl
白葵新4 天前
PCL 移除点云边缘不连续的点
c++·算法·计算机视觉·3d