Ultralytics YOLO27:

ROS(ロボット・オペレーティング・システム)クイックスタートガイド#

このガイドでは、Ultralytics YOLOをROS1(rospy)またはROS2(rclpy)と統合し、RGB画像、深度画像、点群に対してリアルタイムの物体検出セグメンテーションを実行する方法を説明します。

まずYOLOとROSのセットアップに進み、次にRGB画像深度画像、または点群を扱います。

ROSとは何ですか?#

ロボット・オペレーティング・システム(ROS)は、ロボット工学の研究や産業で広く使用されているオープンソースのフレームワークです。ROSは、開発者がロボットアプリケーションを作成するためのライブラリとツールを提供します。ROSはさまざまなロボットプラットフォームで動作するように設計されており、ロボット開発者にとって柔軟で強力なツールです。簡単な紹介については、Open Roboticsによる3分間のROS Introduction動画をご覧ください。

ROSの主な機能#

  1. モジュラーアーキテクチャ: ROSはモジュラーアーキテクチャを採用しており、ノードと呼ばれる小さく再利用可能なコンポーネントを組み合わせて、複雑なシステムを構築できます。各ノードは通常、特定の機能を実行し、ノード同士はトピックまたはサービスを介してメッセージを送受信します。

  2. 通信ミドルウェア: ROSは、プロセス間通信と分散コンピューティングをサポートする堅牢な通信基盤を提供します。これは、データストリーム(トピック)向けのパブリッシュ・サブスクライブモデルと、サービス呼び出し向けのリクエスト・リプライモデルによって実現されます。

  3. ハードウェア抽象化: ROSはハードウェア上に抽象化レイヤーを提供し、開発者がデバイスに依存しないコードを記述できるようにします。これにより、異なるハードウェア構成で同じコードを使用でき、統合や実験が容易になります。

  4. ツールとユーティリティ: ROSには、可視化、デバッグ、シミュレーション向けの豊富なツールとユーティリティが付属しています。たとえば、RVizはセンサーデータやロボット状態情報の可視化に使用され、Gazeboはアルゴリズムやロボット設計をテストするための強力なシミュレーション環境を提供します。

  5. 広範なエコシステム: ROSのエコシステムは広大で継続的に成長しており、ナビゲーション、マニピュレーション、知覚など、さまざまなロボットアプリケーション向けの多数のパッケージが利用できます。コミュニティはこれらのパッケージの開発と保守に積極的に貢献しています。

ROSバージョンの進化

2007年の開発開始以来、ROSは複数のバージョンを経て進化し、ROS 1とROS 2に分かれています。以下の既存の例ではROS1 Noeticを使用しています。ROS2の使用にある簡潔なアダプターでは、現在のROS2リリースに対応するrclpyインターフェースを示しています。

ROS 1とROS 2の比較#

ROS 1がロボット開発の確かな基盤を提供した一方で、ROS 2は次の機能を提供してその欠点に対応しています。

  • リアルタイム性能: リアルタイムシステムと決定論的な動作のサポートが向上しています。
  • セキュリティ: さまざまな環境で安全かつ信頼性の高い運用を実現する、強化されたセキュリティ機能を備えています。
  • スケーラビリティ: マルチロボットシステムと大規模なデプロイメントのサポートが向上しています。
  • クロスプラットフォームサポート: Linuxに加えて、WindowsやmacOSなど、さまざまなオペレーティングシステムとの互換性が拡張されています。
  • 柔軟な通信: より柔軟で効率的なプロセス間通信を実現するDDSを使用します。

ROSのメッセージとトピック#

ROSでは、ノード間の通信はメッセージトピックを介して行われます。メッセージはノード間で交換される情報を定義するデータ構造であり、トピックはメッセージを送受信する名前付きのチャネルです。ノードはトピックにメッセージをパブリッシュしたり、トピックのメッセージをサブスクライブしたりできるため、相互に通信できます。このパブリッシュ・サブスクライブモデルにより、ノード間の非同期通信と疎結合が可能になります。ロボットシステムの各センサーまたはアクチュエーターは通常、トピックにデータをパブリッシュし、他のノードが処理や制御に利用できるようにします。このガイドでは、Image、Depth、PointCloudメッセージとカメラトピックに焦点を当てます。

Ultralytics YOLOとROSのセットアップ#

ROS1の例は、ROSbot ROSリポジトリのフォークであるこのROS環境を使用してテストしました。ROS2でも同じYOLOとNumPyの処理を適用できますが、ノードのライフサイクルとメッセージ変換のみが異なります。

Husarion ROSbot 2 PRO autonomous robot platform

依存関係のインストール#

ROS環境に加えて、次の依存関係をインストールする必要があります。

  • ROS NumPyパッケージ: ROS ImageメッセージとNumPy配列を高速に変換するために必要です。

    pip install ros_numpy
  • Ultralyticsパッケージ:

    pip install ultralytics

ROS2の使用#

ROS2では、rospyrclpyに置き換わり、画像変換のros_numpycv_bridgeに置き換わります。次のノードは、以下に示すRGB検出フローに対応するROS2の完全な実装です。モデルは一度だけインスタンス化し、コールバック間で再利用します。

import cv_bridge
import rclpy
from rclpy.node import Node
from rclpy.qos import qos_profile_sensor_data
from sensor_msgs.msg import Image

from ultralytics import YOLO

class UltralyticsNode(Node):
    """Run YOLO detection on ROS2 image messages."""

    def __init__(self):
        """Initialize the ROS2 node, model, and image interfaces."""
        super().__init__("ultralytics")
        self.bridge = cv_bridge.CvBridge()
        self.model = YOLO("yolo26m.pt")
        self.publisher = self.create_publisher(Image, "/ultralytics/detection/image", 5)
        self.create_subscription(Image, "/camera/color/image_raw", self.callback, qos_profile_sensor_data)

    def callback(self, message):
        """Publish the annotated camera frame."""
        image = self.bridge.imgmsg_to_cv2(message, desired_encoding="bgr8")
        annotated = self.model(image)[0].plot(show=False)
        self.publisher.publish(self.bridge.cv2_to_imgmsg(annotated, encoding="bgr8"))

def main(args=None):
    """Start the ROS2 node."""
    rclpy.init(args=args)
    node = UltralyticsNode()
    rclpy.spin(node)
    node.destroy_node()
    rclpy.shutdown()

if __name__ == "__main__":
    main()

深度画像では、以下の深度処理コードを再利用し、メッセージの取得と変換のみを置き換えます。

self.create_subscription(Image, "/camera/color/image_raw", self.rgb_callback, qos_profile_sensor_data)
self.create_subscription(Image, "/camera/depth/image_raw", self.depth_callback, qos_profile_sensor_data)

def rgb_callback(self, message):
    self.rgb_image = self.bridge.imgmsg_to_cv2(message, desired_encoding="bgr8")

def depth_callback(self, message):
    depth_image = self.bridge.imgmsg_to_cv2(message, desired_encoding="passthrough")
    # Apply the NumPy mask and distance calculation from the depth example below.

点群では、ROS2がsensor_msgs_py.point_cloud2を提供します。整理済み点群を一度変換し、以下のNumPyによるセグメンテーションと3Dマッピングを再利用します。

from sensor_msgs_py import point_cloud2

points = point_cloud2.read_points_numpy(message, field_names=("x", "y", "z", "rgb"))
points = points.reshape(message.height, message.width, 4)

ROSでUltralyticsを使用する sensor_msgs/Image#

sensor_msgs/Imageメッセージ型は、画像データを表現するためにROSで一般的に使用されます。エンコーディング、高さ、幅、ピクセルデータのフィールドを含むため、カメラやその他のセンサーで取得した画像の送信に適しています。Imageメッセージは、視覚認識、物体検出、ナビゲーションなどのロボットアプリケーションで広く使用されています。

Detection and Segmentation in ROS Gazebo

Imageのステップごとの使用方法#

次のコードスニペットでは、Ultralytics YOLOパッケージをROSで使用する方法を示します。この例では、カメラトピックをサブスクライブし、受信した画像をYOLOで処理して、検出したオブジェクトを検出セグメンテーション用の新しいトピックにパブリッシュします。

まず、必要なライブラリをインポートし、2つのモデルをインスタンス化します。1つはセグメンテーション用、もう1つは検出用です。ROS masterとの通信を有効にするため、ノード名ultralyticsでROSノードを初期化します。安定した接続を確保するために短い待機時間を設け、処理を続行する前にノードが接続を確立するための十分な時間を確保します。

import time

import rospy

from ultralytics import YOLO

detection_model = YOLO("yolo26m.pt")
segmentation_model = YOLO("yolo26m-seg.pt")
rospy.init_node("ultralytics")
time.sleep(1)

2つのROSトピックを初期化します。1つは検出用、もう1つはセグメンテーション用です。これらのトピックに注釈付き画像をパブリッシュし、後続の処理で利用できるようにします。ノード間の通信にはsensor_msgs/Imageメッセージを使用します。

from sensor_msgs.msg import Image

det_image_pub = rospy.Publisher("/ultralytics/detection/image", Image, queue_size=5)
seg_image_pub = rospy.Publisher("/ultralytics/segmentation/image", Image, queue_size=5)

最後に、/camera/color/image_rawトピックのメッセージを待ち受け、新しいメッセージごとにコールバック関数を呼び出すサブスクライバーを作成します。このコールバック関数はsensor_msgs/Image型のメッセージを受け取り、ros_numpyを使用してNumPy配列に変換し、先ほどインスタンス化したYOLOモデルで画像を処理して注釈を付けた後、検出用の/ultralytics/detection/imageトピックとセグメンテーション用の/ultralytics/segmentation/imageトピックにパブリッシュします。

import ros_numpy

def callback(data):
    """Callback function to process image and publish annotated images."""
    array = ros_numpy.numpify(data)
    if det_image_pub.get_num_connections():
        det_result = detection_model(array)
        det_annotated = det_result[0].plot(show=False)
        det_image_pub.publish(ros_numpy.msgify(Image, det_annotated, encoding="rgb8"))

    if seg_image_pub.get_num_connections():
        seg_result = segmentation_model(array)
        seg_annotated = seg_result[0].plot(show=False)
        seg_image_pub.publish(ros_numpy.msgify(Image, seg_annotated, encoding="rgb8"))

rospy.Subscriber("/camera/color/image_raw", Image, callback)

while True:
    rospy.spin()
完全なコード
import time

import ros_numpy
import rospy
from sensor_msgs.msg import Image

from ultralytics import YOLO

detection_model = YOLO("yolo26m.pt")
segmentation_model = YOLO("yolo26m-seg.pt")
rospy.init_node("ultralytics")
time.sleep(1)

det_image_pub = rospy.Publisher("/ultralytics/detection/image", Image, queue_size=5)
seg_image_pub = rospy.Publisher("/ultralytics/segmentation/image", Image, queue_size=5)

def callback(data):
    """Callback function to process image and publish annotated images."""
    array = ros_numpy.numpify(data)
    if det_image_pub.get_num_connections():
        det_result = detection_model(array)
        det_annotated = det_result[0].plot(show=False)
        det_image_pub.publish(ros_numpy.msgify(Image, det_annotated, encoding="rgb8"))

    if seg_image_pub.get_num_connections():
        seg_result = segmentation_model(array)
        seg_annotated = seg_result[0].plot(show=False)
        seg_image_pub.publish(ros_numpy.msgify(Image, seg_annotated, encoding="rgb8"))

rospy.Subscriber("/camera/color/image_raw", Image, callback)

while True:
    rospy.spin()
デバッグ

ROS(ロボット・オペレーティング・システム)ノードのデバッグは、システムが分散型であるため困難な場合があります。次のようなツールが役立ちます。

  1. rostopic echo <TOPIC-NAME> : このコマンドを使用すると、特定のトピックにパブリッシュされたメッセージを表示でき、データフローを確認できます。
  2. rostopic list: このコマンドを使用すると、ROSシステムで利用可能なすべてのトピックを一覧表示でき、アクティブなデータストリームの概要を把握できます。
  3. rqt_graph: この可視化ツールはノード間の通信グラフを表示し、ノード同士の接続方法や相互作用を把握できます。
  4. 3D表現など、より複雑な可視化にはRVizを使用できます。RViz(ROS可視化)はROS向けの強力な3D可視化ツールです。ロボットと環境の状態をリアルタイムで可視化できます。RVizでは、センサーデータ(例: sensor_msgs/Image)、ロボットモデルの状態、その他さまざまな情報を表示できるため、ロボットシステムの動作のデバッグや理解が容易になります。

std_msgs/Stringで検出クラスをパブリッシュする#

標準のROSメッセージにはstd_msgs/Stringメッセージも含まれています。多くのアプリケーションでは、注釈付き画像全体を再パブリッシュする必要はなく、ロボットの視野内に存在するクラスだけが必要です。次の例では、std_msgs/Stringメッセージを使用して、検出したクラスを/ultralytics/detection/classesトピックに再パブリッシュする方法を示します。これらのメッセージはより軽量で、必要な情報を提供するため、さまざまなアプリケーションで有用です。

使用例#

カメラと物体検出モデルを搭載した倉庫ロボットを考えます。大きな注釈付き画像をネットワーク経由で送信する代わりに、ロボットは検出したクラスのリストをstd_msgs/Stringメッセージとしてパブリッシュできます。たとえば、ロボットが「box」、「pallet」、「forklift」などのオブジェクトを検出すると、これらのクラスを/ultralytics/detection/classesトピックにパブリッシュします。この情報は、中央監視システムによる在庫のリアルタイム追跡、障害物を回避するためのロボットの経路計画の最適化、検出したboxのピックアップなど特定のアクションのトリガーに使用できます。この方法により、通信に必要な帯域幅を削減し、重要なデータの送信に集中できます。

Stringのステップごとの使用方法#

この例では、Ultralytics YOLOパッケージをROSで使用する方法を示します。この例では、カメラトピックをサブスクライブし、受信した画像をYOLOで処理して、std_msgs/Stringメッセージを使用して検出したオブジェクトを新しい/ultralytics/detection/classesトピックにパブリッシュします。ros_numpyパッケージを使用してROS ImageメッセージをNumPy配列に変換し、YOLOで処理します。

import time

import ros_numpy
import rospy
from sensor_msgs.msg import Image
from std_msgs.msg import String

from ultralytics import YOLO

detection_model = YOLO("yolo26m.pt")
rospy.init_node("ultralytics")
time.sleep(1)
classes_pub = rospy.Publisher("/ultralytics/detection/classes", String, queue_size=5)

def callback(data):
    """Callback function to process image and publish detected classes."""
    array = ros_numpy.numpify(data)
    if classes_pub.get_num_connections():
        det_result = detection_model(array)
        classes = det_result[0].boxes.cls.cpu().numpy().astype(int)
        names = [det_result[0].names[i] for i in classes]
        classes_pub.publish(String(data=str(names)))

rospy.Subscriber("/camera/color/image_raw", Image, callback)
while True:
    rospy.spin()

UltralyticsをROSの深度画像で使用する#

RGB画像に加えて、ROSは深度画像もサポートしています。深度画像は、カメラからオブジェクトまでの距離に関する情報を提供します。深度画像は、障害物回避、3Dマッピング、自己位置推定などのロボットアプリケーションに不可欠です。

深度画像は、各ピクセルがカメラからオブジェクトまでの距離を表す画像です。色を取得するRGB画像とは異なり、深度画像は空間情報を取得するため、ロボットが環境の3D構造を認識できるようになります。

深度画像の取得

深度画像は、さまざまなセンサーを使用して取得できます。

  1. ステレオカメラ: 2台のカメラを使用し、画像の視差に基づいて深度を計算します。
  2. Time-of-Flight(ToF)カメラ: 光がオブジェクトから戻るまでの時間を測定します。
  3. 構造化光センサー: パターンを投影し、表面上での変形を測定します。

深度画像でYOLOを使用する#

ROSでは、深度画像はsensor_msgs/Imageメッセージ型で表現されます。この型には、エンコーディング、高さ、幅、ピクセルデータのフィールドが含まれます。深度画像のエンコーディングフィールドでは、「16UC1」のような形式がよく使用されます。これは、1ピクセルあたり16ビットの符号なし整数を示し、各値がオブジェクトまでの距離を表します。深度画像は通常、RGB画像と組み合わせて使用し、環境をより包括的に把握します。

YOLOを使用すると、RGB画像と深度画像の両方から情報を抽出して組み合わせることができます。たとえば、YOLOでRGB画像内のオブジェクトを検出し、その検出結果を使用して深度画像内の対応する領域を特定できます。これにより、検出したオブジェクトの正確な深度情報を抽出でき、ロボットが環境を3次元で理解する能力が向上します。

RGB-Dカメラ

深度画像を扱う場合は、RGB画像と深度画像が正しく位置合わせされていることを確認する必要があります。Intel RealSenseシリーズなどのRGB-Dカメラは、同期したRGB画像と深度画像を提供するため、両方のソースの情報を簡単に組み合わせられます。RGBカメラと深度カメラを別々に使用する場合は、正確に位置合わせできるよう、両方をキャリブレーションすることが重要です。

深度画像のステップごとの使用方法#

この例では、YOLOを使用して画像をセグメンテーションし、抽出したマスクを深度画像に適用してオブジェクトをセグメンテーションします。これにより、対象オブジェクトの各ピクセルとカメラの焦点中心との距離を特定できます。この距離情報を取得することで、カメラとシーン内の特定のオブジェクトとの距離を計算できます。まず、必要なライブラリをインポートし、ROSノードを作成して、セグメンテーションモデルとROSトピックをインスタンス化します。

import time

import rospy
from std_msgs.msg import String

from ultralytics import YOLO

rospy.init_node("ultralytics")
time.sleep(1)

segmentation_model = YOLO("yolo26m-seg.pt")

classes_pub = rospy.Publisher("/ultralytics/detection/distance", String, queue_size=5)

次に、受信した深度画像メッセージを処理するコールバック関数を定義します。この関数は深度画像とRGB画像のメッセージを待機し、それらをNumPy配列に変換して、RGB画像にセグメンテーションモデルを適用します。その後、検出した各オブジェクトのセグメンテーションマスクを抽出し、深度画像を使用してカメラからオブジェクトまでの平均距離を計算します。ほとんどのセンサーには最大距離があり、クリップ距離と呼ばれます。この距離を超える値はinf(np.inf)として表されます。処理前にこれらのnull値を除外し、0の値を割り当てることが重要です。最後に、検出したオブジェクトとその平均距離を/ultralytics/detection/distanceトピックにパブリッシュします。

import numpy as np
import ros_numpy
from sensor_msgs.msg import Image

def callback(data):
    """Callback function to process depth image and RGB image."""
    image = rospy.wait_for_message("/camera/color/image_raw", Image)
    image = ros_numpy.numpify(image)
    depth = ros_numpy.numpify(data)
    result = segmentation_model(image)

    all_objects = []
    for index, cls in enumerate(result[0].boxes.cls):
        class_index = int(cls.cpu().numpy())
        name = result[0].names[class_index]
        mask = result[0].masks.data.cpu().numpy()[index, :, :].astype(int)
        obj = depth[mask == 1]
        obj = obj[~np.isnan(obj)]
        avg_distance = np.mean(obj) if len(obj) else np.inf
        all_objects.append(f"{name}: {avg_distance:.2f}m")

    classes_pub.publish(String(data=str(all_objects)))

rospy.Subscriber("/camera/depth/image_raw", Image, callback)

while True:
    rospy.spin()
完全なコード
import time

import numpy as np
import ros_numpy
import rospy
from sensor_msgs.msg import Image
from std_msgs.msg import String

from ultralytics import YOLO

rospy.init_node("ultralytics")
time.sleep(1)

segmentation_model = YOLO("yolo26m-seg.pt")

classes_pub = rospy.Publisher("/ultralytics/detection/distance", String, queue_size=5)

def callback(data):
    """Callback function to process depth image and RGB image."""
    image = rospy.wait_for_message("/camera/color/image_raw", Image)
    image = ros_numpy.numpify(image)
    depth = ros_numpy.numpify(data)
    result = segmentation_model(image)

    all_objects = []
    for index, cls in enumerate(result[0].boxes.cls):
        class_index = int(cls.cpu().numpy())
        name = result[0].names[class_index]
        mask = result[0].masks.data.cpu().numpy()[index, :, :].astype(int)
        obj = depth[mask == 1]
        obj = obj[~np.isnan(obj)]
        avg_distance = np.mean(obj) if len(obj) else np.inf
        all_objects.append(f"{name}: {avg_distance:.2f}m")

    classes_pub.publish(String(data=str(all_objects)))

rospy.Subscriber("/camera/depth/image_raw", Image, callback)

while True:
    rospy.spin()

ROSでUltralyticsを使用する sensor_msgs/PointCloud2#

Detection and Segmentation in ROS Gazebo

sensor_msgs/PointCloud2メッセージ型は、ROSで3D点群データを表現するために使用されるデータ構造です。このメッセージ型はロボットアプリケーションに不可欠であり、3Dマッピング、物体認識、自己位置推定などのタスクを可能にします。

点群は、3次元座標系内で定義されたデータ点の集合です。これらのデータ点は、3Dスキャン技術によって取得されたオブジェクトまたはシーンの外表面を表します。点群内の各点には、空間内の位置に対応するXYZ座標があり、色や強度などの追加情報が含まれる場合もあります。

参照フレーム

sensor_msgs/PointCloud2を扱う際は、点群データを取得したセンサーの参照フレームを考慮することが重要です。点群は最初にセンサーの参照フレームで取得されます。この参照フレームは、/tf_staticトピックをリッスンすることで特定できます。ただし、アプリケーションの要件によっては、点群を別の参照フレームに変換する必要があります。この変換は、座標フレームの管理とフレーム間のデータ変換のためのツールを提供するtf2_rosパッケージで実行できます。

点群の取得

点群は、さまざまなセンサーを使用して取得できます。

  1. LIDAR(光検出と測距): レーザーパルスを使用してオブジェクトまでの距離を測定し、高-精度の3Dマップを作成します。
  2. 深度カメラ: 各ピクセルの深度情報を取得し、シーンの3D再構成を可能にします。
  3. ステレオカメラ: 2台以上のカメラを使用し、三角測量によって深度情報を取得します。
  4. 構造化光スキャナー: 既知のパターンを表面に投影し、変形を測定して深度を計算します。

点群でYOLOを使用する#

YOLOをsensor_msgs/PointCloud2型のメッセージと統合するには、深度マップで使用した方法に似た手法を利用できます。点群に含まれる色情報を利用して2D画像を抽出し、その画像をYOLOでセグメンテーションして、得られたマスクを3次元点に適用することで、対象の3Dオブジェクトを分離できます。

点群の処理には、使いやすいPythonライブラリであるOpen3D(pip install open3d)の使用をお勧めします。Open3Dは、点群データ構造の管理、可視化、複雑な操作のシームレスな実行に対応する堅牢なツールを提供します。このライブラリによって処理を大幅に簡略化し、YOLOベースのセグメンテーションと連携して点群を操作・分析する能力を高められます。

点群のステップごとの使用方法#

必要なライブラリをインポートし、セグメンテーション用のYOLOモデルをインスタンス化します。

import time

import rospy

from ultralytics import YOLO

rospy.init_node("ultralytics")
time.sleep(1)
segmentation_model = YOLO("yolo26m-seg.pt")

pointcloud2_to_array関数を作成し、sensor_msgs/PointCloud2メッセージを2つのNumPy配列に変換します。sensor_msgs/PointCloud2メッセージには、取得した画像のwidthheightに基づくn個の点が含まれます。たとえば、480 x 640画像には307,200個の点があります。各点には3つの空間座標(xyz)と、RGB形式の対応する色が含まれます。これらは2つの独立した情報チャネルとみなすことができます。

この関数は、元のカメラ解像度(width x height)の形式でxyz座標とRGB値を返します。ほとんどのセンサーには最大距離があり、クリップ距離と呼ばれます。この距離を超える値はinf(np.inf)として表されます。処理前にこれらのnull値を除外し、0の値を割り当てることが重要です。

import numpy as np
import ros_numpy

def pointcloud2_to_array(pointcloud2: PointCloud2) -> tuple:
    """Convert a ROS PointCloud2 message to a numpy array.

    Args:
        pointcloud2 (PointCloud2): the PointCloud2 message

    Returns:
        (tuple): tuple containing (xyz, rgb)
    """
    pc_array = ros_numpy.point_cloud2.pointcloud2_to_array(pointcloud2)
    split = ros_numpy.point_cloud2.split_rgb_field(pc_array)
    rgb = np.stack([split["b"], split["g"], split["r"]], axis=2)
    xyz = ros_numpy.point_cloud2.get_xyz_points(pc_array, remove_nans=False)
    xyz = np.array(xyz).reshape((pointcloud2.height, pointcloud2.width, 3))
    nan_rows = np.isnan(xyz).all(axis=2)
    xyz[nan_rows] = [0, 0, 0]
    rgb[nan_rows] = [0, 0, 0]
    return xyz, rgb

次に、/camera/depth/pointsトピックをサブスクライブして点群メッセージを受信し、sensor_msgs/PointCloud2メッセージをpointcloud2_to_array関数で変換して、XYZ座標とRGB値を含むNumPy配列にします。YOLOモデルでRGB画像を処理し、セグメンテーションされたオブジェクトを抽出します。検出した各オブジェクトについてセグメンテーションマスクを抽出し、RGB画像とXYZ座標の両方に適用して、3D空間内のオブジェクトを分離します。

マスクはバイナリ値で構成されているため、処理は簡単です。1はオブジェクトの存在を示し、0は不在を示します。マスクを適用するには、元の各チャネルにマスクを単純に乗算します。この操作によって、画像内の対象オブジェクトを効果的に分離できます。最後に、Open3Dの点群オブジェクトを作成し、対応する色とともにセグメンテーションされたオブジェクトを3D空間で可視化します。

import sys

import open3d as o3d

ros_cloud = rospy.wait_for_message("/camera/depth/points", PointCloud2)
xyz, rgb = pointcloud2_to_array(ros_cloud)
result = segmentation_model(rgb)

if not len(result[0].boxes.cls):
    print("No objects detected")
    sys.exit()

classes = result[0].boxes.cls.cpu().numpy().astype(int)
for index, class_id in enumerate(classes):
    mask = result[0].masks.data.cpu().numpy()[index, :, :].astype(int)
    mask_expanded = np.stack([mask, mask, mask], axis=2)

    obj_rgb = rgb * mask_expanded
    obj_xyz = xyz * mask_expanded

    pcd = o3d.geometry.PointCloud()
    pcd.points = o3d.utility.Vector3dVector(obj_xyz.reshape((ros_cloud.height * ros_cloud.width, 3)))
    pcd.colors = o3d.utility.Vector3dVector(obj_rgb.reshape((ros_cloud.height * ros_cloud.width, 3)) / 255)
    o3d.visualization.draw_geometries([pcd])
完全なコード
import sys
import time

import numpy as np
import open3d as o3d
import ros_numpy
import rospy
from sensor_msgs.msg import PointCloud2

from ultralytics import YOLO

rospy.init_node("ultralytics")
time.sleep(1)
segmentation_model = YOLO("yolo26m-seg.pt")

def pointcloud2_to_array(pointcloud2: PointCloud2) -> tuple:
    """Convert a ROS PointCloud2 message to a numpy array.

    Args:
        pointcloud2 (PointCloud2): the PointCloud2 message

    Returns:
        (tuple): tuple containing (xyz, rgb)
    """
    pc_array = ros_numpy.point_cloud2.pointcloud2_to_array(pointcloud2)
    split = ros_numpy.point_cloud2.split_rgb_field(pc_array)
    rgb = np.stack([split["b"], split["g"], split["r"]], axis=2)
    xyz = ros_numpy.point_cloud2.get_xyz_points(pc_array, remove_nans=False)
    xyz = np.array(xyz).reshape((pointcloud2.height, pointcloud2.width, 3))
    nan_rows = np.isnan(xyz).all(axis=2)
    xyz[nan_rows] = [0, 0, 0]
    rgb[nan_rows] = [0, 0, 0]
    return xyz, rgb

ros_cloud = rospy.wait_for_message("/camera/depth/points", PointCloud2)
xyz, rgb = pointcloud2_to_array(ros_cloud)
result = segmentation_model(rgb)

if not len(result[0].boxes.cls):
    print("No objects detected")
    sys.exit()

classes = result[0].boxes.cls.cpu().numpy().astype(int)
for index, class_id in enumerate(classes):
    mask = result[0].masks.data.cpu().numpy()[index, :, :].astype(int)
    mask_expanded = np.stack([mask, mask, mask], axis=2)

    obj_rgb = rgb * mask_expanded
    obj_xyz = xyz * mask_expanded

    pcd = o3d.geometry.PointCloud()
    pcd.points = o3d.utility.Vector3dVector(obj_xyz.reshape((ros_cloud.height * ros_cloud.width, 3)))
    pcd.colors = o3d.utility.Vector3dVector(obj_rgb.reshape((ros_cloud.height * ros_cloud.width, 3)) / 255)
    o3d.visualization.draw_geometries([pcd])

Point Cloud Segmentation with Ultralytics

まとめ#

Ultralytics YOLOをROSに統合すると、ロボットはRGB画像、深度画像、点群に対して物体検出セグメンテーションを実行し、生のセンサーストリームを実用的な知覚情報に変換できます。ここからPredictモードで推論オプションを確認するか、コンピュータービジョンプロジェクトの手順に従ってロボットアプリケーションをプロトタイプから本番環境へ進めてください。

FAQ#

  • ロボット・オペレーティング・システム(ROS)は、開発者が堅牢なロボットアプリケーションを作成するためにロボット工学で広く使用されているオープンソースのフレームワークです。ロボットシステムの構築とインターフェース接続のためのライブラリとツールを提供し、複雑なアプリケーションを容易に開発できます。ROSは、トピックまたはサービスを介したメッセージによるノード間通信をサポートします。

  • Ultralytics YOLOとROSの統合では、ROS環境をセットアップし、YOLOを使用してセンサーデータを処理します。まず、ros_numpyやUltralytics YOLOなど、必要な依存関係をインストールします。

    pip install ros_numpy ultralytics

    次に、ROSノードを作成し、画像トピックをサブスクライブして受信データを物体検出用に処理します。以下に最小限の例を示します。

    import ros_numpy
    import rospy
    from sensor_msgs.msg import Image
    
    from ultralytics import YOLO
    
    detection_model = YOLO("yolo26m.pt")
    rospy.init_node("ultralytics")
    det_image_pub = rospy.Publisher("/ultralytics/detection/image", Image, queue_size=5)
    
    def callback(data):
        array = ros_numpy.numpify(data)
        det_result = detection_model(array)
        det_annotated = det_result[0].plot(show=False)
        det_image_pub.publish(ros_numpy.msgify(Image, det_annotated, encoding="rgb8"))
    
    rospy.Subscriber("/camera/color/image_raw", Image, callback)
    rospy.spin()
  • ROSトピックは、パブリッシュ・サブスクライブモデルを使用してROSネットワーク内のノード間通信を実現します。トピックは、ノードがメッセージを非同期に送受信するための名前付きチャネルです。Ultralytics YOLOでは、ノードで画像トピックをサブスクライブし、YOLOで画像を処理して検出セグメンテーションなどのタスクを実行し、結果を新しいトピックにパブリッシュできます。

    たとえば、カメラトピックをサブスクライブし、受信した画像を検出用に処理します。

    rospy.Subscriber("/camera/color/image_raw", Image, callback)
  • sensor_msgs/Imageで表現されるROSの深度画像は、カメラからオブジェクトまでの距離を提供します。これは、障害物回避、3Dマッピング、自己位置推定などのタスクに不可欠です。RGB画像と深度情報を使用することで、ロボットは3D環境をより適切に理解できます。

    YOLOを使用すると、RGB画像からセグメンテーションマスクを抽出し、それらを深度画像に適用して正確な3Dオブジェクト情報を取得できます。これにより、ロボットが周囲を移動し、周囲とインタラクションする能力が向上します。

  • YOLOを使用してROSで3D点群を可視化するには、次の手順を実行します。

    1. sensor_msgs/PointCloud2メッセージをNumPy配列に変換します。
    2. YOLOを使用してRGB画像をセグメント化します。
    3. セグメンテーションマスクを点群に適用します。

    可視化にOpen3Dを使用する例を示します。

    import sys
    
    import numpy as np
    import open3d as o3d
    import ros_numpy
    import rospy
    from sensor_msgs.msg import PointCloud2
    
    from ultralytics import YOLO
    
    rospy.init_node("ultralytics")
    segmentation_model = YOLO("yolo26m-seg.pt")
    
    def pointcloud2_to_array(pointcloud2):
        pc_array = ros_numpy.point_cloud2.pointcloud2_to_array(pointcloud2)
        split = ros_numpy.point_cloud2.split_rgb_field(pc_array)
        rgb = np.stack([split["b"], split["g"], split["r"]], axis=2)
        xyz = ros_numpy.point_cloud2.get_xyz_points(pc_array, remove_nans=False)
        xyz = np.array(xyz).reshape((pointcloud2.height, pointcloud2.width, 3))
        return xyz, rgb
    
    ros_cloud = rospy.wait_for_message("/camera/depth/points", PointCloud2)
    xyz, rgb = pointcloud2_to_array(ros_cloud)
    result = segmentation_model(rgb)
    
    if not len(result[0].boxes.cls):
        print("No objects detected")
        sys.exit()
    
    classes = result[0].boxes.cls.cpu().numpy().astype(int)
    for index, class_id in enumerate(classes):
        mask = result[0].masks.data.cpu().numpy()[index, :, :].astype(int)
        mask_expanded = np.stack([mask, mask, mask], axis=2)
    
        obj_rgb = rgb * mask_expanded
        obj_xyz = xyz * mask_expanded
    
        pcd = o3d.geometry.PointCloud()
        pcd.points = o3d.utility.Vector3dVector(obj_xyz.reshape((-1, 3)))
        pcd.colors = o3d.utility.Vector3dVector(obj_rgb.reshape((-1, 3)) / 255)
        o3d.visualization.draw_geometries([pcd])

    このアプローチでは、セグメント化されたオブジェクトを3Dで可視化できるため、ロボティクスアプリケーションにおけるナビゲーションやマニピュレーションなどのタスクに役立ちます。

コメント