이 가이드는 인류와 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 pipeline4. 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
- Encoding mismatch: RealSense publishes rgb8, many models expect bgr8 — always set desired_encoding explicitly.
- QoS incompatibility: camera drivers use BEST_EFFORT; a RELIABLE subscriber gets zero messages.
- Header loss: keep msg.header on republished images or TF sync breaks.
- Blocking inference: run heavy models in a separate callback group / executor so the image callback never stalls.
- Depth alignment: use aligned_depth_to_color, not the raw depth stream, before pixel_to_3d.
10. Perception Performance Tuning
- Model size: yolov8n on edge, yolov8m+ only with a GPU.
- Resolution: downscale to 640px before inference — huge speedup, tiny accuracy loss.
- Frame skipping: detect every Nth frame, track in between with a lightweight tracker.
- Compressed transport: never move raw 1080p frames across the DDS network.
- MultiThreadedExecutor: parallelize capture, inference, and publishing.
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.