[자율주행] 센서 시간 동기화 — 가장 조용히 틀리는 데이터
Data Engineering/자율주행
· 2026-07-14
들어가며
지난 글에서 주행 로그를 질의 가능한 데이터로 바꾸는 파이프라인을 훑으며, 시간 동기화에 대해 이렇게 적었다.
"정렬이 어긋난 데이터는 조용히 틀린 답을 낸다. 가장 눈에 안 띄면서 가장 치명적이다."
이번 글은 그 한 문장을 파고드는 글이다. 왜 시간 동기화가 자율주행 데이터에서 가장 까다로운 문제이고, 실제로 무엇이 어긋나며, 그걸 어떻게 맞추고 — 데이터 엔지니어 입장에서 더 중요하게는 — 어떻게 검증하는가.
먼저 직관 하나를 깨고 가자. 우리는 흔히 "라이다 481번 프레임과 카메라 481번 프레임"을 같은 순간으로 여긴다. 하지만 엄밀히 말하면 그 둘은 같은 순간이 아니다. 심지어 라이다 481번 프레임 '안'의 점들끼리도 같은 순간이 아니다. 이 어긋남을 다루는 게 이번 글의 전부다.
센서마다 시계가 다르다
문제의 뿌리는 단순하다. 차량 위의 모든 센서는 저마다 독립된 시계와 트리거를 갖고 있다.
- 라이다는 모터로 회전한다. 10Hz라면 한 바퀴에 100ms. 트리거는 이 회전에 물려 있다.
- 카메라는 셔터로 노출한다. 노출 시작·종료 시각이 따로 있고, 프레임률(예: 30fps)도 라이다와 무관하다.
- 레이더는 또 자기만의 chirp 주기가 있다.
- CAN / IMU는 훨씬 빠른 주기(수백 Hz~kHz)로 자기 시각을 찍는다.
이것들이 각자의 클럭으로 타임스탬프를 찍으면, 세 가지가 동시에 어긋난다.
- 오프셋(offset): 시계들의 0점이 다르다.
- 드리프트(drift): 크리스탈 오차로 시계가 서로 다른 속도로 흘러, 시간이 지날수록 벌어진다.
- 지터(jitter): 트리거·전송 지연이 프레임마다 들쭉날쭉하다.
동기화란 결국 이 세 가지를 잡아, 모든 센서를 하나의 기준 시간축 위로 끌어오는 일이다.
'언제'가 두 개다: 센서 시각 vs 호스트 도착 시각
여기서 데이터 엔지니어가 가장 먼저 밟는 지뢰가 있다. 하나의 센서 프레임에는 사실 시각이 두 개 붙는다.
1[ 센서 캡처 시각 ] ──전송·드라이버·버퍼링(지연·지터)──▶ [ 호스트 도착 시각 ] 2 sensor time host recv time
- 센서 캡처 시각: 실제로 그 데이터가 물리적으로 측정된 순간.
- 호스트 도착 시각: 그 메시지가 기록 PC에 도착해 로그에 찍힌 순간.
둘 사이에는 전송·드라이버·버퍼링에 따른 가변 지연이 낀다. 문제는, 급하게 만든 파이프라인일수록 편해서 호스트 도착 시각으로 정렬해버린다는 것이다. 이러면 지연이 큰 라이다와 지연이 작은 CAN이 서로 다르게 밀려, 정지 상태에선 멀쩡해 보이다가 고속 주행에서만 어긋나는 최악의 버그가 생긴다. 검출은 안 되는데 학습 데이터는 조용히 오염된다.
원칙은 하나다. 가능한 한 센서 캡처 시각(hardware timestamp)을 쓰고, 호스트 시각은 최후의 수단으로만 쓴다.
시계를 맞추는 하드웨어: PTP와 PPS
그렇다면 애초에 센서들의 시계를 물리적으로 맞춰버리면 되지 않을까? 맞다. 실제 차량은 소프트웨어 정렬 이전에 하드웨어로 시계를 맞춘다.
- PTP (IEEE 1588, Precision Time Protocol): 이더넷으로 연결된 센서·컴퓨트 노드들이 마스터 클럭에 시계를 맞춘다. 네트워크 지연을 측정·보정해 마이크로초 수준까지 동기화한다. 이더넷 라이다·카메라가 늘어나며 사실상 표준이 되었다.
- GPS PPS (Pulse-Per-Second): GNSS 수신기가 매초 정확히 상승하는 펄스를 쏘고, 센서들이 이 펄스에 트리거·타임스탬프를 물린다. GPS 시각이라는 절대 기준에 모두를 묶는 방식.
- 하드웨어 트리거: 한 신호로 여러 카메라의 셔터를 동시에 끊는다. 소프트웨어로 "동시에 찍어"라고 명령하는 것과는 정밀도가 다르다.
핵심은, 좋은 데이터셋일수록 이 하드웨어 동기화가 이미 걸려 있고, 타임스탬프가 신뢰할 만하다는 점이다. 데이터 엔지니어의 일은 그 신뢰를 검증하고, 없으면 소프트웨어로 메우는 것이다.
라이다는 한 프레임 안에서도 시간이 흐른다
이제 이 글에서 가장 중요한 부분이다. 라이다 프레임은 하나의 순간에 찍힌 스냅샷이 아니다.
10Hz 라이다는 한 바퀴 도는 데 100ms가 걸린다. 즉, 프레임의 첫 점과 마지막 점 사이에는 100ms의 시간차가 있다. 그동안 차가 시속 72km(=20m/s)로 달렸다면, 차는 그 사이 2m를 이동한다. 그런데 순진하게 모든 점을 "프레임 시각 하나"로 취급하면, 스캔은 자차 이동만큼 찌그러진다. 이걸 모션 왜곡(motion distortion)이라 하고, 바로잡는 걸 디스큐(deskew) / 언디스토션(undistortion) 이라 한다.
그래서 제대로 된 라이다 데이터는 점마다 타임스탬프를 따로 갖는다.
1{ 2 "frame_id": 481, 3 "t_start": 1718332800.000, 4 "t_end": 1718332800.100, 5 "points": [ 6 { "x": 12.1, "y": 0.3, "z": -1.2, "t": 1718332800.001 }, 7 { "x": 12.0, "y": 0.4, "z": -1.2, "t": 1718332800.052 }, 8 { "x": 11.9, "y": 0.4, "z": -1.2, "t": 1718332800.099 } 9 ] 10}
보정 원리는 이렇다. 각 점의 시각 t에서의 자차 자세(ego pose)를 IMU·오도메트리로 구해, 그 점을 기준 시각(보통 t_end 또는 스캔 중앙)의 좌표계로 되돌려 놓는다.
1# 점 p_i 를 기준 시각의 좌표계로 되돌리는 보정 2p_corrected = T( pose(t_ref) )⁻¹ · T( pose(t_i) ) · p_i 3 4# T(pose): 해당 시각의 자차 자세를 나타내는 변환(회전+병진) 5# → 즉, "그 점이 찍힌 순간의 자차 위치"만큼의 이동을 상쇄한다
이 보정이 빠지면, 정지 상태 데이터에선 아무 문제가 없어 보인다. 그런데 고속 주행·급회전 구간에서만 벽이 휘고 차선이 어긋난다. 이게 앞에서 말한 "조용히 틀리는" 데이터의 실체다. 그리고 이 보정을 하려면 라이다 점 타임스탬프와 IMU 시각이 정확히 동기화되어 있어야 한다 — 결국 다시 시간 동기화로 돌아온다.
카메라의 롤링 셔터
카메라도 순진한 가정이 깨지는 지점이 있다. 대부분의 카메라는 롤링 셔터(rolling shutter) 라서, 센서를 위에서 아래로 한 줄씩 순차 노출한다. 프레임 위쪽 픽셀과 아래쪽 픽셀은 수 ms~수십 ms 차이로 찍힌다.
그래서 카메라 프레임의 타임스탬프도 엄밀히는 "이 프레임의 시각"이 아니라 노출 중앙 시각(mid-exposure) 으로 정의하는 게 맞다. 라이다 점을 카메라 픽셀에 투영해 라벨을 옮기는(projection) 작업을 할 때, 이 노출 타이밍을 무시하면 빠르게 움직이는 물체에서 어긋남이 생긴다. 정밀 캘리브레이션 파이프라인이 롤링 셔터까지 모델링하는 이유다.
그래서 어떻게 정렬하나
물리적으로 완벽히 동시에 찍는 건 불가능하다. 결국 하나의 기준 시간축을 정하고, 나머지를 거기에 맞춘다.
- 기준 클럭을 정한다. 보통 라이다(가장 느리고 무거운 모달)를 기준으로 삼거나, 공통 트리거/GPS 시각을 기준으로 쓴다.
- 연관(association) 전략을 고른다.
- 최근접(nearest): 기준 시각에 가장 가까운 다른 센서 프레임을 붙인다. 단순하지만 최대 반 주기만큼 어긋날 수 있다.
- 보간(interpolation): 자차 자세·연속 신호는 앞뒤 샘플을 시간 가중으로 보간해 기준 시각의 값을 만든다. 포즈·CAN처럼 연속적인 양에 적합하다.
- 허용 오차를 정하고, 초과하면 버린다. "±5ms 안에서만 매칭, 아니면 그 프레임은 폐기." 억지로 붙인 쌍은 없느니만 못하다.
여기서 데이터 엔지니어의 판단이 들어간다. 무엇을 최근접으로 붙이고 무엇을 보간할지, 허용 오차를 얼마로 둘지는 데이터의 용도(학습용/디버깅용/평가용)에 따라 달라진다.
데이터 엔지니어의 몫: 동기화를 '검증'하는 일
지금까지가 "어떻게 맞추나"였다면, 데이터 엔지니어의 진짜 몫은 "맞았는지 어떻게 아나" 다. 동기화는 한 번 걸고 끝나는 게 아니라, 수천 시간의 로그에서 계속 검증되어야 하는 지표다.
파이프라인에 심어두는 검증 항목은 대략 이렇다.
- 프레임 간격 검사: 실제 프레임 간격이 기대 주기(±지터)를 벗어나면 드롭·중복 의심.
- 크로스 모달 오프셋 추적: 라이다-카메라 타임스탬프 차이의 분포를 프레임마다 기록하고, 드리프트가 커지는 로그를 격리.
- 호스트-센서 시각 갭 모니터링: 이 갭이 튀는 구간은 전송 병목·기록 문제 신호.
- 디스큐 sanity check: 보정 전후 스캔의 벽·차선 직선도를 비교해, 보정이 실제로 먹었는지 확인.
이런 지표는 결국 카탈로그의 품질 컬럼으로 들어간다. 그래야 지난 글에서 만든 장면 검색이 "시간 동기화가 검증된 프레임만" 걸러낼 수 있다.
1-- 학습셋에는 동기화가 확인된 프레임만 담는다 2SELECT scene_id, frame_id 3FROM drive_frames 4WHERE lidar_cam_offset_ms < 5 -- 크로스 모달 오프셋 허용치 이내 5 AND frame_gap_ok = true -- 드롭/중복 없음 6 AND deskew_applied = true; -- 모션 왜곡 보정 완료
동기화를 '한 번 맞추는 작업'이 아니라 '지속적으로 감시하는 데이터 품질 지표' 로 다루는 것 — 이게 파이프라인 관점에서 시간 동기화를 대하는 방식이다.
마치며
정리하면 이렇다. 자율주행에서 "같은 순간"이란 공짜로 주어지지 않는다. 센서마다 시계가 다르고, 라이다는 한 프레임 안에서도 시간이 흐르며, 카메라는 한 줄씩 찍힌다. 이 어긋남을 하드웨어(PTP·PPS)로 좁히고, 소프트웨어(디스큐·보간)로 메우고, 마지막으로 데이터 품질 지표로 계속 검증하는 것 — 여기까지가 시간 동기화의 전부다.
첫 글에서 이 문제를 "가장 조용히 틀리는 데이터"라고 불렀던 이유가 여기 있다. 어긋난 동기화는 에러를 던지지 않는다. 그냥 고속 구간에서만, 급회전에서만, 조용히 틀린 답을 낼 뿐이다. 그래서 데이터 엔지니어는 던져지지 않는 에러를 스스로 지표로 만들어 감시해야 한다.
다음 글에서는 이렇게 정렬된 데이터를 실제로 어떻게 쌓는지 — 컬럼형 저장과 레이크하우스 — 를 파볼 생각이다.