うごくものづくりのために

技術的な備忘録がメインです。

"RefereeBox"のインストールをしたかった

Small Size Robot League - referee

ソースファイルは、Source (.tar.gz) version 2014 finalからダウンロード。

解凍後のフォルダ内にREADME-LINUX.txtというファイルがあるので、それを参考にしながらインストールを進めます。

sudo apt-get install libprotobuf-dev protobuf-compiler libgtkmm-2.4-dev make g++
make

これにてインストール(というかコンパイル?)完了。


。。。

さて、無事にmakeは通ったのですが、バイナリが実行できない…

f:id:tilt_silvie:20141012010101p:plain

なんだかよくわからないので、ひとつ昔のバージョンのプログラムをダウンロードして、同様にインストールしてみようと思います。

Source (.tar.gz) version 2013 final


今度はmakeでコケた…

f:id:tilt_silvie:20141012012459p:plain

つらみが高まってきたので、今日はこのへんにしておきます。 原因が特定できて、問題が解決したらまた記事を書きます。

tfは遅い?

現在、grSimとROSの結合作業をしています。

とりあえず、grSimからマルチキャストで送信される各ロボットの座標を、tfでブロードキャストしようと思ったのですが、問題発生。

おおよそ60Hzで座標データが飛んでくるので、その周期に合わせてブロードキャストしようと思い、コードを書きました。 しかし、2つ以上のデータを60Hzでブロードキャストしようとすると、一番最後にブロードキャストした以外のフレームの更新頻度が極端に落ちます。 一番最後にブロードキャストしたフレームは60Hz近くで更新されるのですが、それ以外が10Hzとか3Hzとか。

いろいろと原因を探ってみたのですが、いまいちよく分からず。 帯域が逼迫しているわけでもなさそうですし、tf.sendTransform()自体もそんな時間は食っていないようです。

一つのノードからは、高い周波数で複数のフレームをブロードキャストできないのかもしれません…。困ったな。 何か打開策を考えます。

#!/usr/bin/env  python
import  rospy
import  tf
import  socket
import  messages_robocup_ssl_wrapper_pb2

##################

multicast_if_addr='192.168.0.10'
multicast_group='224.5.23.2'
multicast_port=10020

my_addr='0.0.0.0'
server_address=(my_addr, multicast_port)

sock=socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind(server_address)

mreq=socket.inet_aton(multicast_group)+socket.inet_aton(multicast_if_addr)
sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq)

#make protobuf instance
ssl_wrapper = messages_robocup_ssl_wrapper_pb2.SSL_WrapperPacket()



def loadGeometry():
    br = tf.TransformBroadcaster()
    
    while not rospy.is_shutdown():   
        data    = sock.recv(1024)
        ssl_wrapper.ParseFromString(data)   #Parse sent data
        
        for i in range(0, 3):
            x   = ssl_wrapper.detection.robots_blue[i].x / 1000.0
            y   = ssl_wrapper.detection.robots_blue[i].y / 1000.0
            th  = ssl_wrapper.detection.robots_blue[i].orientation
                 
            br.sendTransform((x, y, 0),
                   tf.transformations.quaternion_from_euler(0, 0, th),
                   rospy.Time.now(),
                   "blue" + str(i),
                   "world")
        
        
        
if  __name__ == '__main__':
    rospy.init_node('grsim_geometry_loader')
    
    try:
        loadGeometry()
    except  rospy.ROSInterruptException:
        rospy.loginfo('Exception occured')
        pass

f:id:tilt_silvie:20140928164259p:plain

PythonとGoogleProtocolBuffersを用いてgrSimのデータを取得する

GoogleProtocolBuffers for Pythonのインストール

grSimをインストールした時 にダウンロードした GoogleProtocolBuffers のフォルダ内に、Python用セットアップファイルが同梱されているので、それを用いてインストールします。

まずは、このフォルダに移動。 今回は、Downloadsフォルダ内に解凍しているので、以下のパスとなっています。 適宜変えてください。

cd ~/Downloads/protobuf-2.5.0/python/

このフォルダ内にあるreadme.txtを参考にしながら、インストールを進めていきます。

python setup.py build
python setup.py test #このテストを全部通らないとまずい

python setup.py install #自分はsudoで実行する必要がありました

grSimのprotoファイルをPythonコンパイル

まずは、適当に作業フォルダを作ります。

mkdir -p ~/Documents/SSL_develop/temp_proto/
cd ~/Documents/SSL_develop/temp_proto/

grSimが通信に用いるprotoファイルは、grSim/src/proto/ 内にあります。

このファイルたちを作業フォルダへとコピーします。

cp -r ~/Documents/SSL_develop/grSim/src/proto ./proto
ls ./proto

そのうち、 messages_robocup_ssl_*** というファイルが、grSimが吐き出すシミュレータデータのprotoファイルですので、それらをprotocコンパイラPythonへと変換します。

protoc -I=./proto --python_out=./ ./proto/messages_robocup_ssl_detection.proto
protoc -I=./proto --python_out=./ ./proto/messages_robocup_ssl_geometry.proto
protoc -I=./proto --python_out=./ ./proto/messages_robocup_ssl_refbox_log.proto
protoc -I=./proto --python_out=./ ./proto/messages_robocup_ssl_wrapper.proto
ls

これで、messages_robocup_ssl***pb2.pyというファイルが4つできていれば成功です。

grSimのデータを取得

以下のページに、GoogleProtocolBuffersの使い方がまとまっています。 このページの下の方にある、"Reading A Message"というサンプルコードを参考に、 昨日書いたマルチキャストのソースコードを改造していきます。

Protocol Buffer Basics: Python - Protocol Buffers — Google Developers

#!/usr/bin/env python

import  socket
import  messages_robocup_ssl_wrapper_pb2

multicast_if_addr='192.168.0.10'  

multicast_group='224.5.23.2'
multicast_port=10020

my_addr='0.0.0.0'
server_address=(my_addr, multicast_port)

sock=socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind(server_address)

mreq=socket.inet_aton(multicast_group)+socket.inet_aton(multicast_if_addr)
sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq)

#make protobuf instance
ssl_wrapper = messages_robocup_ssl_wrapper_pb2.SSL_WrapperPacket()

while True:

    data    = sock.recv(1024)
    ssl_wrapper.ParseFromString(data)
    
    print   '-------------------------------------------'
    print   ssl_wrapper

あとは、grSimを起動したあとに、このコードを実行!

f:id:tilt_silvie:20140927151417p:plain

座標データやらなんやら取得できたーっ!

Pythonでマルチキャストのデータを読む

RoboCupSSLでは、チーム間共有のビジョンサーバがフィールド上のカメラ情報を統合し、各ロボットの座標などのデータを取得してくれます。 そのデータは、UDPマルチキャストによって送信されてきます。 その情報を使って、各チームのAIはいろいろと戦略を練って、ロボットへ指示を与えるわけです。

自分が、マルチキャストってなんぞい?っていう状態から、なんとかPythonを用いてサーバ(正確にはシミュレータ)からのデータを受信できるようになったのでメモしておきます。

マルチキャストって何

ネットワークのおべんきょしませんか? テクニカルエンジニア(ネットワーク)、Cisco CCNA/CCNP対策に!

上のページを参考にしました。

ネットワーク上の複数の端末に、同じ情報を同時に送信するための仕組みです。 同様に、全体に同時送信するための仕組みで、ブロードキャストというのもあるのですが、 マルチキャストはブロードキャストより通信のオーバーヘッドが少ないです。

で、マルチキャストでデータを送信するためには、データを送信する対象となるグループを作る必要があります。 逆に、マルチキャストで送信されるデータを受信するためには、そのグループに参加する必要があります。

あんまり詳しくはわかっていないので、解説はこのへんで適当に終わらせておきます。 詳細な情報はぐぐってみてください…

Pythonマルチキャストのデータを受信する

このページを参考にしてソースを書きました。(というかほぼコピペ)

pythonでマルチキャストを受信する - karasuyamatenguの日記

今回は、前回インストールしたgrSim を使って、grSimが吐いてくれるマルチキャストのデータをPythonで拾おうと思います。

#!/usr/bin/env python
import socket

#LANアダプタに割り当てられているIPアドレス
multicast_if_addr='192.168.0.10'

#マルチキャストアドレスとポート(grSim参照)
multicast_group='224.5.23.2'
multicast_port=10020

my_addr='0.0.0.0' #これは変えない
server_address=(my_addr, multicast_port)

sock=socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind(server_address)

mreq=socket.inet_aton(multicast_group)+socket.inet_aton(multicast_if_addr)
sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq)

while True:

    data, address=sock.recvfrom(1024) #本当は受信したデータを表示させたいけど、バイナリなので表示できない
    print 'Received'

一応"Received"が端末に表示されまくったので、受信できているでしょう。 バイナリデータからちゃんとしたデータに直すために、次はPythonでGoogleProtocolBuffersを使ってみようと思います。

"SSL-Vision"のインストール

RoboCup SSLの画像処理サーバである、SSL-Visionをインストールします。

以下のページを参考にインストールを進めていきます

RequirementsInstallation - ssl-vision - Software and hardware requirements for using SSL-Vision. Installation guide. - RoboCup Small Size League Shared Vision System - Google Project Hosting

まずは必要なものをインストール。

sudo apt-get install subversion g++ libqt4-dev libeigen2-dev protobuf-compiler libprotobuf-dev libdc1394-22 libdc1394-22-dev cmake libjpeg-dev libpng12-dev

インストールが終わったら、本体をダウンロードします。

cd ~/Document/SSL_develop/
svn checkout http://ssl-vision.googlecode.com/svn/trunk/ ssl-vision-read-only

ダウンロードできたら、makeしましょう。

cd ssl-vision-read-only
make

これにてインストール完了。


起動は、bin/vision で。

f:id:tilt_silvie:20140927002222p:plain

インストールはできたんですが、このPCにはカメラが繋げないので、あんまり意味はないです…

RoboCup SSL Simulator "grSim"のインストール

これからRoboCupSSLロボットの開発を始めます。

自分はAI担当になったので、がんばってAI開発します。

まずは、開発環境づくりからというわけで、 オープンソースのシミュレータ"grSim"のインストールを行います。

なお、環境は Ubuntu 14.04LTS です。

mani-monaj/grSim · GitHub


github上の INSTALL.md を参考にして、インストールを行います。

sudo apt-get install build-essential cmake libqt4-dev libgl1-mesa-dev libglu1-mesa-dev libprotobuf-dev libode-dev

#[In a temporary directory]
cd /tmp
wget http://vartypes.googlecode.com/files/vartypes-0.7.tar.gz
tar xfz vartypes-0.7.tar.gz
cd vartypes-0.7
mkdir build && cd build
cmake ..
make 
sudo make install

#Download grSim repository
cd ~/Document
mkdir SSL_develop
cd SSL_develop
git clone https://github.com/mani-monaj/grSim.git

#[In grSim folder]
cd grSim
mkdir build
cd build
cmake ..
make

たぶん、最後のgrSimのmakeでコケます。 google protocol buffer(以下protobuf)が無い!って怒られます。

dpkg -L libprotobuf-devをしてみたところ、ROSのインストール時にprotobufがすでにインストールされているみたい。ですが、どうやらおかしなパスに入っている模様。

面倒なので、別のところに新しくprotobufを入れます。 ROSのインストール時に入ったものはROSから使われているみたいなので、それは置いておいて新しく入れます。 競合しそうで怖いですが…

protobufの導入は、以下のサイトを参考に。

Protocol Buffersの使い方まとめ - hrendohの日記

まずは、以下のページからprotobufをダウンロード。今回はVer 2.5.0を選択。 Downloads - protobuf - Protocol Buffers - Google's data interchange format - Google Project Hosting

ダウンロードして解凍したら、以下のコマンドを実行します。

cd protobuf-2.5.0
./configure
make
make check
sudo make install

全部のコマンドが問題なく通ったら、うまくインストールできているか確認します。

/usr/local/bin/protoc --version

libprotoc 2.5.0と出ればインストール成功。


さて、grSimのmakeに再チャレンジ。

cd ~/Document/SSL_develop/grSim/build
make

f:id:tilt_silvie:20140924031052p:plain

うまくいきましたー!

最後に、grSim/bin/grSimを実行して、ほんとに動くかどうかテスト。

f:id:tilt_silvie:20140924031201p:plain

動いたっ!

座標変換パッケージ "tf"の使い方

ROSの座標変換ライブラリ、tfについて勉強しています。ひとまずtutorial(Python)をひと通り終えました。

ja/tf/Tutorials - ROS Wiki

今までのところでわかったことを記述しておきます。


tfでは、様々な座標軸を定義することで、定義された座標軸から見た別の座標軸の位置を簡単に求めることができます。 どのくらい簡単かというと、ベースとなる座標系と、その座標系上で表したい別の座標軸、この2つの座標系の名前を指定するだけです。三角関数とか同次変換行列とか使う必要ありません。シンジラレナーイ

利用の流れとしては、 座標系の登録(Broadcaster) -> 座標系の変換(Listener) という順になります。

座標系の登録(Broadcaster)

座標変換を行うために、まずは座標系の登録を行います。 参考のソースコードチュートリアルページから引っ張ってきました。

ja/tf/Tutorials/Writing a tf broadcaster (Python) - ROS Wiki

  #!/usr/bin/env python  
  import roslib
  roslib.load_manifest('learning_tf')
  import rospy
  
  import tf
  import turtlesim.msg
  
  def handle_turtle_pose(msg, turtlename):
      br = tf.TransformBroadcaster()
      br.sendTransform((msg.x, msg.y, 0),
                       tf.transformations.quaternion_from_euler(0, 0, msg.theta),
                       rospy.Time.now(),
                       turtlename,
                       "world")
  
  if __name__ == '__main__':
      rospy.init_node('turtle_tf_broadcaster')
      turtlename = rospy.get_param('~turtle')
      rospy.Subscriber('/%s/pose' % turtlename,
                       turtlesim.msg.Pose,
                       handle_turtle_pose,
                       turtlename)
      rospy.spin()

この例は、以下のような流れで座標登録を行っています。

  1. turtle*/poseトピックをSubscribeし、"world"座標系から見た"turtle*"の座標(Pose型)を取得
  2. 座標系をtfへ登録
  3. turtle*/poseトピックが更新される度、座標系を更新登録

実際に2の座標系登録を行っているのは、以下の部分になります。

      br.sendTransform((msg.x, msg.y, 0),
                       tf.transformations.quaternion_from_euler(0, 0, msg.theta),
                       rospy.Time.now(),
                       turtlename,
                       "world")

TransformBroadcaster.sendTransform()を用いて座標の登録を行います。

tf (Python) — tf 0.1.0 documentation #TransformBroadcaster.sendTransform

sendTransform()メソッドは、5つの引数を取ります。

  • 第1引数は、ベースとなる座標系から見た、登録対象の直交座標です。x,y,zをまとめたタプルとして与えます。
  • 第2引数は、ベースとなる座標系から見た、登録対象の回転です。これは、クオータニオンで与えます。クオータニオンについてはよく理解していませんが、quaterninon_from_euler()で、オイラー角からクオータニオンへの変換が行えるようです。こちらも、タプルとして与えます。
  • 第3引数は、タイムスタンプです。基本的には現在時刻を与えます。(tfは、内部で座標の時間推移を保存しているようです。)
  • 第4引数は、登録したい座標系の、任意の名前を指定します。
  • 第5引数は、ベースとなる座標系の名前を指定します。

座標系の変換(Listener)

登録した座標系を用いて、2つの座標系間での変換を行います。 同じように、参考のソースコードチュートリアルページから引っ張ってきました。

ja/tf/Tutorials/Writing a tf listener (Python) - ROS Wiki

#!/usr/bin/env python  
import roslib
roslib.load_manifest('learning_tf')
import rospy
import math
import tf
import geometry_msgs.msg
import turtlesim.srv
 
if __name__ == '__main__':
    rospy.init_node('tf_turtle')

    listener = tf.TransformListener()

    rospy.wait_for_service('spawn')
    spawner = rospy.ServiceProxy('spawn', turtlesim.srv.Spawn)
    spawner(4, 2, 0, 'turtle2')
 
    turtle_vel = rospy.Publisher('turtle2/cmd_vel', geometry_msgs.msg.Twist)

    rate = rospy.Rate(10.0)
    while not rospy.is_shutdown():
        try:
            (trans,rot) = listener.lookupTransform('/turtle2', '/turtle1', rospy.Time(0))
        except (tf.LookupException, tf.ConnectivityException, tf.ExtrapolationException):
            continue
 
        angular = 4 * math.atan2(trans[1], trans[0])
        linear = 0.5 * math.sqrt(trans[0] ** 2 + trans[1] ** 2)
        cmd = geometry_msgs.msg.Twist()
        cmd.linear.x = linear
        cmd.angular.z = angular
        turtle_vel.publish(cmd)
  
        rate.sleep()

座標変換を行っているのは、以下の部分です。

        try:
            (trans,rot) = listener.lookupTransform('/turtle2', '/turtle1', rospy.Time(0))
        except (tf.LookupException, tf.ConnectivityException, tf.ExtrapolationException):
            continue

例外構文がありますが、これは座標変換が失敗したときにその後の処理をしないようにするためのものです。 座標変換自体は、

            (trans,rot) = listener.lookupTransform('/turtle2', '/turtle1', rospy.Time(0))

この一行です。lookupTransform()メソッドを用います。

tf (Python) — tf 0.1.0 documentation #Transformer.lookuptransform

lookupTransform()メソッドは、3つの引数を取ります。

  • 第1引数は、変換のベースとなる座標系の名前を指定します。
  • 第2引数は、変換したい対象の座標系の名前を指定します。
  • 第3引数は、変換したい時間を指定します。基本的には、rospy.Time(0)で良いようですが、過去のデータを元に座標変換したいときはきちんと指定しないとまずいみたいです。

また、戻り値として、変換した座標データが戻ってきます。直交座標をまとめたタプル(x, y, z)と、回転をまとめたタプル(x, y, z, w)(クオータニオン)が戻ってきます。

例外は、例にあげた3つが返ってくるようです。詳しくはドキュメントを見てください。

気になったこと

座標系が登録されてから、実際にそれが座標変換に使えるようになるまで、数msの時間がかかるようです。 座標変換に使えるようになるまで待機するメソッドもあるのですが、そもそも数msの遅延があるのが若干不安です…


とりあえず、今わかっている情報はこんなところです。

チュートリアルをひと通りやった感じだと、かなーり便利そう。 今後わかった情報があれば随時更新していこうと思います。