Googleマップを使ってランダムな運転経路生成を100倍速にする
こんにちは、TURING株式会社でインターンをしている東大工学部4年の井上です。
TURING(チューリング)は完全自動運転EVを開発するベンチャー企業です。
完全自動運転AIの開発のため、独自にデータの収集を行っており、
- 2022年内に500時間
- 2023年内に50,000時間
という目標を掲げています。このうち、2022年の500時間のデータ収集については既にデータベース化が完了し、次の50,000時間の達成に向けた開発が進められています。
チューリング社ではデータ取得のため、自社開発の収集キットを搭載した車両に乗車するデータ取得チームを結成し、公道上での走行データの取得を開始致しました。2022年4月〜10月のフェーズ1において500時間分の走行データの試験取得を完了していますが、2022年12月から開始するフェーズ2として2023年末までを目標期間として、国内最大規模である50,000時間分の走行データベースを構築致します
本記事は、この走行データ収集の基盤を作る上で行った工夫の1つをお話するものです。
生成マップと収集データ例
1. 概要
今回お話する内容をざっくりまとめると、
- 自動運転AIには大量の走行データが必要
- 走行ルートを毎回手動で設定するのは大変
- Googleマップでルートを生成するスクリプトを実装
ということになります。
2. なぜ自動生成が必要なのか
記事のタイトルで、「データ収集の経路を自動生成する話」といきなり述べましたが、
なぜ経路を自動で生成する必要があるの?
という疑問がまず頭に浮かぶと思います。
TURINGが実現を目指す完全自動運転AIに学習には多様で膨大な運転データが必要となります。
そこで今年は、複数のドライバーの方に、データ収集用のデバイスを搭載した専用車両で走行データを集めて頂きました。
データ収集は、エンジニアが走行計画から経路を手動で設定、ドライバーへ共有、走行後に計画をアップデート、という流れで行われました。
この方法は、
- 経路設定のとき、望んだコースになるように経由地を入力する必要がある
- 走行した経路を毎回記録する必要がある
- ドライバーの人数だけ、これを繰り返す必要がある
という懸念点があります。
2023年には、50,000時間という大きな目標達成を目指し、大幅なドライバー増加を計画しているため、よりスケーラブルなデータ収集基盤を構築しなくてはなりません。
そのため、データ収集用の経路をできる限り簡単に設定できるようにする必要がありました。
3. どう解決したのか
データ収集のための運転経路の生成には、以下の3ステップを必要とします。
- 開始地点とルートの長さを決める
- 周回経路を生成する
- ナビゲーション機能と共有機能を付与する
まず、ドライバーの稼働時間や天候状況に応じて、その日のルートの長さを見積もります(ここは人間が判断します)。今回自動化したのは主に2.と3.の部分になります。
TURINGでは、ドライバーと学習データ収集を日々加速させています。こちらの経路生成の実装も、できるだけ早く開発したいという要望がありました。そのため、既存APIを調査し、適切に組み合わせることで2日で実装し、実際の運用に投入しました。
結果、10分ほどかかっていた経路の用意を、数秒で終えることができるようになりました。
開発で利用したAPIは、
の2つです。
GraphHopperは、地図上でルートの生成や最適化を行うことのできるサービスで、開始(および終了)地点の緯度経度と経路長を与えるだけで周回経路(round-trip
という)を生成してくれます。この様な機能は、スマホのマラソンアプリでよく見られます。今回は、周回経路の生成に利用しました。
Googleマップは、言わずもがな地図アプリケーションで、カーナビの代わりとして利用できます。今回は、ナビゲーション機能と共有機能の付与に利用しました。
4. 実装詳細
今回実装した経路自動生成アプリケーションは、入力として、
- 出発(終了)地点の緯度と経度
- 経路の長さ
の2つの情報を必要とします。
parser = argparse.ArgumentParser(description="走行経路用のGoogleマップURLを自動生成するプログラム")
parser.add_argument("lat", help="開始・終了地点の緯度")
parser.add_argument("lon", help="開始・終了地点の経度")
parser.add_argument("per", type=float, help="経路の長さ[km]")
GraphHopperのAPIは、経路生成の際に様々なオプションを入力することが出来ます。今回は自動車で周回するルートを作りたいので、以下のように設定してみます。
- profile...
car
- 移動方法を入力します。今回は自動車用の経路なので、
car
とします。
- 移動方法を入力します。今回は自動車用の経路なので、
- algorithm...
round_trip
- コース生成に特殊なルールを課す場合のオプションです。スタートからゴールまでを最短経路で結ぶ直線的なルートでなく、一周するルートを生成したいので、
round_trip
と入力します。
- コース生成に特殊なルールを課す場合のオプションです。スタートからゴールまでを最短経路で結ぶ直線的なルートでなく、一周するルートを生成したいので、
- round_trip.seed...
(random seed)
- seed値に応じたルートを生成します。地点と距離が同じ場合、ここを重複なく入力する必要があります。(方法は後述)
- pass_through...
true
- 経由地でのUターンを回避するためのオプションです。
- ch.disable...
true
-
round_trip
やpass_through
といった特殊なオプションを有効化するには、ここをtrue
にする必要があります。
-
- key...
(api key)
- ここは自分で取得したGraphHopperのAPIキーを入力します。
# 入力オプション
start_pos = f"{args.arg1}, {args.arg2}" # ルート生成開始位置(緯度,軽度)
perimeter = f"{args.arg3 * 1000}" # 走行想定距離
# Graph Hopperから経路を取得するためのURL
url = f"https://graphhopper.com/api/1/route?point={start_pos}&profile=car&ch.disable=true&pass_through=true&algorithm=round_trip&round_trip.distance={perimeter}&round_trip.seed={seed}&key=(YOUR_API_KEY)&type=gpx"
ここで指定したオプションを反映するURLを文字列操作で作成し、APIを叩くとルートが得られるのですが、このままでは同地点から同距離のルートを作成した際に毎回同じルートが生成されてしまいます。特定の道に最適化した自動運転を目指してはいないので、経路生成のたびにシード値を記録し重複することがないようにします。
# 経路生成シード値の作成
new_route_flg = False
while !new_route_flg:
# シード値の生成
random.seed()
seed = random.randint(0, 2 * 63 - 1)
# 過去の生成履歴と照合
with open("generator_seed_log.txt", "r") as f:
for line in f:
list = line.split(",")
if (args.arg1 == list[0] and args.arg2 == list[1] and perimeter == list[2] and seed == list[3]):
break
new_route_flg = True
# 初めての経路であれば記録
with open("generator_seed_log.txt", "a") as f:
f.write(f"{args.lat},{args.lon},{args.per},{seed}\n")
APIからのレスポンスは、どの形式でも良いのですが今回はgpxファイルで受け取りxmlファイルとして処理することにします。xmlファイルには、経路を構成する経由地の情報が含まれており、この点の集合で経路を構成しています。
res = requests.get(url)
gpx = gpxpy.parse(res.content)
# GPXファイルを変換
route = gpx.to_xml()
route = route.split("\n")
ex. 経由地(1点)の表記例
<rtept lat="35.856145" lon="140.134506">
<extensions>
<gh:distance>37.0</gh:distance>
<gh:time>8335</gh:time>
<gh:direction>NE</gh:direction>
<gh:azimuth>51.62</gh:azimuth>
<gh:sign>-2</gh:sign>
</extensions>
</rtept>
GraphHopperにもルート案内機能はあるのですが、カーナビとしての使いやすさやドライバーの方への共有のしやすさを考え、ルートをGoogleマップ上へ移行、ナビゲーションをつけてURLで共有する形式にしました。
先程のxmlファイルには非常に多くの経由地情報が含まれますが、Googleマップで扱える経由地は9点までです。従って、経路全長を10等分するような位置の経由地を抽出します。GoogleマップはURL上で経由地を指定できるので、経由地情報を加えたURLを文字列操作で作成すると、アクセス時にナビゲーションが起動するという仕組みです。
url = "https://google.co.jp/maps/dir/"
point = []
for line in route:
lat = re.findall(r"lat=\"(.*)\" ", line)
lon = re.findall(r"lon=\"(.*)\"", line)
if len(lat) > 0 and len(lon) > 0:
lat = lat[0]
lon = lon[0]
point.append({"lat": lat, "lon": lon})
# 経由地の数
waypoint = 9
pickup_idx = [int(i / (waypoint - 1) * (len(point) - 1)) for i in range(waypoint)]
for i in range(waypoint):
url += point[pickup_idx[i]]["lat"] + "," + point[pickup_idx[i]]["lon"] + "/"
ex. 自動生成された周回ルートを表すGoogleマップURL
https://google.co.jp/maps/dir/35.896875,139.953049/36.001938,140.038545/36.023182,140.219841/35.89476,140.379874/35.871316,140.396038/35.703949,140.314199/35.656129,140.097278/35.75376,139.918069/35.896875,139.953049/
URLをクリックした際に起動するナビゲーション画面
生成結果はシード値とともに保存され、過去に通過した全てのルートを重ねて表示することで、現在までの走行実績マップを作るようにもしました。
実験的に生成した3回分の経路の合計
5. 使ってみた様子
現在、すでにこのプログラムを使ってデータ収集用の経路を自動生成しています。
社内Slackで運転経路をドライバーの方へ共有している様子
コースは毎回ランダムなので、色んな景色を見ることができます。
海ほたるで綺麗な夕焼けに巡り会えたドライバーの方の投稿
6. おわりに
TURINGでは2022年の500時間分のデータ取得を完了し、日本道路の総延長に匹敵する50,000時間のデータ収集に着手しています。
完全自動運転の開発には、様々な交通状況を網羅的に把握するための巨大なデータセットの構築が必要です。
TURING 自動運転MLチームでは、
50,000時間という膨大なデータを扱う情報基盤を作りたい
そのデータから完全自動運転AIを作りたい
という想いをもったAI開発人材を募集しています。
AI開発に自信/強い興味のある学生のインターンもお待ちしています。
問い合わせ先
弊社での開発業務にご興味をお持ち頂けた方は、求人一覧、Wantedlyを是非ご覧下さい。
その他、ご質問や気になる点がありましたら、お気軽にTwitterのDMをお送りください。共同代表山本、青木どちらもDMを開放しております。
→@issei_y, @aoshun7
Discussion