Files
GhiVideo/docs/발표_GhiVideo_좌표정합과측점기반구현.md
b23042andClaude Opus 4.8 e0ff5dd6d0 feat: 지도 나침반(OSM/위성) 추가 + 측점 검색·마커 정확도 개선 + POI/영상 수정
- 지도 기반 나침반(노스업, 현재위치, 시야 역삼각형): 호버 확대·휠 줌·클릭 위성전환, OSM/Esri 타일 서버 프록시(/api/tile)
- 스테이션 검색: 실제 측점(chain) 기준 이동, 없으면 '측점 없음' 안내
- 역 마커: 직교 투영 측점 일치 시에만 표시, 미도착 종점은 추가 방식
- POI 팝업 겹침/재등장·라벨 정합 수정
- 영상 fps 데이터 기반 자동 산출
- 기술/발표/쉬운설명 문서 추가

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 18:03:30 +09:00

40 KiB
Raw Permalink Blame History

GhiVideo 발표 자료

드론 GPS · 영상 · POI 좌표를 영상 위 정확한 위치에 — 그리고 프레임에서 측점(스테이션)으로

작성일: 2026-06-30 · 발표용 요약 문서 슬라이드 구분은 --- 입니다 (Marp/reveal.js 변환 가능). 각 장은 "그림 → 핵심 → 코드 근거" 순.

<style> figure.fig{margin:18px 0;text-align:center;page-break-inside:avoid;break-inside:avoid;} figure.fig svg{width:100%;max-width:680px;height:auto;border:1px solid #e7e0d2;border-radius:10px;background:#fffdf8;} figure.fig figcaption{font-size:0.88em;color:#777;margin-top:6px;} text{font-family:'Malgun Gothic','Hancom Gothic',sans-serif;} h2{border-bottom:2px solid #e7e0d2;padding-bottom:4px;} .lead{background:#f8f6ef;border-left:4px solid #b45309;padding:10px 14px;border-radius:6px;} </style>

목차

🟦 공간 정합 🟩 시간 동기화 🟧 표시 안정 🟦 측점 전환
지도좌표 → 화면픽셀 영상시각 ↔ 드론프레임 떨림·튐 제거 프레임 → 측점(공간)
  1. 우리가 풀어야 했던 문제
  2. 입력 데이터 3종 — GPS · 영상 · POI
  3. 전체 파이프라인 한 장
  4. 핵심 ① 좌표 정합 — 지도 좌표를 화면 픽셀로 (투영)
  5. 핵심 ② 시간 동기화 — 영상 시각과 드론 프레임 맞추기
  6. 핵심 ③ 라벨 안정화 — 떨림·튐 제거
  7. 핵심 ④ 프레임 기반 → 측점(스테이션) 기반 전환
  8. 정확도 보정 도구 (DEM · 드래그 · 세로화각)
  9. 정리 — 무엇을, 어떻게

1. 우리가 풀어야 했던 문제

드론이 철도 노선을 따라 비행하며 찍은 영상 위에, 교량·터널·역사·지장물(POI)의 이름표를 실제 위치에 정확히 띄우고 싶다.

🌉 회덕제1가도교 🚇 법동터널 GPS 36.334, 127.456 · 고도 123m · 측점 157K970 N 드론 영상 + 실제 위치에 붙는 이름표 + 위치 HUD
그림 1. 목표 — 흔들리는 드론 영상 위에, 지도 좌표만 가진 POI를 "진짜 그 자리"에 표시.

난이도 4가지 → 그래서 세 가지 정합이 필요합니다.

난이도 무엇이 어려운가 해결 정합
드론 흔들림 yaw/pitch/roll·고도 변화·빠른 이동 🟦 공간 + 🟧 안정
POI는 지도좌표만 "화면 어디"인지 모름 🟦 공간(투영)
시간 어긋남 영상 시각 ↔ 드론 데이터 시각 🟩 시간
매끄러움 60fps·떨림 없이 🟧 안정

2. 입력 데이터 3종

① 드론 로그 프레임별 비행 CSV frame lat yaw 0 36.33 12° 1 36.33 13° … alt·pitch·roll ② 영상 mp4/webm 주행영상 ③ POI / 측점 KMZ(1순위)+측점CSV 🌉 교량 (lat,lon,z) 🚇 터널 (lat,lon,z) 📍 측점 157K970 이 셋을 시간·공간으로 묶는다
그림 2. 드론 로그(언제·어디·어느 방향) + 영상 + 지도 POI를 결합.

3. 전체 파이프라인 한 장

🟩 영상 재생 시각 t smoothTimeRef: 60fps 단조보간 t → 드론 프레임 보간 → 그 순간의 위치·자세(pose) 🟦 좌표 정합 (투영) — 4단계 POI(위경도·표고) → ENU(평면 미터) → 카메라 좌표 (yaw/pitch/roll 회전) → 화면 정규픽셀(0~1) [핀홀 카메라] → object-fit:cover 보정 → 실제 화면 px 🟧 라벨 평활(One Euro) + 이상치 거부 → 떨림 제거 Canvas RAF 60fps 렌더 → 영상 위 이름표 🟦 GPS→측점 투영 → 하단 측점바 커서
그림 3. 시각 t → pose 보간 → 4단계 투영 → 평활 → 렌더. 동시에 GPS는 측점값으로 투영되어 측점바로.

4. 핵심 ① 좌표 정합 — 지도 좌표를 화면 픽셀로

"POI는 위경도만 안다. 그게 지금 화면 어디에 보이는가?" — 이게 이 프로그램의 심장. 4단계 투영으로 푼다.

위경도+표고POI 🌉 (지도) 평면 미터ENU · 1단계 카메라 좌표회전 · 2단계 화면 0~1핀홀 · 3단계 화면 pxcover · 4단계
그림 4. 4단계 투영 파이프라인. 파일: client/src/utils/geoProjection.ts

4-1. 둥근 지구를 평평하게 (ENU 변환)

지구는 둥글어 거리 계산이 어렵습니다. 노선 한 구간만 잘라 평평한 모눈종이(미터 단위) 로 폅니다.

둥근 지구 (위경도) EPSG:5186 TM 투영 E 동(m) N 북(m) 기준점(0,0,0) POI (E=120, N=300, U=-5)
그림 5. E/N = 동·북 몇 m, U = 기준점 대비 상대 높이(alt refAlt).
// geoToEnu (9197)
const [e, n] = latLonToTM(lat, lon);   // 위경도 → 평면 미터
return [e, n, alt - refAlt];

4-2. 드론이 보는 방향으로 회전 (카메라 좌표)

세상 좌표(E/N/U)를 드론의 yaw·pitch·roll 회전행렬에 곱해 "카메라가 보는 좌표(Xc, Yc, Zc)"로 바꿉니다.

세상 기준 (북 고정) E N 🚁 북쪽 봄 POI 회전 드론이 보는 기준 (정면이 기준) 🚁 앞(Zc) 옆(side) POI (앞 50m, 옆 8m)
그림 6. 북 기준 좌표를 "드론 정면 기준"으로 회전. 진행방향 거리 fwd(앞)·side(옆)도 함께 산출.
// toCameraCoords (301317)
cc.fwd  = relEnu[0]*sy + relEnu[1]*cy;   // +면 앞쪽
cc.side = relEnu[0]*cy - relEnu[1]*sy;   // +면 오른쪽 → "앞은 멀리, 옆은 가깝게" 비등방 필터 근거

4-3. 3D를 납작한 사진으로 (핀홀 카메라 투영)

핵심 한 줄: 깊이(Zc)로 나눈다 → 멀수록 화면 가운데로 작게. (바늘구멍 사진기 원리)

구멍(렌즈) 가까운 🌉🌉 화면(0~1) 가까운 게 크게 맺힘 거리(Zc)가 클수록 → 화면에 작게
그림 7. pxRaw = 0.5 + (Xc/Zc)·(f/sensorW). 0.5=정중앙, f/sensor=화각.
// pixelFromCamera (126137)
pxRaw = (0.5 + cx0) + (Xc / Zc) * (f / sW);  // 가로 0~1
pyRaw = (0.5 + cy0) + (Yc / Zc) * (f / sH);  // 세로 0~1

4-4. 높이 기준 통일 (지오이드 보정) + 화면 정렬

두 개의 '0층' 문제: 지도 높이(정표고·해발)와 GPS 높이(타원체고)는 기준이 달라 대전 기준 약 25.8m 차이. 안 맞추면 라벨이 위아래로 어긋납니다.

타원체고(GPS) 0 정표고(해발) 0 ≈ 25.8m (geoidOffset) 지도 높이에 더해 GPS 기준으로 통일 기준을 맞춰야 라벨이 제 높이에 붙는다
그림 8. 지도 표고 + geoidOffset → GPS와 같은 기준. 마지막에 object-fit:cover 잘림을 반영해 0~1 → 실제 px.
geoToEnu(lat, lon, targetAlt + params.geoidOffset, ...);   // 높이 기준 통일
// 이후 coverRef(StationOverlay 921930)로 정규(0~1) → 실제 화면 px 정렬

➡ 4-1 ~ 4-4를 거치면 POI가 영상 속 진짜 그 자리에 찍힙니다.


5. 핵심 ② 시간 동기화 — 영상 시각 ↔ 드론 프레임

좌표가 맞아도 "지금 영상 시각의 드론 위치" 를 못 집으면 라벨이 엉뚱한 데 뜹니다.

문제: 브라우저 video.currentTime은 ~250ms 간격으로만 갱신 → 커서·라벨이 뚝뚝 끊김. 해결: smoothTimeRef — 벽시계로 60fps 단조 보간.

실제 currentTime (250ms 띄엄띄엄) → 라벨/커서가 뚝뚝 끊김 smoothTimeRef (매 프레임 채움 · 60fps) media + 경과시간×배속 · 0.3s 이상 벌어지면 재동기화
그림 9. 띄엄띄엄한 실제 시각을 60fps로 메워 부드럽게. 이 t로 드론 pose를 보간.
// VideoPlayer.tsx 62117
let est = a.media + ((performance.now() - a.wall)/1000) * rate;
if (real - est > 0.3) est = real;     // 재동기화
smoothTimeRef.current = t;            // ref로 노출 → 리렌더 없이 매 프레임 읽음

6. 핵심 ③ 라벨 안정화 — 떨림·튐 제거

드론은 미세하게 흔들립니다. 그대로 투영하면 라벨이 부르르 떨립니다. → One Euro 방식 속도적응 평활 + 이상치 거부.

떨림(왕복) 평활속도≈0 → 강하게 평활 (안정) 실제 이동 방향 일관 → 즉시 추종 (지연 없음) 갑작스런 점프 ✗ 8프레임까지 무시 후 수용 (시크는 진짜 → 결국 수용)
그림 10. 떨림은 죽이고, 진짜 이동은 즉시 따라가고, 튐(점프)은 무시. 세 동작을 한 필터로.
// StationOverlay.tsx 4861
if (d > REJECT_DIST && prev.rej < MAX_REJECT_FRAMES)   // 0.12 초과 점프 = 이상치
  return { ...prev, rej: prev.rej + 1 };               // 위치 유지
const a = min(maxAlpha, minAlpha + (maxAlpha-minAlpha)*min(1, speed/speedRef));
return { x: prev.x + dx*a, y: prev.y + dy*a, ... };    // 속도 클수록 빠르게 추종

7. 핵심 ④ 프레임 기반 → 측점(스테이션) 기반

7-1. 왜 바꿨나? — 시간축의 한계

철도 현장은 위치(측점) 로 말합니다: "157K970 지점의 교량". 그런데 시간축은 드론이 호버(정지) 하면 무너집니다.

시간축 (균등) 0s1s2s3s4s5s 드론 호버(정지) 시간은 가는데 위치는 안 변함 → 시간축에선 구분 불가 ✗
그림 11. 시간 균등축에선 "공중 대기(호버)" 구간을 표현할 수 없다.

➡ 그래서 측점(공간)축으로 재해석: 드론이 호버하면 측점도 멈춰 있어야 자연스럽다.


7-2. 구현 ① — GPS를 측점값으로 투영

드론 GPS를 측점 폴리라인(선로)에 수직으로 내려 "측점값(km)"과 "선로 이격(m)"을 구합니다.

157K900 158K000 측점 폴리라인(선로) 🚁 드론 GPS km=157K958, 이격 4.2m projectToChain (수직 내림)
그림 12. 측점명 "157K970" → 157970m 환산으로 선로를 만들고, GPS를 수직 투영해 측점값·이격 산출.
// chainage.ts projectToChain (3550)
const t = clamp(((px-a.x)*dx + (py-a.y)*dy) / L2, 0, 1);  // 선분 위 투영 비율
bestKm = a.km + (b.km - a.km) * t;                         // 보간 측점값

7-3. 구현 ② — 이동거리축 (핵심 아이디어)

시간축 대신 드론이 실제로 이동한 거리를 축으로 씁니다. 측점값 변화량 |Δ|을 매 프레임 누적.

측점값(평활): 920 → 930 → 940 → 940 940 940 → 950 → 960 누적 이동거리 = 축 위치 ⏸ 정상 주행 →→→ 호버: 누적 멈춤 → 커서 정지! →→→ 주행 호버 구간에서 커서가 멈춰 "공중 대기"가 한눈에 보인다
그림 13. |Δ측점값|을 누적해 0~1로 정규화 → 축 위치. 호버는 누적이 멈추므로 커서도 정지.
// StationBar.tsx 233274
sm[i] = (pre[hi]-pre[lo])/(hi-lo);          // 측점값 ±8프레임 평활
if (i>0) cum += Math.abs(sm[i]-sm[i-1]);    // |Δ| 누적 = 실제 이동거리
frac[i] = cum / total;                       // 0~1 정규화 → 축 위치

7-4. 측점 기반의 결과 — 하단 측점바

🚉🌉교량 🚇터널 🌉구교 ◌종점 현재 위치 커서 (60fps) 회색=미도착
그림 14. 측점·구조물을 공간축에 배치. 영상별 FPS 자동산출로 배지 정밀화 · 종점 미도착은 회색 + 빈 링.

7-5. 프레임 ↔ 측점 — 한 장 비교

구분 프레임 기반 측점 기반 (현재)
시간(초) 실제 이동거리/측점(km)
호버 구분 불가 커서 정지로 가시화
현장 용어 "3분 20초" "157K970 교량"
위치 질의 어려움 GPS→측점 투영으로 즉답
동기화 currentTime smoothTimeRef + 측점투영

시간축 영상을 공간축(측점)으로 재해석한 것이 이 프로그램의 정체성.


8. 정확도 보정 도구

투영은 카메라 파라미터·표고 가정에 민감 → 사용자가 미세보정할 수 있게 함. 핵심은 투영의 역방향(화면→세계)을 푸는 것.

영상 화면 라벨을 끌어 제자리로 실제 땅 위경도 역산 화면점 → 광선 → 지면 교차 → 실제 위치 groundPointFromPixel
그림 15. "끌어다 맞추기" = 역투영. 끈 화면점을 광선으로 쏴 지면과 만나는 실제 좌표를 복원.
도구 동작
DEM 자동표고 모든 POI를 실제 지형고도로 (서버 /api/elevation, SRTM 30m)
드래그 편집 라벨을 끌면 groundPointFromPixel로 위경도 역산
세로화각 보정 POI를 실제 위치로 끌면 sensorH만 역산 자동보정
보정값 저장 poi_overrides.json 내보내기/가져오기

9. 정리 — 무엇을, 어떻게

드론 GPS 영상 POI 좌표 🟩 시간 동기화smoothTimeRef60fps 보간 🟦 좌표 투영ENU→카메라→핀홀→cover+geoid 보정 🟧 평활One Euro +이상치 거부 영상 위정확한 라벨 🟦 GPS→측점 투영 → 이동거리축→ 측점 기반 측점바 (호버까지 표현)
그림 16. 3대 정합(공간·시간·안정) + 패러다임 전환(프레임→측점).

3대 정합 기술

  1. 🟦 공간: 4단계 투영(ENU→카메라회전→핀홀→cover) + 지오이드 보정 + 역투영 보정
  2. 🟩 시간: smoothTimeRef 60fps 단조보간으로 영상시각↔드론프레임 정합
  3. 🟧 안정: One Euro 속도적응 평활 + 이상치 거부

패러다임 전환

  • 프레임(시간) 기반 → 측점(공간) 기반: GPS를 선로에 투영해 측점값화, 이동거리축으로 호버까지 표현

부록 — 핵심 소스 색인

기술 파일 라인
ENU 변환 geoProjection.ts 9197
카메라 좌표 + fwd/side geoProjection.ts 301317
핀홀 투영 geoProjection.ts 126137
지오이드 보정 geoProjection.ts 5367, projectPoint
역투영(보정) geoProjection.ts 149251
object-fit cover 정렬 StationOverlay.tsx 921930
라벨 평활/이상치 StationOverlay.tsx 4861
RAF 60fps 렌더 StationOverlay.tsx 9051198
smoothTimeRef VideoPlayer.tsx 62117
측점 투영(chainage) chainage.ts 1064
이동거리축 StationBar.tsx 233274
종점역 미도착 StationBar.tsx 300310, 574586
DEM 표고 StationOverlay.tsx / elevation.ts 13841415 / 1467

상세 구현은 구현상세_GhiVideo_기술-소스코드매칭.md 참조.