ROS2ノードの概要と実装例

ロボットの各機能は、それぞれが独立した構成要素であり、一つの「作業単位」として機能します。これらの作業単位が特定の仕組みによって接続され、全体としてロボットを形成します。この「作業単位」をノードと呼びます。各ノードは、独立した機能を担うものです。

通信モデル

ロボットシステムは物理的に統合されたものではなく、例えばロボットの体にはコンピュータAが搭載されており、カメラを通じて周囲の環境情報を取得し、モーターで足を動かして移動させることができます。また、別のコンピュータBがデスクトップに設置され、その上でロボットの視覚情報を遠隔監視したり、速度やパラメータをリモート設定したり、ジョイスティックを使って人為的に移動させることも可能です。
これらは異なるコンピュータに存在するが、いずれもロボットの一部である作業単位、つまりノードであり、これらが協調して完全なロボットシステムを構成しています。

  • ノードの役割は特定のタスクを実行することです。
  • 各ノードは独立して実行可能なプログラムファイルであり、PythonスクリプトやC++でコンパイルされた実行ファイルなども含まれます。
  • ノードのプログラミング言語は多様です。
  • ノードは機能ごとに異なり、システム設計によってコンピュータA、B、またはクラウド上に配置されることがあり、これは分散型アーキテクチャと呼ばれます。
  • 各ノードには一意の識別名が必要であり、ノードの検索や状態確認を行う際に使用されます。

ケース1:Hello Worldノード(手順型)

ros2 runコマンドを使用して、コンパイル済みのコードを実行し、ノードの動作を確認します。その後、コードの実装内容を分析します。

$ ros2 run learning_node node_helloworld

コード解説

ソースファイル learning_node/node_helloworld.py

import rclpy                                   # ROS2 Pythonインターフェースライブラリ
from rclpy.node import Node                    # ROS2ノードクラス
import time

def main(args=None):                           # ROS2ノードのメイン関数
    rclpy.init(args=args)                      # ROS2 Pythonインターフェースの初期化
    node = Node("node_helloworld")             # ROS2ノードのインスタンスを作成

    while rclpy.ok():                          # ROS2システムが稼働しているか確認
        node.get_logger().info("Hello World")  # ログ出力
        time.sleep(0.5)                        # 処理周期の調整

    node.destroy_node()                        # ノードの破棄
    rclpy.shutdown()                           # ROS2 Pythonインターフェースの終了

コードを記述後、ビルド設定ファイルsetup.pyを編集し、Pythonプログラムのエントリポイントを登録します:

    entry_points={
        'console_scripts': [
         'node_helloworld       = learning_node.node_helloworld:main',
        ],

ノード作成フロー

ノードの作成手順は以下の通りです:

  • インターフェースの初期化
  • ノードの作成と初期化
  • 機能の実装
  • ノードの破棄とインターフェース終了

手続き型プログラミングでは複雑なシステムの開発においてモジュール化が困難であるため、ROS2ではオブジェクト指向のプログラミングスタイルが推奨されます。これにより、コードの可読性と再利用性が向上し、デバッグも容易になります。

ケース2:Hello Worldノード(オブジェクト指向)

$ ros2 run learning_node node_helloworld_class

コード解説

learning_node/node_helloworld_class.py

import rclpy                                     # ROS2 Pythonインターフェースライブラリ
from rclpy.node import Node                      # ROS2ノードクラス
import time

"""
HelloWorldノードを定義。初期化時にログ出力を行う
"""
class HelloWorldNode(Node):
    def __init__(self, name):
        super().__init__(name)                     # 親クラスの初期化
        while rclpy.ok():                          # ROS2システムが稼働しているか確認
            self.get_logger().info("Hello World")  # ログ出力
            time.sleep(0.5)                        # 処理周期の調整

def main(args=None):                               # ROS2ノードのメイン関数
    rclpy.init(args=args)                          # ROS2 Pythonインターフェースの初期化
    node = HelloWorldNode("node_helloworld_class") # ノードインスタンス作成
    rclpy.spin(node)                               # ROS2終了まで待機
    node.destroy_node()                            # ノードの破棄
    rclpy.shutdown()                               # ROS2 Pythonインターフェースの終了

コードを記述後、setup.pyにエントリポイントを追加します:

entry_points={
        'console_scripts': [
         'node_helloworld       = learning_node.node_helloworld:main',
         'node_helloworld_class = learning_node.node_helloworld_class:main',
        ],

ノード作成フローは手続き型と同じです。

ケース3:物体認識ノード

画像処理を例に、実際のロボットにおけるノードの実装方法を模倣します。

このサンプルではOpenCVライブラリを使用するため、事前にインストールが必要です:

$ sudo apt install python3-opencv

その後、実行します:

$ ros2 run learning_node node_object    # 画像パスを変更して再ビルドが必要

実行前にlearning_node/node_object.py内の画像パスを適切なものに書き換え、再ビルドしてください。
例:image = cv2.imread('/home/hcx/dev_ws/src/ros2_21_tutorials/learning_node/learning_node/apple.jpg')

実行結果として、赤いリンゴが検出され、輪郭に緑の枠線が描かれ、中心に緑の点が表示されます。

コード解説

learning_node/node_object.py

import rclpy                            # ROS2 Pythonインターフェースライブラリ
from rclpy.node import Node             # ROS2ノードクラス

import cv2                              # OpenCV画像処理ライブラリ
import numpy as np                      # NumPy数値計算ライブラリ

lower_red = np.array([0, 90, 128])     # 赤色のHSV閾値下限
upper_red = np.array([180, 255, 255])  # 赤色のHSV閾値上限

def object_detect(image):
    hsv_img = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)      # BGRからHSVへの変換
    mask_red = cv2.inRange(hsv_img, lower_red, upper_red) # 二値化処理

    contours, hierarchy = cv2.findContours(mask_red, cv2.RETR_LIST, cv2.CHAIN_APPROX_NONE) # 輪郭検出

    for cnt in contours:                                  # 小さなノイズを除去
        if cnt.shape[0] < 150:
            continue

        (x, y, w, h) = cv2.boundingRect(cnt)              # 枠の座標とサイズを取得
        cv2.drawContours(image, [cnt], -1, (0, 255, 0), 2)# 輪郭を描画
        cv2.circle(image, (int(x+w/2), int(y+h/2)), 5, (0, 255, 0), -1) # 中心点を描画

    cv2.imshow("object", image)                           # OpenCVで表示
    cv2.waitKey(0)
    cv2.destroyAllWindows()

def main(args=None):                                      # メイン関数
    rclpy.init(args=args)                                 # ROS2初期化
    node = Node("node_object")                            # ノード作成
    node.get_logger().info("ROS2ノード例:画像内リンゴの検出")

    image = cv2.imread('/home/hcx/dev_ws/src/ros2_21_tutorials/learning_node/learning_node/apple.jpg')  # 画像読み込み
    object_detect(image)                                   # 物体検出
    rclpy.spin(node)                                       # ROS2終了まで待機
    node.destroy_node()                                    # ノード破棄
    rclpy.shutdown()                                       # ROS2終了

ビルド設定ファイルsetup.pyにエントリポイントを追加します:

entry_points={
    'console_scripts': [
     'node_helloworld       = learning_node.node_helloworld:main',
     'node_helloworld_class = learning_node.node_helloworld_class:main',
     'node_object           = learning_node.node_object:main',
    ],

ケース4:リアルタイム画像認識ノード

カメラからの映像を読み取り、リアルタイムで物体を認識します。

別の端末を開き、以下のコマンドを実行します:

$ ros2 run learning_node node_object_webcam # カメラ設定が必要

仮想マシンでの実行時は、以下の設定が必要です:

  1. USB 3.1互換の設定
  2. カメラを仮想マシンに接続

成功すると、カメラが起動し、赤い物体をリアルタイムで認識できます。

コード解説

前回の静止画処理とは違い、ここではOpenCVのVideoCapture()を使ってカメラから連続的にフレームを取得し、それを処理します。
learning_node/node_object_webcam.py

import rclpy                            # ROS2 Pythonインターフェースライブラリ
from rclpy.node import Node             # ROS2ノードクラス

import cv2                              # OpenCV画像処理ライブラリ
import numpy as np                      # NumPy数値計算ライブラリ

lower_red = np.array([0, 90, 128])     # 赤色のHSV閾値下限
upper_red = np.array([180, 255, 255])  # 赤色のHSV閾値上限

def object_detect(image):
    hsv_img = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)       # BGRからHSVへ変換
    mask_red = cv2.inRange(hsv_img, lower_red, upper_red)  # 二値化

    contours, hierarchy = cv2.findContours(mask_red, cv2.RETR_LIST, cv2.CHAIN_APPROX_NONE) # 輪郭検出

    for cnt in contours:                                   # ノイズを除外
        if cnt.shape[0] < 150:
            continue

        (x, y, w, h) = cv2.boundingRect(cnt)               # 枠の座標とサイズを取得
        cv2.drawContours(image, [cnt], -1, (0, 255, 0), 2) # 輪郭描画
        cv2.circle(image, (int(x+w/2), int(y+h/2)), 5, (0, 255, 0), -1)    # 中心点描画

    cv2.imshow("object", image)                            # 表示
    cv2.waitKey(50)

def main(args=None):                                       # メイン関数
    rclpy.init(args=args)                                  # ROS2初期化
    node = Node("node_object_webcam")                      # ノード作成
    node.get_logger().info("ROS2ノード例:カメラ映像から物体を検出")

    cap = cv2.VideoCapture(0)   # カメラ初期化

    while rclpy.ok():
        ret, image = cap.read()          # 1フレーム取得
        if ret == True:
            object_detect(image)         # 物体検出

    node.destroy_node()                  # ノード破棄
    rclpy.shutdown()                     # ROS2終了

ビルド設定ファイルsetup.pyにエントリポイントを追加します:

entry_points={
    'console_scripts': [
     'node_helloworld       = learning_node.node_helloworld:main',
     'node_helloworld_class = learning_node.node_helloworld_class:main',
     'node_object           = learning_node.node_object:main',
     'node_object_webcam    = learning_node.node_object_webcam:main',
    ],

OpenCVのHSVカラーモデルについて

HSVカラーモデルの各要素は以下の通りです:

  • 色相(Hue, H):角度で表され、0°〜360°の範囲。赤が0°、緑が120°、青が240°。
  • 飽和度(Saturation, S):0.0〜1.0の範囲。
  • 明度(Value, V):0.0(黒)〜1.0(白)の範囲。

タグ: ros2 ノード Python OpenCV カメラ

5月15日 07:39 投稿