숫자가 가깝다고 같은 시간이 아니다
취득 시각은 센서가 관측한 사건의 시각, 수신 시각은 프로그램이 메시지를 받은 시각이다. 전송/대기·처리 지연이 있으므로 같지 않을 수 있다. 시스템/시뮬레이션/장치 시계는 시작점과 진행 속도가 다를 수 있다. 타임스탬프를 모두 초 단위로 표시했다고 바로 빼지 않는다. 어떤 시계/기준 사건을 뜻하는지와 reset/정지/역행 정책을 확인한다.
단순 시계 모형을 c=a t+b라고 하자. t는 공통 기준 시각(s), c는 장치 시계 표시(s), a는 무차원 속도 비율, b는 offset(s)다. 같은 두 물리 사건이 기준 t=0/10 s에서 장치 c=2/12.01 s로 기록됐다고 정확히 주어지면 a=(12.01−2)/10=1.001, b=2다. c=12.01을 공통 시간으로 바꾸려면 t=(c−2)/1.001=10 s다. offset만 2를 빼면 10.01 s로 10 ms가 남는다.
이 예는 동시 사건 대응이 알려진 조건이다. 메시지를 받은 시각 차이만 보고 그 전부가 시계 offset이라고 추정하면 전송 지연이 섞인다. 실제 추정에는 대응 오차/지연 비대칭·드리프트 변화와 유효 구간을 기록한다. 시계가 reset되면 이전 a/b를 계속 쓰지 않는다. 어떤 자료가 시스템 시각이고 어떤 자료가 /clock 기반인지도 명시한다.
서로 다른 표본 사이의 상태를 구한다
카메라와 엔코더는 같은 주기로 취득하지 않을 수 있다. 카메라 30 Hz/상태 100 Hz라면 매 영상마다 같은 stamp의 상태가 있다는 보장이 없다. 관련 자료가 이미 같은 시간 기준으로 변환됐다는 전제 아래 가까운 표본을 고르거나 보간하는 정책을 정한다. 여기서는 원리 계산을 위해 별도 10 Hz 상태 두 점만 쓴다.
t₀=1.00 s의 q₀=0.2 rad, t₁=1.10 s의 q₁=0.4 rad에서 영상 시각 t*=1.04 s의 상태를 선형 보간하자. α는 구간 안에서의 시간 비율이다.
α=0.4이므로 q=0.6×0.2+0.4×0.4=0.28 rad다. 구간 안에서 변화가 선형이라는 모형이며 급가속·이상치/센서 고장에서는 정확하지 않을 수 있다. t₁=t₀이면 분모가 0이므로 중복 시각 처리 정책이 필요하고, t*가 구간 밖이면 보간이 아닌 외삽이다. 비유한 값/역행과 너무 큰 빈 구간을 검사한다.
오프라인 파일에는 양쪽 표본이 있어도 온라인에서 t=1.045 s 현재에는 1.10 s의 미래 관측이 아직 없다. 이 경우 0.28을 이미 관측한 상태처럼 사용할 수 없다. 양쪽 표본을 기다리거나 이전 값을 유지/제한된 외삽·미응답으로 처리하는 정책을 선택하고 지연/불확실성을 기록한다. 전송 지연이 0이어도 1.10까지 기다리려면 현재부터 최소 55 ms가 더 필요하다. 미래 자료를 이용한 보간을 지연 없는 실시간 결과로 표시하지 않는다.
회전도 숫자를 그대로 평균하면 실패할 수 있다. 예를 들어 경계 설명용 degree에서+179→−179는 짧은 회전으로 2도 차이지만 단순 평균은 0이다. 먼저 179→181처럼 연속 표현으로 풀면 중간은 180도다. 실제 내부 계산 단위rad와 회전 경로를 명시하고, 일반 3차원 회전은 해당 공간에 맞는 보간을 써야 한다. quaternion 성분을 무조건 각각 선형 평균해 유효 회전이라고 보지 않는다.
ApproximateTime은 자료를 새로 측정하지 않는다
message_filters의 TimeSynchronizer는 header의 stamp가 맞는 메시지 묶음을 찾는다. ApproximateTimeSynchronizer는 큐에 들어온 자료를 정한 시차 범위로 묶는다. 이는 각 stamp가 공통 시간 기준에서 해당 관측의 취득 시점을 올바르게 나타낸다는 전제 아래의 자료 선택이다. 서로 다른 시계를 보정하거나 센서를 같은 순간에 작동시켜 주는 기능도, 상태를 자동 보간하는 기능도 아니다. Jazzy 공식 Python 구현을 기준으로 이 역할을 구분한다.
그림 M14.03-1. 각 칸은 서로 다른 역할/근거다. 나란히 비교할 항목이며 위아래가 시간 순서를 뜻하지 않는다. 직접 제작한 개념도이며 본문의 조건과 함께 읽는다.
queue_size는 입력별 보관 수, slop은 초 단위 허용 시차다. 이번에 확인한 Jazzy Python 구현의 최종 묶음 검사는 max stamp−min stamp<slop이므로 정확히 경계인 경우를 무조건 통과로 읽지 않는다. C++ 정책/다른 버전의 API나 내부 선택을 그대로 동일하다고 주장하지 않는다. 기본값은 False인 allow_headerless를 True로 켜서 header 없는 메시지에 수신 때 ROS 시각을 붙이면 원래 취득 시각을 되찾은 것이 아니며 지연이 섞일 수 있다.
합성 도착 순서를A stamp1.00→B stamp1.02→B stamp0.99, queue_size=2/slop=0.03 s로 두자. 처음A만 있을 때는 쌍이 없고 B1.02가 왔을 때 차이 0.02로 쌍을 만들어 두 메시지를 소비할 수 있다. 나중에 온B0.99가 A와 차이 0.01로 더 가까워도 이미 소비한 쌍을 전역 최적으로 다시 짜 주는 기능이라고 가정하지 않는다. 큐에 어떤 후보가 어느 시점에 있었는지와 도착 순서를 기록해야 한다.
큐를 늘리면 후보를 더 보관할 수 있지만 오래된 자료/대기·메모리도 고려한다. callback이 안 오면 시각만 아니라 입력별 실제 수신·QoS 호환/누락/큐 탈락을 조사한다. 정책이 한 세트의 자료를 소비하므로 서로 다른 빈도에서 모든 표본이 일대일로 쓰인다고도 가정하지 않는다. 같은 객체의 관측/프레임/시각과 선택 정책을 따로 남긴다.
허용 시차는 공간 오차와 연결한다
단순 optical 운동 모형에서 Z=1 m가 일정하고 점의 상대 X속도 v_x=0.2 m/s, f_x=400 pixel이라고 하자. 회전·Y/Z 변화·왜곡은 없고 두 자료는 같은 시계다. 사용한 상태가 영상보다 δt만큼 늦으면 위치 차이는 v_xδt, 투영의 가로 차이는 f_x v_xδt/Z다. δt=0.02 s면 0.004 m/1.6 pixel이다. +x 운동에서 늦은 상태를 쓰면 더 오른쪽으로 예측하는 부호도 확인한다.
이 모형에서 1 pixel 이하를 목표로 한다면 |δt|≤1×Z/(f_x|v_x|)=0.0125 s, 즉 12.5 ms가 필요하다. slop30 ms를 골랐다고 이 공간 예산을 만족하는 것은 아니다. 작은 시차여도 더 가까운 깊이/빠른 상대 운동에서 오차가 커질 수 있다. 실제 6자유도 운동/센서 노출과 다른 시계에서는 추가 모형과 불확실성이 필요하다.
기존 4.4 멀티레이트와 선택 투영/시간 개념 모형을 연결해 볼 수 있다. 위젯은 고정Z·정해진 상대속도·같은 시계의 단순 보충이며 실제 동기화/rolling shutter/필터 추정을 수행하지 않는다. 기본 활동은 아래 표만으로 끝낸다.