Atlas Robotics
Computer Vision / AINâng cao

Tích hợp ROS2 + YOLO: đóng gói nhận diện vật thể thành node

Atlasbot · 18/8/2026 · 12 phút đọc

Tích hợp ROS2 + YOLO: đóng gói nhận diện vật thể thành node
Pipeline ROS2 + YOLO: subscribe ảnh, chạy inference, publish detection

Chạy được YOLO độc lập trên một file ảnh/video là bước khởi đầu (đã học ở bài YOLO là gì?) — nhưng để dùng thật trong robot, kết quả nhận diện cần trở thành một phần của hệ thống ROS2: đọc ảnh trực tiếp từ camera node, và publish kết quả ra topic để các node khác (Nav2 né vật cản động, node giám sát...) sử dụng được.

Khái niệm chính

camera node (v4l2_camera)  →  topic /image_raw
                                    │
                                    ▼
                         node YOLO detection (mới viết)
                                    │
                                    ▼
                    topic /detections (danh sách bounding box + nhãn)

Node YOLO đóng vai trò vừa là subscriber (nhận ảnh từ camera) vừa là publisher (gửi kết quả detection đi) — mẫu thiết kế phổ biến cho mọi node "xử lý rồi chuyển tiếp" trong ROS2, đúng vai trò một node trong ROS2 Layer đã học ở bài ROS2 Layer trong AMR.

Nguyên lý hoạt động

import rclpy
from rclpy.node import Node
from sensor_msgs.msg import Image
from cv_bridge import CvBridge
from ultralytics import YOLO

class YoloDetectionNode(Node):
    def __init__(self):
        super().__init__("yolo_detection_node")
        self.model = YOLO("yolov8n.pt")
        self.bridge = CvBridge()

        self.subscription = self.create_subscription(
            Image, "/image_raw", self.image_callback, 10
        )
        # Thực tế nên publish sensor_msgs/Image (đã vẽ box) hoặc
        # vision_msgs/Detection2DArray (dữ liệu có cấu trúc) thay vì tự định nghĩa
        self.publisher = self.create_publisher(Image, "/detections/image", 10)

    def image_callback(self, msg):
        frame = self.bridge.imgmsg_to_cv2(msg, desired_encoding="bgr8")
        results = self.model(frame, verbose=False)

        annotated = results[0].plot()   # vẽ sẵn bounding box lên ảnh
        out_msg = self.bridge.cv2_to_imgmsg(annotated, encoding="bgr8")
        self.publisher.publish(out_msg)

def main():
    rclpy.init()
    node = YoloDetectionNode()
    rclpy.spin(node)

cv_bridge là gói chuyển đổi qua lại giữa ảnh dạng OpenCV (numpy array) và message ROS2 (sensor_msgs/Image) — cầu nối bắt buộc phải có mỗi khi xử lý ảnh ROS2 bằng OpenCV/mô hình AI, đúng bước tiền xử lý đã học ở bài OpenCV cơ bản.

Kiến thức kỹ thuật

Công thức

Chạy inference AI trên mỗi khung hình camera (thường 30 khung hình/giây) có thể vượt quá khả năng xử lý của máy tính nhúng, gây tắc nghẽn (backlog) callback — ước lượng số khung hình bị bỏ lỡ:

Nếu Thời_gian_inference > Chu_kỳ_khung_hình_camera:
Số_khung_hình_tồn_đọng_mỗi_giây ≈ FPS_camera − (1/Thời_gian_inference)

VD: camera 30fps (chu kỳ 33ms), inference mất 100ms/khung hình (CPU thuần)
FPS_xử_lý_được ≈ 1/0.1 = 10fps
Số_khung_hình_tồn_đọng ≈ 30 − 10 = 20 khung hình/giây bị dồn lại hoặc bỏ qua

→ Cần giới hạn tần số xử lý hoặc dùng QoS depth=1 để luôn lấy khung hình
  mới nhất thay vì xử lý tuần tự các khung hình cũ đã lỗi thời

Thông số

Cách xử lýƯu điểmNhược điểm
Xử lý mọi khung hình (không giới hạn)Không bỏ sót dữ liệuDễ tắc nghẽn nếu inference chậm hơn camera
Giới hạn tần số (VD: mỗi 3-5 khung hình)Giảm tải tính toánBỏ qua một số khung hình giữa các lần xử lý
QoS depth=1Luôn xử lý khung hình mới nhấtBỏ qua khung hình cũ hoàn toàn, không xử lý tuần tự

Ứng dụng thực tế

Cách xử lý phổ biến trong thực tế: giới hạn tần số xử lý (VD: chỉ chạy YOLO mỗi 3-5 khung hình thay vì mọi khung hình), hoặc dùng qos_profile với depth=1 để tự động bỏ qua khung hình cũ khi node xử lý không kịp, ưu tiên luôn xử lý khung hình mới nhất — đúng nguyên tắc QoS cho dữ liệu cảm biến tần số cao đã học ở bài ROS2 Layer trong AMR (ưu tiên dữ liệu mới nhất hơn đảm bảo nhận đủ mọi message cũ).

Ví dụ thực tế

class YoloDetectionNode(Node):
    def __init__(self):
        super().__init__("yolo_detection_node")
        self.model = YOLO("yolov8n.pt")
        self.bridge = CvBridge()
        self.frame_count = 0
        self.SKIP_FRAMES = 3  # chỉ xử lý 1/3 khung hình

        self.subscription = self.create_subscription(
            Image, "/image_raw", self.image_callback, 10
        )
        self.publisher = self.create_publisher(Image, "/detections/image", 10)

    def image_callback(self, msg):
        self.frame_count += 1
        if self.frame_count % self.SKIP_FRAMES != 0:
            return  # bo qua khung hinh nay, giam tai tinh toan

        frame = self.bridge.imgmsg_to_cv2(msg, desired_encoding="bgr8")
        results = self.model(frame, verbose=False)
        annotated = results[0].plot()
        out_msg = self.bridge.cv2_to_imgmsg(annotated, encoding="bgr8")
        self.publisher.publish(out_msg)
Với camera 30fps và SKIP_FRAMES=3:
Tần số xử lý thực tế ≈ 30/3 = 10fps
Đủ cho nhiều ứng dụng phát hiện vật cản (không cần phản ứng ở 30fps),
trong khi giảm 3 lần tải tính toán so với xử lý mọi khung hình

Lỗi thường gặp

  • Xử lý mọi khung hình trên phần cứng không đủ nhanh — như tính ở mục Công thức, gây tồn đọng callback, độ trễ tăng dần theo thời gian thay vì ổn định.
  • Không dùng cv_bridge mà tự viết chuyển đổi ảnh — dễ sai định dạng encoding (BGR/RGB, đã học ở bài OpenCV cơ bản), cv_bridge đã xử lý đúng các trường hợp phổ biến.
  • Publish kết quả detection dưới dạng ảnh đã vẽ box thay vì dữ liệu có cấu trúc — hạn chế khả năng các node khác (Nav2, logic ứng dụng) sử dụng kết quả detection theo chương trình; nên cân nhắc vision_msgs/Detection2DArray cho dữ liệu có cấu trúc dùng được trực tiếp.
  • Không giới hạn tần số xử lý khi phần cứng không đủ mạnh — dẫn tới hiện tượng đã tính ở mục Công thức, độ trễ ngày càng tăng, ảnh hưởng tới cả các node khác chờ dữ liệu từ node YOLO.

Kết luận

Việc tích hợp YOLO vào ROS2 về bản chất chỉ là một node subscriber/publisher thông thường, với cv_bridge làm cầu nối giữa dữ liệu ảnh OpenCV và message ROS2. Điểm cần lưu ý nhất khi triển khai thực tế là quản lý hiệu năng — đảm bảo pipeline AI không làm nghẽn các node khác đang cần dữ liệu thời gian thực, bằng cách giới hạn tần số xử lý hoặc dùng QoS phù hợp. Đây là bài cuối của chuyên mục Sensor & Perception — từ LiDAR, Camera, IMU, tới Computer Vision/AI, toàn bộ các cảm biến nền tảng của một AMR đã được trình bày đầy đủ, sẵn sàng ghép vào kiến trúc phần cứng và phần mềm đã học ở chuyên mục AMR/AGV.

Tích hợp ROS2 + YOLO: đóng gói nhận diện vật thể thành node — Atlasbot