LYCÉE → PRÉPA · L20

Module L20 · Partie I · Robotique et systèmes embarqués

Où suis-je ? Localisation, cartographie, SLAM, et l’architecture d’un robot.

Un robot autonome doit répondre à trois questions : où suis-je (localisation), à quoi ressemble le monde (cartographie), et comment faire les deux en même temps sans connaître ni l’un ni l’autre (SLAM — le problème qui a occupé la robotique pendant 30 ans). Ce module construit un filtre particulaire, une carte d’occupation, un SLAM par graphe, puis présente ROS 2, le système qui assemble tout cela sur un vrai robot.

Durée : 4 séances · Prérequis : L05 (graphes, A*), L11 (Bayes), L19 (EKF). Objectifs : localisation de Markov et filtre particulaire (MCL), modèle de télémètre, cartes d’occupation (log-odds), scan matching (ICP, idée), SLAM par graphe (optimisation de poses), fermeture de boucle, ROS 2 (nœuds, topics, services, tf2, Nav2), et où en est la recherche (SLAM visuel, cartes sémantiques).

Ce que vous saurez faire à la fin
  • Localiser un robot simulé dans une carte connue avec 500 particules et un lidar.
  • Construire une carte d’occupation à partir de scans et de poses.
  • Formuler et résoudre un petit SLAM par graphe avec Gauss-Newton.
  • Lire l’architecture ROS 2 d’un robot et écrire un nœud.

Références : Probabilistic Robotics chapitres 4-8 et 9-11, cours « SLAM » de Cyrill Stachniss (YouTube, Bonn), documentation ROS 2 et Nav2, « A Tutorial on Graph-Based SLAM » (Grisetti et al.).

Fiche de cours · Définitions

Localisation, cartographie, ROS : définitions

Définition (pose, repères, odométrie). Pose 2D (x, y, θ) ; 3D (position + rotation). Repères : monde (map), odométrie (odom), robot (base_link), capteurs. L’odométrie intègre les déplacements mesurés (encodeurs, IMU) : précise à court terme, dérive sans borne à long terme.
Définition (localisation, cartographie, SLAM). Localisation : estimer la pose dans une carte connue. Cartographie : construire la carte avec des poses connues. SLAM : les deux simultanément — un problème « œuf et poule » résolu probabilistiquement (EKF-SLAM, FastSLAM, graphe de poses).
Définition (modèles de mouvement et de mesure). Modèle de mouvement p(xt | xt−1, ut) : odométrie bruitée (translation, rotations). Modèle de mesure p(zt | xt, m) : pour un lidar, produit sur les rayons d’une vraisemblance (gaussienne autour de la distance attendue, obtenue par lancer de rayon dans la carte, plus composantes pour obstacles inattendus, ratés, aléatoire).
Définition (localisation de Monte-Carlo, MCL). Filtre particulaire sur la pose : N particules (xi, wi) ; prédiction par le modèle de mouvement échantillonné, pondération par la vraisemblance des mesures, rééchantillonnage proportionnel aux poids. Estimation = moyenne pondérée (ou particule de plus grand poids).
Définition (grille d’occupation). Carte = grille de cellules avec p(occupée). Mise à jour en log-odds ℓ = log(p/(1 − p)) : ℓ ← ℓ + ℓmesure − ℓ₀, additive et sans saturation prématurée.
Définition (graphe de poses, ICP). Sommets = poses successives, arêtes = contraintes relatives (odométrie, fermetures de boucle) avec covariances ; on minimise Σ ‖eij‖²Ωij (moindres carrés non linéaires). ICP (Iterative Closest Point) : aligner deux nuages de points en alternant appariement au plus proche et transformation rigide optimale.
Définition (ROS 2). Middleware : nœuds (processus), topics (publication/abonnement, typés), services (requête/réponse), actions (tâches longues avec retour), paramètres, tf2 (arbre des repères horodatés), bags (enregistrement), launch (démarrage). Communication par DDS avec QoS.

Fiche de cours · Formules

Formules à connaître

Odométrie différentielle : Δs = (Δsd + Δsg)/2, Δθ = (Δsd − Δsg)/L ; x += Δs cos(θ + Δθ/2), y += Δs sin(θ + Δθ/2), θ += Δθ
Composition de poses (⊕) : (x₁, y₁, θ₁) ⊕ (x₂, y₂, θ₂) = (x₁ + x₂cos θ₁ − y₂sin θ₁, y₁ + x₂sin θ₁ + y₂cos θ₁, θ₁ + θ₂) — matrice homogène T₁T₂
Filtre bayésien : bel⁻(x) = ∫ p(x | x′, u) bel(x′) dx′ ; bel(x) = η p(z | x) bel⁻(x)
Log-odds : ℓ = log(p/(1−p)), p = 1/(1 + e−ℓ) ; cellule traversée : ℓ −= ℓlibre ; cellule touchée : ℓ += ℓocc
Vraisemblance lidar (modèle « beam ») : p(z | x, m) = Πkhit N(zk; z*k, σ²) + αshort·… + αmax·1z=zmax + αrand/zmax] en log, une somme ; sous-échantillonner les rayons pour éviter une confiance excessive
Neff = 1/Σi wi² ; rééchantillonner si Neff < N/2 (rééchantillonnage systématique à faible variance)
Graphe de poses : minx Σ(i,j) eij(x)ᵀ Ωij eij(x), eij = (xi⁻¹ ⊕ xj) ⊖ zij ; Gauss-Newton : (JᵀΩJ)Δx = −JᵀΩe
ICP (rigide 2D) : centrer les nuages, H = Σ piqiᵀ, SVD H = UΣVᵀ, R = VUᵀ (det = +1), t = q̄ − R p̄

Fiche de cours · Théorèmes et démonstrations

Démonstrations à savoir refaire (1/2)

Théorème 1 (dérivation du filtre bayésien). Sous l’hypothèse de Markov (xt ne dépend que de xt−1 et ut ; zt ne dépend que de xt), bel(xt) = η p(zt | xt) ∫ p(xt | xt−1, ut) bel(xt−1) dxt−1.
bel(xt) = p(xt | z1:t, u1:t). Bayes sur zt : = η p(zt | xt, z1:t−1, u1:t) p(xt | z1:t−1, u1:t). Markov des mesures : le premier facteur est p(zt | xt). Pour le second (bel⁻), on marginalise sur xt−1 : ∫ p(xt | xt−1, z1:t−1, u1:t) p(xt−1 | z1:t−1, u1:t) dxt−1 ; Markov de l’état réduit le premier terme à p(xt | xt−1, ut), et le second ne dépend pas de ut (la commande future n’informe pas sur l’état passé) : c’est bel(xt−1). Tous les filtres (histogramme, Kalman, particulaire) sont des implémentations de cette équation avec des représentations différentes de bel.
Théorème 2 (mise à jour en log-odds d’une grille d’occupation). Si les mesures sont conditionnellement indépendantes sachant l’état de la cellule, la mise à jour bayésienne de p(occ | z1:t) s’écrit ℓt = ℓt−1 + log [p(occ | zt)/(1 − p(occ | zt))] − ℓ₀.
Bayes : p(occ | z1:t) = p(zt | occ) p(occ | z1:t−1)/p(zt | z1:t−1), et de même pour « libre ». Le quotient occ/libre élimine le dénominateur : oddst = [p(zt | occ)/p(zt | libre)]·oddst−1. Par Bayes encore, p(zt | occ)/p(zt | libre) = [p(occ | zt)/p(libre | zt)]·[p(libre)/p(occ)]. En passant au log : ℓt = ℓt−1 + ℓ(zt) − ℓ₀. Le modèle inverse ℓ(zt) est une constante par cas (rayon traversant : ℓlibre < 0 ; extrémité : ℓocc > 0). Additif, sans multiplication ni normalisation : idéal en temps réel.

Fiche de cours · Théorèmes et démonstrations

Démonstrations à savoir refaire (2/2)

Théorème 3 (transformation rigide optimale — Kabsch/Procrustes). Étant donné des paires appariées (pi, qi), la rotation R et la translation t minimisant Σ‖Rpi + t − qi‖² sont t = q̄ − Rp̄ et R = VUᵀ où UΣVᵀ est la SVD de H = Σ(pi − p̄)(qi − q̄)ᵀ (avec correction de signe si det < 0).
Dérivée en t : Σ(Rpi + t − qi) = 0 ⇒ t = q̄ − Rp̄. En substituant et en centrant (p′i = pi − p̄, q′i = qi − q̄), il reste à minimiser Σ‖Rp′i − q′i‖² = Σ‖p′i‖² + Σ‖q′i‖² − 2Σ q′iᵀRp′i (R conserve les normes). Il faut donc maximiser Σ q′iᵀRp′i = tr(RH) avec H = Σ p′iq′iᵀ. Avec H = UΣVᵀ : tr(RUΣVᵀ) = tr(ΣVᵀRU) = Σ σkMkk où M = VᵀRU est orthogonale, donc |Mkk| ≤ 1 ; le maximum Σσk est atteint pour M = I, soit R = VUᵀ. Si det(VUᵀ) = −1 (réflexion), prendre V·diag(1, …, 1, −1)·Uᵀ. C’est le cœur d’ICP : une étape d’appariement, une SVD 2×2 ou 3×3, répéter.
Théorème 4 (ICP décroît l’erreur et converge). En alternant (a) appariement de chaque pi à son plus proche q, (b) transformation optimale pour ces appariements, l’erreur Σ‖Rpi + t − qπ(i)‖² décroît à chaque itération et converge (vers un minimum local).
L’étape (a), à transformation fixée, choisit pour chaque point le q qui minimise sa distance : l’erreur ne peut que diminuer. L’étape (b), à appariement fixé, calcule la transformation qui minimise l’erreur (Théorème 3) : elle diminue encore. La suite des erreurs est décroissante et minorée par 0 : elle converge. Comme il n’y a qu’un nombre fini d’appariements possibles, l’algorithme se stabilise. Rien ne garantit le minimum global : ICP a besoin d’une initialisation proche (odométrie) — sinon il converge vers un alignement faux, symétrie ou glissement le long d’un couloir.
Théorème 5 (une fermeture de boucle borne la dérive). Dans un graphe de poses, ajouter une contrainte entre x0 et xn avec covariance Σb rend l’incertitude relative de xn par rapport à x0 ≤ Σb, quelle que soit la dérive accumulée par les n arêtes d’odométrie.
L’information (inverse de la covariance) s’additionne pour des mesures indépendantes (Théorème 1 de L19, forme matricielle) : Ωtotal = Ωodom + Ωboucle, où Ωodom (chaîne de n arêtes) est petite (covariance somme de n termes) et Ωboucle = Σb⁻¹. Donc Σtotal = (Ωodom + Σb⁻¹)⁻¹ ≼ Σb. L’optimisation répartit la correction sur toutes les poses de la boucle (pondérées par leurs covariances) — c’est le « claquement » de la carte lors d’une fermeture de boucle.

Fiche de cours · Méthodes

Méthodes et pièges

Méthode — mettre en œuvre MCL. Initialisation uniforme (ou autour d’une pose connue). À chaque pas : déplacer chaque particule avec l’odométrie plus du bruit (sinon les particules s’effondrent) ; calculer la vraisemblance avec un sous-ensemble de rayons ; normaliser ; rééchantillonner si Neff faible ; injecter quelques particules aléatoires (robot kidnappé). Diagnostiquer : la dispersion des particules doit refléter l’ambiguïté réelle (couloir symétrique → deux nuages).
Méthode — architecture ROS 2 d’un robot mobile. Nœuds : pilotes (moteurs, lidar, IMU) → odométrie (tf odom→base_link) → localisation (map→odom) → carte → planificateur global (path) → contrôleur local (cmd_vel) → sécurité (arrêt si obstacle proche, priorité sur cmd_vel). Un topic par donnée, horodatage sur tout, QoS « sensor data » pour les capteurs, « reliable » pour les commandes.
Méthode — déboguer une localisation. Rejouer un bag ; visualiser particules, scan et carte dans RViz ; vérifier tf (arbre complet, horodatages cohérents, pas de saut) ; comparer à une vérité terrain (marqueurs, capture de mouvement) avec l’erreur absolue de trajectoire (ATE) et l’erreur relative (RPE).

Pièges : angles non normalisés ; multiplier 360 vraisemblances de rayons (produit → 0, confiance excessive : sous-échantillonner ou lisser) ; carte et scan dans des repères différents (map vs odom) ; horodatages du lidar et de l’odométrie non synchronisés (à 1 m/s, 50 ms = 5 cm) ; ICP sans initialisation ; fermeture de boucle fausse (deux couloirs identiques) qui détruit la carte.

Fiche de cours · Exercices corrigés

Exercices corrigés

Exercice 1. Robot différentiel, entraxe L = 0,3 m, encodeurs mesurant Δsg = 0,10 m et Δsd = 0,13 m sur un pas, pose (1, 2, 0). Nouvelle pose ? Si chaque Δs est connu à ±1 mm, incertitude sur Δθ, et dérive angulaire après 1 000 pas ?
Correction. Δs = 0,115 m, Δθ = 0,03/0,3 = 0,1 rad. x = 1 + 0,115 cos(0,05) = 1,1149 ; y = 2 + 0,115 sin(0,05) = 2,0057 ; θ = 0,1 rad. Incertitude : σΔθ = √(σd² + σg²)/L = √2·10⁻³/0,3 = 4,7 mrad (propagation, Théorème 3 de L19). Après 1 000 pas indépendants : σθ = 4,7·√1000 mrad = 0,15 rad ≈ 8,5°, et cette erreur d’angle se traduit en erreur de position ≈ distance × 0,15 = 17 m pour 115 m parcourus. C’est pourquoi l’odométrie seule ne suffit jamais : il faut des mesures absolues (lidar contre carte, fermetures de boucle).
Exercice 2. Une cellule de grille a p = 0,5 initialement. Le modèle inverse donne p(occ | rayon s’arrête ici) = 0,7 et p(occ | rayon traverse) = 0,3. Après 3 « touchés » et 1 « traversé », quelle est p ? Combien de « touchés » pour dépasser 0,99 ?
Correction. ℓ₀ = 0 ; ℓocc = log(0,7/0,3) = 0,847 ; ℓlibre = log(0,3/0,7) = −0,847. ℓ = 3·0,847 − 0,847 = 1,695 ⇒ p = 1/(1 + e−1,695) = 0,845. Pour 0,99 : ℓ ≥ log(99) = 4,595 ⇒ 4,595/0,847 = 5,4 → 6 touchés nets. En pratique on borne ℓ (par exemple ±5) pour que la carte reste révisable si l’environnement change (porte qui s’ouvre).
Exercice 3. Deux nuages 2D : P = {(0, 0), (1, 0), (0, 1)} et Q = {(1, 1), (1, 2), (0, 1)} (Q est P tourné puis translaté, points appariés dans l’ordre). Trouver R et t par la méthode SVD.
Correction. Centres : p̄ = (1/3, 1/3), q̄ = (2/3, 4/3). Centrés : p′ = {(−1/3, −1/3), (2/3, −1/3), (−1/3, 2/3)}, q′ = {(1/3, −1/3), (1/3, 2/3), (−2/3, −1/3)}. H = Σ p′q′ᵀ = [[Σ p′xq′x, Σ p′xq′y], [Σ p′yq′x, Σ p′yq′y]] = [[−1/9 + 2/9 + 2/9, 1/9 + 4/9 + 1/9], [−1/9 − 1/9 − 4/9, 1/9 − 2/9 − 2/9]] = [[1/3, 2/3], [−2/3, −1/3]]. H est (1/3)·[[1, 2], [−2, −1]] ; on cherche R orthogonale maximisant tr(RH). Essayons R = rotation de 90° = [[0, −1], [1, 0]] : RH = (1/3)[[2, 1], [1, 2]], trace 4/3 ; c’est le maximum (= σ₁ + σ₂ car HᵀH a pour trace 10/9… vérification : les valeurs singulières de H sont 1 et 1/3, somme 4/3 ✓). Donc R = R(90°), t = q̄ − Rp̄ = (2/3, 4/3) − (−1/3, 1/3) = (1, 1). Vérification : R(0, 0) + t = (1, 1) ✓, R(1, 0) + t = (0 + 1, 1 + 1) = (1, 2) ✓, R(0, 1) + t = (−1 + 1, 0 + 1) = (0, 1) ✓.

01 / Localiser

Le monde simulé : une carte, un robot, un lidar

Le lancer de rayon est le modèle de capteur : « si j’étais en (x, y, θ), que verrais-je ? ». Toute la localisation probabiliste repose sur la comparaison entre ce que le robot verrait à une pose hypothétique et ce qu’il voit vraiment.

01 / Localiser

Filtre particulaire (Monte-Carlo Localization) : 300 hypothèses qui votent

Pourquoi des particules

Le filtre de Kalman suppose une croyance gaussienne : une seule bosse. Un robot kidnappé dans un couloir symétrique a une croyance à plusieurs bosses, et la carte rend le modèle de mesure violemment non linéaire. Le filtre particulaire (module L11 : Bayes séquentiel) représente n’importe quelle distribution par des échantillons : prédiction = bouger les particules, correction = les pondérer, rééchantillonnage = concentrer. Coût : O(N × rayons) par pas — d’où les astuces réelles : rééchantillonner seulement quand les poids dégénèrent (N_eff), rayons sous-échantillonnés, tables de distance précalculées, KLD-sampling pour adapter N. C’est l’algorithme d’AMCL dans ROS, utilisé par des dizaines de milliers de robots.

02 / Cartographier

Carte d’occupation : chaque case accumule des preuves (log-odds)

Log-odds : additionner au lieu de multiplier

Bayes met à jour P(occupé | mesures) par des produits ; en log-odds l = ln(p/(1−p)), chaque mesure ajoute une constante : rapide, stable, et l’inconnu vaut 0. La carte d’occupation (Moravec & Elfes, 1985) est la représentation standard pour la navigation 2D ; en 3D, OctoMap (octrees) ; pour les robots récents, des cartes de distances signées (TSDF) ou des champs neuronaux (NeRF). Sa limite : elle suppose les poses connues. Si les poses viennent de l’odométrie, la carte se déforme avec la dérive — d’où le SLAM.

03 / SLAM

Le problème de la poule et de l’œuf, et le SLAM par graphe

Pourquoi ça marche, et à quelle échelle

Chaque mesure relative est une contrainte élastique entre deux poses ; la fermeture de boucle ajoute un ressort entre la fin et le début, qui « tire » toute la trajectoire vers sa forme cohérente. On minimise une somme de carrés pondérés par l’information Ω = Σ⁻¹ (module L12, Gauss-Newton). La matrice H est creuse (chaque arête ne touche que 6 lignes) : avec Cholesky creux, on optimise des graphes de 100 000 poses en secondes (g2o, GTSAM, Ceres). Ajouter des amers (positions de balises, points 3D) donne le SLAM complet ; le module « frontal » qui détecte les fermetures de boucle (reconnaissance de lieux par images ou scans) est aussi crucial que le « dorsal » qui optimise. Cartographer (Google), ORB-SLAM3 et RTAB-Map sont construits ainsi.

03 / SLAM

Aligner deux scans : ICP, le scan matching de base

ICP (Besl & McKay, 1992) est l’algorithme qui produit les contraintes entre poses successives à partir de scans (ou de nuages 3D). Le sous-problème « meilleure transformation rigide pour des appariements donnés » a une solution exacte par SVD (Procrustes orthogonal) — l’un des joyaux de l’algèbre linéaire appliquée. Limites : bassin de convergence étroit (il faut une bonne initialisation, l’odométrie), sensibilité aux valeurs aberrantes (variantes robustes, point-à-plan, NDT).

04 / ROS 2

ROS 2 : le système d’exploitation (qui n’en est pas un) des robots

# Un robot ROS 2 = des NŒUDS (processus) qui échangent des MESSAGES sur des TOPICS (patron Observateur, module L02)
$ ros2 node list
/lidar_driver  /imu_driver  /amcl  /map_server  /planner_server  /controller_server  /motor_driver  /camera  /detector
$ ros2 topic list
/scan  /imu/data  /odom  /tf  /map  /cmd_vel  /camera/image_raw  /detections  /goal_pose
$ ros2 topic echo /scan --once          # voir un message LaserScan : angle_min, ranges[…]
$ ros2 topic hz /scan                   # 10 Hz
$ ros2 run teleop_twist_keyboard teleop_twist_keyboard    # publier /cmd_vel au clavier
$ ros2 bag record /scan /odom /tf       # enregistrer pour rejouer hors ligne (indispensable pour déboguer)
$ rviz2                                 # visualiser carte, scans, particules, plan
# Un nœud minimal en Python (rclpy) : s'abonne au lidar, publie une commande d'arrêt d'urgence
import rclpy
from rclpy.node import Node
from sensor_msgs.msg import LaserScan
from geometry_msgs.msg import Twist

class ArretUrgence(Node):
    def __init__(self):
        super().__init__("arret_urgence")
        self.pub = self.create_publisher(Twist, "/cmd_vel", 10)
        self.create_subscription(LaserScan, "/scan", self.sur_scan, 10)
        self.declare_parameter("distance_min", 0.3)          # réglable sans recompiler : ros2 param set
    def sur_scan(self, msg):
        devant = [r for r in msg.ranges[len(msg.ranges)//2 - 10 : len(msg.ranges)//2 + 10] if msg.range_min < r < msg.range_max]
        if devant and min(devant) < self.get_parameter("distance_min").value:
            self.pub.publish(Twist())                        # vitesses nulles
            self.get_logger().warn(f"obstacle à {min(devant):.2f} m : STOP")

rclpy.init(); rclpy.spin(ArretUrgence())
Les concepts
ConceptRôle
NœudUn processus avec une responsabilité (pilote, algorithme, interface).
TopicFlux de messages typés, publication/abonnement, plusieurs à plusieurs.
ServiceRequête/réponse synchrone (« recharge la carte »).
ActionTâche longue avec retour de progression et annulation (« va à ce but »).
ParamètreConfiguration modifiable à chaud.
tf2Arbre des repères (map → odom → base_link → lidar) et leurs transformations dans le temps (module L10).
LaunchFichier Python qui démarre et configure des dizaines de nœuds.
DDS / QoSCouche de transport (UDP multicast) avec qualité de service : fiable ou « best effort », historique, échéances.

ROS 2 apporte ce que la super-boucle ne peut pas : composer des logiciels écrits par des équipes différentes (un lidar Velodyne, un planificateur Nav2, votre détecteur), en C++ ou Python, sur plusieurs machines. Le prix : latences de quelques millisecondes et une complexité qu’il faut apprivoiser. Le microcontrôleur, lui, garde le temps réel dur (module L18) et parle à ROS par micro-ROS ou un pont série.

04 / ROS 2

La pile de navigation : de la carte au mouvement

ÉtageNœud Nav2Ce module / le cours
Cartemap_server, slam_toolboxCarte d’occupation, SLAM par graphe
LocalisationamclFiltre particulaire
Planification globaleplanner_server (NavFn, Smac)A* / Dijkstra sur la grille (L05), avec coûts d’inflation autour des obstacles
Planification locale / contrôlecontroller_server (DWB, MPPI, RPP)Fenêtre dynamique : échantillonner (v, ω), simuler 1 s, scorer, choisir (L21)
Comportementsbt_navigator (arbre de comportement)Machine à états de la séance 28, en plus structuré : « si bloqué, reculer, replanifier »
Sécuritécollision_monitor, velocity_smootherArrêt d’urgence par lidar, limitation d’accélération

05 / Frontière

Où en est la recherche

Un projet de TIPE ou de stage de L3 accessible : implémenter un SLAM 2D par graphe sur un robot à lidar bon marché (RPLidar A1) avec ROS 2, et comparer à slam_toolbox. C’est faisable en un semestre avec les outils de ce module.

Cours

Cours 1 — Le filtre bayésien général et ses quatre incarnations

L’équation unique. Croyance bel(xt) = P(xt | z1..t, u1..t). Deux étapes récursives : prédiction bel⁻(xt) = ∫ P(xt | xt−1, ut) bel(xt−1) dxt−1 (le modèle de mouvement étale la croyance) ; correction bel(xt) = η P(zt | xt) bel⁻(xt) (le modèle de mesure la concentre). Tout dépend de comment on représente bel :

Représentation de belFiltrePrédictionCorrectionForces / limites
Gaussienne (μ, Σ), modèles linéairesKalman (L19)μ ← Fμ + Bu ; Σ ← FΣFᵀ + QGain KExact et optimal ; unimodal, linéaire
Gaussienne, modèles non linéairesEKF / UKFLinéarisation / points sigmaIdemRapide ; peut diverger ; unimodal
Histogramme sur une grilleFiltre à grille (localisation de Markov)Convolution par le modèle de mouvementMultiplication case par caseMultimodal, exact sur la grille ; coût exponentiel en dimension
Échantillons pondérésParticulaire (MCL)Déplacer chaque particulePondérer, rééchantillonnerMultimodal, tout modèle ; coût en nombre de particules, dégénérescence

Choisir. État de petite dimension, croyance possiblement multimodale (localisation globale, symétries) → particulaire. État de grande dimension (SLAM avec 1000 amers), croyance unimodale → EKF/UKF ou, mieux, optimisation par graphe. Beaucoup de systèmes combinent : particules pour initialiser, Kalman pour suivre (Rao-Blackwellisation : FastSLAM).

Cours

Cours 2 — Exemple travaillé : le modèle de vraisemblance d’un télémètre, et pourquoi le gaussien naïf échoue

Le cours utilise P(z | x) ∝ exp(−Σ(zi − ẑi)²/2σ²) : gaussienne, rayons indépendants. C’est faux de trois façons, et chaque défaut a une parade classique (Probabilistic Robotics, ch. 6) :

  1. Aberrations : un rayon qui touche un objet non cartographié (personne, chaise) donne une distance plus courte. Parade : mélange de lois — gaussienne + exponentielle décroissante (objet inattendu) + pic à la portée max (rien vu) + uniforme (bruit). Poids ≈ 0,7 / 0,1 / 0,1 / 0,1 : une aberration ne tue plus la particule.
  2. Indépendance : 360 rayons ne sont pas indépendants (un mur = des centaines de rayons corrélés) ; la vraisemblance devient sur-confiante (10−300). Parade : sous-échantillonner (30 rayons), ou élever la vraisemblance à une puissance < 1.
  3. Coût : le lancer de rayon pour chaque particule est lent. Parade : modèle de champ de vraisemblance : précalculer la distance de chaque case au mur le plus proche (transformée de distance, BFS L05) ; la vraisemblance d’un impact = gaussienne de cette distance ; pas de lancer de rayon.

Cours

Cours 3 — Anatomie d’un système ROS 2 de navigation : fichiers, nœuds, repères, débogage

mon_robot/                          # un « workspace » ROS 2 (colcon)
└── src/
    ├── mon_robot_description/      # URDF : géométrie, repères (base_link, laser, imu), inertie ; visualisable dans RViz
    ├── mon_robot_bringup/          # launch/ : robot.launch.py démarre pilotes + tf + nav ; config/ : YAML des paramètres
    ├── mon_robot_driver/           # nœud C++/Python qui parle au microcontrôleur (série, L22) : publie /odom, /imu, souscrit /cmd_vel
    ├── mon_robot_nav/              # config Nav2 (planificateur, contrôleur, coûts), cartes, comportements
    └── mon_robot_perception/       # vos nœuds : détecteur, filtre, ...

$ colcon build --symlink-install && source install/setup.bash
$ ros2 launch mon_robot_bringup robot.launch.py
$ ros2 run tf2_tools view_frames            # PDF de l'arbre : map → odom → base_link → laser
$ ros2 topic hz /scan ; ros2 topic echo /tf ; ros2 param list /amcl
$ ros2 bag record -a ; ros2 bag play ma_session
RepèrePublié parSignification
map → odomAMCL / SLAMCorrection lente, peut sauter (fermeture de boucle)
odom → base_linkOdométrie (pilote, EKF robot_localization)Continu, lisse, dérive
base_link → laser / imu / cameraURDF (statique)Où sont les capteurs sur le châssis : à mesurer au millimètre

Les bugs ROS les plus fréquents : horloges non synchronisées (use_sim_time, horodatages : « message too old ») ; repères manquants ou mal nommés (tf : « could not transform ») ; QoS incompatibles (l’abonné ne reçoit rien : « best effort » vs « reliable ») ; deux nœuds publiant le même tf ; unités (degrés au lieu de radians, mm au lieu de m). Outils : rqt_graph, rqt_tf_tree, ros2 doctor, rviz2.

TP guidé

TP — SLAM et navigation avec ROS 2 en simulation, puis sur robot (sur PC, 4 h)

Exercices

Exercices auto-corrigés — filtres à grille et particules

Exercice 1 — Filtre à grille 2D avec modèle de mouvement incertain

Sur la carte du module (grille, cases libres), écrivez predire2d(bel, du, dv) (déplacement demandé (du, dv) réalisé avec probabilité 0,8, glissement ±1 case perpendiculaire avec 0,1 chacun, murs = on reste sur place) et corriger2d(bel, scan_obs, sigma) avec le lidar du module (4 rayons suffisent) ; le robot part inconnu, fait 8 pas ; vérifiez que l’argmax converge vers la vraie position (à 1 case près).

Correction
def predire2d(bel, du, dv):
    nb = np.zeros_like(bel); perp = (dv, du)
    for (dx, dy), p in (((du, dv), 0.8), ((du + perp[0], dv + perp[1]), 0.1), ((du - perp[0], dv - perp[1]), 0.1)):
        for y, x in zip(*np.where(bel > 0)):
            ny, nx = y + dy, x + dx
            if 0 <= ny < H and 0 <= nx < W and libres[ny, nx]: nb[ny, nx] += p * bel[y, x]
            else: nb[y, x] += p * bel[y, x]
    return nb / nb.sum()
def corriger2d(bel, scan_obs, sigma=0.5):
    v = np.zeros_like(bel)
    for y, x in zip(*np.where(libres)):
        att = lidar((x + 0.5, y + 0.5, 0), n_rayons=4, bruit=0.0); v[y, x] = np.exp(-0.5 * np.sum((scan_obs - att)**2) / sigma**2)
    b = bel * v; return b / b.sum()

Exercice 2 — Rééchantillonnage systématique et N_eff

Écrivez n_eff(poids) et reechantillonner_systematique(particules, poids) (une seule valeur aléatoire, variance minimale : algorithme du « peigne »). Vérifiez : la moyenne pondérée est conservée en espérance ; avec des poids uniformes, chaque particule est copiée exactement une fois ; N_eff vaut N pour des poids uniformes et 1 pour un poids concentré.

Correction
def n_eff(w): return 1.0 / np.sum(np.asarray(w)**2)
def reechantillonner_systematique(P, w):
    N = len(w); u = (rng.random() + np.arange(N)) / N; cum = np.cumsum(w); idx = np.searchsorted(cum, u)
    return P[np.minimum(idx, N - 1)]

Exercices

Exercices auto-corrigés — cartes et graphes de poses

Exercice 3 — Transformations de poses 2D : composer, inverser, relatif

Écrivez composer(a, b) (pose b exprimée dans le repère a → pose dans le repère monde), inverse(a) et relatif(a, b) = composer(inverse(a), b), avec angles ramenés dans ]−π, π]. Vérifiez les identités : composer(a, relatif(a, b)) = b ; composer(a, inverse(a)) = identité ; associativité.

Correction
def composer(a, b):
    c, s = np.cos(a[2]), np.sin(a[2])
    return np.array([a[0] + c * b[0] - s * b[1], a[1] + s * b[0] + c * b[1], (a[2] + b[2] + np.pi) % (2 * np.pi) - np.pi])
def inverse(a):
    c, s = np.cos(a[2]), np.sin(a[2])
    return np.array([-c * a[0] - s * a[1], s * a[0] - c * a[1], (-a[2] + np.pi) % (2 * np.pi) - np.pi])
def relatif(a, b): return composer(inverse(a), b)

Exercice 4 — Détecter une fermeture de boucle par comparaison de scans

Le robot enregistre des scans à 36 rayons à chaque pose. Écrivez signature(scan) invariante par rotation (par exemple l’histogramme des distances, ou le spectre d’amplitude de la FFT du scan) et candidats_boucle(scans, i, seuil) qui renvoie les indices j ≤ i − 10 dont la signature est proche de celle de i. Vérifiez sur des scans simulés depuis la carte du module : deux scans pris au même endroit avec un cap différent doivent être détectés ; deux endroits différents non.

Correction
def signature(scan):
    return np.abs(np.fft.rfft(scan))[1:8]            # amplitudes des basses fréquences : invariantes par rotation du scan (décalage circulaire)
def candidats_boucle(scans, i, seuil):
    s = signature(scans[i]); return [j for j in range(i - 9) if np.linalg.norm(signature(scans[j]) - s) < seuil]

Un décalage circulaire du scan (changement de cap) ne change pas le module de sa transformée de Fourier : c’est la propriété qui rend la signature invariante. Les vrais systèmes utilisent des descripteurs plus riches (Scan Context, sacs de mots visuels), puis vérifient géométriquement par ICP avant d’ajouter la contrainte au graphe — sinon une fausse fermeture détruit la carte (défi ★★★).

06 / Défis

Défi ★ — Le robot kidnappé et le rééchantillonnage adaptatif

Consigne

1) Après convergence du MCL, téléportez le robot ailleurs : combien de pas pour que le filtre le retrouve ? Pourquoi peut-il échouer (toutes les particules sont concentrées au mauvais endroit) ? 2) Ajoutez l’injection de 5 % de particules aléatoires à chaque pas : mesurez le temps de récupération. 3) Implémentez le nombre effectif de particules N_eff = 1/Σw² et ne rééchantillonnez que si N_eff < N/2 : quel effet sur la stabilité ? 4) Placez le robot dans un couloir symétrique de la carte et montrez l’ambiguïté (deux nuages).

Piste

La dégénérescence : après rééchantillonnage, toutes les particules descendent de quelques ancêtres ; si l’ancêtre était faux, plus rien ne peut corriger. L’injection aléatoire (ou proportionnelle à la baisse soudaine de la vraisemblance moyenne, « augmented MCL ») est la parade standard. N_eff évite de perdre la diversité quand les poids sont déjà uniformes.

06 / Défis

Défi ★★ — SLAM complet en 2D : poses + amers

Consigne

Étendez le SLAM par graphe : le robot parcourt une trajectoire et observe 8 balises (position relative bruitée, identité connue) ; les inconnues sont les 20 poses ET les 8 positions de balises. 1) Écrivez les deux types d’arêtes (pose-pose, pose-balise) avec leurs jacobiennes numériques. 2) Optimisez ; comparez la carte des balises à la vérité. 3) Retirez la fermeture de boucle : que devient l’erreur ? 4) Vérifiez que H est creuse (affichez sa structure) et mesurez le temps avec scipy.sparse.linalg.spsolve si disponible, sinon commentez.

Piste

Arête pose-balise : e = R(θ_i)ᵀ(l_j − p_i) − z_ij (position de la balise dans le repère du robot). Les blocs de H pour la balise sont 2×2. Indexation : poses aux indices 3i, balises à 3n + 2j. Ce défi est exactement l’exercice du cours de Stachniss « least squares SLAM » ; le résoudre vous met au niveau d’un M1 en robotique.

06 / Défis

Défi ★★★ — Esprit prépa : consistance, jauge et fermeture de boucle robuste

Consigne

1) Montrez que sans fixer la première pose, la matrice H du SLAM par graphe est singulière (noyau de dimension 3 : translation et rotation globales) — expliquez pourquoi et vérifiez numériquement les valeurs propres. 2) Fausse fermeture de boucle : ajoutez une contrainte erronée (le frontal s’est trompé de lieu). Montrez que Gauss-Newton déforme toute la carte. Implémentez une fonction de coût robuste (Huber, ou « switchable constraints » : un poids s_ij ∈ [0, 1] optimisé avec un prior vers 1) et montrez que la fausse contrainte est désactivée. 3) Prouvez que l’EKF-SLAM (état = pose + toutes les balises, covariance pleine) a un coût O(n²) par mise à jour, et expliquez pourquoi le SLAM par graphe le supplante. 4) Discutez : quelle garantie peut-on donner qu’un SLAM ne « diverge » pas ? Cherchez « SLAM consistency » et résumez en 10 lignes l’état de l’art.

Piste (Huber)

Remplacer e ᵀΩe par ρ(‖e‖_Ω) avec ρ(x) = x² si x < δ, 2δx − δ² sinon : au-delà de δ, le coût croît linéairement et le gradient est borné, donc une contrainte aberrante ne « tire » plus que d’une force constante. En pratique (g2o, GTSAM) : IRLS — repondérer chaque arête par ρ′(x)/x à chaque itération. Le noyau de H : ajouter (δx, δy, 0) à toutes les poses, ou une rotation globale, ne change aucune erreur relative ; ces 3 directions sont les degrés de liberté de jauge.

07 / Vérification

Pourquoi AMCL utilise-t-il un filtre particulaire plutôt qu’un EKF pour localiser un robot dans une carte ?

Deux questions supplémentaires

1. Pourquoi borner les log-odds d’une carte d’occupation ? Pour qu’une case puisse changer d’état (porte ouverte/fermée) en un nombre raisonnable de mesures.

2. Que fait une fermeture de boucle dans un SLAM par graphe ? Elle ajoute une contrainte entre deux poses éloignées dans le temps, ce qui corrige la dérive accumulée sur tout le tour.

Référence

Les mots à retenir

MotDéfinition
Lancer de rayonModèle de capteur : ce qu’on verrait depuis une pose.
MCL / filtre particulaireLocalisation par échantillons : prédire, pondérer, rééchantillonner.
N_eff, injectionParades à la dégénérescence des particules.
Carte d’occupationGrille de probabilités mises à jour en log-odds.
SLAMLocalisation et cartographie simultanées.
SLAM par graphePoses = nœuds, mesures = arêtes ; moindres carrés creux.
Fermeture de boucleReconnaître un lieu déjà visité ; contrainte qui corrige la dérive.
ICPAligner deux nuages de points par appariements itératifs et SVD.
JaugeDegrés de liberté non observables (repère global) ; fixer une pose.
ROS 2Nœuds, topics, services, actions, tf2, launch, DDS.
Nav2Pile de navigation : carte, AMCL, planificateurs, contrôleur, arbre de comportement.

Pour continuer

Le robot sait où il est

Module suivant : planification et contrôle — cinématique, planification de mouvement (RRT), commande optimale (LQR, MPC) : décider comment bouger.

À faire chez soi

← L19SommaireL21 : Planification et contrôle →