ローカル環境のDiffusersでIP-Adapter-FaceID-PlusV2-SDXLを試そうと思ったら想像以上に苦労した話【Python】最強顔認識モデルで同じ顔の画像を生成する

Diffusers

現時点で最強とされる顔認識モデルのIP-Adapter-FaceID-PlusV2について、SDXL版を使おうとしたら想像以上に苦労しました。

今回は、備忘録的にトラブルと解決策をまとめていきます。

概要

IP-Adapter-FaceID-PlusV2とは

IP-Adapter-FaceID-PlusV2は、顔IDの埋め込みを使用して顔に基づいてスタイルイメージを生成する実験的なモデルです。IP-AdapterモデルとLoRAを組み合わせて、IDの一貫性を向上させます。

といってもよくわからないと思いますが、簡単にいってしまえば「IP-Adapter-FaceID-PlusV2を使えば、同じ顔で違う画像を再現できる」のです。

以下は公式で紹介されている画像です。

同じ顔で様々なスタイルの画像が生成されていることがわかります。

画像生成(IP-Adapter-FaceID-SDXL)

公式サイトでは、PlusV2ではないIP-Adapter-FaceID-SDXLのコードがサンプルとして紹介されていました。

公式サイト:https://huggingface.co/h94/IP-Adapter-FaceID

サンプルコード(公式サンプル)

まず、insightfaceで顔IDの埋め込みを抽出します。

import cv2
from insightface.app import FaceAnalysis
import torch

app = FaceAnalysis(name="buffalo_l", providers=['CUDAExecutionProvider', 'CPUExecutionProvider'])
app.prepare(ctx_id=0, det_size=(640, 640))

image = cv2.imread("person.jpg")
faces = app.get(image)

faceid_embeds = torch.from_numpy(faces[0].normed_embedding).unsqueeze(0)

そして、顔の埋め込みに条件付けられた画像を生成します。

import torch
from diffusers import StableDiffusionXLPipeline, DDIMScheduler
from PIL import Image

from ip_adapter.ip_adapter_faceid import IPAdapterFaceIDXL

base_model_path = "SG161222/RealVisXL_V3.0"
ip_ckpt = "ip-adapter-faceid_sdxl.bin"
device = "cuda"

noise_scheduler = DDIMScheduler(
    num_train_timesteps=1000,
    beta_start=0.00085,
    beta_end=0.012,
    beta_schedule="scaled_linear",
    clip_sample=False,
    set_alpha_to_one=False,
    steps_offset=1,
)
pipe = StableDiffusionXLPipeline.from_pretrained(
    base_model_path,
    torch_dtype=torch.float16,
    scheduler=noise_scheduler,
    add_watermarker=False,
)

# load ip-adapter
ip_model = IPAdapterFaceIDXL(pipe, ip_ckpt, device)

# generate image
prompt = "A closeup shot of a beautiful Asian teenage girl in a white dress wearing small silver earrings in the garden, under the soft morning light"
negative_prompt = "monochrome, lowres, bad anatomy, worst quality, low quality, blurry"

images = ip_model.generate(
    prompt=prompt, negative_prompt=negative_prompt, faceid_embeds=faceid_embeds, num_samples=2,
    width=1024, height=1024,
    num_inference_steps=30, guidance_scale=7.5, seed=2023
)

注意点

たしかにこれで実行はできたのですが、いくつか注意点があります。

IP-Adapter-FaceID-SDXLモデルのローカル保存

1点目、ip_ckptで指定されている“ip-adapter-faceid_sdxl.bin"はローカルに保存しなければ機能しません
HuggingFaceから自動ダウンロードはされませんので、予めgitなどで全モデルをダウンロードしておくと良いと思います。

ダウンロードリンク:https://huggingface.co/h94/IP-Adapter-FaceID/tree/main

ip_adapterライブラリのインストール方法

2点目、コード内で使用しているip_adapterライブラリは通常のpipでインストールしてしまうと機能しません

既にインストールしてしまった方は一度アンインストールし、以下からインストールし直してください。

pip uninstall ip_adapter
pip install git+https://github.com/tencent-ailab/IP-Adapter.git

PyPIの更新が間に合っていないのでしょうか・・・個人的にはここがかなり大きく躓いたところです。
いろいろ探していたところ、こちらの記事に救われました。

生成画像の保存方法

3点目、これは注意というほどでもありませんが、画像の保存は以下のコードで可能です。

images[0].save("result.png")

通常のDiffusionPipelineと異なるので、最初は若干戸惑いますが、覚えてしまえば何ということはありません。

# 通常のPipeline
image = pipe(prompt=prompt).images[0]
image.save("result.png")

# IP-Adapter-FaceID-SDXL
image = ip_model.generate(prompt=prompt)[0]
image.save("result.png")

生成結果

顔モデルとして使用したのはネットで適当に拾ってきたコチラの画像です。

結果は以下のようになりました。

いや、再現度高すぎません?
もはやこれで全然良い気がしてきました。

プロンプトがサンプル通り、「"A closeup shot of a beautiful Asian teenage girl in a white dress wearing small silver earrings in the garden, under the soft morning light"(柔らかい朝の光の下、庭で小さな銀のイヤリングを付けた白いドレスを着た美しいアジアの十代の少女のクローズアップショット)」でしたので、改善すればもっと良い画像が生成されるかもしれません。

画像生成(IP-Adapter-FaceID-PlusV2-SDXL)

サンプルコード(自作)

先程のサンプルコードを基に、IP-Adapter-FaceID-PlusV2-SDXL向けに変更を加えてみました。

まず、顔の抽出を行います。

import cv2
from insightface.app import FaceAnalysis
from insightface.utils import face_align
import torch

app = FaceAnalysis(name="buffalo_l", providers=['CUDAExecutionProvider', 'CPUExecutionProvider'])
app.prepare(ctx_id=0, det_size=(640, 640))

image = cv2.imread("person.jpg")
faces = app.get(image)

image_size = 640
faceid_embeds = torch.from_numpy(faces[0].normed_embedding).unsqueeze(0)
face_image = face_align.norm_crop(image, landmark=faces[0].kps, image_size=image_size)

そして、抽出した顔から画像を生成します。

import torch
from diffusers import StableDiffusionXLPipeline, DDIMScheduler
from PIL import Image

from ip_adapter.ip_adapter_faceid import IPAdapterFaceIDPlusXL

base_model_path = "SG161222/RealVisXL_V3.0"
image_encoder_path = "laion/CLIP-ViT-H-14-laion2B-s32B-b79K"
ip_ckpt = "IP-Adapter-FaceID/ip-adapter-faceid-plusv2_sdxl.bin"
device = "cuda"

noise_scheduler = DDIMScheduler(
    num_train_timesteps=1000,
    beta_start=0.00085,
    beta_end=0.012,
    beta_schedule="scaled_linear",
    clip_sample=False,
    set_alpha_to_one=False,
    steps_offset=1,
)
pipe = StableDiffusionXLPipeline.from_pretrained(
    base_model_path,
    torch_dtype=torch.float16,
    scheduler=noise_scheduler,
    add_watermarker=False,
)

# load ip-adapter
ip_model = IPAdapterFaceIDPlusXL(pipe, image_encoder_path, ip_ckpt, device)

# generate image
prompt = "A closeup shot of a beautiful Asian teenage girl in a white dress wearing small silver earrings in the garden, under the soft morning light"
negative_prompt = "monochrome, lowres, bad anatomy, worst quality, low quality, blurry"

images = ip_model.generate(
    prompt=prompt, negative_prompt=negative_prompt, face_image=face_image, faceid_embeds=faceid_embeds, num_samples=2,
    width=1024, height=1024,
    num_inference_steps=30, guidance_scale=7.5, seed=2023
)

images[0].save("result2.png")

変更点

PlusV2へバージョンアップするにあたって、いくつかの変更点があります。

モジュール変更

PlusV2では使用するモジュールが異なりますので、以下のように変更します。

from ip_adapter.ip_adapter_faceid import IPAdapterFaceIDPlusXL

これを間違えてIPAdapterFaceIDXLのまま動かしてしまうと、こんなエラーが出てきます。

RuntimeError: Error(s) in loading state_dict for MLPProjModel: (略)

image_encoder_pathの指定

PlusV2ではimage_encoder_pathを指定する必要があります。
ここでは"laion/CLIP-ViT-H-14-laion2B-s32B-b79K"を指定しました。

image_encoder_path = "laion/CLIP-ViT-H-14-laion2B-s32B-b79K"
ip_model = IPAdapterFaceIDPlusXL(pipe, image_encoder_path, ip_ckpt, device)

IPAdapterFaceIDPlusXLにてimage_encoder_pathは必須ですので、必ず指定しましょう。

face_imageの指定

このあたりは全然理解していないのですが、face_imageというのを指定しないといけないみたいです。

image_size = 640
face_image = face_align.norm_crop(image, landmark=faces[0].kps, image_size=image_size)
images = ip_model.generate(
    prompt=prompt, negative_prompt=negative_prompt, face_image=face_image, faceid_embeds=faceid_embeds, num_samples=2,
    width=1024, height=1024,
    num_inference_steps=30, guidance_scale=7.5, seed=2023
)

正直、何をやっているのかは全くわかりません。

生成結果

先ほどの顔モデルで生成した結果、以下のようになりました。

真面目に再現度高すぎませんか・・・?

ただ正直な話、試行回数(生成枚数)が少ないということもあり、IP-Adapter-FaceID-SDXLとIP-Adapter-FaceID-PlusV2-SDXLの違いはよく分かりませんでした。

どちらにするかお困りの方は、比較的簡単なIP-Adapter-FaceID-SDXLを使うのが良いと思います。

注意点

顔モデル画像について

1点、かなり注意したい点があるので書き留めておきます。

以下は、Flux.1モデルで生成した顔画像なのですが、こちらの画像から顔IDは読み込んでもらえませんでした。

エラー内容としては、以下のようになりました。

(略)
UserWarning: Specified provider 'CUDAExecutionProvider' is not in available provider names.Available providers: 'AzureExecutionProvider, CPUExecutionProvider'
warnings.warn(
Applied providers: ['CPUExecutionProvider'], with options: {'CPUExecutionProvider': {}}
find model: C:\.../.insightface\models\buffalo_l\1k3d68.onnx landmark_3d_68
(中略)
Traceback (most recent call last):
File "C:\...\main.py", line 12, in <module>
faceid_embeds = torch.from_numpy(faces[0].normed_embedding).unsqueeze(0)
IndexError: list index out of range

このエラーを見て、「CUDAが認識されていない?」と思って色々試したのですが、結果的には「そもそも顔が認識されていない」というのがオチで、先ほど載せた顔画像に差し替えたところ無事に認識されました。

全く試していないので完全な推測になりますが、アニメ風の画像からは顔認識してもらえないのか、あるいは正面を向いていないと認識してもらえないのかもしれません。

Tracebackの方をちゃんと見ておくべきだったとはいえ、CPUExecutionProviderとかCUDAExecutionProviderとかってやたらめったら出てくるのはやめてほしいですね。。

以上です。

 

【Reference】

https://huggingface.co/h94/IP-Adapter-FaceID

スポンサーリンク

Diffusers

Posted by このめ