Multisensor fusion (dual-EKF + ANN pseudo-sensor + FLS) for indoor/outdoor handoff, TurtleBot3/Humble

Sharing a course project (Politecnico di Torino, team of five) replicating and extending Yousuf & Kadri’s 2020 ANN+FLS localization paper (Robotica, doi:10.1017/S0263574720000351) in ROS 2 Humble/Gazebo. Full writeup with equations below; looking for critique, especially on the FLS results, they’re weaker than the reference paper and I think I know why, but curious what this community thinks.

Repo: GitHub - yousefh112/ros2_neurofuzzy_ekf_localization: ROS 2 hybrid indoor/outdoor localization using dual Extended Kalman Filters, an online-trained ANN, and a fuzzy logic system to handle GPS-denied segments. · GitHub

A short video of the system running is posted on my LinkedIn. Picture a robot navigating flawlessly outdoors, then losing all sense of where it is the instant it crosses a doorway. That is not a design flaw, it is physics: GPS simply does not work indoors. Most… | Yousef H.

Architecture

Six ROS 2 packages: bt_orchestrator_pkg (BT brain, C++, BehaviorTree.CPP v4, Groot2-monitored), gps_ins_pkg (KF1: GPS+IMU EKF), gps_odometry_pkg (KF2: GPS+odometry EKF), robot_control_brain (online ANN training/inference), robot_description_pkg (TurtleBot3 URDF + Gazebo world), control_pkg (trajectory controller).


TurtleBot3 Burger, the differential-drive platform used for all simulation runs.

Two EKFs (robot_localization, per Moore & Stouch 2014) run in parallel on a 15-element state vector. KF1 fuses GPS+IMU, KF2 fuses GPS+odometry, kept as two independent filters rather than one monolithic filter specifically so GPS bias and odometry drift can be characterized separately.

A feedforward ANN (15 in, 10 hidden, 5 hidden, 2 out, log-sigmoid) is trained online, only while outdoors and GPS-confirmed, on 15 explicitly GPS-free features: 6 IMU, 5 odometry-derived, plus the current KF1/KF2 position estimates. Indoors, those KF position inputs freeze at their last GPS-valid values while the network keeps predicting position from motion alone.

The FLS detects wheel slip from the IMU-vs-odometry velocity discrepancy, runs it through triangular membership functions over three linguistic rules (small/zero/large error), and produces the blend weights via centre-of-gravity defuzzification.

The BT root is a Selector: GPS branch (outdoors) blends KF1+KF2 through the FLS into /odometry/bt_fused; No-GPS branch (indoors) substitutes the ANN for GPS and blends it with KF2 via the same FLS. An IndoorDetector leaf checks a geofence against /odom every tick to pick the branch, and a _pending_bt_reset flag forces unconditional acceptance of the first bt_fused update after each indoor to outdoor transition, clearing any stale GPS anchor from dead-reckoning.


The Behaviour Tree structure (visualized live in Groot2): Root leads into a Main_Orchestrator sequence and a ReactiveFallback Localization_Strategy node, switching every tick between the GPS_Available_Branch and the GPS_Unavailable_Branch.

The trajectory controller doesn’t fully trust the fused pose: dual-source, bt_fused outdoors, raw /odom directly indoors, so GPS bias in the fused estimate can’t propagate into indoor navigation. Control law is always-forward proportional pursuit (omega = clip(K_omega times theta_err, plus/minus omega_max), v = v_max times max(cos(theta_err), 0.15)), removing pure-rotation deadlocks. Door crossings need a geometric crossing-and-alignment gate (Guard v4, checked against actual SDF wall coordinates) before a waypoint captures. A jump-rejection filter (1.0 m threshold, 5 s fallback) guards against GPS re-acquisition spikes.

Integration bug that generalizes: an early version computed indoor position as odometry plus an offset to align frames. It caused a repeatable jam at one interior doorway: the offset silently reintroduced GPS bias into the doorway guard’s crossing check. Fix was removing the offset, not improving it, raw odometry already shared the map’s origin at spawn.

Environment + validation

Two adjacent Gazebo buildings (~7.5x10 m each), six rooms, seven doors. Fixed 131-waypoint route (3 indoor passes, 2 outdoor loops), run six times (T1-T6), all 131/131:

Figure 1, trajectory for run T5 (fastest, 12.88 min):

Ground truth /odom (red) vs fused /odometry/bt_fused (blue) vs ANN prediction /ann/trajectory (green). Blue tracks red closely outdoors with a visible bounded GPS bias; green is noisier, especially near indoor/outdoor transitions.

Results, and where I want pushback

We replicated the reference paper’s own evaluation method: ME/STD of position, ANN-only vs ANN+FLS, across 3 simulated GPS outages, under two conditions (no induced wheel slippage vs. slippage from a physical obstacle placed in-path during an outage):

No slippage: ANN-only and ANN+FLS are nearly identical (matches the reference paper, FLS gain is marginal without slip). With slippage: ANN+FLS consistently lower STD than ANN-only in both axes, all 3 outages (e.g. one outage: STD X 0.1258 to 0.1246, STD Y 0.1214 to 0.1201). X-axis carries a persistent ~0.49 m bias in both conditions, attributable to the GPS ENU offset (bounded dx between -0.42 and +0.40 m across T1-T6).

So the FLS does what it’s supposed to do under slip, but the improvement is smaller than in the original paper. My read: Gazebo’s simulated sensor noise behaves like a constant low-level slippage even in the ‘no slippage’ condition, so that baseline isn’t as clean as intended and the contrast between conditions gets compressed. Separately, anchoring KF1 to odometry velocity indoors gave an unambiguous 2.22x reduction in KF1’s peak position covariance, that result I’m confident in and it’s independent of the ANN/FLS comparison above.

Debugging notes

Most of the real debugging here happened by staring at PlotJuggler, not logs: overlaying /odom, /odometry/gps, and /odometry/bt_fused on one timeline is what made GPS bias visible as soon as it appeared, and comparing EKF covariance and FLS weights across all six runs side by side is what let us confirm a fix actually worked rather than just hoping it did.

Question for the group

Curious whether others have hit the same ‘simulated noise floor masquerades as slip’ issue when trying to reproduce slip-dependent results in Gazebo, and whether there’s a cleaner way to isolate a true zero-slip baseline than turning off an explicit obstacle. Also interested if anyone’s compared this kind of FLS/BT mode-switching against a factor-graph-based smoother for the same GPS-denied handoff, trying to gauge whether the added ANN/FLS complexity earns its keep versus a simpler formulation.

Happy to share the full report or rosbags if useful.

2 Likes

Nice writeup.

On the Gazebo question: your slip signal is |ω_IMU − ω_odom|, and in Gazebo the IMU and odometry each carry independent noises, so that difference has a non-zero floor even when nothing is actually slipping, you’re partly just detecting sensor noise, not slip.

On whether the ANN/FLS earns its keep, your own result is the tell. The ANN is essentially learning to predict position from motion, which dead reckoning already does deterministically. For the GPS-denied handoff the hard part usually isn’t predicting position without GPS (odometry + IMU already does that), it’s two narrower things: rejecting the confidently-wrong fix on re-acquisition, and handing the covariance across the boundary so the estimate coasts instead of snapping…

If you want a concrete simpler baseline to compare against, I maintain one (FusionCore), so take that with the appropriate bias. On your factor-graph question: a smoother would likely beat both by re-linearising across the gap, at the cost of being harder to run truly online, so the real axis is filter-simplicity vs smoother-accuracy, with the ANN/FLS sitting in an awkward middle.