659.36 canTransform()을 이용한 변환 가용성 확인

1. 개요

canTransform()은 TF2 라이브러리에서 두 좌표 프레임 사이의 변환이 현재 조회 가능한 상태인지를 사전에 확인하는 메서드이다. lookupTransform()이 변환을 실제로 조회하여 결과를 반환하는 반면, canTransform()은 변환의 가용 여부만을 불리언(boolean) 값으로 반환한다. 이를 통해 예외가 발생하는 비용을 회피하고, 조건부 로직을 구현할 수 있다. 특히, 시스템 초기화 단계에서 필요한 변환이 아직 발행되지 않은 경우, 또는 네트워크 지연으로 인해 변환 데이터가 불완전한 경우에 매우 유용하다.

2. canTransform()의 함수 시그니처

2.1 rclcpp 기본 오버로드

bool canTransform(
    const std::string & target_frame,
    const std::string & source_frame,
    const tf2::TimePoint & time,
    std::string * error_msg = nullptr) const;

2.1.1 파라미터 상세

파라미터자료형설명
target_frameconst std::string &변환의 타깃 좌표 프레임 이름
source_frameconst std::string &변환의 소스 좌표 프레임 이름
timeconst tf2::TimePoint &변환을 조회할 시간
error_msgstd::string *(선택) 변환 불가 시 오류 사유를 저장할 문자열 포인터

error_msg 파라미터: 이 파라미터는 선택적(optional)이며, nullptr이 아닌 유효한 포인터를 전달하면, 변환이 불가능한 경우 그 사유를 문자열로 기록한다. 이는 디버깅 시 유용하며, 변환 실패의 원인을 로그로 출력할 때 활용한다.

2.2 rclcpp 타임아웃 포함 오버로드

bool canTransform(
    const std::string & target_frame,
    const std::string & source_frame,
    const tf2::TimePoint & time,
    const tf2::Duration & timeout,
    std::string * error_msg = nullptr) const;

타임아웃을 지정하면, 변환이 가용해질 때까지 지정된 시간만큼 대기한다. 대기 중에 변환이 가용해지면 즉시 true를 반환하고, 타임아웃이 만료되면 false를 반환한다.

2.3 rclcpp 시간 여행 오버로드

bool canTransform(
    const std::string & target_frame,
    const tf2::TimePoint & target_time,
    const std::string & source_frame,
    const tf2::TimePoint & source_time,
    const std::string & fixed_frame,
    std::string * error_msg = nullptr) const;

이 오버로드는 서로 다른 시간에서의 변환 가용성을 확인한다. lookupTransform()의 시간 여행(time travel) 오버로드에 대응하며, fixed_frame을 매개로 두 시간 사이의 변환이 가능한지를 판정한다.

2.4 rclpy 기본 시그니처

def can_transform(
    self,
    target_frame: str,
    source_frame: str,
    time: rclpy.time.Time,
    timeout: rclpy.duration.Duration = Duration(),
    return_debug_tuple: bool = False
) -> Union[bool, Tuple[bool, str]]:
파라미터자료형기본값설명
target_framestr(필수)타깃 좌표 프레임 이름
source_framestr(필수)소스 좌표 프레임 이름
timerclpy.time.Time(필수)조회 시간
timeoutrclpy.duration.DurationDuration()대기 시간
return_debug_tupleboolFalse디버그 정보 포함 여부

return_debug_tupleTrue로 설정하면, 반환값이 (bool, str) 튜플이 되며, 두 번째 요소에 오류 메시지가 포함된다.

2.5 rclpy 시간 여행 시그니처

def can_transform_full(
    self,
    target_frame: str,
    target_time: rclpy.time.Time,
    source_frame: str,
    source_time: rclpy.time.Time,
    fixed_frame: str,
    timeout: rclpy.duration.Duration = Duration(),
    return_debug_tuple: bool = False
) -> Union[bool, Tuple[bool, str]]:

3. 내부 동작 메커니즘

3.1 검증 절차

canTransform()은 내부적으로 다음과 같은 순차적 검증을 수행한다.

  1. 프레임 이름 유효성 검증: target_framesource_frame이 빈 문자열이 아닌지 확인한다. 빈 문자열이 전달되면 false를 반환하고, error_msg에 사유를 기록한다. 이는 lookupTransform()이 예외를 발생시키는 것과 대비된다.
  2. 프레임 존재 확인: 두 프레임이 변환 트리에 등록되어 있는지 확인한다. 아직 등록되지 않은 프레임이 있으면 false를 반환한다.
  3. 연결성 확인: 두 프레임 사이에 유효한 경로가 존재하는지 확인한다. 서로 다른 트리에 속하여 연결 경로가 없으면 false를 반환한다.
  4. 시간 범위 확인: 지정된 시간이 캐시에 저장된 변환의 시간 범위 내에 있는지 확인한다. 시간이 범위를 벗어나면 false를 반환한다.
  5. 보간 가능성 확인: 지정된 시간에 대해 보간(interpolation)이 가능한지, 즉 해당 시간 전후에 유효한 변환 데이터가 존재하는지 확인한다.

3.2 lookupTransform()과의 차이

특성canTransform()lookupTransform()
반환값bool (또는 튜플)TransformStamped
예외 발생발생하지 않음실패 시 예외 발생
연산 비용가용성 확인만 수행 (상대적으로 경량)실제 변환 계산 수행
용도사전 확인, 조건부 로직변환 데이터 취득

canTransform()은 예외를 발생시키지 않으므로, 예외 처리의 오버헤드(overhead)가 없다. 그러나 변환 가용 여부만 확인하므로, 실제 변환 데이터가 필요한 경우에는 lookupTransform()을 반드시 호출해야 한다.

4. 사용 패턴

4.1 패턴 1: 단순 사전 확인

가장 기본적인 사용 패턴으로, lookupTransform() 호출 전에 변환 가용성을 확인한다.

if (tf_buffer_->canTransform("map", "base_link", tf2::TimePointZero)) {
    auto t = tf_buffer_->lookupTransform(
        "map", "base_link", tf2::TimePointZero);
    processTransform(t);
} else {
    RCLCPP_INFO(get_logger(),
        "Transform map -> base_link not yet available");
}

4.2 패턴 2: 오류 사유 로깅

error_msg 파라미터를 활용하여 변환 실패 사유를 로그에 기록한다.

std::string error_msg;
if (!tf_buffer_->canTransform(
        "map", "base_link", tf2::TimePointZero, &error_msg)) {
    RCLCPP_WARN(get_logger(),
        "Cannot transform: %s", error_msg.c_str());
    return;
}

auto t = tf_buffer_->lookupTransform(
    "map", "base_link", tf2::TimePointZero);

4.3 패턴 3: 타임아웃을 이용한 초기화 대기

시스템 초기화 단계에서 필요한 변환이 발행될 때까지 대기하는 패턴이다.

// 시스템 초기화 시 최대 10초까지 변환 대기
std::string error_msg;
bool transform_ready = tf_buffer_->canTransform(
    "map", "base_link",
    tf2::TimePointZero,
    tf2::durationFromSec(10.0),
    &error_msg);

if (transform_ready) {
    RCLCPP_INFO(get_logger(), "Transform is now available");
    initializeNavigation();
} else {
    RCLCPP_ERROR(get_logger(),
        "Transform not available after 10s: %s",
        error_msg.c_str());
    return;
}

4.4 패턴 4: rclpy에서의 디버그 튜플 활용

can_transform, error_msg = self.tf_buffer.can_transform(
    'map',
    'base_link',
    Time(),
    timeout=Duration(seconds=1.0),
    return_debug_tuple=True)

if can_transform:
    t = self.tf_buffer.lookup_transform(
        'map', 'base_link', Time())
    self.process_transform(t)
else:
    self.get_logger().warn(
        f'Transform not available: {error_msg}')

4.5 패턴 5: 다중 프레임 가용성 확인

여러 좌표 프레임 간의 변환이 모두 가용한지를 확인하는 패턴이다.

bool allTransformsAvailable()
{
    std::vector<std::pair<std::string, std::string>> required_transforms = {
        {"map", "odom"},
        {"odom", "base_link"},
        {"base_link", "camera_link"},
        {"base_link", "lidar_link"}
    };

    for (const auto & [target, source] : required_transforms) {
        std::string error;
        if (!tf_buffer_->canTransform(
                target, source, tf2::TimePointZero, &error)) {
            RCLCPP_DEBUG(get_logger(),
                "Missing transform %s -> %s: %s",
                target.c_str(), source.c_str(), error.c_str());
            return false;
        }
    }
    return true;
}

5. TOCTOU 경합 조건에 대한 주의사항

canTransform()lookupTransform()을 순차적으로 호출하는 패턴에는 TOCTOU(Time of Check to Time of Use) 경합 조건이 존재한다. canTransform()true를 반환한 시점과 lookupTransform()이 실행되는 시점 사이에 변환이 캐시에서 만료될 수 있다. 따라서 canTransform()을 사용하더라도, lookupTransform() 호출 시 예외 처리를 병행하는 것이 방어적 프로그래밍(defensive programming)의 관점에서 권장된다.

if (tf_buffer_->canTransform("map", "base_link", tf2::TimePointZero)) {
    try {
        auto t = tf_buffer_->lookupTransform(
            "map", "base_link", tf2::TimePointZero);
        processTransform(t);
    } catch (const tf2::TransformException & ex) {
        // TOCTOU 경합에 대비한 예외 처리
        RCLCPP_WARN(get_logger(),
            "Race condition: %s", ex.what());
    }
}

6. canTransform()의 성능 특성

canTransform()lookupTransform()에 비해 연산 비용이 낮다. 이는 실제 변환 합성(composition)과 보간(interpolation) 연산을 수행하지 않고, 가용 여부만 확인하기 때문이다. 그러나 타임아웃을 지정한 경우에는 블로킹 호출이 되어.호출 스레드가 대기 상태에 놓일 수 있으므로, 실시간 제어 루프에서는 제로 타임아웃 또는 타임아웃 없는 오버로드를 사용하는 것이 바람직하다.

6.1 빈도별 성능 지침

호출 빈도타임아웃 설정권장 사용 시나리오
고빈도 (>100 Hz)제로 타임아웃실시간 제어 루프 내부
중빈도 (10~100 Hz)짧은 타임아웃 (10~50 ms)센서 데이터 처리 콜백
저빈도 (<10 Hz)긴 타임아웃 (0.5~5 s)시스템 초기화, 상태 점검

7. 활용 사례

7.1 조건부 센서 퓨전

여러 센서의 데이터를 퓨전할 때, 모든 센서의 좌표 변환이 가용한 경우에만 퓨전을 수행한다.

void sensorFusionCallback(const sensor_msgs::msg::LaserScan::SharedPtr scan)
{
    if (!tf_buffer_->canTransform(
            "base_link", scan->header.frame_id,
            scan->header.stamp)) {
        RCLCPP_DEBUG(get_logger(),
            "Waiting for transform to fuse scan data");
        return;  // 변환 불가 시 퓨전 건너뜀
    }

    auto t = tf_buffer_->lookupTransform(
        "base_link", scan->header.frame_id,
        scan->header.stamp);
    fuseScanData(scan, t);
}

7.2 시스템 진단

로봇 시스템의 변환 트리 상태를 주기적으로 점검하는 진단(diagnostic) 기능에 활용한다.

void diagnosticCheck()
{
    diagnostic_msgs::msg::DiagnosticStatus status;
    status.name = "TF2 Transform Health";

    std::vector<std::pair<std::string, std::string>> critical_frames = {
        {"map", "odom"},
        {"odom", "base_link"}
    };

    bool all_ok = true;
    for (const auto & [target, source] : critical_frames) {
        std::string error;
        if (!tf_buffer_->canTransform(
                target, source, tf2::TimePointZero, &error)) {
            status.message += target + " -> " + source + ": " + error + "; ";
            all_ok = false;
        }
    }

    status.level = all_ok ?
        diagnostic_msgs::msg::DiagnosticStatus::OK :
        diagnostic_msgs::msg::DiagnosticStatus::WARN;
}

8. 요약

canTransform()은 변환 조회의 사전 가용성 확인을 위한 비예외(non-throwing) 메서드로, 조건부 로직 구현, 시스템 초기화 대기, 진단 기능 등에 핵심적으로 활용된다. 선택적 error_msg 파라미터를 통해 실패 사유를 확인할 수 있으며, 타임아웃을 지정하여 변환 대기도 가능하다. 다만, TOCTOU 경합 조건이 존재하므로 lookupTransform()의 예외 처리를 병행하는 것이 안전하다.


참고 문헌 및 출처

  • ROS 2 공식 문서, “tf2_ros::Buffer Class Reference,” https://docs.ros2.org/latest/api/tf2_ros/classtf2__ros_1_1Buffer.html (ROS 2 Humble Hawksbill)
  • Open Robotics, “tf2 Tutorials,” https://docs.ros.org/en/humble/Tutorials/Intermediate/Tf2/Tf2-Main.html
  • Foote, T., “tf: The Transform Library,” Proceedings of IEEE Conference on Technologies for Practical Robot Applications (TePRA), 2013.
  • ROS 2 소스 코드, “tf2_ros/buffer.h,” https://github.com/ros2/geometry2 (geometry2 리포지토리)