一文了解DINet模型结构和运行原理

本文主要介绍DINet网络,来源于2023年AAAI顶会的文章。

DINet网络主要实现高分辨率图像的面部配音,包含形变和修复两个部分。以前的网络主要通过特征数据生成嘴部像素,作者认为在高分辨率图像下,通过少量的语音特征数据生成的嘴部像素会相对模糊,因此作者提出首先利用形变网络部分分别编码原人脸头部姿态特征和驱动音频的语音内容,然后利用这些特征去将参考人脸进行形变操作,最后修复网络部分通过卷积层合并原始人脸特征与形变结果,去修复原始嘴巴区域的像素。其网络结构如下图(图片来自网上):

对于三个输入,

1. 原图,实际上是截取面部的一张图片,图片大小取决于算法训练时的分辨率,比如嘴部分辨率为256x256时,面部大小为416x320,计算如下:

resize_w = int(opt.mouth_region_size + opt.mouth_region_size // 4)
resize_h = int((opt.mouth_region_size // 2) * 3 + opt.mouth_region_size // 8)

实际工程中这个原图一般是一段视频的其中一帧,通过脸部处理工具OpenFace的帮助,可以得到这一帧图像的脸部截图,接着resize到(320,416),然后归一化处理,最后是将嘴部区域像素点值置为0并在送入模型之前将WHC格式转换为NCHW,代码如下:

crop_frame_data = cv2.resize(crop_frame_data, (resize_w,resize_h)) crop_frame_data = crop_frame_data / 255.0
crop_frame_data[opt.mouth_region_size//2:opt.mouth_region_size//2 + opt.mouth_region_size,
                opt.mouth_region_size//8:opt.mouth_region_size//8 + opt.mouth_region_size, :] = 0

crop_frame_tensor = torch.from_numpy(crop_frame_data).float().permute(2, 0, 1).unsqueeze(0)

当然模型最后的输出也需要做逆动作逐步还原为一张图片。

这里补充说明下OpenFace工具,它可以将一段视频逐帧识别脸部关键点坐标68个并输出为csv文件,文件中的每一行为一帧的数据。

打开OpenFaceOffline.exe配置一下(注意勾选的Record),就可以使用了

在windows上安装比较麻烦,还需要下载模型,比较耗时,这里有完整的可运行软件包供下载使用:

扫码支付-八图片

2. 驱动语音,实际送入模型的数据是经过很多处理的,并不是简单的语音采样数据

具体地,驱动音频文件要求采样率为16000,如果不是则会对原始音频的单通道采样数据进行重新采样,如下:

import resampy
from scipy.io import wavfile

audio_sample_rate, audio = wavfile.read(audio_path)
if audio.ndim != 1:
    audio = audio[:, 0]

if audio_sample_rate != self.target_sample_rate:
    resampled_audio = resampy.resample(
        x=audio.astype(np.float32),
        sr_orig=audio_sample_rate,
        sr_new=self.target_sample_rate)
else:
    resampled_audio = audio.astype(np.float32)

接着,音频采样数据经过MFCC进行特征提取。MFCC(Mel-scale Frequency Cepstral Coefficients)是一种音频信号处理中常用的特征提取方法,其原理大致如下:

  1. 预处理:将原始采样数据划分为短时帧,一般是20-40毫秒
  2. 傅里叶变换:对每帧数据进行快速傅里叶变换(一种积分变换,这里不详述),可以将时域信号转换为频域信号
  3. 梅尔滤波器组:由于人类对不同的声音频率感知不同,梅尔滤波器将上面的频谱信号转换为”梅尔频谱”,根据符合人类感知能力
  4. 对数运算:增强低频部分特征,减弱高频部分特征,使得幅度变化较大的信号更加平顺一些
  5. 离散余弦变换:得到一组倒谱系数,用于提取信息的频率特征,算法一般会指定选取的倒谱系数个数

代码如下,指定的num_cepstrum为倒频谱系数个数,传入值为26:

from python_speech_features import mfcc

features = mfcc(
    signal=audio,
    samplerate=sample_rate,
    numcep=num_cepstrum)

features = features[::2]

实际9.68秒的音频文件,采样率16000,样本数量一共154964,经过mfcc后输出shape为(968,26),说明mfcc的短时帧只有10ms,然后取样一半最后为(484,26)

然后,音频的频域特征数据会经过DeepSpeech网络,该网络主要由卷积和LSTM网络组成,能够进一步捕捉音频样本的时序信息,输入数据首先从(484,26)扩展为(484, 19, 26)然后减维到(484, 494)送入DeepSpeech网络,输出取了模型中间节点(‘logits:0’)的输出,输出shape为(484,1 ,29),代码如下:

network_output = sess.run(
        self.logits_ph,
        feed_dict={
            self.input_node_ph: input_vector[np.newaxis, ...],
            self.input_lengths_ph: [input_vector.shape[0]]})
ds_features = network_output[::2,0,:]

得到最后的音频特征数据,其shape为(242, 29),可以看到音频特征数据的帧个数从前面的968经过了两次折半取样后为242,相当于每个帧对应40ms,正好和25fps采样率的视频对应。

注意DINet在github的源码:https://github.com/MRzzm/DINet

并没有训练好的DeepSpeech权重文件,下方工程文件地址中可以直接获取。

实际推理过程中,一般是输入一段视频+驱动音频,视频拆帧后为一系列图片,驱动音频经过上面的处理后和视频帧的时序对齐,窗口大小为5帧,窗口每次向前前进一帧,首先计算窗口内中间帧的脸部切取数据作为DINet的一个输入,然后计算窗口内5帧的音频数据作为DINet的另一个输入,最后一个输入5张ref脸部切取数据并不需要和驱动语音的5帧数据时序对齐,而是随机在整个视频中选择了5帧。

3)5帧参考人脸图像,这个没什么需要说的,前面都说了。

讲完了模型的输入和输出,其实也说清楚了模型训练或者推理的前处理过程。

接下来看下DINet的形变网络部分,

如上图所言,人脸图像特征(嘴部被掩盖)Fs和参考人脸图像特征Fref合并后一起输入对齐编码器中,计算出对齐特征Falign,然后对齐特征和语音特征Faudio合并后输入一个全连接网络,计算出三个非常重要的空间形变系数R,T,S,分别对应旋转,平移,缩放形变操作,形变公式如下,这个推导比较简单,只是扩展了下坐标旋转的计算方式:

注意形变参数是作用在参考人脸特征上的,得到形变特征图Fd

网络修复部分:

人脸图像特征(嘴部被掩盖)Fs和形变特征图Fd沿着C维度合并到一起,和常规解码器一样,做一些卷积和上采样操作后输出最终图片。

前面讲到推理过程是逐帧进行的,最终生成的图片只是脸部剪切数据,还需要利用原图和OpenFace的数据还原成为一张完整图片,并作为一帧写入视频流。帧采样率设置为25,得到完整视频后再合并音频。需要注意的是,推理的帧数是驱动语音的帧数,如果输入视频的帧数较少,则用于推理的视频帧是拆帧数据的反复重复构成,反之则只使用一部分拆帧数据用于推理。这才是真正意义上的语音驱动生成!

对于训练流程,首先我们需要知道,训练的数据来源是n段视频,视频的图像和语音会进行分离,A视频的语音可以作为B视频图像的驱动语音,所以可以很容易得到一段驱动语音对应的真实口型。DINet网络的损失函数计算比较复杂,将在后面的文章中再仔细分析,这里不深入探讨。

附:完整可运行工程代码,修改支持在cpu上运行,直接运行inference.py即可:

扫码支付-八图片

补充一些代码中常见的函数说明:

torch tensor常用函数:

unsqueeze:扩展维度,如unsqueeze(0)

permute:将tensor的维度变换

repeat: 用于将tensor在各个维度上重复,因此参数个数必须和维度相同

view:重构张量的维度,不影响数据的多少

python中list操作a[::-1]表示将列表a倒序,类似的操作有:

np.flip(x,0)表示将x的0维度数据翻转

np.stack([x, y], 2) 表示将相同维度的数据在某个维度上叠加

比如 x为二维数组[[1,2,3,4]] y为二维数组[[5,6,7,8]]

则np.stack([x, y], 2) = [[[1,5],[2,6],[3,7],[4,8]]]

即 1*4*1 1*4*1 -> 1*4*2

ffmpeg的基本参数:

-i:输入文件

-c:v 视频编码器

-c:a 音频编码器

-map 合并

-map 0:v:0 -map 1:a:0 表示合并第一个文件的第一个视频流与第二个文件的第一个音频流

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

AI印象

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值