数字人解决方案——ER-NeRF实时对话数字人模型训练与项目部署

前言

1、算法概述

ER-NeRF是基于NeRF用于生成数字人的方法,可以达到实时生成的效果。具体来说,为了提高动态头部重建的准确性,ER-NeRF引入了一种紧凑且表达丰富的基于NeRF的三平面哈希表示法,通过三个平面哈希编码器剪枝空的空间区域。对于语音音频,ER-NeRF提出了一个区域关注模块,通过注意机制生成区域感知的条件特征。与现有方法不同,它们使用基于MLP的编码器隐式学习跨模态关系不同,注意机制建立了音频特征和空间区域之间的明确连接,以捕获本地动作的先验知识。此外,ER-NeRF引入了一种直接且快速的自适应姿势编码,通过将头部姿势的复杂变换映射到空间坐标,来优化头部和躯干的分离问题。大量实验证明,与先前方法相比,ER-NeRF的方法可以呈现更高保真度和音频嘴唇同步的数字人,细节更加逼真。

2.算法比较

在官方的实验可以看到,与之前的生成的数字人相比,ER-NeRF具有更好的高保真度和音频嘴唇同步的人像谈话视频,具有更真实的细节和更高的效率。

一、环境安装

1.环境配置

官方给的环境配置是Ubuntu 18.04, Pytorch 1.12 和CUDA 11.3,但我在win10下使用这个配置依赖,总是报了一堆莫名的错误,而且win10下在线安装pythorch3d并不容易成功,在安装过程中也报各种各样的错误。

经过几次安装测试,在win10下,最容易装上的配置依赖是: Pytorch 2.0,CUDA11.7(11.8也试过,但在训练的时候,一直卡着不动),cudnn 8.5,要本地安装pytorch3d,所以要安装Visual Studio,版本是2019或者2022都可以。Pytorch如果在官网不好下,这里我上传了一份源码到百度网盘,可以下载使用:链接:https://pan.baidu.com/s/1ZdJy8KHDBnwOOQDgWc0OPA 提取码:sxv7

安装vs要选以下这几个功能:

3.环境安装

bash 复制代码
#下载源码
git clone https://github.com/Fictionarry/ER-NeRF.git
cd ER-NeRF
#创建虚拟环境
conda create --name vrh python=3.10
activate vrh
#pytorch 要单独对应cuda进行安装,要不然训练时使用不了GPU
conda install pytorch==2.0.0 torchvision==0.15.0 torchaudio==2.0.0 pytorch-cuda=11.7 -c pytorch -c nvidia
conda install -c fvcore -c iopath -c conda-forge fvcore iopath
#安装所需要的依赖
pip install -r requirements.txt

#处理音频时用的
pip install tensorflow

下载pytorch3d源码,如果下载不了,按上面的百度网盘下载:链接:https://pan.baidu.com/s/178WbPBRtCl3hF8b3niCeSA 提取码:icd8

bash 复制代码
git clone https://github.com/facebookresearch/pytorch3d.git
cd pytorch3d
python setup.py install

在安装pytorch3d可能出现错误,可以看文章结尾列的常见错误。

3.项目模型下载

为了避免在运行时下不了模型或者模型下载缓慢,这里我把所需模型我这里都下载好放在网盘上,。链接:https://pan.baidu.com/s/1UteMZauIpn15nQrgRehh1g 提取码:klr7 ,如果不习惯网盘下载,也可以按下面的命令下载模型

下载人脸解析模型79999_iter.pth放到data_utils/face_parsing/这个目录

bash 复制代码
wget https://github.com/YudongGuo/AD-NeRF/blob/master/data_util/face_parsing/79999_iter.pth?raw=true -O data_utils/face_parsing/79999_iter.pth

下载头部姿态估计模型到data_utils/face_tracking/3DMM/

bash 复制代码
wget https://github.com/YudongGuo/AD-NeRF/blob/master/data_util/face_tracking/3DMM/exp_info.npy?raw=true -O data_utils/face_tracking/3DMM/exp_info.npy
wget https://github.com/YudongGuo/AD-NeRF/blob/master/data_util/face_tracking/3DMM/keys_info.npy?raw=true -O data_utils/face_tracking/3DMM/keys_info.npy
wget https://github.com/YudongGuo/AD-NeRF/blob/master/data_util/face_tracking/3DMM/sub_mesh.obj?raw=true -O data_utils/face_tracking/3DMM/sub_mesh.obj
wget https://github.com/YudongGuo/AD-NeRF/blob/master/data_util/face_tracking/3DMM/topology_info.npy?raw=true -O data_utils/face_tracking/3DMM/topology_info.npy

下载01_MorphableModel.mat模型到data_utils/face_trackong/3DMM/目录

bash 复制代码
https://faces.dmi.unibas.ch/bfm/main.php?nav=1-1-0&id=details

4.运行时所需模型下载

下载这四个模型放到到用户目录xxx.cache\torch\hub\checkpoints下,如果没有这个目录,自己创建出来,如果不先下载,运行再下载的话可能会很慢或者下载不了。

bash 复制代码
https://download.pytorch.org/models/resnet18-5c106cde.pth
https://www.adrianbulat.com/downloads/python-fan/s3fd-619a316812.pth
https://www.adrianbulat.com/downloads/python-fan/2DFAN4-cd938726ad.zip
https://download.pytorch.org/models/alexnet-owt-7be5be79.pth

语音特征提取模型

下载deepspeech-0_1_0-b90017e8.pb放到.tensorflow\models目录

二、数据处理

1.数据准备

自己拍摄一段不大于5分钟的视频或者从网上下载不侵权的视频,视频人像单一,面对镜头,背景尽量简单,这是方便等下进行抠人像与分割人脸用的。然后视频编辑软件,只切取一部分上半身和头部的画面。按1比1切取。这里的剪切尺寸不做要求,只是1比1就可以了。

导出的时候,按官方要求的尺寸导出(512*512),但不一定完全按这个尺寸来,就是只要是正方形就可以,帧率是25fps。

2.获得 AU45眨眼

要获取眨眼数据,要使用OpenFace,可以直接下载OpenFace的可运行文件,然后打开OpenFace目录下的OpenFaceOffline.exe,只选择AUs这个功能就可以。

这里我把OpenFace打好包放网盘上,可以直接下载使用:链接:https://pan.baidu.com/s/15yr0aSbAmSqhOhnjd7ATwg 提取码:hxgl

选择要获取眨眼数据的视频:

运行完之后,在processed目录下就有与视频名相同的csv文件:

3.数据处理

在ER-NERF/data目录,创建一个与视频名同名的目录:

数据处理要花的时间跟视频长短有关,一般要1个小时以上,有两种处理方式,一种是直接一次运行所有步骤,但处理过程可能存在错误,所以建议使用第二种,按步骤来处理.

1.一次性处理数据
bash 复制代码
按自己的数据与目录来运行对应的路径
python data_utils/process.py data/anc/anc.mp4
2.分步处理

按步骤处理时,

bash 复制代码
python data_utils/process.py data/anc/anc.mp4 --task x
--task 1  #分离音频

--task 2  #生成aud_eo.npy

--task 3  #把视频拆分成图像

--task 4  #分割人像

--task 5  #提取背景图像

--task 6 #分割出身体部分

--task 7 #获取人脸landmarks lms文件 

--task 8 #获取人脸跟踪数据,这步要训练一个追踪模型,会很慢

--task 9 #保存所有数据

4.数据说明

处理完成之后,把OpenFace处理出来的眨眼数据复制到当前目录,重新命名成au.csv,把原本的aud.npy重新命名成aud_ds.npy,如果不想改数据,就改在代码里面改。

5.人像分割问题

当分步处理数据时,到第四步是人像分割,这个分割如果没有分割好,就会影响训练的效果,比如下面的图像,第一个下巴分割的位置不对,第二个地方是把白色的衣服错误的分割成背景了,这里可以借助别人工具进行分割,我这里​​Segment-and-Track Anything进行分割,效果会好很多,关于​Segment-and-Track Anything可以看我之前的博客:​Segment-and-Track Anything------通用智能视频分割、跟踪、编辑算法解读与源码部署

上面那张图像使用Segment-and-Track Anything分割出来的效果:

Segment-and-Track Anything分割出来的图像,要转换成模型所需要的数据格式,下面是我用来转的C++代码,可以用参考改自己的数据:

cpp 复制代码
int main()
{

	for (int i = 0; i < 3418; i++)
	{
		std::string name = "";

		if (i < 10)
		{
			name = "tao6_masks/0000" + std::to_string(i) + ".png";
		}
		else if (i > 9 && i < 100)
		{
			name = "tao6_masks/000" + std::to_string(i) + ".png";
		}
		else if (i > 99 && i < 1000)
		{
			name = "tao6_masks/00" + std::to_string(i) + ".png";
		}
		else if (i > 999 && i < 10000)
		{
			name = "tao6_masks/0" + std::to_string(i) + ".png";
		}

		cv::Mat cv_src = cv::imread(name);

		cv::Mat cv_seg(cv_src.size(), CV_8UC3, cv::Scalar(255, 255, 255));
		cv::Mat cv_neck(cv_src.size(), CV_8UC3, cv::Scalar(0, 0, 0));
		cv::Mat cv_body = cv_neck.clone();
		cv::Mat cv_face = cv_neck.clone();

		for (int i = 0; i < cv_src.rows; i++)
		{
			for (int j = 0; j < cv_src.cols; j++)
			{
				if (cv_src.at<cv::Vec3b>(i, j)[2] == 140 &&
					cv_src.at<cv::Vec3b>(i, j)[1] == 238 &&
					cv_src.at<cv::Vec3b>(i, j)[0] == 157)
				{
					cv_neck.at<cv::Vec3b>(i, j)[2] = 0;
					cv_neck.at<cv::Vec3b>(i, j)[1] = 255;
					cv_neck.at<cv::Vec3b>(i, j)[0] = 0;
				}
			}
		}

		cv::Mat element_n = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(7, 7));
		morphologyEx(cv_neck, cv_neck, cv::MORPH_DILATE, element_n);   //结果保存到自身

		for (int i = 0; i < cv_neck.rows; i++)
		{
			for (int j = 0; j < cv_neck.cols; j++)
			{
				if (cv_neck.at<cv::Vec3b>(i, j)[2] == 0 &&
					cv_neck.at<cv::Vec3b>(i, j)[1] == 255 &&
					cv_neck.at<cv::Vec3b>(i, j)[0] == 0)
				{
					cv_seg.at<cv::Vec3b>(i, j)[2] = 0;
					cv_seg.at<cv::Vec3b>(i, j)[1] = 255;
					cv_seg.at<cv::Vec3b>(i, j)[0] = 0;
				}
			}
		}

		for (int i = 0; i < cv_src.rows; i++)
		{
			for (int j = 0; j < cv_src.cols; j++) {
				if (cv_src.at<cv::Vec3b>(i, j)[2] == 152 &&
					cv_src.at<cv::Vec3b>(i, j)[1] == 212 &&
					cv_src.at<cv::Vec3b>(i, j)[0] == 77)
				{
					cv_body.at<cv::Vec3b>(i, j)[2] = 255;
					cv_body.at<cv::Vec3b>(i, j)[1] = 0;
					cv_body.at<cv::Vec3b>(i, j)[0] = 0;
				}
			}
		}

		cv::Mat element_b = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(7, 7));
		cv::morphologyEx(cv_body, cv_body, cv::MORPH_DILATE, element_b);
		cv::morphologyEx(cv_body, cv_body, cv::MORPH_OPEN, element_b);

		for (int i = 0; i < cv_body.rows; i++)
		{
			for (int j = 0; j < cv_body.cols; j++) {
				if (cv_body.at<cv::Vec3b>(i, j)[2] == 255 &&
					cv_body.at<cv::Vec3b>(i, j)[1] == 0 &&
					cv_body.at<cv::Vec3b>(i, j)[0] == 0)
				{
					cv_seg.at<cv::Vec3b>(i, j)[2] = 255;
					cv_seg.at<cv::Vec3b>(i, j)[1] = 0;
					cv_seg.at<cv::Vec3b>(i, j)[0] = 0;
				}
			}
		}

		for (int i = 0; i < cv_src.rows; i++)
		{
			for (int j = 0; j < cv_src.cols; j++)
			{
				if (cv_src.at<cv::Vec3b>(i, j)[2] == 251 &&
					cv_src.at<cv::Vec3b>(i, j)[1] == 231 &&
					cv_src.at<cv::Vec3b>(i, j)[0] == 252)
				{
					cv_face.at<cv::Vec3b>(i, j)[2] = 0;
					cv_face.at<cv::Vec3b>(i, j)[1] = 0;
					cv_face.at<cv::Vec3b>(i, j)[0] = 255;
				}
			}
		}
		cv::Mat element_f = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(3, 3));
		cv::morphologyEx(cv_face, cv_face, cv::MORPH_DILATE, element_f);
		cv::morphologyEx(cv_face, cv_face, cv::MORPH_OPEN, element_b);

		for (int i = 0; i < cv_face.rows; i++)
		{
			for (int j = 0; j < cv_face.cols; j++)
			{
				if (cv_face.at<cv::Vec3b>(i, j)[2] == 0 &&
					cv_face.at<cv::Vec3b>(i, j)[1] == 0 &&
					cv_face.at<cv::Vec3b>(i, j)[0] == 255)
				{
					cv_seg.at<cv::Vec3b>(i, j)[2] = 0;
					cv_seg.at<cv::Vec3b>(i, j)[1] = 0;
					cv_seg.at<cv::Vec3b>(i, j)[0] = 255;
				}
			}
		}

		cv::imwrite("mask/" + std::to_string(i) + ".png", cv_seg);
		/*cv::imshow("src", cv_neck);
		cv::imshow("seg", cv_seg);
		cv::waitKey();*/
	}
}

三、模型训练

1.头部训练

bash 复制代码
python main.py data/vrh/ --workspace trial_vrh/ -O --iters 100000

这步训练完成之后,

2.微调嘴型动作

bash 复制代码
python main.py data/vrh/ --workspace trial_vrh/ -O --iters 125000 --finetune_lips --patch_size 32

运行完成之后,会保存最后的一个模型,这个模型下一步训练身体时用到:

3.训练身体

训练身体时,导入上一步生成的头部模型,模型路径和名称按项目按自己环境生成的结果来写:

bash 复制代码
python main.py data/vrh/ --workspace trial_vrh_torso/ -O --torso --head_ckpt trial_vrh/ngp_ep0041.pth --iters 200000

下面的模型就是我们最终需要的模型:

四、测试项目

1.测试

bash 复制代码
python main.py data/vrh/ --workspace trial_vrh/ -O --test 
python main.py data/vrh/ --workspace trial_vrh_torso/ -O --torso --test 

2.使用音频进行推理

这里要提取音频的特征才能进行推理,提取特征可以参考数据处理第二步:

bash 复制代码
python main.py data/vrh/ --workspace trial_vrh_torso/ -O --torso --test --test_train --aud <audio>.npy

五、常见错误

pytorch4d安装时常见错误

subprocess.CalledProcessError: Command '['ninja', '-v']' returned non-zero exit status 1.

出现这个错误时,找到dist-packages/torch/utils/cpp_extension.py这个文件,找到command = ['ninja', '-v'],改成 command = ['ninja', '--version'],改了效果如下:

bash 复制代码
   # command = ['ninja', '-v']
    command = ['ninja', '--version']

AttributeError: 'Upsample' object has no attribute 'recompute_scale_factor'

把cuda改成11.7就可以解决这个错误。

相关推荐
成富34 分钟前
文本转SQL(Text-to-SQL),场景介绍与 Spring AI 实现
数据库·人工智能·sql·spring·oracle
CSDN云计算1 小时前
如何以开源加速AI企业落地,红帽带来新解法
人工智能·开源·openshift·红帽·instructlab
艾派森1 小时前
大数据分析案例-基于随机森林算法的智能手机价格预测模型
人工智能·python·随机森林·机器学习·数据挖掘
hairenjing11231 小时前
在 Android 手机上从SD 卡恢复数据的 6 个有效应用程序
android·人工智能·windows·macos·智能手机
小蜗子1 小时前
Multi‐modal knowledge graph inference via media convergenceand logic rule
人工智能·知识图谱
SpikeKing1 小时前
LLM - 使用 LLaMA-Factory 微调大模型 环境配置与训练推理 教程 (1)
人工智能·llm·大语言模型·llama·环境配置·llamafactory·训练框架
黄焖鸡能干四碗2 小时前
信息化运维方案,实施方案,开发方案,信息中心安全运维资料(软件资料word)
大数据·人工智能·软件需求·设计规范·规格说明书
2 小时前
开源竞争-数据驱动成长-11/05-大专生的思考
人工智能·笔记·学习·算法·机器学习
ctrey_2 小时前
2024-11-4 学习人工智能的Day21 openCV(3)
人工智能·opencv·学习
攻城狮_Dream2 小时前
“探索未来医疗:生成式人工智能在医疗领域的革命性应用“
人工智能·设计·医疗·毕业