Small Mac OS tip: Try clicking on your WiFi icon in the navbar while holding alt (or option in Mac OS terms). You will see the current RSSI of your connection (only when you are connected to a network!).
So what can we do with RSSI? Well, there is a relation between RSSI and distance (see also Figure 1). As you can probably imagine: the larger the distance between you and the sender of a signal, the lower the signal strength will be. While many researchers question the usability of RSSI measurements in general^{1}, I’ve used them extensively (and with success) for indoor localization purposes. In this article I will show you how to use RSSI measurements and, maybe even more important, to remove noise from the raw data using Kalman filters.
Figure 1: RSSI measurements over time. The received signal strength of a device is clearly influenced by distance but the amount of noise is substantial. For this plot, a bluetooth device was set up as a iBeacon to continuously broadcast its unique identifier. Another bluetooth device was placed at various distances from the beacon and acted as a recording device. With a 1Hz sample rate RSSI values were sampled. For the ‘room’ case, the beacon was placed in an adjacent room to show the effect of walls.
RSSI is measured in dBm but is in its raw form not really useful in an application (apart from being a diagnostic measurement). The nice thing about RSSI is that we can translate the measurements to distance estimates in meters. More precise, we can describe the relation between RSSI and distance using a model, the Log-Distance pathloss model^{2}:
\begin{equation} \text{RSSI} = -10n \log _ {10}(\frac{d}{d _ {0}}) + A_{0} \end{equation}
In this model, $d$ describes the distance between the transceiver and recipient, $n$ the signal propagation exponent and $A _ {0}$ a referenced RSSI value at $d _ {0}$. Usually $d _ {0}$ is taken to be 1 such that $A _ {0}$ becomes the signal strength measured at a distance of 1 meter of the device.
$A _ 0$ can usually be retrieved from the device itself as it is often part of the broadcast message. Alternatively, you can approximate $A _ 0$ by collecting data and averaging the signal strength measurements. The signal propagation exponent $n$ is a constant that differs from environment to environment. For indoor applications, $n$ is often set to $2$.
Figure 2: RSSI converted to distance in meters using the Log-distance pathloss model. Source of the data is the same as in Figure 1. Note that the distance estimations are roughly correct but contains a lot of noise.
Okay, we can translate RSSI to distance, but our estimates are still not very good. As shown in Figure 2, at some point in time (roughly around t=29) our RSSI-based estimate states that the device should be 16 meters away, when in fact it is at a 1 meter distance.
Where does this discrepancy come from? Following our model, in an ideal world, the RSSI value is only dependent on the distance between the two devices. In reality, however, RSSI values are heavily influenced by the environment and have, consequently, high levels of noise. This noise is, for example, caused by multi-path reflections: signals bounce against objects in the environment such as walls and furniture.
So, we need to fight the noise. Here Kalman filters come in to play.
The Kalman filter is a state estimator that makes an estimate of some unobserved variable based on noisy measurements. It is a recursive algorithm as it takes the history of measurements into account. In our case we want to know the true RSSI based on our measurements. The regular^{3} Kalman filter assumes linear models. That is, the step from the current state to the next state, and the translation from state to measurement should be linear transformations.
The general form of the transition model is as follows:
\begin{equation} x _ {t} = A _ {t}x_{t-1} + B _ {t}u _ {t} + \epsilon _ {t} \end{equation}
The current state $x _ t$ is defined as a combination of the previous state $x _ {t-1}$ (given some transformation matrix $A$), a control input $u$ (this could be a movement command in a robot) and noise $\epsilon$. THe noise is called the process noise: noise caused by the system itself. Don’t worry if this looks daunting, we will simplify this formula shortly. Hold on!
For our RSSI filtering application we assume that a device doesn’t move. Moreover, we assume that in the time frame of our measurement our own position is also static. In other words: over time we expect a constant RSSI signal, everything else is noise. To apply this to our model we completely ignore $u$ and set $A$ to an identity matrix. These two changes result in a very simple model:
\begin{equation} x _ {t} = A _ {t}x _ {t-1} + B _ {t}u _ {t} + \epsilon _ {t} \approx x _ {t-1} + \epsilon _ {t} \end{equation}
The second part of implementing the Kalman filter is defining the observation model: how does a particular state $x$ result in a measurement $z$? The general model is as follows:
\begin{equation} z _ {t} = C _ {t}x _ {t} + \delta _ {t} \end{equation}
Here $C$ is the transformation matrix and $\delta$ measurement noise (noise caused by faulty measurements). We model RSSI directly; i.e. our state and measurements are equal. This results in the following reduced measurement model:
\begin{equation} z _ {t} = C _ {t}x _ {t} + \delta _ {t} \approx x _ {t} + \delta _ t \end{equation}
With our two transitions defined we can define the prediction step of the Kalman filter. This step describes what we expect our state to be without using any measurements. As we assume a static system our prediction step is straightforward:
\begin{equation} \bar\mu _ t = \mu _ {t-1} \end{equation}
\begin{equation} \bar\Sigma _ t = \Sigma _ {t-1} + R _ t \end{equation}
Note the difference between $x$ and $\mu$: $\mu$ describes our prediction and $x$ the true value of the state. The bar above the $\mu$ denotes that we yet have to incorporate information from the measurement. $\Sigma$ defines the certainty of our prediction. We base this on the previous certainty (if we were unsure about the previous measurement, the next one will be unsure too) and the process noise $R$ which describes noise caused by the system itself. For the RSSI example we use a low value for the process noise (e.g. 0.008); we assume that most of the noise is caused by the measurements.
Using our prediction estimate we compute the Kalman gain. For our static system we have a simplified version of the normal Kalman gain:
\begin{equation} K_t = \bar\Sigma_t (\bar\Sigma_t + Q_t)^{-1} \end{equation}
Update 2021: The Kalman gain equation was missing a + sign as spotted by Ala and Bojan. This has been corrected in the post. The implementation in KalmanJS was not affected and has not been changed.
The gain is used as a weighting function between the certainty of our estimate and the certainty of the measurement (influenced by the measurement noise $Q$). $Q$ is set to a value that relates to the noise in the actual measurements (e.g. the variance of the RSSI signal).
When we are very uncertain about our prediction of the system (i.e. $\Sigma$ is large) we should trust the measurement more. Likewise, if the measurement noise is low (i.e. $Q$ is low) it is also wise to use those measurements. In these cases the Kalman gain will be high. Likewise, if we are very certain about our prediction of the system and/or the measurement noise is high the Kalman gain will be low (e.g., we don’t trust the measurement). This translates directly to the update step:
\begin{equation} \mu_t = \bar\mu_t + K_t(z_t - \bar\mu_t) \end{equation} \begin{equation} \Sigma_t = \bar\Sigma_t - (K_t \bar\Sigma_t) \end{equation}
In the update step we compute the final prediction of the system ($\mu$, without the bar) and the certainty $\Sigma$. The larger the Kalman gain the more we integrate the measurement into our state estimate. If the Kalman gain is low we trust our prediction more and only use little information of the measurement.
Try to imagine how this characteristic results in a filter: On every step, the Kalman filter decides how much of the measurement it takes in to account based on the certainty of the measurements and our state.
We defined our model, we defined the filter. Now it is time to put the filter to a test. Does our Kalman fitler remove the noise from the raw RSSI signal?
I applied a simple Kalman filter to the “1m” data of the RSSI example dataset. I am using my KalmanJS library which you can find on GitHub or read more about in a separate post on this blog. The results are shown in the figure below:
By using a Kalman filter we are able to remove noise from a very noisy signal. As the update functions are easy to compute the time complexity of the filter is very low; this results in a high performing system. The only drawback is that we lose a bit of responsiveness, but get a clearer signal in return!
Any questions or comments? Please see the comments section.
W. Bulten, A. C. V. Rossum and W. F. G. Haselager, “Human SLAM, Indoor Localisation of Devices and Users,” 2016 IEEE First International Conference on Internet-of-Things Design and Implementation (IoTDI), Berlin, 2016, pp. 211-222. doi: 10.1109/IoTDI.2015.19 Online publicationMany researchers question whether RSSI values contains any value at all. For example:
Dong, Q., & Dargie, W. (2012). Evaluation of the reliability of RSSI for indoor localization. In 2012 International Conference on Wireless Communications in Underground and Confined Areas, ICWCUCA 2012. doi:10.1109/ICWCUCA.2012.6402492 ↩︎
Oguejiofor, O., Okorogu, V., Abe, A., & BO, O. (2013). Outdoor Localization System Using RSSI Measurement of Wireless Sensor Network. International Journal of Innovative Technology and Exploring Engineering (IJITEE), 2(2), 1–6. ↩︎
There is also a extended version of the Kalman filter that works with non-linear models. ↩︎
I’ve used Kalman filters extensively in the past and they are a fast and easy solution for many noise filtering applications. Recently, I was in need of a small javascript library (i.e. no dependencies) that would work fast on 1D data (i.e. the dimensionality of the input data is 1). I’ve created this as part of my indoor localization project and just recently transformed the Kalman filter part into a separate library. In this blog post I quickly show how you can use the library to filter out noise. Even if you create your own implementation, it should be able to give you the gist of how everything works. In the next few days a second blog post will focus more on the how of these filters. For now, we just look at the results and some fancy figures!
Tl;dr: Why should I use Kalman filters?
Minimal working example (based on the latest browser release of KalmanJS):
<script src="kalman.min.js" type="text/javascript"></script>
<script type="text/javascript">
var kf = new KalmanFilter();
console.log(kf.filter(3)); // 3
console.log(kf.filter(2)); // 2.3333333333333335
console.log(kf.filter(1)); // 1.5000000000000002
</script>
We start with a simple exampling in the form of a constant system: our true state (e.g. distance measurements) is constant but measurements of this state are affected by high levels of noise. If you look at the graph below, would you be able to find a good estimate of the true state?
Note: Data is automatically generated, every time you refresh the page you will see a different dataset.
Figure 1: Our first data is a constant system. Unfortunately, as in real life, we only measure the noisy data. Noise is produced by adding Gaussian noise.
The data is generated by two simple map operations:
var dataConstant = Array.apply(null, {length: dataSetSize}).map(function() {
return 4;
});
//Add noise to data
var noisyDataConstant = dataConstant.map(function(v) {
return v + randn(0, 3);
});
randn(mean, sd)
is a simple function that returns random numbers following a Gaussian distribution
So how do we filter this data? The KalmanJS library consists of a single class and each instantiation of that class represents a single system. To use the Kalman filter we apply the filter on each value of our dataset:
//Apply kalman filter
var kalmanFilter = new KalmanFilter({R: 0.01, Q: 3});
var dataConstantKalman = noisyDataConstant.map(function(v) {
return kalmanFilter.filter(v);
});
Note that we apply the filter on each individual data point. The filter does not keep a record of all previous values; it only stores the current estimate and a measure of its certainty.
The only thing we have to worry about in our constant system is the estimate of the noise levels which are represented by R
and Q
.
There are more variables we can set in the filter but these are not important for our current example.
R
models the process noise and describes how noisy our system internally is. Or, in other words, how much noise can we expect from the system itself? As our system is constant we can set this to a (very) low value. Q
resembles the measurement noise; how much noise is caused by our measurements? As we expect that our measurements will contain most of the noise, it makes sense to set this parameter to a high number (especially in comparison to the process noise).
In real life scenario’s you usually make an estimate of R
and Q
based on measurements or domain knowledge. For this example we assume we know the noise levels.
Now let’s apply the Kalman filter on the data set and see how it performs:
Figure 2: Kalman filter applied on the constant data set. The true state of the system is also displayed. The Kalman filter, after some initialization, fits the data nicely.
Constant data is nice, but sometimes (better: often) we have a system that moves. For this second example I generated values from a simple exponential function. Depending on the noise (try reloading the page a few times) the function is only barely noticeable in the data.
Figure 3: Second example showing an exponential increasing function. Only the noisy measurements are shown.
Data is again generated using simple map operations:
var dataExponential = Array.apply(null, {length: dataSetSize}).map(function(e, i) {
return Math.pow(1.1, i);
});
var noisyDataExponential = dataExponential.map(function(v) {
return v + randn(0, 20);
});
Now we apply the Kalman filter the same way as in the previous example.
The Kalman filter can also incorporate movement information in its estimation steps. If you know, beforehand, that a system is non-constant you can use this to improve your results. For now, we just apply a constant filter and see how it performs.
var kalmanFilter = new KalmanFilter({R: 0.01, Q: 20});
var dataExponentialKalman = dataExponential.map(function(v) {
return kalmanFilter.filter(v);
});
Figure 4: Result of the Kalman filter on the exponential data. Depending on the noise the results will range from “not very good” to “awful” (again, try reloading the page to see more examples). The constant filter is not able to capture the growth of the function.
Hmm, our constant Kalman filter is not really able to keep up with the exponential growth. This simple approach often works for systems that have a small growth function. The Kalman filter can easily adapt to changes if they are not to large in relation to the data points. Unfortunately, our exponential growth is to large.
Can we still rescue our exponential data?
Luckily for us, the Kalman filter offers several ways to incorporate more domain knowledge. We can model how a previous state effects a new state (through the parameter A
of the library) but can also incorporate motion commands. This last option is useful if you know that a function only changes given some external or internal input (e.g. a movement command to a robot).
A simple example would be a function that only grows given some external input:
//Here B is the control parameter and is multiplied by the control/action on each filter step
var kalmanFilter = new KalmanFilter({R: 0.01, Q: 20, B: 2});
//Additionally to the measurement we also give an additional control value to the filter
kalmanFilter.filter(v, 1); //Motion is applied
kalmanFilter.filter(v, 0); //Motion is not applied
How you can use these control vectors is a bit out of the scope of this post; just remember: if you have a system where there is movement caused by events there are ways to incorporate it into the filter.
For now we focus on A
which is the transition variable that describes how a state transforms from a previous state to the current. In our exponential growth example we know that each new state is the result of a multiplication of the growth factor (1.1) times the previous state. We can tell this to the Kalman filter and the filter will use this additional information in the filter step:
var kalmanFilter = new KalmanFilter({R: 0.01, Q: 20, A: 1.1});
var dataExponentialKalman = dataExponential.map(function(v) {
return kalmanFilter.filter(v);
});
This adjustment results in a far better fit. Using our prior estimate of the growth we are able to nicely fit the data and filter out a large portion of the noise:
Figure 5: Result of the new Kalman filter (with growth model) on the exponential data. The filter performs far better than the previous filter which did not have any model for the growth of the data.
Want to experiment with the variables and various settings yourself? @benwinding made a nice interactive demo of the Kalman JS library. In the demo you can see how the settings interact and how this effects the filtered output.
By now you should have a rough idea of how you can apply Kalman filters to filter out noise. If you want to see the internal workings of the filter I used in this post you can check out the GitHub repository. For everyone who has 1D data and is in need of a low complexity implementation, I suggest that you give these type of filters a try. They are fast and easy to use 😀.
Higher dimensional data? Rest assured, there are also multi-dimensional implementations available on the web!
Any questions or comments? Please see the comments section.
Kalman filters can be useful in a broad range of projects. Regularly I get questions whether KalmanJS is available in other languages than Javascript. Several contributers have made implementations of my library in other languages, including Python, Java and Objective-C. These versions are stored in the contrib folder on GitHub.
Disclaimer: These implementations are often untested and are only placed in the contrib folder for convenience in the hope that they can be useful for other projects.
W. Bulten, A. C. V. Rossum and W. F. G. Haselager, “Human SLAM, Indoor Localisation of Devices and Users,” 2016 IEEE First International Conference on Internet-of-Things Design and Implementation (IoTDI), Berlin, 2016, pp. 211-222. doi: 10.1109/IoTDI.2015.19 Online publication ]]>