4、解构三个重要的Pipeline(SD-Inpainting, ControlNet, AnimateDiff) [代码级手把手解析diffusers库]


上一篇我们解析了所有Pipeline的基类 DiffusionPipeline。后续各种各样的pipeline都继承了DiffusionPipeline的模型加载保存等功能,然后再配合各个组件实现各种的结构即可。

在这里插入图片描述

事实上,一个Pipeline通常包含了如下模块(from_pretrained函数根据model_index.json文件new了一个Pipeline,挨个扫描子文件夹创建模块加载对应的weight和config):

  • VAE,即变分自编码器,把图像编码到特征,进行生成过程后再把特征解码到图像。(有weight)
  • UNet,用于迭代采样预测噪声的模型。(有weight)
  • Text Encoder,用于把tokens编码为一串向量,用来控制扩散模型的生成。(有weight)
  • Tokenizer,把输入的文本按照字典编码为上面的tokens。
  • Scheduler,我们知道扩散模型有很多采样方法,Scheduler定义了我们用哪种采样方法
  • Safety_checker,NSFW检测器,很多人应该都不想要这个,可以去掉。
  • Feature_extractor,也是NSFW检测器的一部分,也可以去掉。
  • Image Encoder,如果有image作为条件就会需要,计算image embedding,用来控制扩散模型的生成(有weight)
  • Image Processor,配合Image Encoder,在计算emebedding之前进行一些数据增强。
  • ControlNet,如果使用各种control condition就会需要,本质是半个UNet,用于计算condition_images的feature 融合到UNet中(有weight)

本节我将带领大家解构diffusers中3个我觉得最有学习意义的Pipeline:

  • SD-InpaintingStableDiffusionInpaintPipeline
  • ControlNetStableDiffusionControlNetPipeline
  • AnimateDiffAnimateDiffPipeline

理解了上方这几个Pipeline,我们后续就很好对任意的pipeline进行修改和自定义了,无论是需要图像生成结构控制,还是局部重绘,亦或是需要视频生成一致性控制,都可以有一个很好的理解。

对于每个pipeline,我将按照如下方式展开:

  • 首先,进行组件的介绍__init__
  • 然后,按照pipeline的执行顺序__call__,分模块的讲解实现代码和原理。

【注意:SD作为经典的Pipeline我放在前面讲解,后面的Pipeline的某些代码,如果和SD相同,我将不再重复讲解,如果有细微差别,我会专门讲解】

题外话:我们看那典中典的五行代码,最后一行pipeline(xxx),代表着我们用这个对象的名称作为一个函数的调用,那么这种用法就会自动调用StableDiffusionPipeline类中的__call__()函数。事实上,Diffusers库中的大多数类我们首先就要看__init__()函数和__call__()函数。

StableDiffusionInpaintPipeline

Inpaint是一项图片局部重绘技术,可以从图片上去除不必要的物体,让您轻松摆脱照片上的水印、划痕、污渍、标志等瑕疵。

一般来讲,图片的inpaint过程可以理解为两步:
1、找到图片中的需要重绘的部分,比如上述提到的水印、划痕、污渍、标志等。
2、自动填充图片重绘区域应该有的内容,如去掉水印、划痕、污渍、标志等。

组件:

vae: Union[AutoencoderKL, AsymmetricAutoencoderKL],
text_encoder: CLIPTextModel,
tokenizer: CLIPTokenizer,
unet: UNet2DConditionModel,
scheduler: KarrasDiffusionSchedulers,
safety_checker: StableDiffusionSafetyChecker,
feature_extractor: CLIPImageProcessor,
image_encoder: CLIPVisionModelWithProjection = None,

加载模型

根据上节可知,StableDiffusionInpaintPipeline调用from_pretrained依次加载各个模块,再最后执行__init__()函数:这一段把所有模块组合起来,并为pipeline注册对应的配置信息(一个FrozenDictself._internal_dict中)。

	def __init__(
        self,
        vae: Union[AutoencoderKL, AsymmetricAutoencoderKL],
        text_encoder: CLIPTextModel,
        tokenizer: CLIPTokenizer,
        unet: UNet2DConditionModel,
        scheduler: KarrasDiffusionSchedulers,
        safety_checker: StableDiffusionSafetyChecker,
        feature_extractor: CLIPImageProcessor,
        image_encoder: CLIPVisionModelWithProjection = None,
        requires_safety_checker: bool = True,
    ):
        super().__init__()

		# 中间是对safety_checker的警告,可以直接无视,
		
        self.register_modules(
            vae=vae,
            text_encoder=text_encoder,
            tokenizer=tokenizer,
            unet=unet,
            scheduler=scheduler,
            safety_checker=safety_checker,
            feature_extractor=feature_extractor,
            image_encoder=image_encoder,
        )

		# vae的缩放系数,以及vae的图像预处理操作
        self.vae_scale_factor = 2 ** (len(self.vae.config.block_out_channels) - 1)
        self.image_processor = VaeImageProcessor(vae_scale_factor=self.vae_scale_factor)
        self.mask_processor = VaeImageProcessor(
            vae_scale_factor=self.vae_scale_factor, do_normalize=False, do_binarize=True, do_convert_grayscale=True
        )
        self.register_to_config(requires_safety_checker=requires_safety_checker)

这里的所有模块已经new好并已经读取了对应的模型文件(由DiffusionPipeline.from_pretrained实现)

register_to_config完成模型参数的配置(维护一个字典_internal_dict),我们可以打印pipeline._internal_dict查看配置信息:

	FrozenDict([('vae', ('diffusers', 'AutoencoderKL')),
            ('text_encoder', ('transformers', 'CLIPTextModel')),
            ('tokenizer', ('transformers', 'CLIPTokenizer')),
            ('unet', ('diffusers', 'UNet2DConditionModel')),
            ('scheduler', ('diffusers', 'PNDMScheduler')),
            ('safety_checker',
             ('stable_diffusion', 'StableDiffusionSafetyChecker')),
            ('feature_extractor', ('transformers', 'CLIPImageProcessor')),
            ('requires_safety_checker', True)])

其中对于difusers库的组件(vae、unet、scheduler),我们也可以查看它们的_internal_dict,内部保存了模型的结构和配置参数,如vae._internal_dict

FrozenDict([('in_channels', 3),
            ('out_channels', 3),
            ('down_block_types',
             ['DownEncoderBlock2D',
              'DownEncoderBlock2D',
              'DownEncoderBlock2D',
              'DownEncoderBlock2D']),
            ('up_block_types',
             ['UpDecoderBlock2D',
              'UpDecoderBlock2D',
              'UpDecoderBlock2D',
              'UpDecoderBlock2D']),
            ('block_out_channels', [128, 256, 512, 512]),
            ('layers_per_block', 2),
            ('act_fn', 'silu'),
            ('latent_channels', 4),
            ('norm_num_groups', 32),
            ('sample_size', 512),
            ('_class_name', 'AutoencoderKL'),
            ('_diffusers_version', '0.6.0'),
            ('_name_or_path',
             '/data1/huggingface/StableDiffusion/stable-diffusion-v1-5/vae')])

此Pipe继承自 DiffusionPipeline。除此之外Pipe还继承了以下加载方法:

  • load_textual_inversion() 用于加载文本反转嵌入
  • load_lora_weights() 用于加载 LoRA 权重
  • save_lora_weights() 用于节省 LoRA 权重
  • load_ip_adapter() 用于加载 IP 适配器
  • from_single_file() 用于加载文件.ckpt

Pipe推理流程

如何做到这一点呢?我们需要结合img2img方法,我们首先考虑inpaint的两个输入:一个是原图,另外一个是mask图
在这里插入图片描述
Stable Diffusion中的inpaint的实现方式有两种,由num_channels_unet = self.unet.config.in_channels参数决定:

  • 重新训练in_channel=9的UNet:只需要更改unet的输入channel=9=(4+4+1),重新训练,直接将(4通道的noise_latent 、 4通道的masked_image_latent 、1通道的mask_latent )三者进行concat送入unet即可。
  • 利用任意预训练好的in_channel=4的UNet:让Stable Diffusion只生成指定区域,并且在生成指定区域的时候参考其它区域。在图像重建的20步中,对于每个timestep送入unet的latent特征 n e w L a t e n t = m a s k ∗ i m a g e L a t e n t + ( 1 − m a s k ) ∗ o l d L a t e n t new Latent = mask*imageLatent + (1-mask)*oldLatent newLatent=maskimageLatent+(1mask)oldLatent,我们利用mask将不重建的地方都替换成 原图按照当前步数加噪后的latent特征(此时不重建的地方的特征都由输入图片决定),然后不替换需要重建区域的latent利用unet计算噪声更新需要重建的地方
    在这里插入图片描述
    在这里插入图片描述

Pipeline的__call__函数就是整个生成推理环节的代码:

	@torch.no_grad()
    def __call__(
        self,
        prompt: Union[str, List[str]] = None,
        image: PipelineImageInput = None,
        mask_image: PipelineImageInput = None,
        masked_image_latents: torch.FloatTensor = None,
        height: Optional[int] = None,
        width: Optional[int] = None,
        padding_mask_crop: Optional[int] = None,
        strength: float = 1.0,
        num_inference_steps: int = 50,
        timesteps: List[int] = None
  • 17
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
我很抱歉,作为AI语言模型,我无法实现代码复现。但是,我可以提供以下步骤来帮助您实现图像修复(image inpainting): 1. 收集数据集:首先,您需要收集一些包含缺失区域的图像。您可以使用任何数据集,例如CelebA,COCO等。然后,您需要使用图像编辑工具(例如Photoshop)创建一些缺失区域,以便进行修复。 2. 加载数据集并准备数据:将数据集加载到您的开发环境中,并将其准备好使用。您需要将图像裁剪为相同的大小,并将其转换为您的模型可以接受的格式。 3. 建立模型:您可以使用任何深度学习框架(例如TensorFlow,PyTorch等)来建立您的模型。您可以使用卷积神经网络(CNN)或生成对抗网络(GAN)等架构来构建模型。 4. 训练模型:使用准备好的数据集和模型,开始训练模型。您需要确定训练模型的超参数,例如学习率,批量大小,迭代次数等。您可以使用交叉验证技术来评估模型的性能。 5. 测试模型:在训练模型之后,您需要测试模型的性能。您可以使用测试数据集来评估模型的性能,并查看模型是否能够准确地修复缺失区域。 6. 优化模型:如果您的模型的性能不是很好,您可以尝试使用其他超参数或尝试使用不同的架构来优化模型。 总之,图像修复是一个复杂的过程,需要大量的数据集和模型训练。通过尝试不同的模型和超参数,您可以找到最适合您数据集和任务的模型。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Yuezero_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值