좌표 이름에는 서로 다른 역할이 있다
frame은 위치/방향을 표현하는 기준이다. base_link는 로봇 베이스에 강체로 붙인 기준이며 반드시 바닥 접촉점이나 무게중심인 것은 아니다. odom은 단기간의 연속적인 로봇 운동을 표현하는 세계 고정 기준으로 사용하고, 누적 오차가 시간에 따라 커질 수 있다. map은 장기 지도/전역 위치의 기준이며 새로운 위치 보정으로 로봇 pose가 불연속적으로 바뀔 수 있다. REP105 공식 원문이 이 의미를 정한다.
이는 frame 계약이며 실제 추정기가 그 성능을 자동 만족한다는 인증이 아니다. map이라고 이름 붙였다고 참값/전역 정확도가 생기지 않고 odom도 미끄럼/센서·보정 오류로 부정확할 수 있다. 로컬 제어가 갑작스러운 전역 보정을 그대로 받지 않도록 연속 운동과 전역 보정을 분리하는 이유를 이해하자. 지도나 localization이 아직 없으면 map 변환이 없을 수 있고, 이를 숨기려고 임의의 항등 변환을 정답처럼 넣지 않는다.
REP103의 SI/오른손 규약에서 로봇 body는 x 전방/y 왼쪽/z 위, 카메라 optical은 x 오른쪽/y 아래/z 전방을 사용한다. 다른 규약이면 변환/이유를 명시한다. base_link 아래의 sensor_link와 optical frame도 값의 방향/장착과 연결한다. 토픽 namespace는 이름 충돌을 관리하지만 TF frame 문자열/변환을 자동으로 고쳐 주는 기능이 아니다.
TF 체인과 발행 책임을 한 방향으로 연결한다
기본 체인을 map → odom → base_link → sensor_link로 두자. TF의 한 child는 한 parent에 연결되는 트리로 관리하고 각 동적 변환에 일관된 발행 소유를 정한다. 예를 들어 odom→base_link는 odometry 시스템, map→odom은 전역 위치 추정/SLAM 쪽이 담당할 수 있다. 같은 child/변환을 서로 다른 노드가 경쟁 발행하거나 map→base_link를 추가해 두 parent를 만들지 않는다.
그림 M09.04-1. 각 칸은 서로 다른 역할/근거다. 아래 방향은 자료/처리의 연결이며 실제 성공을 자동으로 보장하지 않는다. 직접 제작한 개념도이며 본문의 조건과 함께 읽는다.
정적 장착과 동적 로봇 운동도 구분한다. 고정 센서 장착은 정적 변환, 움직이는 관절은 해당 시각의 상태로부터 계산한 동적 변환이 필요하다. robot_state_publisher는 URDF와 joint state로 링크 관계를 발행하는 역할이며 주행/전역 위치를 스스로 추정하는 노드는 아니다. bridge로 pose 메시지가 왔다는 사실과 필요한 TF가 올바르게 발행된 사실은 별도다.
T_AB는 B 좌표를 A에서 표현하도록 바꾸는 강체 변환이고 곱은 오른쪽부터 적용한다. 같은 시각의 체인은 다음과 같다. 역표시 −1은 해당 강체 변환의 역이다.
이 식은 전역 위치 추정이 보정한 base pose와 연속 odom pose로부터 map→odom을 연결하는 방법을 보여 준다. 서로 다른 시각/축·모델 버전의 행렬을 이름만 맞춰 곱하면 같은 로봇 상태가 되지 않는다. 아래 활동은 전역 추정 알고리즘을 실행하지 않고 보정 결과를 합성 입력으로 제공한다.
연속 odom과 전역 보정의 수치를 비교한다
평면의 모든 회전이 0인 같은 시각의 합성 예에서 odom의 base 위치는 (2.0,0.5) m, 전역 map에서의 보정 위치는 (1.8,0.5) m다. map에서 본 odom 원점의 이동은 (1.8−2.0,0.5−0.5)=(−0.2,0) m다. (−0.2,0)+(2.0,0.5)=(1.8,0.5)로 체인을 확인한다. 회전이 있으면 위치 차이만 빼는 일반식으로 쓰지 않고 위 강체 변환을 적용한다.
다음 시각의 odom base가 (2.1,0.5)로 연속 이동했는데 전역 보정이 map→odom=(−0.5,0)으로 바뀌면 map base는 (1.6,0.5)다. odom x는 +0.1 m 변했지만 map x는 1.8→1.6으로 −0.2 m 점프했다. 실제 로봇이 순간 뒤로 이동한 것이 아니라 전역 위치 추정/좌표 보정이 바뀐 예다. 어느 구간에 변화가 생겼는지 따로 기록한다.
Jazzy nav_msgs/Odometry는 pose를 header.frame_id에서, twist를 child_frame_id에서 표현하도록 정한다. 별도 예에서 header=odom/child=base_link, base의 yaw가 odom에 대해 +90도이고 body 전방 속도가 0.2 m/s면 odom 표현의 선속도는 (0,0.2) m/s다. body의 x 속도를 그대로 odom x에 복사하지 않는다. 위치/속도·공분산의 frame과 출처도 확인한다.
시뮬레이션 시계는 node별로 선택한다
ROS clock 설계에서 use_sim_time이 켜진 노드는 활성화된 ROS time source를 사용한다. 노드마다 설정을 확인해야 하며 한 노드의 설정이 모든 노드로 자동 전파되는 것은 아니다. 센서/TF·위치 추정/제어가 같은 모형 시간 계약을 공유해야 같은 stamp의 의미를 비교할 수 있다. 실제 경과 timeout은 적절한 경과 시계로 따로 관리한다.
Gazebo에서 ROS의 /clock으로 가는 경로도 필요하다. ros_gz_bridge Jazzy 문서는 clock의 Gazebo→ROS 단방향 연결과 하나의 clock 출처를 설명한다. 실제 Transport 토픽이 /clock인지 /world/demo/clock인지 확인한다. 다른 publisher가 있을 때 완전 수식 이름만 생성되는 조건도 있어 이름을 추측하지 않는다. 메시지 header를 wall time으로 덮어쓰는 옵션은 별도 의미 변경이므로 모든 경로의 시간 계약과 확인한다.
합성 노드 A는 use_sim_time=true로 현재 2.1 s를 쓰고 B는 false로 시스템 시각 1,700,000,000 s를 쓴다고 하자. frame 체인이 맞아도 두 stamp를 직접 빼서 지연/TF 신선함을 판단할 수 없다. 잘못된 extrapolation/만료가 생길 수 있다. node별 설정·clock 수신/출처와 해당 메시지 stamp의 생성 위치를 조사한다.
같은 시각의 변환과 관측 나이를 함께 본다
센서 취득 2.000 s/검사 2.080 s가 같은 clock이면 나이는 80 ms다. 합성 허용 나이가 100 ms 이하라면 통과한다. 검사 시각이 2.150 s면 나이는 150 ms로 50 ms 초과다. 하지만 시간 관문이 맞아도 올바른 frame/변환·현재 상태가 없으면 전체 입력이 유효하지 않다. 반대로 frame 이름이 맞아도 취득 시각에 필요한 TF가 없거나 외삽이면 값을 임의로 채우지 않는다.
움직이는 로봇의 과거 관측에 현재의 'latest TF'를 무조건 적용하면 다른 시점의 기하가 된다. 일정 속도 0.2 m/s에서 0.1 s 시차만 있어도 단순 병진 차이는 0.02 m다. 실제 오차 방향/회전·센서 장착과 보정 변화는 추가 조건이다. TF 보간/lookup의 시각/유효 구간과 관측 ID를 기록하고 보정/변환이 바뀐 구간을 구별한다.
reset으로 모형 시간이 뒤로 가면 기존 TF/추적·관측/명령의 캐시와 epoch를 어떻게 정리할지 계약한다. e1의 stamp를 e2의 0초에서 재사용해 음수 나이를 신선함으로 처리하지 않는다. 첫 clock 전의 미초기화 0과 명시적인 reset 뒤의 0은 출처/상태로 구분한다. map/odom 초기 원점·보정 상태도 같은 실험 세대에서 확인한다.
진단 표는 변환의 parent/child·발행 소유/시각, node별 clock/use_sim_time, 관측/메시지 ID와 실제/추정 pose·공분산을 나눈다. 기존 4.3 TF와 4.4 타임스탬프를 연결하자. 이 표의 통과는 실제 localization/SLAM 성과나 모든 위치의 정확성을 입증하지 않는다.