After completing this chapter series, you will be able to:
Explain the three levels of sensor fusion (raw data, feature, and decision) and select the appropriate level for a given application
Implement complementary filters to combine fast/noisy and slow/accurate sensor measurements
Apply Kalman filters for optimal state estimation in linear systems with Gaussian noise
Design particle filter solutions for non-linear, non-Gaussian indoor localization problems
Evaluate sensor fusion architectures (centralized, distributed, hierarchical) based on system requirements
For Beginners: Multi-Sensor Data Fusion
Multi-sensor data fusion combines readings from different sensor types to get a more complete and accurate picture. Think of how a doctor uses multiple tests – temperature, blood pressure, and blood work – rather than diagnosing from a single measurement. In IoT, fusing data from temperature, humidity, and motion sensors reveals patterns that no single sensor could detect.
In 60 Seconds
Multi-sensor data fusion combines data from multiple imperfect sensors to produce estimates more accurate than any single sensor alone. This chapter series covers the full spectrum: from simple weighted averages and complementary filters, through Kalman and particle filters, to system architecture decisions. Use the Quick Start Guide below to jump to the topic most relevant to your project.
18.1 Multi-Sensor Data Fusion
Interactive: Sensor Fusion Calculator
Interactive: Sensor Fusion Visualizer
18.2 Overview
Multi-sensor data fusion combines data from multiple sensors to produce more accurate, reliable, and complete information than any single sensor could provide. In IoT systems, sensor fusion is essential for applications like autonomous vehicles, robotics, navigation, and activity recognition.
Minimum Viable Understanding: Data Quality Through Sensor Fusion
Core Concept: Individual sensors lie in predictable ways - GPS drifts indoors, accelerometers accumulate bias, magnetometers suffer interference. Sensor fusion combines multiple imperfect measurements to produce estimates more accurate than any single sensor alone.
Why It Matters: Single-sensor systems fail catastrophically in real-world conditions. A drone relying solely on GPS loses position indoors; one using fused GPS + IMU + barometer maintains accuracy. Data quality is not about perfect sensors - it is about intelligent combination of imperfect ones.
Key Takeaway: Start with complementary filters (simple, computationally cheap) for combining fast/noisy sensors with slow/accurate ones. Graduate to Kalman filters when you need optimal uncertainty tracking.
18.3 Chapter Series
This comprehensive topic is covered across 8 focused chapters:
Why do drones need MULTIPLE sensors? The Sensor Squad explains sensor fusion!
Sammy the Sensor is trying to figure out which direction a drone is pointing. But he has a problem – each of his sensor friends gives a DIFFERENT answer, and none of them are perfect!
Gerry the Gyroscope says: “I know EXACTLY how fast we are turning! But… I slowly drift off course over time. After 5 minutes, I think we are pointing north when we are actually pointing east!”
Ally the Accelerometer says: “I can feel gravity pulling down, so I always know which way is UP! But… if the drone shakes or accelerates, I get confused and give wobbly answers.”
“So you are both wrong?!” asks Lila the LED.
“Not exactly,” explains Max the Microcontroller. “Gerry is great for FAST changes but bad over LONG times. Ally is great over LONG times but bad during FAST changes. What if we COMBINE them?”
Max creates a recipe called a complementary filter: - For quick movements (right now): Trust Gerry the Gyroscope 98% - For overall direction (over time): Trust Ally the Accelerometer 2%
“It is like asking two friends for directions,” explains Bella the Battery. “One friend is great at short cuts but gets lost on long trips. The other friend always finds the destination but walks really slowly. Together, they are the PERFECT team!”
The result? The drone knows exactly where it is pointing – better than either sensor alone! This is called sensor fusion: combining imperfect information to get a nearly perfect answer.
“And when we add Maggie the Magnetometer,” says Max, “who knows where north is but gets confused near metal, we have THREE helpers that cover each other’s weaknesses!”
18.6.1 Try This at Home!
Close your eyes and spin around slowly 3 times. Can you point to the door? You probably cannot – that is like a gyroscope drifting! Now open your eyes. Instantly, you know where everything is – that is like adding an accelerometer! Your brain does sensor fusion all the time, combining your inner ear (gyroscope), your eyes (accelerometer), and your sense of touch (pressure sensor) to know where you are.
Try It: Complementary Filter for IMU Sensor Fusion
Objective: Implement a complementary filter that fuses accelerometer and gyroscope data to estimate tilt angle, demonstrating how two imperfect sensors create a better result together.
import mathimport random# Simulate sensor data for a tilting IoT devicedt =0.01# 100 Hz samplingtrue_angle =0.0accel_angle_readings = []gyro_rate_readings = []true_angles = []for i inrange(500):# True angle: slow tilt from 0 to 30 degrees and back t = i * dt true_angle =30* math.sin(2* math.pi *0.2* t) true_angles.append(true_angle)# Accelerometer: noisy but no drift (good long-term) accel_noise = random.gauss(0, 3.0) # +/- 3 degrees noise accel_angle_readings.append(true_angle + accel_noise)# Gyroscope: clean but drifts over time gyro_bias =0.5# 0.5 deg/s constant bias (causes drift when integrated) gyro_noise = random.gauss(0, 0.3) true_rate = (true_angles[-1] - true_angles[-2]) / dt if i >0else0 gyro_rate_readings.append(true_rate + gyro_bias + gyro_noise)# Complementary filteralpha =0.98# Trust gyro 98% for fast changes, accel 2% for drift correctionfused_angle =0.0fused_angles = []accel_only = []gyro_only_angle =0.0gyro_only = []for i inrange(len(accel_angle_readings)):# Gyroscope integration (accumulates drift) gyro_only_angle += gyro_rate_readings[i] * dt gyro_only.append(gyro_only_angle)# Accelerometer only (noisy) accel_only.append(accel_angle_readings[i])# Complementary filter: best of both fused_angle = alpha * (fused_angle + gyro_rate_readings[i] * dt) +\ (1- alpha) * accel_angle_readings[i] fused_angles.append(fused_angle)# Calculate errors (last 200 samples where drift is significant)def rmse(estimated, true_vals, start=300): errors = [(e - t) **2for e, t inzip(estimated[start:], true_vals[start:])]return math.sqrt(sum(errors) /len(errors))print("Sensor Fusion Results (RMSE in degrees):")print(f" Accelerometer only: {rmse(accel_only, true_angles):.2f} deg (noisy)")print(f" Gyroscope only: {rmse(gyro_only, true_angles):.2f} deg (drifts)")print(f" Complementary fused: {rmse(fused_angles, true_angles):.2f} deg (best)")print(f"\nThe fused result is better than either sensor alone!")print(f"Alpha={alpha}: gyro trusted for fast changes, accel corrects drift")
What to Observe:
Accelerometer alone is noisy but doesn’t drift – good for static/slow measurements
Gyroscope alone is smooth but drifts over time – good for fast dynamic movements
The complementary filter combines both: gyro handles fast changes, accelerometer corrects drift
The fused RMSE is lower than either individual sensor – fusion improves accuracy
Try It: Complementary Filter Simulator
Adjust the alpha parameter and noise levels to see how the complementary filter combines a drifting gyroscope with a noisy accelerometer. Watch how the fused output (green) tracks the true signal better than either sensor alone.
Try It: Weighted Average Fusion for Multi-Sensor Temperature
Objective: Combine readings from multiple temperature sensors with different accuracies to get a more accurate estimate.
import randomimport math# Three temperature sensors with different characteristicssensors = {"DHT22": {"true_offset": 0.2, "noise_std": 0.5, "accuracy": "medium"},"BME280": {"true_offset": -0.1, "noise_std": 0.2, "accuracy": "high"},"DS18B20": {"true_offset": 0.0, "noise_std": 0.3, "accuracy": "high"},}true_temp =22.5# Actual room temperature# Collect 50 readings from each sensorreadings = {name: [] for name in sensors}for _ inrange(50):for name, config in sensors.items(): reading = true_temp + config["true_offset"] + random.gauss(0, config["noise_std"]) readings[name].append(round(reading, 2))# Method 1: Simple average (treats all sensors equally)simple_avg =sum(sum(r) for r in readings.values()) / (50*3)# Method 2: Variance-weighted fusion (trust accurate sensors more)weights = {}for name, data in readings.items(): variance =sum((x -sum(data) /len(data)) **2for x in data) /len(data) weights[name] =1.0/max(variance, 0.001) # Inverse variance weightingtotal_weight =sum(weights.values())for name in weights: weights[name] /= total_weight # Normalizeweighted_avg =sum( weights[name] * (sum(readings[name]) /len(readings[name]))for name in sensors)print(f"True temperature: {true_temp} C\n")print("Individual sensor averages:")for name, data in readings.items(): avg =sum(data) /len(data) std = math.sqrt(sum((x - avg) **2for x in data) /len(data))print(f" {name:10s}: {avg:.2f} C (std: {std:.2f}, weight: {weights[name]:.2f})")print(f"\nSimple average: {simple_avg:.3f} C (error: {abs(simple_avg - true_temp):.3f})")print(f"Weighted fusion: {weighted_avg:.3f} C (error: {abs(weighted_avg - true_temp):.3f})")print(f"\nWeighted fusion gives more influence to lower-variance sensors.")
What to Observe:
Each sensor has different noise levels and slight calibration offsets
Simple averaging treats all sensors equally, even noisy ones
Variance-weighted fusion automatically assigns higher weights to more precise sensors
The fused result is typically closer to true temperature than any single sensor
Try It: Weighted Average Sensor Fusion Tuner
Set the readings and noise levels for three temperature sensors. Observe how inverse-variance weighting automatically assigns higher trust to more precise sensors, producing a fused estimate closer to the true value.
Worked Example: Drone Orientation Estimation Using IMU Fusion
Scenario: A quadcopter drone uses a 9-DOF IMU (accelerometer, gyroscope, magnetometer) to estimate its orientation (roll, pitch, yaw) for stable flight. Each sensor has different characteristics:
Gyroscope: Measures rotation rate (°/s). Fast (200 Hz), accurate for short-term changes, but drifts over time (±2°/minute accumulation).
Accelerometer: Measures gravity direction. Stable long-term reference for roll/pitch, but noisy during movement (±5° jitter) and cannot measure yaw.
Magnetometer: Measures Earth’s magnetic field for yaw reference. Stable long-term, but suffers interference from motors (±15° error during flight).
Goal: Fuse all three sensors to achieve <2° orientation error with 50 Hz update rate on a Cortex-M4 microcontroller.
Key Insight: Complementary filters achieve 95% of Kalman filter accuracy with 2% of the computational cost. For drones, wearables, and robotics on microcontrollers, this is the sweet spot.
When Complementary Filter Fails: If sensors have complex non-linear relationships or non-Gaussian noise (e.g., magnetometer interference spikes), upgrade to Extended Kalman Filter or Madgwick filter.
Try It: Alpha Tuning Explorer
The alpha parameter controls the balance between gyroscope (fast but drifting) and accelerometer (noisy but stable). Explore how different alpha values affect short-term noise, long-term drift, and overall RMSE.
Question 3: Fast + Slow? NO (multiple sensor types, varying rates)
Question 5: Linear? APPROXIMATELY (small angles, low speeds)
Question 6: Need uncertainty? YES (for path planning) → Decision: Kalman filter → State: [x, y, velocity, heading] → Result: Position accuracy ±0.5m (vs ±3m GPS alone)
Scenario 4: Mall Indoor Navigation App
Sensors: Wi-Fi RSSI (multipath, non-Gaussian), Bluetooth beacons, magnetometer, step counter
Question 7: Non-Gaussian? YES (multimodal due to reflections)
Multimodal distributions? YES (user could be in one of several locations) → Decision: Particle filter (500 particles) → Incorporates: Floor plan constraints (walls), path history → Result: 3-5m positioning accuracy in 90% of mall areas
Common Pitfall: Using Kalman filter for non-linear systems because “Kalman is optimal”. Solution: Kalman is optimal only for linear systems with Gaussian noise. For gyro+accel orientation (non-linear rotation), use Complementary or Extended Kalman.
Fusion Performance Checklist:
Rule of Thumb: Start simple (weighted average or complementary filter), measure performance, then graduate to Kalman only if simple methods fail to meet requirements. Overengineering with particle filters on MCUs often results in missed deadlines and wasted effort.
Try It: Fusion Algorithm Selector
Answer the questions below to find the best sensor fusion algorithm for your project. The tool walks you through the decision framework from the table above.
Show code
viewof fa_redundant = Inputs.select(["Select...","Yes - same physical quantity","No - different sensor types"], {label:"Do sensors measure the same quantity?"})
Show code
viewof fa_known_accuracy = Inputs.select( fa_redundant ==="Yes - same physical quantity"? ["Select...","Yes - known accuracy specs","No - unknown accuracy"]: ["N/A (different sensor types)"], {label:"Do sensors have known accuracy specs?"})
Show code
viewof fa_fast_slow = Inputs.select( fa_redundant ==="No - different sensor types"? ["Select...","Yes - one fast/noisy + one slow/accurate","No - other characteristics"]: ["N/A (redundant sensors)"], {label:"One fast/noisy + one slow/accurate sensor?"})
Show code
viewof fa_compute = Inputs.select( fa_fast_slow ==="Yes - one fast/noisy + one slow/accurate"? ["Select...","Yes - MCU < 100MHz, need < 1ms","No - more compute available"]: ["N/A"], {label:"Tight compute budget (MCU)?"})
Show code
viewof fa_linear = Inputs.select( (fa_fast_slow ==="No - other characteristics"|| fa_compute ==="No - more compute available")? ["Select...","Yes - approximately linear, Gaussian noise","No - nonlinear or non-Gaussian"]: ["N/A"], {label:"Is the system approximately linear?"})
Show code
viewof fa_multimodal = Inputs.select( fa_linear ==="No - nonlinear or non-Gaussian"? ["Select...","Yes - multimodal distributions","No - unimodal but nonlinear"]: ["N/A"], {label:"Multimodal probability distributions?"})
Show code
fa_recommendation = {let algo ="Answer the questions above to get a recommendation.";let detail ="";let color ="#7F8C8D";let ops ="";let example ="";if (fa_redundant ==="Yes - same physical quantity") {if (fa_known_accuracy ==="Yes - known accuracy specs") { algo ="Inverse Variance Weighted Average"; detail ="Weight each sensor inversely by its variance. More precise sensors get more influence."; color ="#16A085"; ops ="~1 multiply per sensor"; example ="5 temperature sensors with different accuracy specs"; } elseif (fa_known_accuracy ==="No - unknown accuracy") { algo ="Equal-Weight Average"; detail ="Average all sensor readings equally. Simple but effective when accuracy is unknown."; color ="#16A085"; ops ="~1 add per sensor"; example ="Multiple identical pressure sensors"; } } elseif (fa_redundant ==="No - different sensor types") {if (fa_fast_slow ==="Yes - one fast/noisy + one slow/accurate") {if (fa_compute ==="Yes - MCU < 100MHz, need < 1ms") { algo ="Complementary Filter"; detail ="High-pass gyro + low-pass accel. Alpha=0.95-0.98 typical. Only ~10 operations per update."; color ="#3498DB"; ops ="~10 operations"; example ="Wearable IMU on Cortex-M4 at 100Hz"; } elseif (fa_compute ==="No - more compute available") {if (fa_linear ==="Yes - approximately linear, Gaussian noise") { algo ="Kalman Filter"; detail ="Optimal state estimation with uncertainty tracking. Predict-update cycle with Kalman gain."; color ="#9B59B6"; ops ="~50 operations (matrix math)"; example ="GPS + accelerometer fusion on Raspberry Pi"; } elseif (fa_linear ==="No - nonlinear or non-Gaussian") {if (fa_multimodal ==="Yes - multimodal distributions") { algo ="Particle Filter"; detail ="Represents belief as weighted particles. Handles any distribution shape. 200-1000 particles typical."; color ="#E74C3C"; ops ="~1000+ operations"; example ="Indoor mall navigation with Wi-Fi + BLE + pedometer"; } elseif (fa_multimodal ==="No - unimodal but nonlinear") { algo ="Extended Kalman Filter (EKF)"; detail ="Linearizes nonlinear models via Jacobian. Good for weakly nonlinear systems."; color ="#E67E22"; ops ="~200 operations"; example ="GPS+IMU fusion for autonomous vehicle"; } } } } elseif (fa_fast_slow ==="No - other characteristics") {if (fa_linear ==="Yes - approximately linear, Gaussian noise") { algo ="Kalman Filter"; detail ="Optimal for linear/Gaussian systems. Tracks state uncertainty for downstream use."; color ="#9B59B6"; ops ="~50 operations"; example ="Forklift position from GPS + wheel encoders + IMU"; } elseif (fa_linear ==="No - nonlinear or non-Gaussian") {if (fa_multimodal ==="Yes - multimodal distributions") { algo ="Particle Filter"; detail ="Can incorporate map constraints (walls), handle multipath interference, and represent multiple hypotheses."; color ="#E74C3C"; ops ="~1000+ operations"; example ="Indoor positioning with RSSI multipath"; } elseif (fa_multimodal ==="No - unimodal but nonlinear") { algo ="Extended Kalman Filter (EKF)"; detail ="Handles moderate nonlinearity with Jacobian linearization."; color ="#E67E22"; ops ="~200 operations"; example ="Nonlinear sensor with single-mode noise"; } } } }const showResult = algo !=="Answer the questions above to get a recommendation.";return htl.html`<div style="background: var(--bs-light, #f8f9fa); padding: 1rem; border-radius: 8px; border-left: 4px solid ${color}; font-family: inherit; margin-top: 0.5rem;"> <div style="font-size: 1.1em; font-weight: bold; color: ${color}; margin-bottom: 0.4rem;">${showResult ?"Recommended: ":""}${algo} </div>${showResult ?`<div style="margin-bottom: 0.4rem;">${detail}</div> <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 0.5rem; font-size: 0.9em;"> <div><strong>Compute cost:</strong> ${ops}</div> <div><strong>Example:</strong> ${example}</div> </div>`:""} </div>`;}
Common Mistake: Forgetting to Tilt-Compensate Magnetometer Readings
The Mistake: A drone uses raw magnetometer X/Y readings to calculate yaw (heading), without accounting for the drone’s current roll and pitch angles. When the drone tilts, the heading estimate becomes wildly inaccurate (±30-40° errors), causing flight instability and crashes.
Why It Happens:
# WRONG: Ignoring tilt in yaw calculationmag_yaw = np.arctan2(mag_y, mag_x) *180/ np.pi# This assumes the drone is perfectly level (roll=0, pitch=0)# When tilted, you're measuring a projection, not true heading
The Physics Problem:
When a drone is level (roll=0°, pitch=0°): - Magnetometer X points north (horizontal component) - Magnetometer Y points east (horizontal component) - Magnetometer Z points down (vertical component) → Heading = arctan2(Y, X) ✓ Correct
When drone tilts forward 30° (pitch=30°): - Magnetometer X now measures a mix of north + down - Magnetometer Y still measures east (mostly) - Magnetometer Z measures a mix of down + south → Heading = arctan2(Y, X) ❌ Incorrect (includes vertical component)
Visual Example:
Level drone facing north (pitch=0°):
↑ Mag X (north)
← Mag Y (east)
⊙ Mag Z (down)
Heading calculation: arctan2(0, X) = 0° North ✓
Tilted drone facing north (pitch=30° forward):
↗ Mag X (north+down mixed)
← Mag Y (east)
⊙ Mag Z (down+south mixed)
Heading calculation: arctan2(0, X_mixed) = 15° ❌ WRONG!
Even with tilt compensation, magnetometers need calibration for: 1. Hard iron offset (permanent magnetic fields from PCB, motors) 2. Soft iron distortion (nearby ferromagnetic materials distorting Earth’s field)
# Hard iron calibration (measure offset in all orientations)mag_x_offset =-12.5# µT offsetmag_y_offset =+8.3mag_z_offset =-5.1mag_x_calibrated = mag_x - mag_x_offsetmag_y_calibrated = mag_y - mag_y_offsetmag_z_calibrated = mag_z - mag_z_offset# Then apply tilt compensation to calibrated values
Checklist to Avoid This Mistake:
Quick Self-Test: If your heading estimate changes by >5° when you tilt your device while keeping the same heading, you have a tilt compensation problem.
Try It: Tilt Compensation Visualizer
Set the drone’s true heading, pitch, and roll angles. Compare the uncorrected magnetometer yaw (which ignores tilt) to the tilt-compensated yaw. Watch the error grow dramatically as tilt increases.
The key principle is that fusing imperfect sensors creates better estimates than any single sensor—GPS drifts indoors, accelerometers accumulate bias, magnetometers suffer interference, but together they provide robust positioning.