0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

2024年問題に「コードで」挑む ― SUMO + Pythonで自動運転トラック隊列走行シミュレーターを作る

0
Posted at

はじめに

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>

vTypeemissionClass="HDV_D_EU5" を指定すると、トラックらしい排出モデルで評価できます。先頭車と後続車で minGaptau を変えながら、後続車の燃料消費が減る傾向を観測できます。


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-3Veins(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エンジニアが交通や物流の課題を動くコードで解ける時代 が、すでに始まっている

まずは本記事のひな形を動かし、自分なりの隊列制御アルゴリズム を差し込んで遊んでみてください。物流を変えるアイデアは、多くの場合、小さなシミュレーションから生まれます。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?