Ultralytics YOLO27:
Get Started

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

Hướng dẫn này chỉ 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ượng và phân đoạn thời gian thực trên ảnh RGB, ả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 ảnh RGB, ảnh độ sâu hoặc đám mây điểm.

ROS là gì?#

Hệ điều hành Robot (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 một tập hợp thư viện và công cụ giúp nhà phát triển tạo ứng dụng robot. ROS được thiết kế để hoạt động với nhiều nền tảng robot, trở thành công cụ linh hoạt và mạnh mẽ dành cho các chuyên gia robot. Để có phần giới thiệu ngắn gọ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 nhà phát triể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à 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 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à điện toán phân tán. Hệ thống thực hiện điều này 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ời 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 nhà phát triển viết code không phụ thuộc thiết bị. Nhờ đó, cùng một code có thể được 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 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, gỡ lỗi 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, còn Gazebo cung cấp môi trường mô phỏng mạnh mẽ để kiểm thử thuật toán và thiết kế robot.

  5. Hệ sinh thái phong phú: Hệ sinh thái ROS rộng lớn và không ngừng 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.v. Cộng đồng tích cực đóng góp vào quá trình 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 đã trải qua nhiều phiên bản, chia thành ROS 1 và ROS 2. Các ví dụ hiện có bên dưới sử dụng ROS1 Noetic; những adapter gọn nhẹ trong phần Sử dụng ROS2 trình bày các giao diện rclpy tương ứng cho những bản ROS2 hiện hành.

ROS 1 và ROS 2#

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

  • Hiệu năng thời gian thực: Hỗ trợ tốt hơn cho các hệ thống thời gian thực và hoạt động có tính xác định.
  • Bảo mật: Các tính năng bảo mật được tăng cường để 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 hệ thống nhiều robot và triển khai quy mô lớn.
  • Hỗ trợ đa nền tảng: Tăng khả năng tương thích với nhiều hệ điều hành 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.

Thông điệp và topic trong ROS#

Trong ROS, việc giao tiếp giữa các node được thực hiện thông qua thông điệp và topic. Thông điệp là một cấu trúc dữ liệu xác định thông tin được trao đổi giữa các node, trong khi topic là kênh có tên để gửi và nhận thông điệp. Các node có thể xuất bản thông điệp lên topic hoặc đăng ký nhận thông điệp từ topic, nhờ đó giao tiếp với nhau. Mô hình xuất bản–đăng ký này cho phép giao tiếp bất đồng bộ và tách rời các node. Mỗi cảm biến hoặc bộ truyền động trong hệ thống robot thường xuất bản dữ liệu lên một topic để các node khác sử dụng cho việc xử lý hoặc điều khiển. Trong hướng dẫn này, chúng ta sẽ tập trung vào thông điệp 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 nhánh rẽ của repository ROSbot ROS. Quy trình xử lý YOLO và NumPy tương tự trong ROS2; chỉ vòng đời của node và việc chuyển đổi thông điệp 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:

  • Gói ROS NumPy: Gói này cần thiết để chuyển đổi nhanh giữa thông điệp ROS Image và mảng NumPy.

    pip install ros_numpy
  • Gói Ultralytics:

    pip install ultralytics

Sử dụng ROS2#

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

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")
    # Áp dụng mask NumPy và phép tính khoảng cách từ ví dụ xử lý độ sâu bên dưới.

Đối với point cloud, ROS2 cung cấp sensor_msgs_py.point_cloud2; hãy chuyển đổi point cloud có cấu trúc một lần, sau đó dùng lại 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 sensor_msgs/Image thông điệp thường được dùng trong ROS để biểu diễn dữ liệu ảnh. Loại này có các trường encoding, chiều cao, chiều rộng và dữ liệu pixel, phù hợp để truyền ảnh được chụp từ camera hoặc cảm biến khác. Thông điệp 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 thị giác, phát hiện đối tượng và điều hướng.

Detection and Segmentation in ROS Gazebo

Hướng dẫn từng bước sử dụng Image#

Đ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ý ảnh nhận được bằng YOLO và xuất bản các đối tượng được phát hiện lên những topic mới để phát hiện và phân đoạn.

Trước tiên, nhập các thư viện cần thiết và khởi tạo hai model: một model phân đoạn và một model phát hiện. Khởi tạo một node ROS (với tên ultralytics) để bật giao tiếp với ROS master. Để đảm bảo kết nối ổn định, chúng ta chờ một khoảng ngắn để 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 để phát hiện và một topic để phân đoạn. Các topic này sẽ được dùng để xuất bản ảnh đã được chú thích, giúp các quy trình xử lý tiếp theo có thể truy cập ảnh. Việc giao tiếp giữa các node được thực hiện bằng 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 subscriber lắng nghe 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 thông điệp thuộc loại sensor_msgs/Image, chuyển đổi thông điệp thành mảng NumPy bằng ros_numpy, xử lý ảnh bằng các model YOLO đã khởi tạo trước đó, chú thích ảnh, rồi xuất bản ảnh trở lại các topic tương ứng: /ultralytics/detection/image để phát hiện và /ultralytics/segmentation/image để 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

Việc gỡ lỗi 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 thông điệp được xuất bản trên một topic cụ thể, giúp kiểm tra luồng dữ liệu.
  2. rostopic list: Dùng lệnh này để liệt kê tất cả topic hiện có trong hệ thống ROS, qua đó cung cấp cái nhì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ị đồ thị giao tiếp giữa các node, giúp bạn hiểu cách các node kết nối và tương tác với nhau.
  4. Để tạo 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ể dùng RViz. RViz (ROS Visualization) là công cụ trực quan hóa 3D mạnh mẽ dành 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 gỡ lỗi và tìm hiểu hành vi của hệ thống robot dễ dàng hơn.

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

Các thông điệp ROS tiêu chuẩn cũng bao gồm thông điệp std_msgs/String. Trong nhiều ứng dụng, không cần xuất bản lại toàn bộ ảnh đã chú thích; 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 minh họa cách dùng thông điệp std_msgs/String để xuất bản lại các lớp được phát hiện lên topic /ultralytics/detection/classes. Các thông điệp này gọn nhẹ hơn và cung cấp thông tin thiết yếu, hữu ích cho 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 ảnh đã chú thích có dung lượng lớn 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 đối tượng như "box", "pallet" và "forklift", robot sẽ xuất bản các lớp này lên topic /ultralytics/detection/classes. Sau đó, hệ thống giám sát trung tâm có thể 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 để 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 đã phát hiện. Cách tiếp cận 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.

Hướng dẫn từng bước sử dụng String#

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ý ảnh nhận được bằng YOLO và xuất bản các đối tượng được phát hiện lên topic mới /ultralytics/detection/classes bằng thông điệp std_msgs/String. Gói ros_numpy được dùng để chuyển đổi thông điệp ROS Image thành mảng 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 ảnh độ sâu ROS#

Ngoài ảnh RGB, ROS còn hỗ trợ ảnh độ sâu, cung cấp thông tin về khoảng cách từ các đối tượng đến camera. Ả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ị.

Ảnh độ sâu là ảnh trong đó mỗi pixel biểu thị khoảng cách từ camera đến một đối tượng. Khác với ảnh RGB ghi nhận màu sắc, ảnh độ sâu ghi nhận 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.

Thu thập ảnh độ sâu

Có thể thu thập ả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 độ chênh lệch giữa các ảnh.
  2. Camera Time-of-Flight (ToF): Đo thời gian ánh sáng phản hồ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 lên bề mặt và đo độ biến dạng của mẫu.

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

Trong ROS, ảnh độ 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 encoding, chiều cao, chiều rộng và dữ liệu pixel. Trường encoding của ảnh độ sâu thường dùng định dạng như "16UC1", biểu thị số nguyên không dấu 16 bit cho mỗi pixel, trong đó mỗi giá trị thể hiện khoảng cách đến đối tượng. Ảnh độ sâu thường được dùng kết hợp với ảnh RGB để cung cấp góc nhìn toàn diện hơn về môi trường.

Có thể dùng YOLO để trích xuất và kết hợp thông tin từ cả ảnh RGB lẫn ảnh độ sâu. Ví dụ, YOLO có thể phát hiện đối tượng trong ảnh RGB, sau đó dùng kết quả phát hiện để xác định các vùng tương ứng trong ả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, tăng cường khả năng nhận biết môi trường ba chiều của robot.

Camera RGB-D

Khi làm việc với ảnh độ sâu, cần đảm bảo ảnh RGB và ảnh độ sâu được căn chỉnh chính xác. Các camera RGB-D như dòng Intel RealSense cung cấp ảnh RGB và ảnh độ sâu được đồng bộ, 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à camera độ sâu riêng biệt, điều quan trọng là phải hiệu chuẩn để đảm bảo căn chỉnh chính xác.

Hướng dẫn từng bước sử dụng ảnh độ sâu#

Trong ví dụ này, chúng ta dùng YOLO để phân đoạn ảnh và áp dụng mask thu được để phân đoạn đối tượng trong ảnh độ sâu. Nhờ đó, chúng ta có thể xác định khoảng cách từ tâm tiêu cự của camera đến từng pixel của đối tượng cần quan tâm. Với 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 khung cảnh. Bắt đầu bằng cách nhập các thư viện cần thiết, tạo một node ROS, rồi khởi tạo model phân đoạn và 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ý thông điệp ảnh độ sâu đầu vào. Hàm này chờ thông điệp ảnh độ sâu và ảnh RGB, chuyển đổi chúng thành mảng NumPy rồi áp dụng model phân đoạn cho ả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 ảnh độ sâu. Hầu hết cảm biến đều có khoảng cách tối đa, gọi là khoảng cách cắt, vượt quá giới hạn này thì giá trị được biểu diễn dưới dạng inf (np.inf). Trước khi xử lý, cần lọc bỏ các giá trị null này và gán cho chúng giá trị 0. Cuối cùng, hàm xuất bản 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, retina_masks=True)  # masks at the original image resolution

    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, retina_masks=True)  # masks at the original image resolution

    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 sensor_msgs/PointCloud2 thông điệp là cấu trúc dữ liệu dùng trong ROS để biểu diễn dữ liệu point cloud 3D. Loại thông điệp này đóng vai trò thiết yếu trong các ứng dụng robot, hỗ trợ những tác vụ như lập bản đồ 3D, nhận dạng đối tượng và định vị.

Point cloud 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 khung cảnh, được thu thập bằng công nghệ quét 3D. Mỗi điểm trong point cloud có tọa độ X, Y và Z, 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, cần xem xét hệ quy chiếu của cảm biến đã thu thập dữ liệu point cloud. Point cloud ban đầu được ghi nhận 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 point cloud sang một hệ quy chiếu khác. Có thể thực hiện phép biến đổi này bằng gói tf2_ros, cung cấp các công cụ quản lý hệ tọa độ và chuyển đổi dữ liệu giữa các hệ tọa độ.

Thu thập point cloud

Có thể thu thập point cloud bằng nhiều loại cảm biến:

  1. LIDAR (Light Detection and Ranging): Sử dụng xung laser để đo khoảng cách đến các đối tượng và tạo bản đồ 3D có độ chính xác cao.
  2. Camera độ sâu: Ghi nhận thông tin độ sâu cho từng pixel, cho phép tái tạo khung cảnh 3D.
  3. Camera stereo: Sử dụng hai camera trở lên để thu thập thông tin độ sâu thông qua phép tam giác đạc.
  4. Máy quét ánh sáng 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 point cloud#

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

Để xử lý point cloud, 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 point cloud, trực quan hóa dữ liệu 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 và nâng cao khả năng thao tác, phân tích point cloud kết hợp với phân đoạn dựa trên YOLO.

Hướng dẫn từng bước sử dụng point cloud#

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 hàm pointcloud2_to_array để chuyển đổi thông điệp sensor_msgs/PointCloud2 thành hai mảng NumPy. Thông điệp sensor_msgs/PointCloud2 chứa các điểm n dựa trên width và height của ảnh thu được. Ví dụ, ả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 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ề tọa độ xyz và các giá trị RGB theo độ phân giải gốc của camera (width x height). Hầu hết cảm biến đều có khoảng cách tối đa, gọi là khoảng cách cắt, vượt quá giới hạn này thì giá trị được biểu diễn dưới dạng inf (np.inf). Trước khi xử lý, cần lọc bỏ 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, đăng ký topic /camera/depth/points để nhận thông điệp point cloud, rồi 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 (bằng hàm pointcloud2_to_array). Xử lý ả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ả ảnh RGB lẫn tọa độ XYZ để tách riêng đối tượng trong không gian 3D.

Việc xử lý mask rất đơn giản vì mask 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 gốc với mask. Thao tác này giúp cô lập đối tượng cần quan tâm trong ảnh. Cuối cùng, tạo một point cloud 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, retina_masks=True)  # mask ở độ phân giải ảnh gốc

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, retina_masks=True)  # mask ở độ phân giải ảnh gốc

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ới ROS, robot của bạn có thể thực hiện phát hiện đối tượng và phân đoạn trên ảnh RGB, ảnh độ sâu và point cloud, biến các luồng dữ liệu cảm biến thô thành thông tin nhận thức có thể hành động. Tiếp theo, hãy khám phá chế độ Predict để tìm hiểu 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ừ nguyên mẫu đến môi trường production.

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

  • Hệ điều hành robot (ROS) là một framework mã nguồn mở thường được dùng trong lĩnh vực robot để hỗ trợ nhà phát triển 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 ứ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 thông điệp 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 cần thiết 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 ảnh để xử lý dữ liệu nhận được nhằm phát hiện đối tượng. Đây là 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 thông qua mô hình xuất bản–đăng ký. Topic là một kênh có tên mà các node dùng để gửi và nhận thông điệp bất đồng bộ. Trong ngữ cảnh Ultralytics YOLO, bạn có thể thiết lập một node đăng ký topic ảnh, xử lý ảnh bằng YOLO cho các tác vụ như phát hiện hoặc phân đoạn, rồi xuất bản kết quả lên các topic mới.

    Ví dụ, đăng ký một topic camera và xử lý ảnh nhận được để phát hiện:

    rospy.Subscriber("/camera/color/image_raw", Image, callback)
  • Ảnh độ sâu trong ROS, được biểu diễn bằng sensor_msgs/Image, cung cấp khoảng cách từ các đối tượng đến camera, rất cần thiết 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 ảnh RGB, robot có thể hiểu rõ hơn về môi trường 3D.

    Với YOLO, bạn có thể trích xuất mask phân đoạn từ ảnh RGB và áp dụng các mask này lên ảnh độ sâu để thu thập 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 point cloud 3D trong ROS bằng YOLO:

    1. Chuyển đổi thông điệp sensor_msgs/PointCloud2 thành mảng NumPy.
    2. Dùng YOLO để phân đoạn ảnh RGB.
    3. Áp dụng mask phân đoạn lên point cloud.

    Dưới đây là 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, retina_masks=True)  # mask ở độ phân giải ảnh gốc
    
    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 về các đối tượng đã phân đoạn, hữu ích cho những tác vụ như điều hướng và thao tác trong các ứng dụng robot.

Bình luận