
画像生成モデルで動画化したキャラを、ステレオグラムで立体化し、VR/MR で対面してみました:
- ※1
- 生成画像は、次のモデルを使用しています:
- ・
- https://huggingface.co/WarriorMama777/OrangeMixs
関連
- ◯
- ComfyUI で画像生成 〜 なぜそこにつなぐのか:ComfyUI, Stable Diffusion
- ◯
- ComfyUI を、クラウドのコンテナに設置する:ComfyUI, Docker, GCP
- ◯
- ComfyUI の API を使う:ComfyUI, API, JSON, Python, Docker, GCP
検証:修正版
- ◯
- アプリ
- ・
- 画像生成:ComfyUI
- ◯
- VR/MR
- ・
- PC〜HMD間ストリミーング:Virtual Desktop
- ・
- HMD:Quest 2
- ◯
- サーバ
- ・
- クラウド:GCP
- ・
- コンテナ:Docker
- ・
- ホスト:Ubuntu 22.04
- ・
- ゲスト:Ubuntu 22.04
検証:通常版
- ◯
- アプリ
- ・
- 画像生成:ComfyUI
- ・
- 画像編集:GIMP
- ◯
- VR/MR
- ・
- PC〜HMD間ストリミーング:Virtual Desktop
- ・
- HMD:Quest 2
- ◯
- クライアント
- ・
- コンテナ:Docker Desktop
- ・
- ホスト:macOS
- ・
- ゲスト:Ubuntu 22.04
- ◯
- サーバ
- ・
- クラウド:GCP
- ・
- コンテナ:Docker
- ・
- ホスト:Ubuntu 22.04
- ・
- ゲスト:Ubuntu 22.04
概要
いまは、一枚の画像から深度を推定する技術が進歩し、立体視化もそれほど違和感のないレベルに達しています。また画像生成モデルだけでも、あるていどの動画生成は可能になっています。
ここでは、キャラの動画を立体視できるようにして、VR/MR(パススルー)で対面してみます。
前提
ComfyUI では、次のカスタムノード群を使います:
- ◯
- 動画生成(AnimateDiff)
- ・
- https://github.com/Kosinkadink/ComfyUI-AnimateDiff-Evolved
文章(プロンプト)から生成される画像を、さらに動画化します。
- ◯
- 物体検出(Rembg/U2-Net)[※1]
- ・
- https://github.com/cubiq/ComfyUI_essentials[※2]
- ・
- https://github.com/Jcd1230/rembg-comfyui-node[※3]
動画のフレーム(画像)群から物体(キャラ)を検出し、背景と切り離しますーーこれで、目的の対象だけを表示できるようになります。
- ◯
- 深度推定(ControlNet Auxiliary Preprocessors/MiDaS)[※4]
- ・
- https://github.com/Fannovel16/comfyui_controlnet_aux
動画のフレーム(画像)群から奥行きを推定し、深度マップ群を作ります。
- ◯
- 立体視化(NegiTools/Stereo Image Generator)
- ・
- https://github.com/natto-maki/ComfyUI-NegiTools[※5]
動画のフレーム(画像)群とその深度マップ群から、ステレオグラムのフレーム群を作成しますーーこれで、VR/MR で、対象を立体視できるようになります。
- ※1
- Rembg は、汎用の物体検出モデル U2-Net を使うコードの総称です。
- ※2
- このカスタムノードは、バッチ処理に対応し、アニメ調を検出できるモデルも使うことができます。ただしパッケージの構成は、物体検出のノード以外にもさまざまなノードがふくまれたものです(アプリ本体の構成によっては、依存関係に問題が出るかもしれません)。
- ※3
- このカスタムノードは、バッチ処理に対応しておらず、アニメ調を検出できるモデルも使えません。ただしパッケージの構成は、物体検出のノードのみですーーこのノードでアニメ調のキャラを使うなら、フラットな塗りだと誤検出も多いので、厚塗り系と呼ばれる生成モデルを使う方がいいかもしれません(それでもフラットな塗りのキャラを使いたいならーーたとえば、ここでの処理は深度マップを生成するので、GIMP などを使い、一定の深さの箇所(背景)を黒の画像と合成する、といったやり方があるかも)。
- ※4
- ここでは深度マップの生成に、MiDaS / control_v11f1p_sd15_depth を使っていますーー生成は軽く速いものの、推定の精度はそれなりですーーキャラがリアル調なら Marigold、アニメ調ならLine2Depth (Line2Normalmap) などを使うと、より精度の高い深度マップが生成されるはずです:
- ・
- https://github.com/kijai/ComfyUI-Marigold
- ・
- https://huggingface.co/toyxyz/Line2Depth_sd1.5
- ※5
- このカスタムノードは、バッチ処理に対応していません。また(おそらく入力される深度マップの想定が違うため、ここでの使用のしかただと)平行法(left-right)のステレオグラムを作成するのに、交差法(R-L)を選択する必要がありますーーコード自体は、次の A1111 / Stable Diffusion web UI 向けプラグインのラッパです:
- ・
- https://github.com/thygate/stable-diffusion-webui-depthmap-script
設置〜作成
カスタムノード群の設置とステレオグラムの作成は、次の2とおりの手順を示しています:
- ・
- 修正版……設置がノーマルでない代わりに、作成はノンストップになります[※1]
- ・
- 通常版……作成がノンストップでない代わりに、設置はノーマルになります
- ※1
- ステレオグラムのカスタムノードがバッチ処理に対応していないので、これを修正したものを使いますーーこれですべての処理を、ワークフロー上で完結させることができます。
設置:修正版
- ※
- この手順は、設置がノーマルでない代わりに、作成はノンストップになります。
次の手順で、物体検出/立体視化のカスタムノード群を設置します:
- ◯
- 物体検出(Rembg/U2-Net)
カスタムノードのフォルダを作成します:
$ cd ${directory_project}
$ mkdir ComfyUI_essentials_002
$ cd ${directory_project}/ComfyUI/custom_nodes
$ ln -s ${directory_project}/ComfyUI_essentials_002 ComfyUI_essentials_002
カスタムノードのファイルを作成します:
- ・
- ComfyUI_essentials_002/__init__.py
import torch
import torchvision.transforms.v2 as T
def p(image):
return image.permute([0,3,1,2])
def pb(image):
return image.permute([0,2,3,1])
class RemBGSession:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"model": (["u2net: general purpose", "u2netp: lightweight general purpose", "u2net_human_seg: human segmentation", "u2net_cloth_seg: cloths Parsing", "silueta: very small u2net", "isnet-general-use: general purpose", "isnet-anime: anime illustrations", "sam: general purpose"],),
"providers": (['CPU', 'CUDA', 'ROCM', 'DirectML', 'OpenVINO', 'CoreML', 'Tensorrt', 'Azure'],),
},
}
RETURN_TYPES = ("REMBG_SESSION",)
FUNCTION = "execute"
CATEGORY = "essentials"
def execute(self, model, providers):
from rembg import new_session as rembg_new_session
model = model.split(":")[0]
return (rembg_new_session(model, providers=[providers+"ExecutionProvider"]),)
class ImageRemoveBackground:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"rembg_session": ("REMBG_SESSION",),
"image": ("IMAGE",),
},
}
RETURN_TYPES = ("IMAGE", "MASK",)
FUNCTION = "execute"
CATEGORY = "essentials"
def execute(self, rembg_session, image):
from rembg import remove as rembg
image = p(image)
output = []
for img in image:
img = T.ToPILImage()(img)
img = rembg(img, session=rembg_session)
output.append(T.ToTensor()(img))
output = torch.stack(output, dim=0)
output = pb(output)
mask = output[:, :, :, 3] if output.shape[3] == 4 else torch.ones_like(output[:, :, :, 0])
return(output[:, :, :, :3], mask,)
NODE_CLASS_MAPPINGS = {
"RemBGSession+": RemBGSession,
"ImageRemoveBackground+": ImageRemoveBackground
}
NODE_DISPLAY_NAME_MAPPINGS = {
"RemBGSession+": "🔧 RemBG Session",
"ImageRemoveBackground+": "🔧 Image Remove Background"
}
ライブラリ群を設置します(ComfyUI が動く環境で):[※1]
$ pip install rembg[gpu] $ pip install kornia
- ◯
- 立体視化(Stereo Image Generator)
カスタムノードのフォルダを作成します:
$ cd ${directory_project}
$ mkdir ComfyUI-NegiTools_002
$ cd ${directory_project}/ComfyUI/custom_nodes
$ ln -s ${directory_project}/ComfyUI-NegiTools_002 ComfyUI-NegiTools_002
ラッパ元のソースコードを取得しますーーステレオグラムの生成に必要なのは、次のファイルのみです:
- ・
- https://github.com/thygate/stable-diffusion-webui-depthmap-script/blob/main/src/stereoimage_generation.py
$ cd ${directory_project}/ComfyUI-NegiTools_002
$ mkdir -p negi dependencies/stable-diffusion-webui-depthmap-script/src
$ cd ${directory_project}/ComfyUI-NegiTools_002/dependencies/stable-diffusion-webui-depthmap-script/src
$ wget https://raw.githubusercontent.com/thygate/stable-diffusion-webui-depthmap-script/main/src/stereoimage_generation.py
初期化ファイルを作成します:
- ・
- ComfyUI-NegiTools_002/__init__.py
from .negi.stereo_image_generator_002 import StereoImageGenerator_002
NODE_CLASS_MAPPINGS = {
"NegiTools_StereoImageGenerator_002": StereoImageGenerator_002
}
NODE_DISPLAY_NAME_MAPPINGS = {
"NegiTools_StereoImageGenerator_002": "StereoImageGenerator_002 🧅"
}
カスタムノードを修正します:
- ・
- https://github.com/natto-maki/ComfyUI-NegiTools/blob/master/negi/stereo_image_generator.py
これは、[1]バッチ処理ができるようコードを追加し、[2]深度マップを補正するコードを削除したものですーーこの修正により、(API などを介することなく)ノンストップで動画生成から立体視化まで行え、ステレオグラムも平行法(L-R)が使えるようになります(なお(VR/MR には不要ということもあり)、平行法以外のステレオグラム生成のコードも削除しています):
- ・
- ComfyUI-NegiTools_002/negi/stereo_image_generator_002.py
import importlib
import numpy as np
import torch
import torchvision
_dependency_dir = "dependencies"
_repository_name = "stable-diffusion-webui-depthmap-script"
class StereoImageGenerator_002:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"l_image": ("IMAGE",),
"l_depth_image": ("IMAGE",),
"divergence": ("FLOAT", {
"default": 5.0, "min": 0.05, "max": 10.0, "step": 0.01, "round": 0.001, "display": "slider"
}),
"stereo_offset_exponent": ("FLOAT", {
"default": 1.0, "min": 0.1, "max": 3.0, "step": 0.1, "round": 0.01, "display": "slider"
}),
"fill_technique": ([
"polylines_sharp", "polylines_soft", "naive", "naive_interpolating", "none"
],),
}
}
RETURN_TYPES = ("IMAGE",)
RETURN_NAMES = ("STEREO_IMAGE",)
FUNCTION = "doit"
OUTPUT_NODE = False
CATEGORY = "Generator"
def doit(self, l_image, l_depth_image, divergence, stereo_offset_exponent, fill_technique):
m = importlib.import_module(
"." + ".".join([_dependency_dir, _repository_name, "src", "stereoimage_generation"]),
".".join(__name__.split(".")[:-2]))
modes = ["left-right"]
l = []
for (i, _) in enumerate(l_image):
image = torchvision.transforms.functional.to_pil_image(torch.permute(l_image[i], (2, 0, 1)))
depth_map = l_depth_image[i].to('cpu').detach().numpy()[:, :, 0]
images = m.create_stereoimages(
image, depth_map, divergence, modes=modes,
stereo_offset_exponent=stereo_offset_exponent, fill_technique=fill_technique)
l.append(torchvision.transforms.functional.to_tensor(images[0]).permute(1, 2, 0).unsqueeze(0))
return (torch.cat(l),)
- ※1
- 物体検知のモデル U2-Net は、ホームディレクトリに配置されます:
- ・
- ${HOME}/.u2net/u2net.onnx
作成:修正版
- ※
- この手順は、設置がノーマルでない代わりに、作成はノンストップになります。
次の手順で、動画のステレオグラム群を作成します:
次のフローを実行すれば、動画のステレオグラム(GIF 形式)がノード上に生成されますーーこのノードから、ファイルをローカルに保存します。:
- ・
- ${directory_development}/cnf/try004_workflow_stereogram.json
設置:通常版
- ※
- この手順は、作成がノンストップでない代わりに、設置はノーマルになります。
次の手順で、物体検出/立体視化のカスタムノード群を設置します:
- ◯
- 物体検出(Rembg/U2-Net)
カスタムノードを取得します:
$ cd ${directory_project}
$ git clone --depth=1 https://github.com/Jcd1230/rembg-comfyui-node.git
$ cd ${directory_project}/ComfyUI/custom_nodes
$ ln -s ${directory_project}/rembg-comfyui-node rembg-comfyui-node
ライブラリ群を設置します(ComfyUI が動く環境で):[※1]
$ pip install rembg[gpu] $ pip install kornia
- ◯
- 立体視化(Stereo Image Generator)
カスタムノードのフォルダを作成します:[※2]
$ cd ${directory_project}
$ mkdir ComfyUI-NegiTools_002
$ cd ${directory_project}/ComfyUI/custom_nodes
$ ln -s ${directory_project}/ComfyUI-NegiTools_002 ComfyUI-NegiTools_002
カスタムノードのファイルを取得しますーーステレオグラムの生成に必要なのは、次のファイルのみです:
- ・
- https://github.com/natto-maki/ComfyUI-NegiTools/blob/master/negi/stereo_image_generator.py
- ・
- https://github.com/natto-maki/ComfyUI-NegiTools/blob/master/negi/noise_image_generator.py
$ cd ${directory_project}/ComfyUI-NegiTools_002
$ mkdir negi
$ cd ${directory_project}/ComfyUI-NegiTools_002/negi
$ wget https://raw.githubusercontent.com/natto-maki/ComfyUI-NegiTools/master/negi/stereo_image_generator.py
$ wget https://raw.githubusercontent.com/natto-maki/ComfyUI-NegiTools/master/negi/noise_image_generator.py
初期化ファイルを作成します:
- ・
- ComfyUI-NegiTools_002/__init__.py
from .negi.stereo_image_generator import StereoImageGenerator
NODE_CLASS_MAPPINGS = {
"NegiTools_StereoImageGenerator": StereoImageGenerator
}
NODE_DISPLAY_NAME_MAPPINGS = {
"NegiTools_StereoImageGenerator": "Stereo Image Generator 🧅"
}
- ※1
- 物体検知のモデル U2-Net は、ホームディレクトリに配置されます:
- ・
- ${HOME}/.u2net/u2net.onnx
- ※2
- ノードが最初に使われるときに、ラッパするライブラリを取得するため、次のリポジトリからフォルダ内に、コード群が配置されます:
- ・
- https://github.com/thygate/stable-diffusion-webui-depthmap-script
作成:通常版
- ※
- この手順は、作成がノンストップでない代わりに、設置はノーマルになります。
次の手順で、動画のステレオグラム群を作成します:
まず、動画のフレーム群を生成しますーー最初は、Save Image ノードは無効にしておいた方がいいかもしれません。
生成結果を動画(GIF )で確認しつつ、プロンプトやシードを決めていきます。決まったら、履歴からその状態に戻り、Save Image ノードを有効にします。ふたたび生成を実行すると、動画のフレームになる画像群が、output フォルダに保存されます:
- ・
- ${directory_development}/cnf/try004_workflow_animatediff.json

フォルダ output に生成されたフレーム群を、フォルダ input に移します:
$ cd ${directory_comfyui}/input
$ mv ${directory_comfyui}/output/??????_???_?????_.png .
ComfyUI の API を使って、動画のフレーム群をステレオグラムのフレーム群に変換します。
元になるワークフローは、画像1枚ごとに、[1]背景を抜く〜[2]深度マップを作る〜[3]ステレオグラムのフレームを作る、ように構成します(なお、Rembg ノードが出力する背景は透過状態(アルファチャネルでの指定)になるので、Split Image with Alpha ノードを介して(Stable Diffusion があつかえる)通常の画像のマスクに変換します)。
このワークフローを、フレームの枚数分だけ繰り返すよう、スクリプトを作成します。このスクリプトを、Python を実行できる環境から、ComfyUI が動く環境に対し、実行します:[※1]
- ・
- ${directory_development}/cnf/try004_workflow_api_stereogram.json

- ・
- ${directory_development}/bin/try004.py
import sys
import time
import json
from urllib import request
def reqcmf(dctflw):
prompt = {'prompt': dctflw}
conweb = json.dumps(prompt).encode('utf-8')
reqweb = request.Request(f"{urltgt}prompt", data=conweb)
request.urlopen(reqweb)
urltgt = sys.argv[1]
strflw = sys.stdin.read()
dctflw = json.loads(strflw)
l = list(range(1,17))
for i in l:
dctflw['2']['inputs']['image'] = 'try004_frm_' + f'{i:05}' + '_.png'
reqcmf(dctflw)
time.sleep(1)
$ cat ${directory_development}/cnf/try004_workflow_api_stereogram.json | python ${directory_development}/bin/try004.py http://${server}:${port}/
作成されたステレオグラムのフレーム群を取得します:
$ cd ${directory_working}
$ scp ${user}@${server}:${directory_comfyui}/output/'??????_???_?????_.png' .
画像編集アプリの GIMP で、フレーム群から動画ファイル(GIF)を作ります:[※2]
> file open: <image_1>.png
> open as layers: <image_2>.png, ..., <image_N>.png # 一括での選択可
> filters > animation >
> optimize (for gif)
> playback # 再生を確認
> file > export as
> select file type (by extension)
> <image>.gif
> [export]
> as animation: <yes>

- ※1
- ここのカスタムノードの”Image batch To Image List”を使っても、たんにテンソルをリストに変換するだけでしょうし……:
- ・
- https://github.com/ltdrdata/ComfyUI-Impact-Pack
- ※2
- このステレオグラムは、平行法でみることができますーー連続した画像でも、統一した立体感が与えられていることを確認できると思います(統一感はあるものの、右足の遠近感が微妙ですね……ただこの生成画像だとこうするしかなさそうで、深度推定はがんばってると思います)。
利用
動画ファイル(GIF)を、Virtual Desktop Streamer が動く環境に移します。
HMDで Virtual Desktop を実行し、動画ファイル(GIF)を画像ビューアで開きますーーデスクトップ画面のモードを、SBS (Side-by-Side) + Transparency にすることで、生成した動くキャラと MR で対面できます:[※1][※2][※3]
> menu (bottom) > full sbs > menu (top) > transparency
- ※1
- 動画の上下がはみ出すときは、ディスプレの向きの設定を縦にするなどし、表示できる領域を確保します。
- ※2
- Windows 既定の画像ビューアや、その他のビューアも使えます(IrfanView, ...)。
- ※3
- ここでクロマキーが使えればいいんですけどねーーVirtual Desktop のデスクトップ画面は、黒色を透過させるだけですーーSteamVR のメディアプレイヤなら(Virtual Desktop のゲーム内になるので)SBS でもクロマキーが使えますが、画面の位置調整が煩雑など、使い勝手がよくありませんしーー一長一短です……
感想
目の前にいるキャラは、奥行きがあり動いてもいるので、じっさいにその場にいるように感じられますーーこれがたった数文字のプロンプトから生まれた存在だと考えると、感慨深いものがありますね。[※1][※2]
- ※1
- いまのところただの動画なので、触れてもスカスカな状態ですーーただ深度マップはもっているので、HMDの深度センサと組み合わせれば、<触れる>感触を得ることも可能ですーーこれがリアルタイムに生成できるようになれば、インタラクティブ性をもたせることもできるでしょうし、物理演算をふくめた多彩な反応が得られそうです。
- ※2
- もちろんその数文字のプロンプトの背後には、膨大な数の創作者(絵画〜写真)の熱意と努力の成果がありますーー生成モデルの普及で、それらの利用のしかたが、<個別の作品を鑑賞・体験する>というかたちから、<作品の集合を融合・体験する>というかたちに変わってきていますーー問題のひとつは、もともとあった<出典>(作家と作品など)という構造が、フラットになってしまっている、という点です。ここをどう解決していくかが、技術側の課題といえそうです。
