音视频开发之旅(102)-ID一致性之PuLID原理及PuLID_Flux应用

目录

1. 背景与问题

2. PuLID网络结构和原理

3. 效果对比与应用场景

4. ComfyUI_Pulid_Flux的应用实践

5. 源码分析

6. 资料

一. 背景与问题

在文生图(Text-to-Image,T2I)领域,保证人物ID的一致性,是一个关键技术,可以通过lora进行微调训练,但要针对每个人物进行训练成本还是比较高,ipadapter和InstantID在人物一致性方面取得了很好的进展,但是插入ID身份信息时往往会干扰原始模型的行为,导致生成的图像在风格,布局等方面和原始模型生成的图片有较大的差异.

字节团队提出了一种PuLID(Pure and Lightning ID Customization via Contrastive Alignment)的方案,通过对比对齐实现纯粹且快速的ID一致性,主要的亮点:

  • 增加了对比对齐loss和ID loss,最大限度减少对原始模型的破坏并保证ID的一致性

  • 提示词可以很好的引导生成过程

图片

二. PuLID网络结构和原理

2.1.PuLID网络结构

图片

双分支训练框架:PuLID采用了一个结合了常规的扩散模型分支(Conventional Diffusion branch)和快速文生图分支(Lightning T2I branch)的训练框架,通过对齐损失和精确的ID损失,减少对原始模型图像生成过程的干扰,同时实现精准的身份ID定制

ID Encoder:使用Arcface(对人脸进行识别特征提取) 和Clip-VIT(对图像特征提取)对采集的1.5M人像数据进行特征提取和MLP影射拼接,作为condition信息,指导模型生成和condition人脸相似的图像.

对比对齐(Contrastive alignment): 构建两条生成路径(具有相同的prompt和latent初始条件,差异点在于一条保护ID嵌入,一条不包含),使用对齐损失(alignment Loss)来语义和布局上对齐两条路径的UNet特征,指导模型在不干扰原始模型行为(保持风格,布局的一致性)的情况下嵌入ID信息.

精确ID损失(Accurate ID Loss):在ID插入后,PuLID从生成的图像(predict x0)提取提取人脸特征,和真实的人脸特征(C_id)计算ID损失,确保生成的图像在身份特征上的高保真度.

模型总的学习目标:   扩散模型Loss + 对齐Loss(语义对齐和布局对齐)+ID Loss,训练出生成ID一致性的高质量图像的模型.

图片

下面我们详细介绍下每个Loss

2.2 扩散模型的Loss

扩散过程使用噪声调度器对噪声ε进行采样添加到原始数据x_0上,在时间步t生成一个x_t噪声样本;去噪过程使用由残差网络,自注意力机制和交叉注意力机制组成的UNet去噪模型ε_θ,将x_t,t和可选的附加条件C作为输入来预测噪声,对应公式如下:

图片

图片

2.3 对齐loss

对齐损失由语义对齐损失(Lalign-sem)和布局对齐损失(Lalign-layout)构成

图片

2.3.1 语义对齐loss

Qt:不带ID embeding的UNet features

Qtid:带ID embeding的Unet features

是一个Attention(K,Q,Q),两个路径的插值越小,ID嵌入对原始模型的Unet的影响越小

图片

2.3.2 布局对齐loss

Lalign-sem语义损失显著的减轻了ID信息破坏原始模型行为的问题,但是它不能保证布局的一致性,为此添加一个布局对其损失Lalgin_layout. 通过计算Qt和Qtid的loss,使得图像布局上保持原始模型的特征

图片

图片

2.4 ID loss

通过余弦相似性计算生成图像和真实图像Cid之间的ID特征的差异,使其最小化从而保证ID的一致性

Cid: id Condition 身份条件

Ctxt: text Condition 文本条件

ϕ(Cid):真实图像的ID特征

ϕ(L−T2I(Xt,Cid,Ctxt)):生成图像的ID特征

Xt:时间步为t的扩散过程产物

T2I:结合身份条件,文本条件来生成图像

L:表示扩散过程,它接受噪声图像和时间步t作为输入,并预测在该时间步长的噪声

图片

图片

三. 效果对比与应用场景

3.1 不同方案的对比

PuLID实现了更高的ID保真度,同时减少对原始模型中文本提示的干扰. PuLID在光影,风格,布局方面和提示词描述更加吻合

图片

3.2 不同权重下的结果

可以看到当ID权重设置相对较小值时,ID嵌入对原始模型的影响很小,随着权重的增加,可以看出PuLID只影响和ID相关的部分,不会显著的扰乱原始模型的行为.

图片

3.3 更多应用场景

论文中也给出了PuLID的更多应用场景:

风格变化,IP融合,配饰修改,场景修改,人物属性修改,ID混合等

图片

图片

四. ComfyUI_Pulid_Flux的应用实践

上面我们介绍原理的PuLID是基于SDXL的,网络结构还是UNet, Flux相比SDXL有更大参数,UNet也改为了DiT,随着Flux的发布以及相应生态建立和完善,基于Flux的ComfyUI插件也是很快跟上. 这一小节,我们对ComfyUI_Pulid_Flux进行实践.

4.1 模型下载与依赖

下载Flux.1-dev放在ComfyUI/models/checkpoints下

下载PuLID_flux预训练权重,放在ComfyUI/models/pulid下

下载EVA-Clip放在ComfyUI/models/clip下. 这里需要注意,由于代码中eva-clip自动下载到cache路径,这里需要修改源码中的权重加载路径:ComfyUI/custom_nodes/ComfyUI-PuLID-Flux/pulidflux.py

eva_clip_model_path='ComfyUI/models/clip/EVA02_CLIP_L_336_psz14_s6B.pt'model, _, _ = create_model_and_transforms('EVA02-CLIP-L-14-336', eva_clip_model_path, force_custom_clip=True)

安装facexlib,下载相关的权重

detection_Resnet50_Final.pthparsing_parsenet.pthparsing_bisenet.pth

下载insightFace需要的AtelopeV2放在ComfyUI/models/insightface/models/antelopev2

公众号"音视频开发之旅",关注并回复"PuLID"获取相关权重

4.2 ComfyUI_Pulid_Flux

使用https://github.com/balazik/ComfyUI-PuLID-Flux/tree/master/examples/pulid_flux_16bit_simple.json 工作流

基于FLUX.1-dev-fp8模型

图片

图片

图片

小技巧: 使用过程中,如果显存不足,报OOM,可以把图片的wh等比例缩小, 生成后在使用超分放大模型进行处理

五. 源码分析

https://github.com/balazik/ComfyUI-PuLID-Flux/pulidflux.py

ComfyUI-PuLID-Flux支持的节点类型如下,其中前三个分别是模型加载器, ApplyPulidFlux是把Flux低模和PulidFluxMode,人脸检测模型PulidFluxInsight以及EVA编码模型NODE_CLASS_MAPPINGS = {    "PulidFluxModelLoader": PulidFluxModelLoader,    "PulidFluxInsightFaceLoader": PulidFluxInsightFaceLoader,    "PulidFluxEvaClipLoader": PulidFluxEvaClipLoader,    "ApplyPulidFlux": ApplyPulidFlux,}我们重点看ApplyPulidFlux节点,它结合了人脸检测,特征提取,条件嵌入和模型应用等多个步骤.以实现在图像中嵌入身份信息,同时保证图像的其他属性不变.  代码注释如下:class ApplyPulidFlux:    """    apply_pulid_flux方法接受多个参数,包括低模、PuLID_Flux模型、Eva Clip模型、人脸分析器、输入图像、PuLID_Flux权重、起始和结束时间步长、注意力掩码和唯一标识符。"""    def apply_pulid_flux(self, model, pulid_flux, eva_clip, face_analysis, image, weight, start_at, end_at, attn_mask=None, unique_id=None):        device = comfy.model_management.get_torch_device()        dtype = model.model.diffusion_model.dtype        #将eva_clip和pulid_flux模型转换到指定的设备和数据类型。eva_clip.to(device, dtype=dtype)        pulid_flux.to(device, dtype=dtype)                #将输入图像转换为张量。image = tensor_to_image(image)
        #人脸检测和特征提取        face_helper = FaceRestoreHelper(            upscale_factor=1,            face_size=512,            crop_ratio=(1, 1),            det_model='retinaface_resnet50',            save_ext='png',            device=device,        )        face_helper.face_parse = None        face_helper.face_parse = init_parsing_model(model_name='bisenet', device=device)                bg_label = [0, 16, 18, 7, 8, 9, 14, 15]        cond = []
        # Analyse multiple images at multiple sizes and combine largest area embeddings        for i in range(image.shape[0]):            # get insightface embeddings            iface_embeds = None            #从图像中提取人脸特征            for size in [(size, size) for size in range(640, 256, -64)]:                face_analysis.det_model.input_size = size                face_info = face_analysis.get(image[i])                if face_info:                    face_info = sorted(face_info, key=lambda x: (x.bbox[2] - x.bbox[0]) * (x.bbox[3] - x.bbox[1]))[-1]                    iface_embeds = torch.from_numpy(face_info.embedding).unsqueeze(0).to(device, dtype=dtype)                    break            else:                # No face detected, skip this image                logging.warning(f'Warning: No face detected in image {str(i)}')                continue
            # get eva_clip embeddings            face_helper.clean_all()            face_helper.read_image(image[i])            face_helper.get_face_landmarks_5(only_center_face=True)            face_helper.align_warp_face()
            if len(face_helper.cropped_faces) == 0:                # No face detected, skip this image                continue
            # Get aligned face image            align_face = face_helper.cropped_faces[0]            # Convert bgr face image to tensor            align_face = image_to_tensor(align_face).unsqueeze(0).permute(0, 3, 1, 2).to(device)            parsing_out = face_helper.face_parse(functional.normalize(align_face, [0.485, 0.456, 0.406], [0.229, 0.224, 0.225]))[0]            parsing_out = parsing_out.argmax(dim=1, keepdim=True)            bg = sum(parsing_out == i for i in bg_label).bool()            white_image = torch.ones_like(align_face)            # Only keep the face features            face_features_image = torch.where(bg, white_image, to_gray(align_face))
            # Transform img before sending to eva_clip            # Apparently MPS only supports NEAREST interpolation?            face_features_image = functional.resize(face_features_image, eva_clip.image_size, transforms.InterpolationMode.BICUBIC if 'cuda' in device.type else transforms.InterpolationMode.NEAREST).to(device, dtype=dtype)            face_features_image = functional.normalize(face_features_image, eva_clip.image_mean, eva_clip.image_std)
            # eva_clip            id_cond_vit, id_vit_hidden = eva_clip(face_features_image, return_all_features=False, return_hidden=True, shuffle=False)            id_cond_vit = id_cond_vit.to(device, dtype=dtype)            for idx in range(len(id_vit_hidden)):                id_vit_hidden[idx] = id_vit_hidden[idx].to(device, dtype=dtype)
            id_cond_vit = torch.div(id_cond_vit, torch.norm(id_cond_vit, 2, 1, True))
            # 将人脸特征和clip-vit条件 嵌入到扩散模型中            id_cond = torch.cat([iface_embeds, id_cond_vit], dim=-1)
            # Pulid_encoder            cond.append(pulid_flux.get_embeds(id_cond, id_vit_hidden))

        # average embeddings        cond = torch.cat(cond).to(device, dtype=dtype)        if cond.shape[0] > 1:            cond = torch.mean(cond, dim=0, keepdim=True)
        sigma_start = model.get_model_object("model_sampling").percent_to_sigma(start_at)        sigma_end = model.get_model_object("model_sampling").percent_to_sigma(end_at)
        flux_model = model.model.diffusion_model
        # 将条件嵌入到Flux模型中,并存储在pulid_data 字典中。flux_model.pulid_data[unique_id] = {            'weight': weight,            'embedding': cond,            'sigma_start': sigma_start,            'sigma_end': sigma_end,        }
        self.pulid_data_dict = {'data': flux_model.pulid_data, 'unique_id': unique_id}
        return (model,)

小结

PuLID将常规的扩散分支和Lightning T2I分支想结合,引入对比对齐损失和准确的ID损失,最大限度的减少对原始模型的破坏,生成具有较高ID一致性高质量图像.

但也存在一些局限性:引入T2I分支,需要更多的显存;T2I分支引入IdLoss显著提升ID一致性的同时也一定程度上影响了生成图像的质量,例如导致面部模糊等问题

六. 资料

1.PULID论文:https://arxiv.org/abs/2404.16022

2.源码: https://github.com/ToTheBeginning/PuLID

3.ComfyUI_Pulid_flux节点: https://github.com/balazik/ComfyUI-PuLID-Flux

4.PULID:对比对齐的ID定制化技术:https://blog.csdn.net/qq_44091004/article/details/139455437

5.PuLID-字节跳动开源的个性化文本到图像生成框架:https://ai-bot.cn/pulid/

6.Review_PuLID: https://cp0000.github.io/posts/review_pulid/

7.【论文笔记】| 定制化生成PuLID:https://blog.csdn.net/m0_62249876/article/details/139013586

8.Flux_pulid工作流: https://openart.ai/workflows/bulldog_fruitful_46/flux_pulid/dq1yBzUQim9ArrJQslVG

9.Face Swapping: EcomID vs. Flux PuLID vs. InstantID工作流:https://openart.ai/workflows/myaiforce/2ATyK62dutoPVCevX8o5

10.字节发布文生图模型PuLID:高效身份ID特征定制,单张图像克隆AI虚拟分身:https://blog.csdn.net/nulifancuoAI/article/details/138636073

感谢你的阅读

接下来我们继续学习输出AI相关内容,欢迎关注公众号“音视频开发之旅”,一起学习成长。

欢迎交流

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值