ROS 2 tf2에서 transform을 찾지 못하는 이유: frame tree·timestamp·extrapolation 계약

현재 G1 보행 환경에는 좌표 변환 코드가 이미 있습니다. root_lin_vel_w를 root quaternion으로 역회전해 base_lin_vel_b를 만들고, 발바닥 sample은 link frame에서 world frame으로 보냅니다. 시뮬레이터 한 프로세스 안에서는 tensor가 어느 시점의 어느 로봇에 속하는지 실행 문맥이 알고 있기 때문에 이 계산이 성립합니다.

그런데 같은 pose가 ROS 2를 거쳐 카메라·state estimator·VLA·WBC 사이를 이동한다고 생각하면 이야기가 달라집니다. 현재 BackendRequest에는 stateobservation이라는 dictionary만 있고, pose를 해석할 frame_id, 측정 timestamp, transform source, robot epoch가 없습니다. 숫자 세 개와 quaternion 네 개를 전달할 수는 있지만, 그 숫자가 pelvis 기준인지 sim_world 기준인지, 이미지가 촬영된 순간의 자세인지 callback이 처리된 현재 자세인지 증명할 수 없습니다.

그래서 이번에 정리할 문제는 quaternion 계산법이 아닙니다. 그 수학과 G1의 body/world 실패 사례는 world·body·joint frame 글이 맡고 있습니다. 여기서는 ROS 2 tf2가 frame 사이 관계를 시간과 함께 어떻게 보존하는지, lookupTransform()이 왜 실패해야 안전한지, 그리고 현재 workspace에 무엇이 구현돼 있지 않은지를 다룹니다.

G1의 sim_world, pelvis, torso, camera와 발 frame tree를 transform buffer의 측정 시각과 연결하고 lookup·connectivity·past·future extrapolation을 구분하는 ROS 2 tf2 런타임 계약 다이어그램
tf2는 좌표 변환 행렬 목록이 아니라 시간에 따라 변하는 tree의 이력입니다. frame 경로가 있어도 측정 시각이 buffer 밖이면 변환은 실패해야 하며, 최신 transform으로 조용히 대체하면 다른 물리 시점의 데이터를 섞게 됩니다. 그림을 누르면 원본 크기로 볼 수 있습니다.

현재 구현은 좌표를 계산하지만 좌표의 계보를 전달하지 않습니다

origin/main@ee4d96eg1_walk_env.py는 simulator가 제공한 root_quat_w, root_lin_vel_w, root_ang_vel_w를 같은 control step에서 읽습니다. 그 뒤 quat_apply_inverse()로 world 속도와 중력을 base frame으로 가져와 73차원 policy observation에 넣습니다. 발바닥 위치도 body_link_pos_wbody_link_quat_w를 이용해 link-local sample을 world로 회전·이동합니다.

base_quat = robot.data.root_quat_w
base_lin_vel_w = robot.data.root_lin_vel_w

base_lin_vel_b = quat_apply_inverse(base_quat, base_lin_vel_w)
foot_sample_w = quat_apply(foot_quat_w, sample_b) + foot_pos_w

이 경로는 implemented입니다. 그러나 분산 runtime의 frame graph가 구현됐다는 뜻은 아닙니다. repository tree를 확인하면 ROS 2용 .launch.py, .msg, .srv, .action 파일이 없고 코드에도 TransformBroadcaster, TransformListener, lookup_transform가 없습니다. 유일한 package.xml은 H1 description asset의 ROS 1 catkin package입니다.

라우터 쪽 경계는 더 얇습니다. BackendRequestobservationstate의 schema를 강제하지 않고, Runner.run()은 이를 한 backend의 run_step()에 넘긴 뒤 response를 JSON으로 저장합니다. WBC kinematics도 아직 Pinocchio 호출이 아닌 placeholder입니다. 즉, 현재 확인된 것은 simulator 내부의 frame-aware tensor 계산이고, ROS 2 frame transport·buffer·lookup·failure policy는 designed 단계에도 아직 명시적으로 들어가 있지 않습니다.

tf2는 행렬 저장소가 아니라 시간 인덱스를 가진 tree입니다

ROS 2 tf2 공식 설명은 여러 coordinate frame의 관계를 시간에 따라 추적하고, 그 관계를 time-buffered tree로 유지한다고 정의합니다. 이 문장에서 빠뜨리기 쉬운 단어가 두 개입니다. treetime입니다.

tree이므로 한 child frame은 하나의 parent를 가져야 합니다. 공식 frame 추가 튜토리얼도 closed loop를 허용하지 않고 한 frame에 single parent만 둘 수 있다고 설명합니다. pelvis가 동시에 sim_worldodom의 직접 child로 발행된다면 소비자는 두 경로 중 어느 것이 로봇 위치의 정본인지 판단할 수 없습니다. 같은 child edge를 두 node가 번갈아 발행하는 상황도 운영 계약에서는 금지해야 합니다.

time-buffered이므로 transform은 “카메라는 torso 앞에 있다” 같은 관계만 저장하지 않습니다. 움직이는 pelvis가 시각 t에 어디에 있었는지 이력으로 저장합니다. consumer가 요청하는 것은 단순한 T_target_source가 아니라 T_target_source(t_measurement)입니다. frame 이름이 맞아도 요청 시각의 이력이 없으면 답을 만들 수 없습니다.

G1 frame tree는 URDF link 이름과 runtime 기준 frame을 구분해야 합니다

현재 g1_29dof.urdf에는 pelvis, torso_link, 양쪽 ankle link, imu_in_torso, imu_in_pelvis, d435_link, mid360_link가 있습니다. d435_joint와 두 IMU joint는 fixed joint이며 parent와 origin도 asset에 기록돼 있습니다. 이 정보는 robot link 사이 kinematic tree를 구성하는 근거입니다.

하지만 URDF link tree가 곧 전체 runtime frame tree는 아닙니다. 실제 이동을 설명하려면 로봇 밖의 기준도 필요합니다. 예를 들어 localization이 소유하는 map→odom, estimator가 소유하는 odom→pelvis, URDF와 joint state로 만들어지는 pelvis→torso_link→d435_link가 이어질 수 있습니다. 여기서 이름 자체보다 중요한 것은 각 edge의 단일 authority, 갱신 방식, timestamp source입니다.

제안 edge 관계의 성격 소유해야 할 구성요소 실패 시 의미
map→odom localization correction, dynamic localization 한 곳 전역 목표와 local odometry의 연결 상실
odom→pelvis robot base pose, dynamic state estimator 한 곳 현재 robot pose의 시간 이력 상실
pelvis→…→torso_link 여러 joint를 잇는 kinematic chain, dynamic robot state publisher 계층 상체·sensor frame chain 단절
torso_link→d435_link URDF fixed extrinsic asset/calibration publisher 카메라 위치를 torso 기준으로 해석 불가
d435_link→camera_optical_frame optical convention/calibration, static camera calibration 계층 pixel ray의 축 의미 상실

이 표는 현재 실행 중인 graph가 아니라 구현을 위한 제안입니다. 실제 frame 이름은 Unitree driver, camera driver, URDF와 충돌하지 않도록 inventory한 뒤 고정해야 합니다. base_link라는 흔한 이름을 관습만으로 추가하고 pelvis와 동일하다고 가정해서도 안 됩니다. 두 frame이 같다면 identity edge를 누가 발행하는지, 다르다면 물리적 offset과 회전이 무엇인지 manifest가 답해야 합니다.

TransformStamped의 timestamp는 발행 시간이 아니라 관계가 유효한 시각이어야 합니다

geometry_msgs 문서에서 TransformStampedheader.frame_id에서 child_frame_id로 가는 transform입니다. 여기서 header.stamp는 “메시지를 보낸 순간”이라는 편의값으로 채우면 안 됩니다. dynamic transform이라면 그 공간 관계가 측정·추정된 시각이어야 consumer가 sensor data와 같은 물리 시점을 조회할 수 있습니다.

카메라가 가장 분명한 예입니다. sensor_msgs/Image 정의는 header timestamp를 image acquisition time으로, frame_id를 camera optical frame으로 두도록 설명합니다. 이미지가 host callback에 도착한 시각이 아니라 shutter 또는 acquisition 시각을 보존해야 한다는 뜻입니다.

VLA가 image에서 물체 pose를 추정하고 G1 손 목표로 바꾸려면, image stamp의 camera_optical_frame→pelvis transform을 찾아야 합니다. 로봇이 고개나 torso를 움직이는 동안 callback 시점의 최신 transform을 적용하면 수학은 정확해도 다른 자세의 카메라 extrinsic을 사용하게 됩니다. 오류가 숫자로 드러나지 않고 그럴듯한 pose로 남기 때문에 더 위험합니다.

Time(0)의 ‘latest’는 편리하지만 measurement alignment를 증명하지 않습니다

tf2_ros Buffer API에서 time 0은 latest transform을 요청합니다. robot 상태 표시판처럼 지금 가장 최근 pose를 보여 주는 용도에는 맞을 수 있습니다. 그러나 stamped sensor를 제어 입력으로 변환할 때 latest는 측정 시각을 뜻하지 않습니다.

예를 들어 image의 acquisition stamp가 t_img이고 최신 robot transform이 t_tf_latest라면, 필요한 것은 두 값 중 더 새로운 쪽이 아닙니다. t_img에서 camera와 pelvis의 관계입니다. latest lookup으로 오류가 사라졌다는 사실은 frame graph가 건강하다는 증거가 아니라, consumer가 시간 정렬 요구를 포기했다는 신호일 수 있습니다.

따라서 처음 구현할 wrapper는 호출자가 time 0을 조용히 선택하지 못하게 해야 합니다. live control용 API는 source_stamp를 필수로 받고, “latest 허용”은 RViz나 비제어 telemetry처럼 별도 purpose로 선언된 consumer에만 열어 두는 편이 낫습니다. 이 규칙의 clock domain과 time jump 처리는 G1 timestamp 동기화 계약이 소유하고, 이번 글은 그 timestamp로 어떤 transform 이력을 조회할지에 집중합니다.

past extrapolation과 future extrapolation은 반대 방향의 증거입니다

ExtrapolationException은 막연한 “tf 오류”가 아닙니다. 요청 시각과 buffer가 가진 이력의 관계를 말합니다. 요청 시각이 가장 오래된 transform보다 과거라면 past extrapolation이고, 최신 transform보다 미래라면 future extrapolation입니다. 공식 tf2 debugging 튜토리얼도 now()를 요청했지만 최신 transform이 그보다 조금 이전이라 future extrapolation이 발생하는 사례를 보여 줍니다.

실패 분류 직접 관측 가능한 원인 금지할 대응
Lookup 요청 frame 이름을 buffer가 모름 오타, publisher 미기동, namespace 불일치 임의의 다른 frame 이름으로 대체
Connectivity 두 frame은 존재하지만 경로가 없음 tree가 둘로 분리됨, parent edge 누락 좌표 숫자를 같은 frame이라고 간주
Past extrapolation t_request < t_oldest sensor backlog, 너무 짧은 cache, 오래된 재전송 oldest transform을 붙여 계속 실행
Future extrapolation t_request > t_latest transform 지연, clock mapping 오류, callback 순서 무기한 block하거나 latest로 자동 강등
Timeout 정한 대기 시간 안에 조건이 성립하지 않음 publisher stall, executor starvation, dependency failure timeout을 성공 또는 안전한 pose로 기록

past extrapolation에서는 cache를 무조건 늘리기 전에 sensor backlog가 왜 생겼는지 봐야 합니다. 오래된 image를 뒤늦게 처리하는 pipeline이라면 transform history를 늘려 lookup을 성공시켜도 command age가 허용 범위를 넘을 수 있습니다. 반대로 future extrapolation은 transform publisher가 곧 따라올 정상적인 scheduling 차이일 수도 있고, 서로 다른 clock을 비교한 구조적 오류일 수도 있습니다. 짧게 기다릴 수는 있지만, 기다린 뒤에도 measurement age와 command deadline을 다시 검사해야 합니다.

lookup timeout은 stale pose를 사용할 권한이 아닙니다

Buffer API의 timeout은 transform이 사용 가능해지기를 얼마나 기다릴지 정합니다. 성공 조건을 완화하는 값이 아닙니다. 정한 timeout 안에 transform을 얻었다면 lookup은 성공했지만, 그 사이 image와 robot state가 application deadline을 넘었을 수 있습니다. 따라서 transform availability와 application freshness를 별도 gate로 검사해야 합니다.

제가 구현하려는 lookup evidence는 최소한 target frame, source frame, requested stamp, returned transform stamp, wait duration, buffer oldest/latest, chain, broadcaster identity 또는 manifest reference를 남깁니다. 그 뒤 command layer가 source_age, transform_age, deadline을 보고 실행·drop·hold를 결정합니다. middleware가 값을 반환했다고 actuator authority까지 자동으로 열리는 구조는 피해야 합니다.

blocking lookup을 callback 안에 오래 두는 문제도 있습니다. transform listener가 subscription callback을 처리해야 buffer가 채워지는데 같은 executor thread를 blocking wait가 점유하면 스스로 필요한 callback을 막을 수 있습니다. 공식 Python tf2 example 문서도 blocking wait 예제에는 MultiThreadedExecutor가 필요하고, coroutine 기반 async wait는 single-threaded executor에서도 callback을 막지 않는다고 구분합니다. executor 배치와 callback group 상세는 ROS 2 executor·backpressure 글의 책임이지만, tf lookup API를 선택할 때부터 그 교착 가능성을 고려해야 합니다.

static transform은 변하지 않는다는 주장이지 검증 없이 믿어도 된다는 뜻이 아닙니다

공식 tf2 Python example은 static transform을 startup에 한 번 발행하는 고정 관계, dynamic transform을 변화할 때마다 다시 발행하는 관계로 구분합니다. G1 URDF의 torso_link→d435_linkpelvis→imu_in_pelvis는 fixed joint이므로 static graph 후보입니다.

하지만 fixed joint라는 사실은 calibration이 맞다는 증거가 아닙니다. asset의 origin이 실제 장착 위치와 다른지, camera driver가 별도 optical frame을 발행하는지, 두 publisher가 같은 child를 소유하는지 확인해야 합니다. static edge도 source asset hash, calibration version, parent·child 이름, translation, normalized quaternion을 manifest에 넣고 launch preflight에서 비교할 필요가 있습니다.

dynamic edge는 더 엄격합니다. odom→pelvis publisher가 재시작하면 timestamp stream과 estimator epoch가 바뀝니다. 새 publisher가 같은 frame 이름으로 과거 상태를 이어 쓰는 것처럼 보이면 consumer가 restart 경계를 놓칩니다. frame manifest에 process identity와 runtime epoch를 연결하고, epoch가 바뀐 뒤 buffer가 새 이력으로 충분히 채워질 때까지 control lookup을 닫는 것이 안전합니다. 이 readiness 순서는 ROS 2 launch·readiness·shutdown 계약과 이어집니다.

buffer는 시스템 전체의 진실이 아니라 listener가 받은 지역 이력입니다

tf2 Buffer는 과거 transform을 일정 기간 보관합니다. cache_time도 consumer가 구성할 수 있습니다. 따라서 node A의 lookup 성공과 node B의 lookup 성공이 자동으로 같지는 않습니다. 두 listener의 시작 시점, QoS 호환, executor 부하, cache 길이가 다르면 각 buffer가 가진 oldest/latest 시각과 frame graph가 달라질 수 있습니다.

이 점 때문에 "tf2_echo에서는 보이는데 내 node에서는 안 된다"는 상황이 가능합니다. CLI tool은 별도의 listener와 buffer를 만듭니다. 그 도구의 성공은 broadcaster가 무언가 발행한다는 유용한 증거지만, 문제가 난 node의 buffer가 같은 이력을 받았다는 증거는 아닙니다. tf2 CLI 공식 문서view_frames --node, tf2_monitor, tf2_echo를 역할별로 나눠 써야 합니다.

  • view_frames: frame graph, broadcaster, rate, oldest/latest, buffer length을 inventory합니다.
  • tf2_echo: 특정 source·target transform의 수치와 갱신을 확인합니다.
  • tf2_monitor: 두 frame 사이 chain의 delay와 broadcaster별 timing을 봅니다.
  • 문제 node의 debug tuple·trace: 그 consumer가 실제로 요청한 frame·stamp와 local buffer 상태를 보존합니다.

QoS가 transform 전달에 어떤 영향을 주는지는 ROS 2 QoS와 lowstate·lowcmd 계약이 소유합니다. 이번 글에서 필요한 결론은 buffer가 공유 database가 아니라 각 consumer가 수신해 만든 지역 상태라는 점입니다.

현재 router에는 pose를 안전하게 받기 위한 envelope가 필요합니다

state={"base_pose": ...} 같은 dictionary는 prototype에는 빠르지만 live control 경계로는 부족합니다. key 이름만으로 frame과 시각을 추정하기 때문입니다. 첫 단계에서는 모든 backend schema를 ROS message로 바꾸기보다, router 앞의 adapter가 stamped pose를 명시적 envelope로 정규화하는 편이 작고 검증 가능합니다.

FrameStampedValue(
    value=pose_or_vector,
    source_frame="camera_optical_frame",
    target_frame="pelvis",
    source_stamp=ros_time,
    receive_monotonic_ns=...,
    robot_epoch=...,
    frame_manifest_hash=...,
    transform_evidence={
        "requested_stamp": ...,
        "returned_stamp": ...,
        "chain": [...],
        "wait_ns": ...,
    },
)

이 스키마는 아직 코드에 없습니다. 설계안의 목적은 pose 숫자와 provenance를 분리하지 않는 것입니다. backend가 body-frame vector만 받는다면 adapter가 변환을 끝내고 evidence를 붙입니다. lookup이 실패하거나 age gate를 넘으면 빈 pose를 임의의 0으로 채우지 않고 request 전체를 frame_unavailable, transform_stale, clock_mismatch처럼 구분해 거부합니다.

특히 VLA image와 state를 함께 묶을 때 같은 "현재"라는 표현을 쓰지 않습니다. image acquisition stamp, state sample stamp, transform stamp, inference start·finish monotonic time을 따로 저장합니다. sensor measurement에서 actor observation까지의 provenance는 state estimation 계보 글과 결합해야 합니다.

frame manifest는 이름 목록보다 edge ownership을 고정해야 합니다

launch 전에 확인할 manifest에는 expected frame 이름만 적어서는 부족합니다. 어느 node가 어느 parent→child edge를 발행하는지, static인지 dynamic인지, 어느 clock과 epoch를 쓰는지, startup 뒤 언제 ready로 볼지를 함께 고정해야 합니다.

manifest 항목 검증 질문
parent·child·authority 한 child edge의 정본 publisher가 하나인가
static/dynamic classification 변해야 할 edge가 고정됐거나 fixed edge가 계속 덮이지 않는가
asset/calibration hash URDF와 sensor extrinsic이 승인된 bundle과 같은가
clock ID·runtime epoch stamp를 같은 시간축에서 비교할 수 있고 restart를 구분하는가
expected rate·max age dynamic edge가 consumer deadline 안에 갱신되는가
required path set control에 필요한 source→target chain이 모두 연결됐는가
buffer warm-up gate 필요한 measurement window가 local buffer에 채워졌는가

이 manifest가 맞아야 lifecycle node의 activate와 actuator lease 승격을 허용합니다. frame graph가 잠깐 보였다는 사실만으로 ready를 선언하지 않습니다. required path마다 현재 transform, 과거 measurement stamp lookup, normalized quaternion, loop·duplicate authority, age bound를 확인해야 합니다.

첫 검증은 실제 G1이 아니라 synthetic frame graph에서 합니다

지금 단계에서 실제 robot driver를 붙이면 frame 이름 오류, clock 오류, executor 지연, calibration 오류가 한꺼번에 나타납니다. 먼저 authority 0인 synthetic graph에서 failure semantics를 고정하는 편이 낫습니다. transform 계산값보다 "어떤 실패를 어떤 상태로 기록하고 command를 어떻게 닫는가"가 첫 acceptance입니다.

주입 조건 기대 판정 금지되는 오판
존재하지 않는 camera_optical_frame Lookup failure, required path 미충족 d435_link를 같은 frame으로 간주
map tree와 pelvis tree 분리 Connectivity failure 각 frame의 숫자를 직접 결합
measurement stamp가 buffer oldest보다 과거 Past extrapolation과 source age 기록, drop oldest transform으로 clamp
measurement stamp가 latest보다 미래 bounded async wait 후 재검사 또는 fail 무기한 wait·latest 자동 대체
동일 child를 두 authority가 발행 manifest conflict, activation 차단 마지막으로 온 값을 정본으로 사용
dynamic publisher restart·epoch 변경 buffer warm-up 전 control gate closed old epoch transform과 새 state 결합
ROS time backward jump buffer·epoch policy에 따라 fail-closed 재초기화 jump 이전과 이후 stamp를 한 이력으로 계산
lookup 성공 후 command deadline 초과 transform success와 별개로 stale reject lookup 성공을 actuator 승인으로 해석

각 case는 source·target·requested stamp·buffer interval·exception type·decision·manifest hash를 artifact로 남깁니다. 이 결과가 반복 가능해야 Isaac Sim bridge를 연결합니다. 그 다음에만 simulator의 root_quat_w와 tf2에서 되찾은 sim_world→pelvis가 같은 control step에서 translation·rotation round trip을 만족하는지 교차 검증할 수 있습니다.

구현 순서는 broadcaster보다 frame contract가 먼저입니다

  1. frame inventory: G1 URDF link, Isaac prim, Unitree message, camera optical frame, router state key를 한 표에 모으고 alias를 제거합니다.
  2. edge manifest: parent·child, authority, static/dynamic, asset hash, clock, epoch, rate·age gate를 고정합니다.
  3. synthetic BufferCore test: 정상 chain과 lookup·connectivity·past·future·timeout·time-jump failure를 authority 0에서 검증합니다.
  4. Isaac bridge: simulator의 world·link pose를 stamped transform으로 발행하되 physics stamp와 reset epoch를 보존합니다.
  5. robot description path: URDF fixed joint와 joint state를 이용한 kinematic edge를 한 authority로 구성하고 duplicate publisher를 차단합니다.
  6. router adapter: stamped observation을 measurement time에 target frame으로 변환하고 evidence envelope를 만듭니다.
  7. readiness·fault suite: required path, buffer warm-up, stale publisher, restart, clock jump를 launch gate와 연결합니다.
  8. shadow 비교: actuator authority 없이 direct Isaac tensor transform과 tf2 lookup 결과·age·실패율을 대조합니다.

이 순서에서는 static_transform_publisher 하나를 실행해 RViz에 축이 보이는 것이 완료 조건이 아닙니다. graph ownership, measurement-time lookup, failure response가 먼저 고정돼야 합니다. 실제 G1 command는 shadow에서 transform evidence가 안정적으로 쌓이고, 별도의 배포·canary gate를 통과한 뒤에만 연결합니다.

현재 확인된 것과 아직 말할 수 없는 것을 나눕니다

상태 현재 근거 경계
Implemented Isaac Lab 내부의 world↔base quaternion 변환, link-local foot sample→world 변환, G1 URDF fixed sensor joints 분산 ROS 2 frame graph 구현을 뜻하지 않음
Designed 이 글의 frame manifest·lookup evidence·fault gate·구현 순서 실제 tf2 package·broadcaster·listener code는 없음
Executed 기존 simulator frame 계산과 PPO trace는 실행 기록이 있음 synthetic tf2 fault suite와 Isaac bridge는 미실행
Observed 기존 v161에서 body/world 의미 불일치가 평가 실패로 나타남 그 사례가 tf2 lookup 실패였다는 뜻은 아님
Accepted frame별 의미와 clock domain을 분리해 기록하는 진단 원칙 live G1 tf graph·calibration·latency acceptance 없음
Deployed 없음 실물 G1 actuator 경로에 tf2를 연결했다고 주장할 수 없음

tf2 lookup이 실패하는 이유는 좌표 변환 수식이 어려워서만이 아닙니다. 이름이 없는 frame, 연결되지 않은 tree, buffer 밖의 시각, 늦게 도착한 publisher, 다른 clock과 epoch가 각각 다른 실패를 만듭니다. 그 실패를 latest transform 하나로 덮으면 시스템은 움직일 수 있지만, 어떤 물리 시점의 좌표를 실행했는지 설명할 수 없게 됩니다.

제가 다음에 통과시킬 시험은 단순합니다. image acquisition stamp가 buffer 안에 있을 때만 camera_optical_frame→pelvis lookup을 허용하고, 같은 image를 buffer window 밖으로 지연시키면 pose를 생성하지 않은 채 정확한 extrapolation evidence와 command rejection을 남기는지 확인합니다. 두 경우의 차이가 artifact로 남아야 frame tree를 자연어 action과 실제 actuator authority 사이에 놓을 수 있습니다.

함께 읽기: G1의 world·body·joint frame과 quaternion · G1 tick·ROS time·monotonic clock 동기화 · 센서 측정값에서 actor observation까지의 계보 · ROS 2 QoS와 lowstate·lowcmd · executor·callback group·backpressure · launch·readiness·shutdown

공식 문서: tf2 개요 · tf2_ros Buffer API · tf2 오류 진단 튜토리얼 · tf2 CLI tools · sensor_msgs/Image timestamp·frame 계약

댓글 달기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다

위로 스크롤