[ROS2 Mastery 10편] TF2 프로그래밍 – 정적(Static) 브로드캐스터 작성하기 완벽 가이드

안녕하세요! 자율주행과 로봇 시스템 제어의 세계를 깊이 있게 탐구하고 계신 대학생 및 연구원 여러분. 쿼드(QUAD) 드론연구소의 자료를 바탕으로 진행되는 ‘ROS2 속성 마스터 과정’ 연재 블로그 10편에 오신 것을 진심으로 환영합니다.

지난 8편과 9편에서는 로봇 수학의 복잡성을 해결해 주는 TF2(Transform System)의 핵심 원리를 배우고, 터미널 명령어와 유틸리티 도구를 통해 싱글 프레임 및 움직이는 로봇 모델의 3D 변환을 직접 눈으로 확인해 보았습니다.

우리가 앞서 사용했던 static_transform_publisher 명령어는 매우 편리하지만, 로봇 소프트웨어를 전문적으로 연구하고 개발하기 위해서는 우리가 작성하는 코드(Python/C++) 내부에서 직접 좌표 변환 데이터를 다루고 배포할 수 있는 능력이 필수적입니다. 센서(LiDAR, 카메라 등)의 초기 보정 값을 로드하거나 복잡한 시스템의 기준 좌표를 동적으로 설정하려면 프로그래밍 방식의 접근이 요구되기 때문입니다.

이번 10편에서는 Python을 사용하여 ROS2 네트워크에 정적 좌표 프레임을 직접 쏘아 올리는 정적(Static) 브로드캐스터 노드를 A부터 Z까지 직접 프로그래밍해 보겠습니다.


패키지 생성 및 준비 (learning_tf2_py)

가장 먼저 해야 할 일은 TF2 관련 노드들을 담아둘 새로운 작업 패키지를 생성하는 것입니다. 이번 튜토리얼과 다음 튜토리얼(동적 프레임 및 리스너)에서 공통으로 사용할 패키지명은 learning_tf2_py로 정하겠습니다.

터미널을 열고 여러분의 작업 공간(Workspace)에 있는 src 디렉토리로 이동하여 아래 명령어를 실행해 주세요.

Bash
# ROS2 기본 환경 소싱
source /opt/ros/jazzy/setup.bash

# 작업 공간의 src 폴더로 이동
cd ~/ros2_ws/src

# ament_python 빌드 타입을 사용하는 새 패키지 생성
ros2 pkg create --build-type ament_python learning_tf2_py

명령어를 실행하면 learning_tf2_py 패키지와 함께 필수 파일(package.xml, setup.py 등) 및 폴더가 성공적으로 생성되었다는 확인 메시지가 출력됩니다.


정적(Static) 브로드캐스터 노드 코드 작성

패키지 생성이 완료되었다면, 노드의 소스 코드가 위치할 디렉토리(~/ros2_ws/src/learning_tf2_py/learning_tf2_py)로 이동하여 static_turtle_tf2_broadcaster.py라는 이름의 파이썬 파일을 생성합니다.

Bash
cd ~/ros2_ws/src/learning_tf2_py/learning_tf2_py
touch static_turtle_tf2_broadcaster.py
chmod +x static_turtle_tf2_broadcaster.py

이제 VS Code 등 익숙한 텍스트 에디터로 방금 만든 파이썬 파일을 열고 아래의 코드를 작성합니다. 주석을 꼼꼼히 읽으며 타이핑해 보시길 권장합니다.

Python
#!/usr/bin/env python3
import rclpy
from rclpy.node import Node

# TF2 변환에 필요한 메시지 타입과 브로드캐스터 라이브러리를 import 합니다.
from geometry_msgs.msg import TransformStamped
from tf2_ros.static_transform_broadcaster import StaticTransformBroadcaster

class StaticFramePublisher(Node):
    def __init__(self):
        # 1. 노드 이름을 'static_turtle_tf2_broadcaster'로 초기화합니다.
        super().__init__('static_turtle_tf2_broadcaster')
        
        # 2. 정적 변환 브로드캐스터 객체를 생성합니다.
        self.tf_static_broadcaster = StaticTransformBroadcaster(self)
        
        # 3. 노드가 시작될 때 정적 변환을 한 번만 전송하는 함수를 호출합니다.
        self.make_transforms()

    def make_transforms(self):
        # 4. 변환 트리에 게시할 메시지 템플릿인 TransformStamped 객체를 생성합니다.
        t = TransformStamped()

        # 5. 헤더(Header) 메타데이터 설정
        # 현재 시간을 타임스탬프로 찍습니다. Node의 시계를 사용합니다.
        t.header.stamp = self.get_clock().now().to_msg()
        # 부모 프레임(기준점)의 이름을 'world'로 설정합니다.
        t.header.frame_id = 'world'
        # 자식 프레임(새롭게 정의할 좌표계)의 이름을 'mystaticturtle'로 설정합니다.
        t.child_frame_id = 'mystaticturtle'

        # 6. 6D 포즈 중 이동(Translation) 정보 입력
        # 거북이를 원점 좌표에서 Z축 방향으로 1미터 공중에 띄웁니다.
        t.transform.translation.x = 0.0
        t.transform.translation.y = 0.0
        t.transform.translation.z = 1.0

        # 7. 6D 포즈 중 회전(Rotation) 정보 입력 (Quaternion 사원수 방식)
        # 회전이 없는 기본 상태(Identity)로 설정합니다.
        t.transform.rotation.x = 0.0
        t.transform.rotation.y = 0.0
        t.transform.rotation.z = 0.0
        t.transform.rotation.w = 1.0

        # 8. 설정이 끝난 변환 메시지를 브로드캐스트합니다.
        self.tf_static_broadcaster.sendTransform(t)
        self.get_logger().info('정적 프레임 변환을 성공적으로 게시했습니다: world -> mystaticturtle')

def main(args=None):
    rclpy.init(args=args)
    node = StaticFramePublisher()
    
    try:
        rclpy.spin(node)
    except KeyboardInterrupt:
        pass
    finally:
        node.destroy_node()
        rclpy.shutdown()

if __name__ == '__main__':
    main()

[연구원을 위한 코드 심층 분석]

로봇 시스템 제어에 있어 프레임 코드를 완벽히 이해하는 것은 매우 중요합니다. 위 코드를 핵심 부분으로 나누어 분석해 보겠습니다.

  1. TransformStamped 메시지: geometry_msgs/msg에 정의되어 있으며, TF 트리에 올라가는 가장 핵심적인 데이터 구조입니다. 이 메시지 안에는 ‘언제(stamp)’, ‘어떤 프레임을 기준(frame_id)으로’, ‘어떤 프레임을(child_frame_id)’, ‘얼마나 이동/회전(transform) 시킬 것인가’에 대한 모든 정보가 담겨 있습니다.
  2. StaticTransformBroadcaster: 일반적인 Publisher가 아닌 tf2_ros 패키지에서 제공하는 전용 브로드캐스터입니다. 이를 통해 데이터를 발행하면 내부적으로 /tf_static 토픽을 타고 전송되며, ROS2의 특성상 정적 변환은 동적 변환(/tf)과 달리 매 주기마다 쏠 필요 없이 노드 시작 시 한 번만 쏘면 네트워크에 영구적으로 유지되는 이점이 있습니다.
  3. Quaternion (사원수) 기반의 회전: 직관적인 Roll, Pitch, Yaw(오일러 각) 대신 x, y, z, w로 이루어진 사원수(Quaternion) 값을 사용해야 합니다. 위 코드의 [0.0, 0.0, 0.0, 1.0]은 회전이 전혀 발생하지 않은 기본 단위 사원수(Identity Quaternion)를 의미합니다. (짐벌 락 현상을 막기 위해 ROS2의 TF는 모두 쿼터니언을 기본 포맷으로 사용합니다.)


설정 파일 업데이트: package.xml 및 setup.py

새로운 패키지와 노드를 만들었으므로 ROS2의 빌드 시스템인 Colcon이 올바르게 의존성을 찾고 실행 파일을 매핑할 수 있도록 환경 설정 파일들을 업데이트해야 합니다.

1. package.xml 의존성(Dependency) 추가

코드 내부에서 geometry_msgs, rclpy, tf2_ros_py 등의 패키지를 불러왔습니다. 패키지 루트 디렉토리(~/ros2_ws/src/learning_tf2_py)에 있는 package.xml을 열어 <license> 태그 아래에 다음 종속성들을 추가합니다.

XML
  <depend>geometry_msgs</depend>
  <depend>python3-numpy</depend>
  <depend>rclpy</depend>
  <depend>tf2_ros_py</depend>
  <depend>turtlesim</depend>

이 과정을 누락하면 배포를 위해 다른 PC에서 빌드할 때 패키지 누락 에러가 발생할 수 있으니 연구원 여러분은 항상 종속성 관리를 철저히 하는 습관을 들여야 합니다.

2. setup.py 진입점(Entry Point) 추가

명령줄에서 ros2 run으로 우리가 만든 스크립트를 호출할 수 있게 만들어 줍시다. 같은 폴더 내의 setup.py 파일을 열고 entry_points 부분을 아래와 같이 수정합니다.

Python
    entry_points={
        'console_scripts': [
            'static_turtle_tf2_broadcaster = learning_tf2_py.static_turtle_tf2_broadcaster:main',
        ],
    },


패키지 빌드 및 대망의 실행

모든 프로그래밍이 완료되었습니다! 터미널을 열고 작업 공간 루트로 이동하여 패키지를 빌드해 봅시다. 항상 빌드 전 rosdep을 사용하여 누락된 종속 패키지가 없는지 검사하는 것이 모범 사례입니다.

Bash
# 작업 공간 루트로 이동
cd ~/ros2_ws

# 누락된 의존성 자동 설치
rosdep install -i --from-path src --rosdistro jazzy -y

# 패키지 빌드
colcon build --symlink-install

에러 없이 완벽하게 빌드가 되었다면, 노드를 실행하여 우리가 코딩한 거북이 프레임이 정상적으로 띄워지는지 확인합니다.

[실행 단계]

  1. 첫 번째 터미널 (브로드캐스터 실행)
  2. 터미널에 “정적 프레임 변환을 성공적으로 게시했습니다”라는 로그가 출력되며 노드가 대기 상태로 진입합니다.
  3. 두 번째 터미널 (토픽 검증) 우리의 목적대로 /tf_static 채널에 데이터가 실렸는지 echo 명령으로 확인합니다.

성공입니다! 출력된 로그를 보면 우리가 코드에 적어 넣은 대로 mystaticturtle 프레임이 world 프레임 위로 1미터(z=1.0) 공중에 둥둥 떠 있도록 정적 변환 좌표가 네트워크 전체에 브로드캐스트되고 있음을 알 수 있습니다.


실무자를 위한 꿀팁: CLI 도구와 Launch 파일의 활용

오늘 튜토리얼은 “파이썬 코드로 TF2를 어떻게 다루는가?”라는 원리 이해를 위해 매우 중요한 실습이었습니다.

하지만 실제 연구 및 개발 현장(예: 로봇 팔 베이스 좌표와 LiDAR 센서 간의 좌표 오프셋 설정 등)에서 이러한 정적 변환이 필요할 때마다 매번 파이썬 파일을 새로 짜지는 않습니다. 대신 ROS2가 내장하고 있는 static_transform_publisher 실행 파일을 Launch 파일(로봇 시동 스크립트) 안에 직접 선언하여 매우 간결하게 처리합니다.

앞선 9편에서 배웠던 CLI 명령어를 코드로 작성하지 않고 터미널이나 Launch 파일에서 인자 값으로 넘겨 사용하는 방식입니다. 다음은 x, y, z 이동(미터)과 Roll, Pitch, Yaw(라디안) 회전을 부여하는 명령어의 예시입니다.

Bash
# 방식 1: Euler Angle (Roll, Pitch, Yaw) 단위로 입력 (가장 자주 쓰임)
ros2 run tf2_ros static_transform_publisher 0 0 1 0 0 0 world mystaticturtle

# 방식 2: Quaternion 단위 (x, y, z, w)로 입력
ros2 run tf2_ros static_transform_publisher 0 0 1 0 0 0 1 world mystaticturtle

Launch 파이썬 파일에서는 아래처럼 노드 형태로 간편하게 삽입할 수 있습니다.

Python
Node(
    package='tf2_ros',
    executable='static_transform_publisher',
    arguments=['0', '0', '1', '0', '0', '0', 'world', 'mystaticturtle']
)


마무리하며

이번 [ROS2 Mastery 10편]에서는 파이썬을 활용하여 ROS2 TF2 시스템의 핵심 클래스인 StaticTransformBroadcasterTransformStamped 메시지를 직접 다뤄보았습니다.

이제 여러분은 ROS2 환경 내에서 로봇을 코딩할 때, 다양한 센서나 고정된 환경의 좌표를 자유자재로 설정하고 전체 좌표계 트리(Tree) 안으로 편입시킬 수 있는 든든한 기술을 확보하셨습니다.

하지만 로봇은 고정되어 있지 않고 끊임없이 움직입니다. 다음 [ROS2 Mastery 11편]에서는 이번 편에서 배운 기본기를 바탕으로, 거북이가 주행할 때마다 실시간으로 위치 좌표계가 바뀌는 “동적(Dynamic) 로봇의 TF2 브로드캐스터 및 리스너(Listener) 구현”에 대해 깊이 있게 알아보겠습니다.

대학생, 연구원 여러분의 성공적이고 열정적인 로봇 공학 연구를 언제나 응원합니다! 다음 편에서 더욱 유익한 내용으로 찾아뵙겠습니다. 감사합니다.

작성자: 에이든(Aiden), 쿼드(QUAD) 드론연구소 마케팅팀

기고일: 2026.06.10

Similar Posts

답글 남기기