唇形同步視頻生成工具: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.pswp.cn/web/62504.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/62504.shtml
英文地址,請注明出處:http://en.pswp.cn/web/62504.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

C編程求助問題:實驗報告類型如何畫出流程圖并編寫程序?

求助問題&#xff1a;請問一下怎么做 是實驗報告類型的 畫出流程圖并編寫程序&#xff1a; (1) 從鍵盤上任意輸入5個字母&#xff0c;按ASCII從小到大的順序依次排列輸出。 (2) 輸入某個字母&#xff0c;查找題(1)數組中是否存在&#xff0c;若存在則輸出該字母在數組中的位置。…

C—指針初階(2)

如果看完閣下滿意的話&#xff0c;能否一鍵三連呢&#xff0c;我的動力就是大家的支持與肯定&#xff0c;沖&#xff01; 二級指針 我們先看概念以及作用&#xff1a;用來存放一級指針的地址的指針 先看例子&#xff0c;我們逐一分析 我們先分析上面那個“1” 標注那里&#x…

Gradle-學習

本來沒有想了解Gradle&#xff0c;但是在想看SpringBoot源碼的時候發現&#xff0c;在SpringBoot2.2.8版本之后&#xff0c;不再使用maven進行構建&#xff0c;而是使用Gradle。想著把SpringBoot源碼導入idea學習下源碼&#xff0c;但是來來回回折騰了好幾回&#xff0c;都是報…

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;優質的客戶服務成為了企業脫穎而出的關鍵。沃豐科技智能客服憑借其先進的技術和人性化的設計理…

uniapp 彈出軟鍵盤后打開二級頁面,解決其UI布局變動

軟鍵盤彈出&#xff0c;此時點擊某按鈕打開二級頁面&#xff0c;position:fixed 位于底部的按鈕不見了&#xff08;通過加高其區域&#xff0c;發現被下移動了&#xff09;&#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…

【ETCD】【源碼閱讀】 深入解析 raftNode.start`函數:Raft 核心啟動邏輯剖析

raftNode.start方法 是 etcd 中 Raft 模塊的核心啟動點&#xff0c;其職責是管理 Raft 狀態機的狀態變遷、日志處理及集群通信等邏輯。通過對源碼的逐行分析&#xff0c;我們將全面揭示其運行機制&#xff0c;探討其設計背后的分布式系統理念。 函數核心結構 raftNode.start 方…

車站值班員題庫

1. 聯系用手信號顯示十、五、三車距離信號中的“三車”&#xff08;約33m&#xff09;信號時&#xff0c;晝間的顯示方式為展開的綠色信號旗單臂平伸下壓 &#xff08; 一 &#xff09;次。J442 2. 聯系用手信號顯示股道號碼時&#xff0c;晝間右臂向上直伸&#xff0c…

BI中場戰事:國外廠商退,國產廠商進

從沉睡的黃金到經濟的新寵&#xff0c;數據要素正上演華麗轉身。 近年來&#xff0c;數字經濟的長驅向前&#xff0c;離不開數據要素價值釋放所帶來的持續動力。作為第五大生產要素&#xff0c;數據要素的價值釋放需要從數據采集、傳輸到存儲、治理&#xff0c;再到分析和可視…

2024年華中杯數學建模C題基于光纖傳感器的平面曲線重建算法建模解題全過程文檔及程序

2024年華中杯數學建模 C題 基于光纖傳感器的平面曲線重建算法建模 原題再現 光纖傳感技術是伴隨著光纖及光通信技術發展起來的一種新型傳感器技術。它是以光波為傳感信號、光纖為傳輸載體來感知外界環境中的信號&#xff0c;其基本原理是當外界環境參數發生變化時&#xff0c…