☰
Diffusers DiffEdit 实战指南:用 StableDiffusionDiffEditPipeline 实现文本驱动的语义图像编辑
2026/10/12 20:17:02 网站建设 项目流程

Diffusers DiffEdit 实战指南:用 StableDiffusionDiffEditPipeline 实现文本驱动的语义图像编辑

【免费下载链接】diffusers🤗 Diffusers: State-of-the-art diffusion models for image, video, and audio generation in PyTorch.项目地址: https://gitcode.com/GitHub_Trending/di/diffusers

本文以 Hugging Face Diffusers 仓库中的 DiffEdit 使用指南为蓝本(对应文档:diffedit.md,英文用户可参考 controlling_generation.md 中的 DiffEdit 章节),系统讲解如何使用StableDiffusionDiffEditPipeline完成"免手工蒙版"的语义图像编辑:从文本自动生成编辑掩码、用 DDIM 反演获得图像 latent,再到掩码引导的局部重绘,最后介绍用 Flan-T5 与 BLIP 将整个流程自动化的进阶方案。读完本文,你将掌握 DiffEdit 三阶段算法的完整调用链、全部关键参数的含义与调优方法,以及一条可复制运行的端到端编辑管线。

DiffEdit 是什么:三步语义编辑原理

传统的图像编辑(如 inpainting)通常要求用户手工提供"要改哪里"的蒙版,而 DiffEdit 的价值在于:只需给出描述原图的source_prompt与描述编辑目标的target_prompt,蒙版便会自动生成,无需任何图像编辑软件。

DiffEdit 算法分为三个步骤(与文档描述一致,且每一步都能在StableDiffusionDiffEditPipeline源码中找到对应实现):

  1. 语义掩码推断:让扩散模型分别以查询文本(target)和参考文本(source)为条件对图像去噪,对图像不同区域产生不同的噪声估计;取两者噪声预测的差异,即可推断出"为了匹配查询文本,图像中哪些区域需要被修改",据此生成掩码。这一步对应源码中的generate_mask方法。
  2. 潜在空间编码:使用 DDIM 将输入图像编码(反演)到潜在空间,得到"部分反演的 latents"。对应源码中的invert方法与DDIMInverseScheduler调度器。
  3. 掩码引导解码:以掩码为引导,用条件于查询文本的扩散模型对 latents 去噪,同时保证掩码之外的像素与输入图像保持一致。对应管线主入口__call__中的去噪循环——在每一步,源码通过latents = latents * mask_image + image_latents[i] * (1 - mask_image)(源码第 1516 行)将掩码区域的重绘结果与掩码外的原始图像 latent 融合。

从源码结构看,StableDiffusionDiffEditPipeline位于 src/diffusers/pipelines/deprecated/stable_diffusion_diffedit/pipeline_stable_diffusion_diffedit.py,它继承自DiffusionPipeline、StableDiffusionMixin,并组合了TextualInversionLoaderMixin与StableDiffusionLoraLoaderMixin,因此天然支持文本反转(textual inversion)与 LoRA 权重加载;同时该管线被标记为实验性/已弃用(deprecated)特性,_last_supported_version = "0.33.1",使用前请确认版本兼容性。

环境准备与安装

开始之前,请确保以下库已安装:

# 取消注释以在 Colab 中安装所需库 #!pip install -q diffusers transformers accelerate

其中diffusers提供管线本体,transformers提供文本编码器(CLIP)以及进阶方案中的 Flan-T5、BLIP 模型,accelerate用于设备管理与 CPU offload。从 deprecated/stable_diffusion_diffedit/init.py 可以看到,StableDiffusionDiffEditPipeline的导入依赖torch与transformers同时可用,否则只会得到一个占位空对象。

完整工作流:从零编辑一张图片

加载管线、调度器与内存优化

StableDiffusionDiffEditPipeline需要两类调度器配合使用:正向去噪用的DDIMScheduler,以及反演用的DDIMInverseScheduler。此外文档还建议启用模型 CPU offload 与 VAE 切片来降低显存占用:

import torch from diffusers import DDIMScheduler, DDIMInverseScheduler, StableDiffusionDiffEditPipeline pipeline = StableDiffusionDiffEditPipeline.from_pretrained( "stabilityai/stable-diffusion-2-1", dtype=torch.float16, safety_checker=None, use_safetensors=True, ) pipeline.scheduler = DDIMScheduler.from_config(pipeline.scheduler.config) pipeline.inverse_scheduler = DDIMInverseScheduler.from_config(pipeline.scheduler.config) pipeline.enable_model_cpu_offload() pipeline.vae.enable_slicing()

几点说明(结合源码):

  • 从 源码示例文档字符串 看,官方推荐的精确保数参数写法是torch_dtype=torch.float16,文档中的dtype=为旧式写法,二者效果相同;use_safetensors=True表示优先加载 safetensors 权重。
  • safety_checker=None会跳过安全检测器(源码中会打印警告,提示仅在分析或审计场景下禁用);inverse_scheduler属于可选组件(见_optional_components = ["safety_checker", "feature_extractor", "inverse_scheduler"],源码第 288 行),但 DiffEdit 流程必须手动挂载它。
  • enable_model_cpu_offload()按"text_encoder->unet->vae"的顺序(model_cpu_offload_seq,源码第 287 行)逐模块卸载到 CPU。

加载输入图片

from diffusers.utils import load_image, make_image_grid img_url = "https://github.com/Xiang-cd/DiffEdit-stable-diffusion/raw/main/assets/origin.png" raw_image = load_image(img_url).resize((768, 768)) raw_image

load_image是 diffusers 提供的小工具,可直接从 URL 或本地路径读取图片。注意:由于 VAE 的 8 倍下采样特性,图片宽高需要是 8 的整数倍(源码preprocess中也有resize to integer multiple of 8的说明),示例中统一resize((768, 768))正是为此。

生成语义掩码:generate_mask

掩码是 DiffEdit 自动化的核心。调用generate_mask时传入source_prompt与target_prompt,两者共同决定"图片中改什么":

from PIL import Image source_prompt = "a bowl of fruits" target_prompt = "a basket of pears" mask_image = pipeline.generate_mask( image=raw_image, source_prompt=source_prompt, target_prompt=target_prompt, ) Image.fromarray((mask_image.squeeze()*255).astype("uint8"), "L").resize((768, 768))

例如要把"一碗水果"(a bowl of fruits)改成"一篮梨"(a basket of pears),source_prompt描述现状,target_prompt描述目标。源码中掩码的计算过程为(第 1041-1051 行):

  1. 对同一张图分别以 source 和 target 为条件做一次带噪声的前向,得到两组噪声预测noise_pred_source与noise_pred_target;
  2. 取两者绝对差torch.abs(noise_pred_target - noise_pred_source),在num_maps_per_mask张噪声图与通道维度上求平均,得到"语义引导图"(mask guidance map);
  3. 用mask_guidance_map.mean() * mask_thresholding_ratio作为钳制上限做归一化,再以 0.5 为阈值二值化,得到最终掩码(0/1)。

返回的掩码形状为(height // vae_scale_factor, width // vae_scale_factor)(潜在空间分辨率),示例代码把它放大回 768×768 以便可视化。

DDIM 反演:invert

下一步用invert将输入图像反演为部分加噪的 latents。这里传入描述图像的prompt(即 caption),它能引导反演采样过程,使结果更稳定——caption 通常直接用source_prompt,但完全可以用其他文本描述做实验:

inv_latents = pipeline.invert(prompt=source_prompt, image=raw_image).latents

源码中反演循环使用inverse_scheduler.step()逐时间步执行(第 1268 行),并收集每个时间步的 latents 后逆序堆叠,输出维度为(batch, num_timesteps, ...)的张量——这也是后续__call__期望的输入格式。invert还支持 Pix2PixZero 风格的噪声正则化(lambda_auto_corr、lambda_kl、num_reg_steps、num_auto_corr_rolls,默认分别为 20.0、20.0、0、5),源码注释明确说明这部分"并非原始论文内容,而是借鉴自 Pix2PixZero"。

掩码引导的修复生成:调用管线

最后把掩码与反演 latents 交给管线主入口。此时target_prompt作为正向prompt,source_prompt作为negative_prompt使用:

output_image = pipeline( prompt=target_prompt, mask_image=mask_image, image_latents=inv_latents, negative_prompt=source_prompt, ).images[0] mask_image = Image.fromarray((mask_image.squeeze()*255).astype("uint8"), "L").resize((768, 768)) make_image_grid([raw_image, mask_image, output_image], rows=1, cols=3)

将原始图、掩码、编辑结果拼成三格图即可直观对比。__call__的去噪循环中,掩码通过latents = latents * mask_image + image_latents[i] * (1 - mask_image)(第 1516 行)起作用:掩码为 1(白色)的区域被重绘,掩码为 0(黑色)的区域保留原始图像内容。输入的mask_image会在preprocess_mask中被转为单通道张量、校验数值在 [0,1] 区间,并以 0.5 为阈值二值化(第 237-239 行)。

进阶一:用 Flan-T5 自动生成 source/target 文本并编码为嵌入

手工编写source_prompt/target_prompt之外,还可以借助 Flan-T5 语言模型自动生成一批候选描述,再通过文本编码器聚合成嵌入向量,实现"给一个概念词,自动完成语义编辑"的流程。

加载 Flan-T5 模型与分词器

import torch from transformers import AutoTokenizer, T5ForConditionalGeneration tokenizer = AutoTokenizer.from_pretrained("google/flan-t5-large") model = T5ForConditionalGeneration.from_pretrained("google/flan-t5-large", device_map="auto", dtype=torch.float16)

构造生成提示词

给定 source 概念(原图中的物体)与 target 概念(目标物体),构造"为包含某物体的图片生成英文描述(不超过 150 字符)"的指令式文本:

source_concept = "bowl" target_concept = "basket" source_text = f"Provide a caption for images containing a {source_concept}. " "The captions should be in English and should be no longer than 150 characters." target_text = f"Provide a caption for images containing a {target_concept}. " "The captions should be in English and should be no longer than 150 characters."

批量采样候选描述

定义生成函数,通过temperature、top_k、do_sample控制生成策略的随机性与多样性(关于各类采样策略的取舍,可参考 Transformers 文档中的 generation strategies 指南):

@torch.no_grad() def generate_prompts(input_prompt): input_ids = tokenizer(input_prompt, return_tensors="pt").input_ids.to("cuda") outputs = model.generate( input_ids, temperature=0.8, num_return_sequences=16, do_sample=True, max_new_tokens=128, top_k=10 ) return tokenizer.batch_decode(outputs, skip_special_tokens=True) source_prompts = generate_prompts(source_text) target_prompts = generate_prompts(target_text) print(source_prompts) print(target_prompts)

用管线的文本编码器编码嵌入

复用StableDiffusionDiffEditPipeline自带的 CLIP 分词器与文本编码器(pipeline.tokenizer/pipeline.text_encoder),将每个候选描述编码后取平均,得到单个代表嵌入:

import torch from diffusers import StableDiffusionDiffEditPipeline pipeline = StableDiffusionDiffEditPipeline.from_pretrained( "stabilityai/stable-diffusion-2-1", dtype=torch.float16, use_safetensors=True ) pipeline.enable_model_cpu_offload() pipeline.vae.enable_slicing() @torch.no_grad() def embed_prompts(sentences, tokenizer, text_encoder, device="cuda"): embeddings = [] for sent in sentences: text_inputs = tokenizer( sent, padding="max_length", max_length=tokenizer.model_max_length, truncation=True, return_tensors="pt", ) text_input_ids = text_inputs.input_ids prompt_embeds = text_encoder(text_input_ids.to(device), attention_mask=None)[0] embeddings.append(prompt_embeds) return torch.concatenate(embeddings, dim=0).mean(dim=0).unsqueeze(0) source_embeds = embed_prompts(source_prompts, pipeline.tokenizer, pipeline.text_encoder) target_embeds = embed_prompts(target_prompts, pipeline.tokenizer, pipeline.text_encoder)

用嵌入替换文本参数

最终将嵌入传给generate_mask、invert与管线主入口。注意source_embeds在反演阶段作为prompt_embeds,在主入口中作为negative_prompt_embeds;target_embeds则作为prompt_embeds:

from diffusers import DDIMInverseScheduler, DDIMScheduler from diffusers.utils import load_image, make_image_grid from PIL import Image pipeline.scheduler = DDIMScheduler.from_config(pipeline.scheduler.config) pipeline.inverse_scheduler = DDIMInverseScheduler.from_config(pipeline.scheduler.config) img_url = "https://github.com/Xiang-cd/DiffEdit-stable-diffusion/raw/main/assets/origin.png" raw_image = load_image(img_url).resize((768, 768)) mask_image = pipeline.generate_mask( image=raw_image, source_prompt_embeds=source_embeds, target_prompt_embeds=target_embeds, ) inv_latents = pipeline.invert( prompt_embeds=source_embeds, image=raw_image, ).latents output_image = pipeline( mask_image=mask_image, image_latents=inv_latents, prompt_embeds=target_embeds, negative_prompt_embeds=source_embeds, ).images[0] mask_image = Image.fromarray((mask_image.squeeze()*255).astype("uint8"), "L") make_image_grid([raw_image, mask_image, output_image], rows=1, cols=3)

补充说明:prompt与prompt_embeds是互斥参数,两者同时传入会触发源码check_inputs中的ValueError;直接传入嵌入时,generate_mask会以num_maps_per_mask作为encode_prompt的num_images_per_prompt对嵌入做 batch 扩展,因此手工构造嵌入时务必保证source_embeds与target_embeds形状一致(check_source_inputs会校验)。

进阶二:用 BLIP 自动生成反演用的图片描述

invert需要一个能描述原图的 caption 来引导反演采样。除了直接用source_prompt,也可以用 BLIP 图像描述模型自动生成——让"图片理解"也自动化。

加载 BLIP 模型与处理器

import torch from transformers import BlipForConditionalGeneration, BlipProcessor processor = BlipProcessor.from_pretrained("Salesforce/blip-image-captioning-base") model = BlipForConditionalGeneration.from_pretrained("Salesforce/blip-image-captioning-base", dtype=torch.float16, low_cpu_mem_usage=True)

定义生成 caption 的工具函数

generate_caption以"a photograph of"作为前缀提示,生成后立即把模型移回 CPU,避免长期占用显存:

@torch.no_grad() def generate_caption(images, caption_generator, caption_processor): text = "a photograph of" inputs = caption_processor(images, text, return_tensors="pt").to(device="cuda", dtype=caption_generator.dtype) caption_generator.to("cuda") outputs = caption_generator.generate(**inputs, max_new_tokens=128) # 将 caption generator 移回 CPU caption_generator.to("cpu") caption = caption_processor.batch_decode(outputs, skip_special_tokens=True)[0] return caption

生成 caption 并用于反演

from diffusers.utils import load_image img_url = "https://github.com/Xiang-cd/DiffEdit-stable-diffusion/raw/main/assets/origin.png" raw_image = load_image(img_url).resize((768, 768)) caption = generate_caption(raw_image, model, processor)

对示例原图,BLIP 生成的描述约为 "a photograph of a bowl of fruit on a table"(一碗摆在桌上的水果)。随后把该 caption 传给invert即可获得部分反演的 latents:

inv_latents = pipeline.invert(prompt=caption, image=raw_image).latents

关键参数速查与调参建议

以下参数均以源码中的默认值与校验逻辑为准:

参数所属方法默认值含义与取值范围
source_prompt/target_promptgenerate_mask必填描述"原图现状"与"编辑目标",二选一传入字符串或对应*_prompt_embeds
num_maps_per_maskgenerate_mask10用于生成语义掩码的噪声图数量,必须是正整数;越多掩码越平滑,但计算量线性增长
mask_encode_strengthgenerate_mask0.5掩码生成时对图像添加噪声的强度,取值 [0, 1],同时充当check_inputs中的strength校验
mask_thresholding_ratiogenerate_mask3.0语义引导图钳制上限 = 均值 × 该倍数,必须为正;越大掩码区域越小(更保守)
num_inference_steps三个方法50去噪/反演步数,越多质量越高但越慢
guidance_scale三个方法7.5无分类器引导强度,> 1时启用 CFG(源码中do_classifier_free_guidance = guidance_scale > 1.0)
inpaint_strengthinvert/__call__0.8反演/重绘的噪声化程度,[0, 1];1 表示跑满全部时间步,0 表示不修复
prompt/negative_promptinvert/__call__—反演时 prompt 通常为原图 caption;主入口中 prompt 为目标文本、negative_prompt 为 source 文本
eta__call__0.0DDIM 的 η 参数(仅DDIMScheduler生效,源码prepare_extra_step_kwargs会按调度器签名注入)
lambda_auto_corr/lambda_kl/num_reg_steps/num_auto_corr_rollsinvert20.0 / 20.0 / 0 / 5Pix2PixZero 风格的反演噪声正则化,非论文原生内容,一般保持默认
output_type三个方法"np"/"pil"generate_mask默认输出 numpy 数组,invert与__call__默认输出 PIL

调参直觉:

  • 掩码过大/过小:mask_thresholding_ratio调大→掩码更小(只改差异最明显的区域);num_maps_per_mask增大可抑制掩码噪声抖动。
  • 编辑强度:主入口的inpaint_strength决定重绘区域"被改得多彻底",值越低越接近原图。
  • 编辑保真度:guidance_scale越大越贴合target_prompt,但可能牺牲图像质量;negative_prompt=source_prompt本身就在引导模型"不要保持原样"。

注意事项

  1. 管线状态:StableDiffusionDiffEditPipeline在仓库中被归入 src/diffusers/pipelines/deprecated/stable_diffusion_diffedit/,属于实验性且已标记弃用的特性(_last_supported_version = "0.33.1"),在较新版本中可能被移除,生产使用前请核对 diffusers 版本。
  2. 输入约束:图片宽高需为 8 的整数倍;mask_image必须是单通道(L)且数值落在 [0,1],源码会以 0.5 阈值强制二值化。
  3. 参数互斥:prompt与prompt_embeds、negative_prompt与negative_prompt_embeds不可同时传入,且直接传入的prompt_embeds与negative_prompt_embeds形状必须一致。
  4. 显存策略:整套流程包含掩码生成、反演、重绘三次前向,建议开启enable_model_cpu_offload()与vae.enable_slicing();BLIP 生成 caption 后记得把模型移回 CPU(文档示例中已示范)。
  5. 安全合规:safety_checker=None仅建议在分析、审计等场景使用;面向公众的服务应保持安全过滤器开启,并遵守 Stable Diffusion 模型许可条件。

至此,你已经掌握 DiffEdit 从"两个文本提示"到"自动掩码 + 反演 + 局部重绘"的完整链路,并具备用 Flan-T5 自动生成提示、用 BLIP 自动生成 caption 的全自动化扩展能力——整个流程无需任何图像编辑软件介入。

【免费下载链接】diffusers🤗 Diffusers: State-of-the-art diffusion models for image, video, and audio generation in PyTorch.项目地址: https://gitcode.com/GitHub_Trending/di/diffusers

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询