見出し画像

女の子がウィンク - Stable DiffusionとPythonの画像処理と自動化(2)

はい、まずはこちらをみましょう。

きれいな女の子が、ウィンクしてあなたを誘ってくれています。
これ、Stable Diffusion とかなんか、ともかく楽にできる方法あったら教えてください。
僕は Stable Diffusion で、目を開いた画像と閉じた画像を作るのが精いっぱいでした。

じゃあどうしてその動画はアニメーションになってんのかって、Python の画像処理に頼っちゃったわけですよ・・・

いいじゃん。これでいいんだよもう。
アニメーターじゃない人間がアニメを作れればそれでいいじゃん。



動作環境

前回の記事と同じです。詳細はそちらへ。
ただ、今回使う LoRA は SD15 ベースなので、XL 使いたい方は、自分でなんとかウィンクさせてください。


ウィンクした女の子を作る

LoRA を見つける

すいません、これ、みんな知ってたんでしょうね。。
したことなかったんで、見つけるまで苦労しちゃいました。
普通にプロンプトいじるだけじゃダメだったんですね。
ウィンク用の LoRA が必要だったんですねー。

ダウンロードして、Stable Diffusion の models/Lora に入れましょう。

両目を開いている女の子を作る

まあ、こんなのみんな簡単だよね。
txt2img でこんな適当なプロンプトでつくっちゃえ。
ただ、ネガティブプロンプトにあらかじめ両目が閉じないように、とは指定しておきます。

Prompt : 
masterpiece,best quality,1 girl,extreme close up,pink bandeau bikini,from_above,

Negative Prompt :
(worst quality,low quality:2),text,ugly face,bad face,bad anatomy,deformed eyes,
missing fingers,acnes,skin blemishes,nsfw,nude,nipples,
(both eyes closed:1.8),

できました。

君の好きな女の子をどうぞ

ここで作成した画像をディレクトリに保存して、img2img へ Send image… の絵のボタンで全体を移してください。

片目だけ閉じてる女の子を作る

全体の画像は変えたくないけど、ウィンクして目だけ動かしたいから、片目だけが閉じた画像を作ります。
僕はここのやり方知らなかったんで、見つけるのに苦労しましたが、なんてことない。
img2img の Inpaint でさっくりですよ。

すでに img2img にプロンプトやら画像やらなにやらがコピーされているはずです。されてなければ txt2img の画像が生成されたところから Send image… のボタンを押してません。

ともかく目を閉じさせるので、LoRA を使うプロンプトを追加します。

Prompt :
masterpiece,best quality,1 girl,extreme close up,pink bandeau bikini,from_above,
<lora:ddw_v1:0.75>,wink,one eye closed,

で、Inpaint のところで、目のあたりを塗ります。
ブラウザ上から塗れたんですね。マスク画像でも用意しなきゃいけないのかと思ってましたよ。

ウィンクして目が動くところだけを塗る

あと、画像サイズが txt2img から引き継がれないこともあるようなので、画像サイズを同じになるようにしてください。
Generate!

ウィンクしてくれた💛

さっき保存した同じディレクトリに保存しましょう。


アニメにしちゃう - Python 登場

なにかっつーとすぐにプログラムに頼ってしまいます。
何のために webui があるんでしょうね。。。

( 理論的なこと - 読み飛ばしていいです )
前回の歩く女の子では結局不採用になった補完処理ですが、今回は役に立ちました。が、アルファ値はいらないので、RGB だけで、線形補完させました。今回これが使えたのは、透過がないからですねー。

Python のコードを実行

はい、以下のコードを、Stable Diffusion がインストールされているディレクトリに置きましょう。rgb_interpolate.py とでもしましょうか。

from PIL import Image, ImageEnhance
import numpy as np
import math
import os

def blend_images_rgb(img1, img2, blending_factor):
    """
    2つのRGB画像をブレンドする
    
    :param img1: 最初の画像(PIL Image)
    :param img2: 2番目の画像(PIL Image)
    :param blending_factor: ブレンディングの強さ(0から1の間)
    :return: ブレンドされた画像(PIL Image)
    """
    img1_array = np.array(img1.convert('RGB'))
    img2_array = np.array(img2.convert('RGB'))
    
    # RGBチャンネルを線形補間
    blended_array = img1_array * (1 - blending_factor) + img2_array * blending_factor
    
    return Image.fromarray(blended_array.astype(np.uint8))

def interpolate_images(image1_path, image2_path, output_image_prefix, output_image_postfix, frames=5):
    """ 2つの画像間を補間します。
    """

    # Load images
    img1 = Image.open(image1_path).convert('RGB')
    img2 = Image.open(image2_path).convert('RGB')        

    # Ensure both images are the same size
    size = img1.size    

    interpolated_images_path = []

    for i in range(frames):
        output_path = f'{output_image_prefix}_{i+1:04d}_{output_image_postfix}.png'
        print('Interpolating ' + output_path.split(os.sep)[-1] + ' ...', end='', flush=True)

        # Calculate progression factor
        t = (i + 1.0) / frames if frames > 1 else i
        # Apply sine-based easing function
        eased_t = (math.sin((t - 0.5) * math.pi) + 1) / 2
        # Adjust the easing curve to make changes near the original images even smaller
        adjusted_t = eased_t ** 1.5  # You can adjust this exponent to control the curve
        # Calculate denoising strength
        denoising_strength = max(0.001, min(0.999, adjusted_t))

        # Blend images including alpha channel
        blended_img = blend_images_rgb(img1, img2, denoising_strength)

        # Convert blended image to RGB for img2img
        rgb_img = blended_img.convert('RGB')

        print('Done', flush=True)
        
        #result.image.save(output_path)
        rgb_img.save(output_path)
        interpolated_images_path.append(output_path)

    return interpolated_images_path

def process_all_images(directory, frames):
    """ すべての画像を処理します。
    """
    # ディレクトリ内のすべてのPNGファイルを取得し、ソート
    image_files = sorted([f for f in os.listdir(directory) if f.lower().endswith('.png')])

    # 隣接する画像ペアに対してinterpolate_imagesを呼び出す
    for i in range(len(image_files) - 1):
        image1_path = os.path.join(directory, image_files[i])
        image2_path = os.path.join(directory, image_files[i+1])
        
        # 出力ファイル名のベースを作成
        output_base = os.path.splitext(image_files[i])[0]
        
        # interpolate_images関数を呼び出す
        interpolated_images_path = interpolate_images(
            image1_path, 
            image2_path, 
            os.path.join(directory, output_base),
            'interpolated',
            frames=frames
        )
        
        print(f"Processed: {image_files[i]} and {image_files[i+1]}")

def test_images(directory, frames, prompt, negative_prompt):
    # ディレクトリ内のすべてのPNGファイルを取得し、ソート
    image_files = sorted([f for f in os.listdir(directory) if f.lower().endswith('.png')])[0:2]

    # 隣接する画像ペアに対してinterpolate_imagesを呼び出す
    for i in range(len(image_files) - 1):
        image1_path = os.path.join(directory, image_files[i])
        image2_path = os.path.join(directory, image_files[i+1])
        
        # 出力ファイル名のベースを作成
        output_base = os.path.splitext(image_files[i])[0]
        
        # interpolate_images関数を呼び出す
        interpolated_images = interpolate_images(
            image1_path, 
            image2_path, 
            os.path.join(directory, output_base),
            'interpolated',
            frames=frames,
            prompt=prompt,
            negative_prompt=negative_prompt
        )
        
        print(f"Processed: {image_files[i]} and {image_files[i+1]}")

if __name__ == '__main__':
    DIR='D:\\wink'
    process_all_images(DIR, 59)
    print("Interpolation completed successfully.")

配置したら、コマンドプロンプトから以下を実行。

cd stable-diffusion-webui
venv\Scripts\activate
python rgb_interpolate.py

だだだだだーーー、っと補完された画像がめっちゃできます。

補完された画像群

ffmpeg で映像へ

これを ffmpeg で映像に変換します。
ここでいきなり、ffmpeg ってなに?という方は、前回の記事をどうぞ。

ですが、この画像群、両目を開いている画像が閉じた画像へ変化しているだけなので、後ろに逆再生の画像も追加します。
以下のバッチファイルを、画像があるディレクトリに作成しましょう。
mkmov.bat とかみたいなファイル名でいいでしょう。

@echo off
chcp 65001 > nul
setlocal enabledelayedexpansion

:: Default values
set "output_file=output.mp4"
set "file_list=temp_file_list.txt"
set "video_duration=2"
set "file_pattern=*.png"
set "bg_r=00"
set "bg_g=00"
set "bg_b=00"

echo File pattern: %file_pattern%
echo Output file: %output_file%
echo Video duration: %video_duration% seconds
echo Background color: #%rgb_r%%rgb_g%%rgb_b%

:: Count matching files and calculate framerate
set file_count=0
for %%F in (%file_pattern%) do set /a file_count+=1
set /a framerate=file_count/video_duration
if %framerate% lss 1 set framerate=1

echo Number of matching files: %file_count%
echo Calculated framerate: %framerate%

:: Create a new file list with matching files
if exist "%file_list%" del "%file_list%"
for %%F in (%file_pattern%) do (
    echo file '%%~fF'>>"%file_list%"
)

:: Use FFmpeg to create video from the file list with specified background color, preserving transparency
ffmpeg -y -f concat -safe 0 -r %framerate% -i "%file_list%" ^
-filter_complex "[0:v]scale=1920:1080:force_original_aspect_ratio=decrease,pad=1920:1080:(ow-iw)/2:(oh-ih)/2:color=#00000000,format=rgba[scaled];color=c=0x%bg_r%%bg_g%%bg_b%:s=1920x1080[bg];[bg][scaled]overlay=format=auto,format=yuv420p[v]" ^
-map "[v]" ^
-c:v libx264 ^
-preset ultrafast ^
-crf 23 ^
-t %video_duration% ^
"%output_file%"

echo Video creation complete. Output file: %output_file%

ffmpeg -i %output_file% -filter_complex "[0:v]split[v1][v2];[v2]reverse[rev];[v1][rev]concat=n=2:v=1[outv]" -map "[outv]" looped_%output_file%

echo Looped video creation complete. Output file: looped_%output_file%

echo File list saved as: %file_list%

pause

あとは、なんなら、このバッチファイルをエクスプローラからダブルクリックしてもいいです。
looped_xxxx.mp4 みたいなファイルができたら完成!

追加情報

以下の LoRA は、どちらの目を閉じるか決めるのに役立つかもしれません。
ddw_v1 と組み合わせないと機能しないみたいでした。

masterpiece,best quality,1 girl,extreme close up,pink bandeau bikini,from_above,
<lora:rightwink_v120:1>,<lora:ddw_v1:0.75>,wink,one eye closed,
右目閉じ
masterpiece,best quality,1 girl,extreme close up,pink bandeau bikini,from_above,
<lora:leftwink_v120:1>,<lora:ddw_v1:0.75>,wink,one eye closed,
左目閉じ

終わりに

地味に、ウィンクをさせる LoRA が必要だったことと、img2img の Inpaint で一部だけ変化させられることが勉強になりました。
どんだけ wink とか one eye closed とかのプロンプトを入れても、全然ウィンクしてくれないんだもの。

今回はこんな感じでした。
実はまさか Stable Diffusion 系で 2 回目の記事を書くとは思ってなかったんですが、今後もなにかと書いていこうかなと思います。

はい、宣伝。
ここまで読んだらせめて MV でも鑑賞して、チャンネル登録オナシャス。
してくんないと、いつか記事は有料にしちゃうよ!

前回の歩く女の子が登場していますよ。
これ、音楽のスタイルに、witch house っていうテイスト入れたんですが、我ながら気に入っています。




この記事が気に入ったらサポートをしてみませんか?