659.116 LookupException 처리 (Handling LookupException)

1. LookupException의 정의와 발생 조건

tf2::LookupExceptionlookupTransform() 또는 canTransform() 호출 시, 요청된 프레임 ID(frame_id)가 tf2::Buffer의 내부 프레임 테이블에 등록되어 있지 않을 때 발생하는 예외이다. 이 예외는 tf2::TransformException으로부터 파생되며, 변환 트리에서 요청된 프레임을 식별(lookup)할 수 없음을 의미한다.

LookupException이 발생하는 구체적 조건은 다음과 같다.

  1. 미등록 프레임 조회: 목표 프레임(target frame) 또는 원본 프레임(source frame)으로 지정된 프레임 ID가 tf2::Buffer에 단 한 번도 등록된 적이 없는 경우
  2. 발행 지연: 해당 프레임의 변환을 발행하는 노드가 아직 기동되지 않았거나, 첫 번째 변환 메시지가 수신되기 이전에 조회가 시도된 경우
  3. 프레임 ID 오류: 프레임 이름의 철자 오류, 네임스페이스 접두사 누락, 또는 대소문자 불일치로 인해 의도한 프레임과 다른 이름으로 조회가 수행된 경우

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

LookupExceptionwhat() 메서드는 오류의 원인을 설명하는 문자열을 반환한다. 전형적인 오류 메시지의 형태는 다음과 같다.

"target_frame" passed to lookupTransform argument target_frame does not exist.

또는 타임스탬프가 지정된 경우:

"sensor_frame" passed to lookupTransform argument source_frame does not exist.

이 메시지로부터 다음의 정보를 추출할 수 있다.

정보해석
존재하지 않는 프레임 이름조회가 실패한 구체적 프레임 ID
인자 위치 (target_frame 또는 source_frame)목표 프레임과 원본 프레임 중 어느 쪽이 미등록인지

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

3.1 시간적 원인: 시스템 기동 순서

로봇 시스템의 기동 과정에서 변환 소비자 노드가 변환 발행자 노드보다 먼저 초기화되면, 변환이 아직 발행되지 않은 프레임에 대해 LookupException이 발생한다. 이는 과도 현상(transient behavior)으로, 발행자 노드가 활성화되어 첫 번째 변환 메시지가 수신되면 자연적으로 해소된다.

대표적 사례:

  • robot_state_publisher가 기동되기 전에 내비게이션 노드가 base_linklidar_link 변환을 조회하는 경우
  • AMCL 또는 SLAM 노드가 활성화되기 전에 map 프레임이 조회되는 경우
  • 외부 카메라 캘리브레이션 노드가 기동되기 전에 camera_optical_frame이 요청되는 경우

3.2 구성적 원인: 프레임 이름 불일치

가장 빈번한 구성적 원인은 프레임 이름의 불일치이다. ROS2에서 프레임 ID는 대소문자를 구분하는 문자열이며, 선행 슬래시(/)의 유무도 구별된다. TF2에서는 선행 슬래시가 없는 프레임 ID가 권장되나, 레거시 코드에서는 슬래시가 포함된 프레임 ID가 사용되는 경우가 있다.

발행된 프레임 ID조회된 프레임 ID일치 여부
base_linkbase_link일치
base_linkBase_Link불일치
base_link/base_link불일치
robot1/base_linkbase_link불일치

3.3 구조적 원인: 누락된 변환 발행자

변환 트리에서 특정 구간의 변환을 발행하는 노드가 런치 파일에 포함되지 않았거나, 조건부 실행으로 인해 비활성 상태인 경우 해당 프레임이 등록되지 않아 LookupException이 발생한다.

4. 진단 절차

4.1 현재 등록된 프레임 목록 확인

# 현재 변환 트리에 등록된 모든 프레임 시각화
ros2 run tf2_tools view_frames

# 특정 프레임의 존재 여부 확인
ros2 run tf2_ros tf2_echo target_frame source_frame

view_frames는 현재 /tf/tf_static 토픽에서 수신된 모든 변환을 기반으로 변환 트리를 PDF 파일로 생성한다. 이 트리에 요청한 프레임이 존재하지 않으면, 해당 프레임의 변환이 발행되고 있지 않음을 확인할 수 있다.

4.2 변환 발행 노드 확인

# /tf 토픽의 발행자 목록 확인
ros2 topic info /tf --verbose

# /tf_static 토픽의 발행자 목록 확인
ros2 topic info /tf_static --verbose

# 특정 노드의 상태 확인
ros2 node info /robot_state_publisher

4.3 프레임 이름 비교

개발자가 의도한 프레임 이름과 실제 시스템에 등록된 프레임 이름을 비교하여 불일치를 식별한다.

# 실시간으로 수신되는 모든 프레임 ID 나열
ros2 topic echo /tf --field transforms
ros2 topic echo /tf_static --field transforms

출력에서 header.frame_idchild_frame_id 필드의 값을 확인하여, 코드에서 사용하는 프레임 이름과의 일치 여부를 검증한다.

5. 처리 패턴

5.1 기동 시 과도 현상에 대한 우아한 대기 패턴

시스템 기동 직후의 일시적 LookupException에 대해서는/재시도 기반 대기 패턴이 적합하다.

void onTimer()
{
    try {
        auto transform = tf_buffer_->lookupTransform(
            "map", "base_link", tf2::TimePointZero);
        processTransform(transform);
    } catch (const tf2::LookupException& ex) {
        if (!initialization_complete_) {
            RCLCPP_INFO_THROTTLE(
                this->get_logger(), *this->get_clock(), 2000,
                "기동 중, 프레임 대기: %s", ex.what());
            return;
        }
        // 기동 완료 후 발생하면 구성 오류로 판단
        RCLCPP_ERROR(this->get_logger(),
            "프레임 조회 실패 (구성 점검 필요): %s", ex.what());
    }
}

5.2 프레임 존재 사전 확인 패턴

주기적 콜백에서 매번 예외를 발생시키는 비용을 회피하려면, 프레임의 등록 여부를 사전에 확인하는 패턴을 적용할 수 있다. tf2::Buffer_frameExists() 메서드(C++) 또는 canTransform()의 오류 메시지를 활용한다.

std::string error_msg;
if (!tf_buffer_->canTransform("map", "base_link",
                               tf2::TimePointZero, &error_msg)) {
    if (error_msg.find("does not exist") != std::string::npos) {
        RCLCPP_WARN_THROTTLE(
            this->get_logger(), *this->get_clock(), 5000,
            "프레임 미등록: %s", error_msg.c_str());
        return;
    }
}

5.3 Python에서의 LookupException 처리

from tf2_ros import LookupException

try:
    transform = self.tf_buffer.lookup_transform(
        'map', 'base_link', rclpy.time.Time())
except LookupException as ex:
    self.get_logger().warn(
        f'프레임 조회 실패: {ex}',
        throttle_duration_sec=2.0)
    return

6. 방지 전략

6.1 프레임 이름의 중앙 집중 관리

프레임 이름을 소스 코드에 문자열 리터럴로 분산 작성하면 오타에 의한 LookupException 발생 위험이 높아진다. 프레임 이름을 상수(constant)로 중앙 집중적으로 정의하고, 모든 노드에서 해당 상수를 참조하는 패턴을 적용하면 이 위험을 최소화할 수 있다.

// frame_ids.hpp - 프레임 이름 중앙 정의
namespace robot_frames
{
    constexpr char MAP[] = "map";
    constexpr char ODOM[] = "odom";
    constexpr char BASE_LINK[] = "base_link";
    constexpr char LIDAR_LINK[] = "lidar_link";
    constexpr char CAMERA_LINK[] = "camera_link";
    constexpr char CAMERA_OPTICAL[] = "camera_optical_frame";
}

// 사용 측
auto transform = tf_buffer_->lookupTransform(
    robot_frames::MAP, robot_frames::BASE_LINK, tf2::TimePointZero);

6.2 파라미터 기반 프레임 이름 설정

프레임 이름을 ROS2 파라미터로 외부화하면, 소스 코드 수정 없이 런치 파일이나 YAML 설정 파일에서 프레임 이름을 변경할 수 있다. 이는 다중 로봇 환경에서 네임스페이스 접두사가 적용되는 경우에 특히 유용하다.

this->declare_parameter("target_frame", "map");
this->declare_parameter("source_frame", "base_link");

std::string target = this->get_parameter("target_frame").as_string();
std::string source = this->get_parameter("source_frame").as_string();

auto transform = tf_buffer_->lookupTransform(target, source,
                                              tf2::TimePointZero);

6.3 런치 파일에서의 의존성 순서 명시

ROS2 런치 시스템에서 변환 발행 노드가 변환 소비 노드보다 먼저 기동되도록 의존성 순서를 명시하면, 기동 시 과도 현상의 영향을 최소화할 수 있다.

from launch.actions import TimerAction

# robot_state_publisher를 먼저 기동
robot_state_pub = Node(
    package='robot_state_publisher',
    executable='robot_state_publisher',
    parameters=[{'robot_description': robot_desc}],
)

# 2초 후 내비게이션 스택 기동
nav_stack = TimerAction(
    period=2.0,
    actions=[
        Node(
            package='nav2_bringup',
            executable='navigation_launch.py',
        ),
    ],
)

그러나 고정된 지연 시간에 의존하는 것은 견고하지 않으므로, 소비자 노드 내에서 필수 프레임의 가용성을 확인하는 초기화 로직을 병행하는 것이 바람직하다.

7. 오류 메시지 분석을 통한 근본 원인 식별 흐름도

LookupException이 발생하였을 때, 다음의 단계적 점검 절차를 통해 근본 원인을 식별한다.

  1. view_frames로 현재 변환 트리를 확인한다.
  2. 요청한 프레임 ID가 트리에 존재하는지 확인한다.
  3. 존재하지 않으면, 해당 프레임을 발행하는 노드가 실행 중인지 ros2 node list로 확인한다.
  4. 노드가 실행 중이면, 해당 노드가 발행하는 프레임 이름을 ros2 topic echo /tf로 확인하여 이름 불일치 여부를 점검한다.
  5. 노드가 실행 중이 아니면, 런치 파일의 구성을 점검하여 해당 노드의 포함 여부를 확인한다.

참고 문헌 및 출처

  • ROS2 geometry2 리포지터리, tf2 패키지, 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.
  • REP 105 – Coordinate Frames for Mobile Platforms, https://www.ros.org/reps/rep-0105.html
  • ROS2 공식 문서, TF2 튜토리얼, https://docs.ros.org/en/humble/Tutorials/Intermediate/Tf2/Tf2-Main.html

버전: 1.0