扩散模型实战(十二):使用调度器DDIM反转来优化图像编辑

 推荐阅读列表:

 扩散模型实战(一):基本原理介绍

扩散模型实战(二):扩散模型的发展

扩散模型实战(三):扩散模型的应用

扩散模型实战(四):从零构建扩散模型

扩散模型实战(五):采样过程

扩散模型实战(六):Diffusers DDPM初探

扩散模型实战(七):Diffusers蝴蝶图像生成实战

扩散模型实战(八):微调扩散模型

扩散模型实战(九):使用CLIP模型引导和控制扩散模型

扩散模型实战(十):Stable Diffusion文本条件生成图像大模型

扩散模型实战(十一):剖析Stable Diffusion Pipeline各个组件

一、配置环境

# !pip install -q transformers diffusers accelerateimport torchimport requestsimport torch.nn as nnimport torch.nn.functional as Ffrom PIL import Imagefrom io import BytesIOfrom tqdm.auto import tqdmfrom matplotlib import pyplot as pltfrom torchvision import transforms as tfmsfrom diffusers import StableDiffusionPipeline, DDIMScheduler# 定义接下来将要用到的函数def load_image(url, size=None):    response = requests.get(url,timeout=0.2)    img = Image.open(BytesIO(response.content)).convert('RGB')    if size is not None:        img = img.resize(size)    return imgdevice = torch.device("cuda" if torch.cuda.is_available() else "cpu")

二、加载预训练过的Stable Diffusion Pipeline

        加载预训练pipeline并配置DDIM调度器,而后进行一次采样,代码如下:

# 载入一个管线pipe = StableDiffusionPipeline.from_pretrained("runwayml/stable- diffusion-v1-5").to(device) # 配置DDIM调度器pipe.scheduler = DDIMScheduler.from_config(pipe.scheduler.config) # 从中采样一次,以保证代码运行正常prompt = 'Beautiful DSLR Photograph of a penguin on the beach,  golden hour'negative_prompt = 'blurry, ugly, stock photo'im = pipe(prompt, negative_prompt=negative_prompt).images[0]im.resize((256, 256)) # 调整至有利于查看的尺寸

三、DDIM采样

给定任意时刻t,加噪后的图像公式如下所示:

下面是绘制加噪alpha的随时间步的变化:

# 绘制'alpha','alpha'(即α)在DDPM论文中被称为'alpha bar'(即α)。# 为了能够清晰地表现出来,我们# 选择使用Diffusers中的alphas_cumprod函数来得到alphas)timesteps = pipe.scheduler.timesteps.cpu()alphas = pipe.scheduler.alphas_cumprod[timesteps]plt.plot(timesteps, alphas, label='alpha_t');plt.legend();

标准DDIM(https://arxiv.org/abs/2010.02502)采样的实现代码如下所示:

# 采样函数(标准的DDIM采样)@torch.no_grad()def sample(prompt, start_step=0, start_latents=None,           guidance_scale=3.5, num_inference_steps=30,           num_images_per_prompt=1, do_classifier_free_ guidance=True,           negative_prompt='', device=device):# 对文本提示语进行编码    text_embeddings = pipe._encode_prompt(            prompt, device, num_images_per_prompt,             do_classifier_free_guidance, negative_prompt    )# 配置推理的步数    pipe.scheduler.set_timesteps(num_inference_steps, device=device) # 如果没有起点,就创建一个随机的起点    if start_latents is None:       start_latents = torch.randn(1, 4, 64, 64, device=device)       start_latents *= pipe.scheduler.init_noise_sigma     latents = start_latents.clone()     for i in tqdm(range(start_step, num_inference_steps)):            t = pipe.scheduler.timesteps[i]# 如果正在进行CFG,则对隐层进行扩展    latent_model_input = torch.cat([latents] * 2)  if do_classifier_free_guidance else latents    latent_model_input = pipe.scheduler.scale_model_input(latent_       model_input, t)# 预测残留的噪声    noise_pred = pipe.unet(latent_model_input, t, encoder_hidden_       states=text_embeddings).sample# 进行引导    if do_classifier_free_guidance:            noise_pred_uncond, noise_pred_text = noise_pred.chunk(2)            noise_pred = noise_pred_uncond + guidance_scale *                (noise_pred_text - noise_pred_uncond)     # 使用调度器更新步骤        # latents = pipe.scheduler.step(noise_pred, t, latents). # prev_sample # 现在不用调度器,而是自行实现        prev_t = max(1, t.item() - (1000//num_inference_steps)) # t-1        alpha_t = pipe.scheduler.alphas_cumprod[t.item()]        alpha_t_prev = pipe.scheduler.alphas_cumprod[prev_t]        predicted_x0 = (latents - (1-alpha_t).sqrt()*noise_pred) /            alpha_t.sqrt()        direction_pointing_to_xt = (1-alpha_t_prev).sqrt()*noise_           pred        latents = alpha_t_prev.sqrt()*predicted_x0 + direction_           pointing_to_xt# 后处理     images = pipe.decode_latents(latents)    images = pipe.numpy_to_pil(images)     return images# 生成一张图片,测试一下采样函数,效果如图7-4所示sample('Watercolor painting of a beach sunset', negative_prompt=    negative_prompt, num_inference_steps=50)[0].resize((256, 256))

四、DDIM反转

       反转的目标是”颠倒“采样的过程。我们最终想得到”带噪“的隐式表示。如果将其用作采样过程的起点,那么生成的图像将是原始图像。

       我们现在首先来加载一张图像,来看看DDIM反转如何做?有什么效果?

#图片来源:https://www.pexels.com/photo/a-beagle-on-green-grass- # field-8306128/(代码中使用对应的JPEG文件链接)input_image = load_image('https://images.pexels.com/photos/ 8306128/pexels-photo-8306128.jpeg', size=(512, 512))

       我们使用一个包含无分类器引导的文本Prompt来进行反转操作,代码如下:

input_image_prompt = "Photograph of a puppy on the grass"

       接下来,我们将这幅PIL图像转换为一系列隐式表示,这些隐式表示将被用作反转操作的起点。

# 使用VAE进行编码with torch.no_grad(): latent = pipe.vae.encode(tfms.functional.to_   tensor(input_image).unsqueeze(0).to(device)*2-1)l = 0.18215 * latent.latent_dist.sample()

       我们使用invert函数进行反转,可以看出invert与上面的sample函数非常类似,但是invert函数是朝相反的方向移动的:从t=0开始,想噪声更多的方向移动的,而不是在更新隐式层的过程中那样噪声越来越少。我们可以利用预测的噪声来撤回一步更新操作,并从t移动到t+1。

## 反转@torch.no_grad()def invert(start_latents, prompt, guidance_scale=3.5,           num_inference_steps=80,num_images_per_prompt=1,            do_classifier_free_guidance=True, negative_prompt='',            device=device): # 对提示文本进行编码    text_embeddings = pipe._encode_prompt(      prompt, device, num_images_per_prompt,      do_classifier_free_guidance, negative_prompt     )     # 已经指定好起点     latents = start_latents.clone()     # 用一个列表保存反转的隐层     intermediate_latents = []     # 配置推理的步数     pipe.scheduler.set_timesteps(num_inference_steps,device=device)      # 反转的时间步     timesteps = reversed(pipe.scheduler.timesteps)      for i in tqdm(range(1, num_inference_steps), total=num_         inference_steps-1): # 跳过最后一次迭代     if i >= num_inference_steps - 1: continue      t = timesteps[i] # 如果正在进行CFG,则对隐层进行扩展      latent_model_input = torch.cat([latents] * 2) if do_        classifier_free_guidance else latents     latent_model_input = pipe.scheduler.scale_model_        input(latent_model_input, t)     # 预测残留的噪声      noise_pred = pipe.unet(latent_model_input, t, encoder_        hidden_states=text_embeddings).sample     # 进行引导     if do_classifier_free_guidance:        noise_pred_uncond, noise_pred_text = noise_pred.chunk(2)        noise_pred = noise_pred_uncond + guidance_scale *        (noise_pred_text - noise_pred_uncond)current_t = max(0, t.item() - (1000//num_inference_steps))#tnext_t = t # min(999, t.item() + (1000//num_inference_steps)) # t+1alpha_t = pipe.scheduler.alphas_cumprod[current_t]alpha_t_next = pipe.scheduler.alphas_cumprod[next_t] # 反转的更新步(重新排列更新步,利用xt-1(当前隐层)得到xt(新的隐层))latents = (latents - (1-alpha_t).sqrt()*noise_pred)*(alpha_t_next.   sqrt()/alpha_t.sqrt()) + (1-alpha_t_next).sqrt()*noise_pred# 保存intermediate_latents.append(latents)return torch.cat(intermediate_latents)

将invert函数应用于上述小狗的图片,得到图片的一系列隐式表示。

inverted_latents = invert(l, input_image_prompt,num_inference_steps=50)inverted_latents.shape
# 输出torch.Size([48, 4, 64, 64])

将得到的最终隐式表示作为起点噪声,尝试新的采样过程。

# 解码反转的最后一个隐层with torch.no_grad():  im = pipe.decode_latents(inverted_latents[-1].unsqueeze(0))pipe.numpy_to_pil(im)[0]

通过调用call方法将反转隐式表示输入给Pipeline。

pipe(input_image_prompt, latents=inverted_latents[-1][None],      num_inference_steps=50, guidance_scale=3.5).images[0]

      看到生成的图片是不是有点蒙了,这不是刚开始输入的图片呀?

      这是因为DDIM反转需要一个重要的假设-在时刻t预测的噪声与在时刻t+1预测的噪声相同,但这个假设在反转50步或100步是不成立的。

       我们既可以使用更多的时间步来得到更准确的反转,也可以采取”作弊“的方法,直接从相应反转过程50步中的第20步的隐式表示开始。

# 设置起点的原因start_step=20sample(input_image_prompt, start_latents=inverted_latents[-(start_step+1)][None], start_step=start_step, num_inference_steps=50)[0]

      经过这一折腾,生成的图片和原始图片很接近了,那为什么要这么做呢?

       因为我们现在想用一个新的文本Prompt来生成图片。我们想要得到一张除了与Prompt相关以外,其他内容都与原始图片大致相同的图片。例如,将小狗换成小猫,得到的结果如下所示:

# 使用新的文本提示语进行采样start_step=10new_prompt = input_image_prompt.replace('puppy', 'cat')sample(new_prompt, start_latents=inverted_latents[-(start_step+1)]       [None],start_step=start_step, num_inference_steps=50)[0]

       到此为止,读者可能有一些疑问,比如为什么不直接使用Img2Img?为什么要反转?为什么不直接对输入图像添加噪声,然后用新的Prompt直接”去噪“呢?

       其实是可以采用上述方法做的,但是生成的效果对添加的噪声量十分敏感,噪声量大时会生成十分夸张的图片,噪声量小时生成的图片几乎没有变化。

start_step = 10num_inference_steps=50pipe.scheduler.set_timesteps(num_inference_steps)noisy_l = pipe.scheduler.add_noise(l, torch.randn_like(l), pipe.   scheduler.timesteps[start_step])sample(new_prompt, start_latents=noisy_l, start_step=start_step,     num_inference_steps=num_inference_steps)[0]

五、DDIM反转整体方案

      将上述代码封装到一个简单函数中,并输入一张图片和两个文本Prompt,便可以得到一张通过反转修改后的图片。

def edit(input_image, input_image_prompt, edit_prompt, num_steps=100, start_step=30,guidance_scale=3.5):    with torch.no_grad(): latent = pipe.vae.encode(tfms.functional.      to_tensor(input_image).unsqueeze(0).to(device)*2-1)    l = 0.18215 * latent.latent_dist.sample()    inverted_latents = invert(l, input_image_prompt,num_inference_       steps=num_steps)    final_im = sample(edit_prompt, start_latents=inverted_latents[       -(start_step+1)][None],start_step=start_step, num_inference_       steps=num_steps,guidance_scale=guidance_scale)[0]    return final_imAnd in action: # 实际操作edit(input_image, 'A puppy on the grass', 'an old grey dog on  the grass', num_steps=50,start_step=10) 

修改一下Prompt和参数来看看效果如何不同

edit(input_image, 'A puppy on the grass', 'A blue dog on the lawn',  num_steps=50,start_step=12, guidance_scale=6) 

得到如下图片

更多迭代能够得到更好的表现,我们可以测试一下

# 更多步的反转测试edit(input_image, 'A puppy on the grass', 'A puppy on the grass',     num_steps=350, start_step=1)

我们换一张图片进行测试一下看看效果

原始图片如下所示:

# 图片来源:https://www.pexels.com/photo/girl-taking-photo-1493111/ # (代码中使用对应的JPEG文件链接)face = load_image('https://images.pexels.com/photos/1493111/pexels- photo-1493111.jpeg', size=(512, 512))
edit(face, 'A photograph of a face', 'A photograph of a face with sunglasses', num_steps=250, start_step=30, guidance_scale=3.5)

生成的效果如下所示:

PS:读者可以通过测试不同的Prompt来观察生成的效果,强烈建议了解一下Null-text Inversion:一个基于DDIM来优化空文本(无条件Prompt)的反转过程,有更准确的反转过程与更好的编辑效果。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

vue3+ts 依赖注入 provide inject

父级&#xff1a; <template><div><h1>App.vue (爷爷级别)</h1><label><input type"radio" v-model"colorVal" value"red" name"color" />红色</label><label><input type"r…

Win11画图板的解决方法

Win11画图板的解决方法 现状: 伴随着windows由win10更新到win11,windows自带的画图板也随之更新,但是它就变得对我们用户就不太友善了,变得很难使用. 具体表现: 需求: 但是由于各种需求,就以我来举例,由于博主写博客的需要,去使用其它的软件,就找不到其它这么好用的画图软件进…

Linux的基本指令(三)

目录 前言 echo指令&#xff08;简述&#xff09; Linux的设计理念 输出重定向操作符 > 追加输出重定向操作符 >> 输入重定向操作符 < 补充知识 学前补充 more指令 less指令 head指令 tail指令 查看文件中间的内容 利用输出重定向实现 利用管道“ |…

python树的孩子链存储结构

树的孩子链存储结构是一种树的存储方式&#xff0c;它使用孩子兄弟表示法来表示树的结构。在这种存储结构中&#xff0c;树的每个节点都有一个指向其第一个孩子的指针和一个指向其下一个兄弟的指针。这样&#xff0c;可以通过这些指针来表示树的层次结构和节点之间的关系。 具…

大公司为什么喜欢centos系统写爬虫?

CentOS是一个基于Red Hat Enterprise Linux&#xff08;RHEL&#xff09;源代码构建的开源操作系统&#xff0c;它受到大企业喜欢大多数因为他系统的稳定性&#xff0c;安全性以及兼容性等。可以为企业提供更多的商业支持。以我个人为例&#xff0c;公司在做爬虫数据抓取多是采…

Vue 双向数据绑定

之前通过v-bind来完成的数据绑定&#xff0c;属性值和表达式进行绑定&#xff0c;表达式的值发生变化了属性值也跟着发生变化。 单向数据绑定&#xff1a; <!DOCTYPE html> <html> <head><meta charset"UTF-8"><title>首页</titl…

信息素养大赛知识点

基础理论准备 开放存储期刊 开放存取期刊是一种免费的网络期刊&#xff0c;旨在使所有用户都可以通过因特网无限制地访问期刊论文全文。此种期刊一般采用作者付费出版、读者免费获得、无限制使用的运作模式&#xff0c;论文版权由作者保留。在论文质量控制方面&#xff0c;oa…

Couchdb 命令执行漏洞复现 (CVE-2017-12636)

Couchdb 命令执行漏洞复现 &#xff08;CVE-2017-12636&#xff09; 1、下载couchdb.py 2、修改目标和反弹地址 3、Python3调用执行即可 couchdb.py文件下载地址: https://github.com/vulhub/vulhub/blob/master/couchdb/CVE-2017-12636/exp.py ‍ 在VULFocus上开启环境 …

虚拟内存的基本概念

文章目录 虚拟内存虚拟地址空间(其他 Unix 系统的设计也与此类似)。程序代码和数据堆共享库栈内核虚拟内存 参考 虚拟内存 虚拟内存是一个抽象概念&#xff0c;它为每个进程提供了一个假象&#xff0c;即每个进程都在独占地使用主存。每个进程看到的内存都是一致的&#xff0c…

城市安全守护者:分析无人机在交通领域的应用

随着科技的进步&#xff0c;无人机在交通领域的应用不断增加&#xff0c;为智慧交通管理提供了新便利。无人机凭借其灵活性&#xff0c;在违章取证、交通事故侦查、交通疏导等方面展现出巨大的应用潜力。无人机在交通领域的应用有哪些&#xff1f;跟着我们一探究竟。 1、违章取…

iOS移动应用程序的备案与SHA-1值查看

​ 目录 &#x1f4dd;iOS移动应用程序的备案与SHA-1值查看 引言 第一部分&#xff1a;App备案 第二部分&#xff1a;查看SHA-1值 引言 在开发和发布移动应用程序时&#xff0c;进行App备案是非常重要的一步&#xff0c;它是确保您的应用在合规性方面符合相关法规的过程。…

STM32入门

写在前面&#xff1a;本文是基于哔哩哔哩江协科技的STM32入门教程-2023版 细致讲解 中文字幕学习时写的笔记&#xff0c;复习查阅方便&#xff0c;如有侵权&#xff0c;联系删除。 另外本人也是初学者&#xff0c;有很多理解不透彻的或者错误的理解&#xff0c;希望大家多多批评…

JVM——垃圾回收算法(垃圾回收算法评价标准,四种垃圾回收算法)

目录 1.垃圾回收算法发展简介2.垃圾回收算法的评价标准1.吞吐量2.最大暂停时间3.堆使用效率 3.垃圾回收算法01-标记清除算法垃圾回收算法-标记清除算法的优缺点 4.垃圾回收算法02-复制算法垃圾回收算法-复制算法的优缺点 5.垃圾回收算法03-标记整理算法标记整理算法的优缺点 6.…

基于C#实现鸡尾酒排序(双向冒泡排序)

通俗易懂点的话&#xff0c;就叫“双向冒泡排序”。 冒泡是一个单向的从小到大或者从大到小的交换排序&#xff0c;而鸡尾酒排序是双向的&#xff0c;从一端进行从小到大排序&#xff0c;从另一端进行从大到小排序。 从图中可以看到&#xff0c;第一次正向比较&#xff0c;我们…

第二证券:煤炭板块震荡走高 潞安环能、晋控煤业涨超5%

证券时报网讯&#xff0c;煤炭板块27日盘中发力走高&#xff0c;到发稿&#xff0c;潞安环能、晋控煤业涨超5%&#xff0c;平煤股份、山西焦煤涨逾3%&#xff0c;恒源煤电、开滦股份等上扬。 职业方面&#xff0c;近期寒潮来袭&#xff0c;气温下降带动居民用电需求增加&#…

操作系统——解决了我的一些困惑

目录 1、电脑开机做了什么事情 2、真正实现并行的计算机 3、计算机中的淘汰算法 & 分配算法 & 调度算法 & 空间管理 4、什么是虚拟内存&#xff1f;为什么需要虚拟内存&#xff1f;最多可分配多少&#xff1f; 5、TLB&#xff08;快表&#xff09;、分页存储&…

TechSmith Camtasia2024中文版简单好用的视频处理软件

TechSmith Camtasia 2024中文版是由techsmith公司推出的一款简单好用的视频处理软件&#xff0c;它集视频录制与视频后期处理为一体&#xff0c;用户可以使用软件来进行屏幕录制&#xff0c;其中包括了影像、音效、鼠标移动的轨迹、解说声音等任何模式下的电脑屏幕状态&#xf…

【LeetCode:907. 子数组的最小值之和 | 贡献法 乘法原理 单调栈】

&#x1f680; 算法题 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;…

王者荣耀小游戏

第一步是创建项目 项目名自拟 第二部创建个包名 来规范class 然后是创建类 GameFrame 运行类 package com.sxt; package com.sxt;import java.awt.Graphics; import java.awt.Image; import java.awt.Toolkit; import java.awt.event.ActionEvent; import java.awt.event.…