NVIDIA Cosmos Transfer 2.5 で車載映像を霧・雨・夜に変換してみた

NVIDIA Cosmos Transfer 2.5 で車載映像を霧・雨・夜に変換してみた

学習データの拡充に使える NVIDIA Cosmos Transfer 2.5 を NVIDIA Brev の H100 インスタンスで動かし、公式サンプルの車載映像を霧・雨・夜へ変換してみました。エッジ制御映像に輪郭が写っていない領域は、入力と異なる内容が生成されることが分かりました。
2026.08.31

こんにちは、製造ビジネステクノロジー部 モビリティチーム の若槻です。

自動運転の開発では、学習データをどのように集めるかが常に課題になります。雨・霧・夜・逆光といった条件の映像は、実際に自動車を走らせて撮影しようとすると手間もコストもかかりますし、そもそも危険な条件ほどデータが集まりません。

そこで候補になるのが、既にある走行映像を別の条件へ変換するアプローチです。NVIDIA Cosmos Transfer は、入力動画の構造を保持したまま天候や時間帯を作り変えられる、まさにこの用途に適したモデルとなっています。

本記事では Cosmos Transfer 2.5 を NVIDIA Brev の H100 インスタンスで動かし、公式サンプルの車載映像の霧・雨・夜への変換を試してみました。

先に結論

今回の検証では、主に次の 2 点が分かりました。

  1. エッジ制御映像に輪郭が写っていない領域は、入力と異なる内容が生成されやすい。モデルは輪郭が写っていない領域を、入力ではなくプロンプトだけから生成するため
  2. vis 制御は、入力の大まかな明るさの配置を出力でも保持する。そのため、明るさを変えるプロンプトの指示が反映されない場合がある

Cosmos Transfer 2.5 とは

Cosmos Transfer 2.5 は、NVIDIA の動画生成モデル Cosmos-Predict2.5 をもとに、制御映像で生成結果を条件付けられるようにしたモデルです。入力動画から作った制御映像(エッジ、ブラー、深度、セグメンテーション)で「入力のどの要素を保持するか」をモデルに伝えつつ、プロンプトで指定した天候や時間帯に外観を変更します。本記事では、この条件付けの仕組みを「制御」、その入力になる映像を「制御映像」と呼びます。

ライセンスは、GitHub リポジトリで公開されている推論コードや設定ファイルと、Hugging Face で配布されているモデルウェイト(学習済みのパラメータ)とで異なります。推論コードは Apache 2.0 なので改変や再配布の制約が少ない一方、モデルウェイトは NVIDIA 独自のライセンスで利用条件が定められています。その条件は、この章の最後にまとめます。

対象 ライセンス
推論コード(GitHub リポジトリ) Apache 2.0
モデルウェイト(Hugging Face) NVIDIA Open Model License

Cosmos Transfer 2.5 で指定できる制御は 4 種類で、それぞれ保持する入力の要素が異なります。制御映像は、生成の設定ファイル(spec)の control_path で指定するか、指定しなければ入力動画から自動生成されます。

制御 保持する入力 control_path 未指定時の生成元
edge 輪郭・エッジ Canny Edge
vis 大まかな明暗と色の配置 Bilateral Gaussian Blur
depth 奥行き VideoDepthAnything
seg 領域分割 GroundingDINO(base) + SAM2

edge の生成元となる Canny は、画像から輪郭線を抽出する古典的なアルゴリズムです。画素(ピクセル。画像を構成する最小の点)ごとに明るさの変化量を求め、その変化量がしきい値を超えた画素を輪郭として検出します。後述する preset_edge_threshold は、このしきい値の高さを指します。

vis 制御の役割

4 つの制御のうち、本記事の検証で指定するのは edge と vis の 2 つです。edge は輪郭を保持する制御だと名前から想像できますが、vis は名前から役割が分かりにくいので、ここで vis について予め説明しておきます。

vis の制御映像は、入力動画に強いブラーをかけたものです。細部は完全に失われますが、「どこが明るくてどこが暗いか」「全体としてどんな色みか」という情報は保持されます。目を細めて景色を見たときに見える情報と考えると分かりやすいと思います。

入力映像から自動生成された vis の制御映像
入力映像から自動生成された vis の制御映像(後述する夜への変換時のもの)

上の画像は、後述する夜への変換のときに入力映像から自動生成された vis の制御映像です。輪郭がまったく分からないほどぼやけていますが、空が白く、路面がグレーであることは見て取れます。

この「大まかな明暗と色の配置」を、画像処理では低周波成分と呼びます。画像の中で明るさがゆるやかに変化している部分が低周波成分で、細かい模様や輪郭のように明るさが急に変化している部分が高周波成分です。上のぼやけた vis 制御映像は、高周波成分を除いて低周波成分だけを残した画像の例になります。vis はこの低周波成分だけをモデルに渡す制御です。そのため、細部は入力に合わせず、全体の明るさの配置だけを入力に合わせるという作用をします。

以降で「輝度構造」と書いた場合は、この「大まかな明暗の配置」のことを指します。

前述のとおり、制御映像は control_path を指定しなければ入力動画から自動生成されるので、ユーザーが事前に用意する必要はありません。本記事の検証でもすべて自動生成に任せており、自動生成された制御映像は後で実際に確認します。

NVIDIA Open Model License の主な条件

モデルウェイトに適用される NVIDIA Open Model License(2025 年 10 月 24 日版)の主な条件は次のとおりです。

  • 商用利用と、派生モデルの作成・配布ができます。生成した出力について NVIDIA は権利を主張しません
  • モデルを再配布する場合は、ライセンスの写しと「Licensed by NVIDIA Corporation under the NVIDIA Open Model License」という表記を添付する必要があります
  • Cosmos モデルを使った製品やサービスを提供する場合や、Cosmos モデルやその出力で AI モデルを学習・改善する場合は、Web サイトやドキュメントなどに「Built on NVIDIA Cosmos」と表示する必要があります
  • モデルに組み込まれたガードレールなどの安全機構を回避・無効化すると、同等の機構を代わりに用意していない限り、ライセンスは自動的に終了します
  • 利用は NVIDIA の Trustworthy AI の条件に沿う必要があります

業務で使う場合は、特に「Built on NVIDIA Cosmos」の表示義務と、ガードレールを無効化できないことを事前に確認しておく必要があります。

検証環境

検証環境には NVIDIA Brev で H100 インスタンス 1 台を立てて使用しました。

H100 は NVIDIA のデータセンター向け GPU です。ゲーム向けの GeForce とは別系統の製品で、サーバーに搭載して生成 AI の学習や推論に使われます。個人の PC に搭載できる GPU ではないので、こうしたクラウドサービスで時間単位で利用するのが現実的です。

今回使用したのは PCIe 版の 1 枚構成でした。H100 には接続方式の違いで PCIe(Peripheral Component Interconnect Express)版と SXM 版があり、SXM 版のほうが高速で、複数枚をつないで使う用途に向きます。SXM は NVIDIA 独自の、基板に直接取り付ける接続方式です。PCIe は拡張カードを挿す一般的な規格で、今回は 1 枚だけ使うのでこちらで十分です。

nvidia-smi で確認したところ、搭載メモリは 81,559 MiB(約 80GB) でした。nvidia-smi は、NVIDIA のドライバに付属している GPU の状態を見るコマンドです。GPU 名・メモリ量・使用率・消費電力などが分かります。

Brev の基本的な使い方は同僚の田中が詳しく書いているので、インスタンスの作り方と接続方法はそちらをご覧ください。

Brev の GPU 選択画面
Brev のコンソールでインスタンス作成時に GPU を選択する様子

構成は以下のとおりです。

項目 値
GPU NVIDIA H100 PCIe 80GB(81,559 MiB)
CPU / RAM 28 CPU / 180GB
ドライバ 570.195.03(CUDA 12.8)
OS Ubuntu 22.04.5 LTS
Python 3.10.12
料金 $3.00/hr(ストレージ込み)

CUDA は、NVIDIA の GPU に計算をさせるための基盤となるソフトウェア群です。PyTorch のような機械学習ライブラリは、CUDA を介して GPU を使います。CUDA のバージョンとライブラリが対応するバージョンが合っていないとライブラリが GPU を認識できず、環境構築ではこの 2 つのバージョンを合わせる必要がありました。

後述する cu128 という表記は「CUDA 12.8 向けにビルドされたもの」という意味です。

H100 を選んだ理由

公式ドキュメントの要件が以下となっているため、当初は下位環境の L40S 48GB($1.74/hr)で十分だと考えていました。

用途 最小 GPU 数 GPU あたり VRAM
単一ビュー推論 1 24GB+
マルチコントロール推論 1 40GB+
マルチビュー / auto 7 24GB+
ポストトレーニング 8 80GB+

ところが、同僚の森茂が DGX Spark で試した際の記事に、気になる記述がありました。

DGX Spark で NVIDIA Cosmos 世界基盤モデルを動かしてみた

制御信号(エッジ制御映像、深度マップなど)からリアルな映像を生成する Transfer 2.5 も試みましたが、2B モデルでも 65.4GB の VRAM を必要とし

公称 24GB に対して実測は 65.4GB で、2.7 倍です。DGX Spark は unified memory の ARM 環境(CPU と GPU が 1 つのメモリを共有する構成)で、GPU 専用の VRAM を持つ H100 とはメモリの構成が異なるので、そのまま比べられる数字ではありません。それでもこの差を無視して 48GB を選ぶのは危険だと判断し、H100 80GB に変更しました。

結論から言うと、この判断は正解でした。後述しますが、実測は 65,679 MiB となり、48GB では収まりませんでした。

その他注意点

Brev は従量課金で、プロバイダによってはインスタンスの停止ができないなどの制約もあります。インスタンスを選ぶ画面にも注意点として表示されます。意識しておいた方が良いので少し触れておきます。

Brev のコスト内訳と注意書き
インスタンス選択画面に表示される料金の内訳と注意書き

① ストレージの課金条件(Configure Storage)

Storage is billed based on the amount provisioned and continues to accrue even when your instance is stopped.

確保した容量に対して課金され、インスタンスを停止していても課金され続けます。

② インスタンスの制約(Instance Attributes)

プロバイダによっては、次の 2 点の制約が表示されます。

  • This instance cannot be stopped or restarted(停止・再起動ができない)
  • If your organization runs out of credits, Brev deletes the instance and all data on it(クレジットが尽きるとインスタンスごと削除され、データは復旧できない)

共有のクレジットを使っている場合は、後者の影響が大きくなります。

③ 料金の内訳(Cost Breakdown)

料金の内訳は Compute と Storage に分かれて表示され、今回選んだ構成では Storage が Included でしたが、プロバイダによっては Storage に別途金額が加算されます。

インスタンスを起動すると、コンソールに次のように表示されました。

起動した H100 インスタンス
起動した H100 インスタンス

セットアップ

手順そのものはリポジトリの Setup ドキュメントに書かれているので、そちらをご覧ください。ここでは手順中でハマった箇所を中心にご紹介します。

  1. ディスクの置き場所(コンソールの表示と実体が違う)
  2. Python のバージョン(リポジトリの指定どおりだとインストールできない)
  3. Hugging Face のゲート同意(同意が必要なリポジトリが 3 つある)
  4. ゲート状態の確認方法(API の応答では判断できない)
  5. トークン登録(失敗しても原因が分からない)

いずれも原因が分かっていれば数分で対処できますが、知らないと原因の特定に合計で数時間を費やします。実際、私は 1 と 2 で環境の作り直しになりました。

コンソールの表示と実際のディスク容量の違い

Brev のコンソールにはディスクが 850GB と表示されていましたが、ルートディスク(/ にマウントされているディスク)は 97GB しかありませんでした。

$ df -h
Filesystem      Size  Used Avail Use% Mounted on
/dev/vda1        97G   21G   76G  22% /
/dev/vdb        738G  180M  700G   1% /ephemeral

残りの容量は /ephemeral にマウントされていました。モデルのチェックポイントは後述のとおり 34GB あり、既定の設定のままではルートディスク側のホームディレクトリ配下にダウンロードされて、97GB の容量を圧迫します。そこで、Hugging Face のライブラリがモデルなどをキャッシュするディレクトリを指す環境変数 HF_HOME に /ephemeral/hf を設定し、モデルのチェックポイントのダウンロード先を容量の大きい /ephemeral 側にしました。

export HF_HOME=/ephemeral/hf

.python-version と依存関係のバージョンの不一致

リポジトリの .python-version には 3.13 と書かれていますが、そのまま従うと次のエラーでインストールに失敗しました。

error: Distribution `flash-attn==2.7.3+cu128.torch27` can't be installed
       because it doesn't have a source distribution or wheel for the current platform

hint: You're using CPython 3.13 (`cp313`), but `flash-attn` (v2.7.3+cu128.torch27)
      only has wheels with the following Python ABI tag: `cp310`

依存インデックスを確認したところ、flash-attn の cp313 ホイールは cu128・cu130 のどちらにも存在しませんでした。用意されているのは cp310 と一部 cp312 だけです。.python-version がリポジトリ自身の依存インデックスと矛盾しています。

uv venv --python 3.10 のように Python 3.10 を明示したところ、インストールできました。

sudo apt-get update
sudo apt-get install -y git-lfs curl ffmpeg libx11-dev tree wget
git lfs install

git clone https://github.com/nvidia-cosmos/cosmos-transfer2.5.git
cd cosmos-transfer2.5
git lfs pull

curl -LsSf https://astral.sh/uv/install.sh | sh
export PATH="$HOME/.local/bin:$PATH"

uv python install 3.10
uv venv --python 3.10 .venv
uv sync --extra=cu128 --python 3.10
source .venv/bin/activate

インストール後に、Cosmos Transfer 2.5 が推論に使う機械学習ライブラリの PyTorch と、Transformer の注意機構(Attention。入力のどの部分に注目するかを計算する仕組み)の計算を高速化するライブラリの flash-attn が、CUDA 12.8 向けのバージョンで入っていることを確認しました。

$ python -c "import torch; print(torch.__version__, torch.version.cuda, torch.cuda.get_device_name(0))"
2.7.0+cu128 12.8 NVIDIA H100 PCIe
$ python -c "import flash_attn; print(flash_attn.__version__)"
2.7.3

Hugging Face で同意が必要な 3 つのリポジトリ

Cosmos のモデルは gated です。gated とは、Hugging Face でモデルの配布に制限がかかっている状態を指します。ページでライセンスに同意しないとファイルをダウンロードできません。同意が必要なリポジトリは以下の 3 つあります。

リポジトリ 用途
nvidia/Cosmos-Transfer2.5-2B 本体
nvidia/Cosmos-Guardrail1 ガードレール
nvidia/Cosmos-Predict2.5-2B Wan2.1 VAE / tokenizer

Transfer のモデルは VAE と tokenizer を Predict 側のリポジトリから取得するため、3 つ目の Predict のリポジトリにも同意が必要です。Transfer を動かすために Predict のリポジトリが必要になるというのは見落としやすいポイントなので注意しましょう。

ここで出てきた VAE と tokenizer という用語について補足します。

  • VAE(Variational Autoencoder、変分オートエンコーダ)— 動画を、元の画素数よりはるかに少ない数値で表した圧縮表現(潜在表現)に変換し、また画素の集まりである動画に戻す仕組みです。動画をそのまま画素単位で扱うと計算量が大きすぎるので、モデルはこの圧縮表現の上で生成し、最後に VAE で画素単位の動画へ戻します
  • tokenizer — ここではプロンプトの文章をモデルが扱える数値の並びに変換する処理です

どちらも生成に欠かせないので、取得できないとモデルは動きません。同意を忘れると、実行が次のエラーで止まります。

Error: Access denied. This repository requires approval.
uvx 'hf>=1.3.5' download nvidia/Cosmos-Predict2.5-2B --repo-type model --revision ... tokenizer.pth

いずれも gated: auto なので、モデルページで "Agree and access repository" を押すと即座に使えるようになり、承認待ちは発生しませんでした。

なお depth や seg の制御を自動生成する場合、さらに depth-anything/Video-Depth-Anything-Large、facebook/sam2-hiera-large、IDEA-Research/grounding-dino-base が必要ですが、これらは gated ではないので手続きは不要です。

API の 200 応答ではゲートの同意状態を判断できない問題

同意状況を確認しようとして、最初は次のようにしていました。

curl -s -o /dev/null -w "%{http_code}" -H "Authorization: Bearer $HF_TOKEN" \
  "https://huggingface.co/api/models/nvidia/Cosmos-Predict2.5-2B/tree/main"
# => 200

ファイル一覧のメタデータは同意前でも取得できるため、この方法では同意前でも 200 が返ります。実ファイルの取得は別扱いなので、実際の可否を見るなら resolve にリクエストする必要があります。

curl -s -o /dev/null -w "%{http_code}" -I -L -H "Authorization: Bearer $HF_TOKEN" \
  "https://huggingface.co/nvidia/Cosmos-Predict2.5-2B/resolve/main/tokenizer.pth"

hf auth login でトークンの入力内容が表示されない問題

トークンの入力は画面に表示されません。貼り付けが空でも次のエラーしか表示されないため、トークンが無効なのか貼り付けに失敗したのか判別できません。

requests.exceptions.HTTPError: Invalid user token.

このような場合は先に curl で切り分けをしましょう。

read -rs -p "token: " T; echo
curl -s -H "Authorization: Bearer $T" https://huggingface.co/api/whoami-v2 | head -c 300

ユーザー情報が返ればトークンは有効です。なお Fine-grained トークンでも、追加の権限設定なしで gated リポジトリを読み取りできました。

動かしてみる

変換に使った入力映像

変換する入力映像には、公式サンプルの assets/car_example/ を使いました。

入力映像(晴天のダッシュカム映像)
入力映像(晴天のダッシュカム映像)

付属の car_prompt.txt によれば、次のような映像です。

ダッシュカムから撮影した都市部の走行シーン。多車線道路、前方に黒い車、歩道に街路樹と歩行者、ロードコーンでの車線規制、晴天

解像度などの仕様は 1280×720 / 29.97fps / 121 フレーム / 約 4 秒です。

最小構成での実行

まず、制御映像を自動生成に任せた場合の挙動と、生成にかかる VRAM・時間を確かめるために、公式サンプルの最小構成の spec をそのまま実行しました。

car_vis_spec.json は 6 行しかありません。

{
  "name": "car_vis",
  "prompt_path": "../car_prompt.txt",
  "video_path": "../car_input.mp4",
  "vis": {
    "control_weight": 1
  }
}

また上記 spec には control_path がありませんね。つまり制御映像のファイルを指定していないので、前述のとおり入力動画から制御映像が自動生成されるはずです。実際にそうなるかを確認するために、この spec のまま実行しました。

python examples/inference.py -i assets/car_example/vis/car_vis_spec.json -o outputs/car_vis

ログを見ると、制御パスがすべて None のまま生成が進んでいました。

Processing the following paths: {'edge': None, 'edge_mask': None, 'edge_mask_prompt': None,
                                 'vis': None, 'vis_mask': None, 'vis_mask_prompt': None}

出力ディレクトリの内容は次のとおりでした。

car_vis.mp4
car_vis_control_vis.mp4   ← 自動生成された制御映像
car_vis.json
config.yaml
console.log
debug.log

car_vis_control_vis.mp4 が生成されていますね。制御映像は自動生成されるだけでなく、ファイルとして出力ディレクトリに保存されるということです。この挙動は、後で生成結果の原因を調べるときに重要になります。

VRAM の実測

次に、Cosmos Transfer 2.5 の実行にどれだけの VRAM が必要かを確かめるために、生成中の使用量を計測しました。H100 を選んだ判断が妥当だったかを確認する目的もあります。

VRAM(Video RAM、ビデオメモリ)は GPU が持つ専用のメモリです。モデルの重みや計算の途中結果はここに格納されるので、不足すると処理が途中で止まってしまいます。

厄介なのは、使用量が生成の進み具合につれて増えたり減ったりすることです。ある時点の値を 1 回確認しただけでは、生成の過程でいちばん多いときにどれだけ使ったのかが分かりません。

そこで、nvidia-smi を -l 2 オプション付きで実行し、使用量を記録し続けました。-l 2 は 2 秒ごとに現在値を出力し続けるオプションです。生成が終わるまで記録し、あとからその中の最大値を抽出しました。本記事で「ピーク」と書いているのはこの最大値です。

nvidia-smi --query-gpu=timestamp,memory.used,utilization.gpu --format=csv,noheader -l 2 > vram.csv

memory.used が使用中の VRAM、utilization.gpu が GPU の使用率です。

計測結果は次のとおりです。

構成 VRAM ピーク
vis のみ 65,679 MiB
edge + vis 67,517 MiB

vis のみの構成で 65,679 MiB(約 64GB)、edge + vis の構成で 67,517 MiB(約 66GB)でした。森茂が DGX Spark で測った 65.4GB という数字が、別環境でほぼそのまま再現された形になりました。

公式ドキュメントの「単一ビュー推論 24GB+」とは大きく開きがあります。ただし、今回試したのは 720p・121 フレーム・num_steps 35 という条件だけで、解像度やフレーム数を落とせば収まる可能性を除外していないため、この記述が誤りだと断定はできません。

ここで、直前の文で触れた num_steps と、それに関係する Distilled モデルという用語について補足します。

  • num_steps — ノイズを取り除いて映像にしていく処理の反復回数です。既定は 35 で、回数が多いほど品質は上がりますが、生成時間は回数に比例して長くなります
  • Distilled モデル — 蒸留(distillation)という手法で軽量化された派生モデルです。大きいモデルの出力を教師にして小さいモデルを訓練したもので、Cosmos Transfer 2.5 では num_steps を 4 まで減らせる Distilled Edge モデルが公開されています

今回の検証では試していませんが、Distilled モデルを使えば工程が 35 回から 4 回に減るので、速度もメモリの使用量も変わるはずです。

今回の検証で分かったのは、既定の設定で 720p の動画を生成する場合は 24GB では不足する、ということです。

所要時間

VRAM と同じく、1 本の生成にどれだけ時間がかかるかを確かめるために、ログから所要時間を集計しました。最小構成を初めて実行したときの内訳は次のとおりです。表のチェックポイントとは、学習済みモデルの重みを保存したファイルのことで、初回の実行時に Hugging Face から取得されます。

項目 値
合計(チェックポイント取得込み) 1,017 秒(約 17 分)
うちチェックポイント取得(34GB) 90 秒(約 1 分 30 秒)
うちサンプリング(13.47 秒/ステップ × 35 ステップ) 472 秒(約 7 分 52 秒)

この表で、サンプリングは約 8 分なのに、合計の所要時間はチェックポイント取得の 90 秒を差し引いても約 15 分あります。サンプリング時間だけを見て所要時間を見積もると、実際の所要時間を大きく見誤ることになります。

この差がどこで生じているのかを確かめるために、後述する霧・雨・夜の 3 本を 1 回の実行でまとめて生成したときのログから、区間ごとの開始・終了時刻と所要時間を抽出しました。

区間 開始 終了 所要
モデルロード 10:51:30 10:53:13 1 分 43 秒
1 本目の生成 10:53:13 11:10:09 16 分 56 秒
2 本目の生成 11:10:09 11:26:57 16 分 48 秒
3 本目の生成 11:26:57 11:43:47 16 分 50 秒

1 本あたり約 17 分で、ばらつきはほとんどありません。サンプリングが 7 分 52 秒なので、残りの約 9 分が別の処理です。この時間には、次の処理が含まれます。

  • 制御映像の自動生成 — 入力動画から、Canny でエッジ制御映像を、ブラーで vis の制御映像を作る処理です
  • ガードレール判定 — プロンプトとネガティブプロンプト(生成結果に含めたくない要素を指定する文)の両方について、不適切な内容が含まれていないかを専用のモデルで検査する処理です(後述)
  • VAE デコード — 生成した圧縮表現(潜在表現)を、画素単位の動画に戻す処理です
  • 動画エンコード — 画素単位の動画を MP4 ファイルに変換する処理です

3 本の実行を時系列で示すと次のようになります。図では各生成の内訳を、サンプリングとサンプリング以外の処理の 2 つに色分けして示しています。実際には、制御映像の生成とガードレール判定はサンプリングの前に、VAE デコードと動画エンコードは後に実行されます。

3 本をまとめて生成したときのタイムライン(緑はモデルロード、青はサンプリング、灰色はサンプリング以外の処理である制御映像の生成・ガードレール判定・VAE デコード・動画エンコード)

パラメータ探索の計画を立てるときは、この約 9 分を含めた 1 本あたり約 17 分で見積もる必要があります。例えば num_steps 35 × 13.47 秒 = 約 8 分で見積もると、パラメータを 10 通り試す計画は約 1 時間 20 分になりますが、実際には約 3 時間かかる計算です。4 秒の動画 1 本に 17 分という時間は、気軽に試行を繰り返せる長さではありません。

依存モデルの数とキャッシュの容量

Cosmos Transfer 2.5 の本体は「2B モデル」と呼ばれています。2B はパラメータ数が 20 億(2 billion)という意味で、生成 AI のモデルとしては小規模な部類に入ります。しかし、実行に必要なモデルは本体だけではなく、次の 4 つのリポジトリから学習済みの重みなどを取得する必要があり、ダウンロードしたファイルの合計は 34GB になりました。

リポジトリ 用途
nvidia/Cosmos-Transfer2.5-2B 本体
nvidia/Cosmos-Guardrail1 ガードレール
nvidia/Cosmos-Predict2.5-2B Wan2.1 VAE / tokenizer
nvidia/Cosmos-Reason1-7B Qwen2.5-VL-7B-Instruct ベース

34GB は、この 4 つのリポジトリから取得した学習済みの重みと設定ファイルの合計で、HF_HOME に指定したディレクトリに保存されました。本体の 2B モデルのほかに、70 億パラメータの Cosmos-Reason1-7B が含まれています。

ここでいうガードレールは、危険な内容や不適切な内容を生成しないよう入出力を検査する仕組みです。Cosmos では専用モデル(Cosmos-Guardrail1)が用意されていて、検査に通らないと生成が止まります。

このガードレールは、プロンプトだけでなくネガティブプロンプト(生成結果に含めたくない要素を指定する文。後述の spec では negative_prompt)に対しても実行され、検査を通過すると実行ログに次の行が出力されます。

Passed guardrail on negative prompt

霧・雨・夜に変換する

ここからが本題で、プロンプトを差し替えて霧・雨・夜の 3 パターンを生成しました。霧の spec は次のようになります。雨と夜の spec は、name・prompt・negative_prompt だけを差し替えたもので、guidance・num_steps・seed・edge・vis の設定は 3 本とも同じです。

{
  "name": "car_fog",
  "video_path": "/path/to/car_input.mp4",
  "prompt": "A driving scene through a modern urban environment, captured from a dashcam inside a vehicle. Dense fog fills the air, heavily reducing visibility. (以下省略)",
  "negative_prompt": "Clear sunny weather, high visibility, crisp sharp horizon, cloudless sky, bright sunshine, transparent air.",
  "guidance": 7,
  "num_steps": 35,
  "seed": 42,
  "edge": { "control_weight": 0.35, "preset_edge_threshold": "medium" },
  "vis":  { "control_weight": 0.24, "preset_blur_strength": "very_high" }
}

preset_edge_threshold と preset_blur_strength は、制御入力を自動生成するときの強さを決めるプリセットです。どちらも very_low / low / medium / high / very_high の 5 段階で、既定は medium です。

フィールド 何を決めるか
preset_edge_threshold Canny のしきい値。低いほど細かいエッジを検出します(ノイズも検出します)
preset_blur_strength ブラーの強さ。強いほど細部が失われ、大まかな輝度だけが保持されます

control_weight が「その制御をどれだけ強く適用するか」なのに対し、こちらは「そもそもどんな制御映像を作るか」を決めます。作用が違うので、混同しないように気を付けましょう。

-i での複数 spec の指定

-i に複数の spec を渡すと、モデルを 1 回だけロードして順次実行されます。

python examples/inference.py \
  -i specs/car_fog.json specs/car_rain.json specs/car_night.json \
  -o outputs/weather

3 本で 3,151 秒(52 分 31 秒)でした。モデルのロードには時間がかかるので、パラメータを変更して比較したいときに便利です。

guidance の値の範囲

spec を書くときに気づいた点ですが、ソースコードを見ると guidance の上限が決まっていました。

Guidance = Annotated[int, pydantic.Field(ge=0, le=7)]

公式サンプルの spec では 3 ですが、今回は天候の変化を強く反映させたかったので、上限の 7 を指定しました。型定義で 7 以下に制限されているため、8 以上を指定しても spec の読み込み時に検証エラーになり、これ以上は上げられません。変換をさらに強めたい場合は control_weight の配分やプロンプト側で調整することになります。

結果

4 分割で並べた比較映像がこちらです。左上が入力、右上が霧、左下が雨、右下が夜です。

https://youtu.be/KqFkvwqjF8w

天候の表現そのものは、よく再現されています。

  • 霧: 路面が濡れて光を反射し、前走車のテールランプが滲み、ビルが霞んで見えなくなります
  • 雨: 路面の照り返し、飛沫、そしてプロンプトに書いた「歩道の傘」まで再現されました
  • 夜: 建物の窓が橙色に灯り、ネオンや信号機が光ります

霧に変換した結果
霧に変換した結果

雨に変換した結果
雨に変換した結果

夜に変換した結果
夜に変換した結果

そして、車線、矢印マーキング、前走車 2 台は、3 本とも入力と同じ位置に生成されています。

入力と異なる内容が生成される領域

ここからが本記事の要点です。

出力をよく見ると、入力に写っていないものが生成されている箇所があります。

  • 画面下部にダッシュボードとボンネットが出現(霧と雨)
  • 左手前の高層ビルが別の建物に置き換わる
  • 中央奥のビルが曲面タワーとネオン看板に変わる(雨)

これらの箇所について入力と異なる内容が生成された理由は、自動生成されたエッジ制御映像を見ると分かります。

自動生成されたエッジ制御映像
自動生成されたエッジ制御映像

領域 エッジ制御映像での検出 出力
車線・矢印マーキング くっきり検出 入力どおり
前走車 2 台 輪郭あり 入力どおり
右手のビル群 窓枠まで検出 入力どおり
左手前の高層ビル ほぼ無検出(一様なガラス面) 別の建物に置き換わる
画面下部 ほぼ無検出(空の路面) 入力に写っていないダッシュボードが生成

エッジ制御映像に輪郭が写っていない領域では、モデルは入力を参照せずプロンプトだけから生成します。コントラストが低く一様な面ほどエッジが検出されないので、入力と異なる内容になりやすいです。

ただし、この領域に毎回同じものが生成されるわけではありません。ダッシュボードは霧と雨では生成されましたが、夜では生成されませんでした。輪郭が写っていない領域は、毎回同じ結果になるのではなく、毎回何が生成されるか分からない領域です。

対処としては preset_edge_threshold を下げる方向が考えられます。very_low から very_high の 5 段階があり、低いほど細かいエッジを検出します(ノイズも検出します)。

制御映像とプロンプトの競合

夜に変換した結果には、入力と異なる内容が生成される問題とは別に、プロンプトの指示が反映されない箇所がありました。

プロンプトには「空は暗い」と書いています。建物の窓は灯り、ネオンも信号機も出ているので、モデルは「夜」の指示を反映しています。それなのに、空だけが入力の明るい灰色のままでした。

自動生成されたブラー制御映像。空が明るいまま保持されている
自動生成されたブラー制御映像(空が明るいまま保持されている)

空が明るいままだった原因は、vis 制御だと考えられます。vis は入力映像の大まかな明るさの配置(輝度構造)を出力でも保持する制御なので、control_weight: 0.24 の重みで「元の明るい空」を出力でも保持し続けます。プロンプトの「暗い空」とは両立せず、制御映像側が優先されている状態です。

自動生成された car_night_control_vis.mp4 を見ると、空が明るいまま保持されているのが確認できます。

空や海面のように、明るさが一様な面が画面の大部分を占める映像では、この競合が起きやすいと考えられます。「白くしたい」「暗くしたい」という、明るさを変える指示が反映されにくくなります。

生成結果を確認する手順

ここまでの内容をまとめると、生成結果が意図と異なるときは、次の順で制御映像と出力を確認すると原因を切り分けられます。

  1. 自動生成されたエッジ制御映像(<name>_control_edge.mp4)を確認し、輪郭が写っていない黒い領域を特定します。この領域には入力と異なる内容が生成されやすいです
  2. 自動生成されたブラー制御映像(<name>_control_vis.mp4)を確認し、保持されている明るさの配置がプロンプトの指示と矛盾していないかを調べます
  3. 出力と入力を並べて、どこが入力どおりで、どこが入力と異なるかを確認します

制御映像は自動生成され、指定しなくても出力ディレクトリに保存されるので、確認のための追加の作業は要りません。パラメータを手当たり次第に変更する前に、この 3 つを確認するだけで原因の見当が付きます。

なお、show_control_condition と show_input というフラグがあり、コードのコメントによれば制御映像や入力を出力動画に連結するようです。

show_control_condition: Concatenate control videos and masks to the output video.

ファイルを個別に開いて比較するより手軽なはずですが、今回は使っていません。先にこちらを試すほうが早いかもしれません。

おまけ: 出力動画を後段で加工するときの注意

比較用に 4 分割動画を作ろうとしたところ、ffmpeg の処理が終わらなくなり、4 秒のはずの出力が 123MB まで増えました。

原因を調べるために ffprobe で生成された動画の情報を確認すると、1 本の動画に対してフレーム数が 121 と 174 の 2 つ報告され、片方はフレームレートが r_frame_rate=0/0(未定義)になっていました。生成された MP4 ファイルに、実際の映像とは別の不正なフレーム情報が含まれていたと考えられます。4 分割に使った xstack フィルタは、入力のうち最も長いものに出力の長さを合わせます。そのため、この不正な情報が最も長い入力として扱われ、出力が 4 秒で終わらなくなっていました。

対処として、各入力に -t 4 を付けて読み込む長さを 4 秒に固定し、fps=30 フィルタでフレームレートを 30fps に揃えてから結合しました。入力の長さとフレームレートを明示したことで不正なフレーム情報が使われなくなり、出力は 4 秒の動画になりました。実際に使ったコマンドは次のとおりです。

ffmpeg -y -t 4 -i input.mp4 -t 4 -i fog.mp4 -t 4 -i rain.mp4 -t 4 -i night.mp4 \
  -filter_complex "[0:v]scale=640:360,fps=30[a];[1:v]scale=640:360,fps=30[b]; \
                   [2:v]scale=640:360,fps=30[c];[3:v]scale=640:360,fps=30[d]; \
                   [a][b][c][d]xstack=inputs=4:layout=0_0|w0_0|0_h0|w0_h0,format=yuv420p[out]" \
  -map "[out]" -c:v libx264 -crf 20 -t 4 compare.mp4

まとめ

Cosmos Transfer 2.5 で車載映像の天候・時間帯変換を試しました。要点は 3 点です。

1. 入力と異なる内容は、エッジ制御映像に輪郭が写っていない領域で生成される

エッジ制御映像に輪郭が写っていない領域では、モデルは入力を参照せずプロンプトだけから生成します。ガラス張りのビルや均一な路面が別の内容に置き換わるのはこのためです。意図と異なる出力になったら、まず自動生成された制御映像を確認してください。

2. vis はプロンプトと競合することがある

vis 制御は、入力映像の大まかな明るさの配置を出力でも保持します。そのため、夜にして空を暗くしたい、霧で白くしたいといった、入力の明るさを変える指示をプロンプトに書いても、vis 制御の「入力の明るさを保持する」働きのほうが優先され、プロンプトの指示が反映されないことがあります。空や海面のように明るさが一様な面が画面の大部分を占める映像では、この影響が特に起きやすいと考えられます。

3. 1 回ごとの試行コストが大きい

4 秒の動画 1 本に約 17 分、VRAM は 65,679 MiB でした。GPU も時間も要るので、根拠なくパラメータを変更するのは現実的ではありません。1 と 2 のように、制御映像を確認して見当を付けてから変更するほうが早く目的の結果に到達します。

セットアップで問題になった 5 点は本文に書いたとおりです。Python のバージョンとゲート同意の 2 つは、知らないとほぼ確実に失敗します。

次回は、preset_edge_threshold や control_weight を変更して、本記事で見つけた 2 つの仮説を検証してみます。

  • エッジの閾値を下げると、入力と異なる内容の生成を抑えられるのか
  • vis の重みを下げると、空は暗くなるのか

参考

Cosmos Transfer 2.5

NVIDIA Brev

Cosmos の他モデルの検証

どなたかの参考になれば幸いです。

以上

この記事をシェアする

関連記事