VLMで自動運転やってみた

# はじめに

この記事は、[ちゅらデータ Advent Calendar 2024](https://qiita.com/advent-calendar/2024/churadata) 6日目の記事です。

2024年も残すところわずかとなりました。

個人的には色々あった年でしたが、特に7月2日から11月9日までの約4ヶ月間（2024年の1/3）、[自動運転AIチャレンジ2024](https://www.jsae.or.jp/jaaic/2024ver/)というコンペティションに参加させていただいたことが印象に残っています。

そういえばコンペ期間中、「これ面白そうだけど競技に使えなくね？？」となった面白ボツ案が色々あることを思い出しました。今年中に消化しないと多分未来永劫やらないので、まずは個人的やってみたいランキング1位の **「VLM (Vision and Language Model) で自動運転」** にチャレンジすることにしました。まとめていきます。

なおVLMの理論的な話は省略しますので、予めご了承ください。

※ おいおいまた自動運転ネタかよと思われた方はゴメンナサイ、俺にはこれしかないんだ。

::: note
結論を書いておきます。
- 本記事の方法だと、安定した走行が難しいです😭
- ドメインに特化していない素のVLM (Gemini) を使っています。
- Gemini APIの発行が必要です。
- Ubuntu(w/ NVIDIA GPU)の開発環境をお持ちの方は、Let's チャレンジ。
:::

# VLMとは？

まずVLMとは何者かという話ですが、
Vision and Language Model の略称で、その名の通り、画像と言語を扱えるモデルの総称です。
マルチモーダルなLLM (Large Language Model) の一種と言うと伝わる方もいらっしゃるかもしれません。

マリオをプレイさせた以下記事は記憶に新しい方も多いはず。

https://zenn.dev/karaage0703/articles/5a02a0822fba8a

昨今この領域の発展は目覚ましく、自動運転界隈にも確実にその波は到来しています。

例えば、視覚情報と言語的理解の融合による高度な運転シーン文脈の理解が期待されていたり、

https://speakerdeck.com/yuyamaguchi/ri-ben-yu-vision-languagemoderunoxue-xi-toping-jia-bentimakunogou-zhu

チューリング社では完全自動運転実現に向けてLLMやそれを用いたVLMの開発が行われていたり、

https://zenn.dev/turing_motors/articles/00df893a5e17b6

「コンピュータビジョン最前線　Winter 2024」のキーワードが「自動運転VLM」だったりします。

https://www.kyoritsu-pub.co.jp/book/b10092764.html

要は、技術的にかなりホットな領域（筆者主観）なわけです。


::: note
とはいえ、実際に世の中で使おうとすると、計算資源どうする？レイテンシどうする？説明性どうする？コストどうする？のような課題が山のようにあると思います。このあたりに対しても日夜、開発や議論が行われているはずです。
:::


# アーキテクチャ

そんな激アツVLMで自動運転をやってみようと思いましたが、
一口にVLMやLLMを用いた自動運転といっても幅広く、アーキテクチャ次第で

- 認知を担当（入力：画像と自然言語、出力：物体認識結果やシーン理解）
- 認知〜判断を担当（入力：画像と自然言語、出力：目標軌跡や行動決定）
- 認知〜操作を担当（入力：画像と自然言語、出力：ハンドル/ペダル指令値）
...

など色々ありそうです。[Tiny-BERTの先行例](https://zenn.dev/bushio_tech/articles/7ff1e37b109402)では操作を担当させていました。

※ 以下が良くまとまっていそうでした。

https://github.com/ge25nab/Awesome-VLM-AD-ITS


上記のように色々あるのですが、今回は認知〜操作を担当させる、いわゆる **End-to-End** のアーキテクチャとします。つまり、 **画像とプロンプトを入力して、制御指令値を直で出力** させる方式を採用します。

なぜか？　

**一番難しそうだし、一番カッコいいから。**

…というのは冗談で、実装が楽になること（理由は後述）、つまり皆さんが容易に試せる / 敷居が低くなることが大きかったです。裾野を広げることは技術が発展する一つの条件と個人的には思いますので。

さて、具体的に今回使用するE2Eアーキテクチャを描いてみると以下になります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3628135/47be1e42-9914-36c9-2bb5-357975bad0fe.png)


- 前述の通り、End-to-Endの自動運転コントローラとしてVLMを使います。
- 今回は机上検証を行いますので、環境シミュレータ（AWSIM）を使用します。
- VLMにはスクリーンキャプチャとプロンプトを入力し、レスポンスをキーボード入力としてAWSIMに渡します。

これでPythonとAWSIMさえあれば誰でもVLMで自動運転を試すことができると思います。これが前述の「実装が楽」の理由です。Autowareのコントローラ（VLMが担当しない他制御領域の担当）を介在させてVLMにプランナーを担わせる、等もできなくはないと思いますが、とても大変そうなのでまずは簡易検証として本方式でトライします。

※ AWSIMは、ティアフォー社が開発しているOSSのシミュレータです。本来はAutowareといったROS自動運転システム用なのですが、キーボード入力（WASD）で運転が可能なので採用しました。というか自動運転AIチャレンジ開発環境でそのまま使えるので採用しました。

https://github.com/tier4/AWSIM

# VLM選定

さてアーキテクチャは定まったわけですが、ここで最も重要といっても過言ではないのが本記事の主役 **VLM** です。

「レイテンシを考えるなら軽量なローカルモデルが良いのだろうか？でも精度に課題が出そうだな、逆に精度を考えるならAPIを使って強いモデルを使おうかな？レイテンシが課題だな…」などと考えましたが、幸いAWSIM側でTime-Scaleの調整ができる（シミュレータ側だけスローモーションにできる）ので、レイテンシ問題は無視できます。実車で使うなら論外ですが、まずはシミュレーションでの概念検証なのでOK。

すると、APIで強いモデルを使用する方式が良さげです。
選択肢としてGPT, Claude, Gemini等があるわけですが、今回はコスト面で **Gemini** (gemini-1.5-flash) を採用します。

だって15リクエスト/分が無料なんですよ？

https://ai.google.dev/pricing?hl=ja#1_5flash

Structured Outputも使えるし。

https://ai.google.dev/gemini-api/docs/structured-output?hl=ja&lang=python

さらにTuningも無料できます。ひゃー。

https://cloud.google.com/vertex-ai/generative-ai/docs/models/tune_gemini/image_tune

上記理由から（+ Waymoのデータも学習させていないかな〜と淡い期待も抱きつつ…）、Geminiを選定しました。

※ ちなみにローカルVLMだとMobileVLM-V2が良さそうだな〜と以下を読んでいて思っていました。

https://kazuhito00.hatenablog.com/

VRAM 6GBのGPUで、1.7Bは余裕、3Bは量子化モデルなら使えます。レイテンシは私のローカル環境だと、3~7秒でGeminiのほうが速くね？となりました（設定で改善する可能性はあります）。

こちらのモデルもチューニング可能らしいです。Structured Outputはなさそうです。

https://qiita.com/toshi_456/items/4a5025eb8c0ca36f8511

atmaCup #18にて、`OpenGVLab/InternVL2-2B`を使って画像の状況把握（特徴量抽出）を実施した例を本記事執筆中に知ったので、こちらも試してみてもいいかもしれません…

https://www.guruguru.science/competitions/25/discussions/be341f60-a6c8-4b4e-be54-629b7ee849e0/

# やってみた

前置きが長くなりましたが、やってみた結果です。

残念ながら、**最初のカーブで壁に激突してしまいます。**

![ezgif.com-speed.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3628135/befd7664-2950-2ecc-69e3-87f9eb027803.gif)

※ 20倍速です

上記は、Time-Scale:0.05で、Geminiのレートリミットに引っかからないように15RPM(=4秒に1回推論)で実行しました。実時間スケールだと0.2秒に1回推論と等しいので、5Hzで制御ということになりそうです、車両制御にしてはちょっと荒いですね…

また以下がGeminiのレスポンス詳細です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3628135/4b31c0bc-4fca-9025-9fc4-7080511c8f98.png)


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3628135/9c74ec45-ce02-3572-55ea-2328cd222b09.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3628135/4b53b2f9-c1a0-be8d-3b40-9abcd6c10ac8.png)

## うまく走らせるには

特にドメインに特化した学習やチューニングを **行っていない** 素のGeminiでありながら、
推論そのものは、割と良い線いっている感じはあります。

AWSIMのTime-Scaleを限界まで小さくする（0.01にする）とうまく行くかもしれません。

またAWSIMのキーボード操作での操舵は、`操舵しない / マックスで操舵`のいわゆるBang-Bang制御になっている都合上、後処理（押下時間で調整）を入れています。ここも手を入れると良くなる可能性はありそうです。

あとはVLMをチューニングする / 別モデルに変更するなどしても違った結果になりそうですね。Heronとか使ってみたいなあ…

そしてそもそも今回は、前回値などの時系列情報や他車両状態量情報を一切入力していないため、このあたりも入力するとよりスムーズに運転してくれるかもしれません。これはROSノードにGeminiを乗っける形で実現できそう。その際、出力も「↑,↓,←,→」ではなく、操舵角そのものにすることもできると思います。面白そうですね。

E2Eアーキテクチャではなく、他の方式を採用して、どうやったらうまく走るか考えるのも良いと思います。

※ 少しずれますが、LLM (Large Language Mario) のように Gym or GymnasiumでAWSIMの強化学習環境作れると面白そうだなあと思いました。なお、やろうと思って挫折しました。

## 実行手順

「てめえよりも上手く走らせてやんよお！」という方が絶対いると思いますので、簡単に手順を残します。


(1) Ubuntu 22.04がインストールされたNVIDIA GPU付きのPCを用意します。

(2) 環境シミュレータ（AWSIM）を以下の手順に沿って準備します。
※ ちなみにWSL2だとVulkan周りでうまく行かないことが多いようですのでご注意ください（私もだめでした）。

https://automotiveaichallenge.github.io/aichallenge-documentation-2024/setup/visible-simulation.html

(3) Gemini API Keyを取得します。

https://ai.google.dev/gemini-api/docs?hl=ja

(4) 以下をクローン、`uv sync`等で仮想環境を作成 & アクティベートします。

https://github.com/soyaoki/AWSIM-VLM-Drive

(5) Jupyter-labから以下ノートブックを開き、用意したAPI Keyで`YOUR_API_KEY`を置き換えます。

https://github.com/soyaoki/AWSIM-VLM-Drive/blob/main/Notebooks/Drive_by_VLM.ipynb

(6) `AWSIM.x86_64`を実行してTime-Scaleを0.05等に設定します。
※ AWSIM*という名前のウィンドウをキャプチャするので、余計なウィンドウは消してください。

(7) ノートブックを実行し、AWSIM画面をクリックしアクティブにすると…走るはずです。
※ 不足しているものは適宜 `uv pip`や`apt`でインストールください。

簡単にノートブックの説明です。
難しいことはしていないので読めば理解できると思いますが…
- 入力：クロップした画像をプロンプトとともにGeminiに渡しています
- 出力：Structured Outputで、制御指令（キーボード入力）と理由を出力させています

# おわりに

昨今話題のVLMで自動運転に挑戦してみました。
今回の方法だと最後までうまく走行できませんでしたが、VLMの推論自体は悪くなく、非常に可能性を感じさせてくれました。

VLM・LLMの自動運転領域への応用（他技術との組み合わせ含む）が進むことで、悲惨な交通事故が少しでも減り、安全で安心できる世界が実現することを願っています。

ということで、師走でバタバタすると思いますが、くれぐれも安全運転で、良いお年をお迎えください。

この記事は、AI文章校正ツール「ちゅらいと」で校正されています。

<details><summary>（おまけ）</summary>

他モデルの結果などを残しておきます。

## MobileVLM V2

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3628135/897ea305-53e6-51bf-4303-a82a9e6a5447.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3628135/a848d77a-b47e-1d9b-324a-80005f4568ac.png)

減速を選択してくれました。
友人が言い放った「一番安全な車は、走らない車」という言葉を思い出しました。


## GPT-4o

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3628135/f31d748c-c23a-645b-2271-ee0f1ba6eebb.png)

GPT-4oは加速を選択してくれました。
コストの関係から選択しませんでしたがこちらも良さそうですね。

## Gemini

画像の渡し方をミスったときパート1。
暴走しました。

![ezgif-4-0cea517e9e.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3628135/ed0da38b-48da-76d3-22b0-ad7c64a3f4d0.gif)

画像の渡し方をミスったときパート2。
交差点に進入しようとしている、右側から他車が来ているから減速する必要がある、と言っています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3628135/8ae3abb4-8c28-cbb5-1503-033e76348542.png)

```
{"command": "↓", "reason": "The image shows a car approaching an intersection.  There is a car coming from the right. To avoid a collision, the car needs to decelerate."}
```

画像を変更される攻撃は確実に防がないといけないな、と改めて思いました。