A Hands-On Guide to kalbee: Your First Kalman Filter (and Beyond)

A Hands-On Guide to kalbee: Your First Kalman Filter (and Beyond)

Everything you need to go from pip install to a working multi-object tracker, one runnable snippet at a time. kalbee is a Python library for state estimation — the art of recovering a clean signal (position, velocity, temperature, whatever you're measuring) from noisy sensor data. This guide walks through it from the ground up. Every code block runs as-is; copy them into a file and follow along. Install pip install kalbee Enter fullscreen mode Exit fullscreen mode The only runtime dependencies are NumPy and SciPy. Optional extras add object-detection (pip install "kalbee[yolo]") and plotting (pip install "kalbee[viz]") support. The one idea you need: predict and update Every filter in kalbee works the same way. You alternate between two steps: predict() — advance the state forward in time using a motion model ("where do I think the object is now?"). update(z) — correct that prediction with a new measurement z ("what does the sensor actually say?"). The filter tracks two things: the state x (your best estimate) and the covariance P (how uncertain that estimate is). You read them back via kf.x and kf.P. Your first filter Let's track an object moving at roughly constant velocity, measuring only its (noisy) position. Instead of hand-building matrices, we use kalbee's ready-made models: import numpy as np from kalbee import KalmanFilter, rmse from kalbee.models import constant_velocity, position_measurement_model dt = 1.0 # Motion model: state is [position, velocity] F, Q = constant_velocity(dt=dt, process_var=0.01, n_dims=1) # Measurement model: we observe position only, with noise variance 4.0 H, R = position_measurement_model(order=1, n_dims=1, measurement_var=4.0) # Simulate a noisy trajectory rng = np.random.default_rng(0) pos, vel = 0.0, 1.0 truths, measurements = [], [] for _ in range(50): pos += vel * dt truths.append(pos) measurements.append(pos + rng.standard_normal() * 2.0) # std 2.0 -> var 4.0 # Create the filter: start at zero with high uncertainty (P = 100 * I) kf = KalmanFilter(np.zeros((2, 1)), np.eye(2) * 100, F, Q, H, R) estimates = [] for z in measurements: kf.predict(dt=dt) kf.update(np.array([[z]])) estimates.append(kf.x[0, 0]) # estimated position print("Raw measurement RMSE:", round(rmse(np.array(measurements), np.array(truths)), 3)) print("Filtered RMSE: ", round(rmse(np.array(estimates), np.array(truths)), 3)) Enter fullscreen mode Exit fullscreen mode Run it and you'll see the filtered error is meaningfully lower than the raw measurement error — the filter is smoothing out the noise. That's the whole game. What the parameters mean process_var — how much you trust the motion model. Higher = the filter reacts faster but is jumpier. measurement_var (R) — how noisy your sensor is. Higher = the filter leans more on its predictions. Initial P — your starting uncertainty. When in doubt, start large (like 100 * I); the filter converges quickly. Tuning process_var vs measurement_var is the main knob you'll turn. (There's an automatic way to set them — see the EM section below.) Going 2-D (and beyond) Every model takes an n_dims argument. Want to track an object in a plane? Ask for two dimensions and the state becomes [x, vx, y, vy]: F, Q = constant_velocity(dt=1.0, process_var=0.1, n_dims=2) # 4x4 H, R = position_measurement_model(order=1, n_dims=2, measurement_var=0.5) Enter fullscreen mode Exit fullscreen mode Need acceleration too? Use constant_acceleration (state becomes [pos, vel, acc] per axis). Tracking something that turns? constant_turn gives you a coordinated-turn model. Nonlinear systems: EKF and UKF When your motion or measurement isn't linear, swap in the Extended or Unscented Kalman Filter. Instead of matrices, you pass functions: import numpy as np from kalbee import UnscentedKalmanFilter def f(x, dt): # state transition F = np.array([[1, dt], [0, 1]]) return F @ x def h(x): # measurement function return x[:1] # observe position only ukf = UnscentedKalmanFilter( state=np.zeros((2, 1)), covariance=np.eye(2) * 100, transition_covariance=np.eye(2) * 0.01, measurement_covariance=np.array([[4.0]]), transition_function=f, measurement_function=h, ) ukf.predict(dt=1.0) ukf.update(np.array([[1.2]])) print(ukf.x) Enter fullscreen mode Exit fullscreen mode The UKF needs no derivatives; the EKF (ExtendedKalmanFilter) is similar but also takes Jacobian functions. The same predict/update loop applies to all ten filters kalbee ships — including Particle, Ensemble, Information, Square-Root, and Vectorized filters. Not sure which filter? Compare them kalbee has a built-in experiment runner that races several filters on a synthetic signal: from kalbee import run_experiment report = run_experiment( signal="sine", # or "linear", "step", "maneuver" filters=["kf", "ekf", "ukf", "pf"], noise_std=0.5, seed=42, ) print(report.summary()) Enter fullscreen mode Exit fullscreen mode You get a ranked table of position/velocity RMSE and a consistency metric (NEES) for each filter — a quick way to pick the right tool before committing. Tracking many objects To track multiple objects (people in a video, blips on a radar), wrap a filter in a MultiObjectTracker. You supply a small factory that builds a fresh filter for each new object: import numpy as np from kalbee import KalmanFilter, MultiObjectTracker from kalbee.models import constant_velocity, position_measurement_model F, Q = constant_velocity(dt=1.0, process_var=0.05, n_dims=2) H, R = position_measurement_model(order=1, n_dims=2, measurement_var=0.5) def new_track(z): # Seed state [x, vx, y, vy] at the detection, zero initial velocity. x0 = np.array([[z[0]], [0.0], [z[1]], [0.0]]) return KalmanFilter(x0, np.eye(4) * 10.0, F, Q, H, R) tracker = MultiObjectTracker(new_track, n_init=3, max_age=5, gate=6.0) # Each frame, pass a (D, 2) array of detected positions: for detections in detection_stream: confirmed = tracker.update(detections) for t in confirmed: print(f"id={t.id} pos=({t.state[0,0]:.1f}, {t.state[2,0]:.1f})") Enter fullscreen mode Exit fullscreen mode The tracker handles the hard parts for you: matching detections to existing tracks (Hungarian algorithm with distance gating) and managing each track's lifecycle. The knobs: n_init — how many consecutive detections before a track is "confirmed" (filters out flicker). max_age — how many missed frames before a track is deleted (handles occlusion). gate — the maximum distance for a valid match. Only confirmed tracks come back from update(). Feed it the box centers from a detector like YOLO and you have a complete tracking pipeline. Let the data pick your noise values Struggling to choose Q and R? Learn them from a recording with EM (Expectation-Maximization): from kalbee import em_kalman from kalbee.models import constant_velocity, position_measurement_model F, _ = constant_velocity(dt=1.0, n_dims=1) H, _ = position_measurement_model(order=1, n_dims=1) # measurements: array of shape (T, m) result = em_kalman(measurements, F, H, n_iter=50) print("Learned Q:\n", result.Q) print("Learned R:\n", result.R) print("Converged:", result.converged) Enter fullscreen mode Exit fullscreen mode Fit once on representative data, then plug result.Q and result.R into a live KalmanFilter. It's a principled alternative to hand-tuning. A few tips Shapes matter. States are column vectors (n, 1); measurements are (m, 1). When in doubt, reshape(-1, 1). Start uncertain. A large initial P lets the filter trust early measurements and converge fast. Watch consistency, not just error. If the average NEES is far from your state dimension, your Q/R are mistuned — the metrics module (nees, nis, log_likelihood) tells you. Reproducibility is built in. Sampling filters (particle, ensemble) and the signal generators accept a seed/rng argument, so runs are deterministic when you want them to be. Where to go next The documentation has a dedicated page per filter with the underlying math and worked examples. The examples/ folder has runnable scripts, including a full multi-object tracking demo and YOLO integration. Everything shares the same predict/update interface — so once you've done this tutorial, the rest of the library is just variations on what you already know. Happy estimating.

📰 Original Source

Read full article at Dev →

KhanList aggregates and links to publicly available news content. We do not host full articles from third-party sources. Always verify important information with original sources.