🍜

ノードエディタ形式の画像・オーディオ処理ツール「Multimodal-Node-Editor」

に公開

はじめに


ノードエディタ形式の画像・オーディオ処理ツール「Multimodal-Node-Editor」を作りました。

今まで以下の、ノードエディタ形式の画像処理ツール「Image-Processing-Node-Editor」、オーディオ処理ツール「Audio-Processing-Node-Editor」を作ってきました。

https://qiita.com/Kazuhito/items/03741785301f5f47caa2
https://zenn.dev/kazuhito/articles/bef57423d9c181
しかし、これらのアプリは、初期設計をワーッとやって、実装をガーッとやった結果、
色々設計が微妙なところとか、実装が微妙なところとか、
良かれと思って入れた設計が裏目に出ていたところとか、
ちょいちょいあり、ずーっと作り直したいなー。と思っていました。
(特にノード追加方法とか、GUIに自由度を持たせようとした結果、実装漏れが発生しやすいつくりになっていたり。。。)

改めて、設計・実装を刷新し、画像やオーディオを1つのノードエディターで扱えるようにしたのが、「Multimodal-Node-Editor」です。
https://github.com/Kazuhito00/Multimodal-Node-Editor

ちなみに、裏の目的として「フルコーディングエージェントで、ある程度の規模のアプリを作る」経験をしたかった。と言う目的があります。
今まで業務や趣味では、小さなデモ作成や機能追加・機能修正でしか使用していなかったため、使い勝手やハマリどころを体験したく。

ちなみにちなみに、今回も見切り発車でClaude Codeを駆使して作り始め、
その後、年末年始にスペック駆動開発の本を読んだ結果、
早くもClaude Code周りの設定を刷新して、設計・実装も刷新したい気持ちが湧いています😇

まあ「家は3回建てないと理想の家にならない」と言われるように、
ソフトウェアも3回は設計・実装を刷新しないと満足いくものは出来ませんよね。。。( ´ー`)y-~~

「Multimodal-Node-Editor」とは

以下のように、ノードを接続していくことで、画像処理やオーディオ処理、OpenAI API を利用した LLM や VLM が実行できるツールです。
ちなみに「Image-Processing-Node-Editor」は常に処理が動作していましたが、オーディオ処理と統合した結果、Startボタンで明確に処理開始を指示する仕様に変更しました。

特徴

以下のような特徴があります。

  • 画像・音声・テキスト・深層学習など、100 以上のノードを標準搭載
  • TOML + Python による新規ノードの簡単な追加が可能(GUI を自作しない場合)
  • Cropなどの一部ノードでマウス操作での指定に対応
  • 簡易オートレイアウト機能
  • ノードへのコメント機能
  • Google Colaboratory バックエンドでのシステム起動可能
  • 保存したグラフを ReactFlow GUI なしでヘッドレス実行可能
  • モデルウェイトの保存方法を変更し、「Image-Processing-Node-Editor」「Audio-Processing-Node-Editor」では対応していなかった100MB以上のサイズの重みへ対応
  • 「Image-Processing-Node-Editor」「Audio-Processing-Node-Editor」の放置不具合の根本改善(ノード配置前しかグラフjsonをロードできない問題、ノードを重ねた時にヒストグラムなどの表の表示が崩れる問題、エッジ接続解除時に線描画が残る問題、など)

いくつかの特徴の詳細はこちら↓

詳細:TOML + Python による新規ノードの簡単な追加が可能(GUI を自作しない場合)

「Image-Processing-Node-Editor」では、GUI実装の自由度が高すぎたため、
TOML(ノードのメタデータ + GUI情報)とPython(実装)に分離して、GUIパーツを絞ることで比較的シンプルな実装になるよう変更。
※GUIパーツを用意する形式にしたため、パーツにないGUIを作ろうとするとそれなりに苦労が発生しますが。。。 ※既に再設計したい箇所の第一位😇
以下は、Image/Filter/Cannyノードの実装例。

toml例
name = "image.filter.canny"
version = "1.0.0"
display_name = "Canny"
description = "Applies Canny edge detection to an image."
order = 50
gui = ["reactflow", "headless"]

[[ports]]
name = "image"
data_type = "image"
direction = "inout"

[[ports]]
name = "low_threshold"
data_type = "float"
direction = "in"

[[ports]]
name = "high_threshold"
data_type = "float"
direction = "in"

[[properties]]
name = "low_threshold"
display_name = "Low"
type = "int"
default = 50
widget = "slider"
min = 0
max = 255

[[properties]]
name = "high_threshold"
display_name = "High"
type = "int"
default = 150
widget = "slider"
min = 0
max = 255
python例
from typing import Dict, Any
from node_editor.node_def import ComputeLogic
import cv2

class CannyNodeLogic(ComputeLogic):
    """
    Cannyエッジ検出を実行するノードロジック。
    入力・出力ともにOpenCV画像(numpy配列)。Base64変換はcore.pyで自動処理。
    """

    def compute(
        self,
        inputs: Dict[str, Any],
        properties: Dict[str, Any],
        context: Dict[str, Any] = None,
    ) -> Dict[str, Any]:
        img = inputs.get("image")
        if img is None:
            return {"image": None}

        low_threshold = int(properties.get("low_threshold", 50))
        high_threshold = int(properties.get("high_threshold", 150))

        # グレースケールに変換
        if len(img.shape) == 2 or img.shape[2] == 1:
            gray = img
        else:
            gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

        edges = cv2.Canny(gray, low_threshold, high_threshold)

        # 出力をBGRに変換(他ノードとの互換性のため)
        edges_bgr = cv2.cvtColor(edges, cv2.COLOR_GRAY2BGR)

        return {"image": edges_bgr}

詳細:Cropなどの一部ノードでマウス操作での指定に対応

数は多くありませんが、以下のようにマウス操作で領域指定などが出来るノードを実装しました。

詳細:簡易オートレイアウト機能

簡易的なアルゴリズムのため、つなぎ方によってはエッジが交差してしまいますが、
オートレイアウト機能を用意しました。

詳細:ノードへのコメント機能

ノードに対してコメントを付与できるようになりました。

詳細:Google Colaboratory バックエンドでのシステム起動可能

システム構成を、ReactFlow ⇔ FastAPI ⇔ 自作ノードエディターコア に変更し、
Google Colaboratoryバックエンドでの起動も可能になりました。

詳細:保存したグラフを ReactFlow GUI なしでヘッドレス実行可能

たとえば、ReactFlow上で作成した以下のようなフローをグラフjsonとして保存して、

その後、ヘッドレス実行用のスクリプトrun_headless.pyで、ノードエディターを介さずに実行ができます。
※プレビュー画像などをReactFlow⇔FastAPIでやりとりしているため、ReactFlow利用時ではGUIのオーバーヘッドが数ms~数十msありました

アーキテクチャ概要

ざっくり概要ですが、以下のように、フロントエンド(ReactFlow)、バックエンド(FastAPI)、コア、ノード群のような構成になっています。

フロントエンド(ReactFlow):
 ノードエディタUI、画像/音声プレビュー表示、履歴管理 など
バックエンド(FastAPI):
 REST APIエンドポイント提供 など
コア:
 データモデル定義、グラフ実行エンジン など
ノード群:
 ノードの構成定義、個々のノードの計算ロジック実装 など

動作シーケンスのざっくり概要は以下↓

アプリ起動シーケンス
グラフ実行シーケンス(START〜実行ループ〜STOP)

動作例

今までの「Image-Processing-Node-Editor」「Audio-Processing-Node-Editor」で出来なかった事例をいくつか記載しています。

運転動画を15秒おきに OpenAI VLM で解析

LLIE した動画に Before/After の HSV ヒストグラムを重畳表示

動画へウェーブフォームやスペクトログラムを結合

OpenAI LLM を用いた Python コードの自動生成

まとめ

画像処理と音声処理、それぞれで作っていたノードエディターを統合し、
設計・実装を一から見直した「Multimodal-Node-Editor」を公開しました。

今回の Multimodal-Node-Editor では、
・画像・音声・テキスト・深層学習を1つのノードエディターで扱えること
・TOML + Python による比較的シンプルなノード追加方法
・ReactFlow を使わずに実行できるヘッドレス実行
・Colaboratory をバックエンドにシステム起動
・過去ツールで抱えていた設計・実装上の問題の改善
といった点を重視して作り直しています。
(すでに「ここは再設計したい……」という箇所はちょいちょいありますが😇)

興味があれば、ぜひ触ってみてください🦔

Discussion