はじめに
技術チャレンジ部の、とも(Tomo)ことAirです。
自動運転AIチャレンジに参加しているメンバーを応援するために、ROS2のrosbagをROS1に変換しVLP-16のデータをpcapで抽出する環境を構築しました。
自動運転AIチャレンジにおけるrosbag
自動運転AIチャレンジ2023インテグレーション大会では、自動運転車が霧を切り抜ける(あっ)という課題が設定されました。この霧はLiDARで検知されることが想定されており、実車から取得したrosbagデータが運営の方より共有されました。
LiDARはVelodyneのVLP-16が用いられるようです。VLP-16にはEthernetが付いており、データはUDPで送信されます。これをtcpdumpやWiresharkなどで取得し、pcap形式で保存するということが一般に行われています。VeloViewというビューアでも、入力ファイルとしてpcap形式が用いられています。
よって、rosbagからpcapに変換できれば、解析ツールの幅が広がるのでは(≒VeloViewのサイバーな見た目がかっくいいので気分があがった結果もしや優勝できてしまうのでは)、という仮説から始めたら、意外と深かった…という奮闘記的ポエムです。
技術チャレンジ部メンバーによるこちらの記事にある、
チームメンバーにVLP16のデータをrosbagからpcapに変換していただき
の1行に詰まった物語です。
pcapへの変換方針
技術チャレンジ部メンバーによるこちらの記事で、rosbagデータの再生方法が具体的に示されています。そこで、大きく2種類のアプローチを考えました。
- rosbagをROSで再生して、トピックとして受信後に、pcap形式に変換する
- rosbagファイルを直接pcapファイルに変換する
直接pcapに変換できれば、ROSの環境も不要で静的に変換できるかと思い(フラグ)、直接pcapに変換する方法を優先的に調査しました。
rosbagファイルをpcapに変換する方法
具体化するにあたり、再度大きく2種類のアプローチを考えました。
- 変換ソフトを自作する
- 既存の変換ソフトを使用する
どちらにせよ、ChatGPTに聞いてその通りやれば、どうにかなるだろうという軽い気持ちで始めました(フラグ)。
色々試行錯誤しながら調べて得られた中間的な結論は、
- ChatGPTの言う通りにやってもうまく行かない
- 主にROS1の知識を前提としていそう(自分のプロンプトにも問題あり)
- ROSにはROS1とROS2があり、rosbagの形式も異なる
- 本大会はROS2で行われているため、rosbagもROS2形式
-
bag_to_pcapという、そのものずばりなソフトがある
- …が、最終更新が5年前と古く、どうもROS1を前提としている
- rosbagsというソフトで、ROS2のrosbagをROS1に変換できそう
さあ、考えどころです。これらを合わせると、rosbagsでROS1のrosbagに変換した後、bag_to_pcapでpcapに変換するというアプローチが浮かんできます。
rosbagsによるROS2からROS1への変換
rosbagsの本家を見ると、
- rosbag2 reader and writer,
- rosbag1 reader and writer,
(中略)- efficient converter between rosbag1 and rosbag2,
とあるので、ROS2からROS1への変換はできると信じます。
…が、世の中、ROS1からROS2への変換情報ばかりで、不安になります。
それでも、rosbagsをpip install rosbags
でインストールし、LiDARのトピック名を調べた上で、rosbag-convert
を実行すると、エラーとなります。
$ rosbags-convert --include-topic /sensing/lidar/top/velodyne_packets smoke1
ERROR: Converting rosbag: TypesysError("Type 'velodyne_msgs/msg/VelodyneScan' is unknown.")
変換に必要な型が定義されていないようです。
rosbagsのtypes.pyを見ると、様々な具体的な型が定義されていますが、Velodyneに関するものは無さそうです。しかも、
# THIS FILE IS GENERATED, DO NOT EDIT
などとあり、自動生成であることが分かります。
Velodyne関連型の定義
ROS本家の情報を確認すると、Velodyneに関する型の定義情報があります。
VelodynePacketとVelodyneScanの2種類があるようです。
- VelodynePacket
- VelodyneScan
この2つの型を、rosbagsのtypes.pyで定義する必要がありそうです。
types.pyにある他の型を真似しながら、手動で記述してみたものの、うまく行きません。
特に、VelodynePacketは、配列が用いられています。VelodynePacket.msgの1206バイトをどのように表現するか、自信が持てませんでした。
rosbagsの型定義コード生成
注意: この章は、rosbagsの正しい使い方を知らないド素人がてきとーにhackしたポエムです
rosbagsのマニュアルを見ると、ROSで定義する型表記を、文字列として記述した上で、register_types()
で登録することができるようです。
そこで、こんなコードを書いてみます。
PACKET_MSG = """
time stamp
uint8[1206] data
"""
SCAN_MSG = """
Header header
VelodynePacket[] packets
"""
register_types(get_types_from_msg(PACKET_MSG, 'velodyne_msgs/msg/VelodynePacket'))
register_types(get_types_from_msg(SCAN_MSG, 'velodyne_msgs/msg/VelodyneScan'))
すると、なんと、rosbagsがregister.pyの中で、型定義のPythonコードを生成するようです。すごっ!
そこで、コード生成関数であるgenerate_python_code()
がreturnする直前で、print(lines)
するように改変し、内容を確認します。
ここで得られた内容をtypes.pyに反映したパッチがこちら。
(rosbagsのバージョンにより微妙に表記が異なる可能性があります)
このtypes.pyを組み込んだrosbagsで、ROS2のrosbagをROS1に変換することができました。
bag_to_pcapによるrosbagからpcapへの変換
あとはbag_to_pcapを実行するだけ!もう8合目越えた!(フラグ)
…な気分で実行するものの、色々と問題が起こります。
試行錯誤の結果、以下のような結論が得られました。
- bag_to_pcapはPython2
- rosbagsをpipで入れた時も、roslibpyをPython2で入れる(?)ので、Python2がちら見えしていた…
- ビルドにROS1が必要
- 実行にもROS1が必要
- 静的な変換ではなく、ROS1でrosbagを再生しながら、bag_to_pcapで変換するっぽい
この時点でフラグを回収しまくりです。前進あるのみで検討した結果、
- Python3フリーなROS1環境を作る
ことにします。具体的には、Ubuntu 16.04まで戻りROS1環境を作成する、という前進です。
velodyne_vlsのビルド
注意: 以下はUbuntu16.04上のROS1環境です
bag_to_pcapは、velodyne_vlsに含まれています。
まず、依存パッケージをインストールします。
pip install roslibpy
sudo apt install libpcap-dev
次に、velodyne_vlsをビルドします(ROS本家マニュアル)。
mkdir src
cd src
git clone https://github.com/velodynevls/velodyne_vls.git
cd ..
catkin_make -DCMAKE_BUILD=Release
bag_to_pcapの実行
まず、roscoreを実行します(ROS本家マニュアル)。
roscore -p 11311
次に、ROS1環境の環境変数を読み込みます(ROS本家マニュアル)。
source devel/setup.bash
bag_to_pcapを実行します。
rosrun bag_to_pcap bag_to_pcap.py /sensing/lidar/top/velodyne_packets
rosbagを再生します。
rosbag play smoke1.bag
ついにpcapが得られました!
VeloViewによるpcapの再生
pcapファイルできたんだからもう10合目だろう!(フラグ)
VeloViewで再生すると、再生が一瞬で終わってしまいます。当初、これが期待動作なのか分からず…。
Wiresharkでpcapを見ると、タイムスタンプが絶対時刻で2018年2月で同じ、相対時刻で見ると0です。
bag_to_pcapのソースを見ると、仕様のように思えます。
#Global header for pcap 2.4
global_header = 'd4c3b2a10200040000000000000000000000010001000000' # 24 bytes = 48 characters.
VLP-16のマニュアルを見ると、パケット構造が詳細に説明されており、UDPのペイロードとして、LiDARのデータとともにタイムスタンプも入っていることが分かります。
ついでに、Factory Byteから、Return ModeがStrongest(Dual ReturnではなくSingle Return)で、Product IDがVLP-16であることも分かりました。
pcapのタイムスタンプを、
- VLP-16のペイロードのタイムスタンプから生成する
- rosrun bag_to_pcap実行時のパケット取得タイミングからタイムスタンプを生成する
といった案を出している最中に、VeloView 3.5.0だと再生できるという情報が入ってきます。
自分は最新のVeloView 5.1を入れていました。
ということで、過去バージョンもあるダウンロードページから、3.5.0をダウンロードして再生します。
個人的に感動の再生結果がこちらの動画!
感想いろいろ
- 完成度
- きっともっと良い最短ルートがある
- VeloViewの再生結果を見ると、変換が不完全な可能性がある
- 改善点
- rosbagsは正しい方法で型定義しよう
- bag_to_pcapをPython3化しよう
- bag_to_pcapをROS2化しよう
- ROS2のrosbagから直接pcap化するソフトを書こう
- pcapにタイムスタンプを入れよう
- pcapからLiDARデータをROSに流すUDP再生ソフトとかどうかしら
- やってみて
- フラグは回収される
- それでも、実際に手を動かすと、得られることがたくさんある
- 一緒にチャレンジする方、結果を活用する方、みなさんに元気をいただきました!
謝辞
この記事の範囲だけでも、ここにたどりつくまでに、たくさんの方から、たくさんのことを教えていただきました。
技術チャレンジ部のみなさま、本当にありがとうございました!