莱州相亲网站,在婚纱店做网站优化,网络软件开发专业是做什么的,太原网站建设方案优化前言
之前出过三篇换脸的博文#xff0c;遇到一个问题是表情那一块不好处理#xff0c;可行方法是直接基于2D人脸关键点做网格变形#xff0c;强行将表情矫正到目标人脸#xff0c;还有就是使用PRNet的思想#xff0c;使用目标人脸的顶点模型配合源人脸的纹理#xff0c…前言
之前出过三篇换脸的博文遇到一个问题是表情那一块不好处理可行方法是直接基于2D人脸关键点做网格变形强行将表情矫正到目标人脸还有就是使用PRNet的思想使用目标人脸的顶点模型配合源人脸的纹理可以让表情迁移过来但是这个表情是很僵硬的。比如笑脸的3D顶点模型结合不笑人脸的纹理图生成的笑脸是非常奇怪的。有兴趣可以翻csdn前面的文章或者关注公众号检索人脸相关文章。
这里针对表情采用另一种方案——blendshape。这个理论在表情动画中经常使用到目的就是驱动人脸表情无论是动画人脸还是真人的人脸只要你这个人脸具有对应的顶点模型、纹理还有很多标准的blendshape模型分别对应不同的表情。
我们这里采用eos库实现表情变换一来是很多blendshape数据集获取难度比较大二来是这个库还是蛮好用的有C/python/matlab的接口而且与之前研究的PRNet有很多相似的的。
国际惯例参考博客
基于PRNet的3D人脸重建与替换
eos官方文档
eos源码
eos作者提供的model的可视化工具包括blendshape控制
算法流程
分为四步
人脸关键点提取3D人脸拟合表情驱动渲染
注意一般来说人脸重建是基于人脸关键点不断去调整3D标准人脸的使其变换到目标人脸的关键点形状这一点可以去知乎上看看3DMM人脸重建相关文章拟合过程一般涉及到两类参数形状、表情
预备
先安装一些必备的环境直接用pip安装eos-py、opencv-python、opencv-contrib-python
导入必要的库
import eos
import numpy as np
import cv2
from matplotlib import pyplot as plt然后把eos的源码下载保存在一个文件夹中我们写的代码都在eos源码文件夹并列的代码中写不要进到eos文件夹里面写代码面得污染了环境。
人脸关键点提取
之前人脸替换系列的博客都用的opencv人脸关键点检测方法这里也就不再说了直接贴代码
#初始化检测器
cas cv2.CascadeClassifier(./facemodel/haarcascade_frontalface_alt2.xml)
obj cv2.face.createFacemarkLBF()
obj.loadModel(./facemodel/lbfmodel.yaml)# 检测人脸关键点
def detect_facepoint(img):img_gray cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)faces cas.detectMultiScale(img_gray,2,3,0,(30,30))landmarks obj.fit(img_gray,faces)assert landmarks[0],no face detectedif(len(landmarks[1])1):print(multi face detected,use the first)return faces[0],np.squeeze(landmarks[1][0])#可视化图片
def vis_img(img):plt.imshow(cv2.cvtColor(img.copy(),cv2.COLOR_BGR2RGB))测试看看
img_file ./images/zly.jpg
img cv2.imread(img_file)
face_box,coords detect_facepoint(img)# 转换为eos库所需要的关键点输入格式
landmarks []
ibug_index 1 # count from 1 to 68 for all ibug landmarks
for l in range(coords.shape[0]):landmarks.append(eos.core.Landmark(str(ibug_index), [float(coords[ibug_index-1][0]), float(coords[ibug_index-1][1])]))ibug_index ibug_index 1#可视化关键点
img_show img.copy()
for kps in landmarks:face_kps kps.coordinatescv2.circle(img_show,(face_kps[0],face_kps[1]),5,(0,255,0),-1)
vis_img(img_show)
plt.axis(off)使用eos 重建人脸
因为是要用标准人脸和blendshape去拟合图片人脸关键点所以需要先初始化一堆内容这个是固定套路
# 初始化eos
model eos.morphablemodel.load_model(./eos/share/sfm_shape_3448.bin)
blendshapes eos.morphablemodel.load_blendshapes(./eos/share/expression_blendshapes_3448.bin)
# Create a MorphableModel with expressions from the loaded neutral model and blendshapes:
morphablemodel_with_expressions eos.morphablemodel.MorphableModel(model.get_shape_model(), blendshapes,color_modeleos.morphablemodel.PcaModel(), vertex_definitionsNone, texture_coordinatesmodel.get_texture_coordinates())
landmark_mapper eos.core.LandmarkMapper(./eos/share/ibug_to_sfm.txt)
edge_topology eos.morphablemodel.load_edge_topology(./eos/share/sfm_3448_edge_topology.json)
contour_landmarks eos.fitting.ContourLandmarks.load(./eos/share/ibug_to_sfm.txt)
model_contour eos.fitting.ModelContour.load(./eos/share/sfm_model_contours.json)这个操作不用管只要使用这个eos库重建人脸只需把这一串代码复制下来用就行了把模型路径改改就行这些路径对应文件都在官方源码上有。
稍微解释一下作者为什么不把这一串操作封到一个对象里面我们使用的时候直接一句话初始一个对象就行了原因在于这个库是可以支持三种人脸模型(Surrey Face Model (SFM), 4D Face Model (4DFM), Basel Face Model (BFM))而且对应的blendshape表情数也可以增加还有很多其他的映射关系表也根据不同的模型而变化所以还不如全部暴露出来用户自行设置修改。
【注】上述初始化使用的人脸模型是SFM作者提供的这个模型对应的blendshapes只有六种表情anger, disgust, fear, happiness, sadness, surprise所以重建或者驱动效果其实不是特别理想但是能看出来有驱动。如果读者其它两种模型比如4DFM的人脸模型精细度(网格数目)就比较高而且多达36种表情就建议使用高精度模型尝试一波
初始化完毕就可以针对关键点进行拟合
# 重建人脸
(mesh, pose, shape_coeffs, blendshape_coeffs) eos.fitting.fit_shape_and_pose(morphablemodel_with_expressions,landmarks, landmark_mapper, image_width, image_height, edge_topology, contour_landmarks, model_contour)注意上面的返回值shape_coeffs和blenshape_coeffs就是3DMM人脸重建中经常说的形状系数和表情系数了前者拟合脸型后者拟合表情。待会表情驱动就是利用表情系数来做的。
如果还记得之前写的PRNet人脸重建文章里面有几个信息比较重要3D顶点、人脸纹理图、网格顶点索引在eos库中可以直接通过下面这句话获取纹理信息
# 提取纹理
isomap eos.render.extract_texture(mesh,pose,img).swapaxes(0,1)因为后面使用meshlab打开重建的人脸需要这个纹理文件所以提前保存一下顺便可视化一波
cv2.imwrite(result.isomap.png,isomap)
vis_img(isomap)接下来就是需要根据得到的形状参数和表情系数将标准人脸变换成咱赵丽颖的人脸这里需要注意在issuee 35有人提到过C中使用这句话
auto merged_shape morphable_model.get_shape_model().draw_sample(fitted_coeffs) to_matrix(blendshapes) * Mat(blendshape_coefficients);但是在python中并未提供表情系数乘法对应的函数那么直接写一个
def blendshape_add(bss,bc):bs_array []for bs in bss:bs_array.append(bs.deformation)bs_array np.array(bs_array).transpose()bc np.array(bc)return np.dot(bs_array,bc)再仿照C代码写重建方法
merge_shape morphablemodel_with_expressions.get_shape_model().draw_sample(shape_coeffs) blendshape_add(blendshapes,blendshape_coeffs);表情驱动
如果要驱动表情那么仅仅改改表情系数就可以了比如
# 改变表情 anger, disgust, fear, happiness, sadness, surprise
blendshape_coeffs [0,1,0,0,0,0]这只是获取了形状我们最终需要渲染的是mesh所以还需要做一个转换记录一下颜色信息顶点信息什么的
merged_mesh eos.morphablemodel.sample_to_mesh(merge_shape,morphablemodel_with_expressions.get_color_model().get_mean(),morphablemodel_with_expressions.get_shape_model().get_triangle_list(),morphablemodel_with_expressions.get_color_model().get_triangle_list(),morphablemodel_with_expressions.get_texture_coordinates());渲染
接下来介绍两种可视化方法 使用meshlab可视化因为上面我们保存过纹理文件所以这里只需要把mesh保存一下 outputfile result.obj
eos.core.write_textured_obj(merged_mesh,outputfile);这时候我们就有了result.obj、result.mtl、result.isomap.png三个文件直接双击obj用meshlab打开 使用代码可视化按照之前学习PRNet中得到的知识需要分别获取到人脸模型的3D顶点坐标每个人脸网格顶点索引每个顶点的颜色信息这些在eos求取的mesh中都有分别取出来 triangles np.array(merged_mesh.tvi) # 人脸网格对应的顶点索引
# 人脸顶点
vertices []
for v in merged_mesh.vertices:vertices.append(np.array([v[0],-v[1],v[2]]))
vertices np.array(vertices)
vertices vertices-np.min(vertices)
# 纹理坐标
texcoords []
for tc in merged_mesh.texcoords:texcoords.append(tc)
texcoords np.array(texcoords)
# 根据纹理坐标获取每个顶点的颜色
colors []
for i in range(texcoords.shape[0]):colors.append(isomap[int(texcoords[i][1]*(isomap.shape[0]-1)),int(texcoords[i][0]*(isomap.shape[1]-1)),0:3])
colors np.array(colors,np.float32)然后利用顶点的颜色求平均得到网格的颜色 #获取三角形每个顶点的color平均值作为三角形颜色
tri_tex (colors[triangles[:,0] ,:] colors[triangles[:,1],:] colors[triangles[:,2],:])/3.对每个网格上色 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,(int(tri_tex[i][0]), int(tri_tex[i][1]), int(tri_tex[i][2])),-1)可视化 plt.figure(figsize(8,8))
vis_img(img_3D)有人奇怪我上传的图片讲道理没张嘴啊为什么张嘴了因为我们驱动了表情 # 改变表情 anger, disgust, fear, happiness, sadness, surprise
blendshape_coeffs [0,1,0,0,0,0]所以现在看起来是disgust这个表情为啥看起来不自然当然是因为丽颖的照片本来就在笑导致默认纹理在笑然后再加上blendshape比较粗糙看起来就有点怪怪的。
生成表情驱动的gif
通过上面的一系列操作我们可以基于eos自带的6种表情blendshape改变大颖妹子的面部表情那么来搞个gif玩玩思路就是对blendshape_coeffs做一个线性过渡即可
buff []
frame_num 20
for i in range(frame_num): #一个gif 10帧# 改变表情 anger, disgust, fear, happiness, sadness, surpriseblendshape_coeffs [1.0 - i/frame_num,0,0,0,0,i/frame_num]merge_shape morphablemodel_with_expressions.get_shape_model().draw_sample(shape_coeffs) blendshape_add(blendshapes,blendshape_coeffs);merged_mesh eos.morphablemodel.sample_to_mesh(merge_shape,morphablemodel_with_expressions.get_color_model().get_mean(),morphablemodel_with_expressions.get_shape_model().get_triangle_list(),morphablemodel_with_expressions.get_color_model().get_triangle_list(),morphablemodel_with_expressions.get_texture_coordinates());triangles np.array(merged_mesh.tvi) # 人脸网格对应的顶点索引# 人脸顶点vertices []for v in merged_mesh.vertices:vertices.append(np.array([v[0],-v[1],v[2]]))vertices np.array(vertices)vertices vertices-np.min(vertices)# 纹理坐标texcoords []for tc in merged_mesh.texcoords:texcoords.append(tc)texcoords np.array(texcoords)# 根据纹理坐标获取每个顶点的颜色colors []for i in range(texcoords.shape[0]):colors.append(isomap[int(texcoords[i][1]*(isomap.shape[0]-1)),int(texcoords[i][0]*(isomap.shape[1]-1)),0:3])colors np.array(colors,np.float32)#获取三角形每个顶点的color平均值作为三角形颜色tri_tex (colors[triangles[:,0] ,:] colors[triangles[:,1],:] colors[triangles[:,2],:])/3.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,(int(tri_tex[i][0]), int(tri_tex[i][1]), int(tri_tex[i][2])),-1)buff.append(cv2.cvtColor(img_3D,cv2.COLOR_BGR2RGB))
gifimageio.mimsave(expression.gif,buff,GIF,duration0.1)从愤怒到惊讶的效果图 后记
当前只是针对之前人脸替换的表情问题按照blendshape驱动的方法做了一个实验至于还有其它问题比如为啥这个人脸上有黑洞洞啊、怎么把人脸拼接到原图上这个后续有机会再去折腾了这个eos库的python接口文档不是特别详细而且没C那么完善。建议真有兴趣的老铁多看看issues里面有很多有趣的问题。
代码上面都放出来了如果想要我实验的代码直接关注微信公众号在公众号简介中的github中获取同时本博文也同步更新到微信公众号中