扩散模型在图像编辑思路《paint by example、IP-Adapter、anydoor》

摘要

本周对比了《Paint by Example》和《IP-Adapter》两篇文章,分析它们在图像编辑与扩散模型中的创新设计。《Paint by Example》提出了图像示例引导的编辑任务,通过引入信息瓶颈和自监督策略,提升了编辑的自然性和准确性;《IP-Adapter》则以轻量级模块,实现了图像提示与文本提示在扩散模型中的无缝结合,兼容ControlNet等结构控制工具。在此基础上,结合两者优势,思考了在时尚图像编辑领域的应用方向。同时,还学习了《AnyDoor: Zero-shot Object-level Image Customization》论文,了解了通过ID和细节特征提取,支持零样本下灵活的目标编辑,为未来的服装换装、虚拟试穿等应用提供了重要思路。

abstract

This week, two articles, “Paint by Example” and “IP-Adapter”, were compared to analyze their innovative designs in image editing and diffusion models. “Paint by Example” proposes an editing task guided by image examples. By introducing information bottlenecks and self-supervised strategies, it enhances the naturalness and accuracy of editing. “IP-Adapter” realizes the seamless integration of image prompts and text prompts in the diffusion model with a lightweight module and is compatible with structural control tools such as ControlNet. On this basis, combining the advantages of both, the application directions in the field of fashion image editing were considered. Meanwhile, the paper “AnyDoor: Zero-shot Object-level Image Customization” was also studied, and it was understood that through ID and detail feature extraction, flexible target editing under zero-shot conditions is supported, providing important ideas for future applications such as clothing dress-up and virtual try-on.

1.两篇文章的创新点

Paint by Example

  1. 提出图像示例引导的语义图像合成任务,填补了text-to-image之外的编辑方式;
  2. 引入信息瓶颈机制防止trival copy;
  3. 利用自监督训练方式与随机mask配合,有效解决了train-test domain gap;
  4. 可调用mask形状与相似度控制机制,增强用户编辑自由度。

IP-Adapter

  1. 创新性提出解耦交叉注意力机制,分别处理图像和文本特征,克服了传统adapter融合效果差的问题;
  2. 模块极轻量,支持plug-and-play;
  3. 支持任意的衍生扩散模型结合,保持风格一致性;
  4. 完美兼容ControlNet等结构控制工具,支持多模态生成。

2.在方法上的差异

方面Paint by ExampleIP-Adapter
条件输入图像 + mask图像 + 可选文本
架构基础Diffusion model(Stable Diffusion)基于Stable Diffusion
模型修改利用CLIP图像嵌入,仅用class token并配合信息瓶颈设计引入 解耦交叉注意力(Decoupled Cross-Attention) 模块处理图像特征,原模型冻结
训练方式自监督(利用图像自身合成“参考-源”对)使用图文对训练,仅训练22M参数的小模块
控制能力提供mask形状控制、相似度控制(classifier-free guidance)可控制图像与文本融合程度,可与结构控制模块(如ControlNet)组合
输出特性单步推理、高质量、可调编辑区域多模态输入,风格多样,可在多模型上泛化应用

3.个人思考

通过上述这两篇论文,想利用这两个方法结合到自己的方向上,以下是个人的思考:
利用Paint by Example 的优势

  1. 服装区域编辑控制可以借助其任意形状mask机制;
  2. 使用信息瓶颈机制和自监督训练策略,能帮助提升在自己的模型上无监督或少标注数据下的学习效果;
  3. 对于通用分割模型协同策略,可以借鉴source image+examplar image构成方式,实现风格转移与内容融合。

融合IP-Adapter的优势
4. IP-Adapter的模块化适配策略可以集成在扩散模型编辑管线中,不破坏原模型;
5. 解耦注意力机制可用于服装图像提示的精细风格迁移;
6. 兼容ControNet/T2I-Adapter可以实现服装编辑+人体姿态控制的高级联动。

4.视觉AI任意门

本周阅读的论文《AnyDoor: Zero-shot Object-level Image Customization》,实现了零样本的图像嵌入,主要功能是“图像传送”,点击两下鼠标,就能把物体无缝传送到照片场景中,光线角度和透视也能自动适应。例如,将女生的蓝色短袖换成其他样式的红色衣服。所以。有了它,网购衣服也可以直接看上身效果了。
在这里插入图片描述

论文摘要

anydoor将目标物体以和谐的方式传送到用户指定的位置的新的场景,模型只训练一次,而不是为每个对象调优参数,在推理阶段轻松地推广到不同的对象场景组合。这样具有挑战性的零样本设置需要对某个对象进行充分的表征。为此,使用细节特征补充了常用的身份特征,这些细节特征经过精心设计,以保持纹理细节,但允许灵活的局部变化(例如,照明、方向、姿势等),支持对象与不同的环境良好地融合。

工作原理

在这里插入图片描述

  1. AnyDoor目的是将对象传送到用户指定位置的场景。首先采用分割模块从对象中删除背景,然后使用ID提取器获取其身份信息。然后,我们对“干净”的对象应用高通滤波器,将所得的高频图(HF-Map)与期望位置的场景拼接起来,并使用细节提取器以纹理细节补充ID提取器。最后,将ID标记和细节图注入预训练的扩散模型,以产生最终的合成,其中目标对象与其周围环境良好地融合,但具有良好的局部变化。火焰和雪花分别指可学习和冻结的参数。
  2. 要想实现物体的传送,首先就要对其进行提取。不过在将包含目标物体的图像送入提取器之前,AnyDoor首先会对其进行背景消除。然后,AnyDoor会进行自监督式的物体提取并转换成token。这一步使用的编码器是以目前最好的自监督模型DINO-V2为基础设计的。为了适应角度和光线的变化,除了提取物品的整体特征,还需要额外提取细节信息。这一步中,为了避免过度约束还设计了一种用高频图表示特征信息的方式。
    在这里插入图片描述
    ID提取器是一种专注于焦点区域的视觉细节提取器。"Attention"指的是用于该ID(DINO-V2)的注意力图提取器的骨干部分,而"HF-Map"则指用于细节提取器中使用的高频率图。这两个模块侧重于互补的全局和局部信息。 最后一步就是将这些信息进行注入。利用获取到的token,AnyDoor通过文生图模型对图像进行合成。具体来说,AnyDoor使用的是带有ControlNet的Stable Diffusion。

实验代码
下面这段代码主要管理潜空间分布,支持扩散模型的采样、KL正则计算、推理输出,起到了连接Encoder、Decoder和训练目标的关键作用。

class AbstractDistribution:
    def sample(self):
        raise NotImplementedError()

    def mode(self):
        raise NotImplementedError()


class DiracDistribution(AbstractDistribution):
    def __init__(self, value):
        self.value = value

    def sample(self):
        return self.value

    def mode(self):
        return self.value


class DiagonalGaussianDistribution(object):
    def __init__(self, parameters, deterministic=False):
        self.parameters = parameters
        self.mean, self.logvar = torch.chunk(parameters, 2, dim=1)
        self.logvar = torch.clamp(self.logvar, -30.0, 20.0)
        self.deterministic = deterministic
        self.std = torch.exp(0.5 * self.logvar)
        self.var = torch.exp(self.logvar)
        if self.deterministic:
            self.var = self.std = torch.zeros_like(self.mean).to(device=self.parameters.device)

    def sample(self):
        x = self.mean + self.std * torch.randn(self.mean.shape).to(device=self.parameters.device)
        return x

    def kl(self, other=None):
        if self.deterministic:
            return torch.Tensor([0.])
        else:
            if other is None:
                return 0.5 * torch.sum(torch.pow(self.mean, 2)
                                       + self.var - 1.0 - self.logvar,
                                       dim=[1, 2, 3])
            else:
                return 0.5 * torch.sum(
                    torch.pow(self.mean - other.mean, 2) / other.var
                    + self.var / other.var - 1.0 - self.logvar + other.logvar,
                    dim=[1, 2, 3])

    def nll(self, sample, dims=[1,2,3]):
        if self.deterministic:
            return torch.Tensor([0.])
        logtwopi = np.log(2.0 * np.pi)
        return 0.5 * torch.sum(
            logtwopi + self.logvar + torch.pow(sample - self.mean, 2) / self.var,
            dim=dims)

    def mode(self):
        return self.mean


def normal_kl(mean1, logvar1, mean2, logvar2):
    """
    source: https://github.com/openai/guided-diffusion/blob/27c20a8fab9cb472df5d6bdd6c8d11c8f430b924/guided_diffusion/losses.py#L12
    Compute the KL divergence between two gaussians.
    Shapes are automatically broadcasted, so batches can be compared to
    scalars, among other use cases.
    """
    tensor = None
    for obj in (mean1, logvar1, mean2, logvar2):
        if isinstance(obj, torch.Tensor):
            tensor = obj
            break
    assert tensor is not None, "at least one argument must be a Tensor"

    # Force variances to be Tensors. Broadcasting helps convert scalars to
    # Tensors, but it does not work for torch.exp().
    logvar1, logvar2 = [
        x if isinstance(x, torch.Tensor) else torch.tensor(x).to(tensor)
        for x in (logvar1, logvar2)
    ]

    return 0.5 * (
        -1.0
        + logvar2
        - logvar1
        + torch.exp(logvar1 - logvar2)
        + ((mean1 - mean2) ** 2) * torch.exp(-logvar2)
    )

ClassEmbedder主要是做类别控制的,比如生成狗/猫/车,不给类别也可以自由生成(无条件版本)。
🔹 同时支持dropout机制让模型学会无条件生成。

class ClassEmbedder(nn.Module):
    def __init__(self, embed_dim, n_classes=1000, key='class', ucg_rate=0.1):
        super().__init__()
        self.key = key
        self.embedding = nn.Embedding(n_classes, embed_dim)
        self.n_classes = n_classes
        self.ucg_rate = ucg_rate

    def forward(self, batch, key=None, disable_dropout=False):
        if key is None:
            key = self.key
        # this is for use in crossattn
        c = batch[key][:, None]
        if self.ucg_rate > 0. and not disable_dropout:
            mask = 1. - torch.bernoulli(torch.ones_like(c) * self.ucg_rate)
            c = mask * c + (1-mask) * torch.ones_like(c)*(self.n_classes-1)
            c = c.long()
        c = self.embedding(c)
        return c

    def get_unconditional_conditioning(self, bs, device="cuda"):
        uc_class = self.n_classes - 1  # 1000 classes --> 0 ... 999, one extra class for ucg (class 1000)
        uc = torch.ones((bs,), device=device) * uc_class
        uc = {self.key: uc}
        return uc


def disabled_train(self, mode=True):
    """Overwrite model.train with this function to make sure train/eval mode
    does not change anymore."""
    return self

FrozenT5Embedder主要是把文字提示词(prompt)转成稳定的、不可学习的条件向量,供后续模块(比如扩散U-Net)使用。
🔹 因为冻结住了,不会因为少量任务数据而导致预训练文本特征崩坏。

class FrozenT5Embedder(AbstractEncoder):
    """Uses the T5 transformer encoder for text"""
    def __init__(self, version="google/t5-v1_1-large", device="cuda", max_length=77, freeze=True):  # others are google/t5-v1_1-xl and google/t5-v1_1-xxl
        super().__init__()
        self.tokenizer = T5Tokenizer.from_pretrained(version)
        self.transformer = T5EncoderModel.from_pretrained(version)
        self.device = device
        self.max_length = max_length   # TODO: typical value?
        if freeze:
            self.freeze()

    def freeze(self):
        self.transformer = self.transformer.eval()
        #self.train = disabled_train
        for param in self.parameters():
            param.requires_grad = False

    def forward(self, text):
        batch_encoding = self.tokenizer(text, truncation=True, max_length=self.max_length, return_length=True,
                                        return_overflowing_tokens=False, padding="max_length", return_tensors="pt")
        tokens = batch_encoding["input_ids"].to(self.device)
        outputs = self.transformer(input_ids=tokens)

        z = outputs.last_hidden_state
        return z

    def encode(self, text):
        return self(text)

FrozenCLIPEmbedder 是一个 固定的(冻结的)CLIP文本编码器,
它的功能是:把输入的自然语言文本 prompt,通过CLIP模型处理,转成向量表征(embedding),用于后续图像生成(如扩散模型U-Net中的Cross-Attention)。

class FrozenCLIPEmbedder(AbstractEncoder):
    """Uses the CLIP transformer encoder for text (from huggingface)"""
    LAYERS = [
        "last",
        "pooled",
        "hidden"
    ]
    def __init__(self, version="openai/clip-vit-large-patch14", device="cuda", max_length=77,
                 freeze=True, layer="last", layer_idx=None):  # clip-vit-base-patch32
        super().__init__()
        assert layer in self.LAYERS
        self.tokenizer = CLIPTokenizer.from_pretrained(version)
        self.transformer = CLIPTextModel.from_pretrained(version)
        self.device = device
        self.max_length = max_length
        if freeze:
            self.freeze()
        self.layer = layer
        self.layer_idx = layer_idx
        if layer == "hidden":
            assert layer_idx is not None
            assert 0 <= abs(layer_idx) <= 12

    def freeze(self):
        self.transformer = self.transformer.eval()
        #self.train = disabled_train
        for param in self.parameters():
            param.requires_grad = False

    def forward(self, text):
        batch_encoding = self.tokenizer(text, truncation=True, max_length=self.max_length, return_length=True,
                                        return_overflowing_tokens=False, padding="max_length", return_tensors="pt")
        tokens = batch_encoding["input_ids"].to(self.device)
        outputs = self.transformer(input_ids=tokens, output_hidden_states=self.layer=="hidden")
        if self.layer == "last":
            z = outputs.last_hidden_state
        elif self.layer == "pooled":
            z = outputs.pooler_output[:, None, :]
        else:
            z = outputs.hidden_states[self.layer_idx]
        return z

    def encode(self, text):
        return self(text)

5.论文结论

AnyDoor模型主要用于一键换脸/换衣、虚拟试穿、在线PS等业务场景。可以让很多不懂技术的电商卖家,也能实现专业PS的功能。但目前效果还略微粗糙,需要继续精雕细琢。另外交互对用户来说还不是特别方便,相信AnyDoor一定也会进一步的优化。

6.总结

通过本周对《Paint by Example》和《IP-Adapter》的深入对比学习,理解了在扩散模型中提升图像编辑灵活性与控制精度的不同路径。《Paint by Example》强调以图像示例为引导,优化局部编辑自然性,而《IP-Adapter》则以模块化、轻量的方式实现了多模态输入的无缝结合。结合两者特点,对个人时尚编辑方向启发巨大,计划融合mask控制与多模态提示,提升服装编辑效果。同时阅读了《AnyDoor》,了解了零样本对象迁移的细节处理技术,为实现更智能、用户友好的图像编辑系统奠定了基础。

参考文献

https://arxiv.org/abs/2211.13227
https://arxiv.org/pdf/2308.06721.pdf
https://arxiv.org/abs/2307.09481

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值