ORB-SLAM2 in macOS
MacBook에서 TUM RGB-D fr1_room 시퀀스를 실행하고 Pangolin Viewer로 map point와 current frame을 확인한 결과
시작한 이유
Codex에 익숙해질 겸 뭘 해보면 좋을지 생각하다가 문득 “맥에서도 SLAM을 제대로 돌릴 수 있을까?”라는 의문이 들었다. ORB-SLAM2는 오래된 코드지만 Visual SLAM을 공부할 때 여전히 기준점처럼 언급되는 프로젝트라, 단순히 빌드만 성공시키는 것이 아니라 MacBook에서 실제 RGB-D 시퀀스를 실행하고, Pangolin Viewer로 결과를 확인하고, evo를 통해 정량 평가까지 진행해보기로 했다.
목표는 명확했다. macOS에서 ORB-SLAM2를 실행 가능한 상태로 만들고, TUM RGB-D dataset의
fr1_room 시퀀스를 끝까지 돌린 뒤, 결과 trajectory가 어느 정도 정확한지 수치로 확인하는 것.
“화면이 뜬다”에서 멈추지 않고, 빌드 스크립트와 문서까지 정리해서 다시 재현 가능한 형태로 남기는 것도 포함했다.
프로젝트 목표
Homebrew 기반 의존성과 CMake preset을 정리해 ORB-SLAM2를 MacBook에서 빌드한다.
Pangolin Viewer가 macOS AppKit 제약을 만족하도록 thread 구조를 조정한다.
TUM RGB-D fr1_room 시퀀스로 map 생성과 trajectory 저장까지 확인한다.
evo로 APE와 RPE를 계산해 macOS 실행 결과를 수치로 점검한다.
실행 환경
| Target | MacBook / macOS |
|---|---|
| SLAM | ORB-SLAM2 RGB-D example |
| Core dependencies | OpenCV4, Eigen3, Pangolin, DBoW2, g2o |
| Dataset | TUM RGB-D rgbd_dataset_freiburg1_room |
| Evaluation | evo APE / RPE with trajectory alignment |
어려웠던 지점
처음에는 일반적인 포팅 작업처럼 CMake와 dependency 문제를 하나씩 맞추면 끝날 것이라고 생각했다. 실제로 OpenCV4 API 변경, Eigen allocator 타입, macOS dynamic library suffix, g2o의 오래된 C++ 코드 등 컴파일 레벨에서 손봐야 할 부분이 많았다. 하지만 가장 결정적인 문제는 빌드가 아니라 실행 구조에서 나왔다.
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException',
reason: 'nextEventMatchingMask should only be called from the Main Thread!'
ORB-SLAM2는 기본적으로 Viewer를 별도 std::thread에서 실행한다. Linux 환경에서는 이 구조가 자연스럽지만,
macOS의 AppKit은 UI event loop가 main thread에서 동작해야 한다. Pangolin의 macOS backend도 이 제약을 그대로 받기 때문에,
Viewer thread 안에서 window event를 처리하려고 하면 위와 같은 예외가 발생했다.
수정 방향
해결 방향은 Viewer를 억지로 background thread에서 살리는 것이 아니라, macOS에서는 Viewer를 main thread에서 실행하도록 ORB-SLAM2 example의 실행 흐름을 바꾸는 것이었다. 따라서 tracking sequence는 worker thread로 옮기고, Pangolin Viewer는 main thread에서 도는 구조로 정리했다.
Main thread에서 sequence 처리, Viewer는 내부 background thread에서 실행
Tracking은 worker thread로 분리, Viewer는 main thread에서 실행
이 과정에서 macOS용 build script와 dependency 설치 스크립트도 함께 추가했다. 결과적으로 Linux 중심이던 원본 프로젝트를 macOS에서 바로 따라 실행할 수 있는 구조로 정리할 수 있었다.
실행 명령어
빌드 후에는 ORB-SLAM2 루트에서 아래 명령어로 TUM RGB-D fr1_room 시퀀스를 실행했다.
./Examples/RGB-D/rgbd_tum Vocabulary/ORBvoc.txt \
Examples/RGB-D/TUM1.yaml \
datasets/rgbd_dataset_freiburg1_room \
Examples/RGB-D/associations/fr1_room.txt
실행이 정상적으로 시작되면 vocabulary를 로드한 뒤 RGB-D frame을 처리하고, Viewer에는 current frame,
keyframe graph, map point가 표시된다. 실행이 끝나면 CameraTrajectory.txt와
KeyFrameTrajectory.txt를 저장해 evo 평가에 사용할 수 있다.
성능 실험
macOS에서 실행이 가능해진 뒤에는 CPU 사용량과 화면 반응성이 신경 쓰였다. 그래서 OpenCV thread 수 제한과 macOS QoS 설정을 실험했는데, 결과적으로는 체감과 수치 모두 기본 설정이 더 나았다.
| 설정 | Median tracking time | Mean tracking time |
|---|---|---|
| 기본 동작 | 0.105262 s | 0.143317 s |
| thread/QoS tuning 적용 | 0.109741 s | 0.245917 s |
특히 mean tracking time이 크게 증가했기 때문에, 강제 thread tuning은 기본값에서 제외하고 필요할 때만 환경 변수로 켤 수 있게 남겼다. 이번 작업에서 꽤 인상적이었던 부분은 “더 직접적으로 제어하면 더 빨라질 것”이라는 직관이 실제 측정에서는 틀릴 수 있다는 점이었다.
evo 정량 평가
evo repository
실행 결과가 눈으로 보기에는 정상이어도, trajectory가 ground truth와 얼마나 가까운지는 별도로 확인해야 한다.
그래서 CameraTrajectory.txt와 TUM RGB-D ground truth를 evo에 넣어 APE와 RPE를 계산했다.
| Metric | RMSE (m) | Mean (m) | Median (m) | Max (m) |
|---|---|---|---|---|
| APE trans | 0.051597 | 0.045099 | 0.037835 | 0.133933 |
| RPE trans, 1 frame | 0.014056 | 0.009195 | 0.006391 | 0.122899 |
| Metric | RMSE (deg) | Mean (deg) | Median (deg) | Max (deg) |
|---|---|---|---|---|
| RPE rot, 1 frame | 0.539543 | 0.430591 | 0.351778 | 3.096500 |
평가 명령어
evo_ape tum \
datasets/rgbd_dataset_freiburg1_room/groundtruth.txt \
CameraTrajectory.txt \
-a --t_max_diff 0.02 --plot_mode xyz
evo_rpe tum \
datasets/rgbd_dataset_freiburg1_room/groundtruth.txt \
CameraTrajectory.txt \
-a --t_max_diff 0.02 -r trans_part -d 1 -u f \
--plot_mode xyz
정리
이번 프로젝트는 오래된 오픈소스를 macOS에 맞게 고치는 작업이 단순한 컴파일 에러 해결만은 아니라는 것을 보여줬다. 실제로 가장 중요한 문제는 dependency보다 GUI thread model이었다. Pangolin Viewer가 어떤 thread에서 실행되는지, ORB-SLAM2의 tracking과 local mapping, loop closing이 어떻게 분리되어 있는지를 이해해야 제대로 해결할 수 있었다.
또 하나의 수확은 측정의 중요성이었다. CPU thread를 직접 제어하면 더 부드러워질 것 같았지만, tracking time은 오히려 나빠졌다. 그래서 기본 설정은 원래 동작에 가깝게 유지하고, 실험적인 thread 옵션은 필요할 때만 켤 수 있게 분리했다.
Codex와 함께 로그를 읽고, 원인을 가정하고, 코드를 수정하고, 다시 빌드해서 확인하는 흐름을 반복하면서 “AI를 옆에 둔 디버깅”이 어떤 식으로 생산성을 높일 수 있는지도 체감할 수 있었다. 결과적으로 MacBook에서 ORB-SLAM2 RGB-D example을 실행하고, trajectory 평가까지 마친 하나의 재현 가능한 프로젝트로 정리할 수 있었다.