2009年12月31日木曜日

tfと時間について学ぶ(C++)

今回もC++でtfの勉強です。
つらいのでサクッと終わらせたいです。

http://www.ros.org/wiki/tf/Tutorials/tf%20and%20Time%20(C%2B%2B)


前回のつづきで
$ roscd learning_tf
します。
で、
src/turtle_tf_listener.cppのtryの中を以下のように編集します。


try{
    listener.lookupTransform("/turtle2", "/turtle1",  
                             ros::Time::now(), transform);
変わったのは、carrot1をturtle1に戻したこと、ros::Time(0)をros::Time::now()にしたこと。
ではmakeして実行してみます。
$ make
$ roslaunch learning_tf start_demo.launch

 [ERROR] 1262237846.011536000: You requested a transform that is 0.085 miliseconds in the past, 
but the most recent transform in the tf buffer is 35.950 miliseconds old.
 When trying to transform between /turtle1 and /turtle2.

というエラーがでまくります。そんな最近のデータはないよ。というメッセージですね。
now()のデータはまだできていないので、少し待つ必要があります。

実はtfにはこの待つためのインタフェースが用意されており、
以下のようにして使います。src/turtle_tf_listener.cppを編集してください。
try{
    ros::Time now = ros::Time::now();
    listener.waitForTransform("/turtle2", "/turtle1",
                              now, ros::Duration(3.0));
    listener.lookupTransform("/turtle2", "/turtle1",
                             now, transform);
ros::Duration(3.0)はタイムアウトを指定しています。
このwaitにより指定した時刻(now)のデータ(/turtle2から見た/turtle1の座標系)からがくるまで実行がブロックされます。

ではmake&launchしましょう。

  $ make
  $ roslaunch learning_tf start_demo.launch
[ERROR] 1262238458.663194000: You requested a transform that is 3.003 seconds in the past, 
but the tf buffer only has a history of 2.720 seconds.
 When trying to transform between /turtle1 and /turtle2.
まだ一回だけエラーがでます。
これは亀が生まれる前にtfをリクエストされたためです。
これは仕方ないみたいです。

では次回へ続きます。

フレームを追加する(C++)

今日は大晦日のROS日和ですからじゃんじゃんいきましょう。

フレームを追加する(C++)
http://www.ros.org/wiki/tf/Tutorials/Adding%20a%20frame%20(C%2B%2B)

ローカル座標系の連結がテーマです。
この重要性はロボティクスを勉強した人なら分かりますね。
tfがやりたいのはまさに今回やるテーマだと思います。

例として、レーザーレンジファインダの取り付け位置の話がでています。

レーザー位置はロボットが動くと自動的についてきます。

レーザーレンジファインダが返す、レーザーレンジファインダからの相対位置での障害物位置を、ロボット座標系に変換する、なんてことはやりそうですよね。
tfを使えばそういう変換が楽勝にできます。

tfではツリー構造を作ります。親1つ、子多数の関係です。
循環構造は作れません。
今回作るツリー構造は以下の図です。
turtle1の子としてcarrot1を作ります。
今まで作った/turtle1, /turtle2(引数で指定した)は/worldの子でしたね。


ではいつものように始めます。


 $ roscd learning_tf

していつものところへ。そして以下をsrc/frame_tf_broadcaster.cppとして保存しましょう。

#include <ros/ros.h>
#include <tf/transform_broadcaster.h>

int main(int argc, char** argv){
  ros::init(argc, argv, "my_tf_broadcaster");
  ros::NodeHandle node;

  tf::TransformBroadcaster br;
  tf::Transform transform;

  ros::Rate rate(10.0);
  while (node.ok()){
    transform.setOrigin( tf::Vector3(0.0, 2.0, 0.0) );
    transform.setRotation( tf::Quaternion(0, 0, 0) );
    br.sendTransform(transform, ros::Time::now(), "carrot1", "turtle1");
    rate.sleep();
  }
  return 0;
};


以前作ったbroadcasterと似たコードが並んでいますね。
違うのはコールバックでないこと、と以下が違います。

br.sendTransform(transform, ros::Time::now(), "carrot1", "turtle1");



最後のsendTransformで、turtle1を親にして、carrot1という名前で新しいフレームを作っています。しかし、この引数をとるsendTransformはdepricatedなので、以下のように書き換えたほうがいいですね。


br.sendTransform(tf::StanpedTransform(transform, ros::Time::now(), "turtle1", "carrot1");




その上のところはオフセットです。turtle1からY方向に2.0だけオフセットした位置にcarrot1を作っています。ところで、亀なのにcarrotを追いかけるってどうなの・・・。









transform.setOrigin( tf::Vector3(0.0, 2.0, 0.0) );
    transform.setRotation( tf::Quaternion(0, 0, 0) );


ではいつものようにビルドしてください。
CMakeLists.txtに以下を追加。

rosbuild_add_executable(frame_tf_broadcaster src/frame_tf_broadcaster.cpp)


そして、
$ make
です。

そしてlaunchファイルも追加しましょう。

<launch>
    ...
    <node pkg="learning_tf" type="frame_tf_broadcaster"
          name="broadcaster_frame" />
  </launch>


です。普通に起動するだけですね。
で、普通に起動します。
 $ roslaunch learning_tf start_demo.launch
あれ?前回と何も変わりません。
じゃあ、前回作ったsrc/turtle_tf_listener.cppを編集して、carrotを追いかけるようにしましょう。/turtle1を/carrot1に置き換えるだけです。
listener.lookupTransform("/turtle2", "/carrot1",
                           ros::Time(0), transform);

で、make
して、launchしましょう。



しっかり亀の左によりそう、いい亀になりましたね。

最後におまけで、動くフレームを作っててみましょう。
src/frame_tf_broadcaster.cppを以下のように変更します。
transform.setOrigin( tf::Vector3(2.0*sin(ros::Time::now().toSec()), 2.0*cos(ros::Time::now().toSec()), 0.0) );
    transform.setRotation( tf::Quaternion(0, 0, 0) );


亀のまわりをぐるぐる回る亀ができましたとさ。

今回はこれでおしまいです。



tf listenerを作る(C++)

今回はbroadcastされたtfを読み取るtf listenerをC++で作ります。

前回作ったlearning_tfパッケージで作業しましょう。
$ roscd learning_tf

例のごとく以下をsrc/turtle_tf_listener.cppとして保存しましょう。

#include <ros/ros.h>
#include <tf/transform_listener.h>
#include <turtlesim/Velocity.h>
#include <turtlesim/Spawn.h>
int main(int argc, char** argv){ ros::init(argc, argv, "my_tf_listener"); ros::NodeHandle node; ros::service::waitForService("spawn"); ros::ServiceClient add_turtle = node.serviceClient<turtlesim::Spawn>("spawn"); turtlesim::Spawn srv; add_turtle.call(srv); ros::Publisher turtle_vel = node.advertise<turtlesim::Velocity>("turtle2/command_velocity", 10); tf::TransformListener listener; ros::Rate rate(10.0); while (node.ok()){ tf::StampedTransform transform; try{ listener.lookupTransform("/turtle2", "/turtle1", ros::Time(0), transform); } catch (tf::TransformException ex){ ROS_ERROR("%s",ex.what()); } turtlesim::Velocity vel_msg; vel_msg.angular = 4 * atan2(transform.getOrigin().y(), transform.getOrigin().x()); vel_msg.linear = 0.5 * sqrt(pow(transform.getOrigin().x(), 2) + pow(transform.getOrigin().y(), 2)); turtle_vel.publish(vel_msg); rate.sleep(); } return 0; };




では一行ずつ見ていきます。


 #include <tf/transform_listener.h>

TransformListenerクラスを使って処理を楽するためにインクルードします。

tf::TransformListener listener;

そしてこれでインスタンスを生成しました。
このインスタンスは生成されると自動的にtfのデータを受信しはじめ、10秒間tfのデータをバッファします。


try{
      listener.lookupTransform("/turtle2", "/turtle1",  
                               ros::Time(0), transform);
    }

これは
/turtle2から見た/turtle1の座標系を、最新の時間(ros::Time(0))のもので、transformにセットしろという意味です。ros::Time(0)は最新の時間を表すようです。
見つからない時に例外を発生させるのでtryでかこんでます。

チュートリアルでは解説されていませんが、他に、spawnサービスを使って、亀を1匹生成しています。

ros::service::waitForService("spawn");
  ros::ServiceClient add_turtle = 
       node.serviceClient<turtlesim::Spawn>("spawn");
  turtlesim::Spawn srv;
  add_turtle.call(srv);

また、以下のようにしてturtle2の速度を書き込む宣言をし、
ros::Publisher turtle_vel = 
       node.advertise<turtlesim::Velocity>("turtle2/command_velocity", 10);
以下の追従制御を行っています。この入力としてturtle1,turtle2のposeを直接取得するのではなく、tfからの座標変換結果を用いています。broadcasterがいるのでこのように変換が簡単にできます。

turtlesim::Velocity vel_msg;
    vel_msg.angular = 4 * atan2(transform.getOrigin().y(),
                                transform.getOrigin().x());
    vel_msg.linear = 0.5 * sqrt(pow(transform.getOrigin().x(), 2) +
                                 pow(transform.getOrigin().y(), 2));
    turtle_vel.publish(vel_msg);


では、いつものようにビルドします。CMakeLists.txtに以下を追加し、
rosbuild_add_executable(turtle_tf_listener src/turtle_tf_listener.cpp)

$make
です。

また、前回作ったstart_demo.launchに以下を追加します。

<launch>
    ...
    <node pkg="learning_tf" type="turtle_tf_listener" 
          name="listener" />
  </launch>

そして以下のようにして起動しましょう。

 $ roslaunch learning_tf start_demo.launch


ストーカー亀ができたでしょうか?

以下のようなエラーがでてると思いますが、気にしなくていいです。
亀2(turtle2)のbroadcasterが動く前にlistenerが動いているため、turtle2が見つからないぞ、というエラーがでます。try-catchでちゃんとエラーが捕まえられていますね。せっかくなので、速度指令値をいれるところもtryの中にいれたほうがいい気がしますね。
[ERROR] 1253915565.300572000: Frame id /turtle2 does not exist! When trying to transform between /turtle1 and /turtle2.
[ERROR] 1253915565.401172000: Frame id /turtle2 does not exist! When trying to transform between /turtle1 and /turtle2.

今回は以上です。

tf broadcasterを作る(C++)

tfを使っての座標系の操作と、その発行を行います。

Writing a tf broadcaster (C++)
http://www.ros.org/wiki/tf/Tutorials/Writing%20a%20tf%20broadcaster%20(C%2B%2B)

まず、以下のようにして、新しいlearning_tfというパッケージを作ります。 
$ roscd sandbox
$ roscreate-pkg learning_tf tf roscpp rospy turtle_teleop
$ rosmake learning_tf
roscpp, rospy, turtle_teleopに依存するパッケージという意味です。 
(私の環境ではroscreate-pkgのos.makedirsで失敗しました。
ros/tools/roscreate/src/roscreate/roscreatepkg.pyの、 L77のos.makedirs(py_path)をコメントアウトして対応しました。)
そして
$ roscd learning_tf
します。
それではいつものように 以下をsrc/turtle_tf_broadcaster.cppとして保存してください。





#include <ros/ros.h>
#include <tf/transform_broadcaster.h>
#include <turtlesim/Pose.h>
static std::string turtle_name; void poseCallback(const turtlesim::PoseConstPtr& msg){ static tf::TransformBroadcaster br; tf::Transform transform; transform.setOrigin(tf::Vector3(msg->x, msg->y, 0.0)); transform.setRotation(tf::Quaternion(msg->theta, 0, 0)); br.sendTransform(tf::StampedTransform(transform, ros::Time::now(), "world", turtle_name)); } int main(int argc, char** argv){ ros::init(argc, argv, "my_tf_broadcaster"); if (argc != 2){ ROS_ERROR("need turtle name as argument"); return -1; } turtle_name = argv[1]; ros::NodeHandle node; ros::Subscriber sub = node.subscribe(turtle_name + "/pose", 10, &poseCallback); ros::spin(); return 0; }
では一行ずつ見ていきます。



#include <tf/transform_broadcaster.h>
transform_broadcasterを使って簡単に座標系の情報を発行(publish)することができるので、
そのためにインクルードしています。



static tf::TransformBroadcaster br;
そしてこれがそのインスタンスです。



tf::Transform transform;
  transform.setOrigin(tf::Vector3(msg->x, msg->y, 0.0));
  transform.setRotation(tf::Quaternion(msg->theta, 0, 0));
tf::Transformクラスは位置と姿勢を持つ座標系を表すクラスです。
実はこれはbulletという衝突検知ライブラリのbtTransformです(ここ参照)。
同様にVector3, QuaternionもそれぞれbtVector3、btQuaternionをtypedefしたものです。
亀の2次元座標を3次元のtf::transformに入れています。
チュートリアルではクオータニオンの説明も一切なく、分からない人はもうここで門前払いを喰らいますね。
ROS恐るべし。
やはりロボット系大学院修士卒程度の知識は前提にされていると思います。
なので私も説明しません。
bulletもほとんど使ったことないですが、大体幾何学ライブラリは似たようなものなので、
何か使ったことがあれば大丈夫でしょう。



br.sendTransform(tf::StampedTransform(transform, ros::Time::now(), "world", turtle_name));
brはTransformBroadcasterですので、これを使って、タイムスタンプ付きの座標系をブロードキャストしてもらいます。
tf::StampedTransformに、座標系(transform)と、タイムスタンプとして使う時刻(ros::Time::now())、transformの親とする座標系の名前(world)、今作っている座標系の名前(turtle_name)を引数として与えています。
チュートリアルに、



Note: sendTransform and StampedTransform have opposite ordering of parent and child.
とありますが、これは、

viod tf::TransformBroadcaster::sendTransform
  (const Transform& transform,
   const ros::Time& time,
   const std::string& frame_id,
   const std::string& parent_id)
という引数を取るメンバ関数がtf::TransformBroadcasterにあるからです。
最後の引数が親座標系になっていて、その前が自分のIDです。
しかし、この関数はROSCPP_DEPRECATEDがついているので、使わない方がいいでしょう。

その他のコードはお決まりで、ノードを初期化して、亀の/poseというトピックに対し、poseCallbackというコールバック関数をバインドしています。

以上でソースの説明はおしまいです。

ではお決まりの手順でmakeしましょう。
CMakeLists.txtの最後に以下を追加。



rosbuild_add_executable(turtle_tf_broadcaster src/turtle_tf_broadcaster.cpp)
そして
$ make
です。
また、以下のようなstart_demo.launchというファイルを作成しておきましょう。
  <launch>
    <include file="$(find turtle_teleop)/launch/turtle_keyboard.launch" />

    <node pkg="learning_tf" type="turtle_tf_broadcaster"
          args="/turtle1" name="turtle1_tf_broadcaster" />
    <node pkg="learning_tf" type="turtle_tf_broadcaster"
          args="/turtle2" name="turtle2_tf_broadcaster" />

  </launch>
復習になりますが、これで以下と同じことが実行されますね。
$ roslaunch turtle_teleop turtle_keyboard.launch
$ rosrun learning_tf turtle_tf_broadcaster /turtle1 __name:=turtle1_tf_broadcaster



$ rosrun learning_tf turtle_tf_broadcaster /turtle2 __name:=turtle2_tf_broadcaster
ではそれを意識しつつ、以下を実行しましょう。
$ roslaunch learning_tf start_demo.launch
亀がでましたか?
$ rosrun tf tf_echo /world /turtle1
すると、亀の座標(/worldに対する/turtle1の座標)が表示されたと思います。
亀をキーボードで操作して値が変わることを確認してください。

2009年12月28日月曜日

ROS日本語チュートリアルのwiki

 よく見たらありましたね。

http://www.ros.org/wiki/ja/ROS
http://www.ros.org/wiki/ja/ROS/Tutorials

このかたみたいですね。

僕もコミットしたほうがいいのかなぁ。
まあ、まだいいか。

ソフトでコミットしよう。
そうしよう。

tfを知る

今回はかなりマニアックなtfというライブラリの勉強です。

要するに、座標系と座標系の関係を記述するものと考えてください。
座標系が分からない人は、位置(x, y, z)と姿勢(r, p, y)を合わせたようなものと思ってください。分かる人はそうじゃないだろ、とツッコミを内心いれておいてください。

時系列を考慮して、3秒前の手先からの相対座標での頭の位置姿勢なんかが分かっちゃいます。
http://www.ros.org/wiki/tf

ではイントロからやりましょう。
http://www.ros.org/wiki/tf/Tutorials/Introduction%20to%20tf


$ rosdep install turtle_tf rviz
$ rosmake turtle_tf rviz
して必要なパッケージを作成します。

また、亀ですね。とりあえず下のコマンドを打って動かしてみましょう。
$ roslaunch turtle_tf turtle_tf_demo.launch


亀は矢印キーで動かせます。
なんかもう一匹の亀がストーカーのごとくついてきますね。
この追跡をtfを使ってやっているみたいです。

では何が起きているかを見てみましょう。
$ rosrun tf view_frames
するとframes.pdfがカレントディレクトリにできるので、
$ display frames.pdf
などして中身を見てみましょう。


これを見るとworldという座標系がturtle1とturtle2の親だということが分かります。

さらに、以下のようにするとturtle1から見たturtle2の座標系が表示されます。
$ rosrun tf tf_echo turtle1 turtle2
At time 1261811349.741
- Translation: [-0.000, -0.000, 0.000]
- Rotation: in Quaternion [0.000, 0.000, 0.278, 0.961]
            in RPY [0.000, -0.000, 0.563]
At time 1261811350.736
- Translation: [-0.000, -0.000, 0.000]
- Rotation: in Quaternion [0.000, 0.000, 0.278, 0.961]
            in RPY [0.000, -0.000, 0.563]
今は亀がおいついているので位置のずれは0ですね。
亀を動かすと値が変わるのが分かりますね。
亀の前方がxっぽいですね。
$ rxgraph
で見てみると、


/tfというトピックをいろんな人がいろんなとこへ書き込んでますね。

今回はこれくらいにしておきます。

次回はtf broadcasterを書きます。
勉強のため苦手なC++のほうでやります。

2009年12月26日土曜日

今後の進め方 その2

もう少し調べていくと、ROSでの標準的なロボットの動かしかたを理解するには

  • tf ・・・・・・座標系の関係を規定するライブラリ
  • actionlib・・・ロボットを動かすときの標準インタフェース
などを勉強する必要がありそうです。
思ったより道は険しいですね。

ロボット動かしたいですが、結局デモを動かして終了だと何にも作れるようにならないので、
非常につまらないですが、ひとつひとつこなしていこうと思います。

デモ動かしたければ、チュートリアルに書いてあるようにコマンド叩けばロボットは動きますんで、それでストレス解消してください。