T-CREATOR

ComfyUI ワークフロー設計 101:入力 → 前処理 → 生成 → 後処理 → 出力の責務分離

ComfyUI ワークフロー設計 101:入力 → 前処理 → 生成 → 後処理 → 出力の責務分離

ComfyUI で画像生成ワークフローを構築する際、ノードを適当に繋げてしまうと、後から修正や拡張が困難になってしまいます。実は、ワークフロー設計にも「責務分離」という考え方を取り入れることで、保守性が高く、再利用しやすい構成を実現できるのです。

本記事では、ComfyUI のワークフロー設計における基本的な考え方として「入力 → 前処理 → 生成 → 後処理 → 出力」という 5 つのフェーズに分けた責務分離の手法を解説します。この設計パターンを身につけることで、複雑なワークフローでも整理された状態を保つことができるでしょう。

背景

ComfyUI のワークフロー設計の重要性

ComfyUI は、Stable Diffusion をはじめとする画像生成 AI をノードベースで操作できる強力なツールです。ノードを自由に配置・接続できる柔軟性がある一方で、設計の指針がないと、ワークフローが複雑化し、メンテナンスが困難になります。

特に以下のような問題が発生しがちです。

  • ノードの接続が複雑に絡み合い、データの流れが追いにくい
  • 同じような処理が複数箇所に散らばり、修正時の漏れが発生する
  • パラメータ変更の影響範囲が把握できず、予期しない結果になる
  • 他のワークフローへの再利用が困難

ソフトウェア設計の原則を応用する

ソフトウェア開発の世界では、「関心の分離(Separation of Concerns)」という設計原則があります。これは、システムを独立した責務を持つ部分に分割し、各部分が明確な役割を担うようにする考え方です。

この原則を ComfyUI のワークフロー設計に応用することで、整理された構造を実現できます。具体的には、ワークフローを「入力」「前処理」「生成」「後処理」「出力」という 5 つのフェーズに分離することで、各段階の責務を明確にするのです。

以下の図は、5 つのフェーズの基本的な流れを示しています。

mermaidflowchart LR
  input["入力フェーズ<br/>データ取得"]
  preprocess["前処理フェーズ<br/>データ準備"]
  generation["生成フェーズ<br/>AI 処理"]
  postprocess["後処理フェーズ<br/>品質向上"]
  output["出力フェーズ<br/>保存・配信"]

  input --> preprocess
  preprocess --> generation
  generation --> postprocess
  postprocess --> output

各フェーズは明確な役割を持ち、前のフェーズから受け取ったデータを処理して次のフェーズに渡します。この一方向のデータフローにより、ワークフロー全体の見通しが良くなります。

図で理解できる要点

  • データは左から右へ一方向に流れる
  • 各フェーズは独立した責務を持つ
  • フェーズ間のインターフェースが明確

課題

ワークフロー設計における典型的な問題

ComfyUI でワークフローを構築する際、以下のような課題に直面することがあります。

問題 1:スパゲッティワークフロー

ノードの接続が複雑に絡み合い、どこから読み始めればよいのか分からない状態です。データの流れを追うのに時間がかかり、バグの原因特定も困難になります。

問題 2:重複した処理

同じような画像リサイズ処理や色調整処理が、ワークフロー内の複数箇所に散在します。パラメータを変更する際、すべての箇所を更新する必要があり、修正漏れのリスクが高まります。

問題 3:変更の影響範囲が不明確

あるノードのパラメータを変更したとき、最終的な出力にどのような影響があるのか予測できません。試行錯誤を繰り返すことになり、作業効率が低下します。

問題 4:再利用性の低さ

別のプロジェクトで似たようなワークフローが必要になっても、既存のワークフローからパーツを取り出して再利用することが困難です。結局、ゼロから作り直すことになります。

以下の図は、問題のあるワークフローと、責務分離されたワークフローの違いを示しています。

mermaidflowchart TB
  subgraph bad["❌ 問題のあるワークフロー"]
    direction LR
    n1["ノード A"] --> n2["ノード B"]
    n1 --> n3["ノード C"]
    n2 --> n3
    n3 --> n2
    n2 --> n4["ノード D"]
    n3 --> n4
    n4 --> n1
  end

  subgraph good["✓ 責務分離されたワークフロー"]
    direction LR
    p1["入力"] --> p2["前処理"]
    p2 --> p3["生成"]
    p3 --> p4["後処理"]
    p4 --> p5["出力"]
  end

問題のあるワークフローでは、ノード間の依存関係が複雑で循環参照も発生しています。一方、責務分離されたワークフローは、明確な一方向の流れを持っています。

設計指針の不足

これらの問題の根本原因は、ワークフロー設計における明確な指針がないことです。「何となく動くから良い」という状態では、ワークフローが大きくなるにつれて問題が深刻化します。

体系的な設計手法を導入することで、これらの課題を解決できるのです。

解決策

5 つのフェーズによる責務分離

ワークフローを「入力 → 前処理 → 生成 → 後処理 → 出力」という 5 つのフェーズに分離することで、各段階の責務を明確にします。

以下、各フェーズの役割と、含めるべきノード、含めるべきでないノードを詳しく見ていきましょう。

フェーズ 1:入力フェーズ

責務:外部からデータを取得し、ワークフローに供給する

入力フェーズは、ワークフローの起点となります。画像、テキスト、パラメータなど、処理に必要なすべての素材をここで読み込みます。

#含めるべきノード説明
1Load Image画像ファイルの読み込み
2Load Checkpointモデルファイルの読み込み
3Text Inputプロンプトの入力
4Primitive ノードパラメータ値の設定
5Load LoRA追加モデルの読み込み

このフェーズで避けるべきこと

  • 画像の加工や変換(前処理フェーズの責務)
  • プロンプトの加工や結合(前処理フェーズの責務)
  • AI モデルの実行(生成フェーズの責務)

入力フェーズは、あくまで「データを取得する」ことに専念します。データの加工は次のフェーズに委ねることで、入力元を変更したい場合の影響範囲を最小化できるのです。

フェーズ 2:前処理フェーズ

責務:入力データを生成フェーズに適した形式に整形する

前処理フェーズでは、入力データを AI モデルが処理しやすい形式に変換します。画像のリサイズ、プロンプトの結合、条件分岐などを行います。

#含めるべきノード説明
1Image Resize画像サイズの調整
2Image Crop画像の切り抜き
3Text Concatenateプロンプトの結合
4CLIP Text Encodeテキストのエンコード
5ControlNet PreprocessorControlNet 用の前処理
6VAE Encode画像の潜在空間への変換

このフェーズで避けるべきこと

  • AI による画像生成(生成フェーズの責務)
  • 生成後の品質向上処理(後処理フェーズの責務)
  • ファイルへの保存(出力フェーズの責務)

前処理フェーズは、「生成の準備」に集中します。ここでデータの形式を統一することで、生成フェーズに渡すデータの品質が安定し、予測可能な結果を得やすくなります。

フェーズ 3:生成フェーズ

責務:AI モデルを使用して画像を生成する

生成フェーズは、ワークフローの中核となる部分です。ここでは、AI モデルによる画像生成のみに専念します。

#含めるべきノード説明
1KSamplerメインのサンプリング処理
2KSampler Advanced高度なサンプリング設定
3ControlNet ApplyControlNet の適用
4IPAdapter ApplyIP-Adapter の適用
5VAE Decode潜在空間から画像への変換

このフェーズで避けるべきこと

  • 入力画像の読み込み(入力フェーズの責務)
  • 画像のリサイズや切り抜き(前処理フェーズの責務)
  • アップスケールや色調整(後処理フェーズの責務)
  • ファイルへの保存(出力フェーズの責務)

生成フェーズを純粋に保つことで、生成パラメータ(ステップ数、CFG スケールなど)の変更が、他のフェーズに影響を与えないようにできます。

フェーズ 4:後処理フェーズ

責務:生成された画像の品質を向上させる

後処理フェーズでは、AI が生成した画像をさらに洗練させます。アップスケール、ノイズ除去、色調整などを行います。

#含めるべきノード説明
1Upscale Image画像の拡大
2Image Sharpenシャープネス調整
3Color Correct色補正
4Face Restore顔の修復
5Image Blend複数画像の合成

このフェーズで避けるべきこと

  • 新たな画像の生成(生成フェーズの責務)
  • ファイルへの保存(出力フェーズの責務)
  • プロンプトの変更(前処理フェーズの責務)

後処理フェーズを独立させることで、生成結果に満足できなかった場合、後処理だけを調整して品質を高めることができます。生成フェーズを再実行する必要がないため、時間を節約できるのです。

フェーズ 5:出力フェーズ

責務:最終的な成果物を保存・配信する

出力フェーズでは、完成した画像をファイルに保存したり、外部システムに送信したりします。

#含めるべきノード説明
1Save Image画像ファイルとして保存
2Preview Imageプレビュー表示
3Send to API外部 API への送信
4Image to Base64Base64 エンコード

このフェーズで避けるべきこと

  • 画像の加工や変換(前処理・後処理フェーズの責務)
  • AI モデルの実行(生成フェーズの責務)

出力フェーズを分離することで、保存形式や保存先を変更する際の影響範囲を限定できます。

フェーズ間のインターフェース設計

各フェーズは、前のフェーズから明確な形式でデータを受け取り、次のフェーズに渡します。このインターフェースを意識することで、フェーズ間の結合度を下げられます。

以下の図は、各フェーズ間で受け渡されるデータの種類を示しています。

mermaidflowchart LR
  input["入力<br/>画像/モデル/テキスト"]
  preprocess["前処理<br/>整形済みデータ"]
  generation["生成<br/>生成画像"]
  postprocess["後処理<br/>高品質画像"]
  output["出力<br/>保存完了"]

  input -->|"画像データ<br/>モデルオブジェクト<br/>テキスト"| preprocess
  preprocess -->|"リサイズ済み画像<br/>エンコード済みプロンプト<br/>Latent"| generation
  generation -->|"生成画像<br/>(Latent/Image)"| postprocess
  postprocess -->|"最終画像"| output
  output -->|"ファイルパス<br/>送信結果"| done["完了"]

図で理解できる要点

  • 各フェーズは決まった形式のデータを受け渡す
  • データの変換は各フェーズ内で完結する
  • 後続のフェーズは前のフェーズの実装詳細を知る必要がない

責務分離の利点

この 5 フェーズ設計を採用することで、以下のメリットが得られます。

#メリット説明
1可読性の向上ワークフローの構造が一目で理解できる
2保守性の向上変更の影響範囲が限定され、修正が容易
3再利用性の向上フェーズ単位で他のワークフローに移植可能
4テストしやすさ各フェーズを独立してテストできる
5並列化の可能性フェーズ内の独立した処理を並列実行できる

具体例

基本的な Text-to-Image ワークフロー

責務分離の考え方を実践した、シンプルな Text-to-Image ワークフローを見ていきましょう。

入力フェーズの実装

まず、必要なモデルとプロンプトを読み込みます。

モデルとプロンプトの読み込み

typescript// CheckpointLoaderSimple ノード
// 役割: Stable Diffusion モデルの読み込み
{
  "id": 1,
  "type": "CheckpointLoaderSimple",
  "inputs": {
    "ckpt_name": "sd_xl_base_1.0.safetensors"
  },
  "outputs": {
    "MODEL": "model_output",
    "CLIP": "clip_output",
    "VAE": "vae_output"
  }
}

このノードは、画像生成に使用する AI モデルをメモリに読み込みます。出力される MODEL、CLIP、VAE は、後続のフェーズで使用されます。

ポジティブプロンプトの入力

typescript// CLIPTextEncode ノード(ポジティブ)
// 役割: 生成したい内容を記述
{
  "id": 2,
  "type": "CLIPTextEncode",
  "inputs": {
    "text": "a beautiful landscape with mountains and lake, sunset, highly detailed",
    "clip": "clip_output"  // ノード 1 から受け取る
  },
  "outputs": {
    "CONDITIONING": "positive_conditioning"
  }
}

ポジティブプロンプトは、画像に含めたい要素を AI に伝えます。CLIP モデルを使用してテキストをエンコードし、AI が理解できる形式に変換します。

ネガティブプロンプトの入力

typescript// CLIPTextEncode ノード(ネガティブ)
// 役割: 生成したくない内容を記述
{
  "id": 3,
  "type": "CLIPTextEncode",
  "inputs": {
    "text": "blurry, low quality, text, watermark",
    "clip": "clip_output"  // ノード 1 から受け取る
  },
  "outputs": {
    "CONDITIONING": "negative_conditioning"
  }
}

ネガティブプロンプトは、画像に含めたくない要素を指定します。これにより、望ましくない特徴を持つ画像が生成される確率を減らせます。

前処理フェーズの実装

前処理フェーズでは、空の潜在空間(Latent)を作成します。Text-to-Image の場合、入力画像がないため、ランダムノイズから開始します。

潜在空間の初期化

typescript// EmptyLatentImage ノード
// 役割: 生成の起点となる空の潜在空間を作成
{
  "id": 4,
  "type": "EmptyLatentImage",
  "inputs": {
    "width": 1024,   // 生成画像の幅
    "height": 1024,  // 生成画像の高さ
    "batch_size": 1  // 一度に生成する枚数
  },
  "outputs": {
    "LATENT": "empty_latent"
  }
}

このノードは、指定されたサイズの空の潜在空間を作成します。AI モデルは、このランダムノイズから徐々に画像を形成していきます。

生成フェーズの実装

生成フェーズでは、KSampler を使用して画像を生成します。

サンプリング処理

typescript// KSampler ノード
// 役割: AI モデルを使用して画像を生成
{
  "id": 5,
  "type": "KSampler",
  "inputs": {
    "model": "model_output",               // ノード 1 から受け取る
    "positive": "positive_conditioning",   // ノード 2 から受け取る
    "negative": "negative_conditioning",   // ノード 3 から受け取る
    "latent_image": "empty_latent",       // ノード 4 から受け取る
    "seed": 42,                           // 再現性のためのシード値
    "steps": 20,                          // サンプリングステップ数
    "cfg": 7.0,                           // プロンプトへの従属度
    "sampler_name": "euler",              // サンプリングアルゴリズム
    "scheduler": "normal",                // スケジューラータイプ
    "denoise": 1.0                        // ノイズ除去の強度
  },
  "outputs": {
    "LATENT": "generated_latent"
  }
}

KSampler は、指定されたステップ数だけ反復処理を行い、ランダムノイズから徐々に画像を形成します。CFG(Classifier-Free Guidance)スケールは、プロンプトへの従属度を制御します。

潜在空間から画像への変換

typescript// VAEDecode ノード
// 役割: 潜在空間の表現を通常の画像に変換
{
  "id": 6,
  "type": "VAEDecode",
  "inputs": {
    "samples": "generated_latent",  // ノード 5 から受け取る
    "vae": "vae_output"             // ノード 1 から受け取る
  },
  "outputs": {
    "IMAGE": "generated_image"
  }
}

VAEDecode は、潜在空間で表現されている画像データを、通常の RGB 画像に変換します。この段階で、初めて人間が視認できる画像が得られます。

後処理フェーズの実装

後処理フェーズでは、生成された画像の品質を向上させます。

アップスケール処理

typescript// ImageUpscaleWithModel ノード
// 役割: AI を使用して画像を高解像度化
{
  "id": 7,
  "type": "ImageUpscaleWithModel",
  "inputs": {
    "upscale_model": "RealESRGAN_x4plus.pth",
    "image": "generated_image"  // ノード 6 から受け取る
  },
  "outputs": {
    "IMAGE": "upscaled_image"
  }
}

このノードは、専用の AI モデルを使用して画像を 4 倍に拡大します。単純な補間とは異なり、ディテールを復元しながら高解像度化できます。

シャープネス調整

typescript// ImageSharpen ノード
// 役割: 画像の輪郭を強調して鮮明さを向上
{
  "id": 8,
  "type": "ImageSharpen",
  "inputs": {
    "image": "upscaled_image",  // ノード 7 から受け取る
    "sharpen_radius": 1,        // シャープネスの適用範囲
    "sigma": 1.0,               // ぼかしの強度
    "alpha": 1.5                // シャープネスの強度
  },
  "outputs": {
    "IMAGE": "final_image"
  }
}

シャープネス処理により、画像の輪郭が強調され、より鮮明な印象になります。過度に適用すると不自然になるため、alpha 値の調整が重要です。

出力フェーズの実装

最後に、完成した画像を保存します。

画像の保存

typescript// SaveImage ノード
// 役割: 画像をファイルシステムに保存
{
  "id": 9,
  "type": "SaveImage",
  "inputs": {
    "images": "final_image",           // ノード 8 から受け取る
    "filename_prefix": "landscape_",   // ファイル名の接頭辞
    "format": "png",                   // 保存形式
    "quality": 95                      // 画質(JPEG の場合)
  }
}

このノードは、処理済みの画像を指定された形式で保存します。filename_prefix にタイムスタンプが自動的に付加され、ファイル名の衝突を防ぎます。

ワークフロー全体の構造

以下の図は、上記のノードがどのように接続され、データが流れるかを示しています。

mermaidflowchart TB
  subgraph input_phase["入力フェーズ"]
    n1["CheckpointLoader<br/>モデル読み込み"]
    n2["CLIPTextEncode<br/>ポジティブ"]
    n3["CLIPTextEncode<br/>ネガティブ"]
  end

  subgraph preprocess_phase["前処理フェーズ"]
    n4["EmptyLatentImage<br/>潜在空間初期化"]
  end

  subgraph generation_phase["生成フェーズ"]
    n5["KSampler<br/>画像生成"]
    n6["VAEDecode<br/>画像変換"]
  end

  subgraph postprocess_phase["後処理フェーズ"]
    n7["ImageUpscale<br/>高解像度化"]
    n8["ImageSharpen<br/>鮮明化"]
  end

  subgraph output_phase["出力フェーズ"]
    n9["SaveImage<br/>保存"]
  end

  n1 -->|MODEL| n5
  n1 -->|CLIP| n2
  n1 -->|CLIP| n3
  n1 -->|VAE| n6
  n2 -->|CONDITIONING| n5
  n3 -->|CONDITIONING| n5
  n4 -->|LATENT| n5
  n5 -->|LATENT| n6
  n6 -->|IMAGE| n7
  n7 -->|IMAGE| n8
  n8 -->|IMAGE| n9

この図からわかるように、各フェーズは明確に分離されており、データは一方向に流れています。

図で理解できる要点

  • 各ノードは対応するフェーズ内に配置される
  • フェーズをまたぐ接続は必要最小限
  • データフローが追跡しやすい

Image-to-Image ワークフローの例

次に、既存の画像を加工する Image-to-Image ワークフローを見てみましょう。

入力フェーズ:画像の読み込み

入力画像の読み込み

typescript// LoadImage ノード
// 役割: 加工元となる画像を読み込む
{
  "id": 11,
  "type": "LoadImage",
  "inputs": {
    "image": "input_photo.jpg"
  },
  "outputs": {
    "IMAGE": "input_image",
    "MASK": "input_mask"
  }
}

このノードは、ファイルシステムから画像を読み込みます。IMAGE 出力は RGB 画像データ、MASK 出力は透明度情報(アルファチャンネル)を含みます。

前処理フェーズ:画像の準備

画像のリサイズ

typescript// ImageScale ノード
// 役割: 画像をモデルに適したサイズに調整
{
  "id": 12,
  "type": "ImageScale",
  "inputs": {
    "image": "input_image",      // ノード 11 から受け取る
    "width": 1024,               // 目標の幅
    "height": 1024,              // 目標の高さ
    "upscale_method": "lanczos", // リサイズアルゴリズム
    "crop": "center"             // クロップ方法
  },
  "outputs": {
    "IMAGE": "resized_image"
  }
}

モデルは特定のサイズの画像で学習されているため、入力画像をそのサイズに合わせることで、より良い結果が得られます。

画像のエンコード

typescript// VAEEncode ノード
// 役割: 画像を潜在空間に変換
{
  "id": 13,
  "type": "VAEEncode",
  "inputs": {
    "pixels": "resized_image",  // ノード 12 から受け取る
    "vae": "vae_output"         // CheckpointLoader から受け取る
  },
  "outputs": {
    "LATENT": "encoded_latent"
  }
}

VAEEncode は、通常の画像を潜在空間の表現に変換します。KSampler は潜在空間で動作するため、この変換が必要です。

生成フェーズ:画像の変換

サンプリング処理(Image-to-Image)

typescript// KSampler ノード
// 役割: 既存の画像を基に新しい画像を生成
{
  "id": 14,
  "type": "KSampler",
  "inputs": {
    "model": "model_output",
    "positive": "positive_conditioning",
    "negative": "negative_conditioning",
    "latent_image": "encoded_latent",  // ノード 13 から受け取る
    "seed": 123,
    "steps": 20,
    "cfg": 7.0,
    "sampler_name": "euler",
    "scheduler": "normal",
    "denoise": 0.7  // 重要: 1.0 より小さい値で元画像を保持
  },
  "outputs": {
    "LATENT": "transformed_latent"
  }
}

Image-to-Image では、denoise パラメータが重要です。1.0 の場合は完全に新しい画像を生成し、0.0 に近いほど元画像を保持します。0.7 程度が、元画像の特徴を残しつつ変換する良いバランスです。

ControlNet を使用した高度なワークフロー

ControlNet を使用すると、生成画像の構図をより細かく制御できます。

前処理フェーズ:ControlNet の準備

ControlNet プリプロセッサ

typescript// ControlNetPreprocessor ノード
// 役割: 入力画像から構図情報を抽出
{
  "id": 21,
  "type": "CannyEdgePreprocessor",
  "inputs": {
    "image": "input_image",
    "low_threshold": 100,   // エッジ検出の下限閾値
    "high_threshold": 200   // エッジ検出の上限閾値
  },
  "outputs": {
    "IMAGE": "canny_edges"
  }
}

このノードは、Canny エッジ検出アルゴリズムを使用して、入力画像から輪郭線を抽出します。この輪郭情報が、生成画像の構図を制御するために使われます。

ControlNet の読み込み

typescript// ControlNetLoader ノード
// 役割: ControlNet モデルを読み込む
{
  "id": 22,
  "type": "ControlNetLoader",
  "inputs": {
    "control_net_name": "control_v11p_sd15_canny.pth"
  },
  "outputs": {
    "CONTROL_NET": "controlnet_model"
  }
}

ControlNet モデルは、特定の種類の制御情報(エッジ、深度、ポーズなど)を扱うように学習されています。

生成フェーズ:ControlNet の適用

ControlNet の適用

typescript// ControlNetApply ノード
// 役割: 生成プロセスに構図制御を追加
{
  "id": 23,
  "type": "ControlNetApply",
  "inputs": {
    "conditioning": "positive_conditioning",
    "control_net": "controlnet_model",  // ノード 22 から受け取る
    "image": "canny_edges",             // ノード 21 から受け取る
    "strength": 0.8                     // 制御の強度
  },
  "outputs": {
    "CONDITIONING": "controlled_conditioning"
  }
}

このノードは、ポジティブコンディショニングに ControlNet の制御情報を追加します。strength パラメータで、元のプロンプトと ControlNet の影響のバランスを調整できます。

制御されたサンプリング

typescript// KSampler ノード
// 役割: ControlNet で制御された画像生成
{
  "id": 24,
  "type": "KSampler",
  "inputs": {
    "model": "model_output",
    "positive": "controlled_conditioning",  // ノード 23 から受け取る
    "negative": "negative_conditioning",
    "latent_image": "empty_latent",
    "seed": 456,
    "steps": 20,
    "cfg": 7.0,
    "sampler_name": "euler",
    "scheduler": "normal",
    "denoise": 1.0
  },
  "outputs": {
    "LATENT": "controlled_latent"
  }
}

ControlNet で拡張されたコンディショニングを使用することで、指定した構図に従った画像が生成されます。

バッチ処理ワークフローの例

複数の画像を一度に処理する場合も、責務分離の原則は有効です。

入力フェーズ:複数画像の読み込み

バッチ画像の読み込み

typescript// LoadImageBatch ノード
// 役割: フォルダ内の複数画像を読み込む
{
  "id": 31,
  "type": "LoadImageBatch",
  "inputs": {
    "directory": "input_photos",  // 読み込むフォルダ
    "image_load_cap": 10,         // 最大読み込み枚数
    "start_index": 0,             // 開始インデックス
    "load_always": false          // 常に再読み込み
  },
  "outputs": {
    "IMAGE": "batch_images"
  }
}

このノードは、指定されたフォルダ内のすべての画像をバッチとして読み込みます。バッチ処理により、複数の画像に同じ処理を効率的に適用できます。

生成フェーズ:バッチ処理

バッチでのサンプリング

typescript// KSampler ノード
// 役割: 複数画像を一度に処理
{
  "id": 32,
  "type": "KSampler",
  "inputs": {
    "model": "model_output",
    "positive": "positive_conditioning",
    "negative": "negative_conditioning",
    "latent_image": "batch_latent",
    "seed": 789,
    "steps": 20,
    "cfg": 7.0,
    "sampler_name": "euler",
    "scheduler": "normal",
    "denoise": 0.5
  },
  "outputs": {
    "LATENT": "batch_output_latent"
  }
}

バッチ入力の場合、KSampler は各画像に対して同じパラメータで処理を実行します。効率的に複数の画像を変換できます。

出力フェーズ:バッチ保存

バッチでの保存

typescript// SaveImage ノード
// 役割: 複数画像を連番で保存
{
  "id": 33,
  "type": "SaveImage",
  "inputs": {
    "images": "batch_output_images",
    "filename_prefix": "batch_output_",
    "format": "png",
    "quality": 95
  }
}

SaveImage ノードは、バッチ内の各画像に自動的に連番を付けて保存します。

実践的な設計パターン

パターン 1:プロンプトテンプレートの活用

複数のワークフローで共通のプロンプト構成を使用する場合、プロンプトテンプレートを入力フェーズに集約します。

typescript// Text Multiline ノード(ベースプロンプト)
// 役割: 共通のプロンプト要素を定義
{
  "id": 41,
  "type": "Text Multiline",
  "inputs": {
    "text": "masterpiece, best quality, highly detailed, 8k"
  },
  "outputs": {
    "STRING": "base_prompt"
  }
}
typescript// Text Concatenate ノード
// 役割: ベースプロンプトと個別プロンプトを結合
{
  "id": 42,
  "type": "Text Concatenate",
  "inputs": {
    "text1": "base_prompt",         // ノード 41 から受け取る
    "text2": "a beautiful forest",  // 個別の内容
    "separator": ", "               // 区切り文字
  },
  "outputs": {
    "STRING": "combined_prompt"
  }
}

このパターンにより、品質向上のための共通フレーズを一箇所で管理でき、すべてのワークフローに反映できます。

パターン 2:条件分岐による動的処理

入力に応じて処理を変更する場合、前処理フェーズで条件分岐を行います。

typescript// Image Size Detector ノード
// 役割: 画像サイズを判定
{
  "id": 51,
  "type": "Image Size Detector",
  "inputs": {
    "image": "input_image"
  },
  "outputs": {
    "WIDTH": "image_width",
    "HEIGHT": "image_height"
  }
}
typescript// Conditional Image Resize ノード
// 役割: サイズに応じてリサイズ方法を変更
{
  "id": 52,
  "type": "Conditional Image Resize",
  "inputs": {
    "image": "input_image",
    "current_width": "image_width",
    "current_height": "image_height",
    "target_size": 1024,
    "method": "adaptive"  // サイズに応じて最適な方法を選択
  },
  "outputs": {
    "IMAGE": "optimized_image"
  }
}

条件分岐を前処理フェーズに集約することで、生成フェーズは入力の形式を気にせず、常に一貫した処理を実行できます。

パターン 3:多段階生成

粗い生成から徐々に詳細化する多段階生成も、責務分離で整理できます。

第 1 段階:低解像度での生成

typescript// EmptyLatentImage ノード(低解像度)
// 役割: まず小さなサイズで生成
{
  "id": 61,
  "type": "EmptyLatentImage",
  "inputs": {
    "width": 512,
    "height": 512,
    "batch_size": 1
  },
  "outputs": {
    "LATENT": "low_res_latent"
  }
}
typescript// KSampler ノード(第 1 段階)
// 役割: 全体的な構図を決定
{
  "id": 62,
  "type": "KSampler",
  "inputs": {
    "latent_image": "low_res_latent",
    "steps": 30,
    "cfg": 7.0,
    "denoise": 1.0
  },
  "outputs": {
    "LATENT": "low_res_result"
  }
}

第 2 段階:高解像度化と詳細化

typescript// Latent Upscale ノード
// 役割: 潜在空間で 2 倍に拡大
{
  "id": 63,
  "type": "Latent Upscale",
  "inputs": {
    "samples": "low_res_result",
    "upscale_method": "bilinear",
    "width": 1024,
    "height": 1024
  },
  "outputs": {
    "LATENT": "upscaled_latent"
  }
}
typescript// KSampler ノード(第 2 段階)
// 役割: 詳細を追加
{
  "id": 64,
  "type": "KSampler",
  "inputs": {
    "latent_image": "upscaled_latent",
    "steps": 20,
    "cfg": 7.0,
    "denoise": 0.5  // 構図は保ちつつディテールを追加
  },
  "outputs": {
    "LATENT": "high_res_result"
  }
}

多段階生成では、各段階を独立したサブフェーズとして扱うことで、各段階のパラメータを独立して調整できます。

まとめ

ComfyUI のワークフロー設計において、「入力 → 前処理 → 生成 → 後処理 → 出力」という 5 つのフェーズに責務を分離することで、整理された構造を実現できます。

各フェーズの役割を再確認しましょう。

#フェーズ主な責務含めるノード例
1入力データの取得Load Image, Load Checkpoint, Text Input
2前処理データの整形Image Resize, CLIP Text Encode, VAE Encode
3生成AI による処理KSampler, ControlNet Apply, VAE Decode
4後処理品質向上Image Upscale, Image Sharpen, Color Correct
5出力保存・配信Save Image, Preview Image

この設計パターンを採用することで、以下のメリットが得られます。

可読性の向上

ワークフローの構造が一目で理解でき、新しいメンバーでもすぐに全体像を把握できます。

保守性の向上

変更の影響範囲が明確になり、安心してパラメータ調整や機能追加ができます。

再利用性の向上

フェーズ単位で切り出して、他のワークフローに移植できます。テンプレート化も容易です。

テストのしやすさ

各フェーズを独立してテストでき、問題の切り分けが簡単になります。

責務分離は、小規模なワークフローでは過剰に感じるかもしれませんが、ワークフローが成長するにつれて、その価値が明確になります。最初から整理された構造を保つことで、長期的なメンテナンスコストを大幅に削減できるのです。

ComfyUI でワークフローを作成する際は、ぜひこの 5 フェーズ設計を意識してみてください。整理された美しいワークフローは、作業効率を高めるだけでなく、作業そのものを楽しくしてくれるはずです。

関連リンク