A robot perceives the world through a measurement chain: a physical quantity hits a sensor, the sensor produces a noisy signal, firmware digitizes it, and software fuses it into a state estimate. Every control decision downstream is only as good as that chain. This guide covers the sensor families, their error models, the fusion math that combines them, and a working ROS2 pipeline.
Quick Takeaways
- Proprioceptive sensors (encoders, IMU) measure the robot’s own state. Exteroceptive sensors (LiDAR, cameras, sonar) measure the environment.
- No single sensor is sufficient. Encoders drift, IMUs integrate noise, and LiDAR fails on glass. Sensor fusion closes the gaps.
- A Kalman filter weights each measurement by its inverse covariance. Trust the sensor with the lower variance.
- In ROS2, each sensor is a
nodepublishing a typed message on atopic. Fusion nodes subscribe and publish a fused/odomor/tf.
| Sensor | Measures | Typical Rate | Typical Accuracy | Cost |
|---|---|---|---|---|
| Wheel encoder | Wheel rotation | 100–1000 Hz | 0.1–1° per tick | $ |
| IMU (6/9-DOF) | Accel, gyro, mag | 100–1000 Hz | ±0.01°/s bias drift | $–$$ |
| 2D LiDAR | Range scan | 5–40 Hz | ±20–30 mm | $$ |
| Depth camera | Dense depth | 30–90 Hz | ±1–2% of range | $$ |
| GNSS-RTK | Global position | 1–20 Hz | ±1–2 cm | $$$ |
Sensor Taxonomy: Proprioceptive vs. Exteroceptive
Split your sensors by what they observe.
Proprioceptive sensors observe the robot itself: joint angles, wheel speed, body acceleration, angular velocity. They are fast, local, and drift over time.
Exteroceptive sensors observe the world: distance to walls, obstacle shape, visual features. They are slower, richer, and sensitive to the environment (lighting, reflectivity, rain).
A second split matters for power and integration: active sensors emit energy (LiDAR lasers, ultrasonic pings, structured-light projectors). Passive sensors only receive it (cameras, IMUs, encoders). Active sensors work in the dark but can interfere with each other.
Proprioceptive Sensors in Detail
Wheel and Joint Encoders
An incremental encoder outputs quadrature pulses on channels A and B. A 1000 PPR encoder with 4x decoding yields 4000 counts per revolution.
For a differential drive robot, odometry follows from the wheel displacements:
d_left = 2π · r · (ΔN_left / CPR) # left wheel arc length (m)
d_right = 2π · r · (ΔN_right / CPR) # right wheel arc length (m)
Δs = (d_right + d_left) / 2 # forward distance
Δθ = (d_right - d_left) / L # heading change (rad), L = wheel base
x_k+1 = x_k + Δs · cos(θ_k + Δθ/2)
y_k+1 = y_k + Δs · sin(θ_k + Δθ/2)
θ_k+1 = θ_k + Δθ
Error source: wheel slip and unequal wheel radii. Heading error accumulates without bound. That is why encoders alone fail past a few meters.
Inertial Measurement Units (IMU)
An IMU combines a 3-axis accelerometer and a 3-axis gyroscope. A 9-DOF unit adds a magnetometer.
The gyro measures angular rate ω. Integrate it to get orientation. The measurement model is:
ω_measured = ω_true + b_gyro + n_gyro
a_measured = R^T · (a_true - g) + b_accel + n_accel
b is a slowly varying bias. n is white noise. Integrating b twice in position gives error growing as t². A consumer MEMS IMU drifts meters within seconds if dead-reckoned alone.
A complementary filter gives a cheap tilt estimate:
θ_est = α · (θ_est + ω · dt) + (1 - α) · θ_accel
# α ≈ 0.98 at 100 Hz: trust the gyro short-term, the accelerometer long-term
Exteroceptive Sensors in Detail
LiDAR
A LiDAR measures range by time-of-flight:
r = c · Δt / 2 # c = 3×10^8 m/s, Δt = round-trip time
A 2D scanner such as the RPLIDAR A1 returns about 8,000 samples per second at 5.5 Hz, covering 360° at roughly 1° resolution. Convert polar to Cartesian for mapping:
x_i = r_i · cos(θ_min + i · Δθ)
y_i = r_i · sin(θ_min + i · Δθ)
Failure modes: transparent glass, mirrors, black matte surfaces at low incidence, and direct sunlight on some units.
Depth Cameras
A stereo camera computes depth from disparity d between two images:
Z = f · B / d # f = focal length (px), B = baseline (m), d = disparity (px)
Depth error grows quadratically with range: ΔZ ≈ Z² · Δd / (f · B). Doubling the distance quadruples the error. Time-of-flight and structured-light cameras give denser depth indoors but wash out in sunlight.
Ultrasonic Sensors
An ultrasonic sensor (HC-SR04 class) emits a 40 kHz burst. Range is r = v_sound · t / 2 with v_sound ≈ 343 m/s at 20°C. The beam cone is wide (about 15–30°), so angular resolution is poor. Use it for cheap bumper-style detection, not mapping.
Force/Torque and Tactile Sensors
A strain-gauge force/torque sensor measures deformation through a Wheatstone bridge. Manipulators use it for compliant control and contact detection. Resolution is typically 0.01–0.1 N.
Sensor Comparison: Choosing the Right Perception Stack
| Criterion | 2D LiDAR | Stereo Camera | ToF Camera | Ultrasonic |
|---|---|---|---|---|
| Range | 0.15–12 m | 0.3–20 m | 0.2–5 m | 0.02–4 m |
| Accuracy | ±20 mm | ±1–5% of range | ±10 mm | ±3 mm (narrow) |
| Sunlight robustness | Medium | High | Low | High |
| Glass handling | Poor | Medium | Poor | Good |
| Compute load | Low | High | Medium | Very low |
| Power | 2–5 W | 1–3 W + GPU | 2–4 W | <0.1 W |
| Cost | $$ | $–$$ | $$ | $ |
Sensor Fusion: The Math
Fusion combines uncertain measurements into one estimate with lower variance than any input.
Weighted Averaging of Two Sensors
Given two independent measurements z1 and z2 with variances σ1² and σ2²:
x_fused = (z1/σ1² + z2/σ2²) / (1/σ1² + 1/σ2²)
σ_fused² = 1 / (1/σ1² + 1/σ2²)
The fused variance is always smaller than either input.
The Kalman Filter Loop
The Kalman filter runs two steps at each cycle.
Predict (using a motion model, for example encoder odometry or IMU integration):
x_k|k-1 = F · x_k-1 + B · u_k
P_k|k-1 = F · P_k-1 · F^T + Q
Update (using a measurement, for example a LiDAR pose):
K_k = P_k|k-1 · H^T · (H · P_k|k-1 · H^T + R)^-1 # Kalman gain
x_k = x_k|k-1 + K_k · (z_k - H · x_k|k-1) # state correction
P_k = (I - K_k · H) · P_k|k-1 # covariance update
Qis process noise covariance. Raise it when your motion model is unreliable.Ris measurement noise covariance. Raise it for sensors you trust less.Kshifts weight toward whichever side has the smaller covariance.
For nonlinear robots, use the Extended Kalman Filter (EKF), which linearizes f and h through Jacobians. The robot_localization package implements this for ROS.
ROS2 Implementation: Wiring a Perception Pipeline
Sensor Topics and Message Types
| Sensor | Topic | Message Type | Key Fields |
|---|---|---|---|
| LiDAR | /scan |
sensor_msgs/LaserScan |
ranges[], angle_min, angle_increment |
| IMU | /imu/data |
sensor_msgs/Imu |
orientation, angular_velocity, linear_acceleration |
| Wheel odometry | /wheel/odom |
nav_msgs/Odometry |
pose.pose, twist.twist |
| Depth camera | /camera/depth/image_raw |
sensor_msgs/Image |
data, encoding |
A Minimal LiDAR Obstacle Node (Python, rclpy)
import math
import rclpy
from rclpy.node import Node
from rclpy.qos import qos_profile_sensor_data
from sensor_msgs.msg import LaserScan
from std_msgs.msg import Bool
class ObstacleDetector(Node):
def __init__(self):
super().__init__('obstacle_detector')
# Sensor QoS: best-effort, small queue. Matches most LiDAR drivers.
self.sub = self.create_subscription(
LaserScan, '/scan', self.on_scan, qos_profile_sensor_data)
self.pub = self.create_publisher(Bool, '/obstacle_ahead', 10)
self.declare_parameter('stop_distance', 0.5) # meters
self.declare_parameter('cone_deg', 30.0) # half-width of front cone
def on_scan(self, msg: LaserScan):
stop = self.get_parameter('stop_distance').value
cone = math.radians(self.get_parameter('cone_deg').value)
blocked = False
for i, r in enumerate(msg.ranges):
# Reject invalid returns: inf, NaN, or outside sensor limits
if not math.isfinite(r) or r < msg.range_min or r > msg.range_max:
continue
# Beam angle in the sensor frame
angle = msg.angle_min + i * msg.angle_increment
angle = math.atan2(math.sin(angle), math.cos(angle)) # wrap to [-pi, pi]
if abs(angle) <= cone and r < stop:
blocked = True
break
self.pub.publish(Bool(data=blocked))
def main():
rclpy.init()
rclpy.spin(ObstacleDetector())
rclpy.shutdown()
if __name__ == '__main__':
main()
EKF Fusion Configuration (robot_localization)
# ekf.yaml
ekf_filter_node:
ros__parameters:
frequency: 50.0 # filter loop rate (Hz)
two_d_mode: true # planar robot: ignore z, roll, pitch
odom_frame: odom
base_link_frame: base_link
world_frame: odom
odom0: /wheel/odom
# Fuse order: x, y, z, roll, pitch, yaw, vx, vy, vz, vroll, vpitch, vyaw, ax, ay, az
odom0_config: [false, false, false,
false, false, false,
true, false, false, # use forward velocity only
false, false, true, # and yaw rate
false, false, false]
imu0: /imu/data
imu0_config: [false, false, false,
false, false, true, # absolute yaw from the IMU
false, false, false,
false, false, true, # gyro yaw rate
true, false, false] # forward acceleration
imu0_remove_gravitational_acceleration: true
Launch it:
ros2 run robot_localization ekf_node --ros-args --params-file ekf.yaml
ros2 topic hz /odometry/filtered # verify the 50 Hz output
ros2 topic echo /odometry/filtered --once
Calibration and Time Synchronization
Fusion fails quietly when calibration is off. Check four things.
- Extrinsics. Publish static transforms from
base_linkto each sensor frame. A 2° yaw error in the LiDAR mount produces 35 mm lateral error at 1 m range. - Intrinsics. Calibrate cameras with a checkerboard to get focal length, principal point, and distortion coefficients.
- IMU bias. Average 30 seconds of stationary data. Subtract the mean gyro reading at startup.
- Timestamps. Use hardware timestamps where available. A 20 ms offset on a robot turning at 1 rad/s produces 0.02 rad of heading error.
ros2 run tf2_ros static_transform_publisher \
--x 0.12 --y 0 --z 0.20 --yaw 0 --pitch 0 --roll 0 \
--frame-id base_link --child-frame-id laser_frame
Real-World Workflow: Indoor AMR Perception Stack
A warehouse AMR (autonomous mobile robot) at 1.2 m/s needs reliable localization and obstacle avoidance. A proven layout:
- Wheel encoders (4000 CPR, 200 Hz) feed odometry.
- 6-DOF IMU (200 Hz) corrects yaw drift.
- 2D LiDAR (15 Hz) feeds SLAM and the costmap.
- Front depth camera (30 Hz) catches overhanging obstacles the planar LiDAR misses, such as shelf edges at 0.8 m height.
- Ultrasonic ring (10 Hz) provides a last-resort stop zone at 0.2 m.
Pipeline steps:
- Run the EKF on encoders plus IMU for smooth odometry at 50 Hz.
- Feed
/scanand/odometry/filteredinto SLAM Toolbox to build the map. - Switch to AMCL for localization against the saved map.
- Feed LiDAR and depth point clouds into the Nav2 costmap as separate observation sources.
Expected result: sub-5 cm localization error in structured indoor spaces, with graceful degradation if one sensor drops out.
Common Failure Modes and Fixes
| Symptom | Likely Cause | Fix |
|---|---|---|
| Map smears during turns | Timestamp skew between LiDAR and odometry | Sync clocks, use use_sim_time consistently, check tf latency |
| Robot drifts in a straight line | Unequal wheel radii | Calibrate wheel diameter by driving 5 m and comparing |
| Phantom obstacles | Dust or reflections in LiDAR | Add a range filter and a minimum-hits threshold |
| Yaw jumps near motors | Magnetometer interference | Disable the magnetometer, rely on gyro plus LiDAR |
| EKF diverges | Q/R mistuned or NaN input |
Validate topics, raise R for the noisy source |
FAQ
What are the main types of robot sensors?
Robot sensors fall into two groups. Proprioceptive sensors (encoders, IMUs, force sensors) measure internal state. Exteroceptive sensors (LiDAR, cameras, ultrasonic, radar) measure the environment. Most practical robots combine both through sensor fusion.
How does a robot know where it is?
A robot estimates its pose by fusing motion data (encoders, IMU) with environmental observations (LiDAR scan matching, visual features, GNSS). A Kalman filter or particle filter combines them. SLAM builds a map and localizes within it at the same time.
LiDAR or camera: which is better for robot navigation?
LiDAR gives precise, lighting-independent range data and suits mapping and obstacle avoidance. Cameras give color and semantic detail at lower cost but need more compute and good lighting. Production robots typically use both.
What is sensor fusion in robotics?
Sensor fusion combines data from multiple sensors into one estimate with lower uncertainty than any single input. The standard tool is the Kalman filter, which weights each measurement by its inverse covariance.




