Wan2.1 图生视频 Wan2.1-I2V-14B-720P-Diffusers
flyfish
使用模型
Wan2.1-I2V-14B-720P-Diffusers
文件大小 90.1 GB
Diffusers
是 Hugging Face 开发的一个用于扩散模型(Diffusion Models)的开源库,它提供了丰富的工具和预训练模型,方便开发者进行图像生成、视频生成、音频生成等多种任务。
扩散模型
在了解 Diffusers
之前,有必要先了解一下扩散模型。扩散模型是一类基于概率建模的生成模型,其核心思想是通过逐步向数据中添加噪声,将数据转换为噪声分布,然后再通过反向过程从噪声中逐步恢复出原始数据。这种模型在图像生成、音频生成等领域取得了显著的成果,能够生成高质量、多样化的样本。
Diffusers
的主要特点
- 易于使用:
Diffusers
提供了简单易用的 API,开发者可以通过几行代码快速实现图像生成、视频生成等任务。例如,使用Diffusers
的图像生成管道,只需提供文本提示,就可以生成相应的图像。 - 丰富的预训练模型:该库包含了大量的预训练扩散模型,如 Stable Diffusion、DALL - E 等。这些模型经过大规模数据的训练,能够生成高质量的样本,开发者可以直接使用这些预训练模型,无需从头开始训练。
- 模块化设计:
Diffusers
采用模块化设计,将扩散模型的各个组件(如编码器、解码器、调度器等)进行了封装,方便开发者进行定制和扩展。开发者可以根据自己的需求替换或修改这些组件,以实现特定的任务。 - 支持多种任务:除了图像生成,
Diffusers
还支持视频生成、音频生成、图像修复等多种任务。这使得开发者可以在一个库中完成多种不同类型的生成任务。
Diffusers
的主要组件
- Pipelines(管道):
Diffusers
提供了各种预定义的管道,如StableDiffusionPipeline
、WanImageToVideoPipeline
等。这些管道将多个模型和处理步骤封装在一起,形成一个完整的工作流程,方便用户进行特定任务的生成。用户只需提供输入数据(如图像、文本等),就可以直接得到生成的结果。 - Models(模型):库中包含了多种扩散模型,如
AutoencoderKL
、UNet2DModel
等。这些模型可以用于图像编码、解码、去噪等操作,是扩散模型的核心组成部分。 - Schedulers(调度器):调度器用于控制扩散过程中噪声的添加和去除。不同的调度器可以影响生成样本的质量和多样性,
Diffusers
提供了多种调度器供用户选择,如DDIMScheduler
、PNDMScheduler
等。
import os
# 设置环境变量,禁用 tokenizers 的并行处理,避免潜在的线程安全问题
os.environ["TOKENIZERS_PARALLELISM"] = "false"
import torch
import numpy as np
from diffusers import AutoencoderKLWan, WanImageToVideoPipeline
from diffusers.utils import export_to_video
from transformers import CLIPVisionModel
from PIL import Image
# 定义输入图像的文件路径
img_path = "/media/i2v_input.JPG"
# 定义模型所在的本地路径
model_id = "/home/Wan-AI/Wan2_1-I2V-14B-720P-Diffusers/"
# 从指定路径加载 CLIP 视觉模型,用于对输入图像进行编码
image_encoder = CLIPVisionModel.from_pretrained(model_id, subfolder="image_encoder", torch_dtype=torch.float32)
# 从指定路径加载自编码器(VAE),用于图像的编码和解码
vae = AutoencoderKLWan.from_pretrained(model_id, subfolder="vae", torch_dtype=torch.float32)
# 从指定路径加载图像到视频生成的管道,使用之前加载的 VAE 和图像编码器,
# 指定数据类型为 torch.bfloat16,并使用 balanced 模式将模型参数均衡分配到多个 GPU 上
pipe = WanImageToVideoPipeline.from_pretrained(model_id, vae=vae, image_encoder=image_encoder, torch_dtype=torch.bfloat16, device_map="balanced")
# 打开输入图像文件,并将其转换为 RGB 模式
image = Image.open(img_path).convert("RGB")
# 定义生成视频的最大像素面积
max_area = 720 * 1280
# 计算输入图像的高宽比
aspect_ratio = image.height / image.width
# 计算模型处理时的空间缩放因子和 patch 大小的乘积,用于后续的尺寸调整
mod_value = pipe.vae_scale_factor_spatial * pipe.transformer.config.patch_size[1]
# 根据最大面积和高宽比计算调整后的图像高度,并将其调整为 mod_value 的整数倍
height = round(np.sqrt(max_area * aspect_ratio)) // mod_value * mod_value
# 根据最大面积和高宽比计算调整后的图像宽度,并将其调整为 mod_value 的整数倍
width = round(np.sqrt(max_area / aspect_ratio)) // mod_value * mod_value
# 将输入图像调整为计算得到的宽度和高度
image = image.resize((width, height))
# 定义生成视频的文本提示,描述视频的内容和风格
prompt = (
"Summer beach vacation style, a white cat wearing sunglasses sits on a surfboard. The fluffy-furred feline gazes directly at the camera with a relaxed expression. Blurred beach scenery forms the background featuring crystal-clear waters, distant green hills, and a blue sky dotted with white clouds. The cat assumes a naturally relaxed posture, as if savoring the sea breeze and warm sunlight. A close-up shot highlights the feline's intricate details and the refreshing atmosphere of the seaside."
"the background. High quality, ultrarealistic detail and breath-taking movie-like camera shot."
)
# 定义负向文本提示,用于排除不希望出现在生成视频中的特征
negative_prompt = "Bright tones, overexposed, static, blurred details, subtitles, style, works, paintings, images, static, overall gray, worst quality, low quality, JPEG compression residue, ugly, incomplete, extra fingers, poorly drawn hands, poorly drawn faces, deformed, disfigured, misshapen limbs, fused fingers, still picture, messy background, three legs, many people in the background, walking backwards"
# 统计推理前的显存使用情况
before_inference_memory = torch.cuda.memory_allocated() if torch.cuda.is_available() else 0
# 使用图像到视频生成管道进行视频生成,传入输入图像、提示、负向提示、高度、宽度、帧数和引导比例等参数
# 并从生成的帧序列中取出第一组帧
output = pipe(
image=image, prompt=prompt, negative_prompt=negative_prompt, height=height, width=width, num_frames=17, guidance_scale=5.0
).frames[0]
# 将生成的帧序列导出为视频文件,指定输出文件名和帧率
export_to_video(output, "output.mp4", fps=16)
组件的功能和用途
1. AutoencoderKLWan
- 功能:
AutoencoderKLWan
是一种自编码器(Autoencoder),并且结合了 KL 散度(Kullback - Leibler divergence)相关的机制。自编码器是一种无监督学习模型,其主要作用是将输入数据进行编码,压缩成低维的表示(编码过程),然后再从这个低维表示中重构出原始输入数据(解码过程)。KL 散度常被用于变分自编码器(VAE)中,用于衡量潜在变量分布与先验分布之间的差异。 - 在图像到视频生成中的作用:在图像到视频生成的任务里,
AutoencoderKLWan
可以对输入的图像进行编码,将其转换为一个潜在空间的表示。这个潜在表示包含了图像的关键特征信息,然后在生成视频的过程中,解码器可以根据这些特征信息逐步生成视频帧。例如,在视频生成的流程中,它可以帮助模型理解输入图像的内容和风格,为后续的视频生成提供基础。
2. WanImageToVideoPipeline
- 功能:
WanImageToVideoPipeline
是一个图像到视频生成的管道。它将多个深度学习模型和处理步骤封装在一起,形成一个完整的工作流程,方便用户进行图像到视频的生成任务。这个管道通常会集成图像编码器、解码器、扩散模型等多个组件,用户只需提供输入图像和文本提示等信息,就可以直接得到生成的视频。 - 使用方式:用户通过调用
WanImageToVideoPipeline
的__call__
方法,传入输入图像、文本提示、负向提示、视频的高度、宽度、帧数以及引导比例等参数,管道就会自动完成图像编码、特征提取、视频帧生成等一系列操作,并最终输出生成的视频帧序列。例如在你提供的代码中:
output = pipe(
image=image, prompt=prompt, negative_prompt=negative_prompt, height=height, width=width, num_frames=81, guidance_scale=5.0
).frames[0]
3. CLIPVisionModel
- 功能:
CLIPVisionModel
是 OpenAI 提出的 CLIP(Contrastive Language - Image Pretraining)模型中的视觉部分。CLIP 是一种多模态模型,它通过对比学习的方式,将图像和文本映射到同一个特征空间中,使得图像和对应的文本在这个空间中具有相似的表示。CLIPVisionModel
专门用于处理图像数据,将输入的图像编码为一个特征向量。 - 在图像到视频生成中的作用:在图像到视频生成任务中,
CLIPVisionModel
可以对输入的图像进行特征提取,将图像转换为一个高维的特征向量。这个特征向量包含了图像的语义信息和视觉特征,模型可以根据这个特征向量以及文本提示来生成符合要求的视频。例如,它可以帮助模型理解输入图像中的物体、场景等信息,结合文本提示中的描述,生成与图像和文本相匹配的视频内容。
正向提示词
夏日海滩度假风格,一只戴墨镜的白猫坐在冲浪板上。毛茸茸的猫咪慵懒地直视镜头,表情放松。背景是模糊的海滩景色,包括清澈见底的海水、远处的翠绿山丘和缀有白云的湛蓝天空。猫咪摆出自然放松的姿势,仿佛在惬意地享受海风与温暖阳光。特写镜头突出了猫咪的细节特征以及海滨的清新氛围。
背景需呈现高质量、超现实细节,以及电影般的震撼镜头效果
负向提示词
明亮的色调、过曝、静态、细节模糊、字幕、风格、作品、绘画、图像、呆板、整体灰暗、质量最差、低质量、JPEG压缩痕迹、丑陋、不完整、多余的手指、绘制粗糙的手、绘制粗糙的脸部、畸形、面容扭曲、四肢变形、手指粘连、静态画面、杂乱背景、三条腿、背景中有很多人、倒退行走
安装diffusers注意的问题
pip uninstall diffusers -y
pip install git+https://github.com/huggingface/diffusers.git
代码实现
import os
import torch
import numpy as np
from diffusers import AutoencoderKLWan, WanImageToVideoPipeline
from diffusers.utils import export_to_video
from transformers import CLIPVisionModel
from PIL import Image
import time
# 从指定路径加载 CLIP 视觉模型,用于对输入图像进行编码,指定数据类型为 torch.float32
def load_image_encoder(model_id):
return CLIPVisionModel.from_pretrained(model_id, subfolder="image_encoder", torch_dtype=torch.float32)
# 从指定路径加载自编码器(VAE),用于图像的编码和解码,指定数据类型为 torch.float32
def load_vae(model_id):
return AutoencoderKLWan.from_pretrained(model_id, subfolder="vae", torch_dtype=torch.float32)
# 从指定路径加载图像到视频生成的管道
def load_pipeline(model_id, vae, image_encoder):
return WanImageToVideoPipeline.from_pretrained(model_id, vae=vae, image_encoder=image_encoder,
torch_dtype=torch.bfloat16, device_map="balanced")
# 加载提示信息
def load_prompts():
# 打开并读取文本文件的内容
with open('prompt.txt', 'r', encoding='utf-8') as file:
lines = file.readlines()
# 将多行内容合并为单行
prompt = ''.join(lines).replace('\n', ' ').strip()
# 定义负向文本提示,用于排除不希望出现在生成视频中的特征
negative_prompt = "Bright tones, overexposed, static, blurred details, subtitles, style, works, paintings, images, static, overall gray, worst quality, low quality, JPEG compression residue, ugly, incomplete, extra fingers, poorly drawn hands, poorly drawn faces, deformed, disfigured, misshapen limbs, fused fingers, still picture, messy background, three legs, many people in the background, walking backwards"
return prompt, negative_prompt
# 处理单张图像
def process_image(pipe, img_path, prompt, negative_prompt):
image = Image.open(img_path).convert("RGB")
max_area = 832 * 480
aspect_ratio = image.height / image.width
mod_value = pipe.vae_scale_factor_spatial * pipe.transformer.config.patch_size[1]
height = round(np.sqrt(max_area * aspect_ratio)) // mod_value * mod_value
width = round(np.sqrt(max_area / aspect_ratio)) // mod_value * mod_value
image = image.resize((width, height))
start_time = time.time()
output = pipe(
image=image, prompt=prompt, negative_prompt=negative_prompt, height=height, width=width, num_frames=81,
guidance_scale=5.0
).frames[0]
end_time = time.time()
inference_time = end_time - start_time
return output, inference_time
# 主函数
def main(input_folder, output_folder, model_id):
image_encoder = load_image_encoder(model_id)
vae = load_vae(model_id)
pipe = load_pipeline(model_id, vae, image_encoder)
prompt, negative_prompt = load_prompts()
total_time = 0
image_count = 0
for idx, filename in enumerate(os.listdir(input_folder)):
if filename.lower().endswith(('.png', '.jpg', '.jpeg')):
img_path = os.path.join(input_folder, filename)
output, inference_time = process_image(pipe, img_path, prompt, negative_prompt)
total_time += inference_time
image_count += 1
timestamp = time.strftime("%Y%m%d-%H%M%S")
output_filename = f"{timestamp}-{idx + 1}-{filename.split('.')[0]}.mp4"
output_path = os.path.join(output_folder, output_filename)
export_to_video(output, output_path, fps=16)
print(f"处理 {filename} 耗时: {inference_time:.2f} 秒,输出文件: {output_filename}")
if image_count > 0:
print(f"总共处理 {image_count} 张图像,总耗时: {total_time:.2f} 秒,平均每张图像耗时: {total_time / image_count:.2f} 秒")
if __name__ == "__main__":
input_folder = "input"
output_folder = "output"
model_id = "/home//Wan-AI/Wan21-I2V-14B-720P-Diffusers/"
if not os.path.exists(output_folder):
os.makedirs(output_folder)
main(input_folder, output_folder, model_id)