659.62 tf2_geometry_msgs를 이용한 메시지 변환

1. 개요

tf2_geometry_msgs는 ROS2의 geometry_msgs 패키지에 정의된 기하학적 메시지 타입을 TF2 프레임워크와 연동하여 좌표 변환을 수행할 수 있도록 하는 인터페이스 패키지이다. 이 패키지는 TF2의 핵심 변환 메커니즘인 tf2::doTransform() 함수 템플릿의 특수화(template specialization)를 제공함으로써, geometry_msgs 타입의 메시지를 소스 좌표 프레임(source frame)에서 대상 좌표 프레임(target frame)으로 투명하게 변환할 수 있게 한다.

2. tf2_geometry_msgs의 아키텍처

2.1 TF2 변환 확장 메커니즘

TF2는 핵심 변환 기능과 데이터 타입별 변환 기능을 분리하는 확장 가능한 아키텍처를 채택하고 있다. 핵심 라이브러리인 tf2tf2_ros는 변환 트리(transform tree)의 관리, 변환 발행(broadcast), 변환 조회(lookup) 등의 기본 기능을 제공하며, 특정 메시지 타입에 대한 변환 로직은 별도의 확장 패키지에서 구현된다.

tf2_geometry_msgs는 이 확장 메커니즘의 대표적 구현체로서, C++ 템플릿 특수화(template specialization)와 Python 함수 등록 메커니즘을 통해 geometry_msgs 메시지 타입에 대한 doTransform() 함수를 제공한다.

2.2 패키지 구조

tf2_geometry_msgs 패키지의 주요 구성 요소는 다음과 같다.

구성 요소파일 경로역할
C++ 헤더tf2_geometry_msgs/tf2_geometry_msgs.hppC++ doTransform() 템플릿 특수화
Python 모듈tf2_geometry_msgs/tf2_geometry_msgs.pyPython doTransform() 함수 등록
CMakeLists.txtCMakeLists.txt빌드 설정 및 의존성
package.xmlpackage.xml패키지 메타데이터 및 의존성 선언

3. 의존성 설정

3.1 C++ (CMakeLists.txt)

tf2_geometry_msgs를 C++ 프로젝트에서 사용하려면 CMakeLists.txt에 다음 의존성을 추가해야 한다.

find_package(tf2_geometry_msgs REQUIRED)
find_package(tf2 REQUIRED)
find_package(tf2_ros REQUIRED)
find_package(geometry_msgs REQUIRED)

ament_target_dependencies(${PROJECT_NAME}
  tf2
  tf2_ros
  tf2_geometry_msgs
  geometry_msgs
)

3.2 C++ (package.xml)

package.xml에는 다음 의존성을 명시한다.

<depend>tf2</depend>
<depend>tf2_ros</depend>
<depend>tf2_geometry_msgs</depend>
<depend>geometry_msgs</depend>

3.3 Python (package.xml)

Python에서 사용할 경우에도 동일한 package.xml 의존성 선언이 필요하다.

<exec_depend>tf2_geometry_msgs</exec_depend>

4. 핵심 헤더 포함

4.1 C++에서의 헤더 포함

C++에서 tf2_geometry_msgs의 변환 기능을 사용하려면 반드시 해당 헤더를 포함(include)해야 한다. 이 헤더가 포함되지 않으면 doTransform() 함수의 템플릿 특수화가 인식되지 않아 컴파일 오류가 발생한다.

#include <tf2_geometry_msgs/tf2_geometry_msgs.hpp>

이 헤더는 내부적으로 다음의 메시지 타입별 변환 함수를 등록한다.

  • geometry_msgs::msg::Point
  • geometry_msgs::msg::PointStamped
  • geometry_msgs::msg::Vector3
  • geometry_msgs::msg::Vector3Stamped
  • geometry_msgs::msg::Pose
  • geometry_msgs::msg::PoseStamped
  • geometry_msgs::msg::PoseWithCovariance
  • geometry_msgs::msg::PoseWithCovarianceStamped
  • geometry_msgs::msg::Transform
  • geometry_msgs::msg::TransformStamped
  • geometry_msgs::msg::Quaternion
  • geometry_msgs::msg::QuaternionStamped
  • geometry_msgs::msg::Wrench
  • geometry_msgs::msg::WrenchStamped

4.2 Python에서의 모듈 임포트

Python에서는 다음과 같이 모듈을 임포트한다.

from tf2_geometry_msgs import do_transform_point
from tf2_geometry_msgs import do_transform_vector3
from tf2_geometry_msgs import do_transform_pose
from tf2_geometry_msgs import do_transform_pose_stamped
from tf2_geometry_msgs import do_transform_wrench

또는 전체 모듈을 임포트할 수 있다.

import tf2_geometry_msgs

5. 변환 수행의 기본 패턴

5.1 C++ 기본 패턴

tf2_geometry_msgs를 이용한 좌표 변환은 다음의 표준 패턴을 따른다.

#include <rclcpp/rclcpp.hpp>
#include <tf2_ros/buffer.h>
#include <tf2_ros/transform_listener.h>
#include <tf2_geometry_msgs/tf2_geometry_msgs.hpp>
#include <geometry_msgs/msg/point_stamped.hpp>

class TransformNode : public rclcpp::Node
{
public:
  TransformNode() : Node("transform_node")
  {
    tf_buffer_ = std::make_shared<tf2_ros::Buffer>(this->get_clock());
    tf_listener_ = std::make_shared<tf2_ros::TransformListener>(*tf_buffer_);
  }

  geometry_msgs::msg::PointStamped transformPoint(
    const geometry_msgs::msg::PointStamped & input_point,
    const std::string & target_frame)
  {
    geometry_msgs::msg::PointStamped output_point;
    
    try {
      // 변환 조회
      geometry_msgs::msg::TransformStamped transform =
        tf_buffer_->lookupTransform(
          target_frame,
          input_point.header.frame_id,
          input_point.header.stamp);
      
      // 변환 적용
      tf2::doTransform(input_point, output_point, transform);
      
    } catch (const tf2::TransformException & ex) {
      RCLCPP_ERROR(this->get_logger(), "변환 실패: %s", ex.what());
    }
    
    return output_point;
  }

private:
  std::shared_ptr<tf2_ros::Buffer> tf_buffer_;
  std::shared_ptr<tf2_ros::TransformListener> tf_listener_;
};

5.2 Python 기본 패턴

Python에서의 변환 패턴은 다음과 같다.

import rclpy
from rclpy.node import Node
from tf2_ros import Buffer, TransformListener, TransformException
from geometry_msgs.msg import PointStamped
import tf2_geometry_msgs

class TransformNode(Node):
    def __init__(self):
        super().__init__('transform_node')
        self.tf_buffer = Buffer()
        self.tf_listener = TransformListener(self.tf_buffer, self)
    
    def transform_point(self, input_point, target_frame):
        try:
            # 변환 조회
            transform = self.tf_buffer.lookup_transform(
                target_frame,
                input_point.header.frame_id,
                input_point.header.stamp)
            
            # 변환 적용
            output_point = tf2_geometry_msgs.do_transform_point(
                input_point, transform)
            
            return output_point
            
        except TransformException as ex:
            self.get_logger().error(f'변환 실패: {ex}')
            return None

6. Buffer.transform() 메서드를 이용한 간편 변환

6.1 통합 변환 인터페이스

tf2_ros::Buffer 클래스는 변환 조회와 변환 적용을 하나의 호출로 통합하는 transform() 메서드를 제공한다. 이 메서드는 내부적으로 lookupTransform()doTransform()을 순차적으로 호출한다.

6.1.1 C++ 사용법

geometry_msgs::msg::PointStamped input_point;
input_point.header.frame_id = "sensor_link";
input_point.header.stamp = this->now();
input_point.point.x = 1.0;
input_point.point.y = 2.0;
input_point.point.z = 3.0;

geometry_msgs::msg::PointStamped output_point;
try {
  output_point = tf_buffer_->transform(input_point, "base_link");
} catch (const tf2::TransformException & ex) {
  RCLCPP_ERROR(this->get_logger(), "%s", ex.what());
}

6.1.2 Python 사용법

input_point = PointStamped()
input_point.header.frame_id = 'sensor_link'
input_point.header.stamp = self.get_clock().now().to_msg()
input_point.point.x = 1.0
input_point.point.y = 2.0
input_point.point.z = 3.0

try:
    output_point = self.tf_buffer.transform(input_point, 'base_link')
except TransformException as ex:
    self.get_logger().error(f'{ex}')

Buffer.transform() 메서드의 사용이 가능하려면 해당 메시지 타입에 대한 doTransform() 특수화가 등록되어 있어야 하며, 이는 tf2_geometry_msgs 헤더 또는 모듈의 포함을 통해 이루어진다.

7. 지원 메시지 타입

tf2_geometry_msgs가 변환을 지원하는 주요 메시지 타입과 각 타입의 변환 특성은 다음과 같다.

메시지 타입Stamped 여부변환 종류비고
PointStampedO회전 + 병진위치점 변환
Vector3StampedO회전만방향 벡터 변환 (병진 무관)
PoseStampedO회전 + 병진위치 + 자세 변환
PoseWithCovarianceStampedO회전 + 병진 + 공분산공분산 행렬도 함께 변환
QuaternionStampedO회전만자세(방향) 변환
WrenchStampedO회전만힘/토크 변환
TransformStampedO회전 + 병진변환 자체의 변환

7.1 변환 의미론의 차이

각 메시지 타입의 변환 동작은 해당 물리량의 기하학적 특성에 따라 상이하다.

  • Point(점): 회전과 병진이 모두 적용된다. 공간상의 위치를 나타내므로 좌표계 원점의 이동에 영향을 받는다.
  • Vector3(벡터): 회전만 적용되고 병진은 무시된다. 자유 벡터(free vector)로서 방향과 크기만을 나타내므로 좌표계 원점의 위치에는 무관하다.
  • Pose(자세): 위치(Point)에 대해서는 회전과 병진이, 방향(Quaternion)에 대해서는 회전만 적용된다.
  • Wrench(렌치): 힘(force)과 토크(torque)는 벡터 물리량이므로 회전만 적용된다.

이러한 변환 의미론의 차이는 tf2_geometry_msgs 패키지 내의 각 doTransform() 특수화에 올바르게 구현되어 있다.

8. Stamped vs Non-Stamped 메시지

8.1 Stamped 메시지의 자동 변환

tf2_geometry_msgs는 주로 Stamped 메시지(예: PointStamped, PoseStamped)에 대한 변환을 지원한다. Stamped 메시지는 header 필드에 frame_idstamp를 포함하므로, 변환 후 대상 프레임의 frame_id와 적절한 타임스탬프가 결과 메시지에 자동으로 설정된다.

8.2 Non-Stamped 메시지의 처리

Non-Stamped 메시지(예: geometry_msgs::msg::Point, geometry_msgs::msg::Pose)도 doTransform()을 통해 변환할 수 있으나, frame_idstamp 정보가 없으므로 변환에 사용할 TransformStamped를 외부에서 직접 제공해야 한다. 결과 메시지에도 프레임 정보가 포함되지 않으므로, 변환 후의 프레임 추적은 개발자의 책임이다.

9. 일반적 오류와 주의사항

9.1 헤더 누락 오류

tf2_geometry_msgs 사용 시 가장 빈번하게 발생하는 오류는 C++에서 tf2_geometry_msgs/tf2_geometry_msgs.hpp 헤더를 포함하지 않는 것이다. 이 경우 tf2::doTransform()의 템플릿 특수화가 존재하지 않아 다음과 같은 컴파일 오류가 발생한다.

error: no matching function for call to 'doTransform(...)'

또는 Buffer::transform() 호출 시 다음 런타임 오류가 발생할 수 있다.

TypeException: Type [...] if not loaded or does not support stamped interfaces

9.2 프레임 ID 불일치

입력 메시지의 header.frame_idlookupTransform()의 소스 프레임이 일치하지 않으면 의도하지 않은 변환 결과가 산출된다. Buffer::transform() 메서드를 사용하면 입력 메시지의 header.frame_id를 소스 프레임으로 자동 사용하므로 이 오류를 방지할 수 있다.

9.3 타임스탬프 관리

변환 조회 시 사용되는 타임스탬프는 변환의 시간적 정확성에 직접적인 영향을 미친다. 동적으로 변하는 변환(예: odom → base_link)에 대해 잘못된 타임스탬프를 사용하면, 과거나 미래 시점의 변환이 적용되어 위치 오차가 발생한다.

10. 참고 문헌


버전: 2026-03-26