唇形同步视频生成工具:Wav2Lip

一、模型介绍 

       今天介绍一个唇形同步的工具-Wav2Lip;Wav2Lip是一种用于生成唇形同步(lip-sync)视频的深度学习算法,它能够根据输入的音频流自动为给定的人脸视频添加准确的口型动作。

(Paper)

       Wav2Lip模型是基于生成对抗网络(GAN)构建的,它包含生成器和判别器两个主要部分。生成器负责根据输入的音频波形生成逼真的面部动画,而判别器则负责区分生成的动画与真实的面部动画 ;

其主要结构和工作原理的详细描述如下:

  1. 判别器(D_{SyncNet}):第一阶段是训练一个能够判别声音与嘴型是否同步的判别器。这个判别器的目标是提高对声音与嘴型同步性的判断能力。

  2. 生成器(编码-解码模型结构):第二阶段采用编码-解码模型结构,包括一个生成器和两个判别器。生成器尝试生成与音频同步的面部动画,而两个判别器分别负责判断生成的动画与真实动画的同步性和视觉质量。

  3. 主要模块:Wav2Lip模型包括三个主要模块:

    • Identity Encoder(身份编码器):负责对随机参考帧进行编码,以提取身份特征。
    • Speech Encoder(语音编码器):将输入语音段编码为面部动画特征。
    • Face Decoder(人脸解码器):将编码后的特征进行上采样,最终生成面部动画。

二、本地部署

       下面我们就在本地或者魔塔平台上部署一下这个模型,这里我选择在魔塔上部署该项目:

2.1 创建conda虚拟环境

       根据github上的README,我们在硬件上需要有Nvidia的显卡,同时需要在python=3.6的环境下运行,之前博文有详细介绍如何在魔塔上安装miniconda以及创建虚拟环境,这里就不再赘述了,这里我们就创建一个名为wav2lip的虚拟环境;

2.2 安装依赖环境

git clone https://github.com/Rudrabha/Wav2Lip.gitcd Wav2Lip

注:需要注意的一点是,在安装依赖环境之前,将requirements.txt文件中的

opencv-contrib-python>=4.2.0.34改为opencv-contrib-python==4.2.0.34

# 安装依赖环境
pip install -r requirements.txt
# 下载模型权重
git clone https://www.modelscope.cn/GYMaster/Wav2lip.git

2.3 运行

python inference.py --checkpoint_path <ckpt> --face <video.mp4> --audio <an-audio-source> 

其中:

--checkpoint_path 是上面下载的模型权重的路径

--face 是需要同步口型的视频文件路径

--audio 是对应的音频文件路径

需要注意一下几点:

1、音频文件的时长不应超过视频文件的时长;

2、视频文件中必须保证每一帧画面都有清晰的人脸;

2.4 Web-UI

       webUI实现是基于Gradio,测试发现python3.6版本对该库的兼容性不好,所以,如果要做界面部署的话,建议在python=3.7的环境进行项目依赖库的安装,这里给出实现UI调用的脚步inference_ui.py;

# inference_ui.pyfrom os import listdir, path
import numpy as np
import scipy, cv2, os, sys, argparse, audio
import json, subprocess, random, string
from tqdm import tqdm
from glob import glob
import torch, face_detection
from models import Wav2Lip
import platform
import gradio as grparser = argparse.ArgumentParser(description='Inference code to lip-sync videos in the wild using Wav2Lip models')parser.add_argument('--checkpoint_path', type=str, help='Name of saved checkpoint to load weights from', default=None)parser.add_argument('--face', type=str, help='Filepath of video/image that contains faces to use', default=None)
parser.add_argument('--audio', type=str, help='Filepath of video/audio file to use as raw audio source', default=None)
parser.add_argument('--outfile', type=str, help='Video path to save result. See default for an e.g.', default='results/result_voice.mp4')parser.add_argument('--static', type=bool, help='If True, then use only first video frame for inference', default=False)
parser.add_argument('--fps', type=float, help='Can be specified only if input is a static image (default: 25)', default=25., required=False)parser.add_argument('--pads', nargs='+', type=int, default=[0, 10, 0, 0], help='Padding (top, bottom, left, right). Please adjust to include chin at least')parser.add_argument('--face_det_batch_size', type=int, help='Batch size for face detection', default=16)
parser.add_argument('--wav2lip_batch_size', type=int, help='Batch size for Wav2Lip model(s)', default=128)parser.add_argument('--resize_factor', default=1, type=int, help='Reduce the resolution by this factor. Sometimes, best results are obtained at 480p or 720p')parser.add_argument('--crop', nargs='+', type=int, default=[0, -1, 0, -1], help='Crop video to a smaller region (top, bottom, left, right). Applied after resize_factor and rotate arg. ' 'Useful if multiple face present. -1 implies the value will be auto-inferred based on height, width')parser.add_argument('--box', nargs='+', type=int, default=[-1, -1, -1, -1], help='Specify a constant bounding box for the face. Use only as a last resort if the face is not detected.''Also, might work only if the face is not moving around much. Syntax: (top, bottom, left, right).')parser.add_argument('--rotate', default=False, action='store_true',help='Sometimes videos taken from a phone can be flipped 90deg. If true, will flip video right by 90deg.''Use if you get a flipped result, despite feeding a normal looking video')parser.add_argument('--nosmooth', default=False, action='store_true',help='Prevent smoothing face detections over a short temporal window')args = parser.parse_args()
args.img_size = 96def get_smoothened_boxes(boxes, T):for i in range(len(boxes)):if i + T > len(boxes):window = boxes[len(boxes) - T:]else:window = boxes[i : i + T]boxes[i] = np.mean(window, axis=0)return boxesdef face_detect(images):detector = face_detection.FaceAlignment(face_detection.LandmarksType._2D, flip_input=False, device=device)batch_size = args.face_det_batch_sizewhile 1:predictions = []try:for i in tqdm(range(0, len(images), batch_size)):predictions.extend(detector.get_detections_for_batch(np.array(images[i:i + batch_size])))except RuntimeError:if batch_size == 1: raise RuntimeError('Image too big to run face detection on GPU. Please use the --resize_factor argument')batch_size //= 2print('Recovering from OOM error; New batch size: {}'.format(batch_size))continuebreakresults = []pady1, pady2, padx1, padx2 = args.padsfor rect, image in zip(predictions, images):if rect is None:cv2.imwrite('temp/faulty_frame.jpg', image) # check this frame where the face was not detected.raise ValueError('Face not detected! Ensure the video contains a face in all the frames.')y1 = max(0, rect[1] - pady1)y2 = min(image.shape[0], rect[3] + pady2)x1 = max(0, rect[0] - padx1)x2 = min(image.shape[1], rect[2] + padx2)results.append([x1, y1, x2, y2])boxes = np.array(results)if not args.nosmooth: boxes = get_smoothened_boxes(boxes, T=5)results = [[image[y1: y2, x1:x2], (y1, y2, x1, x2)] for image, (x1, y1, x2, y2) in zip(images, boxes)]del detectorreturn results def datagen(frames, mels):img_batch, mel_batch, frame_batch, coords_batch = [], [], [], []if args.box[0] == -1:if not args.static:face_det_results = face_detect(frames) # BGR2RGB for CNN face detectionelse:face_det_results = face_detect([frames[0]])else:print('Using the specified bounding box instead of face detection...')y1, y2, x1, x2 = args.boxface_det_results = [[f[y1: y2, x1:x2], (y1, y2, x1, x2)] for f in frames]for i, m in enumerate(mels):idx = 0 if args.static else i%len(frames)frame_to_save = frames[idx].copy()face, coords = face_det_results[idx].copy()face = cv2.resize(face, (args.img_size, args.img_size))img_batch.append(face)mel_batch.append(m)frame_batch.append(frame_to_save)coords_batch.append(coords)if len(img_batch) >= args.wav2lip_batch_size:img_batch, mel_batch = np.asarray(img_batch), np.asarray(mel_batch)img_masked = img_batch.copy()img_masked[:, args.img_size//2:] = 0img_batch = np.concatenate((img_masked, img_batch), axis=3) / 255.mel_batch = np.reshape(mel_batch, [len(mel_batch), mel_batch.shape[1], mel_batch.shape[2], 1])yield img_batch, mel_batch, frame_batch, coords_batchimg_batch, mel_batch, frame_batch, coords_batch = [], [], [], []if len(img_batch) > 0:img_batch, mel_batch = np.asarray(img_batch), np.asarray(mel_batch)img_masked = img_batch.copy()img_masked[:, args.img_size//2:] = 0img_batch = np.concatenate((img_masked, img_batch), axis=3) / 255.mel_batch = np.reshape(mel_batch, [len(mel_batch), mel_batch.shape[1], mel_batch.shape[2], 1])yield img_batch, mel_batch, frame_batch, coords_batchmel_step_size = 16
device = 'cuda' if torch.cuda.is_available() else 'cpu'
print('Using {} for inference.'.format(device))def _load(checkpoint_path):if device == 'cuda':checkpoint = torch.load(checkpoint_path)else:checkpoint = torch.load(checkpoint_path,map_location=lambda storage, loc: storage)return checkpointdef load_model(path):model = Wav2Lip()print("Load checkpoint from: {}".format(path))checkpoint = _load(path)s = checkpoint["state_dict"]new_s = {}for k, v in s.items():new_s[k.replace('module.', '')] = vmodel.load_state_dict(new_s)model = model.to(device)return model.eval()def main():if not os.path.isfile(args.face):raise ValueError('--face argument must be a valid path to video/image file')elif args.face.split('.')[1] in ['jpg', 'png', 'jpeg']:full_frames = [cv2.imread(args.face)]fps = args.fpselse:video_stream = cv2.VideoCapture(args.face)fps = video_stream.get(cv2.CAP_PROP_FPS)print('Reading video frames...')full_frames = []while 1:still_reading, frame = video_stream.read()if not still_reading:video_stream.release()breakif args.resize_factor > 1:frame = cv2.resize(frame, (frame.shape[1]//args.resize_factor, frame.shape[0]//args.resize_factor))if args.rotate:frame = cv2.rotate(frame, cv2.cv2.ROTATE_90_CLOCKWISE)y1, y2, x1, x2 = args.cropif x2 == -1: x2 = frame.shape[1]if y2 == -1: y2 = frame.shape[0]frame = frame[y1:y2, x1:x2]full_frames.append(frame)print ("Number of frames available for inference: "+str(len(full_frames)))if not args.audio.endswith('.wav'):print('Extracting raw audio...')command = 'ffmpeg -y -i {} -strict -2 {}'.format(args.audio, 'temp/temp.wav')subprocess.call(command, shell=True)args.audio = 'temp/temp.wav'wav = audio.load_wav(args.audio, 16000)mel = audio.melspectrogram(wav)print(mel.shape)if np.isnan(mel.reshape(-1)).sum() > 0:raise ValueError('Mel contains nan! Using a TTS voice? Add a small epsilon noise to the wav file and try again')mel_chunks = []mel_idx_multiplier = 80./fps i = 0while 1:start_idx = int(i * mel_idx_multiplier)if start_idx + mel_step_size > len(mel[0]):mel_chunks.append(mel[:, len(mel[0]) - mel_step_size:])breakmel_chunks.append(mel[:, start_idx : start_idx + mel_step_size])i += 1print("Length of mel chunks: {}".format(len(mel_chunks)))full_frames = full_frames[:len(mel_chunks)]batch_size = args.wav2lip_batch_sizegen = datagen(full_frames.copy(), mel_chunks)for i, (img_batch, mel_batch, frames, coords) in enumerate(tqdm(gen, total=int(np.ceil(float(len(mel_chunks))/batch_size)))):if i == 0:model = load_model(args.checkpoint_path)print ("Model loaded")frame_h, frame_w = full_frames[0].shape[:-1]out = cv2.VideoWriter('temp/result.avi', cv2.VideoWriter_fourcc(*'DIVX'), fps, (frame_w, frame_h))img_batch = torch.FloatTensor(np.transpose(img_batch, (0, 3, 1, 2))).to(device)mel_batch = torch.FloatTensor(np.transpose(mel_batch, (0, 3, 1, 2))).to(device)with torch.no_grad():pred = model(mel_batch, img_batch)pred = pred.cpu().numpy().transpose(0, 2, 3, 1) * 255.for p, f, c in zip(pred, frames, coords):y1, y2, x1, x2 = cp = cv2.resize(p.astype(np.uint8), (x2 - x1, y2 - y1))f[y1:y2, x1:x2] = pout.write(f)out.release()command = 'ffmpeg -y -i {} -i {} -strict -2 -q:v 1 {}'.format(args.audio, 'temp/result.avi', args.outfile)subprocess.call(command, shell=platform.system() != 'Windows')#========================================================================
# 假设我们有一个函数来处理视频和音频,以及选择的模型,并返回处理后的视频
# def process_video_audio(video, audio, model_name):
# 	args.checkpoint_path = './Wav2lip/wav2lip.pth'
# 	args.face = video
# 	args.audio = audio
#     processed_video_path = './result/video.mp4'
#     return processed_video_pathdef process_video_audio(video, audio, model_name):args.checkpoint_path = './Wav2lip/wav2lip.pth'args.face = videoargs.audio = audioif os.path.isfile(args.face) and args.face.split('.')[1] in ['jpg', 'png', 'jpeg']:args.static = Trueprocessed_video_path = './results/result_voice.mp4'return processed_video_path# 定义可用的模型选项
model_choices = ["Model A", "Model B", "Model C"]# 创建Gradio界面
with gr.Blocks(theme="glass") as demo:gr.Markdown("## 视频与音频处理服务")with gr.Row():video_input = gr.Video(label="上传视频文件", type="filepath")audio_input = gr.Audio(label="上传音频文件", type="filepath")model_choice = gr.Dropdown(choices=model_choices, label="选择模型", value=model_choices[0])submit_btn = gr.Button("提交")output_video = gr.Video(label="处理后的视频")# 当点击提交按钮时,调用process_video_audio函数submit_btn.click(fn=process_video_audio,inputs=[video_input, audio_input, model_choice],outputs=output_video)if __name__ == '__main__':# 启动Gradio应用demo.launch(server_name="0.0.0.0", server_port=7860)

将上述脚本放在和inference.py同一级目录,然后运行下面命令:

python inference_ui.py

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/62504.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C—指针初阶(2)

如果看完阁下满意的话&#xff0c;能否一键三连呢&#xff0c;我的动力就是大家的支持与肯定&#xff0c;冲&#xff01; 二级指针 我们先看概念以及作用&#xff1a;用来存放一级指针的地址的指针 先看例子&#xff0c;我们逐一分析 我们先分析上面那个“1” 标注那里&#x…

PE文件结构:NT头部

NT 头部&#xff08;NT Header&#xff09;是 PE 文件格式的核心部分之一&#xff0c;它包含了有关程序如何加载、执行以及一些重要的文件属性。NT 头部常被认为是 PE 头部 的核心或“真正的”PE 头部&#xff0c;因为操作系统加载 PE 文件时&#xff0c;首先会查找 DOS 头部的…

Oracle EBS FA 如何打开关闭的资产会计期间?

用户“运行折旧”,误勾选为“关闭期间”,还有一部分资产还需要操作报废和调整,希望后台打开关闭的资产会计期 系统环境 RDBMS : 12.1.0.2.0 Oracle Applications : 12.2.9 解决方案 由官方提供SQL脚本代码如下: /*rollback120.sql - for Release 12.X only(based on r…

算法基础学习Day6(动态窗口)

文章目录 1.题目2.题目解答1.最大连续1的个数题目及题目解析算法学习思路一:暴力解法思路二:滑动窗口 代码提交 2.将x减到0的最小操作数题目及题目解析算法学习滑动窗口解决问题 代码提交 1.题目 1004. 最大连续1的个数 III - 力扣&#xff08;LeetCode&#xff09;1658. 将 x…

基于springboot+vue的公交线路查询系统(全套)

一、系统架构 前端&#xff1a;vue | element-ui | html 后端&#xff1a;springboot | mybatis-plus 环境&#xff1a;jdk1.8 | mysql | maven | nodejs 二、代码及数据库 三、功能介绍 01. web端-首页1 02. web端-首页2 03. web端-注册 04. web端-登录 …

ASP.NET Core8.0学习笔记(二十五)——EF Core Include导航数据加载之预加载与过滤

一、导航属性数据加载 1.在EF Core中可以使用导航属性来加载相关实体。 2.加载实体的三种方式&#xff1a; (1)预先加载&#xff1a;直接在查询主体时就把对应的依赖实体查出来&#xff08;作为初始查询的一部分&#xff09; (2)显式加载&#xff1a;使用代码指示稍后显式的从…

Linux 基础环境的开发工具以及使用(下)

1. make / Makefile 自动化构建的工具 1&#xff09;引入 在我们进行一些大型的工程的时候&#xff0c;代码量是极其大&#xff0c;当我们代码在进行一系列的编译的时候&#xff0c;难免会出现一些错误&#xff0c;当我们对错误进行一系列的更改之后&#xff0c;难道我们需要…

沃丰科技智能客服在跨境电商独立站中的核心角色

随着全球化进程的加速和互联网技术的不断发展&#xff0c;跨境电商行业蓬勃兴起&#xff0c;为消费者提供了更广阔、更便捷的购物选择。在这样一个竞争激烈的市场环境中&#xff0c;优质的客户服务成为了企业脱颖而出的关键。沃丰科技智能客服凭借其先进的技术和人性化的设计理…

Centos7下搭建Prometheus+Grafana监控

Prometheus 监控 Prometheus 监控系统的架构包括以下组件&#xff1a; Prometheus Server&#xff1a; Prometheus 服务器是监控系统的核心组件&#xff0c;负责收集、存储和处理指标数据。它定期从各种数据源&#xff08;如 Exporter、Agent 等&#xff09;拉取指标数据&…

MyBatis-Plus(为简化开发而生)

一、MyBatis-Plus概述 官网&#xff1a; baomidou.com MyBatis-Plus&#xff08;简称 MP&#xff09; 在 MyBatis 的基础上只做增强不做改变&#xff0c;为简化开发、提高效率而生。 &#xff08;1&#xff09;单表操作 不需要编写sql语句&#xff0c;封装方法&#xff0c;…

深入解析 C++11 的 `std::atomic`:误区、性能与实际应用

在现代 C 开发中&#xff0c;std::atomic 是处理多线程同步时的重要工具之一。它通过提供原子操作保证了线程安全&#xff0c;但在实际使用时却隐藏着许多不为人知的陷阱和性能影响。本篇文章将带你深入理解 std::atomic 的使用方式、潜在问题&#xff0c;以及如何正确应用于多…

芋道源码,芋道sql,yudao,yudao-vue-pro拒绝割韭菜

芋道的开发指南实际上只需要小小的操作就可以观看啦 为了避免被割韭菜 我们可以使用插件去进行解锁文档 项目地址 otomayss/free-yd (github.com)[这里是图片002]https://github.com/otomayss/free-yd

Mac软件推荐

Mac软件推荐 截图SnipasteXnipBob 快捷启动Raycast 系统检测Stats 解压缩The UnarchiverKeka&#xff08;付费&#xff09; 视频播放IINA 视频下载Downie&#xff08;付费&#xff09; 屏幕刘海TopNotchMediaMate&#xff08;付费&#xff09;NotchDrop&#xff08;付费&#x…

车站值班员题库

1. 联系用手信号显示十、五、三车距离信号中的“三车”&#xff08;约33m&#xff09;信号时&#xff0c;昼间的显示方式为展开的绿色信号旗单臂平伸下压 &#xff08; 一 &#xff09;次。J442 2. 联系用手信号显示股道号码时&#xff0c;昼间右臂向上直伸&#xff0c…

BI中场战事:国外厂商退,国产厂商进

从沉睡的黄金到经济的新宠&#xff0c;数据要素正上演华丽转身。 近年来&#xff0c;数字经济的长驱向前&#xff0c;离不开数据要素价值释放所带来的持续动力。作为第五大生产要素&#xff0c;数据要素的价值释放需要从数据采集、传输到存储、治理&#xff0c;再到分析和可视…

2024年华中杯数学建模C题基于光纤传感器的平面曲线重建算法建模解题全过程文档及程序

2024年华中杯数学建模 C题 基于光纤传感器的平面曲线重建算法建模 原题再现 光纤传感技术是伴随着光纤及光通信技术发展起来的一种新型传感器技术。它是以光波为传感信号、光纤为传输载体来感知外界环境中的信号&#xff0c;其基本原理是当外界环境参数发生变化时&#xff0c…

【H2O2|全栈】MySQL的基本操作(三)

目录 前言 开篇语 准备工作 案例准备 多表查询 笛卡尔积 等值连接 外连接 内连接 自连接 子查询 存在和所有 含于 分页查询 建表语句 结束语 前言 开篇语 本篇继续讲解MySQL的一些基础的操作——数据字段的查询中的多表查询和分页查询&#xff0c;与单表查询…

从单体到微服务:如何借助 Spring Cloud 实现架构转型

一、Spring Cloud简介 Spring Cloud 是一套基于 Spring 框架的微服务架构解决方案&#xff0c;它提供了一系列的工具和组件&#xff0c;帮助开发者快速构建分布式系统&#xff0c;尤其是微服务架构。 Spring Cloud 提供了诸如服务发现、配置管理、负载均衡、断路器、消息总线…

yarn : 无法加载文件 C:\Users\L\AppData\Roaming\npm\yarn.ps1,因为在此系统上禁

关于执行安装yarn命令后执行yarn -v报错&#xff1a; 先确认执行安装yarn命令是否有误 # 安装yarn npm install yarn -g 终端输入set-ExecutionPolicy RemoteSigned 当然如果yarn -v仍然执行失败&#xff0c;考虑使用管理员方式运行IDEA&#xff0c; 注&#xff1a;如上操作…

java全栈day12-后端Web实战(IOC+DI)

前言&#xff1a;前面的基础知识了解后进入实战篇&#xff0c;从以下四个方面进行准备 一、开发规范 1.1前后端分离开发 前言回顾 二、Restful风格 引言&#xff1a;前端与后端在进行交互的时候&#xff0c;所使用的url风格叫Restful。 2.1概述 小结 2.2环境准备 2.2.1apif…