diffusersモジュールを使ってmov2movを動かしてみた

## はじめに
この記事ではSD1.5, SD2, SD3-mediumモデルを使ったmov2movをそれぞれdiffuserモジュールで実装した結果について述べています。また、実装したコードと得られた動画については[github](https://github.com/gyantic/mov2mov)にまとめています。
使用したモデルは以下のURLのものです。
- SD1.5の実写風モデル: https://civitai.com/models/25494/beautiful-realistic-asians
- SD2.1: https://huggingface.co/stabilityai/stable-diffusion-2-1
- SD3-medium: https://huggingface.co/stabilityai/stable-diffusion-3-medium
controlnetとして使用したモデル
- SD1.5用: https://huggingface.co/comfyanonymous/ControlNet-v1-1_fp16_safetensors/blob/main/control_v11p_sd15_canny_fp16.safetensors
- SD2.1用: https://huggingface.co/thibaud/controlnet-sd21-canny-diffusers
- SD3-medium用: https://huggingface.co/InstantX/SD3-Controlnet-Canny
使用したGPUはSD1.5とSD2.1がRTX4070、SD3-mediumがA100です。

## 概要
stablediffusionをpythonのdiffusersモジュールを使って動画の各フレームでimg2imgを行うことで、mov2movの実装を行いました。img2imgを行う際に用いるモデルは上で示したSD1.5の実写ファインチューニングモデル、SD2.1、SD3-mediumです。
それぞれのモデルについてmov2movをpythonで実装し、ControlNetを使用した場合と使用しなかった場合の出力結果についての比較を行っています。

今回は3DCG動画を実写風にmov2movすることを想定しています。(そのためSD1.5については実写風にFTされたものを使用)
変換元の動画として[リンク先](https://pixabay.com/ja/videos/%E7%94%B7-%E3%82%AB%E3%82%A6%E3%83%9C%E3%83%BC%E3%82%A4-%E8%A5%BF%E9%83%A8-%E5%B8%BD%E5%AD%90-208373/)の動画を使用しました。

## ControlNetについて
概要においてControlNetの有無で結果を比較すると述べましたが、ここでmov2movにおけるControlNetについて説明を行っておきます。
ControlNetとは生成モデルにおける生成プロセスを制御するためのアーキテクチャーです。具体的に言うと、拡散モデル(diffusion model)中の各プロセスに制約を課すことで拡散モデルにおける遷移を制御できるようにする仕組みのことです。
条件データは入力画像から生成されるエッジマップやポーズデータ等がありますが、今回はControlNetの条件データとしてCannyエッジマップを使用しました。
特に今回利用したStableDiffusionのような画像生成技術によるmov2movにおいては動画はフレームワークに分けられ各フレームワークは独立して扱われますが、ControlNetを使用することによって生成結果を制御して各フレームワークに対する出力に一貫性を持たせられることができます。その結果mov2movについて元の動画の動きを保ちながら変換することが可能になります。


## SD1.5の実写チューニングモデルを使う場合
使用モデル: https://civitai.com/models/25494/beautiful-realistic-asians
上記のリンクからダウンロードしてください。

pythonコードで以下のように引数を指定することで実行できます。
framesは動画の各フレームを分割した際の画像の保存先のファイルを指定してください。
inputは入力動画、outputは出力動画のことを指しています。
```
mov2mov("input.mp4",frames, "output.mp4")
```

### ControlNetモデルを使用しない場合
初めに、ControlNetを使わず単純に動画の各フレームにimg2imgを適用することでmov2movを実装したものを示します。
```SD1.5_noNet.py
from moviepy.editor import VideoFileClip, ImageSequenceClip
from diffusers import StableDiffusionImg2ImgPipeline
import torch
import cv2
import os
from PIL import Image
from tqdm import tqdm

# 動画の分割
def split_video_to_frames(video_path, output_dir):
    clip = VideoFileClip(video_path)
    os.makedirs(output_dir, exist_ok=True)
    for i, frame in enumerate(clip.iter_frames()):
        cv2.imwrite(f"{output_dir}/frame_{i:04d}.png", cv2.cvtColor(frame, cv2.COLOR_RGB2BGR))


# img2imgで各フレームに対してスタイル適用
def apply_img2img_with_reference(frame_path, pipeline):
    # オリジナルの解像度を取得
    original_image = Image.open(frame_path).convert("RGB")
    width, height = original_image.size

    # 幅と高さを64の倍数に調整
    w, h = map(lambda x: x - x % 64, (width, height))
    original_image = original_image.resize((w, h))

    # 画像生成
    with torch.no_grad():
        generated_image = pipeline(
            prompt="(Cinematic Aesthetic:1.4) Realistic photo, a cauboy, dance , moving, dynamic, man wearing a brown hat, Long Sleeve Clothes,4k",
            negative_prompt="cartoon, lowres, blurry, pixelated, sketch, drawing",
            image=original_image,
            guidance_scale=10,
            strength=0.6,
            num_inference_steps=40,
        ).images[0]

    return generated_image

# 動画の再構築
def combine_frames_to_video(frames_dir, output_video_path, fps=30):
    frame_files = sorted(
        [img for img in os.listdir(frames_dir) if os.path.isfile(os.path.join(frames_dir, img))],
        key=lambda x: int(os.path.splitext(x)[0].split('_')[-1])
    )
    frames = [cv2.imread(os.path.join(frames_dir, img)) for img in frame_files]
    clip = ImageSequenceClip([cv2.cvtColor(f, cv2.COLOR_BGR2RGB) for f in frames], fps=fps)
    clip.write_videofile(output_video_path, codec="libx264")


def mov2mov(video_path, output_dir, output_video_path):
    split_video_to_frames(video_path, output_dir)

    # メインモデルのパス
    model_path = "beautifulRealistic_v7.safetensors"

    # パイプラインの初期化
    pipeline = StableDiffusionImg2ImgPipeline.from_single_file(
        model_path,
        torch_dtype=torch.float16
    ).to("cuda")

    # メモリ効率化の設定
    pipeline.enable_attention_slicing()

    # スタイリング後のフレームを保存するディレクトリ
    styled_frames_dir = os.path.join(output_dir, "styled_frames")
    os.makedirs(styled_frames_dir, exist_ok=True)

    # フレームの処理
    frame_files = sorted(
        [img for img in os.listdir(output_dir) if os.path.isfile(os.path.join(output_dir, img))],
        key=lambda x: int(os.path.splitext(x)[0].split('_')[-1])
    )

    for frame_name in tqdm(frame_files, desc="Processing frames"):
        frame_path = os.path.join(output_dir, frame_name)

        # 画像生成（入力サイズを維持）
        styled_frame = apply_img2img_with_reference(frame_path, pipeline)
        styled_frame.save(os.path.join(styled_frames_dir, frame_name))

    # 動画の再構築
    combine_frames_to_video(styled_frames_dir, output_video_path)
```

##### 上のコードで行っている処理の流れについて
初めにsplit_video_to_frame関数で入力した動画をフレームごとに分割します。
次にimg2imgのためのパイプラインの初期化を行い、img2imgを動画の各フレームに行っていきます。
最後にcombine_frames_to_video関数によってimg2imgした画像をフレームとしてそれを結合することで動画を再構成します。この動画がmov2movによる出力です。


### ControlNetモデルを使用する場合
(使用したControlNetモデル：https://huggingface.co/comfyanonymous/ControlNet-v1-1_fp16_safetensors/blob/main/control_v11p_sd15_canny_fp16.safetensors)

ControlNetを使わない場合の実装に対して以下のような変更を行えばControlNetを使用したmov2movを実装することができます。

```
from diffusers import StableDiffusionImg2ImgPipeline
```
の代わりに
```
from diffusers import StableDiffusionControlNetImg2ImgPipeline, ControlNetModel
```
をimportします。
ControlNetを使用する場合、ControlNetモデルを追加で用意する必要があります。
ControlNetモデルは以下のコードを追加することで設定できます。
```
controlnet_model_path = ""   #ControlNetのパスを指定
controlnet = ControlNetModel.from_single_file(
        controlnet_model_path,
        torch_dtype=torch.float16
    )
```

ControlNetの仕様に合わせてパイプラインの設定は以下のように変更します。
```
pipeline = StableDiffusionControlNetImg2ImgPipeline.from_single_file(
        model_path,
        controlnet=controlnet,
        torch_dtype=torch.float16
    )
```
これらの変更や追加を行うことでControlNetを適用したmov2movの実装を行うことができます。
またContolNetモデルについては[リンク先](https://huggingface.co/comfyanonymous/ControlNet-v1-1_fp16_safetensors/blob/main/control_v11p_sd15_canny_fp16.safetensors)からダウンロードし、そのファイルの保存先を上記のcontrolnet_model_pathに指定することで使うことができます。
処理についてはControlNetを追加したのみでその他はControlNetを使用していない場合と同様です。SD2.1,SD3についても細かなコードの差異はあるものの大まかな処理の流れはSD1.5で説明したものと同じです。

## SD2.1を使う場合
SD1.5ではファインチューニングモデルを使用しましたが、SD2.1については[ベースモデル](https://huggingface.co/stabilityai/stable-diffusion-2-1)をそのまま使用しました。

### ControlNetを使用しない場合
ControlNetなしでmov2movを実装したものは以下のように、SD1.5の場合でモデルを変更しているだけです。ただし、モデルを扱う際に
```
model_path = "stabilityai/stable-diffusion-2-1"

    # パイプラインの初期化
pipeline = StableDiffusionImg2ImgPipeline.from_pretrained(
    model_path,
    torch_dtype=torch.float16
)
```
のようにfrom_pretrainedを用いる必要があります。ほかの要素についてはSD1.5のときと同様にしています。

実行方法についてはSD1.5の場合と同じようにmov2movに適切な引数を指定すればよいです。
```SD2.1_noNet.py
from moviepy.editor import VideoFileClip, ImageSequenceClip
from diffusers import StableDiffusionImg2ImgPipeline
import torch
import cv2
import os
from PIL import Image
from tqdm import tqdm

# 動画の分割
def split_video_to_frames(video_path, output_dir):
    clip = VideoFileClip(video_path)
    os.makedirs(output_dir, exist_ok=True)
    for i, frame in enumerate(clip.iter_frames()):
        cv2.imwrite(f"{output_dir}/frame_{i:04d}.png", cv2.cvtColor(frame, cv2.COLOR_RGB2BGR))


# img2imgで各フレームに対してスタイル適用
def apply_img2img_with_reference(frame_path,pipeline):
    # オリジナルの解像度を取得
    original_image = Image.open(frame_path).convert("RGB")
    width, height = original_image.size

    # 幅と高さを64の倍数に調整
    w, h = map(lambda x: x - x % 64, (width, height))
    original_image = original_image.resize((w, h))

    # 画像生成
    with torch.no_grad():
        generated_image = pipeline(
            prompt="(Cinematic Aesthetic:1.4) Realistic photo, a cowboy, dance , moving, dynamic, man wearing a brown hat, Long Sleeve Clothes,4k",
            negative_prompt="cartoon, lowres, blurry, pixelated, sketch, drawing",
            image=original_image,
            guidance_scale=10,
            strength=0.6,
            num_inference_steps=40,
        ).images[0]

    return generated_image

# 動画の再構築
def combine_frames_to_video(frames_dir, output_video_path, fps=30):
    frame_files = sorted(
        [img for img in os.listdir(frames_dir) if os.path.isfile(os.path.join(frames_dir, img))],
        key=lambda x: int(os.path.splitext(x)[0].split('_')[-1])
    )
    frames = [cv2.imread(os.path.join(frames_dir, img)) for img in frame_files]
    clip = ImageSequenceClip([cv2.cvtColor(f, cv2.COLOR_BGR2RGB) for f in frames], fps=fps)
    clip.write_videofile(output_video_path, codec="libx264")


def mov2mov(video_path, output_dir, output_video_path):
    split_video_to_frames(video_path, output_dir)

    # メインモデルのパス
    #model_path = "E:/stadifmodels/beautifulRealistic_v7.safetensors"
    model_path = "stabilityai/stable-diffusion-2-1"

    # パイプラインの初期化
    pipeline = StableDiffusionImg2ImgPipeline.from_pretrained(
        model_path,
        torch_dtype=torch.float16
    ).to("cuda")

    # メモリ効率化の設定
    pipeline.enable_attention_slicing()

    # スタイリング後のフレームを保存するディレクトリ
    styled_frames_dir = os.path.join(output_dir, "styled_frames")
    os.makedirs(styled_frames_dir, exist_ok=True)

    # フレームの処理
    frame_files = sorted(
        [img for img in os.listdir(output_dir) if os.path.isfile(os.path.join(output_dir, img))],
        key=lambda x: int(os.path.splitext(x)[0].split('_')[-1])
    )

    for frame_name in tqdm(frame_files, desc="Processing frames"):
        frame_path = os.path.join(output_dir, frame_name)

        # 画像生成（入力サイズを維持）
        styled_frame = apply_img2img_with_reference(frame_path, pipeline)
        styled_frame.save(os.path.join(styled_frames_dir, frame_name))

    # 動画の再構築
    combine_frames_to_video(styled_frames_dir, output_video_path)

mov2mov("input.mp4", "frames", "output.mp4") #この設定は例です。適切に変更をしてください。
```

### ControlNetを使用する場合
SD1.5同様ControlNetを指定してパイプラインで読み込むように処理させればよいです。ただし、
```
model_path = "stabilityai/stable-diffusion-2-1"
controlnet_model_path = "thibaud/controlnet-sd21-canny-diffusers"

# パイプラインの初期化
pipeline = StableDiffusionControlNetImg2ImgPipeline.from_pretrained(
    model_path,
    controlnet=controlnet,
    torch_dtype=torch.float16
).to("cuda")
```
のようにControlNetなしの場合同様、パイプラインの初期化の際にfrom_pretrainedを用いる必要があります。
また、新たにControlNetモデルの設定をSD1.5のControlNetありの場合と同じように追加しています。
またControlNetの使用モデルはhttps://huggingface.co/thibaud/controlnet-sd21-canny-diffusers です。SD2.1についてはカスタムモデルではないベースモデルを使用するので、モデルをダウンロードする必要はなく上記のパスのままで実行できます。


## SD3-mediumを使う場合
### ControlNetを使用しない場合
使用するモジュールがSD1.5やSD2.1とは異なり
```
from diffusers import StableDiffusion3Img2ImgPipeline
```
ということに注意します。また、SD2.1同様[ベースモデル](https://huggingface.co/stabilityai/stable-diffusion-3-medium)を使用しているのでパイプラインの初期化の際についてはfrom_pretrainedを用います。

``` SD3-Medium_NoNet.py
from moviepy.editor import VideoFileClip, ImageSequenceClip
from diffusers import StableDiffusion3Img2ImgPipeline
import torch
from transformers import T5EncoderModel, BitsAndBytesConfig
import cv2
import os
from PIL import Image
from tqdm import tqdm

# 動画の分割
def split_video_to_frames(video_path, output_dir):
    clip = VideoFileClip(video_path)
    os.makedirs(output_dir, exist_ok=True)
    for i, frame in enumerate(clip.iter_frames()):
        cv2.imwrite(f"{output_dir}/frame_{i:04d}.png", cv2.cvtColor(frame, cv2.COLOR_RGB2BGR))

# img2imgで各フレームに対してスタイル適用
def apply_img2img_with_reference(frame_path, reference_image, pipeline):
    # オリジナルの解像度を取得
    original_image = Image.open(frame_path).convert("RGB")
    width, height = original_image.size

    # 幅と高さを64の倍数に調整
    w, h = map(lambda x: x - x % 64, (width, height))
    original_image = original_image.resize((w, h))
    #edges_image = edges_image.resize((w, h))
    reference_image_resized = reference_image.resize((w, h))

    # 画像生成
    with torch.no_grad():
        generated_image = pipeline(
            prompt="(Cinematic Aesthetic:1.4) Realistic photo, a cowboy, dance , moving, dynamic, man wearing a brown hat, Long Sleeve Clothes,4k",
            negative_prompt="cartoon, lowres, blurry, pixelated, sketch, drawing",
            image=original_image,
            guidance_scale=10,
            strength=0.6,
            num_inference_steps=40,
        ).images[0]

    return generated_image

# 動画の再構築
def combine_frames_to_video(frames_dir, output_video_path, fps=30):
    frame_files = sorted(
        [img for img in os.listdir(frames_dir) if os.path.isfile(os.path.join(frames_dir, img))],
        key=lambda x: int(os.path.splitext(x)[0].split('_')[-1])
    )
    frames = [cv2.imread(os.path.join(frames_dir, img)) for img in frame_files]
    clip = ImageSequenceClip([cv2.cvtColor(f, cv2.COLOR_BGR2RGB) for f in frames], fps=fps)
    clip.write_videofile(output_video_path, codec="libx264")


def mov2mov(video_path, reference_image_path, output_dir, output_video_path):
    split_video_to_frames(video_path, output_dir)
    reference_image = Image.open(reference_image_path).convert("RGB")

    quantization_config = BitsAndBytesConfig(load_in_8bit=True)

    # メインモデルのパス
    #model_path = "stabilityai/stable-diffusion-3-medium-diffusers"
    model_path = "stabilityai/stable-diffusion-3.5-medium"


    # パイプラインの初期化
    pipeline = StableDiffusion3Img2ImgPipeline.from_pretrained(
        model_path,
        text_encoder_3=None,
        tokenizer_3=None,
        torch_dtype=torch.float16,
    ).to("cuda")

    # メモリ効率化の設定
    pipeline.enable_attention_slicing()
    #pipeline.enable_model_cpu_offload()

    # スタイリング後のフレームを保存するディレクトリ
    styled_frames_dir = os.path.join(output_dir, "styled_frames")
    os.makedirs(styled_frames_dir, exist_ok=True)

    # フレームの処理
    frame_files = sorted(
        [img for img in os.listdir(output_dir) if os.path.isfile(os.path.join(output_dir, img))],
        key=lambda x: int(os.path.splitext(x)[0].split('_')[-1])
    )

    for frame_name in tqdm(frame_files, desc="Processing frames"):
        frame_path = os.path.join(output_dir, frame_name)

        # 画像生成（入力サイズを維持）
        styled_frame = apply_img2img_with_reference(frame_path, reference_image, pipeline)
        styled_frame.save(os.path.join(styled_frames_dir, frame_name))

    # 動画の再構築
    combine_frames_to_video(styled_frames_dir, output_video_path)
```
使用するモジュールの違いを除けば、基本的な実装の流れはSD1.5やSD2.1と全く同じです。



### SD3-MediumでControlNetを使用する場合
```SD3_Net.py
from diffusers.models import SD3ControlNetModel
from diffusers import StableDiffusion3ControlNetPipeline

pipeline = StableDiffusion3ControlNetPipeline.from_pretrained(
    "stabilityai/stable-diffusion-3-medium-diffusers",
    controlnet=controlnet
)
pipeline.to("cuda", torch.float16)
```
のようにSD1.5やSD2.1の場合と違って、SD3ControlNetModelというSD3用のモジュールがあるのでそれを使用します。ただしImg2ImgとControlNetを同時に行うものが存在しませんでした
SD3ControlNetModelでControlNetを読み込んでパイプラインを初期化することでColtrolNetをSD3-mediumでも使うことができました。
この時使用したControlNetモデルは https://huggingface.co/InstantX/SD3-Controlnet-Canny です。
この他の部分についてはSD2.1のControlNetを使った場合と同じ実装を行っています。


## 結果
入力として、[リンク先](https://pixabay.com/ja/videos/%E7%94%B7-%E3%82%AB%E3%82%A6%E3%83%9C%E3%83%BC%E3%82%A4-%E8%A5%BF%E9%83%A8-%E5%B8%BD%E5%AD%90-208373/)の動画を使用しました。 

### ①SD1.5
#### ControlNetを使用しない場合

<iframe width="775" height="414" src="https://www.youtube.com/embed/mzvpvhnpVTg" title="SD1 5 NoNet" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>

動画が実写風に変換されていることは分かりますが、各フレームごとに独立して変換をしているので変換結果がばらばらで一貫性がありません。またところどころ変換後に顔が崩れているフレームがありました。

#### ControlNetを使用した場合

<iframe width="775" height="414" src="https://www.youtube.com/embed/ZFov6uwM-rA" title="SD1.5_WithNet" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>

ControlNetを使用しない場合に比べて動画を再生したときの人間のちらつきが減少している、つまり服装や顔がフレーム間で同じになっていて体の動きをはっきりとらえられるという点から一貫性は増しているといえます。
しかし、依然として顔をうまく変換できていないフレームが目立ちます。


### ②SD2.1
#### ControlNetを使用しない場合

<iframe width="775" height="414" src="https://www.youtube.com/embed/MQIzaELkNEM" title="SD2 no Net" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>

各フレームごとにおいては顔が崩れることはなくimg2imgの精度はよいと言えます。
ただしControlNetを使っていないため、各フレームごとにimg2imgをほどこしていることより動画に一貫性がないということがわかります。

#### ControlNetを使用した場合

<iframe width="775" height="414" src="https://www.youtube.com/embed/6MsRIu5rg3E" title="SD2 with Net" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>

ControlNetを適用することで一貫性が向上することを確認できました。変換した出力についても顔が崩れるということがなくきれいに変換がなされていることがわかります。


### ③SD3-mediumを使用した場合
#### ControlNetを使用しない場合
<iframe width="814" height="435" src="https://www.youtube.com/embed/pg-QUM8vM50" title="SD3noNet" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>

SD1.5,SD2.1よりも元動画のスタイルの影響が小さく、しっかりと実写風に変換をすることができていることが分かります。しかし、各フレームごとに変換を行っているのでSd1.5や2.1と同じように、特に顔について変換後の動画におけるばらつきがあります。



#### ControlNetを使用した場合
<iframe width="372" height="662" src="https://www.youtube.com/embed/qiDpjCIJ52I" title="SD3 with Net" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>


上の結果から分かるようにSD3を使用した場合について、SD1.5や2.1の時とは違いcontrolNetを使って上手くmov2movを行うことができませんでした。ただしフレームごとだときれいに実写風に変換することができています。よって変換自体はできています
おそらく、img2imgをcontrolNet有りで行うためのモジュールがSD1.5や2.1とは違って存在しないのでうまくいっていないと考えられます。
またControlNetが$512 \times 512$のサイズの画像に最適化されているので出力のサイズもそのサイズになっています。


## まとめ
#### 同一モデルでControlNetの有無での変化
ControlNetを使う場合がControlNetを使わない場合に比べて動画の各フレームにおける一貫性の向上が見られました。特に顔について、ControlNetを使用しない場合だと各フレームごとに返還後の顔が違うもの見なってしまい動画にするとばらつきがひどくなっていましたがCOntrolNetを使用することでフレーム間の変換後の顔に一貫性を持たせることができていることが分かります。つまり変換前の動画中の人間の動きを変換後も保つことができているといえます。
ただし、SD3についてはControlNetを使うと逆に上手くいかなくなるということが見られました。


#### ControlNetを使用しない場合におけるSD1.5,SD2.1,SD3での結果の比較
SD1.5を使用した場合では顔の崩れが見られました。これはmov2movにおける問題というより、img2imgの精度によるものだと言えます。SD2.1についてもフレームによっては上手く変換できていなことがありました。元動画における人の姿勢によっては顔が隠れていることや正面を向いていないことがありその部分では変換が難しかったのだと考えられます。SD3においてはフレームごとの結果を見れば他の2つのモデル同様、変換がうまくいっていないフレームもありましたが、他のモデルよりも実写風に変換することができています。
全ての場合における共通点として、フレームごとに変換後の顔が違うことにより動画に一貫性がないということがありました。また手の動きについても各フレームで語句率してimg2imgしていることより連続した動きになっていないということもうまく出力結果が得られない原因になっています。


#### ControlNetを使用する場合におけるSD1.5,SD2.1,SD3での結果の比較
初めに、前述のとおりSD3ではうまく出力を得ることができませんでした。おそらくdiffusersモジュールに用意されているものだけではSD3に対してControlNetありでimg2imgはできず拡張を行う必要があると考えられます。
以下ではSD1.5とSD2.1での結果を比較していきます。
共通点について、変換後の動画についてフレーム間でのばらつきがともに抑えられて元の動画の動きを変換後も保つことができていました。
プロンプトで実写になるように行っていましたが、出力が元動画の3DCGによっていることも共通点です。SD1.5の実写風ファインチューニングモデルを使っても3DCGによっているのでこれはControlNetにより引き起こされたものと考えられます。
相違点について、SD1.5においてControlNetを使用してmov2movを行うとControlNetを使わないときと同じく顔が崩れるフレームが目立っていましたが、SD2になるとそのような崩れはみられなくなりました。これはControlNetがない場合と同じなのでモデルの性能差だと思われます。



(この記事は研究室インターンで取り組みました：https://kojima-r.github.io/kojima/)


## 補足
この記事をまとめてる最中に[SD3.5-large](https://huggingface.co/stabilityai/stable-diffusion-3.5-large)がリリースされました。そこでSD3.5についてもmov2movを試してみたので、最後にまとめておきます。

### SD3.5の場合
#### ControlNetを使わなかった場合
実装面について
```SD3.5-large_nonet.py
#モデルパスの設定
model_path = "stabilityai/stable-diffusion-3.5-large"

# パイプラインの初期化
pipeline = StableDiffusion3Img2ImgPipeline.from_pretrained(
    model_path,
    torch_dtype=torch.float16,
).to("cuda")
```
このようにモデルを読み込みますが、これはSD3の場合と同様のやり方です。

A100を使った場合では23分程度実行完了までにかかりました。
また、普通に実行するだけだとメモリ不足になったので、
``` memory.py
import os
os.environ["PYTORCH_CUDA_ALLOC_CONF"] = "expandable_segments:True"
```
を付け足す必要がありました。

<iframe width="814" height="435" src="https://www.youtube.com/embed/pAtJLMFmq58" title="SD3.5noNet" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>

SD3での結果と比べてさらに手の動きについてはフレーム毎のばらつきが少なくなっていることがうかがえます。これはSD3.5におけるimg2imgの精度が向上した、つまりimg2imgの前後において画像の構図を変化させずにスタイルのみを変換させる制度の向上によるものであるといえます。
また服装についてフレーム間で似たようなものが生成されるようになっています。元動画における服装に似ていることより、この服装についての変換に一貫性があることからもimg2imgの精度が向上したといえます。
しかし、変換後の顔が各フレームでバラバラなものになってしまうというのはSD3の場合と変わりませんでした。またところどころ実写ではなく元動画のような3DCG風の画像に変換されているということがみうけられました。



#### ControlNetを使う場合
使用したControlnetモデル：https://huggingface.co/stabilityai/stable-diffusion-3.5-controlnets
実装について
はじめにControlImageを指定の形に変換する必要があります。
```ControlImage.py
from diffusers.image_processor import VaeImageProcessor

class SD3CannyImageProcessor(VaeImageProcessor):
    def __init__(self):
        super().__init__(do_normalize=False)
    def preprocess(self, image, **kwargs):
        image = super().preprocess(image, **kwargs)
        image = image * 255 * 0.5 + 0.5
        return image
    def postprocess(self, image, do_denormalize=True, **kwargs):
        do_denormalize = [True] * image.shape[0]
        image = super().postprocess(image, **kwargs, do_denormalize=do_denormalize)
        return image
```

また、
```SD3.5large_Net_pipeline.py
controlnet = SD3ControlNetModel.from_pretrained(
    "stabilityai/stable-diffusion-3.5-large-controlnet-canny",
    torch_dtype=torch.float16,
    joint_attention_dim=4096,
    low_cpu_mem_usage=False,
    device_map=None
)

controlnet_pipeline = StableDiffusion3ControlNetPipeline.from_pretrained(
    "stabilityai/stable-diffusion-3.5-medium",
    controlnet=controlnet,
    torch_dtype=torch.float16
)
```
としてパイプラインを設定しますが、これはSD3のControlNetありの場合と同様です。


GPUとしてA100を1枚使用して実行しましたがcuda out of memoryとなって今回は実行できませんでした。また、SD3の場合からほとんど変更をしていないので大丈夫だと思いますが、実行が試せていないので上のコードで動く保証はありません。また、SD3の場合同様ControlNetを使用するImg2Img用のものがdiffuersにないので実行できてもよい出力にはならないと思われます。

## 参考文献

#### diffusersでのmov2mov実装の参考資料
https://qiita.com/phyblas/items/00f750b8277f66fb9b13
https://qiita.com/phyblas/items/eed011fd01f5afd0f759

#### ControlNetの解説記事
https://note.com/konapieces/n/n95399958196e#b1887b5d-4837-407a-b028-ed04dae9540d

#### SD3の使用方法について
SD3を使ったimg2imgのサンプルコードが記載されている
https://huggingface.co/blog/sd3
SD3におけるControlNetの使用方法が書かれている
https://huggingface.co/docs/diffusers/main/en/api/pipelines/controlnet_sd3

#### SD3.5関連の記事
SD3.5のdiffuersでの使用方法
https://huggingface.co/blog/sd3-5#using-single-file-loading-with-the-stable-diffusion-35-transformer

SD3.5でのControlNetについて
https://stability.ai/news/sd3-5-large-controlnets