659.32 tf2::Buffer를 이용한 변환 저장과 조회

1. 개요

tf2::Buffer(C++) 및 tf2_ros.Buffer(Python)는 TF2 라이브러리의 핵심 데이터 구조로서, 좌표 변환을 시간순으로 저장하고 조회하는 기능을 제공한다. TransformListener가 수신한 변환 메시지는 Buffer에 축적되며, 사용자는 Buffer의 조회 메서드를 통하여 임의의 두 프레임 간의 변환을 취득한다. 본 절에서는 tf2::Buffer의 아키텍처, 저장 메커니즘, 조회 방법, 그리고 각종 옵션을 체계적으로 기술한다.

2. Buffer의 아키텍처

2.1 클래스 계층 구조

tf2::Buffer는 다음의 클래스 계층을 따른다.

tf2::BufferCoreBase
    └── tf2::BufferCore
            └── tf2_ros::Buffer
  • tf2::BufferCore: 변환 데이터의 저장과 조회를 담당하는 핵심 계층이다. 시간 캐시(time cache), 프레임 그래프(frame graph), 보간(interpolation) 로직을 포함한다.
  • tf2_ros::Buffer: BufferCore를 상속하며, ROS2 노드와의 연동 기능(시계 통합, 비동기 조회 등)을 추가한다.

2.2 내부 데이터 구조

Buffer는 내부적으로 프레임 ID를 키(key)로 하는 해시 맵(hash map) 구조를 유지한다. 각 프레임에 대하여 시간순으로 정렬된 변환 캐시가 관리된다.

Buffer 내부 구조:
┌─────────────────────────────────────────┐
│ Frame: "base_link"                       │
│   └── TimeCache: [(t1, T1), (t2, T2), ...]│
│ Frame: "lidar_link"                      │
│   └── TimeCache: [(t1, T1'), (t2, T2'),...]│
│ Frame: "camera_link"                     │
│   └── TimeCache: [(t1, T1''), ...]       │
└─────────────────────────────────────────┘

TimeCache는 해당 프레임에서 부모 프레임으로의 변환을 타임스탬프 순으로 저장한다.

3. Buffer 생성

3.1 C++ (rclcpp)

#include <tf2_ros/buffer.h>

// 기본 캐시 기간 (10초)
auto tf_buffer = std::make_unique<tf2_ros::Buffer>(this->get_clock());

// 캐시 기간 지정
auto tf_buffer = std::make_unique<tf2_ros::Buffer>(
  this->get_clock(),
  tf2::Duration(std::chrono::seconds(30)));

3.2 Python (rclpy)

from tf2_ros.buffer import Buffer
from rclpy.duration import Duration

# 기본 캐시 기간
tf_buffer = Buffer()

# 캐시 기간 지정
tf_buffer = Buffer(cache_time=Duration(seconds=30))

4. 변환 저장

4.1 setTransform 메서드

setTransform() 메서드는 변환을 Buffer에 저장한다. 일반적으로 TransformListener가 내부적으로 호출하며, 사용자가 직접 호출하는 경우는 드물다.

// C++
bool setTransform(
  const geometry_msgs::msg::TransformStamped & transform,
  const std::string & authority,
  bool is_static = false);
# Python
tf_buffer.set_transform(transform, authority)
tf_buffer.set_transform_static(transform, authority)
매개변수설명
transform저장할 TransformStamped 메시지
authority변환의 출처 (발행 노드 이름 등)
is_static정적 변환 여부 (C++)

authority 매개변수는 디버깅 시 특정 변환이 어떤 노드에 의하여 설정되었는지 추적하는 데 사용된다.

4.2 정적 변환과 동적 변환의 저장 차이

구분동적 변환정적 변환
캐시 만료캐시 기간 경과 시 삭제만료되지 않음
시간 의존성특정 시간에 유효모든 시간에 유효
보간 지원지원해당 없음 (고정값)
메모리 관리자동 정리수동 삭제 필요 없음

5. 변환 조회

5.1 lookupTransform

lookupTransform() 메서드는 두 프레임 간의 변환을 조회한다.

// C++
geometry_msgs::msg::TransformStamped lookupTransform(
  const std::string & target_frame,
  const std::string & source_frame,
  const tf2::TimePoint & time) const;
# Python
transform = tf_buffer.lookup_transform(
    target_frame, source_frame, time)

5.1.1 매개변수 의미

  • target_frame: 변환 결과가 표현될 좌표 프레임이다. 즉, “이 프레임 기준으로 표현하라“는 의미이다.
  • source_frame: 변환의 원점이 되는 좌표 프레임이다. 즉, “이 프레임에서 출발한다“는 의미이다.
  • time: 변환이 유효한 시각이다.

5.1.2 반환값

geometry_msgs::msg::TransformStamped 메시지가 반환된다. 이 메시지는 source_frame에서 target_frame으로의 변환을 나타낸다.

5.2 시간 지정 옵션

5.2.1 최신 가용 변환

// C++
auto t = tf_buffer->lookupTransform(
  "map", "base_link", tf2::TimePointZero);
# Python
t = tf_buffer.lookup_transform(
    'map', 'base_link', rclpy.time.Time())

tf2::TimePointZero(C++) 또는 rclpy.time.Time()(Python)을 사용하면, 가장 최근에 수신된 변환이 반환된다.

5.2.2 특정 시점의 변환

// C++
auto query_time = this->get_clock()->now();
auto t = tf_buffer->lookupTransform(
  "map", "base_link", query_time);
# Python
query_time = self.get_clock().now()
t = tf_buffer.lookup_transform(
    'map', 'base_link', query_time)

지정된 시각의 변환이 캐시에 정확히 존재하지 않는 경우, Buffer는 전후 시점의 변환을 보간(interpolation)하여 결과를 생성한다.

5.2.3 과거 시점의 변환

// C++ - 2초 전 시점의 변환
auto past_time = this->get_clock()->now() - rclcpp::Duration::from_seconds(2.0);
auto t = tf_buffer->lookupTransform(
  "map", "base_link", past_time);
# Python - 2초 전 시점의 변환
from rclpy.duration import Duration
past_time = self.get_clock().now() - Duration(seconds=2)
t = tf_buffer.lookup_transform(
    'map', 'base_link', past_time)

5.3 고급 lookupTransform: 이중 시간 조회

서로 다른 시점의 두 프레임 간 변환을 조회할 수 있다. 이를 “시간 여행(time travel)” 기능이라 한다.

// C++
auto t = tf_buffer->lookupTransform(
  "target_frame", target_time,
  "source_frame", source_time,
  "fixed_frame");
# Python
t = tf_buffer.lookup_transform_full(
    'target_frame', target_time,
    'source_frame', source_time,
    'fixed_frame')

이 기능은 이동하는 프레임의 과거 위치를 현재 좌표계에서 표현하고자 할 때 유용하다.

5.4 canTransform

canTransform() 메서드는 변환이 조회 가능한지 사전에 확인한다.

// C++
bool canTransform(
  const std::string & target_frame,
  const std::string & source_frame,
  const tf2::TimePoint & time,
  std::string * error_msg = nullptr) const;
# Python
can = tf_buffer.can_transform(
    target_frame, source_frame, time)

canTransform()은 예외를 발생시키지 않으므로, 조건부 변환 조회에 유용하다.

// C++ 사용 예
if (tf_buffer->canTransform("map", "base_link", tf2::TimePointZero)) {
  auto t = tf_buffer->lookupTransform(
    "map", "base_link", tf2::TimePointZero);
  // 변환 사용
}

5.5 waitForTransform (비동기)

tf2_ros::Buffer는 비동기적으로 변환이 가용해질 때까지 대기하는 메서드를 제공한다.

// C++ (Future 기반)
auto future = tf_buffer->waitForTransform(
  "map", "base_link",
  tf2::TimePointZero,
  std::chrono::seconds(5),
  [](const%20tf2_ros::TransformStampedFuture%20&%20future) {
    // 콜백 처리
  });

6. 변환 경로 탐색

Buffer는 변환 트리에서 두 프레임 간의 경로를 자동으로 탐색한다.

             map
              │
            odom
              │
          base_link
           /      \
    lidar_link  camera_link

lookupTransform("map", "camera_link", ...)을 호출하면, Buffer는 다음의 경로를 탐색한다.

camera_link → base_link → odom → map

각 구간의 변환을 합성(composition)하여 최종 결과를 반환한다.

T_{map \leftarrow camera} = T_{map \leftarrow odom} \cdot T_{odom \leftarrow base} \cdot T_{base \leftarrow camera}

7. 프레임 정보 조회

7.1 등록된 프레임 목록

// C++
std::string frames;
tf_buffer->_allFramesAsString();
# Python
frames = tf_buffer.all_frames_as_string()
print(frames)

7.2 프레임 존재 확인

// C++
bool exists = tf_buffer->_frameExists("base_link");

7.3 부모 프레임 조회

// C++
std::string parent;
bool success = tf_buffer->_getParent("base_link", tf2::TimePointZero, parent);

8. 예외 처리

lookupTransform() 호출 시 발생할 수 있는 예외들이다.

예외 유형발생 조건대응 방법
LookupException프레임이 존재하지 않음프레임 이름 확인
ConnectivityException두 프레임 간 경로 없음변환 트리 구조 확인
ExtrapolationException요청 시간이 캐시 범위 초과TimePointZero 사용 또는 캐시 기간 조정
InvalidArgumentException잘못된 인자프레임 ID 유효성 확인
// C++ 예외 처리
try {
  auto t = tf_buffer->lookupTransform(
    "map", "base_link", tf2::TimePointZero);
} catch (const tf2::LookupException & ex) {
  RCLCPP_WARN(this->get_logger(), "Frame not found: %s", ex.what());
} catch (const tf2::ConnectivityException & ex) {
  RCLCPP_WARN(this->get_logger(), "No path: %s", ex.what());
} catch (const tf2::ExtrapolationException & ex) {
  RCLCPP_WARN(this->get_logger(), "Extrapolation: %s", ex.what());
}
# Python 예외 처리
from tf2_ros import TransformException, LookupException
from tf2_ros import ConnectivityException, ExtrapolationException

try:
    t = tf_buffer.lookup_transform(
        'map', 'base_link', rclpy.time.Time())
except LookupException as ex:
    self.get_logger().warn(f'Frame not found: {ex}')
except ConnectivityException as ex:
    self.get_logger().warn(f'No path: {ex}')
except ExtrapolationException as ex:
    self.get_logger().warn(f'Extrapolation: {ex}')

9. 성능 고려 사항

9.1 조회 복잡도

lookupTransform()의 시간 복잡도는 변환 트리에서 두 프레임 간의 경로 길이에 비례한다. 일반적인 로봇 시스템에서 경로 길이는 수 단계 이내이므로, 조회 비용은 실질적으로 O(1)에 가깝다.

9.2 캐시 크기와 메모리

각 프레임 쌍에 대한 캐시 메모리 사용량은 다음과 같이 추정할 수 있다.

M \approx N_{frames} \times f_{pub} \times T_{cache} \times S_{entry}

여기서:

  • N_{frames}: 프레임 쌍의 수
  • f_{pub}: 발행 주파수 (Hz)
  • T_{cache}: 캐시 기간 (초)
  • S_{entry}: 캐시 항목 당 크기 (약 100바이트)

9.3 조회 빈도 최적화

고주기 루프에서 lookupTransform()을 호출하는 경우, canTransform()을 먼저 호출하여 예외 발생을 방지하는 것이 효율적이다. 예외 처리(exception handling)의 오버헤드가 canTransform() 호출보다 크기 때문이다.

10. 참고 자료

  • tf2 API 문서, https://docs.ros2.org/latest/api/tf2/
  • tf2_ros API 문서, https://docs.ros2.org/latest/api/tf2_ros/
  • ROS2 공식 문서, “Using tf2 in ROS2”, https://docs.ros.org/en/humble/Tutorials/Intermediate/Tf2/Tf2-Main.html
  • ROS2 Humble Hawksbill