YOLO Vision 2026:

Hướng dẫn bắt đầu nhanh với ROS (Robot Operating System)#

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) để chạy tính năng phát hiện đối tượngphân đoạn thời gian thực trên ảnh RGB, ảnh chiều 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 ảnh RGB, ảnh chiều sâu hoặc đám mây điểm.

ROS Introduction (captioned) from Open Robotics on Vimeo.

ROS là gì?#

Robot Operating System (ROS) là một framework mã 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 tập hợp các thư viện và công cụ giúp lập trình viên 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 nhà phát triển robot.

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 lập trình viên xây dựng hệ thống phức tạp bằng cách kết hợp các thành phần nhỏ hơn, có thể tái sử dụng gọi là các 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 thông điệp qua các topic hoặc các service.

  2. Middleware giao tiếp: ROS cung cấp cơ sở hạ tầng giao tiếp mạnh mẽ hỗ trợ giao tiếp giữa các tiến trình (inter-process communication) và tính toán phân tán. Điều này đạt được thông qua mô hình publish-subscribe cho các luồng dữ liệu (topics) và mô hình request-reply cho các lệnh gọi dịch vụ (service calls).

  3. Trừu tượng hóa phần cứng (Hardware Abstraction): ROS cung cấp một lớp trừu tượng hóa phần cứng, cho phép các nhà phát triển viết mã không phụ thuộc vào thiết bị. Điều này cho phép cùng một đoạn mã được sử dụng với các cấu hình phần cứng khác nhau, tạo điều kiện tích hợp và thử nghiệm dễ dàng hơn.

  4. Các công cụ và tiện ích: ROS đi kèm với một bộ sưu tập phong phú các công cụ và tiện ích để trực quan hóa, gỡ lỗi và mô phỏng. Ví dụ, RViz được sử 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ột môi trường mô phỏng mạnh mẽ để kiểm tra các thuật toán và thiết kế robot.

  5. Hệ sinh thái mở rộng: Hệ sinh thái ROS rất rộng lớn và không ngừng phát triển, với vô số gói (packages) có sẵn cho các ứng dụng robot khác nhau, bao gồm điều hướng, điều khiển (manipulation), nhận thức, v.v. Cộng đồng đóng góp tích cực vào việc phát triển và duy trì các gói này.

Sự phát triển của 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, tách thành ROS 1 và ROS 2. Các ví dụ hiện có dưới đây sử dụng ROS1 Noetic; các bộ điều hợp gọn nhẹ trong Sử dụng ROS2 hiển thị các giao diện tương ứng rclpy cho các phiên bản ROS2 hiện tại.

ROS 1 so với ROS 2#

Mặc dù ROS 1 đã cung cấp một nền tảng vững chắc cho sự phát triển robot, ROS 2 giải quyết những thiếu sót của nó bằng cách cung cấp:

  • Hiệu suất thời gian thực: Hỗ trợ cải thiện cho các hệ thống thời gian thực và hành vi tất đị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 các môi trường khác nhau.
  • Khả năng mở rộng: Hỗ trợ tốt hơn cho các hệ thống đa robot và các 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 nhau ngoài Linux, bao gồm Windows và macOS.
  • Giao tiếp linh hoạt: Sử dụng DDS để giao tiếp giữa các tiến trình linh hoạt và hiệu quả hơn.

Tin nhắn và Topics trong ROS#

Trong ROS, việc giao tiếp giữa các node được hỗ trợ thông qua các thông điệpcác topic. Thông điệp là cấu trúc dữ liệu định nghĩa thông tin được trao đổi giữa các node, trong khi topic là một kênh có tên mà qua đó thông điệp được gửi và nhận. Các node có thể xuất bản thông điệp lên một topic hoặc đăng ký nhận thông điệp 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à sự tách rời giữa các node. Mỗi cảm biến hoặc thiết bị truyền động trong hệ thống robot thường xuất bản dữ liệu lên một topic, dữ liệu này sau đó có thể được các node khác tiêu thụ để xử lý hoặc điều khiển. Đối với hướng dẫn này, chúng ta sẽ tập trung vào các thông điệp Image, Depth, PointCloud và các topic camera.

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

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

Husarion ROSbot 2 PRO autonomous robot platform

Cài đặt các phụ thuộc (Dependencies)#

Ngoài môi trường ROS, bạn sẽ cần cài đặt các phụ thuộc sau:

  • Gói ROS NumPy: Gói này bắt buộc để chuyển đổi nhanh giữa các thông điệp Image của ROS và mảng NumPy.

    pip install ros_numpy
  • Gói Ultralytics:

    pip install ultralytics

Sử dụng ROS2#

ROS2 thay thế rospy bằng rclpy và quá trình chuyển đổi ảnh ros_numpy bằng cv_bridge. Node sau đây là bản tương đương hoàn chỉnh trên ROS2 của luồng phát hiện RGB bên dưới; khởi tạo các model một lần và tái sử dụng chúng qua 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 chiều sâu, hãy tái sử dụng mã xử lý chiều sâu bên dưới và chỉ thay thế việc thu thập và chuyển đổi thông báo:

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; chuyển đổi đám mây có tổ chức một lần, sau đó tái sử dụng quá trình 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#

Kiểu message type sensor_msgs/Image thường được sử dụng trong ROS để biểu diễn dữ liệu hình ảnh. Nó chứa các trường mã hóa, chiều cao, chiều rộng và dữ liệu pixel, giúp nó phù hợp để truyền tải hình ảnh được chụp bởi camera hoặc các cảm biến khác. Các thông điệp hình ảnh được sử dụng rộng rãi trong các ứng dụng robot cho các tác vụ như nhận thị giác, object detection và điều hướng.

Detection and Segmentation in ROS Gazebo

Cách sử dụng hình ảnh từng bước#

Đoạn mã sau minh họa cách sử dụng gói Ultralytics YOLO với ROS. Trong ví dụ này, chúng ta đăng ký một topic camera, xử lý hình ảnh đầu vào bằng YOLO và xuất bản các đối tượng được phát hiện lên các topic mới để phát hiệnphân đoạn.

Đầu tiên, hãy nhập các thư viện cần thiết và khởi tạo hai model: một cho phân đoạn và một cho phát hiện. Khởi tạo một node ROS (với tên ultralytics) để bật khả năng giao tiếp với ROS master. Để đảm bảo kết nối ổn định, chúng ta bao gồm một khoảng dừng ngắn, cung cấp cho node đủ 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 cho phát hiện và một cho phân đoạn. Các topic này sẽ được sử dụng để xuất bản các hình ảnh đã chú thích, giúp chúng có thể truy cập được cho các bước xử lý tiếp theo. Giao tiếp giữa các node được hỗ trợ bằng cách sử dụng các thông điệp 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 bộ đăng ký lắng nghe các thông điệp trên topic /camera/color/image_raw và gọi hàm callback cho mỗi thông điệp mới. Hàm callback này nhận các thông điệp loại sensor_msgs/Image, chuyển đổi chúng thành mảng NumPy bằng cách sử dụng ros_numpy, xử lý hình ảnh với các model YOLO đã được khởi tạo trước đó, chú thích hình ảnh và sau đó xuất bản 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()
Mã 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()
Gỡ lỗi (Debugging)

Việc gỡ lỗi các node ROS (Robot Operating System) có thể rất 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 thông điệp được xuất bản trên một topic cụ thể, giúp bạn kiểm tra luồng dữ liệu.
  2. rostopic list: Sử dụng lệnh này để liệt kê tất cả các topic khả dụng trong hệ thống ROS, cung cấp cho bạn 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ị biểu đồ 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ới nhau và cách chúng tương tác.
  4. Đối với các hình dung 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 (ROS Visualization) là một công cụ trực quan hóa 3D mạnh mẽ dành cho ROS. Nó cho phép bạn trực quan hóa trạng thái của robot và môi trường xung quanh trong 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 mô hình robot và nhiều loại thông tin khác, giúp dễ dàng gỡ lỗi và hiểu hành vi của hệ thống robot.

Xuất bản các lớp được phát hiện với std_msgs/String#

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

Ví dụ về trường hợp sử dụng#

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 có chú thích qua mạng, robot có thể xuất bản danh sách các lớp được phát hiện dưới dạng thông điệp std_msgs/String. Ví dụ, khi robot phát hiện các vật thể như "box", "pallet" và "forklift", nó sẽ xuất bản các lớp này lên topic /ultralytics/detection/classes. Thông tin này sau đó có thể được sử dụng bởi hệ thống giám sát trung tâm để theo dõi hàng tồn kho trong thời gian thực, tối ưu hóa việc lập kế hoạch đường đi của robot để 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 chiếc hộp được phát hiện. Cách tiếp cận này làm giảm băng thông cần thiết cho việc giao tiếp và tập trung vào việc truyền dữ liệu quan trọng.

Cách sử dụng chuỗi (String) từng bước#

Ví dụ này minh họa cách sử dụng gói Ultralytics YOLO với ROS. Trong ví dụ này, chúng ta đăng ký một topic camera, xử lý hình ảnh đến bằng YOLO và xuất bản các đối tượng được phát hiện tới topic mới /ultralytics/detection/classes bằng cách sử dụng các thông điệp std_msgs/String. Gói ros_numpy được sử dụng để chuyển đổi thông điệp Image của ROS thành mảng NumPy để 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 chiều sâu (Depth Images) của ROS#

Ngoài ảnh RGB, ROS hỗ trợ ảnh chiều sâu, cung cấp thông tin về khoảng cách của các đối tượng từ camera. Ảnh chiều 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 chiều sâu là hình ảnh trong đó mỗi pixel đại diện cho khoảng cách từ máy ảnh đến một đối tượng. Khác với hình ảnh RGB thu màu sắc, hình ảnh chiều sâu thu thông tin không gian, cho phép robot nhận thức cấu trúc 3D của môi trường xung quanh.

Lấy hình ảnh chiều sâu

Hình ảnh chiều sâu có thể thu được bằng cách sử dụng các cảm biến khác nhau:

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

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

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

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

Máy ảnh RGB-D

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

Cách sử dụng chiều sâu từng bước#

Trong ví dụ này, chúng tôi sử dụng YOLO để phân đoạn một hình ảnh và áp dụng mask (mặt nạ) trích xuất được để phân đoạn đối tượng trong hình ảnh chiều sâu. Điều này cho phép chúng tôi xác định khoảng cách của từng pixel của đối tượng quan tâm so với tiêu điểm của máy ảnh. Bằng cách lấy thông tin khoảng cách này, chúng tôi có thể tính toán khoảng cách giữa máy ảnh và đối tượng cụ thể trong cảnh. Bắt đầu bằng việc nhập 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 với 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, xác định một hàm callback xử lý thông điệp ảnh chiều sâu đến. Hàm này chờ các thông điệp ảnh chiều sâu và ảnh RGB, chuyển đổi chúng thành các mảng NumPy và áp dụng model phân đoạn cho ảnh RGB. Sau đó, nó trích xuất mặt nạ phân đoạn cho mỗi đối tượng được phát hiện và tính toán khoảng cách trung bình của đối tượng từ camera bằng cách sử dụng ảnh chiều sâu. Hầu hết các cảm biến có khoảng cách tối đa, được gọi là khoảng cách cắt (clip distance), ngoài giới hạn này các giá trị được biểu diễn là inf (np.inf). Trước khi xử lý, việc lọc bỏ các giá trị null này và gán cho chúng một giá trị 0 là rất quan trọng. Cuối cùng, nó xuất bản các đối tượng được phát hiện cùng với khoảng cách trung bình của chúng tới 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()
Mã 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

Kiểu message type 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. Kiểu thông điệp này là một phần không thể thiếu đối với 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 một cảnh, được thu thập thông qua 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 nó trong không gian, và cũng có thể bao gồm thông tin bổ sung như màu sắc và cường độ.

Khung tham chiếu

Khi làm việc với sensor_msgs/PointCloud2, điều quan trọng là phải xem xét hệ quy chiếu của cảm biến mà từ đó dữ liệu đám mây điểm được thu thập. Đám mây điểm ban đầu được chụp 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 thuộc vào yêu cầu ứng dụng cụ thể của bạn, bạn có thể cần chuyển đổi đám mây điểm sang hệ quy chiếu khác. Phép biến đổi này có thể đạt được bằng cách sử dụng gói tf2_ros, cung cấp các công cụ để quản lý khung tọa độ và biến đổi dữ liệu giữa chúng.

Lấy đám mây điểm

Đám mây điểm có thể thu được bằng cách sử dụng các cảm biến khác nhau:

  1. LIDAR (Light Detection and Ranging): Sử dụng các xung laser để đo khoảng cách đến các đối tượng và tạo bản đồ 3D độ chính xác cao.
  2. Máy ảnh chiều sâu: Thu thông tin chiều sâu cho mỗi pixel, cho phép tái tạo 3D của cảnh.
  3. Máy ảnh Stereo: Sử dụng hai hoặc nhiều máy ảnh để lấy thông tin chiều sâu thông qua phép đạc tam giác (triangulation).
  4. Máy quét ánh sáng cấu trúc (Structured Light Scanners): Chiếu một mẫu hình đã biết lên bề mặt và đo sự biến dạng để tính toán chiều sâu.

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

Để tích hợp YOLO với các thông điệp loại sensor_msgs/PointCloud2, chúng ta có thể áp dụng phương pháp tương tự như phương pháp được sử dụng cho bản đồ chiều sâu. Bằng cách tận dụng thông tin màu sắc được nhúng 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, và sau đó áp dụng mặt nạ kết quả cho các điểm ba chiều để cô lập đối tượng 3D quan tâm.

Để xử lý đám mây điểm, chúng tôi khuyên 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 các thao tác phức tạp một cách mượt mà. Thư viện này có thể đơn giản hóa đáng kể quy trình và nâng cao khả năng thao tác cũng như phân tích đám mây điểm kết hợp với tính năng phân đoạn dựa trên YOLO.

Cách sử dụng đám mây điểm từng bước#

Nhập các thư viện cần thiết và khởi tạo model YOLO để 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, chuyển đổi thông điệp sensor_msgs/PointCloud2 thành hai mảng NumPy. Các thông điệp sensor_msgs/PointCloud2 chứa các điểm n dựa trên widthheight của hình ảnh thu được. Ví dụ, một hình ảnh 480 x 640 sẽ có các điểm 307,200. Mỗi điểm bao gồm ba tọa độ không gian (xyz) và màu sắc tương ứng theo định dạng RGB. Chúng có thể được coi 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 định dạng độ phân giải camera gốc (width x height). Hầu hết các cảm biến có khoảng cách tối đa, được gọi là khoảng cách cắt, ngoài giới hạn này các giá trị được biểu diễn là inf (np.inf). Trước khi xử lý, việc lọc bỏ các giá trị null này và gán cho chúng một giá trị 0 là rất quan trọng.

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, đăng ký topic /camera/depth/points để nhận thông điệp đám mây điểm và chuyển đổi thông điệp sensor_msgs/PointCloud2 thành các mảng NumPy chứa tọa độ XYZ và giá trị RGB (sử dụ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 được phân đoạn. Đối với mỗi đối tượng được phát hiện, trích xuất mặt nạ phân đoạn và áp dụng nó cho cả ảnh RGB và tọa độ XYZ để cô lập đối tượng trong không gian 3D.

Xử lý mặt nạ rất đơn giản vì nó bao gồm các giá trị nhị phân, với 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 mặt nạ, chỉ cần nhân các kênh gốc với mặt nạ. Thao tác này cô lập hiệu quả đối tượng quan tâm bên trong hình ảnh. Cuối cùng, tạo một đối tượng đám mây điểm Open3D và trực quan hóa đối tượng được phân đoạn trong không gian 3D với màu sắc liên quan.

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])
Mã 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#

Với Ultralytics YOLO được tích hợp vào ROS, robot của bạn có thể chạy tính năng phát hiện đối tượngphân đoạn trên ảnh RGB, ảnh chiều 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ế độ dự đoán (Predict mode) để có thêm các tùy chọn suy luận, 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ừ bản mẫu lên sản xuất.

Câu hỏi thường gặp#

Robot Operating System (ROS) là gì?#

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

Làm cách nào để tích hợp Ultralytics YOLO với ROS để phát hiện đối tượng thời gian thực?#

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 phụ thuộc 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à đăng ký một topic hình ảnh để xử lý dữ liệu đến cho tác vụ phát hiện đối tượng. Dưới đây là một ví dụ tối thiểu:

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 topics là gì và chúng được sử dụng như thế nào trong Ultralytics YOLO?#

Các topic ROS hỗ trợ giao tiếp giữa các node trong một mạng ROS bằng cách sử dụ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 thông điệp một cách bất đồng bộ. Trong bối cảnh của Ultralytics YOLO, bạn có thể tạo một node đăng ký 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, và xuất bản kết quả lên các topic mới.

Ví dụ, subscribe vào một topic máy ảnh và xử lý hình ảnh đầu vào để phát hiện:

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

Tại sao nên sử dụng hình ảnh chiều sâu với Ultralytics YOLO trong ROS?#

Ảnh chiều sâu trong ROS, được biểu diễn bởi sensor_msgs/Image, cung cấp khoảng cách của các đối tượng từ camera, rất quan trọng cho các 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 chiều sâu cùng với ảnh RGB, robot có thể hiểu rõ hơn về môi trường 3D của chúng.

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

Làm cách nào tôi có thể trực quan hóa đám mây điểm 3D với YOLO trong ROS?#

Để trực quan hóa đám mây điểm 3D trong ROS với YOLO:

  1. Chuyển đổi các thông điệp sensor_msgs/PointCloud2 thành các mảng NumPy.
  2. Sử dụng YOLO để phân đoạn hình ảnh RGB.
  3. Áp dụng mask phân đoạn vào đá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])

Cách tiếp cận này cung cấp khả năng trực quan hóa 3D các đối tượng được 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