Node Composition 개념

  • Component로 정의된 Node를 Composable Node라 하며, Composable Node 들이 하나의 컨테이너(단일 프로세스)에서 실행하는 형태를 Node Composition 이라 한다.

Component

  • Component는 ROS2 노드의 코드를 특정 방식으로 구조화한 것을 의미한다.
  • 일반적인 독립 실행(Standalone) 노드와 달리, Component는 다른 노드와 같은 프로세스 내에 묶여서 실행될 수 있는 재사용 가능한 구성 요소로 설계된다.
  • 이는 여러 노드가 하나의 프로세스 공간을 공유할 수 있도록 하여 시스템의 모듈성과 효율성을 높인다.

Composable Node

  • Composable Node는 Component로 정의된 노드를 지칭한다.
  • Node Composition 아키텍처에서 다른 Component들과 함께 하나의 컨테이너(프로세스) 내에서 실행될 수 있도록 구현된 노드를 말한다.

Node Composition

  • Node Composition은 여러 Composable Node(Component)들이 하나의 운영체제 프로세스(컨테이너) 내에서 함께 실행되는 ROS2의 아키텍처 패턴이다.
  • 이 방식을 통해 한 컨테이너 안에서 묶여서 돌아가는 Composable Node끼리는 단일 PID(Process ID)를 공유하며, std::shared_ptr 형태로 메모리 주소값만 넘겨주는 Zero-Copy 통신이 이루어진다.
  • Zero-Copy 통신은 일반적인 노드 간의 데이터 복사(IPC, Inter-Process Communication)에 비해 데이터 전송 지연시간과 CPU 오버헤드를 대폭 줄여주는 핵심적인 장점이다.
  • 예를 들어,ROS2에 기본 설치된 v4l2_camera 패키지는 독립 실행(Standalone Node)과 컴포넌트 실행(Composable Node)을 둘 다 지원하도록 제작되어 있다.

  • 독립 실행 (Standalone): 각 노드가 rclcpp 런타임, 미들웨어(DDS), C++ 표준 라이브러리 등을 중복으로 메모리에 할당함.
  • 컴포넌트 실행 (Container):하나의 PID(프로세스) 안에서 rclcpp 런타임과 스레드 풀을 공유하므로, RAM 사용량이 획기적으로 줄어들고 CPU 컨텍스트 스위칭(Context Switching) 오버헤드 최소화.
  • 컴포넌트에서 Zero-Copy 실행 (Container+Zero-Copy) : use_intra_process_comms 옵션을 통해 Zero-Copy를 활성화 하면 활성화된 노드끼리의 통신은 단일 프로세스의 메모리 주소를 그대로 넘겨주기 때문에 메모리 복사가 이루어 지지 않아 가장 빠르다.

Node Composition 구성 및 설정

  • Composition에 구성될 노드들을 Component로 정의하고, launch 파일에서 컨테이너로 실행할 노드들과 Standalone으로 실행할 노드들을 구성하여 실행 할 수 있다.
┌──────────────────────────────────────────────────────────┐
│              Component Container (Process A)             │
│                                                          │
│  ┌────────────────────┐  Zero-Copy  ┌──────────────────┐ │
│  │    v4l2_webcam     │ =========== │  yolo_detector   │ │
│  │ (Composable Node)  │  (Pointer)  │ (Composable Node)│ │
│  └────────────────────┘             └─────────┬────────┘ │
└────────────────────────────────────────────────┼─────────┘
                                                 │
                                           일반 DDS 통신
                                         (공유 메모리/소켓)
                                                 │
                                      ┌──────────▼──────────┐
                                      │ display_ai_results  │
                                      │  (Standalone Node)  │
                                      └─────────────────────┘

Component 노드 설정 방법

노드 cpp 파일 수정

  1. 컴포넌트 등록 헤더 추가
#include <rclcpp_components/register_node_macro.hpp> // 컴포넌트 등록 헤더 추가
  1. 클래스 생성자 컴포넌트 명시
class YoloOrtDetectorNode : public rclcpp::Node {
public:
    // Composable Node는 컴포넌트 컨테이너가 생성자 인자(NodeOptions)를 주입하므로
    // 생성자 형태가 아래와 같아야 함 (options를 부모 생성자로 전달)
    explicit YoloOrtDetectorNode(const rclcpp::NodeOptions & options = rclcpp::NodeOptions()) 
    : Node("yolo_ort_detector_node", options) 
    {
	    // ~~~
    }
  1. 코드 최하단 클래스를 컴포넌트 시스템에 등록하는 매크로 추가
// 컴포넌트 플러그인 등록 매크로를 명시한다.
// 이 매크로는 내부적으로 "이 C++ 클래스를 .so 라이브러리 파일 바깥으로 노출(Export)시켜서,
// ROS 2 컨테이너가 라이브러리를 열었을 때 클래스를 찾아낼 수 있도록 등록해 주는 함수"를 만들어낸다.
RCLCPP_COMPONENTS_REGISTER_NODE(YoloOrtDetectorNode)

CMakeLists.txt 수정

  1. finde_package 추가
find_package(rclcpp_components REQUIRED) # 컴포넌트 노드 빌드용 패키지
  1. Component용 공유 라이브러리(.so) 빌드 설정
# =============================================================
# [yolo_detector_onnx_cpu] 빌드 정의 (Composable Node)
# =============================================================
add_library(yolo_detector_onnx_cpu_component SHARED
  src/yolo_detector_onnx_cpu.cpp
  src/utils/yolo_utils.cpp
)
# 컴포넌트 라이브러리에도 RPATH 설정 적용 (ONNXRuntime .so 탐색용)
set_target_properties(yolo_detector_onnx_cpu_component PROPERTIES
  INSTALL_RPATH "$ORIGIN"
  BUILD_WITH_INSTALL_RPATH TRUE
)
target_include_directories(yolo_detector_onnx_cpu_component PRIVATE
  include
  ${ONNXRUNTIME_INCLUDE_DIRS}
  ${OpenCV_INCLUDE_DIRS}
)
# ROS 2 의존성 연결 (rclcpp_components 필수 추가)
ament_target_dependencies(yolo_detector_onnx_cpu_component
  rclcpp
  rclcpp_components  # 컴포넌트 등록 관련 의존성
  # (!! excutable node도 같이 사용된다면 거기에도 추가해야함)
  sensor_msgs
  cv_bridge
  std_msgs
)
target_link_libraries(yolo_detector_onnx_cpu_component
  ${OpenCV_LIBRARIES}
  ${ONNXRUNTIME_LIBRARIES}
  ${cpp_typesupport_target}
)
# 컴포넌트 플러그인 등록 정보 생성
rclcpp_components_register_nodes(yolo_detector_onnx_cpu_component "my_vision::YoloOrtDetectorNode")
  1. 인스톨 규칙 설정
# =============================================================
# [INSTALL] 빌드 정의
# =============================================================
# 인스톨 규칙 설정 (ros2 run 명령 인식용)
# 실행 파일(Executable)과 컴포넌트 라이브러리(.so) 인스톨
install(TARGETS 
  yolo_detector_onnx_cpu
  pidnet_seg_onnx_cpu
  display_ai_results
  yolo_detector_onnx_cpu_component # 컴포넌트 라이브러리 타겟 추가
  RUNTIME DESTINATION lib/${PROJECT_NAME}  # 실행 파일들이 복사되는 위치
  ARCHIVE DESTINATION lib/${PROJECT_NAME}  # 컴포넌트 .so 라이브러리가 복사되는 위치
  LIBRARY DESTINATION lib/${PROJECT_NAME}  # 컴포넌트 .so 라이브러리가 복사되는 위치
)
 
# 런타임용 외부 ONNXRuntime .so 공유 라이브러리 복사
# 노드 및 컴포넌트 라이브러리와 같은 위치(lib/${PROJECT_NAME})로 복사하여 RPATH($ORIGIN)가 동작하도록 설정
install(DIRECTORY "${ONNXRUNTIME_DIR}/lib/"
  DESTINATION lib/${PROJECT_NAME}
)

package.xml 의존성 추가

  • package.xml 파일에 rclcpp_components 의존성을 명시해 주어야 ROS 2 빌드 시스템이 이 노드를 컴포넌트로 인식한다.
<depend>rclcpp_components</depend> <!-- ★ 추가 -->

빌드 및 확인

colcon build
source install/setup.bash
ros2 component types

Composition을 위한 launch 파일 구성

launch.py 파일 수정

  1. 컴포넌트 관련 라이브러리 추가
from launch_ros.actions import Node, ComposableNodeContainer
from launch_ros.descriptions import ComposableNode
  1. 컴포넌트 컨테이너 (Zero-Copy 적용) 생성
def generate_launch_description():
	# [Container]카메라 + YOLO 추론 노드를 하나로 묶는 컴포넌트 컨테이너 (Zero-Copy 적용)
    vision_container = ComposableNodeContainer(
        name='vision_pipeline_container',
        namespace='',
        package='rclcpp_components',
        executable='component_container',
        composable_node_descriptions=[
            # v4l2_camera 컴포넌트 (기존 파라미터 및 FATAL 로그 유지)
            ComposableNode(
                package='v4l2_camera',
                plugin='v4l2_camera::V4L2Camera',
                name='v4l2_webcam',
                parameters=[{
                    'video_device': '/dev/video0',
                    #'image_size': [640, 480]
                }],
                extra_arguments=[
                    {'use_intra_process_comms': True}, # Zero-Copy 통신 활성화
                    {'global_arguments': ['--ros-args', '--log-level', 'FATAL']} # 기존 로그 레벨 제한 유지
                ]
            ),
            # B. C++ YOLO ONNX 컴포넌트 (신규 적용)
            ComposableNode(
                package='my_vision_package_cpp',
                plugin='my_vision::YoloOrtDetectorNode', # Cmake에서 등록한 컴포넌트 이름
                name='yolo_detector_cpu',
                extra_arguments=[{'use_intra_process_comms': True}] # Zero-Copy 통신 활성화
            ),
        ],
        output='screen'
    )

launch 실행 및 컴포넌트 확인

  • 현재 실행 중인 컴포넌트 확인
ros2 component list
 
>> /vision_pipeline_container
  1  /v4l2_webcam
  2  /yolo_detector_cpu
 

컴포넌트 조작

특정 컴포넌트 언로드 및 로드

  • 컴포넌트 아키텍처의 장점은 컨테이너(프로세스)가 켜져 있는 상태(Runtime)에서도 노드를 끄고 켤 수 있다는 점이다.
# /vision_pipeline_container 내부의 1번 컴포넌트 언로드
ros2 component unload /vision_pipeline_container 1
 
# 다시 동적으로 컴포넌트 노드 로드 (주입)
ros2 component load /vision_pipeline_container v4l2_camera v4l2_camera::V4L2Camera \
	-e use_intra_process_comms:=true