659.19 tf2_msgs/TFMessage 토픽 구조
1. 개요
TF2 시스템에서 좌표 변환 데이터는 tf2_msgs/msg/TFMessage 메시지 타입을 통해 전파된다. 이 메시지는 /tf 및 /tf_static 토픽에서 사용되는 유일한 메시지 타입으로, 하나 이상의 geometry_msgs/msg/TransformStamped 메시지를 배열 형태로 포함한다. TFMessage의 설계는 다수의 좌표 변환을 단일 메시지 내에 일괄적으로 전송함으로써 통신 효율성을 극대화하는 것을 목적으로 한다.
2. TFMessage 메시지의 정의
tf2_msgs/msg/TFMessage 메시지는 다음과 같이 정의된다.
# tf2_msgs/msg/TFMessage
geometry_msgs/TransformStamped[] transforms
이 메시지는 단일 필드 transforms만을 포함하며, 이는 geometry_msgs/msg/TransformStamped 타입의 가변 길이 배열(unbounded dynamic array)이다. 배열의 각 요소는 하나의 독립적인 좌표 변환 관계를 기술한다.
3. 메시지 구조의 계층적 표현
TFMessage의 전체 계층적 구조를 다음과 같이 나타낼 수 있다.
TFMessage
└── transforms[] (geometry_msgs/TransformStamped 배열)
├── transforms[0]
│ ├── header (std_msgs/Header)
│ │ ├── stamp (builtin_interfaces/Time)
│ │ │ ├── sec (int32)
│ │ │ └── nanosec (uint32)
│ │ └── frame_id (string)
│ ├── child_frame_id (string)
│ └── transform (geometry_msgs/Transform)
│ ├── translation (geometry_msgs/Vector3)
│ │ ├── x (float64)
│ │ ├── y (float64)
│ │ └── z (float64)
│ └── rotation (geometry_msgs/Quaternion)
│ ├── x (float64)
│ ├── y (float64)
│ ├── z (float64)
│ └── w (float64)
├── transforms[1]
│ └── ... (동일 구조)
└── transforms[n-1]
└── ... (동일 구조)
4. 배열 구조의 설계 근거
4.1 통신 효율성
TFMessage가 단일 TransformStamped가 아닌 배열을 포함하도록 설계된 데에는 명확한 이유가 있다. 로봇 시스템에서는 하나의 노드가 동시에 여러 프레임 간의 변환을 발행하는 경우가 빈번하다. 예를 들어, robot_state_publisher 노드는 URDF에 정의된 모든 링크 간의 변환을 동시에 발행한다. 이때 각 변환을 개별 메시지로 발행하면 네트워크 패킷 오버헤드가 증가하고, DDS 미들웨어의 직렬화/역직렬화 비용이 각 메시지마다 반복된다.
TFMessage의 배열 구조를 활용하면, 동일 시점에 발행되는 다수의 변환을 단일 네트워크 패킷에 포함시킬 수 있어 통신 효율성이 향상된다.
4.2 원자적 발행 (Atomic Publication)
배열 구조는 관련된 변환들의 원자적 발행(atomic publication)을 보장한다. 예를 들어, 로봇 팔의 관절 변환들은 동일 시점의 관절 상태로부터 계산되므로, 이들이 하나의 TFMessage에 포함되어 발행되면 수신 측에서 시간적으로 일관된 변환 집합을 수시에 수신할 수 있다.
5. TFMessage가 사용되는 토픽
TFMessage 메시지는 TF2 시스템에서 두 개의 표준 토픽을 통해 전달된다.
| 토픽 이름 | 목적 | 변환 유형 |
|---|---|---|
/tf | 동적 변환 전달 | 시간에 따라 변하는 변환 |
/tf_static | 정적 변환 전달 | 시간에 무관한 고정 변환 |
두 토픽 모두 동일한 tf2_msgs/msg/TFMessage 메시지 타입을 사용하지만, QoS(Quality of Service) 정책의 차이로 인해 서로 다른 동작 특성을 보인다.
5.1 /tf 토픽
/tf 토픽은 동적 변환(dynamic transform)을 전달하기 위해 사용된다. 이 토픽의 QoS 설정은 다음과 같다.
- Reliability:
RELIABLE또는BEST_EFFORT(구현에 따라 상이) - Durability:
VOLATILE - History:
KEEP_LAST - Depth: 100 (기본값)
VOLATILE 내구성(durability) 설정으로 인해, 구독자(subscriber)가 토픽에 연결되기 이전에 발행된 메시지는 수신되지 않는다. 동적 변환은 주기적으로 갱신되므로, 과거 메시지의 수신이 불필요하기 때문이다.
5.2 /tf_static 토픽
/tf_static 토픽은 정적 변환(static transform)을 전달하기 위해 사용된다. 이 토픽의 QoS 설정은 다음과 같다.
- Reliability:
RELIABLE - Durability:
TRANSIENT_LOCAL - History:
KEEP_ALL또는 충분히 큰KEEP_LAST
TRANSIENT_LOCAL 내구성 설정으로 인해, 나중에 연결된 구독자도 기존에 발행된 정적 변환 메시지를 DDS 캐시로부터 수신할 수 있다. 이는 정적 변환이 한 번 발행된 후 재발행되지 않으므로, 지연 합류(late-joining) 구독자도 해당 변환 정보를 확보할 수 있도록 보장하기 위함이다.
6. 발행자와 구독자의 관계
6.1 TransformBroadcaster의 TFMessage 생성
TransformBroadcaster 및 StaticTransformBroadcaster는 내부적으로 TFMessage 메시지를 구성하여 해당 토픽에 발행한다.
단일 변환 발행:
// C++ 예시
geometry_msgs::msg::TransformStamped t;
// ... 변환 설정 ...
tf_broadcaster_->sendTransform(t);
// 내부적으로 TFMessage.transforms = [t] 형태로 발행
다중 변환 일괄 발행:
// C++ 예시
std::vector<geometry_msgs::msg::TransformStamped> transforms;
transforms.push_back(t1);
transforms.push_back(t2);
transforms.push_back(t3);
tf_broadcaster_->sendTransform(transforms);
// 내부적으로 TFMessage.transforms = [t1, t2, t3] 형태로 발행
6.2 TransformListener의 TFMessage 수신
TransformListener는 /tf 및 /tf_static 토픽을 구독하여 수신된 TFMessage 메시지로부터 변환 데이터를 추출한다. 수신 시 TFMessage의 transforms 배열을 순회하면서 각 TransformStamped를 tf2::Buffer에 등록한다. 이 과정을 의사 코드로 표현하면 다음과 같다.
수신 콜백(TFMessage msg):
for each transform in msg.transforms:
buffer.setTransform(transform, authority, is_static)
authority는 변환을 발행한 노드의 식별자이며, is_static은 정적 변환 여부를 나타내는 불리언 값이다.
7. TFMessage의 직렬화
TFMessage 메시지의 직렬화 시, CDR(Common Data Representation) 인코딩 규칙에 따라 다음과 같은 구조로 바이트 스트림이 생성된다.
- 배열 길이 (4바이트, uint32):
transforms배열의 요소 수 - 각 TransformStamped 요소: 순차적으로 직렬화
header.stamp.sec(4바이트)header.stamp.nanosec(4바이트)header.frame_id(가변 길이)child_frame_id(가변 길이)transform.translation.x/y/z(각 8바이트)transform.rotation.x/y/z/w(각 8바이트)
단일 TransformStamped의 고정 크기 부분은 4 + 4 + 3 \times 8 + 4 \times 8 = 64 바이트이며, 여기에 두 개의 가변 길이 문자열(frame_id, child_frame_id)의 길이가 추가된다.
8. 다수 발행자의 TFMessage 통합
TF2 시스템에서는 다수의 노드가 /tf 또는 /tf_static 토픽에 동시에 TransformStamped를 발행할 수 있다. DDS의 발행-구독 모델에 의해, 각 발행자로부터의 TFMessage는 독립적으로 전달되며, TransformListener는 모든 발행자의 메시지를 수신하여 단일 Buffer에 통합한다.
이 설계는 분산 시스템의 확장성(scalability)을 보장한다. 새로운 센서나 하위 시스템이 추가되어 새로운 변환이 필요할 때, 해당 노드가 독립적으로 /tf에 발행하면 기존 시스템에 변경 없이 변환 트리가 확장된다.
9. 프로그래밍 예시
9.1 TFMessage 직접 생성 (비표준적 방법)
일반적으로 TransformBroadcaster를 통해 간접적으로 TFMessage를 생성하지만, 필요에 따라 직접 생성하여 발행할 수도 있다.
#include <tf2_msgs/msg/tf_message.hpp>
// TFMessage 생성
tf2_msgs::msg::TFMessage tf_message;
geometry_msgs::msg::TransformStamped t1, t2;
// ... t1, t2 변환 설정 ...
tf_message.transforms.push_back(t1);
tf_message.transforms.push_back(t2);
// /tf 토픽에 직접 발행
publisher_->publish(tf_message);
from tf2_msgs.msg import TFMessage
from geometry_msgs.msg import TransformStamped
tf_message = TFMessage()
t1 = TransformStamped()
t2 = TransformStamped()
# ... t1, t2 변환 설정 ...
tf_message.transforms.append(t1)
tf_message.transforms.append(t2)
# /tf 토픽에 직접 발행
publisher.publish(tf_message)
다만, 이러한 직접 발행 방식은 TransformBroadcaster가 내부적으로 처리하는 QoS 설정, 토픽 이름 관리, 그리고 정적/동적 변환 구분 등의 로직을 수동으로 관리하여야 하므로, 특별한 이유가 없는 한 표준 API인 TransformBroadcaster를 사용하는 것이 권장된다.
10. 참고 문헌
- ROS2 공식 문서, “tf2_msgs/msg/TFMessage,” https://docs.ros.org/en/humble/p/tf2_msgs/
- ROS2 공식 문서, “geometry_msgs/msg/TransformStamped,” https://docs.ros.org/en/humble/p/geometry_msgs/
- Foote, T., “tf: The transform library,” 2013 IEEE Conference on Technologies for Practical Robot Applications (TePRA), 2013.
- OMG, “Data Distribution Service (DDS) Specification,” Version 1.4, Object Management Group, 2015.
- ROS2 Humble Hawksbill 기준 (2022)