Up:[[Tutorial]]     Previous:[[サービスプロバイダの使用方法]]     Next:[[エージェントの衝突]]
Up:[[Tutorial]]     Previous:[[RoboCupCamp]]

----
#contents

*エージェント視点の画像取得 (captureView) [#i319ab36]
*ROSノードの利用 [#i319ab36]

※このチュートリアルはv2.1.0以降対応しています。
**はじめに [#v503c171]
ここでは既存のROSノードをSIGVerseで利用する方法を紹介します。ROSのサンプルプログラムTurtleSimで使用されている亀アバターの制御ノードをそのまま用いて、SIGVerse内のロボットを操作することを目的とします。

ここではサービスプロバイダ機能を使用しエージェントに設置されたカメラ画像のデータをコントローラが取得するサンプルを紹介します。
※このチュートリアルはUbuntu12.04LTS上のSIGVerse v2.2.0以降に対応しています。また、少なくとも、http://wiki.ros.orgのチュートリアル:http://wiki.ros.org/ROS/Tutorials/WritingPublisherSubscriber%28c%2B%2B%29 (catkin) まで実施済みの環境を想定しています。

**コントローラ作成 [#v9933e1a]
まずコントローラを作成します。
**SIGVerse内ロボット制御のためのコントローラの作成 [#v9933e1a]
まず、制御ノードからのメッセージを浮け、SIGVerse内のロボットを動かすためのコントローラとなるノードを作成します。ここでは、catkin_make を行うディレクトリは、例えば、ユーザ名を UserNameとし、ROS用ワークスペースを catkin_ws とすると、 /home/UserName/catkin_ws にて行われるものとします(以降<catkin_ws>と表記)。&br;本チュートリアル実施に必要なファイルは &ref(ros_if.tar.gz); からダウンロードできます。これをROSワークスペース <catkin_ws>/src へ展開します。
 $ tar xvfz ros_if.tar.gz -C <catkin_ws>/src
<catkin_ws>/src/ros_if/CMakeLists.txt の102, 103行目にSIGVerseのヘッダファイルパスの指定があります。

 $ emacs captureView.cpp

captureView.cpp

#highlight(cpp){{
#include <Controller.h>
#include <ControllerEvent.h>
#include <Logger.h>
#include <ViewImage.h>
#include <math.h>

#define PI 3.141592
#define DEG2RAD(DEG) ( (PI) * (DEG) / 180.0 )

class RobotController : public Controller
{
public:
  void onInit(InitEvent &evt);
  double onAction(ActionEvent &evt);
  void onRecvMsg(RecvMsgEvent &evt);

private:
  ViewService* m_view;
  double vel;
};

void RobotController::onInit(InitEvent &evt)
{
  // 移動速度
  vel = 10.0;
  // サービスに接続
  m_view = (ViewService*)connectToService("SIGViewer");
}

//定期的に呼び出される関数
double RobotController::onAction(ActionEvent &evt)
{
  return 10.0;
}

//メッセージ受信時に呼び出される関数
void RobotController::onRecvMsg(RecvMsgEvent &evt)
{
  static int iImage = 0;

  //取得したメッセージを表示します
  std::string msg = evt.getMsg();
  LOG_MSG(("msg : %s", msg.c_str()));

  //メッセージ"capture"を受信するとcaptureViewを行います
  if (msg == "capture") {
    if(m_view != NULL) {

      // ビット深度24,画像サイズ320X240の画像を取得します
      ViewImage *img = m_view->captureView(2, COLORBIT_24, IMAGE_320X240);
      if (img != NULL) {

        // 画像データを取得します
        char *buf = img->getBuffer();

        //Windows BMP 形式で保存します
        char fname[256];
        sprintf(fname, "view%03d.bmp", iImage++);
        img->saveAsWindowsBMP(fname);

        //必要なくなったら削除します
        delete img;
      }
    }
  }

  //メッセージ"rotation"を受信すると回転します
  if (msg == "rotation"){

    SimObj *my = getObj(myname());

    // 自分のy軸周りの回転を得ます(クオータニオン)
    double qy = my->qy();

    //くオータニオンから回転角(ラジアン)を導出します
    double theta = 2*asin(qy);

    //体全体を回転させます
    double y = theta + DEG2RAD(45);
    if( y >= PI)
      y = y - 2 * PI;

    my->setAxisAndAngle(0, 1.0, 0, y);
   }

  //メッセージ"move"を受信すると自分の向いている方向に進みます
  if (msg == "move"){

    SimObj *my = getObj(myname());

    //自分の位置を得ます
    Vector3d pos;
    my->getPosition(pos);

    //y軸周りの自分の回転を得ます(クオータニオン)
    double qy = my->qy();

    //クオータニオンから回転角を導出します
    double theta = 2*asin(qy);

    //移動距離
    double dx = 0.0;
    double dz = 0.0;

    //移動する方向を決定します
    dx = sin(theta) * vel;
    dz = cos(theta) * vel;

    //移動します
    my->setPosition( pos.x() + dx, pos.y() , pos.z() + dz );
  }
}

extern "C" Controller * createController ()
{
  return new RobotController;
}
#highlight(cpp:firstline[100]){{
include_directories(include
  ${catkin_INCLUDE_DIRS}
  <SIGVerse_path>/include/sigverse
  <SIGVerse_path>/include/sigverse/comm/controller
)
}}

***captureView [#rd063e94]
これを、例えば次のように環境に応じて変更します。

50行目の関数captureViewで画像データをビューワーから取得します。
#highlight(cpp:firstline[50]){{
 ViewImage *img = m_view->captureView(2, COLORBIT_24, IMAGE_320X240);
#highlight(cpp:firstline[100]){{
include_directories(include
  ${catkin_INCLUDE_DIRS}
  /home/UserName/sigverse/include/sigverse
  /home/UserName/sigverse/include/sigverse/comm/controller
)
}}

1番目の引数は取得したいカメラのID番号を指定します。ここではID番号2を指定しました。2番目と3番目に1ピクセルあたりのビット深度と画像サイズを指定します。(※v2.1.0ではビット深度24, 画像サイズ320×240のみ取得可)
コントローラをビルドします。
 $ cd <catkin_ws>
 $ mkdir devel/lib/libros_if
 $ catkin_make
次のコマンドでファイルが作成されたことを確認してください。
 $ ll devel/lib/libros_if/sig_if_ctrl.so
ファイルが存在しない旨のメッセージが表示される場合は、catkin_make時に何らかのエラーが生じているはずです。それらをチェックし、適宜修正してください。ビルドが確認できたら、SIGVerseの世界ファイルをコピーします。
 $ cp src/ros_if/ros_world.xml devel/lib/libros_if/

取得したViewImageから画像データを取り出すには関数getBuffer()を用います。
** SIGVerseの起動 [#y0140483]
まず、これまでに作業を行っていた端末(terminal)とは別に新しく端末を立ち上げ、そこでROSを立ち上げます。
 $ roscore
起動が確認できたら、別のウィンドウあるいはタブで新しく端末を立ち上げます。その端末で、次のコマンドによりSIGVerseを起動します。
 $ cd <catkin_ws>
 $ source devel/setup.bash
 $ cd devel/lib/libros_if
 $ sigserver.sh -w ./ros_world.xml

このサンプルではエージェントに"capture"というメッセージを送信するとエージェントは画像を取得することができます。
** SIGViewerの起動とシミュレーションの開始 [#ad807c4c]
- 各自のSIGViewerを立ち上げます。これまでのチュートリアル各課題と同様に、ホスト名およびポート名の指定があるので注意してください。
- ホストとの接続が正常にできていることが確認されたら、シミュレーションを開始(start)してください。ロボットが単体で配置されている世界が表示されます。
- ※このチュートリアルではビューアはSIGVerse世界をモニタすることのみに用いており、いかなるサービスも前提にしていません。。

captureViewが成功すると実行ディレクトリにview000.bmp, view001.bmp...というようにbmp形式の画像ファイルが保存されます。
** 制御ノードの起動 [#c2e4683e]
- 次に制御ノードを起動します。これは、TurtleSimとしてROSのインストール時に導入されるパッケージの一部であり、ROSの推奨されるインストール(http://wiki.ros.org/hydro/Installation/Ubuntu の1.4 InstallationのRecommended)を行っていれば追加の操作なく呼び出せます。このノードを呼び出すには、また新たな端末を立ち上げ、次のコマンドを入力します。
 $ rosrun turtlesim turtle_teleop_key
すると、http://wiki.ros.org/ROS/Tutorials/UnderstandingTopics のときと同様に、
 Reading from keyboard
 ---------------------------
 Use arrow keys to move the turtle.
と表示され、同端末はユーザからのキー入力待ち状態になります。この端末はロボットの操作をするとき、キー入力時にアクティブにしてある必要がありますので注意してください。

- ここで、必要なノードが起動されているか確認してください。本操作は、新しく端末を立ち上げ、
 $ rosnode list
によって行えます。これにより、同端末に
 /ros_sig_node
 /rosout
 /teleop_turtle
と表示されることを確認してください
- ros_sig_node はSIGVerse内のオブジェクトを操作するためのインターフェイスの機能を持つノードです。これは、コントローラとしてSIGServerによってロードされ、teleop_turtleが発するメッセージを受信するためのノードとして起動します。sig_if.cppから生成されます。
- rosout は roscoreを起動すると必ず生成されるノードです。
- teleop_turtle は「rosrun turtlesim turtle_teleop_key」によって起動されたノードであり、制御のためのメッセージを発信する機能を持ちます。今回は、本ノードを用いてSIGVerse内のロボットを操作します。

***エージェントの移動 [#zc7075d1]
** 制御ノードの動作確認 [#zf77a354]
- 次に、teleop_turtleノードを起動した端末をアクティブにして、矢印キーを操作します。すると、Upキーで前に、Downキーで後ろに、leftおよびrightキーで左および右周りにロボットが回転する様子が、SIGViewerにおいて確認できます。

このエージェントはメッセージ"move"を受信すると現在向いている方向に進みます。"rotation"を受信するとエージェントは向きを変えます。
** メッセージ送受信の様子の図示 [#oaf8cdd9]
- ros_sig_node は teleop_turtleが発するメッセージをsubscribeしていますが、その様子をグラフで見てみましょう。これは、別の端末を立ち上げ、
 $ rosrun rqt_graph rqt_graph
によって確認でき、次のような図をみることができます。
#br
#br
#ref(rosgraph_turtle.png)
#br
#br
これは、teleop_turtle ノードが turtle1/commad_velocity というトピックにメッセージを投げ、同トピックに投げられたメッセージをros_sig_nodeが聴取していることを示しています。

**コンパイル [#n8f35701]

 $ ./sigmake.sh captureView.cpp


**世界ファイル作成 [#oc477769]

 $ emacs captureView.xml

世界ファイルは前ページ[[視覚に関する操作]]で使用したリビングルームの世界ファイルとほとんど同じものを使います。

captureView.xml
#highlight(xml){{

<?xml version="1.0" encoding="utf8"?>
<world name="myworld5">

  <gravity x="0.0" y="-980.7" z="0.0"/>

  <!--エージェントRobot-niiの設定-->
  <instanciate class="Robot-nii.xml">

    <!--エージェント名-->
    <set-attr-value name="name" value="robot_000"/>

    <!--C++言語の指定-->
    <set-attr-value name="language" value="c++"/>

    <!--コントローラの指定-->
    <set-attr-value name="implementation"
                    value="./captureView.so"/>

    <!--動力学演算をfalseに設定-->
    <set-attr-value name="dynamics" value="false"/>

    <!--エージェントの位置(x,y,z)-->
    <set-attr-value name="x" value="0.0"/>
    <set-attr-value name="y" value="54.0"/>
    <set-attr-value name="z" value="-40.0"/>

    <!--カメラのID番号,リンク名、方向、位置の設定-->
    <camera id="1"
            link="HEAD_LINK"
            direction="0.1 0 1"
            position="-2.0 0.0 7.0"/>
    <camera id="2"
            link="HEAD_LINK"
            direction="-0.1 0 1"
            position="2.0 0.0 7.0"/>
  </instanciate>

  <!--リビングルーム-->
  <instanciate class="seTV.xml">
    <set-attr-value name="name" value="TV_0"/>
    <set-attr-value name="dynamics" value="false"/>
    <set-attr-value name="x" value="-20.0"/>
    <set-attr-value name="y" value="87.5"/>
    <set-attr-value name="z" value="-250.0"/>
    <set-attr-value name="visStateAttrName" value="switch"/>
    <set-attr-value name="switch" value="on"/>
  </instanciate>

  <instanciate class="seDoll_Bear.xml">
    <set-attr-value name="name" value="kuma_0"/>
    <set-attr-value name="dynamics" value="false"/>
    <set-attr-value name="x" value="0.0"/>
    <set-attr-value name="y" value="9.9"/>
    <set-attr-value name="z" value="0.0"/>
  </instanciate>

  <instanciate class="seToy_D.xml">
    <set-attr-value name="name" value="penguin_0"/>
    <set-attr-value name="dynamics" value="false"/>
    <set-attr-value name="x" value="50.0"/>
    <set-attr-value name="y" value="6.15"/>
    <set-attr-value name="z" value="-40.0"/>
  </instanciate>

  <instanciate class="seSofa_2seater.xml">
    <set-attr-value name="name" value="sofa_0"/>
    <set-attr-value name="dynamics" value="false"/>
    <set-attr-value name="x" value="-200.0"/>
    <set-attr-value name="y" value="31.85"/>
    <set-attr-value name="z" value="-100.0"/>
    <set-attr-value name="qw" value="0.707"/>
    <set-attr-value name="qx" value="0.0"/>
    <set-attr-value name="qy" value="0.707"/>
    <set-attr-value name="qz" value="0.0"/>
  </instanciate>

  <instanciate class="seTVbass_B.xml">
    <set-attr-value name="name" value="TVdai_0"/>
    <set-attr-value name="dynamics" value="false"/>
    <set-attr-value name="x" value="-20.0"/>
    <set-attr-value name="y" value="25.1"/>
    <set-attr-value name="z" value="-250.0"/>
  </instanciate>

  <instanciate class="sePlant_B.xml">
    <set-attr-value name="name" value="ki_0"/>
    <set-attr-value name="dynamics" value="false"/>
    <set-attr-value name="x" value="100.0"/>
    <set-attr-value name="y" value="56.5"/>
    <set-attr-value name="z" value="-250.0"/>
  </instanciate>

  <instanciate class="seSidetable_B.xml">
    <set-attr-value name="name" value="sidetable_0"/>
    <set-attr-value name="dynamics" value="false"/>
    <set-attr-value name="x" value="-100.0"/>
    <set-attr-value name="y" value="16.0"/>
    <set-attr-value name="z" value="-100.0"/>
    <set-attr-value name="qw" value="0.707"/>
    <set-attr-value name="qx" value="0.0"/>
    <set-attr-value name="qy" value="0.707"/>
    <set-attr-value name="qz" value="0.0"/>
  </instanciate>
  <instanciate class="seApple.xml">
    <set-attr-value name="name" value="apple_0"/>
    <set-attr-value name="dynamics" value="false"/>
    <set-attr-value name="x" value="-50.0"/>
    <set-attr-value name="y" value="3.875"/>
    <set-attr-value name="z" value="30.0"/>
  </instanciate>

  <instanciate class="seOrange.xml">
    <set-attr-value name="name" value="orange_0"/>
    <set-attr-value name="dynamics" value="false"/>
    <set-attr-value name="x" value="70.0"/>
    <set-attr-value name="y" value="2.215"/>
    <set-attr-value name="z" value="-30.0"/>
  </instanciate>

</world>
}}

エージェントに2つのカメラを設置し、2つのカメラで微妙に位置と方向をずらしました。

カメラID番号1が右目、2が左目に相当します。

**画像取得 [#v44f7911]
それでは実行してみましょう
 $ sigserver.sh -w ./captureView.xml
 
SIGViewerで接続してみるとリビングルームにロボットが立っているのが見えます。

シミュレーションを開始して"rotation"や"move"などのメッセージを送信するとエージェントが向きを変えたり、移動したりして自由に部屋の中を移動できるようになります。

キャプチャしたいタイミングで"capture"を送信するとエージェントに設置されたカメラ(ID番号2)で撮影した画像が得られます。

#ref(エージェント視点の画像取得(v2.0系)/capture_1.PNG,40%)

サーバ側の実行ディレクトリにカメラ2でcaptureした画像ファイルが保存されていれば成功です。



#highlight(end)

*Old Version [#xcbd1594]
-[[エージェント視点の画像取得(v2.0系)]]
-[[エージェント視点の画像取得(v120330, v1.4.8)]]

----
Up:[[Tutorial]]     Previous:[[サービスプロバイダの使用方法]]     Next:[[エージェントの衝突]]
Up:[[Tutorial]]     Previous:[[RoboCupCamp]]

#counter


Front page   New List of pages Search Recent changes   Help   RSS of recent changes