Visual SLAM ↔ Python : un pont propre via sous-processus
Comment intégrer un moteur Visual SLAM C++ open-source dans un back-end FastAPI sans bindings exotiques, et comment aligner ensuite le résultat sur un plan d'étage avec deux contraintes GPS. Retour d'expérience sur un back-office de cartographie indoor.
Du flot de pixels à un plan navigable
Le projet : transformer des vidéos 360° en plans indoor navigables. La pièce centrale du pipeline, c'est le Visual SLAM — Simultaneous Localization And Mapping — qui reconstruit en parallèle la trajectoire 3D de la caméra et un nuage de points 3D de l'environnement.
On a choisi un moteur SLAM open-source en C++, construit sur OpenCV, g2o et Eigen. Notre back-end est en Python (FastAPI). La question : comment les faire dialoguer proprement ?
Sous-processus, pas binding
Trois options techniques sur la table :
Bindings pybind11
Tentant sur le papier. Mais le moteur SLAM expose une API stateful complexe (config YAML, observers, callbacks). Maintenir un binding qui couvre tout ça revient à dupliquer leur surface publique en C++ avec un coût de maintenance énorme à chaque release upstream.
Service réseau séparé (gRPC)
Propre, scalable. Mais surdimensionné : on a un seul job SLAM à la fois par site, déclenché à la demande. Ajouter un service à packager, déployer et monitorer pour ça serait du complexity tax.
Sous-processus + format de sérialisation neutre
On exécute le binaire SLAM en sous-processus depuis FastAPI, on lui passe la vidéo et la config YAML, on récupère un map.msg (msgpack) qu'on parse côté Python. Simple, observable, isolé du runtime web.
On a pris la troisième. Le binaire SLAM crashe ? Le sous-processus meurt, on capture le code de retour, on logue, on remonte une erreur propre. Aucun risque que ça emporte FastAPI avec lui.
De la vidéo au map.msg
- 1
La vidéo 360° est uploadée vers Cloud Storage via signed URL — pas de proxy backend, le client uploade directement.
- 2
À la demande, FastAPI extrait les frames clés (OpenCV) à une cadence configurable. Pas tout d'un coup : pipeline streamé pour limiter l'IO disque.
- 3
FastAPI invoque le moteur SLAM en sous-processus avec la config (camera intrinsics, scale factor, vocabulary FBoW) et le dossier de frames.
- 4
Le binaire écrit un fichier
map.msgcontenant keyframes (poses + descripteurs) et map points (3D + observations). - 5
Côté Python on parse le
map.msgvia la libmsgpack, on materialise des modèles SQLAlchemy, et on stocke en PostgreSQL.
Trois propriétés du sous-processus
Côté FastAPI, le pattern est court : on lance le binaire SLAM via asyncio.create_subprocess_exec, on attend le returncode, on parse le map.msg produit avec la lib msgpack. Pas de binding, pas de FFI, pas de serveur intermédiaire — juste un process Unix et un fichier de sortie. Ce qu'on gagne :
- Isolation runtime : le crash d'un job SLAM ne tue jamais l'API.
- Cancellation propre : tuer le sous-processus revient à l'annuler, sans state corrupted côté Python.
- Observable : stdout/stderr sont capturés et loggés, on debug en lisant les logs comme n'importe quel process Unix.
Aligner la trajectoire sur un plan
Le moteur nous donne la trajectoire dans un repère SLAM arbitraire (origine à la première frame, échelle métrique relative). Pour que ce soit utile dans le back-office, il faut l'aligner sur le plan d'étage en coordonnées WGS84 (lon, lat).
L'opérateur clique sur quelques keyframes et indique « ici dans le SLAM = ici sur le plan ». Avec assez de contraintes, on calcule la transformation rigide qui aligne tout.
1 contrainte
Translation seule : on déplace le repère SLAM pour que le point coïncide. L'orientation et l'échelle restent celles du SLAM.
≥2 contraintes
Sim(2) via l'algorithme d'Umeyama : on résout simultanément rotation 2D, translation et échelle qui minimisent l'erreur quadratique sur les correspondances. Closed-form, robuste, déterministe.
Auto-fit
Si on n'a qu'une contrainte mais que l'opérateur a posé les bornes du plan, on peut auto-scaler la trajectoire pour qu'elle remplisse le plan — sanity check rapide.
Ce qu’on retient
- Pour intégrer une lib C++ avec une API stateful, les sous-processus sont sous-estimés. Ils tracent une frontière nette : un crash là-bas n'est pas un crash chez vous.
- msgpack est un excellent format d'IPC : binaire, typé, multi-langage, sans la surface d'attaque de pickle.
- Pour la perf 3D côté navigateur (milliers de frustums), on a utilisé Three.js directement plutôt que react-three-fiber — le coût React par frame est non négligeable à ces volumes.
- Le géoréférencement Sim(2) Umeyama est en 30 lignes de NumPy. Ne pas se laisser intimider par la littérature SLAM : la plupart des briques utiles ont des solutions closed-form.
- Toujours tester avec une vidéo « cassée » (frame manquante, codec exotique, taille zéro). Le sous-processus doit échouer proprement, pas planter le worker.