Files
duru_pcd_viewer/PCD_Viewer_가이드.md
T
Dongubak beb4182f04 macos-native: clean branch for cloning and building the Tauri desktop app
Orphan branch with no history — includes the Tauri shell (src-tauri/) and
vendored Three.js (vendor/) that were never pushed before, so a fresh clone
can actually build the native app. Drops the large .pcd sample captures and
editor cruft (.obsidian, .DS_Store); they aren't needed to build or run the
app (point clouds load via drag-and-drop, not bundling).
2026-08-22 02:21:38 +09:00

17 KiB

PCD Viewer 가이드

FAST-LIVO2 대시보드입니다. 두 가지로 씁니다.

  1. 실시간 — FAST-LIVO2가 도는 것을 브라우저에서 그대로 봅니다. 누적되는 맵, 궤적, 현재 포즈, 카메라 영상, 상태 텔레메트리.
  2. 사후 계측 — 저장된 .pcd를 열어 **거리 / 포지션(ΔXYZ)**을 측정합니다. 실시간으로 쌓고 있는 맵 위에서도 똑같이 측정됩니다.

GPS를 입혀 위경도를 표시하는 과정은 8절에 정리돼 있습니다(아직 미구현).


1. 파일 위치

항목 경로
뷰어 코드 ~/Documents/pcd viwer/pcd_viewer.html
포인트 클라우드 ~/Documents/pcd viwer/garam_good_lite.pcd
이 문서 ~/Documents/pcd viwer/PCD_Viewer_가이드.md

뷰어는 단일 HTML 파일이며, Three.js(0.160)를 CDN(unpkg)에서 불러옵니다. 빌드도 설치도 없습니다. 실시간 기능도 라이브러리 없이 rosbridge 프로토콜을 직접 말합니다(roslibjs 불필요).

~/Downloads/pcd_viewer.html에 예전 버전이 남아 있습니다. Documents 쪽이 최신입니다.


2. 실행 — 뷰어 띄우기

file://로 직접 열면 브라우저 보안정책(CORS) 때문에 .pcd를 못 읽습니다. 반드시 로컬 HTTP 서버를 띄워 접속해야 합니다.

cd ~/Documents/"pcd viwer"
python3 -m http.server 8000

브라우저에서:

http://localhost:8000/pcd_viewer.html

한 줄로 띄우고 바로 열기(macOS):

cd ~/Documents/"pcd viwer" && python3 -m http.server 8000 & sleep 1 && open "http://localhost:8000/pcd_viewer.html"

서버 종료:

lsof -ti:8000 | xargs kill

CDN을 쓰므로 인터넷 연결이 필요합니다(Three.js 로드용). 뷰어는 켤 때 같은 폴더의 all_raw_points.pcd를 자동으로 찾습니다. 없으면 조용히 "파일 열기" 안내로 넘어가며, 파일 없이 바로 실시간 연결을 해도 됩니다.

클라우드 여는 방법 세 가지

  • 같은 폴더에 all_raw_points.pcd를 두면 자동 로드
  • 파일 열기 버튼
  • .pcd 파일을 창에 드래그 앤 드롭

3. 실행 — 실시간 (ROS 2 Humble)

FAST-LIVO2가 도는 그 컴퓨터에서 rosbridge를 띄우면, 브라우저가 토픽을 직접 구독합니다. RViz는 필요 없습니다 — 이 대시보드가 RViz 자리를 대신합니다.

3-1. rosbridge 설치·실행

sudo apt install ros-humble-rosbridge-suite
ros2 launch rosbridge_server rosbridge_websocket_launch.xml    # :9090, rosapi 포함

rosapi 노드가 함께 떠야 합니다(위 launch에 포함). 토픽 목록을 읽는 데 씁니다.

3-2. 연결

  1. FAST-LIVO2 실행
  2. 뷰어 좌측 LIVE 칸에 주소 입력 → 연결
    • 같은 PC에서 볼 때: ws://localhost:9090
    • 다른 PC에서 볼 때: ws://<호스트IP>:9090
  3. 연결되면 토픽 4칸이 자동으로 채워집니다 (CLOUD / ODOM / PATH / IMAGE)

3-3. 토픽은 이름이 아니라 타입으로 잡습니다

연결 직후 /rosapi/topics로 전체 목록을 받아, 슬롯별로 메시지 타입이 맞는 토픽만 드롭다운에 올리고 하나를 자동 선택합니다. 이름은 우선순위 힌트로만 씁니다.

슬롯 받는 타입 이름 우선순위
CLOUD sensor_msgs/msg/PointCloud2 cloud_registered, cloud_map, laser_map
ODOM nav_msgs/msg/Odometry aft_mapped, odom
PATH nav_msgs/msg/Path path
IMAGE sensor_msgs/msg/CompressedImage image, rgb, color

자동 선택이 틀렸으면 드롭다운에서 바꾸면 즉시 재구독합니다. 사용 안 함으로 끌 수도 있습니다. 해당 타입의 토픽이 하나도 없으면 그렇다고 말합니다 — 그때는 FAST-LIVO2가 실제로 돌고 있는지, 토픽이 발행 중인지(ros2 topic list -t)부터 확인하세요.

카메라가 안 보이면: 지금은 CompressedImage만 받습니다. FAST-LIVO2가 raw sensor_msgs/msg/Image로 낸다면 IMAGE 칸이 비어 있습니다. 우회:

ros2 run image_transport republish raw in:=/origin_img compressed out:=/origin_img/compressed

4. 화면 구성

┌──────────────────────────────────────────────────────────┐
│ PCD VIEWER   FAST-LIVO2      LIVE ■ 연결됨 │ PTS │ SOURCE │  ← 레일: 상태
├────────────┬─────────────────────────────────────────────┤
│ LIVE       │                                             │
│ TELEMETRY  │                 3D 뷰포트                    │
│ MODE       │                                             │
│ DISPLAY    │                              ┌────────────┐ │
│ ACTIONS    │                              │  CAMERA    │ │
│ (조작 힌트) │                              └────────────┘ │
└────────────┴─────────────────────────────────────────────┘
  • 레일(상단): LIVE 연결 상태 · PTS(현재 점 개수) · SOURCE(파일명 또는 토픽명)
  • LIVE: rosbridge 주소, 토픽 바인딩 4칸, 복셀 누적 설정
  • TELEMETRY: 토픽별 수신 주파수, 복셀 유지율, 누적 버퍼 사용량
  • MODE: 탐색 / 거리 / 포지션 + 측정 결과 리드아웃
  • DISPLAY: 점 크기, 원본 색상
  • ACTIONS: 파일 열기 / 뷰 맞춤 / 초기화
  • CAMERA(우하단): 카메라 토픽이 붙어 있을 때만 나타납니다

색에 대한 약속

UI는 전부 무채색입니다. 색이 보이면 그건 마커라는 뜻입니다.

의미
🔴 거리 측정의 점 1
🟢 거리 측정의 점 2 / 포지션 모드의 측정점
🟠 앰버 포지션 모드의 기준점
흰색 측정선·거리 라벨, 현재 센서 포즈
회색 궤적(PATH), 기준점↔측정점 연결선

5. 사용법

마우스 조작

  • 좌클릭 드래그: 회전
  • 우클릭 드래그: 이동(팬)
  • : 확대/축소
  • F 또는 뷰 맞춤: 클라우드 전체가 보이게 카메라 리셋
  • Esc 또는 초기화: 측정 전부 삭제

실시간으로 맵이 자라는 동안에는 자동으로 뷰를 맞춰줍니다. 단, 카메라를 한 번이라도 건드리면 그때부터는 자동 조정을 하지 않습니다(원하는 시점 유지). 다시 전체를 보려면 F.

거리 측정

  1. MODE → 거리 선택
  2. 점 두 개를 차례로 클릭
  3. 두 점 사이에 흰 선과 거리(m)가 표시되고, 리드아웃에 소수 4자리까지 나옵니다
  4. 세 번째 클릭 또는 초기화로 리셋

포지션 (기준점 대비 ΔXYZ)

  1. MODE → 포지션 선택
  2. 첫 클릭 = 기준점(앰버) 설정
  3. 이후 클릭 = 기준점 대비 ΔX / ΔY / ΔZ + 직선거리 표시
  4. 초기화로 리셋

모드는 셋 중 하나입니다. 탐색에서는 클릭해도 점이 찍히지 않습니다(회전만). 측정은 실시간 맵 위에서도 똑같이 동작합니다.

표시

  • 점 크기 슬라이더
  • 원본 색상 — 끄면 클라우드가 중성 회색으로 바뀝니다. 색 없는 클라우드면 비활성화됩니다.

복셀 누적 (LIVE 칸, 기본 켜짐 / 5cm)

실시간으로 들어오는 스캔은 서로 크게 겹칩니다. 한 복셀에 점 하나만 남겨서 메모리가 세션 시간이 아니라 공간 크기에 비례하게 만듭니다. 자세한 원리는 6-2절.

  • 복셀 크기를 바꾸면 이미 쌓인 점도 새 격자로 다시 색인합니다. 키우면 그 자리에서 솎이고, 줄이면 버려진 점은 못 되살리지만 이후 스캔부터 촘촘해집니다.
  • 끄면 원본 그대로 쌓습니다. 짧고 고밀도인 계측에만 쓰세요 — 버퍼가 빨리 찹니다.
  • TELEMETRY의 복셀 행에 5cm · 유지 18.0%처럼 유지율이 나옵니다. 유지율이 높게 유지되면(예: 60% 이상) 복셀이 너무 작다는 뜻이니 키우세요.

복셀은 누적 방식이지 표시 옵션이 아닙니다. 파일로 연 클라우드에는 적용되지 않습니다.


6. 실시간이 동작하는 방식

6-1. 전송

rosbridge v2 프로토콜(subscribe / unsubscribe / call_service / publish)을 WebSocket으로 직접 말합니다. 메시지는 JSON이고, PointCloud2.dataCompressedImage.data 같은 uint8[] 필드는 base64로 옵니다.

  • base64는 바이너리의 약 1.33배입니다. localhost·LAN이면 문제없습니다. 원격에서 무거우면 CBOR 구독으로 올리는 게 다음 수순입니다.
  • 이미지만 10Hz로 throttle합니다. 스캔은 throttle하지 않습니다 — 버리면 맵이 손실되니까.

6-2. 누적 버퍼

600만 점짜리 GPU 버퍼를 미리 한 번 잡아두고, 들어오는 스캔을 뒤에 이어 붙입니다.

  • 스캔마다 객체를 만들지 않으므로 드로우 콜은 끝까지 하나입니다.
  • 매 프레임 전체를 올리지 않고, 새로 붙은 구간만 GPU로 올립니다.
  • 600만 점(약 90MB)이 차면 "가득 참"이라고 말하고 누적을 멈춥니다. 조용히 버리지 않습니다.
  • 더 필요하면 pcd_viewer.htmlLIVE_CAP 값을 올리세요(메모리는 점당 약 15바이트).

6-3. 복셀 누적 — 왜 필요한가

/cloud_registered매 스캔을 통째로 내보냅니다. 같은 벽을 100번 보면 같은 벽이 100번 옵니다. 그대로 쌓으면 버퍼가 1분 남짓에 찹니다(스캔당 1만 점 × 10Hz 기준).

그래서 점이 들어올 때마다 좌표를 복셀 격자로 양자화하고, 이미 점이 있는 복셀이면 버립니다. 결과적으로 누적량이 **"얼마나 오래 돌았나"가 아니라 "얼마나 넓은 공간을 봤나"**에 비례합니다.

  • 복셀 키는 축당 17비트로 패킹한 정수 하나입니다(±65,536 복셀 = 5cm에서 ±3.2km). 이 범위를 벗어난 점은 버리지 않고 중복 검사 없이 그냥 넣습니다.
  • 유지율은 TELEMETRY의 복셀 행에서 실시간으로 보입니다.

6-4. PointCloud2 해석

fields의 offset을 그대로 따라 읽습니다. x/y/z(float32)는 필수, 색은 rgb(packed) → 없으면 intensity(회색 계조) → 없으면 균일 회색 순으로 씁니다. is_dense: false인 클라우드의 NaN 점은 건너뜁니다.

6-5. 텔레메트리는 실측만

CLOUD / ODOM / IMAGE의 Hz는 메시지 도착 간격으로 계산합니다(EMA). 2초간 조용하면 그럴듯한 값 대신 로 떨어집니다. 값이 보이면 실제로 오고 있다는 뜻입니다.


7. 거리값이 계산되는 원리

핵심: PCD의 각 점은 이미 미터 단위의 실측 3D 좌표를 가진다.

7-1. PCD가 담고 있는 것

garam_good_lite.pcd 헤더:

FIELDS x y z rgb
TYPE F F F U
POINTS 2518822
DATA binary

모든 점이 (x, y, z) float 좌표 + 색상(rgb)을 가집니다. 이 좌표는 센서 기준 원점에서의 **실제 물리 위치(미터)**입니다. 사진(픽셀)과 달리 스케일이 이미 현실 단위라서 거리 측정이 가능합니다. 실시간 PointCloud2도 동일합니다 — 그래서 라이브 맵에서도 그대로 측정됩니다.

7-2. 클릭 → 점 찾기 (피킹)

  1. 클릭 위치를 정규화 좌표(-1~1)로 변환
  2. 카메라에서 그 픽셀을 지나는 광선(ray) 생성
  3. 수백만 점 중 광선에 수직으로 가장 가까운 점(distanceToRay 최소)을 선택 → 화면상 "커서 아래 점"
  4. 그 점의 저장된 원본 좌표 (x, y, z)를 읽음

피킹 반경은 카메라 거리에 비례합니다. 멀리서 찍으면 엉뚱한 점을 잡기 쉬우니 확대해서 클릭하세요. 5픽셀 이상 움직이면 클릭이 아니라 드래그로 처리됩니다.

7-3. 두 점 사이 거리 = 3D 유클리드 거리

점 A (x₁,y₁,z₁), 점 B (x₂,y₂,z₂):

d = √[ (x₂-x₁)² + (y₂-y₁)² + (z₂-z₁)² ]
  • 거리 모드: 위 식의 d
  • 포지션 모드의 ΔX/ΔY/ΔZ: 제곱근 씌우기 전의 축별 성분 (x₂-x₁)

정확도는 (1) 센서 좌표 자체의 정밀도, (2) 피킹이 의도한 점을 잡았는지에 달립니다. 좌표 단위가 미터라는 가정만 맞으면 결과도 미터입니다.


8. GPS를 입혀 위경도를 표시하는 과정 (미구현)

8-1. 왜 바로는 안 되는가

FAST-LIVO2 출력 포인트는 **로컬 좌표계(미터)**입니다.

  • 원점 = SLAM을 시작한 위치
  • 축 = 시작 시점의 자세 기준 (IMU 중력 정렬 → Z축은 중력 반대 방향, 스케일 ≈ 1m)
  • (x, y, z)는 "출발점 대비 상대 위치"일 뿐, 지구상 위치 정보는 없음

위경도로 바꾸려면 로컬 좌표계를 지구 좌표계에 정렬(지오레퍼런싱)해야 합니다.

8-2. 구해야 하는 변환

실질 미지수는 두 가지:

  1. 기준 원점의 위경도 (lat₀, lon₀, alt₀)
  2. 헤딩(yaw 회전) — 로컬 X/Y축이 동/북 대비 돌아간 각도

(Z는 이미 중력 정렬되어 있어 roll/pitch는 거의 0)

변환 흐름:

로컬 (x,y,z)
   │  회전 + 평행이동
   ▼
ENU (동, 북, 상 / 미터)
   │  측지 변환 (ENU → ECEF → geodetic)
   ▼
(위도, 경도, 고도)

ENU↔위경도 변환은 표준 공식이며, 작은 영역이면 근사식으로도 충분합니다.

8-3. 변환을 구하는 방법 (정확도 순)

방법 필요 데이터 정확도
A. 시간동기 GPS↔궤적 정합 FAST-LIVO2 타임스탬프 궤적(pose) + GPS 로그(lat/lon/alt) GPS 품질 의존. RTK면 cm급, 일반 GPS면 수 m
B. 기준점 수동 지정 맵 내 한 점의 실제 위경도 + 진행 방향(헤딩) 헤딩 정확도에 민감, 수 m~수십 m
C. SLAM 단계 GPS 융합 GPS 팩터를 그래프에 넣어 재최적화(LIO-SAM/GLIM 등) 가장 일관적·정확
  • 방법 A가 일반적: 궤적의 각 시점 위치와 같은 시각 GPS의 ENU 위치를 짝지어, 둘을 가장 잘 겹치는 **강체변환(또는 7-DOF 유사변환)**을 최소제곱(Umeyama/Kabsch)으로 계산. 충분한 이동과 **회전(코너)**이 있어야 헤딩이 잘 잡힘.
  • FAST-LIVO2는 GPS를 직접 융합하지 않으므로, GPS는 사후 정합 또는 외부 그래프 최적화로 붙여야 함.

8-4. 뷰어 쪽 구현 방향

변환만 정해지면 뷰어 추가는 간단합니다.

  • 클릭 시 이미 로컬 (x,y,z)를 읽고 있으므로, 거기에 변환을 적용해 위/경도/고도를 리드아웃에 함께 표시.
  • 점마다 위경도를 굽지 않고, { 원점 lat/lon/alt, 헤딩, 스케일 } 설정 한 벌만 저장해두고 클릭 시 실시간 변환 → 가볍고 정확.
  • 실시간에도 그대로 적용됩니다. 대시보드에 GEO 슬롯(예: sensor_msgs/msg/NavSatFix)을 하나 더 두면 궤적↔GPS 정합을 세션 중에 계산할 수도 있습니다.

8-5. 진행을 위해 필요한 것

  • 궤적 파일 + GPS 로그가 있으면 → 방법 A로 변환 계산 + 뷰어 위경도 표시 (포맷: TUM/KITTI pose, NMEA, CSV 등)
  • 기준점 위경도 + 헤딩만 알면 → 방법 B로 설정값 입력 UI 추가
  • 우선 위경도 표시 틀만 먼저 넣고 설정값은 나중에 채워도 됨

9. 문제가 생기면

증상 확인할 것
화면이 검고 "뷰어를 불러오지 못했습니다" 인터넷 연결(Three.js CDN). HTTP 서버로 열었는지
.pcd가 안 열림 file://로 연 건 아닌지. 8000 포트 서버로 접속
연결 실패 해당 주소에 rosbridge가 떠 있는지. 원격이면 방화벽 9090
rosapi 응답이 없습니다 rosbridge에 rosapi 노드가 함께 떠 있는지(launch 파일 확인)
토픽 목록이 비어 있음 FAST-LIVO2가 실제로 발행 중인지 — ros2 topic list -t
CAMERA가 안 뜸 토픽이 raw Image일 수 있음 → 3-3의 republish 참고
Hz가 그 토픽이 2초 넘게 조용하다는 뜻(정상 표시). 발행 측 확인
버퍼 가득 참 복셀 크기를 키우세요(즉시 솎임). 복셀이 꺼져 있으면 켜기. 그래도 부족하면 LIVE_CAP 상향
맵이 너무 성김 복셀 크기를 줄이세요. 이미 버린 점은 안 돌아오지만 이후 스캔부터 촘촘해집니다

10. 요약

실시간: rosbridge로 토픽을 직접 구독 → 스캔을 복셀 단위로 솎아 한 버퍼에 누적 → RViz 없이 브라우저에서 봄. 누적량은 돌린 시간이 아니라 본 공간 크기에 비례. 텔레메트리는 실측만 표시하고, 조용하면 조용하다고 말함.

계측: PCD = 실측 3D 좌표(미터)의 집합 → 클릭으로 두 점 좌표를 집어 → 피타고라스(3D)로 거리 계산. 파일이든 라이브 맵이든 동일.

위경도: 별도 GPS 데이터로 로컬↔지구 좌표 변환(지오레퍼런싱)을 한 번 구해두면 표시 가능. 아직 미구현.