SGP Official store ↗
Blog / Projects / Cleaning CSI data

Cleaning Wi-Fi CSI data: filtering, calibration and baselines

Raw CSI off the board is a mess of empty subcarriers, random spikes, drifting phase and slow channel wander. None of that is the person you are trying to detect. This is the standard pipeline that turns that mess into a stable signal: what to drop, what to filter, what to normalize, and how to learn the empty room first.

Published 20 Oct 2026Read 12 minLevel IntermediateTopic Wi-Fi CSI
raw CSI amplitude over time after cleaning motion event drop nulls → remove spikes → smooth → baseline → the real event stands out
FIG 1 — The goal of the pipeline. Raw amplitude (yellow, with red outlier spikes) becomes a quiet baseline with a clear bump when something actually moves (blue).

If your first CSI capture looked like static, you are not doing anything wrong. Raw CSI is noisy by nature, because it reacts to every tiny change in the radio channel plus a stack of hardware quirks. Turning it into something a detector or a model can use is a fixed sequence of steps, and once you know the order it is routine. Here is that order.

01Why cleaning matters

Raw CSI is a blend of several things, and only one of them is useful. There is the real channel, which carries the movement you want. On top of it sit empty subcarriers that carry nothing, sharp outliers from radio glitches, phase that is scrambled by clock errors, and slow drift as the channel wanders over minutes. Feed that blend straight into a threshold or a neural network and the noise buries the signal.

Cleaning is about stripping away everything that is not information and stabilizing what remains, so that a genuine event, a person entering or a chest rising, stands clearly above a quiet background. The steps below are the ones almost every practical CSI project uses, in roughly this order. You do not always need all of them, but this is the full menu. For the raw capture that produces the input, see how to capture Wi-Fi CSI on an ESP32.

02Drop the null and pilot subcarriers

The first step is the cheapest and the most important. The channel contains subcarriers that carry no payload: the guard bands at the edges and the DC bin at the center are empty, and the pilot subcarriers carry fixed references rather than channel information. Their CSI values are meaningless for sensing, so keep the data subcarriers and discard the rest.

On an ESP32 20 MHz link, that usually means trimming a handful of bins from each end of the reported vector and ignoring the near-zero center. Exactly which indices to drop depends on the format and bandwidth, so inspect a quiet capture first and remove the subcarriers that sit flat near zero. The structure behind this is explained in CSI subcarriers and OFDM explained.

Inspect before you trimPlot the mean amplitude of each subcarrier index over a still capture. The null and guard bins show up as values pinned near zero. Drop those indices and you have removed a large slice of useless noise before doing anything clever.

03A Hampel filter for outliers

Radios produce occasional sharp spikes, single samples that are wildly wrong. A plain moving average smears those spikes into their neighbors instead of removing them, so the standard tool here is a Hampel filter, a median-based outlier remover.

For each sample it looks at a small window around it, takes the median and the median absolute deviation of that window, and if the sample lies more than a few deviations from the median it is replaced by the median. Because it works on medians it is robust: one bad value cannot drag the estimate, and genuine motion, which is smooth and sustained, passes through untouched while isolated glitches vanish.

04Smoothing the result

After outliers are gone, the signal still has high-frequency jitter that no real body motion can explain. Human movement is slow in radio terms: walking, gestures and breathing all live below a few hertz. So a gentle low-pass filter or a short moving average over time removes the jitter above that band while keeping the movements you care about.

Keep the window modest. Too much smoothing and you blur the very events you are hunting; too little and the jitter survives. A window spanning a fraction of a second is a sensible starting point, which you then tune against real captures. For breathing specifically you can band-pass around the 0.1 to 0.5 Hz range to isolate the rhythm.

05Normalize, then learn a baseline

Two calibration steps make the signal comparable over time. First, amplitude normalization puts subcarriers and captures on a common scale, for example by dividing each subcarrier by its own mean or standard deviation, so that a naturally strong subcarrier does not dominate a naturally weak one.

Second, and most important for presence work, learn an empty-room baseline. Record CSI for a short period while the space is unoccupied, average it per subcarrier, and store that as the reference. From then on you compare live CSI against the baseline: subtract it, or divide by it, so that only departures from the empty room, meaning activity, remain. Without a baseline you are guessing what "quiet" looks like; with one, the quiet room is defined and anything above it is a candidate event. The honest limits of this approach are covered in our complete guide to Wi-Fi CSI.

06Phase sanitization, if you use phase

If your project only uses amplitude, you can skip this step. If you use phase, it is mandatory, because raw phase is dominated by carrier frequency offset, sampling frequency offset and a random per-packet offset. Sanitization unwraps the phase and then removes the linear component, the slope and offset the clock errors add, typically by fitting a straight line across the subcarriers and subtracting it. What survives is close to the true channel phase and safe to use for fine motion, distance or breathing. The reasoning is laid out in amplitude vs phase in Wi-Fi CSI.

07The pipeline in code

Here is the amplitude path as a short NumPy script: drop the edge subcarriers, remove outliers with a Hampel filter per subcarrier, learn a baseline from the first unoccupied seconds, and subtract it. It is deliberately small so you can read every line.

clean_csi.py · Python 3 + NumPy
import numpy as np

def hampel(x, k=5, n_sigma=3.0):
    # Replace spikes in a 1D series using a sliding median window.
    y = x.copy()
    scale = 1.4826                       # MAD -> std for Gaussian noise
    for i in range(len(x)):
        lo, hi = max(0, i - k), min(len(x), i + k + 1)
        med = np.median(x[lo:hi])
        mad = scale * np.median(np.abs(x[lo:hi] - med))
        if mad > 0 and abs(x[i] - med) > n_sigma * mad:
            y[i] = med                     # outlier -> local median
    return y

# amp: CSI amplitude, shape [packets, subcarriers]
amp = amp[:, 6:-5]                        # 1) drop null/guard subcarriers
amp = np.apply_along_axis(hampel, 0, amp)  # 2) outliers, per subcarrier over time
amp = amp / amp.std(axis=0, keepdims=True)  # 3) normalize each subcarrier

baseline = amp[:200].mean(axis=0)       # 4) learn the empty room (first packets)
clean = amp - baseline                   # 5) keep only departures from empty

motion = np.abs(clean).mean(axis=1)      # one motion value per packet
print("motion range:", motion.min(), motion.max())
Record the baseline on an empty space you controlThe baseline only makes sense if the room is genuinely unoccupied while you learn it, and if it is your own space. Re-learn it whenever you move the hardware or the furniture, because the empty-room fingerprint changes with the geometry.

Add smoothing with a short moving average over the motion series, and for phase insert a sanitization step before step 3. For public captures to practice on, our roundup of Wi-Fi CSI datasets and tools lists sources you can load straight into a script like this one.

08FAQ

Why does raw Wi-Fi CSI need cleaning?

Raw CSI mixes the real channel with empty subcarriers, random outliers, hardware phase errors and slow drift. If you feed it straight into a detector or a model, the noise swamps the small movements you care about. Cleaning removes the parts that carry no information and stabilizes the rest, so a real change stands out from the background.

What is a Hampel filter and why use it on CSI?

A Hampel filter is a sliding-window outlier remover. For each sample it takes the median of a window around it and the median absolute deviation, and if the sample sits too many deviations from the median it is replaced by that median. It is good for CSI because radio glitches produce sharp spikes that a Hampel filter removes without blurring genuine motion the way a plain average would.

What is a CSI baseline and how do I learn one?

A baseline is the CSI pattern of the empty room, the reference the system compares against. You learn it by recording CSI for a short period while the space is unoccupied and averaging it per subcarrier. Afterwards you subtract or divide by this baseline so that only departures from the empty room, meaning activity, remain in the signal.

Do I have to clean CSI phase as well as amplitude?

Only if you use phase. Amplitude is robust and needs just outlier removal, smoothing and a baseline. Raw phase additionally carries carrier and sampling offsets that must be sanitized, usually by unwrapping and removing a linear fit across subcarriers. If your project uses amplitude alone, you can skip phase sanitization entirely.

SGP Lab — written by the team that designs and builds SGP hardware in Europe. We test every example on our own boards before publishing.

SGP devices are sold only at our official store.

sgpstore.net ↗