初版「ROS2ではじめよう 次世代ロボットプログラミング」ROS 1ツアー章公開
実に5年ぶりの改訂となる改訂新版「ROS2ではじめよう 次世代ロボットプログラミング」が9/19に発売されます。それを記念して、9/11からROS2本の紹介記事を連続投稿しています。
今回は初版ではかなり好評をいただいていた1章でROS 1を総ざらいする章をブログフォーマットに直して、無償公開します。2025年にROS 1はディスコンとなるため、あえて改訂新版から削除しました。 2019年当時の内容のままなので、少し古くなっている部分はご了承ください。
ROSの歴史
ROS2を学ぶ前にROS1を知らない、あるいは触ったことのない人向けに、本章1章分を使ってROS1を総ざらいしておきましょう。ROS1ですでに何ができるようになっているのかを理解してからROS2を学ぶと、より包括的にROSのことを知ることができるはずです。
ロボットソフトウェア、アプリケーションを開発することは、膨大な時間と多大な労苦を要します。 ROSはこの問題を解決する手段として、ロボットソフトウェアの共同開発を世界規模で推進することを目指しています。スタンフォード人工知能研究所の学生が開発したSwitchyardプロジェクトを起源にもち、それを引き継いだアメリカのWillow Garageが2007年から本格開発を開始し、2010年1月22日に最初のリリース版であるROS 1.0が公開されました。その後、非営利団体OSRF (Open Source Robotics Foundation) が設立され、ROSの開発を主導する役割が引き継がれました。オープンソースソフトウェアとして開発、公開されており、世界中から多くの人々が開発に参加しています。
ROSでは、ディストリビューションといって、バージョン名をつけたROSパッケージのセットが年1回から2回公開されてきました。最初の公式ディストリビューションは2010年3月2日に公開されたBox Turtleです。Linuxディストリビューションの一つであるUbuntuのコードネーム1と同じように、アルファベット順にディストリビューションが名付けられています。ディストリビューション名には、「亀の上に乗った象が地球を支えている」とする古代の宇宙観2にちなんで、亀の名前を冠することが習わしになっています。 turtlesim
というチュートリアルのプログラム内で動く亀の画像もディストリビューションごとに置き換わる凝りようです。
2019年現在、利用を推奨されるROS1のディストリビューションはROS Melodic Morenia3です。Ubuntu 18.04の公開終了日と同じく2023年5月までの長期メンテナンスが約束されています。 ROSのディストリビューションを使うことにより、比較的安定して動くROSパッケージのみをインストール対象にすることができます。つまり、一度ディストリビューションが公開されると、APIを壊してしまうような更新は行われなくなり、バグフィックスやAPIの破壊を伴わない更新のみが続くように努力されます。
2019年は新しいROS1ディストリビューションはリリースされません。これは、2019年末にPython2の開発が終了する4ことに加え、ほとんどのROS1ユーザーが長期メンテナンスに対応したROS1ディストリビューションを使うためです。次のディストリビューションNoetic Ninjemysのリリースは、Ubuntu 20.04リリースに合わせた2020年5月を予定しています。Noetic Ninjemysは、Python2からPython3への移行を行わなければならないため、開発者の多大な協力が必要となります。5
ROS1の開発環境セットアップ
ROS1関連のソフトウェアインストールは、Canonicalが開発しているUbuntu OSしか基本的に正式サポートしていません。 Ubuntu 18.04のインストールは次章をご覧ください。本書はROS2の書籍ですので、ROS1のセットアップ手順のご紹介は最小限にとどめます。詳しくはROS Wikiをご覧ください。
http://wiki.ros.org/melodic/Installation/Ubuntu
ROS1のインストール
APTソースリストを設定し、ROS1の主要パッケージを一括インストールします。本章ではROS1 Melodicをインストール対象に指定しています。
1
2
3
4
5
6
7
8
9
$ export ROS_DISTRO=melodic
$ sudo sh -c 'echo "deb http://packages.ros.org/ros/ubuntu \
$(lsb_release -sc) main" > /etc/apt/sources.list.d/ros-latest.list'
$ sudo apt-key adv --keyserver hkp://ha.pool.sks-keyservers.net:80 \
--recv-key C1CF6E31E6BADE8868B172B4F42ED6FBAB17C654
$ sudo apt update
$ sudo apt install ros-$ROS_DISTRO-desktop-full python-rosinstall \
python-rosinstall-generator python-wstool build-essential \
python-catkin-tools
そのあと、ROS1の依存関係を解決する rosdep
のデータベースを更新し、環境設定のためのセットアップスクリプトを読み込めば完成です。 bash
ユーザー用以外に zsh
ユーザー用のセットアップスクリプト setup.zsh
も用意されています。
1
2
3
4
5
$ sudo rosdep init
$ rosdep update
$ echo "source /opt/ros/$ROS_DISTRO/setup.bash" >> ~/.bashrc
$ echo "source `catkin locate --shell-verbs`" >> ~/.bashrc
$ source ~/.bashrc
Gazeboのインストール
ROS1に対応したロボット実機を持たないユーザーにもツアーに参加してもらうため、シミュレーションツールであるGazebo6もインストールしておきます。こちらは一発インストールのためのスクリプトが用意されており、こちらを実行するだけでGazeboをインストールできます。そのあと、ROSとGazeboを繋ぐROS1パッケージをインストールすれば完成です。
1
2
3
$ curl -sSL http://get.gazebosim.org | sh
$ sudo apt install ros-$ROS_DISTRO-gazebo-ros-pkgs \
ros-$ROS_DISTRO-gazebo-ros-control
Hello world!
サンプルコードのセットアップ
次項からステップバイステップで実装していくROS1デモパッケージ hello_world
のソースコードはオンラインリソース
https://github.com/youtalk/get-started-ros2/tree/release/ros1/hello_world
にビルド可能な形で全て保存されています。 本文では紙面の都合上、ライセンスやインクルード文などを省略し、ソースコードも一部のみを抜粋して記載しています。ソースコード全体をご覧になりたい場合には、こちらを参照してください。 ライセンス条項に関しては、まとめて付録に記載しています。
サンプルコードのセットアップ方法は以下の通りです。適宜、本文と照らし合わせながら読み進めていってください。
1
2
3
4
5
6
7
8
$ cd ~/ && git clone https://github.com/youtalk/get-started-ros2.git
$ cd get-started-ros2 && git submodule update --init
$ mkdir ~/ros1 && cd ~/ros1
$ ln -s ~/get-started-ros2/ros1 src
$ rosdep install --from-paths src --ignore-src -r -y
$ catkin init
$ catkin build
$ catkin source
トピック
ROS1アプリケーションはノードと呼ばれる実行可能なプログラム同士がROS1独自のTCP通信プロトコルで実装されたメッセージ通信を介して接続されることにより実現されます。
メッセージ通信にはいくつかの種類がありますが、まず初めに代表的なトピックを使ってみましょう。 トピックとはセンサー出力などのストリーミングデータを送信するために用いられるROSで一番利用頻度の高いメッセージ通信方式7です。トピックを流れるメッセージのデータ構造は .msg
形式のファイルで定義されており、この定義ファイルを基に各プログラミング言語に対応したアクセスライブラリが自動生成されます。これにより、異言語間でもメッセージ通信を簡単に実現することができます。
~/ros1/src/hello_world
ディレクトリを作成し、 hello_world
というパッケージを作成します。
1
2
3
4
$ mkdir -p ~/ros1/src && cd ~/ros1
$ catkin init
$ cd src
$ catkin create pkg hello_world --catkin-deps roscpp std_msgs
次に hello_world
ディレクトリ以下に3つのファイルを追加します。 CMakeLists.txt
は自動生成されたものに上書き保存しても結構です。
~/ros1/src/hello_world/talker.cpp(抜粋)
:
文字列トピック chatter
を送信するノードです。10Hz(1秒あたり10回)の送信周期に設定しています。送信される文字列は Hello world!
です。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
int main(int argc, char **argv)
{
// ROS1インフラとtalkerノードの初期化
ros::init(argc, argv, "talker");
// ノードの本体
ros::NodeHandle n;
// chatterトピックを送信する送信器
ros::Publisher chatter = n.advertise<std_msgs::String>(
"chatter", 1000);
// 10Hzの送信周期
ros::Rate loop_rate(10);
// 強制終了していないか確認
while (ros::ok())
{
// 文字列の送信データ
std_msgs::String msg;
msg.data = "Hello world!";
ROS_INFO("%s", msg.data.c_str());
// 文字列の送信
chatter.publish(msg);
// ノードの処理サイクルを1回分進行
ros::spinOnce();
// 10Hzの送信周期になるように待機
loop_rate.sleep();
}
return 0;
}
~/ros1/src/hello_world/listener.cpp(抜粋)
:
文字列トピック chatter
を受信するノードです。受信するたびにコールバック関数 callback()
が自動的に呼び出されます。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// chatterトピックのコールバック関数
void callback(const std_msgs::String::ConstPtr& msg)
{
ROS_INFO("%s", msg->data.c_str());
}
int main(int argc, char **argv)
{
ros::init(argc, argv, "listener");
ros::NodeHandle n;
// chatterトピックの受信器の設定
ros::Subscriber chatter = n.subscribe("chatter", 1000, callback);
// ノードの処理サイクルの進行開始
ros::spin();
return 0;
}
~/ros1/src/hello_world/CMakeLists.txt
:
パッケージをビルドするために必要な構成ファイルです。 talker
と listener
が実行ファイルとして生成され、パッケージ内に保存された任意の実行ファイルを実行する rosrun
コマンドで呼び出すことができるようになります。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
cmake_minimum_required(VERSION 2.8.3)
project(hello_world)
# 依存パッケージの発見
find_package(catkin REQUIRED COMPONENTS roscpp std_msgs)
# ヘッダーファイルの読込先設定
include_directories(include ${catkin_INCLUDE_DIRS})
# ビルド設定の便利関数
function(custom_executable target)
add_executable(${target} ${target}.cpp)
target_link_libraries(${target} ${catkin_LIBRARIES})
install(TARGETS ${target}
RUNTIME DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION})
endfunction()
# talkerノードのビルド設定
custom_executable(talker)
# listenerノードのビルド設定
custom_executable(listener)
# catkinパッケージ宣言
catkin_package()
ようやくビルドの準備ができました。ビルドコマンドを実行してみましょう。
1
2
3
4
$ ls
CMakeLists.txt listener.cpp package.xml talker.cpp
$ cd ~/ros1
$ catkin build
ビルドに成功したら、 talker
ノードと listener
ノードを別ターミナル8でそれぞれ実行してみましょう。 ターミナル0で実行する roscore
は
- ROSマスター: ROS1ノード同士の登録や名前解決
- パラメータサーバ: 1.3.4項で説明するパラメータ機能
rosout
ロギング用ノード: ロギング機能を提供するrosout
ノード
を起動するためのコマンドです。ROS1ノードを実行する前に必ず実行しておく必要があります。なお、ターミナル0は以降でも使い続けるため、起動したままにしておいてください。
- ターミナル0
1
$ roscore
- ターミナル1
1
2
3
4
5
$ catkin source
$ rosrun hello_world talker
[INFO] [talker]: 'Hello world!'
[INFO] [talker]: 'Hello world!'
[INFO] [talker]: 'Hello world!'
- ターミナル2
1
2
3
4
5
$ catkin source
$ rosrun hello_world listener
[INFO] [listener]: 'Hello world!'
[INFO] [listener]: 'Hello world!'
[INFO] [listener]: 'Hello world!'
talker
が10Hzで送信した Hello world!
という文字列が、 listener
でもおうむ返しされたら成功です。
サービス
サービスは一般的にはRPCとも呼ばれ、遠隔のノードが持つ関数を呼び出す機能を実現します。非同期にストリーミングデータが流れてくるトピックと違い、単一のデータを同期的にやり取りするために用いられます。サービスを呼び出すためのリクエスト(関数でいうところの引数)とレスポンス(関数でいうところの返り値)の定義は .srv
形式のファイルに定義されます。 .msg
で定義されたデータ型も使用できます。
ここでは SetMessage.srv
というサービス定義ファイルを作って hello_world
の文字列を変更できるように機能拡張してみましょう。
~/ros1/src/hello_world/srv/SetMessage.srv
:
ROSでは、トピックやサービスに独自の定義を使いたい場合には、 .msg
や .srv
という定義ファイルを用意する必要があります。 SetMessage.srv
は文字列を引数に取り、真偽値を返すサービスの定義ファイルです。
1
2
3
4
5
# 引数
string message
---
# 返り値
bool result
~/ros1/src/hello_world/CMakeLists.txt(抜粋)
:
新たに message_generation
と message_runtime
を依存パッケージに加えて、 add_service_files()
というCMakeコマンドを呼び出します。
1
2
3
4
5
6
7
# 依存パッケージの発見
find_package(catkin REQUIRED COMPONENTS roscpp std_msgs
message_generation message_runtime)
# SetMessageサービスの生成
add_service_files(FILES SetMessage.srv)
generate_messages(DEPENDENCIES std_msgs)
ビルド設定も微修正が必要です。詳しくはオンラインリソースをご覧ください。
~/ros1/src/hello_world/talker_with_service.cpp(抜粋)
:
前項からの変更がある set_message
サービスの定義と登録の部分のみ抜き出します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 文字列の送信データ
std_msgs::String msg;
// set_messageサービスのコールバック関数
bool SetMessage(hello_world::SetMessage::Request &req,
hello_world::SetMessage::Response &res)
{
ROS_INFO("message %s -> %s", msg.data.c_str(), req.message.c_str());
// 文字列の変更
msg.data = req.message;
// 返り値をtrueに設定
res.result = true;
return true;
}
int main(int argc, char **argv)
{
// talkerノードの初期化
ros::init(argc, argv, "talker");
// ノードの本体
ros::NodeHandle n;
// set_messageサービスの登録とコールバック関数の登録
ros::ServiceServer service = n.advertiseService(
"set_message", SetMessage);
...
}
listener.cpp
は前回のままです。 set_message
サービスもプログラムから呼ぶことができますが、ここでは別の手段として、コマンドラインから呼び出してみましょう。 rosservice
というコマンドを使えば実現できます。
- ターミナル1
1
2
$ catkin source
$ rosrun hello_world talker_with_service
- ターミナル2
1
2
$ catkin source
$ rosrun hello_world listener
- ターミナル3
1
2
$ rosservice call /set_mesage "Hello service!"
true
ターミナル1とターミナル2の Hello world!
という文字列が、ターミナル3を実行した後は Hello service!
に置き換わったはずです。 実は、単におうむ返しするだけであれば、 listener.cpp
を書かなくてもほぼ同じことがコマンドラインから実行できます。
1
2
3
4
$ rostopic echo /chatter
'Hello service!'
'Hello service!'
'Hello service!'
パラメータ
ROSにはトピックとサービス以外にも、パラメータというメッセージ通信があります。トピックは非同期のストリーミングデータの通信に、サービスは同期処理を用いた単一データの通信に利用されることが想定されています。
それに対してパラメータは、ROSアプリケーション全体で共有される1.3.2項で登場したパラメータサーバと呼ばれるキーバリューストア9を用い、単純なデータ構造を読み込んだり、書き込んだりする用途に利用されます。環境変数やグローバル変数と同じようなものと捉えると理解しやすいでしょう。
使うことのできるデータ型とそのデータ構造は以下の通りです。
- 真偽値
- 整数
- 浮動小数点数
- 文字列
- 上記データ型のリスト
- バイナリデータ
トピックやサービスと違い、データ型を独自に定義することはできません。トピックやサービスと違い、データ型を独自に定義することはできません。パラメータで実際にやりとりするのは文字列であり、受信側はそれを毎回、想定したデータ型、データ構造と解釈して読み込みます。データ型を定義しなくても良いため、 CMakeLists.txt
の変更などを加えなくても簡単に利用することができ、便利なコマンドライン引数のように使われます。
では、そのパラメータの機能を使って、 talker
の文字列を装飾してみましょう。
~/ros1/src/hello_world/talker_with_service_param.cpp(抜粋)
:
decoration
という文字列パラメータに装飾用の文字列が設定されると、受信した文字列の前後をその装飾で囲むようにしました。 前節から変更のある while
文だけ抜き出します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 強制終了していないか確認
while (ros::ok())
{
// decorationパラメーターの取得
std::string decoration = "";
n.param<std::string>("decoration", decoration, "");
std::string decorated_data = decoration + msg.data + decoration;
ROS_INFO("%s", decorated_data.c_str());
// 文字列の送信
chatter.publish(msg);
// ノードの処理サイクルを1回分進行
ros::spinOnce();
// 10Hzの送信周期になるように待機
loop_rate.sleep();
}
talker_with_service_param
を実行中に別ターミナルで decoration
パラメータに @@@@
を設定すると、標準出力の文字列が Hello world!
から @@@@Hello world!@@@@
に変わります。
- ターミナル1
1
2
3
4
5
6
7
$ catkin source
$ rosrun hello_world talker_with_service_param
[INFO] [talker]: 'Hello world!'
[INFO] [talker]: 'Hello world!'
...
[WARN] [talker]: '@@@@Hello world!@@@@'
[WARN] [talker]: '@@@@Hello world!@@@@'
- ターミナル2
1
$ rosparam set decoration @@@@
以上で読者の皆様もROSの世界の入り口に立つことができました。ROS1のエコシステムは実質的にこのメッセージ通信の上に成り立っています。あとは後述するアクションの仕組みと、これらのメッセージ通信機能を使って実装されているROS1の主要パッケージの概要を把握すれば、ROS1アプリケーションを使いこなすだけでなく、開発を開始することもできるようになるはずです。
引き続き、ROS1の主要パッケージのツアーにご参加ください。
センシング
画像センサー
ROSは主要なセンサーの出力の定義を .msg
のメッセージファイルであらかじめ規定しています。これにより、画像センサーであれば、それがたとえ
- USB接続の一般的なウェブカメラ
- Ethernetケーブルで繋がる特殊な産業用カメラ
- モノクロカメラ
- 高解像度フルカラーカメラ
- カラーエンコードが標準的ではないカメラ
であろうとも、統一的なインタフェースで扱うことができます。 画像センサーから得られる画像を表すメッセージは sensor_msgs/Image.msg
に定義されています。その定義の中身を覗いてみましょう。
1
2
3
4
5
6
7
std_msgs/Header header # タイムスタンプや親座標系名など
uint32 height # 画像の高さ方向のピクセル数
uint32 width # 画像の横方向のピクセル数
string encoding # エンコーディング形式
uint8 is_bigendian # dataがビッグエンディアン並び順かどうか
uint32 step # 1ピクセルあたりのバイト幅
uint8[] data # 画像のバイト列
最初の一つ以外は画像データそのものを表すための値の定義ですが、最初の std_msgs/Header header
は違います。この中にはこの画像がいつ撮られたものなのかを表すタイムスタンプと、この画像がどのカメラで撮られたものなのかを表す親座標系、などの情報が格納されています。この std_msgs/Header
はほとんどのセンサーデータのメッセージ定義に組み込まれています。
ROSには tf
と呼ぶ座標系をツリー構造として扱う仕組みがあり、このおかげでユーザーは任意の座標系間の座標変換を行うことができます。図 75 はLiDARを搭載した移動ロボットだと思ってください。台車の中央に base_link
というフレーム10を置き、そこから上に20cm、前に10cmの位置に base_laser
というLiDARのフレームを置きました。 tf
により、フレーム間の座標変換が行えるため、LiDARで得られた距離データを台車基準の座標に変換することができます。
さらにタイムスタンプと履歴を用いて過去の座標値を内挿で求めることもできます。 図 75 の例は簡単ですが、現実のロボットはアクチュエーターやセンサーをたくさん搭載しており、座標系が多段の木構造で構成され、かつ姿勢によって座標系間の相対関係が常時変更されるような変換を考えないといけません。座標系の扱いと座標変換は自分で実装すると、バグを生みやすいことで知られています。それがROSには親和性の高い形であらかじめ組み込まれていることは非常に好ましいです。
適当なUSB接続のWebカメラ(UVCカメラ)をご用意ください。後はWebカメラに対応した uvc_camera
パッケージをインストールすれば、それだけでカメラがROSアプリケーションに繋がります。インストールする2つ目のパッケージ rqt_image_view
は、カメラから出力された画像をGUIで表示するビュアーです。
1
$ sudo apt install ros-$ROS_DISTRO-uvc-camera ros-$ROS_DISTRO-rqt-image-view
WebカメラをPCに接続し、 uvc_camera_node
を実行してみましょう。
1
2
3
$ ls /dev/video*
/dev/video0
$ rosrun uvc_camera uvc_camera_node
これだけでWebカメラから画像のストリーミングが開始されます。トピックの一覧を取得する rostopic list
コマンドや rqt_image_view
ノードを実行してストリーミング結果を確認してみましょう。
1
2
3
4
5
$ rostopic list
...
/image_raw
...
$ rqt_image_view /image_raw
画像センサーの出力を統一的なインタフェースで扱えるようになったことにより、その上に再利用性の高い画像処理アルゴリズムやキャリブレーションツール、アプリケーションなどを構築することができます。ご興味を持たれた方は、例えば以下のROS Wikiをご覧になってみてください。
- 単眼カメラの内部パラメータのキャリブレーション: http://wiki.ros.org/camera_calibration/Tutorials/MonocularCalibration
- ステレオカメラの内部・外部パラメータのキャリブレーション: http://wiki.ros.org/camera_calibration/Tutorials/StereoCalibration
ORB-SLAM2
による地図作成と自己位置推定: http://wiki.ros.org/orb_slam2_rosYOLOv2
などを使った深層学習ベースの物体認識器: http://wiki.ros.org/rail_object_detectorOpenCV
11のデータ形式と相互変換: http://wiki.ros.org/cv_bridge
最後の cv_bridge
パッケージがあれば、コンピュータビジョン業界でデファクトスタンダードのOpenCVに実装された多種多様な画像処理アルゴリズムを利用することができます。 このように、古典的でありながら慎重な実装が必要なキャリブレーションツールから最新の国際論文に掲載されているようなアルゴリズムまで、ユーザーが手軽に試すことができる再利用性の高さがロボット研究者、エンジニアにROSが非常に好まれている理由になっています。
距離センサー
距離センサーの出力データを表すメッセージは sensor_msgs/PointCloud2.msg
12で定義されています。この定義では出力データを3次元のポイントクラウド13の形式で表していますが、それを白と黒の濃淡で距離を表す2次元の距離画像として取得することもできます。 距離センサーは画像センサー以上に測定メカニズムが多岐にわたり、センサー出力のデータ形式も多種多様です。可視光を使うものあれば、不可視光を使うものもありますし、先の画像センサーで得られた色情報をポイントクラウドに持たせることもあります。測距方式も下記以外にも様々な方式があります。
- ドットパターンを照射する測距方式
- 光の反射の往復時間を測定する方式
- ステレオカメラを使って視差画像から生成する方式
ここでは、近年ロボット技術者の間で普及しているIntel製RealSense D435i14を使います。 距離画像を表示するだけでなく、可視化ツールを使って3次元のポイントクラウドを描画する方法も説明します。
D435iのROSドライバーのインストール自体は本章の本筋ではないため、下記ドキュメントをご覧ください。
https://github.com/IntelRealSense/realsense-ros/blob/development/README.md
それでは、D435iをPCに接続してセンサーデータの取得を開始してみましょう。
1
$ roslaunch realsense2_camera rs_rgbd.launch
これだけでD435iから大量のトピックが送信されます。 launch
ファイルという複数のROSノードを同時実行する手順を記述したXMLファイルに、D435iの起動だけでなく様々な便利ノードも同時実行するように記述されているためです。 launch
ファイルの実行には、これまでの単一ノードの実行コマンド rosrun
ではなく、専用の roslaunch
コマンドを使います。 rostopic list
でどんなトピックがストリーミングされているか確認してみましょう。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$ rostopic list
/camera/Motion_Module/parameter_descriptions
/camera/Motion_Module/parameter_updates
/camera/RGB_Camera/parameter_descriptions
/camera/RGB_Camera/parameter_updates
/camera/Stereo_Module/parameter_descriptions
/camera/Stereo_Module/parameter_updates
/camera/accel/imu_info
/camera/accel/sample
/camera/color/camera_info
/camera/color/image_raw
/camera/color/image_raw/compressed
/camera/color/image_raw/compressed/parameter_descriptions
/camera/color/image_raw/compressed/parameter_updates
/camera/depth/camera_info
/camera/depth/image_rect_raw
/camera/depth/image_rect_raw/compressed
/camera/depth/image_rect_raw/compressed/parameter_descriptions
/camera/depth/image_rect_raw/compressed/parameter_updates
...
D435iには距離センサーだけでなく、画像センサーも内蔵しているため、カラー画像も取得できます。また、その画像をJPEG形式に圧縮した画像のトピックも用意されています。 このうち、距離画像のトピックは /camera/depth/image_rect_raw
です。画像センサーのときと同じ手段で表示してみましょう。
1
$ rosrun image_view image_view image:=/camera/depth/image_rect_raw
距離画像だけでなく、ポイントクラウドを3次元的に確認することもできます。 RViz
という汎用的な可視化ツールの機能を使えば実現できます。 RViz
はROSアプリケーション開発には欠かすことのできない非常に便利なツールです。プラグイン形式で開発されており、様々なセンサーの出力やロボットの現在状態などを統合的に表示できるだけでなく、ロボットに指令を与えるコントローラーの役割をするウィンドウも呼び出すことができます。 RViz
を実行してみましょう。
1
$ rosrun rviz rviz -d `rospack find realsense2_camera`/rviz/pointcloud.rviz
-d
以下に指定したファイルは RViz
の起動時のウィンドウ構成を定義するものです。これにより、ユーザーは毎回面倒なマウス操作をすることなく、所望のウィンドウ構成をいくつも作ることができます。今回のウィンドウ構成では、左側にD435iからのポイントクラウドを3次元描画して、右側上部に画像センサーのカラー画像出力、右側下部に距離画像センサーの濃淡画像出力を表示しています。
画像センサーと同様に、距離センサーの出力も統一的なインタフェースで扱えるようになったことにより、ポイントクラウドの処理アルゴリズムを再利用性高く実装することが可能になりました。例えば以下のROS Wikiをご覧になってみてください。
PCL
15のデータ形式と相互変換: http://wiki.ros.org/perception_pcl- 距離センサーの出力の一部を切り出し、LiDAR16の出力に変換: http://wiki.ros.org/pointcloud_to_laserscan
前者の perception_pcl
パッケージを使えば、3次元ポイントクラウド処理のためのソフトウェアライブラリである PCL
に実装された様々なアルゴリズムを利用することができます。 後者の pointcloud_to_laserscan
パッケージを使えば、LiDARの使用を前提としたナビゲーションアルゴリズムを精度の違いはあれど距離センサーで代用することもできます。ナビゲーションアルゴリズムに関しては以降で取り上げます。
Gazeboシミュレーション
ROSはロボットアプリケーション開発のためのミドルウェアですが、開発者がみんなロボットを持っているわけではありません。シミュレーションツールを使って、ロボット実機の代わりにできれば嬉しいユーザーは多いはずです。 ロボット実機を持っているユーザーでも、例えば計算機上で機械学習をしたいユーザーにとってもシミュレーションツールは不可欠の道具です。
そこで、ROSの主開発団体であるOpen Source Robotics FoundationがROSと同じように開発しているのが、Gazeboです。同じ開発企業が開発しているだけあり、ROSとは非常に親和性の高いソフトウェアになっています。 Gazeboはサーバクライアント形式で実装されており、物理エンジンだけを起動したければサーバだけを、3次元ビューで描画するウィンドウも必要ならクライアントも起動する、といった使い分けもできます。
ただし、物理シミュレーション一般にいえることですが、計算量が非常に多いため、お使いのPCにGPUが追加されていなかったり、非力なCPU、少ないメモリしか内蔵していない場合、物理シミュレーションのサイクルが非常に遅くなってしまいます。例に漏れずGazeboもシミュレーション実行中は非常に重たいソフトウェアです。ご注意ください。
turtlesimシミュレーション
Gazeboは3次元の物理シミュレーションを行う高性能なソフトウェアですが、いきなりその世界に入る前に、2次元の簡単なシミュレーションから入門してみましょう。
コンピューターグラフィクスのプログラミングを勉強するとき、最初に2次元空間の描画を学ぶ題材として、タートルグラフィックス17という単純なプログラミングから入門することがあります。 これは2次元の空間にいるペンを持った亀を
- 前へ進む
- 後ろへ進む
- 右へ旋回する
- 左へ旋回する
という単純なコマンドと制御構文だけを組み合わせて動かし、複雑な軌跡や絵を描かせるというものです。 基本的に車(あるいは移動ロボット)もこの4つのコマンド(に加えてアクセルとブレーキ)だけで動いています。こんな単純なコマンド列ではありますが、これを学べば、立派に移動ロボットをナビゲーションできるようになるわけです。
ROSにもこの題材を真似て、座標系やナビゲーションの仕組みを学ぶチュートリアルのためのパッケージ turtlesim
が用意されています。ROSのロゴもちょうど亀の名前を冠しているので、タートルグラフィックスととても相性がいいですね。 turtlesim
を実行してみましょう。
- ターミナル1
まずは、 turtlesim
のウィンドウを表示します。
1
$ rosrun turtlesim turtlesim_node
- ターミナル2
次に、上記ウィンドウ内にいる亀をキーボード入力で動かすためのノードを起動します。
1
2
3
4
5
6
7
$ rosrun turtlesim turtle_teleop_key
[ INFO] [1412519294.247868693]: Starting turtlesim with node name /turtlesim
[ INFO] [1412519294.251346604]: Spawning turtle [turtle1] at x=[5.544445], y=[5.544445], theta=[0.000000]
Reading from keyboard
---------------------------
Use arrow keys to move the turtle.
キーボードの上下左右を押してみてください。ウィンドウ内の亀がその入力の通り動き回ることが確認できます。
TurtleBot3シミュレーション
前置きが長くなりましたが、これを3次元空間に拡張しましょう。Open Source Robotics FoundationとROBOTIS18が公式にサポートするROSナビゲーションプログラミング入門のためのロボットであるTurtleBot319が販売されています。
このシリーズ展開されたTurtleBot3の一つであるTurtleBot3 Burger(図 125 一番左)のモデルを使ってGazeboシミュレーションを試してみましょう。 まず、TurtleBot3関連のROS1パッケージをインストールします。
1
$ sudo apt install ros-$ROS_DISTRO-turtlebot3 ros-$ROS_DISTRO-turtlebot3-gazebo
TurtleBot3 BurgerのGazeboシミュレーターを起動しましょう。Gazeboは初回起動時にインターネットから3Dモデルなどをダウンロードするため、初回起動時のみ起動時間がかかるかもしれません。
1
2
$ export TURTLEBOT3_MODEL=burger
$ roslaunch turtlebot3_gazebo turtlebot3_world.launch
起動が完了すれば、図 129 のような亀の部屋に囲まれたTurtleBot3 Burgerが現れるはずです。図中の中央寄り左下に見える小さな黒い塊がTurtleBot3 Burgerです。
このTurtleBo3 Burgerを外部から動かすために、 turtlesim
パッケージでも登場したキーボード操作ノードを起動します。操作方法は turtlesim
のときと全く同じです。
1
$ roslaunch turtlebot3_teleop turtlebot3_teleop_key.launch
これでロボット実機がある状況をシミュレーション上で再現できたことになります。もちろんロボットの現在状態やセンサー出力も再現されているので、さきほど紹介した RViz
でこれらのデータを可視化することもできます。TurtleBot3 Burgerには簡易的なLiDARも付属しており、図 132 では、LiDARの現在のスキャン結果が描画されています。
1
2
$ export TURTLEBOT3_MODEL=burger
$ roslaunch turtlebot3_gazebo turtlebot3_gazebo_rviz.launch
ナビゲーション
SLAMによる地図作成
「はじめに」の章でも述べたように、ROS1で最初に力を注いで開発を進めたのが、ナビゲーションパッケージ20です。ロボットが環境を巡回して得られたセンサーデータを使って2次元地図を作成する機能と、その作成されたグローバルマップ上の任意の目標地点への経路計画を行う機能を提供しています。
前節のTurtleBot3 Burgerには簡易的なLiDARも付属しており、ナビゲーションパッケージに対応した立派な移動ロボットです。そのシミュレーターを提供する turtlebot3_gazebo
パッケージは距離センサーやカラーカメラの設定も定義されており、シミュレーション上でナビゲーション実験を行うことができます。
ターミナル1は前節のTurtleBot3 BurgerのGazeboシミュレーターをそのまま起動しておきましょう。
- ターミナル2
次にSLAMアルゴリズムの一つである GMapping
21を開始する turtlebot3_slam.launch
を実行します。これにより、TurtleBot3 Burgerの上部に取り付けられたLiDARからの2次元点群データの記録が始まり、過去の履歴データと現在の点群データの合致を見ることで、2次元地図を時々刻々更新していきます。
1
2
$ export TURTLEBOT3_MODEL=burger
$ roslaunch turtlebot3_slam turtlebot3_slam.launch slam_methods:=gmapping
- ターミナル3
先ほどと同様、キーボード操作ノードを起動して GMapping
が動作中にキーボード入力を使って、TurtleBot3 Burgerに亀の囲いの中を巡回させてください。 RViz
の画面を見ながら、図 140 のような亀の囲いの地図らしいものが完成したら次に移ります。
1
2
$ export TURTLEBOT3_MODEL=burger
$ roslaunch turtlebot3_teleop turtlebot3_teleop_key.launch
- ターミナル4
最後に、 RViz
上で地図が完成したら、その2次元地図を再利用できるようにファイル保存します。
1
2
$ export TURTLEBOT3_MODEL=burger
$ rosrun map_server map_saver -f ~/map
目標地点への経路計画
先ほど得られた地図を使って、TurtleBot3 Burgerを目標地点へ自動で動かしてみましょう。
- ターミナル1
まず前節と同様にTurtleBot3 BurgerのGazeboシミュレーターを起動します。
1
2
$ export TURTLEBOT3_MODEL=burger
$ roslaunch turtlebot3_gazebo turtlebot3_world.launch
- ターミナル2
ナビゲーション関連のノード群を設定、実行する turtle3_navigation.launch
を実行すると、先ほど作成した地図が RViz
上に表示されます。黒で塗りつぶされた部分は障害物だと判断されるので、そこにロボットは移動できません。 RViz
上のツールバーにある 2D Nav Goal
ボタンを押した後、灰色で塗りつぶされた部分をマウスでドラッグしてみてください。ドラッグのクリックした位置がロボット目標位置( XY
)になり、クリックを戻した方向がロボットの目標姿勢( Yaw
)になります。
現在の地点から今与えた目標地点への経路計画が成功すれば、その経路に従ってTurtleBot3 Burgerが動き出すはずです。
1
2
3
$ export TURTLEBOT3_MODEL=burger
$ roslaunch turtlebot3_navigation turtlebot3_navigation.launch \
map_file:=$HOME/map.yaml
今回は作成した地図上に未知の物体は全くない状況でしたが、作成した時点では存在しなかった動的な障害物を避けるように経路計画を再計画する機能も組み込まれています。このおかげで、人やロボットが行き交うような状況でもある程度ナビゲーションを行うことができます。
マニピュレーション
ROS1がナビゲーションの次に力を注いで開発を進めているのが、マニピュレーションのための MoveIt
パッケージ22です。ロボットの形状やリンク構造、アクチュエータ特性などを記述した構成ファイル23を記述すれば、それを元に運動学や逆運動学24を自動計算する機能と、ロボットアーム(マニピュレーターとも呼ばれます)の動作軌道を計画する機能を提供しています。
こちらもROBOTISが販売している5軸ロボットアームのOpenManipulator25を例に話を進めます。
MoveIt
は実機やGazeboシミュレーターがなくても動くように、アクチュエーターの動きをダミーに置き換えたデモンストレーション動作に対応しています。今回は軽量な実行が可能なこのデモンストレーション動作で進めましょう。
OpenManipulatorセットアップ
初めにOpenManipulatorの MoveIt
関連のROS1パッケージをインストールしましょう。
1
$ sudo apt install ros-$ROS_DISTRO-open-manipulator-moveit
OpenManipulatorの MoveIt
デモンストレーションを実行してみましょう。
1
$ roslaunch open_manipulator_moveit demo.launch
図 157 左下のウィンドウに表示された Planning
タブを選択し、 Query
欄の Goal State:
を <random>
に変更すると、ロボットの新しい目標姿勢がランダム生成されます。その後、その左にある Commands
欄の Plan
, Execute
を順に実行すると、現在姿勢から目標姿勢への軌道が自動生成、実行されます。ロボットの手先にある矢印をドラッグすることでもロボットの目標姿勢を変更できます。
MoveIt
によって生成された軌道はロボット自身の干渉を含まない自己干渉回避機能だけでなく、前述の距離センサーと組み合わせれば、その場の距離センサーからの出力を使って、環境中の動的な障害物を回避する障害物回避機能も組み込まれています。
MoveIt
セットアップアシスタント
このように非常に便利な MoveIt
ですが、0から構成ファイルを記述してロボットを MoveIt
対応させるのはとても面倒な作業が必要でした。 しかし、今では MoveIt
セットアップアシスタント26と呼ばれる便利ツールができたおかげで、ロボットの関節構造と幾何学的形状を記述したURDF23ファイルを用意して、それを読み込むだけで MoveIt
に対応させることができるようになりました。
MoveIt
セットアップアシスタントの具体的な使用手順は、ROS1ツアーの大筋から逸れるため、ここでは紹介だけにとどめておきます。
ROS1のない世界
もし世界にROS1が存在しなかったら、どうやってロボットアプリケーションを作ったらよいでしょうか?今ならたくさんのオープンソースソフトウェアがあるのですから、それらを組み合わせればできるはずです。少し空想のお話にお付き合いください。
- OpenCV: 画像処理
- PCL/Open3D27: ポイントクラウド処理
- Kaldi28: 音声認識
- OpenSLAM: 移動ロボットのための地図生成と自己位置推定
- IKFast: ロボットの逆運動学の自動生成
- OMPL/OpenRAVE29: マニピュレーターの動作生成
これらを上手いこと使えば、手元にあるロボットはすぐ動き出すに違いありません。アプリケーションのプログラミングを始めましょう。おっと、買ってきたばかりのアクチュエーターもセンサーも通信の仕方がばらばらで、まずはそれをプログラムから呼べるようにするところから始めないといけませんでした。
ようやくアクチュエーターもセンサーも繋がったし、あとはどこにセンサーを設置するか、自由度30構成をどうするかをプログラミングしたら、ロボットは動き出すはずです。うむむ、どこかの自由度で座標変換を間違ってしまい、運動学の計算結果がおかしくなってしまいました。
動かす前に、現在の自由度構成の状態をGUIで確かめてみましょう。3次元的に確認が必要なので、3次元描画ライブラリのOpenGL31で実装してみましょう。GUIで可視化できたと思ったら、座標変換だけではなく、ロボットの部材同士の干渉も起きていることがわかってしまいました。FCL32などを使って干渉計算もしないといけません。すぐにロボットを動かせると思ったのに、物理エンジンのためのプログラミングも必要になってしまいました。
なんとかロボットの運動学が計算できるようになって、逆運動学もIKFastを使って茨の道を越えて自動生成できたし、OMPLを使えば動作生成もできるようになりました。ようやくロボットアプリケーションの開発に取りかかれそうです。
せっかく腕もあって、カメラも備えているロボットを購入したので、今流行りの深層学習ベースの物体認識を使ってピックアンドプレースを実装してみましょう。しかし、ここでも問題がありました。これまではC/C++でプログラミングしてきましたが、深層学習ライブラリはほとんどすべてがPythonで実装されています。C/C++とPythonの間でカメラデータと物体認識結果をやりとりしなくてはなりません。さぁ、どうやって実装したものでしょうか?ソケット通信にしましょうか?いやいや効率を考えたら、Cで書いてPythonラッパーを用意する手もあります。いきなり効率を目指すことは愚策だといわれているので、ソケット通信にしましょう。
考えることがもう一つありました。ソケット通信するデータの形式を考えないといけません。Web業界でデファクトスタンダードのProtocol Buffers33とgRPC34を使えば、ソケット通信も自分で実装しなくて済みます。カメラのデータなので、単にOpenCVの行列変数の中身をバイナリー形式で送れば、とても効率が良さそうです。何てことだ、縦横サイズやピクセルの並び、浮動小数点数の型も指定しないとバイナリーが読めません。画像一つを取っても、どんなデータ形式にするかは悩ましいものです。
ようやくC/C++とPythonの間で物体認識のためのやりとりができました。ですが、プログラムが2つに分かれたせいで、コンパイルやテスト、デバッグ、ロギングを2箇所で分かれて行う必要が出てきました。開発中、頻繁に行う作業なので、地味に面倒です。プログラムの実行も専用のスクリプトを書かないと、2つのプログラムに引数を渡しながら同時起動することができません。
嘘のような本当の話
何だか信じられない話かもしれませんが、ロボットアプリケーション開発というのは、事実としてこういった泥臭いインテグレーション作業の連続です。上述の空想のお話は、著者が大学院の時にロボット対話システムを作った時に遭遇した経験を基に書きました。その時はまだROSは今のように有名にはなっておらず、こういったミドルウェアに頼ることなくシステムを構築しました。複数人に手伝ってもらったにも関わらず、構築作業だけで数ヶ月もかかってしまいました。この経験は後に非常に役立ちはしているのですが、現在の研究スタイルにはもはや合致しないと思われます。
思慮深い読者の皆様は、ROSを使いましょう。ROSでは、先ほど問題になった
- メッセージ通信
- データシリアライゼーション
- データ型標準化
- 複数言語対応
- 複数ノード起動支援スクリプト
- データ可視化ツール
はROS1標準システムとしてすべて提供されます。こうした共通の開発資産をロボット開発者全員で共有することで、それらは維持、管理も含めたエコシステムとして醸造、昇華されてきました。ロボット開発者は車輪の再発明をすることなく、誰もが使っている車輪の上に車(ロボット)を開発できるようになったのです。シミュレーション連携やデータ保存・再生機能も充実しているので、ロボット実機がなくてもロボット開発が始められます。
ROS1のおかげで、ロボット開発の難しさは劇的に下がりました。ソフトウェアエンジニアリングができる人は、市販のROS1対応ロボットを購入すれば、その上に自分のアプリケーションを開発することができるようになりました。ハードウェアエンジニアリングができる人は、自分で開発したロボットをROS1のAPIに合わせてアクチュエーターとセンサーを接続すれば、最新のアルゴリズムをいとも簡単に活用できるようになりました。
ROS2では機能がさらに増え、洗練され、応用範囲は大きく広がります。それでは次章から本編の始まりです。
脚注
1 https://www.ubuntulinux.jp/ubuntu
2 https://ja.wikipedia.org/wiki/地球平面説
4 https://www.python.org/dev/peps/pep-0373/#maintenance-releases
5 https://discourse.ros.org/t/planning-future-ros-1-distribution-s/6538
7 便宜上「送信」、「受信」という言葉を使っていますが、実際にはトピックは出版・購読型(Publisher/Subscriber)通信を行なっています。一般的なサーバ・クライアント形式と違い、トピックの送信(出版)者側と受信(購読)者側がお互いを特定することなく非同期通信する仕組みです。ただ、日本人には出版・購読型通信といっても馴染みがないため、以降では送信、受信と記します。
8 ユーザーがデータをコンピュータに入出力するのに使うソフトウェアです。
9 KeyとValueを組み合わせる単純な構造からなる辞書型のデータ構造です。Keyを指定すると、Keyに関連付けられたValueが呼び出される仕組みとなっています。ROSのパラメータではKeyは文字列型が用いられます。
10 座標系のことをCoordinate Frameと英語で呼び、Frameと略することがあります。 tf
はTransform Frameの頭文字をとったもので、座標系間の変換を行うパッケージです。
12 sensor_msgs/PointCloud.msg
という 2
がつかないメッセージ定義もありますが、こちらは1世代古い定義です。互換性維持のために残されてはいますが、新規に使う場合には PointCloud2
の方を使うことをお勧めします。
13 コンピュータで扱う点の集合を意味し、点群とも呼ばれます。多くの場合、空間は3次元であり、直交座標 (x, y, z)
で表現されることが多いです。
14 https://www.intelrealsense.com/depth-camera-d435i/
16 Light Detection and Ranging(光の検出と測距)の略。レーダーよりも短い波長の紫外線、可視光線、近赤外線を用いた測距装置を指します。
17 https://en.wikipedia.org/wiki/Turtle_graphics
19 http://emanual.robotis.com/docs/en/platform/turtlebot3/overview/
20 http://wiki.ros.org/navigation
21 https://openslam-org.github.io/gmapping.html
24 ロボットの関節角度列を与えて手先の位置姿勢を求めることを(順)運動学、逆に手先の位置姿勢を与えてロボットの関節角度列を求めることを逆運動学と呼びます。
25 https://robots.ros.org/openmanipulator/
26 http://docs.ros.org/kinetic/api/moveit_tutorials/html/doc/setup_assistant/setup_assistant_tutorial.html
30 ロボットの機構全体の構造を決める可動変数の数を指します。ここでは、基本的に関節の数やアクチュエーターの数だと思ってください。
32 https://github.com/flexible-collision-library/fcl
Comments powered by Disqus.