I have a dream
私には夢がある.それはいつの日か寝ている間に論文が書きあがるようになったらいいなという夢である.残念ながらまだ自動論文執筆機は完成しておらず先週も睡眠時間を大いに削って論文を書いていたところではあるが,夢の実現に向かって寝ている間に論文に載せる表くらいは自動で作れるようになったのでそのノウハウを書き残しておく.
注意:以下のコードなどは特に実際に動かして確かめたりはしていない.朝起きるとクラッシュしていてデータが取れていなかった&論文間に合わなかったとなっても自己責任で.
rosbag play & record自動化のススメ
論文用の表を作るときによくあるのが,たくさんのrosbag(e.g., 00.bag ~ 09.bag)があって,それに対して何らかのノードを走らせて,その出力(e.g., /output_topic)を別のrosbag(e.g., 00_out.bag ~ 09_out.bag)に保存したい,というような状況である.こういう時に世の六割の大学生は1. ノードを起動,2. rosbag record,3. rosbag play,4. 待つ,というのを10回やっている(と思う)が,そんなことをしていては寝る時間が確保できない.こういう時には以下のようなlaunchファイルを使って,記録作業をバッチ化するとよい.
<!-- record.launch -->
<launch>
<arg name="src_bag"/>
<arg name="dst_bag"/>
<node pkg="my_awesome_pkg" type="my_awesome_node" name="awesome_node"/>
<node pkg="rosbag" type="record" name="bag_saver" args="-O $(arg dst_bag) /output_topic"/>
<node pkg="rosbag" type="play" name="bag_player" args="--clock $(arg src_bag)" required="true"/>
</launch>
これでroslaunch record.launch src_bag:=00.bag dst_bag:=00_out.bag
とすれば/output_topic
の記録ができる.重要なのはrosbag playにrequired=”true”をつけることで,これによってbagの再生が終われば自動的に処理が返ってくるので,以下のようなシェルスクリプトを書けば寝ている間にすべてのrosbagを処理することができる.
# record_all.sh
roslaunch record.launch src_bag:=00.bag dst_bag:=00_out.bag
roslaunch record.launch src_bag:=01.bag dst_bag:=01_out.bag
roslaunch record.launch src_bag:=02.bag dst_bag:=02_out.bag
...
もちろんファイルが多いなら以下のように書いてもいい:
# record_all2.sh
for((i=0; i<10; i++)); do
roslaunch record.launch src_bag:=$(printf "%02d.bag" $i) dst_bag:=$(printf "%02d.bag" $i)
done
dockerで並列処理のススメ
rosnodeがシングルスレッドしか対応しておらず,しかも0.1倍速でbag再生しないとうまくデータが取れない,なんてこともあったりする.そういう場合,複数bagの処理をバッチ化しても長い時間がかかり,寝て起きても処理が終わっていないということになる.ROSの場合,バックグラウンドで並列処理するのもROS_MASTER_URIの設定などがかなり面倒になる.そういう場合にはdockerで完全に分離したROS環境を作って,それぞれの中で処理を行うと良い.まず,以下のテンプレートをベースに処理したいノードのdocker環境を作成する.
FROM ros:noetic
RUN apt-get update
RUN apt-get install -y --no-install-recommends
RUN apt-get install -y --no-install-recommends wget nano build-essential git ros-noetic-pcl-ros
# create catkin_ws
RUN mkdir -p /root/catkin_ws/src
WORKDIR /root/catkin_ws/src
RUN /bin/bash -c '. /opt/ros/noetic/setup.bash; catkin_init_workspace'
# place your package in /root/catkin_ws/src
# RUN git clone https://github.com/your/package
# build catkin_ws
WORKDIR /root/catkin_ws
RUN /bin/bash -c '. /opt/ros/noetic/setup.bash; catkin_make -DCMAKE_BUILD_TYPE=Release'
# add your catkin_ws to /ros_entry.point.sh
RUN sed -i "6i source \"/root/catkin_ws/devel/setup.bash\"" /ros_entrypoint.sh
ENTRYPOINT ["/ros_entrypoint.sh"]
CMD ["bash"]
次にDockerファイルと同じディレクトリで次のコマンドでdocker環境をビルドする.
docker build -f Dockerfile --tag pcl_ros .
ビルドが成功すれば,以下のrunコマンドでdockerイメージを起動してシェルから動作確認することができる.ついでに引数に--net host
をつければホストとネットワークを共有するので,ホスト側のROS環境と普通に通信できるようになる.
docker run -it --rm pcl_ros bash
# root@69db2a1e1da8:~/catkin_ws# ls
# build devel src
続いて,バッチ化のときと同じ要領でrosbag play->node->rosbag recordの処理を行うlaunchファイルを作る.ここでは,pcl_rosのダウンサンプリング(pcl/VoxelGrid)処理を行うlaunchを試しに書いてみる.
<launch>
<arg name="src_bag"/>
<arg name="dst_bag"/>
<node pkg="nodelet" type="nodelet" name="downsampling" args="standalone pcl/VoxelGrid">
<remap from="~input" to="/velodyne_points"/>
<remap from="~output" to="/filtered_points"/>
<param name="leaf_size" value="1.0"/>
</node>
<node pkg="rosbag" type="record" name="bag_recorder" args="-O $(arg dst_bag) /filtered_points"/>
<node pkg="rosbag" type="play" name="bag_player" args="--clock -r 0.5 $(arg src_bag)"/>
</launch>
あとは作成したlaunchを以下のようなコマンドでdocker経由で起動すれば良い.
docker run -it --rm \
-v $(realpath .):/ws \
-w /workspace \
pcl_ros \
roslaunch downsample.launch src_bag:=/ws/00.bag dst_bag:=/ws/00_filtered.bag
-v
オプションはボリューム指定で,ここでは現在のディレクトリをdocker内の/ws
にバインドしている.ディレクトリ指定時には相対パスは使えないため,realpath
コマンドが便利である.-w
で先程バインドした/ws
をカレントディレクトリに設定している.あとは,dockerイメージ名(pcl_ros)のあとにdocker内で実行したいコマンドを書けば良い.
docker環境は一つずつ独立しているため,複数のターミナルから起動したりバックグラウンド処理にすることで並列して多数のROS処理系を走らせることができる.これによりシングルスレッドでしか動作しないプログラムであっても,並列処理によってCPUコア数分全体の処理を加速することができる.
並列処理数制御のススメ
今度書く