Windows11にROS2を入れてみた

やりたいこと

windows11にROS2をインストールしたい

参考にしたサイト

ほとんどこの通りにやって動きました! (Dockerを使わない場合) Windows10のWSL2にROS2をインストールする - Qiita

手順(差分だけ記載していきます)

1. Windows11にWSL2をインストール

2. Xserver → 不要

windows11からはlinux guiが使えるようになったので不要でした。

xtech.nikkei.com

3. ROS2導入

参考サイトの手順通りです。

4. RVIZ2を導入

index.ros.org

この手順通りですが、git cloneのときにブランチを指定(foxy)しないとエラーになる

[Processing: rviz_ogre_vendor]
Finished <<< rviz_ogre_vendor [11min 38s]
Starting >>> rviz_rendering
--- stderr: rviz_rendering
/home/rviz2_ws/src/rviz/rviz_rendering/src/rviz_rendering/mesh_loader.cpp:65:10: fatal error: resource_retriever/retriever.hpp: No such file or directory
   65 | #include "resource_retriever/retriever.hpp"
      |          ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
compilation terminated.
make[2]: *** [CMakeFiles/rviz_rendering.dir/build.make:145: CMakeFiles/rviz_rendering.dir/src/rviz_rendering/mesh_loader.cpp.o] Error 1
make[2]: *** Waiting for unfinished jobs....
make[1]: *** [CMakeFiles/Makefile2:344: CMakeFiles/rviz_rendering.dir/all] Error 2
make[1]: *** Waiting for unfinished jobs....
make: *** [Makefile:141: all] Error 2
---
Failed   <<< rviz_rendering [16.7s, exited with code 2]
git clone https://github.com/ros2/rviz.git -b foxy

で解決

5. pythonのインストール

(入ってたので省略)

6. gazeboのインストール

参考サイト通りにやる

さいごに

以上です。 windows11のwslはUSBが使えるようなので、webカメラ使えそうですね。

zenn.dev 私は、unityかUE4を使ってロボットアームを動かしたいので、 連携方法を探してます。 何かいい方法を知ってる人はコメントください。

pythonでGUIアプリ作成 ~画像表示・回転~

やりたいこと

pythonGUIアプリを作る

調べたこと

kivyのドキュメント pyky.github.io

作ったアプリ

  • ファイルパスを読み込む(FileChooser)
  • 画像を表示する(Image)
  • opencvで回転させる

コード(python部分)

from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.floatlayout import FloatLayout
from kivy.graphics.texture import Texture
from kivy.properties import ObjectProperty
from kivy.factory import Factory
from kivy.uix.popup import Popup
from kivy.core.window import Window

import os
import cv2

class LoadDialog(FloatLayout):
    load = ObjectProperty(None)
    cancel = ObjectProperty(None)

class Root(BoxLayout):
    # loadfile = ObjectProperty(None)
    image_texture = ObjectProperty(None)
    imgData = None
    
    def dismiss_popup(self):
        self._popup.dismiss()

    def show_load(self):
        content = LoadDialog(load=self.load, cancel=self.dismiss_popup)
        self._popup = Popup(title="Load file", content=content,
                            size_hint=(0.9, 0.9))
        self._popup.open()

    def load(self, path, filename):
        imgFilepath = os.path.join(path, filename[0])
        # print(self.imgFliepath)
        # with open(os.path.join(path, filename[0])) as stream:
        #     self.text_input.text = stream.read()
        self.imgData = cv2.imread(imgFilepath)
        self.img_show()
        self.dismiss_popup()

    def img_show(self):
        # filename = "../guiapp/IMG_0162.JPG"
        img = self.imgData
        img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
        img = cv2.flip(img, 0)
        texture = Texture.create(size=(img.shape[1], img.shape[0]))
        texture.blit_buffer(img.tobytes())
        self.image_texture = texture
    
    def img_show(self):
        img = self.imgData
        img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
        img = cv2.flip(img, 0)
        texture = Texture.create(size=(img.shape[1], img.shape[0]))
        texture.blit_buffer(img.tobytes())
        self.image_texture = texture

    def rotation_clockwise(self):
        self.imgData = cv2.rotate(self.imgData, cv2.ROTATE_90_CLOCKWISE)
        self.img_show()

    def rotation_affine(self, value):
        img = self.imgData
        center = (img.shape[1] / 2,  img.shape[0] / 2)
        angle = -float(value)
        scale = 1.0
        print(center, angle, scale)
        trans = cv2.getRotationMatrix2D(center, angle, scale)
        img = cv2.warpAffine(img, trans, (img.shape[1], img.shape[0]))
        img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
        img = cv2.flip(img, 0)
        texture = Texture.create(size=(img.shape[1], img.shape[0]))
        texture.blit_buffer(img.tobytes())
        self.image_texture = texture

class mainApp(App):
    def build(self):
        return Root()

Factory.register('Root', cls=Root)
Factory.register('LoadDialog', cls=LoadDialog)
Window.size = (600, 600)

ap = mainApp()
ap.run()

コード(kv部分)

<Root>:
    BoxLayout:
        orientation: "vertical"

        ScrollView:
            bar_width: 10
            Image:
                id: im
                texture: root.image_texture
                size_hint: (1, 1)
        BoxLayout:
            orientation: "horizontal"
            size_hint: (1, 0.25)
            Slider:
                id: slider1
                size_hint: (0.75, 1)
                min: 0
                max: 90
                step: 0.1
                on_value: root.rotation_affine(self.value)
            TextInput:
                id: tx1
                size_hint: (0.25, 1)
                text: str(slider1.value)

        BoxLayout:
            orientation: "horizontal"
            size_hint: (1, 0.25)
            Slider:
                id: slider2
                size_hint: (0.75, 1)
                min: 0
                max: 100
                step: 0.1
        
            TextInput:
                id: tx2
                size_hint: (0.25, 1)
                text: str(slider2.value)
            
        ActionBar:
            background_color: 1, 0, 0, 1
            ActionView:
                use_separator: True
                ActionPrevious:
                    text: "new"
                    with_previous: False
                ActionGroup:
                    text: "menu"
                    ActionButton:
                        text: "open"
                        on_release: root.show_load()
                    ActionButton:
                        text: "clockwise"
                        on_release: root.rotation_clockwise()
                    ActionButton:
                        text: "button3"

<LoadDialog>:
    BoxLayout:
        size: root.size
        pos: root.pos
        orientation: "vertical"
        # FileChooserListView:
        #     id: filechooser

        FileChooser:
            id: filechooser
            rootpath: "../"
            path: "/mnt/c/workspace/"
            filters: ['*.jpg', '*.png','*.JPG', '*.PNG']
            FileChooserListLayout
            FileChooserIconLayout
        BoxLayout:
            size_hint_y: None
            height: 30
            Button:
                text: "Cancel"
                on_release: root.cancel()
            Button:
                text: "Load"
                on_release: root.load(filechooser.path, filechooser.selection)
            BoxLayout:
                orientation: "vertical"
                Button:
                    text: 'Icon View'
                    on_press: filechooser.view_mode = 'icon'
                Button:
                    text: 'List View'
                    on_press: filechooser.view_mode = 'list'

結果

起動時

f:id:techsho:20201004182917p:plain
起動時

ファイル選択

f:id:techsho:20201004183009p:plain
ファイル選択

スライダーでの回転量調整

f:id:techsho:20201004183035p:plain
スライダー

購入検討中の本

pythonでGUIアプリ作成 ~Kivyのインストール~

やりたいこと

pythonGUIアプリを作る

調べたこと

pythonでは、GUIアプリを作るのに以下の4つの環境が候補としてあげられる。

この中で、近年人気なのがKivyらしい techplay.jp

Kivyのインストール

私の開発環境は以下のとおりである - WSLのubuntu18.04LTS - python3.6

できればこの環境を使いまわしたいので、インストールするのはubuntuに決定。

どうやら、ubuntuに入れる方法はややこしいらしい。

pyky.github.io

これを参考にインストールをしていく。

躓いたところ

stable buildsだとエラーが出る

[CRITICAL] [Window      ] Unable to find any valuable Window provider. Please enable debug logging (e.g. add -d if running from the command line, or change the log level in the config) and re-run your app to identify potential causes
egl_rpi - ImportError: cannot import name 'bcm'
  File "/usr/lib/python3/dist-packages/kivy/core/__init__.py", line 63, in core_select_lib
    fromlist=[modulename], level=0)
  File "/usr/lib/python3/dist-packages/kivy/core/window/window_egl_rpi.py", line 12, in <module>
    from kivy.lib.vidcore_lite import bcm, egl

sdl2 - ImportError: libSDL2_image-2.0.so.0: cannot open shared object file: No such file or directory
  File "/usr/lib/python3/dist-packages/kivy/core/__init__.py", line 63, in core_select_lib
    fromlist=[modulename], level=0)
  File "/usr/lib/python3/dist-packages/kivy/core/window/window_sdl2.py", line 27, in <module>
    from kivy.core.window._window_sdl2 import _WindowSDL2Storage

x11 - ImportError: No module named 'kivy.core.window.window_x11'
  File "/usr/lib/python3/dist-packages/kivy/core/__init__.py", line 63, in core_select_lib
    fromlist=[modulename], level=0)

[CRITICAL] [App         ] Unable to get a Window, abort.

これは、ubuntuインストーラーにbcmが含まれないからだそう

github.com

解決策

nightly buildsを使う

$ sudo add-apt-repository ppa:kivy-team/kivy-daily

$ sudo apt-get install python3-kivy

これでinstallが終了。

テストコードの実行

コード

from kivy.app import App
from kivy.uix.widget import Widget
 

class MainWindow(Widget):
    pass


class HelloWorldApp(App):
    def build(self):
        return MainWindow()


if __name__ == '__main__':
    HelloWorldApp().run()

躓いたところ

openGLが2.0以下なのでErrorになる

解決方法

いろいろ試したが、xwindowが選ぶopenGLが1.4になってたのが問題だった。

unset LIBGL_ALWAYS_INDIRECT

と、VcXsrvの起動時に Extra settingsでNative openglをチェックアウトする

すると、openGLが変更され起動する。

結果

f:id:techsho:20200927174524p:plain

購入検討中の本

pythonでROS1のservice

やること

  • ROS1でサービスを使ってみる

やったこと

チュートリアルをやってみて、追加しないといけなかったことを記載しています。(チュートリアルも途中から見るとだめで、ちゃんと前後関係把握しないとだめですね。。。)

前回の環境の続きで始めます。

~/rospython/src/ros_pubsubtestにて、 サービスのパッケージをコピーしてくる

roscp rospy_tutorials AddTwoInts.srv srv/AddTwoInts.srv

結果 ros_bubsubtest/srv/AddTwoInts.srvが生成される。

ros_pubsubtest/CMakeList.txtにおいて、

add_service_files(
   FILES
   AddTwoInts.srv 
)

をコメント解除&追記する

その後、~/rospythonにて、catkin_makeを実行する すると、以下のファイルが生成されているはず。

~/rospython/devel/lib/python2.7/dist-packages/
.
└── ros_pubsubtest
    ├── __init__.py
    ├── __init__.pyc
    ├── msg
    │   ├── __init__.py
    │   ├── __init__.pyc
    │   ├── _pubsub.py
    │   └── _pubsub.pyc
    └── srv # 新たに生成されたフォルダ
        ├── _AddTwoInts.py
        └── __init__.py

では、サービスのソースコードを書いていく。(chmod +xで実行権限を与えるのを忘れない)

#! /usr/bin/python

import sys
import rospy
from ros_pubsubtest.srv import *

def add_two_ints_client(x, y):
    rospy.wait_for_service('add_two_ints')
    try:
        add_two_ints = rospy.ServiceProxy("add_two_ints", AddTwoInts)
        resp1 = add_two_ints(x, y)
        return resp1.sum
    except rospy.ServiceExpection, e:
        print "Service call failed: %s"%e

def usage():
    return "%s [x y]"%sys.argv[0]

if __name__ == "__main__":
    if len(sys.argv) == 3:
        x = int(sys.argv[1])
        y = int(sys.argv[2])
    else:
        print usage()
        sys.exit(1)
    print "Requesting %s+%s"%(x, y)
    print "%s + %s = %s"%(x, y, add_two_ints_client(x, y))
#! /usr/bin/python

from ros_pubsubtest.srv import *
import rospy

def handle_add_two_ints(req):
    print "Returning [%s + %s = %s]"%(req.a, req.b, (req.a + req.b))
    return AddTwoIntsResponse(req.a + req.b)

def add_two_ints_server():
    rospy.init_node("add_two_ints_server")
    s = rospy.Service("add_two_ints", AddTwoInts, handle_add_two_ints)
    print "Ready to add two ints."
    rospy.spin()

if __name__== "__main__":
    add_two_ints_server()

いつものように、~/rospython/catkin_makeを実行し、ターミナル1でroscoreを起動後、ターミナル2, 3それぞれで、

# ターミナル2
rosrun ros_pubsubtest add_two_ints_client.py 10 8
# ターミナル3
rosrun ros_pubsubtest add_two_ints_server.py

実行する。

結果は、

# ターミナル2
Requesting 10+8
# server起動までは待ちが入る
10 + 8 = 18

# ターミナル3
Returning [10 + 8 = 18]

となる。

無事にサービスを作ることができた。

参考になる本

pythonでROS1のpub/sub_part2

やること

  • ROS1でメッセージのやり取り(pub/sub)

前回の記事

techsho.hatenablog.com

前回失敗したところ

ビルドが完了したので実行してみる

terminal1

cd ~/rospython; source devel/setup.bash
roscore

無事にroscore起動

terminal2

cd ~/rospython; source devel/setup.bash
rosrun ros_pubsubtest publish.py

エラーが出る

[rosrun] Couldn't find executable named publish.py below /home/hogehoge/rospython/src/ros_pubsubtest
[rosrun] Found the following, but they're either not files,
[rosrun] or not executable:
[rosrun]   /home/hogehoge/rospython/src/ros_pubsubtest/script/publish.py

ソースコードの確認

terminal1でroscore起動 termnial2でpython publish.pyでpubを起動 terminal3でpython subscribe.pyでsubを起動

f:id:techsho:20200428083353p:plain

左がpubのlogで右がsubのログです。

両方意図通り動いているのでソースコードの不具合ではなさそう。

そもそも、エラーログが、ファイルが存在しないてきな感じなので、やっぱりbuildの設定が原因?
そもそも、タブ補完されないのが怪しすぎる。。。

いろいろ確認していくと、ファイルに実行権限が与えられていなかったのが原因でした。

chmod +x publish.py
chmod +x subscribe.py

を実行

その後、

# ターミナル2
rosrun ros_pubsubtest publish.py
# ターミナル3
rosrun ros_pubsubtest subscribe.py

を実行すると、無事に起動しました。

参考になりそうな本

pythonでROS1のpub/sub_part1

やること

  • ROS1の作業フォルダ作成
  • ROS1でメッセージのやり取り(pub/sub) (pub/subは失敗)

    ROS1の作業フォルダの作成

    フォルダの作成及び、melodicのpath設定

mkdir -p rospython/src
cd rospython
source /opt/ros/melodic/setup.bash
tree .

実行結果

.
└── src

1 directory, 0 files

makeで必要なファイルを作成

catkin_make

実行結果

Base path: /home/hogehoge/rospython
Source space: /home/hogehoge/rospython/src
Build space: /home/hogehoge/rospython/build
Devel space: /home/hogehoge/rospython/devel
Install space: /home/hogehoge/rospython/install
Creating symlink "/home/hogehoge/rospython/src/CMakeLists.txt" pointing to "/opt/ros/melodic/share/catkin/cmake/toplevel.cmake"
####
#### Running command: "cmake /home/hogehoge/rospython/src -DCATKIN_DEVEL_PREFIX=/home/hogehoge/rospython/devel -DCMAKE_INSTALL_PREFIX=/home/hogehoge/rospython/install -G Unix Makefiles" in "/home/hogehoge/rospython/build"
####
-- The C compiler identification is GNU 7.5.0
-- The CXX compiler identification is GNU 7.5.0
-- Check for working C compiler: /usr/bin/cc
-- Check for working C compiler: /usr/bin/cc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Detecting C compile features
-- Detecting C compile features - done
-- Check for working CXX compiler: /usr/bin/c++
-- Check for working CXX compiler: /usr/bin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Using CATKIN_DEVEL_PREFIX: /home/hogehoge/rospython/devel
-- Using CMAKE_PREFIX_PATH: /opt/ros/melodic
-- This workspace overlays: /opt/ros/melodic
-- Found PythonInterp: /usr/bin/python2 (found suitable version "2.7.17", minimum required is "2")
-- Using PYTHON_EXECUTABLE: /usr/bin/python2
-- Using Debian Python package layout
-- Using empy: /usr/bin/empy
-- Using CATKIN_ENABLE_TESTING: ON
-- Call enable_testing()
-- Using CATKIN_TEST_RESULTS_DIR: /home/hogehoge/rospython/build/test_results
-- Found gtest sources under '/usr/src/googletest': gtests will be built
-- Found gmock sources under '/usr/src/googletest': gmock will be built
-- Found PythonInterp: /usr/bin/python2 (found version "2.7.17")
-- Looking for pthread.h
-- Looking for pthread.h - found
-- Looking for pthread_create
-- Looking for pthread_create - not found
-- Looking for pthread_create in pthreads
-- Looking for pthread_create in pthreads - not found
-- Looking for pthread_create in pthread
-- Looking for pthread_create in pthread - found
-- Found Threads: TRUE
-- Using Python nosetests: /usr/bin/nosetests-2.7
-- catkin 0.7.23
-- BUILD_SHARED_LIBS is on
-- BUILD_SHARED_LIBS is on
-- Configuring done
-- Generating done
-- Build files have been written to: /home/hogehoge/rospython/build
####
#### Running command: "make -j8 -l8" in "/home/hogehoge/rospython/build"
####

するとファイルが作成される

├── build
│   ├── CMakeFiles
│   │   ├── 3.10.2
│   │   │   ├── CompilerIdC
│   │   │   │   └── tmp
│   │   │   └── CompilerIdCXX
│   │   │       └── tmp
│   │   ├── CMakeTmp
│   │   ├── clean_test_results.dir
│   │   ├── download_extra_data.dir
│   │   ├── doxygen.dir
│   │   ├── run_tests.dir
│   │   └── tests.dir
│   ├── atomic_configure
│   ├── catkin
│   │   └── catkin_generated
│   │       └── version
│   ├── catkin_generated
│   │   ├── installspace
│   │   └── stamps
│   │       └── Project
│   ├── gtest
│   │   ├── CMakeFiles
│   │   └── googlemock
│   │       ├── CMakeFiles
│   │       │   ├── gmock.dir
│   │       │   │   ├── __
│   │       │   │   │   └── googletest
│   │       │   │   │       └── src
│   │       │   │   └── src
│   │       │   └── gmock_main.dir
│   │       │       ├── __
│   │       │       │   └── googletest
│   │       │       │       └── src
│   │       │       └── src
│   │       └── gtest
│   │           └── CMakeFiles
│   │               ├── gtest.dir
│   │               │   └── src
│   │               └── gtest_main.dir
│   │                   └── src
│   └── test_results
├── devel
│   └── lib
└── src

必要なパッケージを追加して、オリジナルのパッケージを作る

cd src
catkin_create_pkg ros_pubsubtest roscpp rospy std_msgs

出力結果

Created file ros_pubsubtest/package.xml
Created file ros_pubsubtest/CMakeLists.txt
Created folder ros_pubsubtest/include/ros_pubsubtest
Created folder ros_pubsubtest/src
Successfully created files in /home/hogehoge/rospython/src/ros_pubsubtest. Please adjust the values in package.xml.

すると、以下の要はフォルダーが出来上がります

/rospython/src$ tree .
.
├── CMakeLists.txt -> /opt/ros/melodic/share/catkin/cmake/toplevel.cmake
└── ros_pubsubtest
    ├── CMakeLists.txt
    ├── include
    │   └── ros_pubsubtest
    ├── package.xml
    └── src

最後に、もう一度catkin_makeをrospython内で行うとフォルダの完成です

-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-- ~~  traversing 1 packages in topological order:
-- ~~  - ros_pubsubtest
-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-- +++ processing catkin package: 'ros_pubsubtest'
-- ==> add_subdirectory(ros_pubsubtest)

こんなログが出てきます

pub/sub作成

/rospython/src/ros_pubsubtest/msg/にpubsub.msgというファイルを作り、以下を記述する

int32 arg_x
int32 arg_y

14行目ぐらいのfind_packageにmessage_generationを追加
51行目ぐらいのadd_message_filesをコメント解除し、pubsub.msgを追加
73行目ぐらいのgenerate_messagesをすべてコメント解除
107行目ぐらいのcatkin_packageのLIBRARIES ros_pubsubtest, CATKIN_DEPENDS roscpp rospy std_msgs, DEPENDS system_libをコメント解除

rospythonに戻って、catkin_makeを実行すると

Base path: /home/hogehoge/rospython
Source space: /home/hogehoge/rospython/src
Build space: /home/hogehoge/rospython/build
Devel space: /home/hogehoge/rospython/devel
Install space: /home/hogehoge/rospython/install
####
#### Running command: "make cmake_check_build_system" in "/home/hogehoge/rospython/build"
####
-- Using CATKIN_DEVEL_PREFIX: /home/hogehoge/rospython/devel
-- Using CMAKE_PREFIX_PATH: /opt/ros/melodic
-- This workspace overlays: /opt/ros/melodic
-- Found PythonInterp: /usr/bin/python2 (found suitable version "2.7.17", minimum required is "2")
-- Using PYTHON_EXECUTABLE: /usr/bin/python2
-- Using Debian Python package layout
-- Using empy: /usr/bin/empy
-- Using CATKIN_ENABLE_TESTING: ON
-- Call enable_testing()
-- Using CATKIN_TEST_RESULTS_DIR: /home/hogehoge/rospython/build/test_results
-- Found gtest sources under '/usr/src/googletest': gtests will be built
-- Found gmock sources under '/usr/src/googletest': gmock will be built
-- Found PythonInterp: /usr/bin/python2 (found version "2.7.17")
-- Using Python nosetests: /usr/bin/nosetests-2.7
-- catkin 0.7.23
-- BUILD_SHARED_LIBS is on
-- BUILD_SHARED_LIBS is on
-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-- ~~  traversing 1 packages in topological order:
-- ~~  - ros_pubsubtest
-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-- +++ processing catkin package: 'ros_pubsubtest'
-- ==> add_subdirectory(ros_pubsubtest)
-- Using these message generators: gencpp;geneus;genlisp;gennodejs;genpy
-- ros_pubsubtest: 1 messages, 0 services
CMake Warning at /opt/ros/melodic/share/catkin/cmake/catkin_package.cmake:166 (message):
  catkin_package() DEPENDS on 'system_lib' but neither
  'system_lib_INCLUDE_DIRS' nor 'system_lib_LIBRARIES' is defined.
Call Stack (most recent call first):
  /opt/ros/melodic/share/catkin/cmake/catkin_package.cmake:102 (_catkin_package)
  ros_pubsubtest/CMakeLists.txt:107 (catkin_package)


-- Configuring done
-- Generating done
-- Build files have been written to: /home/hogehoge/rospython/build
####
#### Running command: "make -j8 -l8" in "/home/hogehoge/rospython/build"
####
Scanning dependencies of target std_msgs_generate_messages_eus
Scanning dependencies of target _ros_pubsubtest_generate_messages_check_deps_pubsub
Scanning dependencies of target std_msgs_generate_messages_nodejs
Scanning dependencies of target std_msgs_generate_messages_py
Scanning dependencies of target std_msgs_generate_messages_lisp
Scanning dependencies of target std_msgs_generate_messages_cpp
[  0%] Built target std_msgs_generate_messages_eus
[  0%] Built target std_msgs_generate_messages_nodejs
[  0%] Built target std_msgs_generate_messages_py
[  0%] Built target std_msgs_generate_messages_cpp
[  0%] Built target std_msgs_generate_messages_lisp
[  0%] Built target _ros_pubsubtest_generate_messages_check_deps_pubsub
Scanning dependencies of target ros_pubsubtest_generate_messages_eus
Scanning dependencies of target ros_pubsubtest_generate_messages_py
Scanning dependencies of target ros_pubsubtest_generate_messages_nodejs
Scanning dependencies of target ros_pubsubtest_generate_messages_cpp
Scanning dependencies of target ros_pubsubtest_generate_messages_lisp
[ 14%] Generating EusLisp code from ros_pubsubtest/pubsub.msg
[ 28%] Generating Python from MSG ros_pubsubtest/pubsub
[ 57%] Generating EusLisp manifest code for ros_pubsubtest
[ 57%] Generating Javascript code from ros_pubsubtest/pubsub.msg
[ 71%] Generating C++ code from ros_pubsubtest/pubsub.msg
[ 85%] Generating Lisp code from ros_pubsubtest/pubsub.msg
[ 85%] Built target ros_pubsubtest_generate_messages_nodejs
[ 85%] Built target ros_pubsubtest_generate_messages_lisp
[ 85%] Built target ros_pubsubtest_generate_messages_cpp
[100%] Generating Python msg __init__.py for ros_pubsubtest
[100%] Built target ros_pubsubtest_generate_messages_py
[100%] Built target ros_pubsubtest_generate_messages_eus
Scanning dependencies of target ros_pubsubtest_generate_messages
[100%] Built target ros_pubsubtest_generate_messages

というログが出る

次に、pub/subをするpythonコードを作成する
ros_pubsubtestフォルダ内に、scriptフォルダを作成し、その中にコードを作る publish.py

#! /usr/bin/python
# -*- cording: utf-8 -*-

import rospy

from ros_pubsubtest.msg import pubsub

def pub():
    rospy.init_node('pub', anonymous=True)

    pub = rospy.Publisher('input_data', pubsub, queue_size=100)

    r = rospy.Rate(10)

    para_x = 0
    para_y = 2

    msg = pubsub()

    while not rospy.is_shutdown():
        msg.arg_x = para_x
        msg.arg_y = para_y

        pub.publish(msg)
        print "published arg_x=%d arg_y=%d"%(msg.arg_x, msg.arg_y)
        para_x += 1
        para_y += 1

        r.sleep()

if __name__=='__main__':
    try:
        pub()
    except rospy.ROSInterruptException:
        pass

subscribe.py

#! /usr/bin/python
# -*- coding: utf-8 -*-

import rospy
from ros_pubsubtest.msg import pubsub

def callback(data):
    print data.arg_x + data.arg_y

def sub():
    rospy.init_node('sub', anonymous = True)

    rospy.Subscriber('input_data', pubsub, callback)

    rospy.spin()

if __name__ == '__main__':
    sub()

~/rospython/でcatkin_makeを実行

Base path: /home/hogehoge/rospython
Source space: /home/hogehoge/rospython/src
Build space: /home/hogehoge/rospython/build
Devel space: /home/hogehoge/rospython/devel
Install space: /home/hogehoge/rospython/install
####
#### Running command: "make cmake_check_build_system" in "/home/hogehoge/rospython/build"
####
####
#### Running command: "make -j8 -l8" in "/home/hogehoge/rospython/build"
####
[  0%] Built target std_msgs_generate_messages_eus
[  0%] Built target std_msgs_generate_messages_py
[  0%] Built target std_msgs_generate_messages_nodejs
[  0%] Built target std_msgs_generate_messages_cpp
[  0%] Built target std_msgs_generate_messages_lisp
[  0%] Built target _ros_pubsubtest_generate_messages_check_deps_pubsub
[ 42%] Built target ros_pubsubtest_generate_messages_eus
[ 57%] Built target ros_pubsubtest_generate_messages_py
[ 85%] Built target ros_pubsubtest_generate_messages_nodejs
[ 85%] Built target ros_pubsubtest_generate_messages_cpp
[100%] Built target ros_pubsubtest_generate_messages_lisp
[100%] Built target ros_pubsubtest_generate_messages

ビルドが完了したので実行してみる

terminal1

cd ~/rospython; source devel/setup.bash
roscore

無事にroscore起動

terminal2

cd ~/rospython; source devel/setup.bash
rosrun ros_pubsubtest publish.py

エラーが出る

[rosrun] Couldn't find executable named publish.py below /home/hogehoge/rospython/src/ros_pubsubtest
[rosrun] Found the following, but they're either not files,
[rosrun] or not executable:
[rosrun]   /home/hogehoge/rospython/src/ros_pubsubtest/script/publish.py

長くなったので、エラー解析は次回ということで。。。

点群データの平面推定_part3

やること

  • 平面の導出
  • RANSACの勉強

前回の記事

techsho.hatenablog.com

RANSACの勉強

前回は理想的な点群に対して、最小二乗平面を推定した。 今回は、ノイズのあるデータを考える。

ノイズデータの平面推定

前回のコードに対してノイズ成分を付加する

import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
%matplotlib inline
A = 0; B = 0; C = -1 ; D = 1

points = []
cnt = 0
for x in np.arange(0, 10, 0.5):
  for y in np.arange(0, 10, 0.5):
    for z in np.arange(0, 10, 0.5):
      if(abs(A*x + B*y + C*z + D) < 1e-8):
        points.append([x, y, z])
        if((x+y+z) == 15): points.append([x, y, z*1000])
        cnt+=1
points = np.asarray(points).T

fig = plt.figure()
ax = Axes3D(fig)
ax.set_xlabel("X")
ax.set_ylabel("Y")
ax.set_zlabel("Z")

ax.scatter3D(points[0],points[1],points[2])

plt.show()

f:id:techsho:20200412224212p:plain

prePoints = np.copy(points)
points = points.T
vecB = -points[:,2]
points[:,2] = 1
dim = np.dot(points.T, points)
vec = np.dot(vecB, points)
ans = np.dot(np.linalg.inv(dim), vec)


A = ans[0]; B = ans[1]; C = ans[2]
xmesh, ymesh = np.meshgrid(np.linspace(0, 10, 20),
                            np.linspace(0, 10, 20))
print(xmesh.shape, ymesh.shape)
zmesh = (C + A * xmesh.ravel() +
        B * ymesh.ravel()).reshape(xmesh.shape)

fig = plt.figure()
ax = Axes3D(fig)
ax.set_xlabel("X")
ax.set_ylabel("Y")
ax.set_zlabel("Z")
from matplotlib.ticker import ScalarFormatter
ax.zaxis.set_major_formatter(ScalarFormatter(useMathText=True))
ax.ticklabel_format(style="sci",  axis="z",scilimits=(0,0))
ax.scatter(prePoints[0],prePoints[1],prePoints[2])
ax.plot_wireframe(xmesh, ymesh, zmesh, color='r')

f:id:techsho:20200412224322p:plain

このように、ノイズ成分の影響で平面推定にずれが生じる このノイズ成分を除去するためにRandom Sample Consensus(RANSAC)を使う

RANSAC

前回の記事では、Open3dを使った平面推定を行ったが、その際にRANSACによるノイズ除去が実行されていた。
RANSACとは、簡単に言うと以下の処理をする

  1. すべての点群データから、モデル導出に必要な数だけランダムに点群を選ぶ
    • 直線なら2点
    • 平面なら3点
  2. 最小二乗法などで、モデル推定(モデルパラメータ導出)
    • 直線なら直線の方程式がモデルになる
    • 平面なら平面の方程式がモデルになる
  3. 推定したモデルに対して、点群情報を入力し、実値とモデルを使った推定値の差を取る
  4. 差が閾値以下ならモデルにフィットする点、閾値以上なら外れ値として定義
  5. フィットする点が閾値以上集まったら、推定モデルは正しいモデルの候補の一つとする
  6. 1.~5.を任意の数だけ繰り返し、モデルの候補を作る
  7. モデルの候補から最も良いモデルを選ぶ

以上が、RANSACのイメージである

実行したコードを以下に示す

def fit_plane(samples):
  if(len(samples) < 3):
    print("error: The number of samples is small.")
    return 0
  vecB = -samples[:,2]
  samples[:,2] = 1
  dim = np.dot(samples.T, samples)
  print("dim")
  print(dim)
  vec = np.dot(vecB, samples)
  print("vec")
  print(vec)
  print("inv")
  print(np.linalg.inv(dim))
  ans = np.dot(np.linalg.inv(dim), vec)
  return ans

def check_outlier(coeff, p):
  return np.abs((coeff[0]*p[0]+coeff[1]*p[1]+coeff[2]) - (-p[2]))

def ransac(data, n=3, k=100, t=2.0, d=300):
    good_models = []
    good_model_errors = []
    iterations = 0
    while iterations < k:
        sample = data[np.random.choice(len(data), n, False)]
        coeff = fit_plane(sample)
        # print(coeff)

        inliers = []
        for points in data:
            if (points == sample).all(1).any(): continue
            if check_outlier(coeff, points) > t:
                continue
            else:
                inliers.append(points)
        # print(len(inliers))
        if len(inliers) > d:
            error_array = []
            for points in data:
                error_array.append(check_outlier(coeff, points))
            error_array = np.asarray(error_array)
            ave_error = np.mean(error_array)
            good_models.append(coeff)
            good_model_errors.append(ave_error)
        iterations += 1

    best_model_index = np.argmin(good_model_errors)
    return good_models[best_model_index]

a, b, c = ransac(prePoints.T)
print(a, b, c)

実行結果(ax+by+c = -z)は、

a = -1.1102230246251565e-16
b =  0.0
c = -1.0

となる。

今回の方程式の解き方では、逆行列が生成できないケースが出てくる(singular matrix)