はじめに
自動運転AIチャレンジ2026も残り1週間です。
前回記事の Vision Pilot を自動運転AIチャレンジ環境で動かしてみた に続いて、comma.ai の openpilot が使っている運転モデル driving_supercombo を、自動運転AIチャレンジの AWSIM 環境に取り込んでみました。
このモデルを選んだのは、実績と規模の両方が手頃だったからです。openpilot はオンデバイスでリアルタイムに動くことを前提としており、運転モデルは 20 Hz で実行されます。今回使った driving_supercombo は約 58 MB / 30 M パラメータで、重みも MIT License で公開されています。カメラ画像から走行経路まで出せる構成も、今回の E2E AI 部門と相性がよさそうでした。
先に結論を書くと、カメラ画像だけから車両を動かすところまではできましたが、安定してコース周回するまでには至っていません。
実車映像では正常に車線・走行経路を出せる一方、AWSIM のカートコースでは車線や路端をほとんど認識しませんでした。実装よりも学習データとのドメイン差が主因と考えており、実際に走らせるには AWSIM 向けの追加学習が必要そうです。
※ コードは以下に公開しています。
想定読者
- 自動運転AIチャレンジ2026 (E2E AI部門) 参加者
- オープンソース・オープンウェイトなE2E自動運転に興味がある方
driving_supercombo とは
driving_supercombo は comma.ai の openpilot で使われている運転モデルです。
カメラ画像から、走行経路・車線・路端・先行車・自車運動などを1回の推論でまとめて出します。
openpilot の selfdrive/modeld/models/ には、今回関係するモデルとして次の3つがあります。
| モデル | サイズ | 用途 |
|---|---|---|
driving_supercombo.onnx |
58 MB | 通常の運転モデル、今回メインで使用 |
big_driving_supercombo.onnx |
1.76 GB | 大型の運転モデル |
dmonitoring_model.onnx |
7.5 MB | ドライバモニタリングモデル |
comma.ai のポストでは、オンデバイスで動く通常のモデルを standard-class、comma four に外付け GPU を追加した chestnut で動かす大型モデルを chestnut-class と表記しています。
standard-class / chestnut-class の比較は、以下のポストの動画でも確認できます。
今回は standard-class 側の driving_supercombo をメインに使い、big_driving_supercombo も比較として試しています。
入出力
openpilot のモデルはバージョンによって入出力がかなり変わっています。以下は今回使った commaai/openpilot@084747c7 時点の、通常版 driving_supercombo の仕様です。
| 入力 | shape | dtype | 中身 |
|---|---|---|---|
img / big_img
|
(1,12,128,256) | uint8 | 2フレーム分の YUV |
features_buffer |
(1,24,512) | float16 | 過去の内部状態 |
desire_pulse |
(1,25,8) | float16 | 車線変更などの意図。今回は常にゼロ |
traffic_convention |
(1,2) | float16 | 左ハンドル / 右ハンドル |
action_t |
(1,2) | float16 | 制御時刻に関する入力 |
action_t は入力として存在しますが、この通常版の ONNX では計算グラフにつながっておらず、値を変えても推論結果には影響しません。大型版では使われています。
通常版の出力は outputs (1,2576) の1本です。どこからどこまでが何なのかは、ONNX の metadata に保存されている output_slices を読んで分割します。
主な出力は以下です。
| 出力 | 中身 |
|---|---|
meta |
数秒先の解除・急ブレーキ予測 |
desire_pred |
意図の予測 |
pose |
自車の並進・回転 |
wide_from_device_euler |
wide カメラとの相対姿勢 |
road_transform |
路面との相対姿勢 |
lane_lines |
車線4本 |
lane_lines_prob |
車線の確度 |
road_edges |
路端2本 |
lead |
先行車 |
lead_prob |
先行車の確度 |
hidden_state |
次の推論へ渡す内部状態 |
plan |
約10秒先までの走行計画 |
desire_state |
現在の意図 |
大型版 big_driving_supercombo は基本的な入力構成は共通ですが、出力に action が追加されるため、出力サイズと output_slices の構成が異なります。
車両制御に欲しいのは曲率と加速度ですが、通常版ではそれらを直接出しません。openpilot 本体も action 出力がないモデルでは plan から制御量を作る構成になっているため、今回も plan から曲率と加速度を計算しました。大型版では action を直接使えます。
アーキテクチャ
ONNX の計算グラフを辿ると、大きく2本の処理に分けられます。
1つはカメラ画像から特徴量を作る部分、もう1つは過去の特徴量や運転意図などを処理する部分です。最後に両者が合流し、plan や lane_lines などの各出力を生成します。
今回 ONNX を集計した結果は以下でした。
| ノード数 | パラメータ | |
|---|---|---|
| 画像から辿れる部分 | 263 | 23.1 M |
| 履歴・意図などから辿れる部分 | 81 | 6.9 M |
画像側では畳み込み層が60層あり、depthwise convolution と 1x1 convolution の組み合わせが繰り返されています。活性化は GELU、LayerNormalization も使われており、構成としては ConvNeXt に近い形です。チャンネル幅は 64 → 128 → 256 → 512 → 1024 と増えていきます。
パラメータ数を見ると、畳み込み部分が約 6.5 M、その後段の全結合部分が約 16.6 M でした。画像バックボーンだけでなく、その後の特徴変換や出力側にもかなりの重みがあります。
※ ONNX 自体は Netron で可視化できます。
今回やったこと
実装した内容を順にまとめます。
1. ROS 2 ノード化
aichallenge_submit に openpilot_controller パッケージを追加し、control_method:=openpilot で選択できるようにしました。
subscribe するのはカメラ画像、camera_info、車速の3つです。publish するのは AckermannControlCommand に加えて、デバッグ画像、RViz 用マーカー、Trajectory です。
推論は制御指令の publish と別のコールバックグループに分けています。制御側は固定周期で動かし、一定時間新しい推論結果が来なかった場合や、モデルが NaN を返した場合は停止指令を出します。画像入力や推論が止まったときに、最後の操舵角を出し続けるのを防ぐためです。
各種設定は openpilot_controller.param.yaml にまとめています。画角・取り付け角・推論 provider など、調整しやすいものは環境変数からも上書きできます。
2. 画像の前処理
AWSIM のカメラ画像を openpilot のモデル入力へ変換します。
openpilot は入力画像を単純に resize しているわけではありません。カメラの内部パラメータと取り付け角から射影変換を作り、openpilot 固有のモデル座標系へ 512x256 で warp してから YUV に変換します。
AWSIM は /sensing/camera/camera_info を publish しているので、内部パラメータはそこから取得しました。
YUV 側は、Y を 2x2 で分割した4chと、半分の解像度になる U/V の2chを合わせて 256x128 の6chにします。これを2フレーム分積むと、入出力の表にある (1,12,128,256) になります。チャンネル順や補間方法も openpilot 側の実装に合わせています。
3. 時系列の扱い
driving_supercombo は1枚の画像だけを見るモデルではありません。
openpilot はモデルを 20 Hz で実行しますが、画像の文脈入力には約 200 ms 離れた2フレームを使います。20 Hz では4ステップ前の画像です。
一方、AWSIM のカメラは実測で約 10 Hz でした。そのまま4ステップ前を使うと約 400 ms 離れてしまいます。
そこで実測した画像レートから、200 ms に最も近くなるフレーム間隔を自動で選ぶようにしました。今回の環境では2フレーム前、約 210 ms に収束します。
つまり今回は、カメラに合わせて約 9.5〜10 Hz で推論しつつ、モデルへ渡す2画像の時間差は openpilot と同じ約 200 ms に合わせています。
features_buffer に渡す過去の内部状態も同様にキューで管理しています。
4. 自己キャリブレーション
画像の射影変換には、カメラ内部パラメータだけでなく取り付け角も必要です。
openpilot には calibrationd という自己キャリブレーション処理があります。
基本的な考え方は、直進時にはモデルが推定する自車の並進方向がカメラ正面と一致するはずなので、そのずれから pitch / yaw の取り付け誤差を求める、というものです。
この処理も移植しました。推定結果はファイルへ保存し、次回起動時に再利用します。
また、
OPENPILOT_OBSERVE=true
とすると、別のコントローラに車両を走らせながら、openpilot は観測と自己キャリブレーションだけを行えます。
実車の openpilot が、人間の運転中に自己キャリブレーションするのと同じ使い方です。
5. 制御指令への変換
通常版では plan から曲率と加速度を求め、AckermannControlCommand に変換します。
一つ問題だったのが発進です。
停止状態ではモデルが停止を維持する出力を返すため、そのままでは目標速度が 0 のままになり発進しません。そこで今回の環境では、最初だけ最低速度を与える発進補助を入れました。
別の構成として、モデルの plan を Autoware の Trajectory に変換して publish するモードも用意しました。
camera
↓
driving_supercombo
↓
plan
↓
Autoware Trajectory
↓
Pure Pursuit / MPC など
↓
AckermannControlCommand
こちらは openpilot に制御まで担当させるのではなく、openpilot を planner として使い、Trajectory の追従は後段の controller に任せる構成です。大会環境側の Pure Pursuit や MPC などをそのまま使えます。
6. デバッグ表示
走行経路・車線・路端を、モデルが実際に見ている画像へ重ねて表示するようにしました。RViz 用に base_link 座標系のマーカーも publish しています。
モデルの出力は2D画像座標ではなく、3D の点列です。
openpilot のキャリブレーション座標系は x 前・y 右・z 下なので、カメラ座標系へ変換してから内部パラメータを掛けることで画像へ投影します。
走行経路はカメラ高さも考慮する必要があります。ここを入れないと経路が地平線付近に張り付きます。openpilot 本体の描画も同じ扱いです。
なお、デバッグ画像の生成は意外と重く、毎フレーム publish するとパイプライン全体が 9.5 Hz から 2.3 Hz まで低下しました。そのため既定では間引いています。
7. 推論速度の計測
Intel Xeon 2.20 GHz 8コア / NVIDIA L4 の環境で、AWSIM と ROS 2 を同時に動かした状態で測りました。数値は推論のみで、前処理や描画は含みません。
| CUDA (L4) | CPU | |
|---|---|---|
driving_supercombo |
8.8 ms(114 Hz) | 70.7 ms(14 Hz) |
big_driving_supercombo |
18.6 ms(54 Hz) | 2321 ms(0.4 Hz) |
GPU では大型版でも十分リアルタイムです。
CPU では通常版でも 14 Hz 程度で、openpilot 本来の 20 Hz には届きませんでした。ただし今回の AWSIM カメラ自体が約 10 Hz なので、実際のパイプラインではカメラ周期が上限になります。
なお onnxruntime の CUDA provider は cuDNN 9 が必要だったため、既存の PyTorch 環境が使う cuDNN 8 と分離し、このノードだけ別のライブラリパスを通しています。
8. 実装の検証
ここまでで車両は動くようになりましたが、AWSIM では車線も路端もほとんど検出されませんでした。
これだけでは、前処理や出力解釈を間違えているのか、それともモデルが AWSIM を認識できていないのか分かりません。
そこで comma.ai が公開している実車映像を、同じ実装へ入力して比較しました。
| 実車映像 | AWSIM カートコース | |
|---|---|---|
| 車線の検出確度 | 0.90 / 0.99 / 0.97 / 0.69 | 0.00 / 0.01 / 0.02 / 0.00 |
| 車線位置の標準偏差 | 0.06〜0.13 m | 約 4.9 m |
標準偏差はモデル自身が出している、22.7 m 先の車線横位置に対する不確かさです。
※ 左は openpilot の CI 用公開映像、右は AWSIM です。
実車映像では白線に沿って正常に出力されました。この結果から、少なくとも画像の warp、YUV の詰め方、時系列入力、出力の解釈までは動いていると判断できます。
さらに解像度・画角・取り付け角・内部パラメータも変えて試しましたが、大きな改善はありませんでした。実車映像を AWSIM より粗い角度分解能まで落としても車線は検出されます。
したがって今回の主な問題は画質ではなく、バリアで囲まれ、通常の道路標示がないカートコース自体が学習分布から外れていることだと考えています。
この対照実験は verify_against_real_footage.py にしています。前処理などを変更したときの回帰テストにも使えます。
まだできていないこと
1. AWSIM 向けのファインチューニング
今回もっとも必要そうですが、まだ手を付けていません。
openpilot の元の学習パイプラインをそのまま再現するのは難しいため、競技向けなら、AWSIM を既存コントローラで走らせて画像と車両軌跡を収集し、各画像に対応する将来軌跡を教師データとして plan 出力を追加学習するところから試すのが現実的だと思います。
車線・路端・先行車など全出力を再現するより、競技で直接使う plan に対象を絞って AWSIM へ適応させる方が試しやすそうです。
2. 自己キャリブレーションの収束
自己キャリブレーション自体は動きましたが、通常走行中にはほぼ収束しませんでした。
openpilot は一定速度以上かつほぼ直進中のフレームだけを採用します。一方、今回のコースは走行中のヨーレート中央値が 24.7 deg/s と高く、条件を満たす区間がほとんどありません。
低速で直進させると収束し、pitch / yaw ともほぼゼロになりました。
つまり実装の問題ではなく、カートコースでは自己キャリブレーションに使える直進区間が少ないという結果でした。
3. Trajectory 経由の制御性能
plan を Trajectory として publish し、後段の controller で追従するところまでは実装しましたが、性能の比較はできていません。
直接制御する場合より操舵は滑らかでした。ただし元になる plan がコースを外れてしまうため、Pure Pursuit と MPC のどちらが向いているか、といった比較ができるところまで走れていません。
導入手順
ざっくり使い方を載せておきます。
※ ひと通り自動運転AIチャレンジの環境がセットアップされている前提です。
まずリポジトリをクローンして、今回のブランチへ切り替えます。
git clone https://github.com/soyaoki/aichallenge-racingkart
cd aichallenge-racingkart
git switch feat/commaai-openpilot-e2e
make openpilot-models
make autoware-build
カメラ有効・1台・NPCなしの AWSIM プリセットを起動します。
make simulator-openpilot
CONTROL_METHOD=openpilot docker compose up -d autoware
既定では制御指令を出しません。まず RViz で /openpilot/debug/markers と /openpilot/debug/image を確認し、走行経路がコース方向を向いているか見ます。
問題なければ制御を有効にします。
CONTROL_METHOD=openpilot \
OPENPILOT_CONTROL_ENABLED=true \
docker compose up -d autoware
Trajectory として出す場合はこちらです。
CONTROL_METHOD=openpilot \
OPENPILOT_OUTPUT_MODE=trajectory \
docker compose up -d autoware
この場合は openpilot が Trajectory までを担当し、追従する controller は大会環境側の Pure Pursuit / MPC などから選びます。
GPU を使う場合は一度だけ依存ライブラリを準備します。
make openpilot-gpu-deps
シミュレータなしの動作確認もできます。
cd aichallenge/workspace/src/aichallenge_submit/openpilot_controller
python3 scripts/smoke_test.py
python3 scripts/verify_against_real_footage.py
詳細なパラメータや実行方法は openpilot_controller/README.md にまとめています。
おわりに
今回は、自動運転AIチャレンジ2026の E2E AI 部門で使う候補として、comma.ai の driving_supercombo を AWSIM 環境へ取り込みました。
カメラ画像の前処理、時系列入力、自己キャリブレーション、制御指令への変換、Trajectory 出力、デバッグ表示まで一通りつなぎ、カメラ画像だけで車両を動かすところまでは到達しました。一方で、安定してコース周回するところまではできていません。
今回一番大きかったのは、openpilot の実車映像を同じパイプラインに通して比較したことでした。実車映像では正常に車線や走行経路を出せたため、問題を「移植の失敗」ではなく「一般道で学習されたモデルと AWSIM のカートコースとのドメイン差」まで切り分けられました。
大型版へ差し替えても結果はほぼ変わらず、画角や解像度の調整でも改善しませんでした。ゼロショットで使うには厳しそうなので、次に進めるなら AWSIM の走行データを使った plan の追加学習が本命だと思います。
前回の Vision Pilot では「モデル出力を物理的に正しい Trajectory へ落とす部分」が大きな課題として残りました。今回はそこまで含めて実装できた一方で、その手前の「モデルがそもそも走行環境を認識できるか」という別の壁に当たりました。既存の E2E モデルを別環境へ持ってくる場合、この2つは分けて確認した方がよさそうです。
最近では、Generalist AI の GEN-1.5 のように、ロボットが1回のデモをコンテキストとして、その場で新しいタスクへ適応する例も出てきました。E2E 自動運転でも、いつかファインチューニングなしに「このコースを1周見せれば走れる」くらいまで適応できるようになると面白そうです。
おまけ
大型版 big_driving_supercombo なら走れる?
結論は、大型版でもほぼ変わりませんでした。
big_driving_supercombo は約 880 M パラメータあり、通常版の約30倍です。comma.ai が chestnut-class として紹介している大型モデルに相当します。
入力仕様がほぼ同じなので、推論部分を差し替えて試しました。
大型版では action も含めた出力が得られるため、通常版のように plan から制御量を作らず、モデルが出した action を直接使うこともできます。
ただし AWSIM での認識結果はほぼ変わりませんでした。
| モデル | 車線の検出確度(AWSIM) |
|---|---|
driving_supercombo |
0.00 / 0.04 / 0.03 / 0.00 |
big_driving_supercombo |
0.00 / 0.03 / 0.02 / 0.00 |
※ 本文の表とは別フレームでの比較です。
一方、実車映像では大型版の方が車線検出が改善します。単純なモデル容量不足というより、AWSIM のカートコース自体が学習分布から外れていると考える方が自然です。
推論速度は NVIDIA L4 で 18.6 ms だったため、GPU を使う限り計算量は問題になりませんでした。
DMS モデルも動かしてみた
ドライバモニタリング用の dmonitoring_model.onnx も試しました。
以前 Cosmos で作った居眠りシーンの合成動画 を入力したところ、生成 AI の映像でも顔とまばたきを検出できました。
別カメラの映像を入力する場合は、openpilot のドライバーカメラと見え方のスケールを合わせる必要があります。そこで入力時に仮定する画角を変えて試したところ、78°で face_prob が最も高くなりました。
| 仮定した画角 | face_prob |
|---|---|
| 40° | 0.67 |
| 60° | 0.80 |
| 78° | 0.84 |
| 104° | 0.31 |
| 140° | 0.17 |
※ 値は動画全体の平均です。
以下は、最も反応が良かった 78°設定で動画全体を流した結果です。
| 信号 | 平均 | 最大 |
|---|---|---|
face_prob |
0.84 | 0.94 |
left/right_eye_prob |
0.89 / 0.85 | 0.96 / 0.94 |
left/right_blink_prob |
0.46 / 0.47 | 0.79 / 0.82 |
using_phone_prob |
0.03 | 0.04 |
sleep_prob |
0.00 | 0.01 |
まばたきについては、閉眼に合わせて blink_prob が前半の 0.10〜0.28 から後半の 0.74〜0.80 まで上がりました。一方、今回の合成動画では openpilot 側の閉眼判定に使われる水準までは届きませんでした。
走行モデルが AWSIM の景色をほぼ認識できなかったのに対し、DMS は生成 AI の人物映像でもかなり反応しました。同じ学習済みモデルの転用でも、対象ドメインとの距離によって結果が大きく変わるのが面白いところでした。




