659.118 ExtrapolationException 처리 (Handling ExtrapolationException)

1. ExtrapolationException의 정의와 발생 조건

tf2::ExtrapolationExceptionlookupTransform() 호출 시, 요청된 시각(timestamp)이 tf2::Buffer에 캐시된 변환 데이터의 시간 범위를 벗어나는 경우에 발생하는 예외이다. TF2의 보간(interpolation) 메커니즘은 캐시된 두 변환 샘플 사이의 시각에 대해서만 변환 값을 정확하게 계산할 수 있으며, 캐시된 시간 범위 밖의 시각에 대해서는 외삽(extrapolation)이 필요하나, TF2는 기본적으로 외삽을 허용하지 않으므로 예외를 발생시킨다.

ExtrapolationException이 발생하는 두 가지 주요 조건은 다음과 같다.

1.1 미래 시각 외삽 (Forward Extrapolation)

요청된 시각이 Buffer에 저장된 가장 최근 변환의 타임스탬프보다 미래인 경우이다. 이는 센서 데이터의 타임스탬프가 변환 발행자의 최신 타임스탬프보다 앞서 있을 때 발생한다.

t_{\text{request}} > t_{\text{latest}}

1.2 과거 시각 외삽 (Backward Extrapolation)

요청된 시각이 Buffer에 저장된 가장 오래된 변환의 타임스탬프보다 과거인 경우이다. 이는 캐시 기간(cache duration)이 만료되어 오래된 변환이 삭제되었거나, 시스템 기동 직후 충분한 변환 이력이 축적되지 않은 경우에 발생한다.

t_{\text{request}} < t_{\text{oldest}}

2. 예외 메시지의 구조와 해석

ExtrapolationExceptionwhat() 메서드는 구체적인 시간 정보를 포함하는 오류 메시지를 반환한다. 전형적인 메시지의 형태는 다음과 같다.

미래 시각 외삽의 경우:

Lookup would require extrapolation into the future. Requested time 1679012345.500
but the latest data is at time 1679012345.200, when looking up transform from
frame [base_link] to frame [map].

과거 시각 외삽의 경우:

Lookup would require extrapolation into the past. Requested time 1679012335.000
but the earliest data is at time 1679012340.000, when looking up transform from
frame [base_link] to frame [map].

이 메시지에서 추출할 수 있는 진단 정보는 다음과 같다.

정보해석
요청 시각 (Requested time)lookupTransform()에 전달된 타임스탬프
가용 시각 (latest data 또는 earliest data)Buffer에 캐시된 변환의 시간 경계
시간 차이요청 시각과 가용 시각 간의 절대 차이
프레임 쌍조회가 실패한 변환의 프레임 경로상 해당 구간

3. 발생 원인의 체계적 분류

3.1 센서-변환 간 타이밍 불일치

로봇 시스템에서 가장 빈번한 원인은 센서 데이터의 타임스탬프와 변환 발행 타임스탬프 간의 불일치이다. 센서 드라이버가 데이터를 취득한 시점의 타임스탬프를 부여하고, 해당 데이터의 좌표 변환을 위해 lookupTransform()을 호출할 때 센서 타임스탬프를 시간 인자로 전달하면, 변환 발행자의 최신 타임스탬프가 아직 해당 시각에 도달하지 않았을 수 있다.

이 불일치는 다음의 요인에 의해 발생한다.

  1. 클럭 동기화 미비: 센서와 변환 발행 노드가 서로 다른 시계 소스를 사용하는 경우
  2. 처리 지연: 변환 발행 노드의 처리 지연으로 인해 변환 타임스탬프가 센서 타임스탬프보다 늦게 갱신되는 경우
  3. 네트워크 전송 지연: DDS를 통한 /tf 메시지의 전달이 센서 데이터의 전달보다 늦는 경우

3.2 발행 주파수 부족

변환 발행 주파수가 센서 데이터 주파수보다 현저히 낮으면, 연속적인 변환 샘플 간의 시간 간격이 커져 센서 타임스탬프가 최신 변환 타임스탬프를 벗어나는 빈도가 증가한다.

3.3 캐시 기간 초과

과거 시각의 변환을 조회할 때, 해당 시각이 Buffer의 캐시 기간(기본값 10초)을 초과한 과거이면 변환 데이터가 이미 삭제되어 ExtrapolationException이 발생한다.

3.4 시뮬레이션 시간 불일치

시뮬레이터 환경에서 use_sim_time 파라미터가 올바르게 설정되지 않으면, 일부 노드는 시스템 시간(wall time)을, 다른 노드는 시뮬레이션 시간(sim time)을 사용하게 되어 시간 기준의 불일치가 발생한다. 이 불일치는 외삽 예외의 빈번한 원인이 된다.

4. 처리 패턴

4.1 TimePointZero를 이용한 최신 변환 조회

특정 시각의 정밀한 변환이 필요하지 않은 경우, tf2::TimePointZero(시간 0)를 요청 시각으로 전달하면 Buffer에 저장된 가장 최근의 변환이 반환된다. 이 방법은 ExtrapolationException의 발생 가능성을 크게 줄여 준다.

// ExtrapolationException 방지: 최신 가용 변환 사용
auto transform = tf_buffer_->lookupTransform(
    "map", "base_link", tf2::TimePointZero);

그러나 이 방법은 센서 데이터와 변환 간의 시간적 일관성(temporal consistency)을 포기하는 것이므로, 센서 데이터의 정밀한 좌표 변환이 요구되는 경우에는 적합하지 않다.

4.2 타임아웃 기반 대기 조회

lookupTransform()에 타임아웃 인자를 전달하면, 요청된 시각의 변환이 Buffer에 수신될 때까지 지정된 시간만큼 대기한다. 이 방법은 센서 데이터와 변환 간의 시간 정밀성을 유지하면서 일시적인 타이밍 불일치를 해소할 수 있다.

// 최대 100ms 대기하여 변환 조회
try {
    auto transform = tf_buffer_->lookupTransform(
        "map", "base_link", sensor_timestamp,
        std::chrono::milliseconds(100));
    processTransform(transform);
} catch (const tf2::ExtrapolationException& ex) {
    RCLCPP_WARN(this->get_logger(),
        "대기 후에도 변환 외삽 실패: %s", ex.what());
}

이 패턴은 MultiThreadedExecutor 환경에서만 안전하게 사용할 수 있다. SingleThreadedExecutor에서는 대기 중에 TransformListener의 수신 콜백이 실행될 수 없으므로 교착 상태가 발생한다.

4.3 허용 오차 기반 조회

요청 시각과 가용 변환 시각 간의 소폭의 차이를 허용하는 패턴이다. lookupTransform()은 내부적으로 보간을 수행하므로, 요청 시각이 최신 변환 타임스탬프를 미세하게 초과하는 경우에도 허용 범위 내에서 외삽을 수행하도록 하는 방법이 필요하다.

TF2의 기본 구현에서는 외삽 허용 범위(tf2::EXTRAPOLATION_TOLERANCE)가 설정되어 있으나, 그 값은 매우 작다(수 마이크로초 수준). 이 허용 범위를 초과하는 요청은 예외를 발생시킨다. 사용자 수준에서 이를 처리하는 방법은 요청 시각을 미세하게 조정하는 것이다.

// 센서 타임스탬프에서 소폭의 여유를 두어 조회
rclcpp::Time adjusted_time = sensor_timestamp 
    - rclcpp::Duration::from_seconds(0.01);  // 10ms 이전

try {
    auto transform = tf_buffer_->lookupTransform(
        "map", "base_link", adjusted_time);
    processTransform(transform);
} catch (const tf2::ExtrapolationException& ex) {
    RCLCPP_WARN(this->get_logger(), "%s", ex.what());
}

4.4 tf2_ros::MessageFilter를 이용한 동기화

센서 메시지와 변환 데이터 간의 시간적 동기화를 자동으로 관리하려면 tf2_ros::MessageFilter를 활용한다. MessageFilter는 수신된 센서 메시지를 내부 큐에 일시 저장하고, 해당 메시지의 타임스탬프에 대한 변환이 Buffer에 수신되면 콜백을 호출한다.

#include <tf2_ros/message_filter.h>
#include <message_filters/subscriber.h>
#include <sensor_msgs/msg/point_cloud2.hpp>

// 메시지 필터 구성
message_filters::Subscriber<sensor_msgs::msg::PointCloud2> cloud_sub(
    this, "input_cloud");

tf2_ros::MessageFilter<sensor_msgs::msg::PointCloud2> tf_filter(
    cloud_sub, *tf_buffer_, "map", 100, this->get_node_logging_interface(),
    this->get_node_clock_interface());

tf_filter.registerCallback(
    std::bind(&Node::cloudCallback, this, std::placeholders::_1));

이 패턴은 ExtrapolationException의 발생을 원천적으로 방지하면서도, 센서 데이터와 변환 간의 시간적 일관성을 완전히 보장하는 가장 권장되는 방법이다.

4.5 Python에서의 ExtrapolationException 처리

from tf2_ros import ExtrapolationException
import rclpy

try:
    transform = self.tf_buffer.lookup_transform(
        'map', 'base_link', sensor_msg.header.stamp)
except ExtrapolationException as ex:
    self.get_logger().warn(
        f'시간 범위 초과: {ex}')
    # 대체 전략: 최신 가용 변환 사용
    try:
        transform = self.tf_buffer.lookup_transform(
            'map', 'base_link', rclpy.time.Time())
    except Exception:
        return

5. 근본적 방지 전략

5.1 발행 주파수와 소비 주파수의 정합

변환 발행 주파수 f_p가 변환 소비자의 조회 주파수 f_c 이상이 되도록 설정하면, 최신 변환이 항상 센서 데이터의 타임스탬프에 근접하거나 앞서게 되어 미래 시각 외삽의 발생 빈도가 감소한다.

f_p \geq f_c

5.2 클럭 동기화 보장

ROS2 시스템 내 모든 노드가 동일한 시계 소스를 사용하도록 보장한다. 분산 시스템에서는 NTP(Network Time Protocol) 또는 PTP(Precision Time Protocol)를 통해 노드 간 클럭 동기화를 유지하여야 한다. 시뮬레이션 환경에서는 모든 노드에 use_sim_time: true 파라미터를 일관되게 적용하여야 한다.

5.3 캐시 기간 조정

과거 시각의 변환 조회가 빈번한 경우, tf2::Buffer의 캐시 기간을 확장하여 과거 시각 외삽을 방지한다.

// 캐시 기간을 30초로 확장
auto tf_buffer = std::make_unique<tf2_ros::Buffer>(
    this->get_clock(),
    tf2::Duration(std::chrono::seconds(30)));

캐시 기간의 확장은 메모리 사용량의 증가를 수반하므로, 시스템의 가용 메모리와 변환 채널 수, 발행 주파수를 고려하여 적정 값을 결정하여야 한다.

6. 시간 차이의 정량적 진단

ExtrapolationException이 발생할 때 요청 시각과 가용 시각 간의 차이를 정량적으로 파악하면, 근본 원인의 식별에 도움이 된다.

시간 차이 범위의심 원인권장 조치
< 10 ms발행 주파수 대비 미세한 타이밍 차이TimePointZero 사용 또는 MessageFilter 적용
10–100 ms발행 주파수 부족 또는 네트워크 지연발행 주파수 증가, 네트워크 점검
100 ms – 1 s심각한 처리 지연 또는 노드 과부하CPU 부하 분석, 노드 분리
> 1 s클럭 불일치 또는 use_sim_time 오설정시계 동기화 점검
수십 초 이상캐시 기간 초과캐시 기간 확장

참고 문헌 및 출처

  • ROS2 geometry2 리포지터리, tf2tf2_ros 패키지, https://github.com/ros2/geometry2
  • Foote, T. (2013). “tf: The transform library.” IEEE International Conference on Technologies for Practical Robot Applications (TePRA), pp. 1–6.
  • ROS2 공식 문서, TF2 튜토리얼, https://docs.ros.org/en/humble/Tutorials/Intermediate/Tf2/Tf2-Main.html
  • Lu, D.V., Hershberger, D., Smart, W.D. (2014). “Layered Costmaps for Context-Sensitive Navigation.” IEEE/RSJ International Conference on Intelligent Robots and Systems (IROS).

버전: 1.0