ControlNet Canny + LoRA でモニタ配置を制御する — テンプレート描画と生成実験

ControlNet Canny + LoRA でモニタ配置を制御する — テンプレート描画と生成実験 — ControlNet, Canny, LoRA AI画像

こんにちは、パレイド技術部です。

前回の記事(第2回)では、SDXL で生成した画像に対して OpenCV で画面領域を検出するアプローチを試しました。

結果として見えてきたのは、画像の「ブラウン管」の検出の難易度という現実的な問題でした。生成側がどこにモニタを描くかはプロンプトとチェックポイント任せで、それを後から探すのは至難の業でした。

今回は発想を転換し、生成そのものを制御する方向に進みます。使うのは ControlNet の Canny モードです。

ControlNet Canny の考え方

ControlNet は、拡散モデルの生成プロセスに条件画像(conditioning image)を注入する仕組みです。Canny モードでは、エッジマップ(輪郭線の画像)を入力として受け取り、生成画像がそのエッジ構造に沿うようにガイドします。

つまり、ControlNet を使ってこういうアプローチが取れます。

  1. 黒背景にモニタのベゼルを白い矩形で描いたテンプレートを作る
  2. そのテンプレートを Canny ControlNet に渡す
  3. 拡散モデルがベゼルの位置に沿ってモニタを生成する

生成の段階で「ここにモニタを置け」と指示できるので、後から探しやすくなるはずです。理論的には。

テンプレート描画の実装

テンプレート画像の生成も Python + OpenCV で行います。

レイアウトプリセット

モニタの配置は正規化座標(0.0〜1.0)で定義します。画像解像度に依存しない設計です。

PRESETS = {
    "3_monitors": {
        "monitors": [
            {  # 左デスクモニタ
                "cx": 0.20, "cy": 0.55,
                "w": 0.22, "h": 0.18,
                "on_desk": True,
            },
            {  # 中央壁掛けモニタ
                "cx": 0.50, "cy": 0.35,
                "w": 0.28, "h": 0.22,
                "on_desk": False,
            },
            {  # 右デスクモニタ
                "cx": 0.80, "cy": 0.55,
                "w": 0.22, "h": 0.18,
                "on_desk": True,
            },
        ],
        "desk_y": 0.70,
    },
}

cx, cy が中心座標、w, h が幅と高さです。on_desk はデスク上に載っているかどうかを示し、描画時にデスク面の水平線との位置関係を調整します。

テンプレート生成関数

正規化座標から実ピクセルに変換し、cv2.rectangle でベゼルとスクリーン面を描画します。

import cv2
import numpy as np

def generate_template(
    preset_name: str,
    width: int = 1024,
    height: int = 1024,
    bezel_color: int = 255,
    screen_color: int = 80,
    bezel_thickness: int = 3,
) -> np.ndarray:
    """Canny ControlNet 用のテンプレート画像を生成する。"""
    canvas = np.zeros((height, width), dtype=np.uint8)
    preset = PRESETS[preset_name]

    for mon in preset["monitors"]:
        # 正規化座標 → ピクセル座標
        cx = int(mon["cx"] * width)
        cy = int(mon["cy"] * height)
        half_w = int(mon["w"] * width / 2)
        half_h = int(mon["h"] * height / 2)

        # 外枠(ベゼル)— 白線
        pt1 = (cx - half_w, cy - half_h)
        pt2 = (cx + half_w, cy + half_h)
        cv2.rectangle(canvas, pt1, pt2, bezel_color, bezel_thickness)

        # 内側(スクリーン面)— グレー線
        margin = bezel_thickness + 4
        inner_pt1 = (pt1[0] + margin, pt1[1] + margin)
        inner_pt2 = (pt2[0] - margin, pt2[1] - margin)
        cv2.rectangle(canvas, inner_pt1, inner_pt2, screen_color, 1)

    # デスク面の水平線
    if "desk_y" in preset:
        desk_px = int(preset["desk_y"] * height)
        cv2.line(canvas, (0, desk_px), (width, desk_px), bezel_color, 2)

    return canvas

ベゼル外枠は白(255)、スクリーン内枠はグレー(80)で描いています。この明度差は意図的なもので、Canny ControlNet が外枠のエッジをより強く拾うようにしています。デスク面の水平線を入れることで、テーブルの存在もある程度ガイドできます。

生成されるテンプレートは、黒い背景に白い矩形が3つ並び、下部にデスクの水平線が走るシンプルな画像になります。

Cannyの例。シンプルな方がうまくいきやすい

ComfyUI ワークフロー

テンプレート画像を ControlNet に渡すワークフローを ComfyUI で構築しました。主要なパラメータは以下の通りです。

パラメータ
チェックポイントjuggernautXL_v8Rundiffusion
ControlNet モデルcontrol-lora-canny-rank256.safetensors
KSamplerdpmpp_2m_sde, steps=35, cfg=4.5
ControlNet strength0.7
ControlNet start%0.0
ControlNet end%0.8

チェックポイントには SDXL 系のリアル系モデルを選んでいます。ControlNet の強度は 0.7 に設定しました。1.0 にするとテンプレートの線がそのまま残ってしまい、0.5 以下だとガイドが弱すぎてモニタの配置が崩れます。end% を 80% にしているのは、最後の 20% をモデルのディテール生成に任せるためです。

Cannyに沿った生成がされる例

プロンプト設計

プロンプトには時間帯による照明の変化と、画面表示のバリエーションを入れています。

(positive)
retro living room with multiple CRT television monitors,
{morning sunlight|evening amber light|dim night lighting},
screen showing {static noise|retro game|VHS tape content},
realistic photograph, detailed interior, 8k uhd

(negative)
cartoon, anime, illustration, text, watermark,
blurry, low quality, deformed

{} 内はバッチ生成時にランダム選択される要素です。時間帯と画面内容の組み合わせで多様な画像を生成し、どの条件で検出しやすいかを探る狙いがあります。

結果と改善点

良くなった点

テンプレートなしの純粋なプロンプト生成と比較して、明確な改善が見られました。

  • モニタが指定した位置の近辺に出現し、意図した構成がおおむね再現されました。
  • デスク面の水平線により、「机の上に載っている」「壁に掛かっている」の区別がつきやすくなりました
  • レイアウトプリセットを変えるだけで配置パターンを切り替えられるので、再現性のある実験が可能になりました

前回の OpenCV 検出と組み合わせた場合、正面に近いアングルで検出成功率が約 50〜60% まで向上しました。プロンプトだけの生成では 20〜30% 程度だったので、倍近い改善です。

また、よりシンプルに配置を1台にしたりテレビの周囲の空間を広く取ることで表現の自由度を生かすこともできます。

生成されたCannyの例
比較的、うまくCannyに沿って画像が生成された例。よく見ると(よく見なくても)それ以外は色々おかしい

残った問題

しかし、自動化パイプラインに組み込むには精度が足りません。

画面内部の問題: ベゼルの白線は外形をガイドしますが、画面の中身(スクリーン面)は依然としてモデル任せです。反射、映り込み、意図しないテクスチャが描かれることが多く、OpenCV でスクリーン領域を切り出そうとすると境界が曖昧になります。

パース問題: Canny テンプレートは正面から見た矩形しか描けません。生成画像にパースがかかると、テンプレートの矩形と実際のモニタ形状がずれます。ControlNet は「だいたいこの辺にエッジがある」程度の制御なので、斜めのアングルでは精度が大きく落ちます。

残った課題の整理

ここまでの実験で、Canny ControlNet の限界が見えてきました。

  1. 2D エッジしか制御できない — 奥行きやパースの情報がないため、3D 空間としての整合性を保証できません
  2. スクリーン内部が不確定 — エッジの「外形」は制御できても「中身」は制御できません。画面に何が映るかはモデル次第です
  3. 複数オブジェクトの分離が不安定 — モニタが近接すると融合しやすく、ControlNet の強度調整だけでは解決しません

これらはすべて、「2D のエッジマップ」という入力形式の本質的な制約に起因しています。

まとめ

ControlNet Canny を導入したことで、モニタの空間配置に対する制御力は確実に向上しました。テンプレート描画による再現可能なレイアウト指定は、プロンプトだけに頼っていた前回からの大きな前進です。

しかし、2D エッジマップでは奥行き制御ができず、スクリーン内部の描画も制御できないという根本的な限界が残りました。検出成功率 50〜60% では、自動パイプラインに組み込むには不十分です。

次回(第4では、この問題を根本から解決するために Depth ControlNet と Blender を組み合わせたアプローチに進みます。3D 空間全体をコントロールすることで、パースとスクリーン位置の両方を確定させる方法を探ります。

タイトルとURLをコピーしました