659.76 tf2_ros::TransformListener의 내부 동작 메커니즘

1. 개요

tf2_ros::TransformListener는 ROS2의 토픽 통신 인프라를 통해 좌표 변환 데이터를 수신하고 tf2_ros::Buffer에 저장하는 구성 요소이다. TransformListener/tf/tf_static 토픽을 자동으로 구독하며, 수신된 변환 메시지를 파싱하여 버퍼의 내부 변환 트리(transform tree)에 등록한다. 이 클래스의 내부 동작을 이해하면, 변환 지연(latency), 스레드 안전성, 콜백 실행 모델, 그리고 성능 최적화에 관한 깊은 통찰을 얻을 수 있다.

2. 생성자와 초기화

2.1 기본 생성자

tf2_ros::TransformListener(
  tf2_ros::Buffer & buffer,
  bool spin_thread = true);
매개변수기본값설명
buffer-변환 데이터를 저장할 Buffer 참조
spin_threadtrue전용 스레드에서 구독 콜백을 실행할지 여부

2.2 고급 생성자

tf2_ros::TransformListener(
  tf2_ros::Buffer & buffer,
  rclcpp::Node::SharedPtr node,
  bool spin_thread = true,
  const rclcpp::QoS & qos = rclcpp::QoS(100),
  const rclcpp::QoS & static_qos = rclcpp::StaticQoS());

이 생성자는 QoS 정책을 명시적으로 지정할 수 있다.

3. 토픽 구독 메커니즘

3.1 /tf 토픽 구독

TransformListener는 생성 시 /tf 토픽에 대한 구독을 자동으로 생성한다. 이 토픽의 메시지 타입은 tf2_msgs::msg::TFMessage이며, 하나의 메시지에 복수의 TransformStamped를 포함할 수 있다.

기본 QoS 정책:

  • Reliability: RELIABLE
  • History: KEEP_LAST
  • Depth: 100
  • Durability: VOLATILE

3.2 /tf_static 토픽 구독

정적 변환을 위한 /tf_static 토픽도 자동으로 구독된다.

기본 QoS 정책:

  • Reliability: RELIABLE
  • History: KEEP_ALL
  • Durability: TRANSIENT_LOCAL

TRANSIENT_LOCAL 지속성은 새로운 구독자가 연결될 때 기존에 발행된 정적 변환을 자동으로 전달받을 수 있게 한다. 이를 통해 TransformListener가 정적 변환 발행 이후에 생성되더라도 해당 변환을 수신할 수 있다.

4. 콜백 처리 모델

4.1 전용 스레드 모드 (spin_thread = true)

기본 설정에서 TransformListener는 전용 스레드를 생성하여 변환 콜백을 실행한다. 이 모드에서는 다음의 구조가 형성된다.

메인 스레드:
  ├── 사용자 콜백 (센서 데이터, 타이머 등)
  └── lookupTransform() 호출

TF2 전용 스레드:
  ├── /tf 콜백 → Buffer에 변환 저장
  └── /tf_static 콜백 → Buffer에 정적 변환 저장

전용 스레드 모드의 장점은 다음과 같다.

  1. 비차단 수신: 사용자 콜백이 장시간 실행되더라도 변환 데이터의 수신이 차단되지 않는다
  2. 최신 데이터 보장: 변환 데이터가 독립적으로 갱신되므로 lookupTransform() 호출 시 최신 데이터에 접근할 수 있다
  3. 간편한 통합: 사용자가 별도의 멀티스레딩 구성을 할 필요가 없다

4.2 공유 스레드 모드 (spin_thread = false)

spin_thread = false로 설정하면 변환 콜백이 노드의 일반 콜백 그룹(callback group)에서 실행된다. 이 경우 사용자가 적절한 Executor(예: MultiThreadedExecutor)를 사용하여 콜백 처리를 관리해야 한다.

auto tf_buffer = std::make_shared<tf2_ros::Buffer>(node->get_clock());
auto tf_listener = std::make_shared<tf2_ros::TransformListener>(
  *tf_buffer, node, false);  // spin_thread = false

// MultiThreadedExecutor 사용 필요
rclcpp::executors::MultiThreadedExecutor executor;
executor.add_node(node);
executor.spin();

공유 스레드 모드는 SingleThreadedExecutor와 함께 사용할 경우, 사용자 콜백 내에서 lookupTransform()을 호출하면 데드락(deadlock) 위험이 있다. 이는 변환 데이터의 수신 콜백이 동일한 스레드에서 대기하고 있어 진행이 불가능해지기 때문이다.

5. 콜백 내부 처리 흐름

5.1 TFMessage 수신 콜백

/tf 토픽의 콜백 처리 흐름은 다음과 같다.

1. TFMessage 수신
2. 메시지 내의 각 TransformStamped에 대해:
   a. frame_id, child_frame_id 유효성 검사
   b. 타임스탬프 확인
   c. Buffer::setTransform() 호출
      ├── 변환 트리에 부모-자식 관계 등록
      ├── 캐시에 시간별 변환 데이터 저장
      └── 기존 변환의 업데이트 (동일 프레임 쌍)
3. 대기 중인 lookupTransform() 요청 깨우기 (조건 변수 알림)

5.2 변환 저장 (Buffer::setTransform)

bool Buffer::setTransform(
  const geometry_msgs::msg::TransformStamped & transform,
  const std::string & authority,
  bool is_static = false);

authority 매개변수는 변환의 출처(발행 노드)를 식별하는 문자열이다. is_statictrue이면 정적 변환으로 처리되어 캐시 만료가 적용되지 않는다.

6. 스레드 안전성

6.1 Buffer의 내부 동기화

tf2_ros::Buffer는 내부적으로 뮤텍스(mutex)를 사용하여 변환 데이터의 읽기와 쓰기를 동기화한다. 따라서 다음의 동시 접근 패턴은 안전하다.

  • TF2 전용 스레드에서의 setTransform() + 사용자 스레드에서의 lookupTransform()
  • 다수의 사용자 스레드에서의 동시 lookupTransform() 호출

6.2 잠금 경합 (Lock Contention)

고주파 변환 발행(예: 1000 Hz)과 고주파 변환 조회가 동시에 발생하면, 뮤텍스 잠금 경합이 성능 병목이 될 수 있다. 이 경우 변환 조회의 지연 시간이 증가할 수 있다. 변환 조회 빈도를 센서 데이터의 처리 빈도로 제한하고, 불필요하게 높은 조회 빈도를 피하는 것이 권장된다.

7. 시간 관리

7.1 ROS Time vs System Time

TransformListenerBuffer에 전달된 rclcpp::Clock의 시간 유형에 따라 동작한다.

  • System Time (ROS_TIME 미사용): 시스템 시계를 사용한다
  • ROS Time (use_sim_time = true): /clock 토픽에서 수신된 시뮬레이션 시간을 사용한다

시뮬레이션 환경에서는 use_sim_time 파라미터를 true로 설정하여 시뮬레이션 시간 기반의 변환 관리가 이루어지도록 해야 한다.

7.2 변환 지연 분석

변환 수신의 종합적 지연은 다음 요소의 합으로 구성된다.

t_{\text{total}} = t_{\text{compute}} + t_{\text{serialize}} + t_{\text{network}} + t_{\text{deserialize}} + t_{\text{callback}}

요소일반적 범위설명
t_{\text{compute}}~0.01 ms변환 계산 시간
t_{\text{serialize}}~0.01 ms메시지 직렬화
t_{\text{network}}0.01–1 msDDS 전송 지연
t_{\text{deserialize}}~0.01 ms메시지 역직렬화
t_{\text{callback}}~0.01 ms콜백 처리 및 Buffer 저장

일반적으로 동일 호스트 내에서의 총 지연은 1 ms 미만이며, 네트워크를 경유하는 경우 수 ms 수준으로 증가할 수 있다.

8. 네임스페이스와 리매핑

8.1 토픽 리매핑

TransformListener가 구독하는 토픽 이름은 ROS2 표준 리매핑 메커니즘을 통해 변경할 수 있다.

ros2 run my_package my_node --ros-args -r /tf:=/robot1/tf -r /tf_static:=/robot1/tf_static

이는 다중 로봇 시스템에서 각 로봇의 TF 토픽을 분리할 때 유용하다.

9. 일반적 문제와 해결

9.1 변환이 조회되지 않는 경우

  1. TransformListener 미생성: Buffer만 생성하고 TransformListener를 생성하지 않으면 변환 데이터가 수신되지 않는다
  2. 초기화 경쟁 조건: 노드 시작 직후에는 아직 변환 데이터가 수신되지 않았을 수 있다. canTransform()으로 가용 여부를 확인하거나, 타임아웃을 설정하여 변환을 대기해야 한다
  3. 프레임 네임스페이스 불일치: 네임스페이스가 적용된 프레임 이름(예: /robot1/base_link)과 적용되지 않은 이름(base_link)이 혼용되면 프레임을 찾을 수 없다

9.2 메모리 사용량 증가

장시간 실행되는 시스템에서 변환 발행 빈도가 높고 캐시 기간이 긴 경우, Buffer의 메모리 사용량이 증가할 수 있다. 캐시 기간을 필요한 최소 시간으로 제한하여 메모리 사용량을 관리한다.

10. 참고 문헌


버전: 2026-03-26