Node.jsからStable Diffusionを呼び出す!プログラムに画像生成を組み込もう!

Node.jsからStable Diffusionを呼び出す!プログラムに画像生成を組み込もう!

最も普及している、stable diffusion web UIをNode.jsから呼びだして画像生成する方法を解説します。

Stable Diffusionは、AUTOMATIC1111 WebUI と呼ばれているものを利用

AUTOMATIC1111 WebUIでAPI呼び出しを有効化する

Stable DiffusionのRest APIを有効化するには、起動時に --api パラメータを追記します。

webui-user.bat を開き COMMANDLINE_ARGS を設定してから起動します。

@echo off

set PYTHON=
set GIT=
set VENV_DIR=
set COMMANDLINE_ARGS=--api

call webui.bat

node-sd-webui の使い方

Node.jsから、Stable Diffusion を呼び出すのには、node-sd-webui を使います。
GitHub – nerdenough/node-sd-webui: A Node.js client for Automatic1111’s Stable Diffusion WebUI

インストール

npm i -S node-sd-webui

node-sd-webui を使って、Stable Diffusionの txt2image, img2img, pnginfo を実行していきます。

txt2imageの呼び出し

txt2img()を使って、プロンプト文字列から画像を生成します。

  • プロンプト: 青い髪の侍ガール
  • ネガティブプロンプト: とりあえず、指の数が異なるものを排除
  • 画像サイズ: 800 x 400
  • シード: 5
  • 生成画像数: 1
const sdwebui = require('node-sd-webui');
const fs = require('fs');

// StableDiffusionのURL
// ※末尾に "/" を付けない
const STABLE_DIFFUSION_URL = "http://127.0.0.1:7860";

async function main() {
  const client = sdwebui.default({apiUrl: STABLE_DIFFUSION_URL});

  try {
    const response = await client.txt2img({
      prompt: '1girl, solo, blue hair, samurai, looking at viwer',
      negativePrompt: 'More than five fingers, less than five fingers',
      samplingMethod: sdwebui.SamplingMethod.Euler_A,
      width: 800,
      height: 400,
      steps: 20,
      seed: 5, // シード値.-1でランダム
      batchSize: 1, // 生成する画像数
    });

    const parameters = response.parameters;
    console.log("parameters", parameters); // 各種生成パラメータ

    const info = JSON.parse(response.info); // 生成時のシード情報など
    console.log("info", info);

    const images = response.images;

    for (let i=0; i<images.length; ++i) {
      const image = images[i]; // Base64で画像データ
      const seed = info.all_seeds[i];

      // image-<タイムスタンプ>-<seed>.png として画像保存
      const filename = `./out/image-${info.job_timestamp}-${seed}.png`;
      console.log("save: ", filename);

      fs.writeFileSync(filename, image, 'base64');
    }
  } catch (err) {
    console.error(err)
  }
}

main();

要注意ポイント

  • response.infoは、Json形式のテキストなので、パースする
  • 生成画像のシード値は、info.all_seeds[]に入っている。
  • response.imagesは、Base64でエンコードされた画像データ。ファイルに保存するなら、fs.writeFileSync()base64を指定する。

生成された画像がこちら

侍ガール
水の斬撃エフェクトのある、侍っぽい気配のある美少女が生成できました。

img2imgの呼び出し

img2img()を使って、画像から画像を生成します。

  • 元画像: 読み込んだ画像データをBase64エンコードして、imageDataパラメータに配列で渡す
  • プロンプト: 1匹のトイプードル
  • 画像サイズ: 768 x 1100
  • シード: 5
  • 生成画像数: 1
const sdwebui = require('node-sd-webui');
const fs = require('fs');

// StableDiffusionのURL
// ※末尾に "/" を付けない
const STABLE_DIFFUSION_URL = "http://127.0.0.1:7860";

async function main() {
  const client = sdwebui.default({apiUrl: STABLE_DIFFUSION_URL});

  try {

    // 入力画像を読み込んで Base64エンコードに変換
    const imageData = fs.readFileSync("./sample_data/my_dog.jpg");
    const imageBase64 = Buffer.from(imageData).toString('base64');

    const response = await client.img2img({
      imageData: [imageBase64],
      prompt: '1dog, toy-poodle, looking at viwer',
      negativePrompt: 'More than five fingers, less than five fingers',
      samplingMethod: sdwebui.SamplingMethod.Euler_A,
      width: 768,
      height: 1100,
      steps: 20,
      seed: 5, // シード値.-1でランダム
      batchSize: 1, // 生成する画像数
    });

    console.log("response", response);

    const parameters = response.parameters;
    console.log("parameters", parameters); // 各種生成パラメータ

    const info = JSON.parse(response.info); // 生成時のシード情報など
    console.log("info", info);

    const images = response.images;

    for (let i=0; i<images.length; ++i) {
      const image = images[i]; // Base64で画像データ
      const seed = info.all_seeds[i];

      // image-<タイムスタンプ>-<seed>.png として画像保存
      const filename = `./out/image-${info.job_timestamp}-${seed}.png`;
      console.log("save: ", filename);

      fs.writeFileSync(filename, image, 'base64');
    }
  } catch (err) {
    console.error(err)
  }
}

main();

入力パラメータに imageData が増える以外は txt2img とほぼ同じです。

生成結果

入力画像
入力画像

出力画像

出力画像


色味や、構図が似た感じのトイプードルのイラストができました。
美少女イラストが得意なモデルを使ったので、女の子っぽい雰囲気に仕上がってます。

pnginfoの呼び出し

pnginfo()を使って、pngファイルから生成時の情報を取得できます。
ここでは、先程生成した侍ガールを入力しています。

const sdwebui = require('node-sd-webui');
const fs = require('fs');

// StableDiffusionのURL
// ※末尾に "/" を付けない
const STABLE_DIFFUSION_URL = "http://127.0.0.1:7860";

async function main() {
  const client = sdwebui.default({apiUrl: STABLE_DIFFUSION_URL});

  try {

    // 入力画像を読み込んで Base64エンコードに変換
    const imageData = fs.readFileSync("./out/image-20230421000100-5.png");
    const imageBase64 = Buffer.from(imageData).toString('base64');

    const response = await client.pngInfo({
      imageData: imageBase64,
    });

    console.log("response", response);
  } catch (err) {
    console.error(err)
  }
}

main();

出力結果

response { 
  prompt: '1girl, solo, blue hair, samurai, looking at viwer', 
  steps: 20, 
  samplingMethod: 'Euler a', 
  cfgScale: 7, 
  seed: 5, 
  width: 800, 
  height: 400, 
  modelHash: '2202fecad7', 
  model: 'TriPhaze_C', 
  negativePrompt: 'More than five fingers, less than five fingers' 
}

使ったモデルから、各種パラメータまで取得できました。

node-sd-webui でサポートされない機能の呼び出し

他の機能も Rest API を参考に直接呼び出せます。

APIリファレンスの確認方法

まず Stable Diffusion WebUI の下の方から api というリンクをクリックします。
APIリファレンスの確認方法1

すると、このようなリファレンスが確認できます。
APIリファレンス

デフォルトの設定の人は http://127.0.0.1:7860/docs からAPIのリファレンスが見られます。

taggerでタグやレーティング判定

Stable Diffusion WebUI拡張の1つ、taggerを呼び出して画像に写っている要素のタグや、レーティングを取得してみましょう。
GitHub – toriato/stable-diffusion-webui-wd14-tagger: Labeling extension for Automatic1111’s Web UI

tagger APIリファレンス
tagger api
/tagger/v1/interrogate にパラメータは3つ付けて POST すれば良いことがわかります。

  • Base64形式の image
  • 解析用の model
  • タグ判定する閾値 threshold

tagger APIのレスポンス
tagger api
レスポンスは、 caption 要素に タグ名: 重み 形式で取得できます。

サンプルでは、http通信のために axios というnpmパッケージを利用しています。
axios – npm
これを使うと、async / await で待ち合わせする形でhttp通信が簡単にできます。

その前に axios をインストールしておきましょう

npm install axios

tagger APIへアクセスするコード

const axios = require('axios');
const fs = require('fs');

// StableDiffusionのURL
// ※末尾に "/" を付けない
const STABLE_DIFFUSION_URL = "http://127.0.0.1:7860";

async function main() {
  try {
    // 入力画像を読み込んで Base64エンコードに変換
    const imageData = fs.readFileSync("./out/image-20230421000100-5.png");
    const imageBase64 = Buffer.from(imageData).toString('base64');

    const data = {
      image: imageBase64,
      model: "wd14-vit-v2",
      threshold: 0.35, // [0-1]
    };

    const url = STABLE_DIFFUSION_URL + "/tagger/v1/interrogate";
    const response = await axios.post(url, data);

    // captionのうち、"general", "sensitive", "questionable", "explicit"がレーティング判定
    // その他は画像に含まれるタグ
    const caption = response.data.caption;
    console.log(caption);
    
  } catch (err) {
    console.error(err)
  }
}

main();

こちらも先程のsamuraiガールを入力しています。
実行すると次のような結果が取得できました。

{ 
  general: 0.4399340748786926, 
  sensitive: 0.527866780757904, 
  questionable: 0.001455068588256836, 
  explicit: 0.00033295154571533203, 
  '1girl': 0.9881388545036316, 
  solo: 0.9644424915313721, 
  blue_hair: 0.9189961552619934, 
  looking_at_viewer: 0.8000155687332153, 
  ponytail: 0.7931246161460876, 
  brown_eyes: 0.6853416562080383, 
  grey_background: 0.6733701229095459, 
  long_hair: 0.6240038871765137, 
  hair_ornament: 0.6151501536369324, 
  closed_mouth: 0.5795429348945618, 
  bangs: 0.562977135181427, 
  weapon: 0.5414935946464539, 
  sword: 0.5020155310630798, 
  makeup: 0.4913225769996643, 
  upper_body: 0.48966968059539795, 
  portrait: 0.4799497127532959, 
  hair_between_eyes: 0.4498511552810669, 
  orange_eyes: 0.40047234296798706 
}

レーティングは general, sensitive, questionable, explicit のパラメータを見て評価できます。
general < sensitive < questionable < explicit の順に画像の内容が過激になっていきます。

  • sensitive 52%なので、センシティブな画像の可能性あり

タグ判定は、レーティング関連のタグ以外で評価されます。
今回はsamuraiとは判定されなかったけど、ponytailなどの特徴は拾えています。

今後の応用

今回の内容で、Node.jsからStable Diffusionで画像生成できるようになりました。
単体で画像生成するツールだけでなく、他のサービスと組み合わせるといろんなことができるようになります。
例えば、次のようなことが考えられます。

  • slackのchatbotから画像生成して返答
  • GPT4が書いたストーリーから、挿絵のpromptを作成して、Stable Diffusionで画像生成
  • 画像投稿サイトから画像を大量取得して、taggerでエッチな画像だけ保存する

Stable Diffusionで大量生産した画像を taggerの結果で分類すれば良い画像を判定とかできるかも!?