Ultralytics YOLO27:

Hướng dẫn bắt đầu nhanh với ROS (Hệ điều hành robot)#

Hướng dẫn này chỉ cho bạn cách tích hợp Ultralytics YOLO với ROS1 (rospy) hoặc ROS2 (rclpy) để thực hiện phát hiện đối tượngphân đoạn theo thời gian thực trên hình ảnh RGB, hình ảnh độ sâu và đám mây điểm.

Chuyển đến phần thiết lập YOLO với ROS, sau đó làm việc với hình ảnh RGB, hình ảnh độ sâu hoặc đám mây điểm.

ROS là gì?#

Hệ điều hành robot (ROS) là một framework nguồn mở được sử dụng rộng rãi trong nghiên cứu và công nghiệp robot. ROS cung cấp một tập hợp thư viện và công cụ giúp developer tạo các ứng dụng robot. ROS được thiết kế để hoạt động với nhiều nền tảng robot, trở thành một công cụ linh hoạt và mạnh mẽ cho các chuyên gia robot. Để xem phần giới thiệu ngắn, hãy xem video Giới thiệu về ROS dài ba phút của Open Robotics.

Các tính năng chính của ROS#

  1. Kiến trúc mô-đun: ROS có kiến trúc mô-đun, cho phép developer xây dựng các hệ thống phức tạp bằng cách kết hợp những thành phần nhỏ hơn, có thể tái sử dụng, được gọi là node. Mỗi node thường thực hiện một chức năng cụ thể và các node giao tiếp với nhau bằng message thông qua topic hoặc service.

  2. Middleware giao tiếp: ROS cung cấp hạ tầng giao tiếp mạnh mẽ, hỗ trợ giao tiếp liên tiến trình và tính toán phân tán. Điều này được thực hiện thông qua mô hình publish-subscribe cho các luồng dữ liệu (topic) và mô hình request-reply cho các lệnh gọi service.

  3. Trừu tượng hóa phần cứng: ROS cung cấp một lớp trừu tượng trên phần cứng, cho phép developer viết code không phụ thuộc thiết bị. Nhờ đó, cùng một code có thể được sử dụng với các cấu hình phần cứng khác nhau, giúp việc tích hợp và thử nghiệm trở nên dễ dàng hơn.

  4. Công cụ và tiện ích: ROS đi kèm một bộ công cụ và tiện ích phong phú để trực quan hóa, debug và mô phỏng. Ví dụ, RViz được dùng để trực quan hóa dữ liệu cảm biến và thông tin trạng thái robot, trong khi Gazebo cung cấp môi trường mô phỏng mạnh mẽ để kiểm thử các thuật toán và thiết kế robot.

  5. Hệ sinh thái phong phú: Hệ sinh thái ROS rất lớn và liên tục phát triển, với nhiều package dành cho các ứng dụng robot khác nhau, bao gồm điều hướng, thao tác, nhận thức và nhiều lĩnh vực khác. Cộng đồng tích cực đóng góp vào việc phát triển và bảo trì các package này.

Quá trình phát triển các phiên bản ROS

Kể từ khi được phát triển vào năm 2007, ROS đã tiến hóa qua nhiều phiên bản, được chia thành ROS 1 và ROS 2. Các ví dụ hiện có bên dưới sử dụng ROS1 Noetic; các adapter ngắn gọn trong Sử dụng ROS2 minh họa các interface rclpy tương ứng cho những bản phát hành ROS2 hiện tại.

ROS 1 và ROS 2#

Mặc dù ROS 1 cung cấp nền tảng vững chắc cho việc phát triển robot, ROS 2 khắc phục các hạn chế của ROS 1 bằng cách cung cấp:

  • Hiệu năng thời gian thực: Cải thiện khả năng hỗ trợ các hệ thống thời gian thực và hành vi xác định.
  • Bảo mật: Các tính năng bảo mật nâng cao để vận hành an toàn và đáng tin cậy trong nhiều môi trường.
  • Khả năng mở rộng: Hỗ trợ tốt hơn cho các hệ thống đa robot và triển khai quy mô lớn.
  • Hỗ trợ đa nền tảng: Mở rộng khả năng tương thích với nhiều hệ điều hành khác ngoài Linux, bao gồm Windows và macOS.
  • Giao tiếp linh hoạt: Sử dụng DDS để giao tiếp liên tiến trình linh hoạt và hiệu quả hơn.

Message và topic trong ROS#

Trong ROS, giao tiếp giữa các node được thực hiện thông qua messagetopic. Message là một cấu trúc dữ liệu xác định thông tin được trao đổi giữa các node, còn topic là một kênh có tên để gửi và nhận message. Các node có thể publish message lên một topic hoặc subscribe message từ một topic, cho phép chúng giao tiếp với nhau. Mô hình publish-subscribe này cho phép giao tiếp bất đồng bộ và tách rời giữa các node. Mỗi cảm biến hoặc actuator trong một hệ thống robot thường publish dữ liệu lên một topic, sau đó các node khác có thể sử dụng dữ liệu này để xử lý hoặc điều khiển. Trong phạm vi hướng dẫn này, chúng ta sẽ tập trung vào message Image, Depth và PointCloud, cùng các topic camera.

Thiết lập Ultralytics YOLO với ROS#

Các ví dụ ROS1 được kiểm thử bằng môi trường ROS này, một fork của repository ROS của ROSbot. Quy trình xử lý YOLO và NumPy tương tự trong ROS2; chỉ vòng đời node và việc chuyển đổi message là khác nhau.

Husarion ROSbot 2 PRO autonomous robot platform

Cài đặt các dependency#

Ngoài môi trường ROS, bạn cần cài đặt các dependency sau:

  • Package ROS NumPy: Package này cần thiết để chuyển đổi nhanh giữa message ROS Image và array NumPy.

    pip install ros_numpy
  • Package Ultralytics:

    pip install ultralytics

Sử dụng ROS2#

ROS2 thay rospy bằng rclpy và thay đổi việc chuyển đổi hình ảnh ros_numpy bằng cv_bridge. Node sau đây là phiên bản ROS2 tương đương hoàn chỉnh của luồng phát hiện RGB bên dưới; hãy khởi tạo model một lần và tái sử dụng chúng giữa các callback.

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()

Đối với hình ảnh độ sâu, hãy tái sử dụng code xử lý độ sâu bên dưới và chỉ thay thế phần nhận và chuyển đổi message:

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.

Đối với đám mây điểm, ROS2 cung cấp sensor_msgs_py.point_cloud2; hãy chuyển đổi đám mây có tổ chức một lần, sau đó tái sử dụng phần phân đoạn NumPy và ánh xạ 3D bên dưới:

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)

Sử dụng Ultralytics với ROS sensor_msgs/Image#

Loại message sensor_msgs/Image thường được sử dụng trong ROS để biểu diễn dữ liệu hình ảnh. Message này chứa các trường về encoding, chiều cao, chiều rộng và dữ liệu pixel, phù hợp để truyền hình ảnh được camera hoặc các cảm biến khác ghi lại. Message Image được sử dụng rộng rãi trong các ứng dụng robot cho những tác vụ như nhận thức hình ảnh, phát hiện đối tượng và điều hướng.

Detection and Segmentation in ROS Gazebo

Cách sử dụng Image từng bước#

Đoạn code sau minh họa cách sử dụng package Ultralytics YOLO với ROS. Trong ví dụ này, chúng ta subscribe một topic camera, xử lý hình ảnh nhận được bằng YOLO và publish các đối tượng được phát hiện lên những topic mới để phát hiệnphân đoạn.

Trước tiên, import các thư viện cần thiết và khởi tạo hai model: một model cho phân đoạn và một model cho phát hiện. Khởi tạo một node ROS (với tên ultralytics) để cho phép giao tiếp với ROS master. Để đảm bảo kết nối ổn định, chúng ta thêm một khoảng dừng ngắn, giúp node có đủ thời gian thiết lập kết nối trước khi tiếp tục.

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)

Khởi tạo hai topic ROS: một topic cho phát hiện và một topic cho phân đoạn. Các topic này sẽ được dùng để publish những hình ảnh đã được chú thích, giúp chúng có thể được truy cập để xử lý tiếp. Giao tiếp giữa các node được thực hiện bằng message 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)

Cuối cùng, tạo một subscriber lắng nghe message trên topic /camera/color/image_raw và gọi một hàm callback cho mỗi message mới. Hàm callback này nhận các message kiểu sensor_msgs/Image, chuyển đổi chúng thành array NumPy bằng ros_numpy, xử lý hình ảnh với các model YOLO đã khởi tạo, chú thích hình ảnh, rồi publish chúng trở lại các topic tương ứng: /ultralytics/detection/image cho phát hiện và /ultralytics/segmentation/image cho phân đoạn.

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()
Code hoàn chỉnh
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()
Debug

Debug các node ROS (Hệ điều hành robot) có thể khó khăn do tính chất phân tán của hệ thống. Một số công cụ có thể hỗ trợ quá trình này:

  1. rostopic echo <TOPIC-NAME> : Lệnh này cho phép bạn xem các message được publish trên một topic cụ thể, giúp kiểm tra luồng dữ liệu.
  2. rostopic list: Sử dụng lệnh này để liệt kê tất cả topic hiện có trong hệ thống ROS, cung cấp tổng quan về các luồng dữ liệu đang hoạt động.
  3. rqt_graph: Công cụ trực quan hóa này hiển thị đồ thị giao tiếp giữa các node, cung cấp thông tin chi tiết về cách các node được kết nối và tương tác với nhau.
  4. Để có các hình ảnh trực quan phức tạp hơn, chẳng hạn như biểu diễn 3D, bạn có thể sử dụng RViz. RViz (Trực quan hóa ROS) là một công cụ trực quan hóa 3D mạnh mẽ cho ROS. Công cụ này cho phép bạn trực quan hóa trạng thái của robot và môi trường xung quanh theo thời gian thực. Với RViz, bạn có thể xem dữ liệu cảm biến (ví dụ: sensor_msgs/Image), trạng thái model robot và nhiều loại thông tin khác, giúp việc debug và tìm hiểu hành vi của hệ thống robot dễ dàng hơn.

Publish các class được phát hiện bằng std_msgs/String#

Các message ROS tiêu chuẩn cũng bao gồm message std_msgs/String. Trong nhiều ứng dụng, không cần thiết phải publish lại toàn bộ hình ảnh đã chú thích; thay vào đó, chỉ cần các class xuất hiện trong tầm nhìn của robot. Ví dụ sau minh họa cách sử dụng message std_msgs/String để publish lại các class được phát hiện lên topic /ultralytics/detection/classes. Những message này nhẹ hơn và cung cấp thông tin thiết yếu, khiến chúng có giá trị trong nhiều ứng dụng.

Trường hợp sử dụng mẫu#

Hãy xem xét một robot kho hàng được trang bị camera và model phát hiện đối tượng. Thay vì gửi các hình ảnh lớn đã chú thích qua mạng, robot có thể publish danh sách các class được phát hiện dưới dạng message std_msgs/String. Ví dụ, khi robot phát hiện các đối tượng như "box", "pallet" và "forklift", robot publish các class này lên topic /ultralytics/detection/classes. Hệ thống giám sát trung tâm sau đó có thể sử dụng thông tin này để theo dõi hàng tồn kho theo thời gian thực, tối ưu hóa việc lập kế hoạch đường đi của robot nhằm tránh chướng ngại vật hoặc kích hoạt các hành động cụ thể như nhặt một box được phát hiện. Phương pháp này giảm băng thông cần thiết cho giao tiếp và tập trung vào việc truyền dữ liệu quan trọng.

Cách sử dụng String từng bước#

Ví dụ này minh họa cách sử dụng package Ultralytics YOLO với ROS. Trong ví dụ này, chúng ta subscribe một topic camera, xử lý hình ảnh nhận được bằng YOLO và publish các đối tượng được phát hiện lên topic mới /ultralytics/detection/classes bằng message std_msgs/String. Package ros_numpy được dùng để chuyển đổi message ROS Image thành array NumPy nhằm xử lý bằng 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()

Sử dụng Ultralytics với hình ảnh độ sâu trong ROS#

Ngoài hình ảnh RGB, ROS còn hỗ trợ hình ảnh độ sâu, cung cấp thông tin về khoảng cách từ camera đến các đối tượng. Hình ảnh độ sâu rất quan trọng đối với các ứng dụng robot như tránh chướng ngại vật, lập bản đồ 3D và định vị.

Hình ảnh độ sâu là hình ảnh trong đó mỗi pixel biểu diễn khoảng cách từ camera đến một đối tượng. Không giống hình ảnh RGB ghi lại màu sắc, hình ảnh độ sâu ghi lại thông tin không gian, cho phép robot nhận biết cấu trúc 3D của môi trường xung quanh.

Thu nhận hình ảnh độ sâu

Có thể thu nhận hình ảnh độ sâu bằng nhiều loại cảm biến:

  1. Camera stereo: Sử dụng hai camera để tính độ sâu dựa trên độ lệch hình ảnh.
  2. Camera Time-of-Flight (ToF): Đo thời gian ánh sáng quay trở lại từ một đối tượng.
  3. Cảm biến ánh sáng có cấu trúc: Chiếu một mẫu và đo biến dạng của mẫu trên các bề mặt.

Sử dụng YOLO với hình ảnh độ sâu#

Trong ROS, hình ảnh độ sâu được biểu diễn bằng loại message sensor_msgs/Image, bao gồm các trường về encoding, chiều cao, chiều rộng và dữ liệu pixel. Trường encoding của hình ảnh độ sâu thường sử dụng định dạng như "16UC1", cho biết mỗi pixel là một số nguyên không dấu 16-bit, trong đó mỗi giá trị biểu diễn khoảng cách đến đối tượng. Hình ảnh độ sâu thường được sử dụng kết hợp với hình ảnh RGB để cung cấp góc nhìn toàn diện hơn về môi trường.

Với YOLO, có thể trích xuất và kết hợp thông tin từ cả hình ảnh RGB lẫn hình ảnh độ sâu. Ví dụ, YOLO có thể phát hiện các đối tượng trong hình ảnh RGB, sau đó sử dụng kết quả phát hiện này để xác định các vùng tương ứng trong hình ảnh độ sâu. Nhờ đó, có thể trích xuất thông tin độ sâu chính xác của các đối tượng được phát hiện, nâng cao khả năng hiểu môi trường trong không gian ba chiều của robot.

Camera RGB-D

Khi làm việc với hình ảnh độ sâu, điều cần thiết là đảm bảo hình ảnh RGB và hình ảnh độ sâu được căn chỉnh chính xác. Các camera RGB-D, chẳng hạn dòng Intel RealSense, cung cấp hình ảnh RGB và độ sâu được đồng bộ hóa, giúp kết hợp thông tin từ cả hai nguồn dễ dàng hơn. Nếu sử dụng các camera RGB và độ sâu riêng biệt, việc hiệu chuẩn chúng để đảm bảo căn chỉnh chính xác là rất quan trọng.

Cách sử dụng Depth từng bước#

Trong ví dụ này, chúng ta sử dụng YOLO để phân đoạn một hình ảnh và áp dụng mask đã trích xuất nhằm phân đoạn đối tượng trong hình ảnh độ sâu. Điều này cho phép xác định khoảng cách từ mỗi pixel của đối tượng cần quan tâm đến tâm tiêu cự của camera. Bằng cách thu được thông tin khoảng cách này, chúng ta có thể tính khoảng cách giữa camera và đối tượng cụ thể trong cảnh. Bắt đầu bằng cách import các thư viện cần thiết, tạo một node ROS và khởi tạo một model phân đoạn cùng một topic 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)

Tiếp theo, định nghĩa một hàm callback để xử lý message hình ảnh độ sâu nhận được. Hàm này chờ message hình ảnh độ sâu và hình ảnh RGB, chuyển đổi chúng thành các array NumPy rồi áp dụng model phân đoạn cho hình ảnh RGB. Sau đó, hàm trích xuất mask phân đoạn cho từng đối tượng được phát hiện và tính khoảng cách trung bình từ đối tượng đến camera bằng hình ảnh độ sâu. Hầu hết cảm biến đều có khoảng cách tối đa, được gọi là khoảng cách cắt, vượt quá giá trị này thì các giá trị được biểu diễn là inf (np.inf). Trước khi xử lý, điều quan trọng là phải lọc các giá trị null này và gán cho chúng giá trị 0. Cuối cùng, publish các đối tượng được phát hiện cùng khoảng cách trung bình của chúng lên topic /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()
Code hoàn chỉnh
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()

Sử dụng Ultralytics với ROS sensor_msgs/PointCloud2#

Detection and Segmentation in ROS Gazebo

Loại message sensor_msgs/PointCloud2 là một cấu trúc dữ liệu được sử dụng trong ROS để biểu diễn dữ liệu đám mây điểm 3D. Loại message này đóng vai trò quan trọng trong các ứng dụng robot, cho phép thực hiện các tác vụ như lập bản đồ 3D, nhận dạng đối tượng và định vị.

Đám mây điểm là tập hợp các điểm dữ liệu được xác định trong hệ tọa độ ba chiều. Các điểm dữ liệu này biểu diễn bề mặt bên ngoài của một đối tượng hoặc cảnh, được thu nhận thông qua các công nghệ quét 3D. Mỗi điểm trong đám mây có các tọa độ X, YZ, tương ứng với vị trí của điểm trong không gian, đồng thời có thể bao gồm thông tin bổ sung như màu sắc và cường độ.

Hệ quy chiếu

Khi làm việc với sensor_msgs/PointCloud2, điều cần thiết là xem xét hệ quy chiếu của cảm biến nơi thu nhận dữ liệu đám mây điểm. Đám mây điểm ban đầu được ghi lại trong hệ quy chiếu của cảm biến. Bạn có thể xác định hệ quy chiếu này bằng cách lắng nghe topic /tf_static. Tuy nhiên, tùy theo yêu cầu cụ thể của ứng dụng, bạn có thể cần chuyển đổi đám mây điểm sang một hệ quy chiếu khác. Việc biến đổi này có thể được thực hiện bằng package tf2_ros, cung cấp các công cụ để quản lý các hệ tọa độ và biến đổi dữ liệu giữa chúng.

Thu nhận đám mây điểm

Có thể thu nhận đám mây điểm bằng nhiều loại cảm biến:

  1. LIDAR (Phát hiện và đo khoảng cách bằng ánh sáng): Sử dụng các xung laser để đo khoảng cách đến đối tượng và tạo bản đồ 3D có độ chính xác cao.
  2. Camera độ sâu: Ghi lại thông tin độ sâu cho từng pixel, cho phép tái dựng cảnh 3D.
  3. Camera stereo: Sử dụng hai hoặc nhiều camera để thu nhận thông tin độ sâu thông qua phép đo tam giác.
  4. Máy quét ánh sáng có cấu trúc: Chiếu một mẫu đã biết lên bề mặt và đo biến dạng để tính độ sâu.

Sử dụng YOLO với đám mây điểm#

Để tích hợp YOLO với các message kiểu sensor_msgs/PointCloud2, chúng ta có thể sử dụng phương pháp tương tự phương pháp dùng cho bản đồ độ sâu. Bằng cách tận dụng thông tin màu được tích hợp trong đám mây điểm, chúng ta có thể trích xuất một hình ảnh 2D, thực hiện phân đoạn trên hình ảnh này bằng YOLO, sau đó áp dụng mask thu được lên các điểm ba chiều để cô lập đối tượng 3D cần quan tâm.

Để xử lý đám mây điểm, chúng tôi khuyến nghị sử dụng Open3D (pip install open3d), một thư viện Python dễ sử dụng. Open3D cung cấp các công cụ mạnh mẽ để quản lý cấu trúc dữ liệu đám mây điểm, trực quan hóa chúng và thực hiện liền mạch các thao tác phức tạp. Thư viện này có thể đơn giản hóa đáng kể quy trình, đồng thời nâng cao khả năng thao tác và phân tích đám mây điểm kết hợp với phân đoạn dựa trên YOLO.

Cách sử dụng Point Cloud từng bước#

Import các thư viện cần thiết và khởi tạo model YOLO cho tác vụ phân đoạn.

import time

import rospy

from ultralytics import YOLO

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

Tạo một hàm pointcloud2_to_array để biến đổi message sensor_msgs/PointCloud2 thành hai array NumPy. Các message sensor_msgs/PointCloud2 chứa n điểm dựa trên widthheight của hình ảnh đã thu nhận. Ví dụ, một hình ảnh 480 x 640 sẽ có 307,200 điểm. Mỗi điểm bao gồm ba tọa độ không gian (xyz) và màu tương ứng ở định dạng RGB. Có thể xem đây là hai kênh thông tin riêng biệt.

Hàm trả về các tọa độ xyz và các giá trị RGB theo độ phân giải camera ban đầu (width x height). Hầu hết cảm biến đều có khoảng cách tối đa, được gọi là khoảng cách cắt, vượt quá giá trị này thì các giá trị được biểu diễn là inf (np.inf). Trước khi xử lý, điều quan trọng là phải lọc các giá trị null này và gán cho chúng giá trị 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

Tiếp theo, subscribe topic /camera/depth/points để nhận message đám mây điểm và chuyển đổi message sensor_msgs/PointCloud2 thành các array NumPy chứa tọa độ XYZ và giá trị RGB (bằng hàm pointcloud2_to_array). Xử lý hình ảnh RGB bằng model YOLO để trích xuất các đối tượng đã phân đoạn. Với mỗi đối tượng được phát hiện, trích xuất mask phân đoạn và áp dụng mask đó lên cả hình ảnh RGB lẫn tọa độ XYZ để cô lập đối tượng trong không gian 3D.

Xử lý mask rất đơn giản vì mask chỉ gồm các giá trị nhị phân, trong đó 1 biểu thị sự hiện diện của đối tượng và 0 biểu thị sự vắng mặt. Để áp dụng mask, chỉ cần nhân các kênh ban đầu với mask. Thao tác này cô lập hiệu quả đối tượng cần quan tâm trong hình ảnh. Cuối cùng, tạo một object đám mây điểm Open3D và trực quan hóa đối tượng đã phân đoạn trong không gian 3D cùng các màu tương ứng.

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])
Code hoàn chỉnh
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

Kết luận#

Khi tích hợp Ultralytics YOLO vào ROS, robot của bạn có thể thực hiện phát hiện đối tượngphân đoạn trên hình ảnh RGB, hình ảnh độ sâu và đám mây điểm, biến các luồng cảm biến thô thành nhận thức có thể hành động. Từ đây, hãy khám phá chế độ Predict để có thêm tùy chọn inference hoặc làm theo các bước của một dự án thị giác máy tính để đưa ứng dụng robot của bạn từ prototype đến production.

FAQ#

  • Hệ điều hành robot (ROS) là một framework nguồn mở thường được sử dụng trong lĩnh vực robot để giúp developer tạo các ứng dụng robot mạnh mẽ. Framework này cung cấp một tập hợp thư viện và công cụ để xây dựng và kết nối với các hệ thống robot, giúp phát triển các ứng dụng phức tạp dễ dàng hơn. ROS hỗ trợ giao tiếp giữa các node bằng message thông qua topic hoặc service.

  • Tích hợp Ultralytics YOLO với ROS bao gồm việc thiết lập môi trường ROS và sử dụng YOLO để xử lý dữ liệu cảm biến. Bắt đầu bằng cách cài đặt các dependency bắt buộc như ros_numpy và Ultralytics YOLO:

    pip install ros_numpy ultralytics

    Tiếp theo, tạo một node ROS và subscribe một topic hình ảnh để xử lý dữ liệu nhận được cho tác vụ phát hiện đối tượng. Dưới đây là một ví dụ tối giản:

    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()
  • Topic ROS hỗ trợ giao tiếp giữa các node trong mạng ROS bằng mô hình publish-subscribe. Topic là một kênh có tên mà các node sử dụng để gửi và nhận message bất đồng bộ. Trong ngữ cảnh Ultralytics YOLO, bạn có thể cho một node subscribe một topic hình ảnh, xử lý hình ảnh bằng YOLO cho các tác vụ như phát hiện hoặc phân đoạn, rồi publish kết quả lên các topic mới.

    Ví dụ, subscribe một topic camera và xử lý hình ảnh nhận được cho tác vụ phát hiện:

    rospy.Subscriber("/camera/color/image_raw", Image, callback)
  • Hình ảnh độ sâu trong ROS, được biểu diễn bằng sensor_msgs/Image, cung cấp khoảng cách từ camera đến các đối tượng, rất quan trọng cho những tác vụ như tránh chướng ngại vật, lập bản đồ 3D và định vị. Bằng cách sử dụng thông tin độ sâu cùng với hình ảnh RGB, robot có thể hiểu rõ hơn môi trường 3D xung quanh.

    Với YOLO, bạn có thể trích xuất mask phân đoạn từ hình ảnh RGB và áp dụng các mask này lên hình ảnh độ sâu để thu được thông tin 3D chính xác về đối tượng, cải thiện khả năng điều hướng và tương tác với môi trường xung quanh của robot.

  • Để trực quan hóa các đám mây điểm 3D trong ROS bằng YOLO:

    1. Chuyển đổi các message sensor_msgs/PointCloud2 thành các mảng NumPy.
    2. Sử dụng YOLO để phân đoạn ảnh RGB.
    3. Áp dụng mặt nạ phân đoạn cho đám mây điểm.

    Dưới đây là một ví dụ sử dụng Open3D để trực quan hóa:

    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])

    Phương pháp này cung cấp hình ảnh trực quan 3D của các đối tượng đã phân đoạn, hữu ích cho các tác vụ như điều hướng và thao tác trong các ứng dụng robot.

Bình luận