SB
articles
Article · Vision 2026-05 · 12 min read

Visual SLAM ↔ Python: a clean bridge via subprocess

How to integrate an open-source C++ Visual SLAM engine into a FastAPI back-end without exotic bindings, then align the result on a floor plan with two GPS constraints. Lessons from an indoor mapping back-office.

Context

From a stream of pixels to a navigable map

The project: turn 360° videos into navigable indoor maps. The pipeline's centrepiece is Visual SLAM — Simultaneous Localization And Mapping — which jointly recovers the camera's 3D trajectory and a 3D point cloud of the environment.

We picked an open-source C++ SLAM engine, built on OpenCV, g2o and Eigen. Our back-end is Python (FastAPI). Question: how do we make them talk cleanly?

The call

Subprocess, not bindings

Three technical options on the table:

pybind11 bindings

Tempting on paper. But the SLAM engine exposes a stateful API (YAML config, observers, callbacks). Maintaining bindings that cover all of it duplicates their public surface in C++, with a heavy maintenance tax every upstream release.

Separate network service (gRPC)

Clean, scalable. But oversized: we run one SLAM job at a time per site, on demand. Adding a service to package, deploy and monitor for that is pure complexity tax.

Subprocess + neutral serialisation

We run the SLAM binary as a subprocess from FastAPI, hand it the video and the YAML config, and read back a map.msg (msgpack) on the Python side. Simple, observable, isolated from the web runtime.

We took the third one. SLAM binary crashes? The subprocess dies, we capture the return code, log it, surface a clean error. No risk of taking FastAPI down with it.

Pipeline

From video to map.msg

  1. 1

    The 360° video is uploaded to Cloud Storage via signed URL — no backend proxy, the client uploads directly.

  2. 2

    On demand, FastAPI extracts key frames (OpenCV) at a configurable cadence. Not all at once: a streamed pipeline keeps disk IO bounded.

  3. 3

    FastAPI invokes the SLAM engine as a subprocess with the config (camera intrinsics, scale factor, FBoW vocabulary) and the frames folder.

  4. 4

    The binary writes a map.msg file containing keyframes (poses + descriptors) and landmarks (3D + observation counts).

  5. 5

    On the Python side, we parse map.msg with msgpack, hydrate SQLAlchemy models, and store everything in PostgreSQL.

The bridge

Three properties of the subprocess

On the FastAPI side, the pattern is short: spawn the SLAM binary with asyncio.create_subprocess_exec, await the returncode, parse the resulting map.msg with the msgpack library. No binding, no FFI, no intermediate server — just a Unix process and an output file. What we get:

  • Runtime isolation: a SLAM job crash never takes the API down.
  • Clean cancellation: killing the subprocess cancels the job without leaving corrupted state on the Python side.
  • Observability: stdout/stderr are captured and logged, so debugging is just reading logs like any other Unix process.
Georeferencing

Aligning the trajectory on a plan

The engine gives us the trajectory in an arbitrary SLAM frame (origin at the first key frame, relative metric scale). To make it useful in the back-office, we have to align it on the floor plan in WGS84 coordinates (lon, lat).

The operator clicks a few key frames and says "this point in SLAM = this point on the plan". With enough constraints, we compute the rigid transform that aligns everything.

1 constraint

Translation only: we offset the SLAM frame so the point matches. Orientation and scale stay as SLAM produced them.

≥2 constraints

Sim(2) via the Umeyama algorithm: we jointly solve for 2D rotation, translation and scale that minimise the squared error over the correspondences. Closed-form, robust, deterministic.

Auto-fit

With only one constraint but the plan bounds defined, we can auto-scale the trajectory to fill the plan — quick sanity check.

Takeaways

What we keep from it

  • To integrate a C++ library with a stateful API, subprocesses are underrated. They draw a clean boundary: a crash there is not a crash here.
  • msgpack is an excellent IPC format: binary, typed, multi-language, without pickle's attack surface.
  • For browser-side 3D perf (thousands of frustums), we used plain Three.js instead of react-three-fiber — the per-frame React cost is non-trivial at that scale.
  • Sim(2) Umeyama georeferencing fits in 30 lines of NumPy. Don't be intimidated by the SLAM literature: most of the useful bricks have closed-form solutions.
  • Always test with a 'broken' video (missing frame, exotic codec, zero size). The subprocess must fail cleanly, not crash the worker.