はじめに
2024年問題(トラックドライバーの時間外労働規制)の本質は、構造的な人手不足 です。規制緩和では解決せず、最終的な答えの一つが AIと自動運転による完全無人・隊列走行(Platooning) と言われています。
「でも自動運転やV2Xの世界は、Pythonエンジニアの手元では触れない」――実はそんなことはありません。交通シミュレータ SUMO(Simulation of Urban MObility) とそのPython APIである TraCI を使えば、自動運転トラック隊列走行のコア要素(車間制御・V2X的な情報共有・燃費効果)を 手元のPCで丸ごと再現 できます。
本記事では、note記事のテーマを開発者目線で落とし込み、SUMO × Python で隊列走行シミュレーターを構築する具体的な手順 を解説します。
参考にした解説記事:2024年問題の「本当の解決策」はここにある——高速道路を走る完全無人トラックが物流を再定義する | AI Robotics Quantum Lab
この記事のゴールは以下のとおりです。
- SUMOで 直線の高速道路シナリオ を構築する
- TraCI経由で Pythonから隊列(Platoon)制御ロジック を実装する
- 先頭車ブレーキ時の反応を V2X通信 vs 人間反応(反応時間あり) で比較する
- 燃費(FCOオプション)・車間距離・衝突発生有無を 定量評価 する
- CARLA/Autoware連携など、本物の自動運転開発への橋渡し を示す
対象読者は、Pythonに触れたことがあり、自動運転やMaaSに興味のあるエンジニアです。
1. 全体像 ― 何を作るのか
以下のパイプラインを作ります。
[SUMOネットワーク(高速道路)] → [TraCIでPython制御]
├─ 先頭車: 定速巡航 + 計画ブレーキイベント
└─ 後続車: V2Xで先頭情報を受信 → 車間ギャップ制御(ACC/CACC)
↓
出力: 速度/車間/燃費のCSV, GUIアニメーション
- 先頭車がブレーキした瞬間、V2Xで後続車に即時伝達 する実装と、人間反応時間(0.8秒遅延) を挟む実装の2モードを比較
- 「短車間でも追突が起きない」という隊列走行の利点を、数値で示すのがゴール
2. 環境構築
2.1 SUMOのインストール
SUMOはオープンソースの交通シミュレータです。
- macOS:
brew install --cask sumo-guiもしくはbrew install sumo - Ubuntu:
sudo add-apt-repository ppa:sumo/stable && sudo apt install sumo sumo-tools sumo-doc - Windows: 公式サイト からインストーラ
インストール後、SUMO_HOME 環境変数を設定します。
# macOS/Linux 例
export SUMO_HOME=/opt/homebrew/opt/sumo/share/sumo # 環境に応じて変更
export PATH=$SUMO_HOME/bin:$PATH
動作確認:
sumo --version
netconvert --version
2.2 Pythonとライブラリ
mkdir platoon-sim && cd platoon-sim
python3 -m venv .venv
source .venv/bin/activate
pip install --upgrade pip
pip install traci sumolib
pip install pandas matplotlib
2.3 プロジェクト構成
platoon-sim/
├── network/
│ ├── highway.nod.xml
│ ├── highway.edg.xml
│ ├── highway.net.xml # netconvertで生成
│ └── platoon.rou.xml
├── scenarios/
│ └── platoon.sumocfg
├── src/
│ ├── run.py # TraCIエントリポイント
│ ├── platoon.py # 隊列制御ロジック
│ └── analyze.py # ログ解析
└── logs/
3. 高速道路ネットワークを作る
3.1 ノード・エッジ定義
まず2kmの直線道路(3車線)を作ります。
<!-- network/highway.nod.xml -->
<nodes>
<node id="A" x="0" y="0"/>
<node id="B" x="2000" y="0"/>
</nodes>
<!-- network/highway.edg.xml -->
<edges>
<edge id="AB" from="A" to="B" numLanes="3" speed="27.78"/> <!-- 約100km/h -->
</edges>
3.2 netconvertでコンパイル
cd network
netconvert --node-files=highway.nod.xml --edge-files=highway.edg.xml \
--output-file=highway.net.xml
cd ..
3.3 車両タイプとルート
トラック型の車両タイプを定義し、5台の隊列を投入します。
<!-- network/platoon.rou.xml -->
<routes>
<vType id="truck" vClass="truck" length="12.0" maxSpeed="25.0"
accel="1.2" decel="5.0" sigma="0.0" minGap="2.0" tau="0.5"/>
<route id="r0" edges="AB"/>
<!-- 隊列の先頭 t0 と後続 t1-t4 -->
<vehicle id="t0" type="truck" route="r0" depart="0" departLane="1" departSpeed="22"/>
<vehicle id="t1" type="truck" route="r0" depart="0.5" departLane="1" departSpeed="22"/>
<vehicle id="t2" type="truck" route="r0" depart="1.0" departLane="1" departSpeed="22"/>
<vehicle id="t3" type="truck" route="r0" depart="1.5" departLane="1" departSpeed="22"/>
<vehicle id="t4" type="truck" route="r0" depart="2.0" departLane="1" departSpeed="22"/>
</routes>
tau(時間ギャップ)を小さくすることで、隊列走行らしい短車間を表現します。
3.4 SUMOコンフィグ
<!-- scenarios/platoon.sumocfg -->
<configuration>
<input>
<net-file value="../network/highway.net.xml"/>
<route-files value="../network/platoon.rou.xml"/>
</input>
<time>
<begin value="0"/>
<end value="180"/>
<step-length value="0.1"/>
</time>
<output>
<fcd-output value="../logs/fcd.xml"/>
</output>
</configuration>
4. Pythonで隊列制御を実装する
4.1 隊列ロジックの考え方
- ACC(Adaptive Cruise Control):自車のセンサーのみで前車との距離を測り、距離に応じて加減速
- CACC(Cooperative ACC):V2X経由で前車(または先頭車)の加速度情報を受け取り、ACCより早く反応
本記事では、CACC相当の先読み制御 を自前で実装します。
4.2 制御ロジック
# src/platoon.py
from dataclasses import dataclass
@dataclass
class PlatoonConfig:
target_gap: float = 6.0 # m 目標車間
kp_gap: float = 0.6 # 車間誤差ゲイン
kv: float = 0.8 # 速度誤差ゲイン
ka_feedforward: float = 0.9 # 先頭加速度のフィードフォワード
max_accel: float = 1.2
max_decel: float = 5.0
def compute_accel(
own_speed: float,
leader_speed: float,
gap: float,
leader_accel: float,
cfg: PlatoonConfig,
) -> float:
"""CACC相当の加速度指令を計算"""
gap_error = gap - cfg.target_gap
speed_error = leader_speed - own_speed
accel = (
cfg.kp_gap * gap_error
+ cfg.kv * speed_error
+ cfg.ka_feedforward * leader_accel
)
return max(-cfg.max_decel, min(cfg.max_accel, accel))
4.3 TraCIで走らせる
# src/run.py
import os
import sys
import csv
from pathlib import Path
import traci
from src.platoon import PlatoonConfig, compute_accel
PLATOON = ["t0", "t1", "t2", "t3", "t4"]
CFG = PlatoonConfig()
REACTION_TIME = 0.0 # V2Xモード: 0.0秒, 人間反応モード: 0.8秒
def run(use_v2x: bool = True, log_path: str = "logs/run.csv") -> None:
os.makedirs("logs", exist_ok=True)
reaction = 0.0 if use_v2x else 0.8
sumo_cmd = [
"sumo", # GUIで見たければ "sumo-gui"
"-c", "scenarios/platoon.sumocfg",
"--no-step-log", "true",
]
traci.start(sumo_cmd)
# 先頭車のブレーキ計画(t=60sに急減速)
leader_plan = {60.0: -4.0, 62.0: 0.0}
# V2X遅延バッファ: {時刻: (leader_speed, leader_accel)}
history: dict[float, tuple[float, float]] = {}
with open(log_path, "w", newline="") as f:
writer = csv.writer(f)
writer.writerow(["t", "vid", "speed", "gap", "accel_cmd"])
step = 0.0
while traci.simulation.getMinExpectedNumber() > 0:
step = traci.simulation.getTime()
# 先頭車の制御
if "t0" in traci.vehicle.getIDList():
if step in leader_plan:
traci.vehicle.setAcceleration("t0", leader_plan[step], duration=2.0)
leader_speed = traci.vehicle.getSpeed("t0")
leader_accel = traci.vehicle.getAcceleration("t0")
history[step] = (leader_speed, leader_accel)
# 後続車の制御
for i in range(1, len(PLATOON)):
follower = PLATOON[i]
if follower not in traci.vehicle.getIDList():
continue
leader = PLATOON[i - 1]
if leader not in traci.vehicle.getIDList():
continue
own_speed = traci.vehicle.getSpeed(follower)
lead_pos = traci.vehicle.getPosition(leader)[0]
own_pos = traci.vehicle.getPosition(follower)[0]
gap = lead_pos - own_pos - 12.0 # 車長分を引く
# V2Xなら最新の先頭情報、人間モードなら0.8秒前の情報
ref_time = round(step - reaction, 1)
ls, la = history.get(ref_time, (own_speed, 0.0))
cmd = compute_accel(own_speed, ls, gap, la, CFG)
traci.vehicle.setAcceleration(follower, cmd, duration=0.1)
writer.writerow([step, follower, own_speed, gap, cmd])
traci.simulationStep()
traci.close()
if __name__ == "__main__":
mode = sys.argv[1] if len(sys.argv) > 1 else "v2x"
run(use_v2x=(mode == "v2x"), log_path=f"logs/run_{mode}.csv")
実行例:
# V2Xあり(理想的な隊列走行)
python -m src.run v2x
# 人間反応時間ありモード(比較用)
python -m src.run human
sumo-gui に切り替えれば、GUI上でトラックが隊列を組む様子を目視できます。
5. 結果を可視化・評価する
5.1 可視化スクリプト
# src/analyze.py
import pandas as pd
import matplotlib.pyplot as plt
def plot(csv_v2x: str = "logs/run_v2x.csv", csv_human: str = "logs/run_human.csv"):
v2x = pd.read_csv(csv_v2x)
human = pd.read_csv(csv_human)
fig, axes = plt.subplots(2, 1, figsize=(9, 6), sharex=True)
for df, label in [(v2x, "V2X"), (human, "Human reaction 0.8s")]:
follower = df[df["vid"] == "t1"]
axes[0].plot(follower["t"], follower["speed"], label=f"t1 speed ({label})")
axes[1].plot(follower["t"], follower["gap"], label=f"t1 gap ({label})")
axes[0].set_ylabel("speed [m/s]")
axes[1].set_ylabel("gap [m]")
axes[1].set_xlabel("time [s]")
for ax in axes:
ax.legend()
ax.grid(True)
plt.tight_layout()
plt.savefig("logs/comparison.png", dpi=150)
print("Saved logs/comparison.png")
if __name__ == "__main__":
plot()
python -m src.analyze
典型的には、V2XモードではギャップがほぼCfg.target_gap付近を維持 する一方、人間反応モードでは急ブレーキ時にギャップが急減し、場合によっては衝突(gap<0) が発生します。これが「短車間でも隊列走行が安全である理由」の定量的な裏付けになります。
5.2 燃費効果を見る(発展)
SUMOは --emission-output や --summary-output で燃料消費量を出せます。sumocfg に追加してみましょう。
<output>
<fcd-output value="../logs/fcd.xml"/>
<emission-output value="../logs/emissions.xml"/>
</output>
vType に emissionClass="HDV_D_EU5" を指定すると、トラックらしい排出モデルで評価できます。先頭車と後続車で minGap や tau を変えながら、後続車の燃料消費が減る傾向を観測できます。
6. 本物の自動運転開発につなげる
SUMOは 交通流レベル のシミュレータです。次のステップとして、センサーフュージョン・知覚AI まで扱いたければ以下が定番ルートです。
- CARLA:フォトリアルな3D都市と車両物理、Python API。カメラ/LiDARデータが手に入る
- LGSVL / AWSIM:Autoware連携が容易なシミュレータ
- Autoware:オープンソースの自動運転ソフトスタック(ROS 2ベース)
- co-simulation:SUMO(交通流) ↔ CARLA(車両1台の詳細)を結合するコ・シミュレーション例も公開されています
現実のV2Xでは、DSRC/C-V2X(LTE-V2X、5G NR V2X)をモデル化する必要があり、ns-3 や Veins(SUMO + OMNeT++)を用いた評価が研究分野では一般的です。
7. 運用・法制度に関する注意
- 日本ではレベル3(条件付き自動運転)が2023年に解禁、高速道路におけるレベル4の公道運用は限定的です。実車を用いる場合、国交省の認可プロセスが不可欠です。
- 隊列走行は 安全保障・プライバシー・サイバーセキュリティ の観点で、V2X通信のなりすまし対策(PKI・認証)が必須です。シミュレーションでも通信遅延・パケロスを加味するのが本筋です。
- 社会受容性を高めるため、ヒヤリハット共有・説明責任(説明可能AI) の仕組みを設計に組み込むのが望ましいです。
8. 拡張アイデア
- 隊列への 割り込み車 を追加し、CACCの振る舞いを観察する
- 先頭車のブレーキを確率的イベントに変え、多数回試行で 統計的な衝突確率 を評価
- ACC vs CACCの比較実験(先頭加速度のフィードフォワード有無)
-
TraCI経由で 強化学習エージェント(例: Stable-Baselines3)を差し込み、燃費最適な隊列運用ポリシーを学習
まとめ
- 2024年問題の技術的な解は「人を増やす」ではなく「コードで運ぶ」方向にある
- SUMOとPython(TraCI)を使えば、隊列走行のコア要素(車間制御・V2X的情報共有)を手元で再現し、数値で評価 できる
- 手元のシミュレーションで検証した制御ロジックは、CARLAやAutowareと接続することで実車寄りのR&Dに地続きで展開できる
- 「無人で運ぶ」未来を作るのは、巨大自動車メーカーだけの仕事ではない。Pythonエンジニアが交通や物流の課題を動くコードで解ける時代 が、すでに始まっている
まずは本記事のひな形を動かし、自分なりの隊列制御アルゴリズム を差し込んで遊んでみてください。物流を変えるアイデアは、多くの場合、小さなシミュレーションから生まれます。