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).
17 KiB
PCD Viewer 가이드
FAST-LIVO2 대시보드입니다. 두 가지로 씁니다.
- 실시간 — FAST-LIVO2가 도는 것을 브라우저에서 그대로 봅니다. 누적되는 맵, 궤적, 현재 포즈, 카메라 영상, 상태 텔레메트리.
- 사후 계측 — 저장된
.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. 연결
- FAST-LIVO2 실행
- 뷰어 좌측 LIVE 칸에 주소 입력 →
연결- 같은 PC에서 볼 때:
ws://localhost:9090 - 다른 PC에서 볼 때:
ws://<호스트IP>:9090
- 같은 PC에서 볼 때:
- 연결되면 토픽 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가 rawsensor_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.
거리 측정
- MODE → 거리 선택
- 점 두 개를 차례로 클릭
- 두 점 사이에 흰 선과 거리(m)가 표시되고, 리드아웃에 소수 4자리까지 나옵니다
- 세 번째 클릭 또는
초기화로 리셋
포지션 (기준점 대비 ΔXYZ)
- MODE → 포지션 선택
- 첫 클릭 = 기준점(앰버) 설정
- 이후 클릭 = 기준점 대비
ΔX / ΔY / ΔZ+ 직선거리 표시 초기화로 리셋
모드는 셋 중 하나입니다. 탐색에서는 클릭해도 점이 찍히지 않습니다(회전만). 측정은 실시간 맵 위에서도 똑같이 동작합니다.
표시
- 점 크기 슬라이더
- 원본 색상 — 끄면 클라우드가 중성 회색으로 바뀝니다. 색 없는 클라우드면 비활성화됩니다.
복셀 누적 (LIVE 칸, 기본 켜짐 / 5cm)
실시간으로 들어오는 스캔은 서로 크게 겹칩니다. 한 복셀에 점 하나만 남겨서 메모리가 세션 시간이 아니라 공간 크기에 비례하게 만듭니다. 자세한 원리는 6-2절.
- 복셀 크기를 바꾸면 이미 쌓인 점도 새 격자로 다시 색인합니다. 키우면 그 자리에서 솎이고, 줄이면 버려진 점은 못 되살리지만 이후 스캔부터 촘촘해집니다.
- 끄면 원본 그대로 쌓습니다. 짧고 고밀도인 계측에만 쓰세요 — 버퍼가 빨리 찹니다.
- TELEMETRY의 복셀 행에
5cm · 유지 18.0%처럼 유지율이 나옵니다. 유지율이 높게 유지되면(예: 60% 이상) 복셀이 너무 작다는 뜻이니 키우세요.
복셀은 누적 방식이지 표시 옵션이 아닙니다. 파일로 연 클라우드에는 적용되지 않습니다.
6. 실시간이 동작하는 방식
6-1. 전송
rosbridge v2 프로토콜(subscribe / unsubscribe / call_service / publish)을 WebSocket으로
직접 말합니다. 메시지는 JSON이고, PointCloud2.data와 CompressedImage.data 같은
uint8[] 필드는 base64로 옵니다.
- base64는 바이너리의 약 1.33배입니다. localhost·LAN이면 문제없습니다. 원격에서 무거우면 CBOR 구독으로 올리는 게 다음 수순입니다.
- 이미지만 10Hz로 throttle합니다. 스캔은 throttle하지 않습니다 — 버리면 맵이 손실되니까.
6-2. 누적 버퍼
600만 점짜리 GPU 버퍼를 미리 한 번 잡아두고, 들어오는 스캔을 뒤에 이어 붙입니다.
- 스캔마다 객체를 만들지 않으므로 드로우 콜은 끝까지 하나입니다.
- 매 프레임 전체를 올리지 않고, 새로 붙은 구간만 GPU로 올립니다.
- 600만 점(약 90MB)이 차면 "가득 참"이라고 말하고 누적을 멈춥니다. 조용히 버리지 않습니다.
- 더 필요하면
pcd_viewer.html의LIVE_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)로 변환
- 카메라에서 그 픽셀을 지나는 광선(ray) 생성
- 수백만 점 중 광선에 수직으로 가장 가까운 점(
distanceToRay최소)을 선택 → 화면상 "커서 아래 점" - 그 점의 저장된 원본 좌표
(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. 구해야 하는 변환
실질 미지수는 두 가지:
- 기준 원점의 위경도
(lat₀, lon₀, alt₀) - 헤딩(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 데이터로 로컬↔지구 좌표 변환(지오레퍼런싱)을 한 번 구해두면 표시 가능. 아직 미구현.