Skip to main content
🤝

이 가이드는 인류와 AI가 함께 만드는 지식입니다.

이 콘텐츠는 Human + AI Partnership 철학 아래 모든 사람이 로봇·AI를 배울 수 있도록 무료로 제공됩니다. 당신의 질문과 기여가 다음 학생의 미래를 바꿉니다.

ROS 2 Computer Vision & OpenCV Guide 2026

Give robots sight. Bridge OpenCV and ROS 2 with cv_bridge, build efficient image pipelines, run real-time object detection with YOLO, and fuse RGB with depth for full 3D perception.

1. Installing the Vision Stack

Install cv_bridge, image_transport, and the vision utilities:

sudo apt-get install -y   ros-humble-cv-bridge   ros-humble-image-transport   ros-humble-image-pipeline   ros-humble-vision-opencv   ros-humble-image-proc

# Python deps for detection
pip install opencv-python numpy ultralytics

# Verify cv_bridge
python3 -c "from cv_bridge import CvBridge; print('cv_bridge OK')"

2. cv_bridge: ROS Image ↔ OpenCV Mat

cv_bridge converts sensor_msgs/Image into a NumPy/OpenCV array and back:

import rclpy
from rclpy.node import Node
from sensor_msgs.msg import Image
from cv_bridge import CvBridge
import cv2

class VisionNode(Node):
    def __init__(self):
        super().__init__('vision_node')
        self.bridge = CvBridge()
        self.sub = self.create_subscription(
            Image, '/camera/color/image_raw', self.on_image, 10)
        self.pub = self.create_publisher(Image, '/vision/annotated', 10)

    def on_image(self, msg):
        # ROS Image -> OpenCV BGR
        frame = self.bridge.imgmsg_to_cv2(msg, desired_encoding='bgr8')

        gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
        edges = cv2.Canny(gray, 100, 200)
        annotated = cv2.cvtColor(edges, cv2.COLOR_GRAY2BGR)

        # OpenCV -> ROS Image (preserve header for TF sync)
        out = self.bridge.cv2_to_imgmsg(annotated, encoding='bgr8')
        out.header = msg.header
        self.pub.publish(out)

def main():
    rclpy.init()
    rclpy.spin(VisionNode())
    rclpy.shutdown()

if __name__ == '__main__':
    main()

3. Efficient Image Transport

Raw images saturate bandwidth. Use image_transport with compression:

# Subscribe to compressed images (much lower bandwidth)
ros2 run image_transport republish   compressed in:=/camera/color/image_raw   raw out:=/camera/color/image_decompressed

# List available transports for a topic
ros2 topic list | grep image_raw
#   /camera/color/image_raw
#   /camera/color/image_raw/compressed
#   /camera/color/image_raw/theora

# QoS: use SENSOR_DATA (best-effort) for camera streams
# so a slow subscriber never blocks the pipeline

4. Real-Time Object Detection with YOLO

Run YOLOv8 inside a ROS 2 node and publish 2D detections:

from ultralytics import YOLO
from vision_msgs.msg import Detection2DArray, Detection2D, BoundingBox2D
from cv_bridge import CvBridge

class YoloDetector(Node):
    def __init__(self):
        super().__init__('yolo_detector')
        self.model = YOLO('yolov8n.pt')   # nano = fastest on edge
        self.bridge = CvBridge()
        self.sub = self.create_subscription(
            Image, '/camera/color/image_raw', self.detect, 10)
        self.pub = self.create_publisher(
            Detection2DArray, '/detections', 10)

    def detect(self, msg):
        frame = self.bridge.imgmsg_to_cv2(msg, 'bgr8')
        results = self.model(frame, verbose=False)[0]

        arr = Detection2DArray()
        arr.header = msg.header
        for box in results.boxes:
            x1, y1, x2, y2 = box.xyxy[0].tolist()
            det = Detection2D()
            det.bbox = BoundingBox2D()
            det.bbox.center.position.x = (x1 + x2) / 2
            det.bbox.center.position.y = (y1 + y2) / 2
            det.bbox.size_x = x2 - x1
            det.bbox.size_y = y2 - y1
            arr.detections.append(det)
        self.pub.publish(arr)

5. Camera Calibration

Accurate perception needs intrinsic calibration. Use the camera_calibration tool:

# Calibrate with an 8x6 checkerboard, 108mm squares
ros2 run camera_calibration cameracalibrator   --size 8x6 --square 0.108   --ros-args -r image:=/camera/color/image_raw              -r camera:=/camera

# Move the board through the frame until X/Y/Size/Skew bars fill,
# then CALIBRATE -> SAVE. Output: /tmp/calibrationdata.tar.gz
# Load the resulting camera_info.yaml at driver launch.

6. Depth Perception & RGB-D Fusion

Combine a detection's 2D pixel with the depth image to get a 3D point:

import numpy as np

def pixel_to_3d(u, v, depth_image, camera_info):
    # camera_info.k = [fx, 0, cx, 0, fy, cy, 0, 0, 1]
    fx, fy = camera_info.k[0], camera_info.k[4]
    cx, cy = camera_info.k[2], camera_info.k[5]

    z = float(depth_image[int(v), int(u)]) / 1000.0  # mm -> m
    if z == 0.0 or np.isnan(z):
        return None                                  # invalid depth
    x = (u - cx) * z / fx
    y = (v - cy) * z / fy
    return (x, y, z)                                 # point in camera frame

# Publish as geometry_msgs/PointStamped, then TF-transform into 'map'
# to know where a detected object actually is in the world.

7. Visual Servoing

Drive the robot to center a target in the frame with a simple proportional controller:

from geometry_msgs.msg import Twist

def servo_to_target(self, target_u, image_width):
    error = (target_u - image_width / 2) / (image_width / 2)  # -1..1
    cmd = Twist()
    cmd.angular.z = -0.6 * error       # turn toward target
    cmd.linear.x = 0.15 * (1 - abs(error))  # slow when off-center
    self.cmd_pub.publish(cmd)

8. GPU Acceleration with Isaac ROS

On NVIDIA Jetson, offload vision to the GPU with Isaac ROS NITROS pipelines:

# Isaac ROS DNN inference (TensorRT-accelerated)
ros2 launch isaac_ros_yolov8 isaac_ros_yolov8_visualize.launch.py   model_file_path:=yolov8n.onnx   engine_file_path:=yolov8n.plan

# NITROS keeps images in GPU memory between nodes (zero-copy),
# eliminating CPU<->GPU transfer overhead in the pipeline.

9. Common Pitfalls

10. Perception Performance Tuning

Key Takeaways

cv_bridge is the gateway between ROS 2 and OpenCV — preserve headers and encodings. Use compressed image_transport to protect bandwidth, YOLOv8 for real-time detection, and RGB-D fusion to place detections in 3D. On Jetson, Isaac ROS gives zero-copy GPU pipelines. Tune model size, resolution, and executors to hit real-time on your hardware.