Giter Club home page Giter Club logo

fsensor's Introduction

FSensor

Android Sensor Filter and Fusion

Alt text

Introduction

FSensor (FusionSensor) is an Android library that provides alternative, customizable implementations of SensorManager.getOrientation() and getDefaultSensor(SENSOR_TYPE_ROTATION_VECTOR). It removes some/most of the complexity of using Androids orientation sensors (Acceleration, Magnetic and Gyroscope). FSensor expands greatly on the "out-of-the-box" sensor implementations provided by Android allowing you to customize sensor filters and fusions for your specific needs, or just add default filters on what Android already provides.

  • Provides device/sensor agnostic averaging filters in the mean, median and low-pass varieties
  • Provides IMU sensor fusion backed estimations of device orientation in the complementary and Kalman varieties
  • Provides estimations of linear acceleration (linear acceleration = acceleration - gravity) in the averaging filter and sensor fusion varieties

FSensor V2.x

FSensor has some breaking API changes for v2.0.

  • The output is returned in the format of SensorManager.getOrientation() so the sensors can be swapped directly with existing Android implementations of SensorManager.getOrientation() and getDefaultSensor(SENSOR_TYPE_ROTATION_VECTOR).
  • RxJava has been removed as a dependency in favor of FSensor's own simple Observer known as SensorSubject.

There is also a bug fix for the fused sensors where the 'handed-ness' of the Z-axis (azimuth) was reveresed and the order of rotation on the integrations wasn't correct. These issues have been resolved and corner case rotations should work as expected.

Get FSensor

In the project level build.gradle:

allprojects {
    repositories {
        maven { url "https://jitpack.io" }
    }
}

In the module level build.gradle:

dependencies {
    implementation 'com.github.KalebKE:FSensor:v2.x'
}

Usage

Simple examples of using FSensor can be found here:

Full app usage examples of FSensor can be found here:

Averaging Filters

private BaseFilter filter; 

private void init() {
  filter = new ... // LowPassFilter(), MeanFilter(), MedianFilter();
  filter.setTimeConstant(0.18);
}

@Override
public void onSensorChanged(SensorEvent event) {
    // Could be any of the Android sensors
    if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
      // Android reuses events, so you probably want a copy
      System.arraycopy(event.values, 0, acceleration, 0, event.values.length);
      filteredAcceleration = filter.filter(acceleration);
    } 
}

FSensor implements three of the most common smoothing filters, low-pass, mean and median filters. The averaging filters can be found in the .filter.averaging package. All the filters are user configurable based on the time constant in units of seconds. The larger the time constant is, the smoother the signal will be. However, latency also increases with the time constant. Because the filter coefficient is in the time domain, differences in sensor output frequencies have little effect on the performance of the filter. These filters should perform about the same across all devices regardless of the sensor frequency. FSensor is clever about providing an implementation where the time constant is agnostic to the output frequencies of the devices sensors which vary greatly by model and manufacturer.

  • filter = new LowPassFilter();
  • filter = new MeanFilter();
  • filter= new MedianFilter();

Low-Pass Filter

FSensor implements an IIR single-pole low-pass filter. The coefficient (alpha) can be adjusted based on the sample period of the sensor to produce the desired time constant that the filter will act on. It takes a simple form of output[0] = alpha * output[0] + (1 - alpha) * input[0]. Alpha is defined as alpha = timeConstant / (timeConstant + dt) where the time constant is the length of signals the filter should act on and dt is the sample period (1/frequency) of the sensor. Computationally efficient versus a mean or median filter (constant time vs linear time). For more information on low-pass filters, see the Acceleration Explorer Wiki.

Mean Filter

FSensor implements a mean filter designed to smooth the data points based on a time constant in units of seconds. The mean filter will average the samples that occur over a period defined by the time constant... the number of samples that are averaged is known as the filter window. The approach allows the filter window to be defined over a period of time, instead of a fixed number of samples.

Median Filter

FSensor uses a median filter designed to smooth the data points based on a time constant in units of seconds. The median filter will take the median of the samples that occur over a period defined by the time constant... the number of samples that are considered is known as the filter window. The approach allows the filter window to be defined over a period of time, instead of a fixed number of samples.

Orientation Sensor Fusions

public class FSensorExample extends AppCompatActivity {

    private FSensor fSensor;

    private SensorSubject.SensorObserver sensorObserver = new SensorSubject.SensorObserver() {
        @Override
        public void onSensorChanged(float[] values) {
            // Do interesting things here
        }
    };

    @Override
    public void onResume() {
        super.onResume();
        fSensor = // Instantiate your FSensor here, i.e new GyroscopeSensor(this);
        fSensor.register(sensorObserver);
        fSensor.start();
    }

    @Override
    public void onPause() {
        fSensor.unregister(sensorObserver);
        fSensor.stop();

        super.onPause();
    }
}

FSensor offers two different estimations of rotation using IMU sensor fusions and a simple purely gyroscope based sensor. These filters can be found in the .filter.fusion package.

  • fSensor = new GyroscopeSensor(this);
  • fSensor = new ComplementaryGyroscopeSensor(this);
  • fSensor = new KalmanGyroscopeSensor(this);

The gyroscope is the underlying orientation sensor for all FSensor implementations. However, the gyroscope tends to drift due to round off errors and other factors. Most gyroscopes work by measuring very small vibrations in the earth's rotation, which means they really do not like external vibrations. Because of drift and external vibrations, the gyroscope has to be compensated with a second estimation of the devices orientation, which comes from the acceleration sensor and magnetic sensor. The acceleration sensor provides the pitch and roll estimations while the magnetic sensor provides the azimuth.

Of the two fused sensors back by the gyroscope, acceleration and magnetic sensors, the first is based on a quaternion backed complementary filter and the second fusion is based on a quaternion backed Kalman filter. Both fusions use the acceleration sensor, magnetic sensor and gyroscope sensor to provide an estimation the devices orientation relative to world space coordinates.

Quaternions Complementary Filter

Quaternions offer an angle-axis solution to rotations which do not suffer from many of the singularies, including gimbal lock, that you will find with rotation matrices. Quaternions can also be scaled and applied to a complementary filter. The quaternion complementary filter is probably the most elegant, robust and accurate of the filters, although it can also be the most difficult to implement.

The complementary filter is a frequency domain filter. In its strictest sense, the definition of a complementary filter refers to the use of two or more transfer functions, which are mathematical complements of one another. Thus, if the data from one sensor is operated on by G(s), then the data from the other sensor is operated on by I-G(s), and the sum of the transfer functions is I, the identity matrix. In practice, it looks nearly identical to a low-pass filter, but uses two different sets of sensor measurements to produce what can be thought of as a weighted estimation.

A complementary filter is used to fuse the two orientation estimations (the gyroscope and acceleration/magnetic, respectively) together. It takes the form of gyro[0] = alpha * gyro[0] + (1 - alpha) * accel/magnetic[0]. Alpha is defined as alpha = timeConstant / (timeConstant + dt) where the time constant is the length of signals the filter should act on and dt is the sample period (1/frequency) of the sensor.

Quaternion Kalman Filter

Quaternions offer an angle-axis solution to rotations which do not suffer from many of the singularities, including gimbal lock, that you will find with rotation matrices. Quaternions can also be scaled and applied to a complementary filter. The quaternion complementary filter is probably the most elegant, robust and accurate of the filters, although it can also be the most difficult to implement.

Kalman filtering, also known as linear quadratic estimation (LQE), is an algorithm that uses a series of measurements observed over time, containing noise (random variations) and other inaccuracies, and produces estimates of unknown variables that tend to be more precise than those based on a single measurement alone. More formally, the Kalman filter operates recursively on streams of noisy input data to produce a statistically optimal estimate of the underlying system state. Much like complementary filters, Kalman filters require two sets of estimations, which we have from the gyroscope and acceleration/magnetic senor.

Linear Acceleration

public class FSensorExample extends AppCompatActivity {

    private FSensor fSensor;

    private SensorSubject.SensorObserver sensorObserver = new SensorSubject.SensorObserver() {
        @Override
        public void onSensorChanged(float[] values) {
            // Do interesting things here
        }
    };

    @Override
    public void onResume() {
        super.onResume();
        fSensor = // Instantiate your FSensor here, i.e new LowPassLinearAccelerationSensor(this);
        fSensor.register(sensorObserver);
        fSensor.start();
    }

    @Override
    public void onPause() {
        fSensor.unregister(sensorObserver);
        fSensor.stop();

        super.onPause();
    }
}

Acceleration Explorer offers a number of different linear acceleration filters. These filters can be found in the .linearacceleration package. Linear acceleration is defined as linearAcceleration = (acceleration - gravity). An acceleration sensor is not capable of determining the difference between gravity/tilt and true linear acceleration. There is one standalone approach, a low-pass filter, and many sensor fusion based approaches. Acceleration Explorer offers implementations of all the common linear acceleration filters as well as the Android API implementation.

  • fSensor = new AccelerationSensor(this);
  • fSensor = new LinearAccelerationSensor(this);
  • fSensor = new ComplementaryLinearAccelerationSensor(this);
  • fSensor = new KalmanLinearAccelerationSensor(this);

Android Linear Acceleration

Android offers its own implementation of linear acceleration with Sensor.TYPE_LINEAR_ACCELERATION, which is supported by Acceleration Explorer. Most of the time the device must have a gyroscope for this sensor type to be supported. However, some devices implement Sensor.TYPE_LINEAR_ACCELERATION without a gyroscope, presumably with a low-pass filter. Regardless of the underlying impelementation, I have found that Sensor.TYPE_LINEAR_ACCELERATION works well for short periods of linear acceleration, but not for long periods (more than a few seconds).

Low-Pass Linear Acceleration

The most simple linear acceleration filter is based on a low-pass filter. It has the advantage that no other sensors are required to estimate linear acceleration and it is computationally efficient. A low-pass filter is implemented in such a way that only very long term (low-frequency) signals (i.e, gravity) are allow to pass through. Anything short term (high-frequency) is filtered out. The gravity estimation is then subtracted from the current acceleration sensor measurement, providing an estimation of linear acceleration. The low-pass filter is an IIR single-pole implementation. The coefficient, a (alpha), can be adjusted based on the sample period of the sensor to produce the desired time constant that the filter will act on. It is essentially the same as the Wikipedia LPF. It takes a simple form of gravity[0] = alpha * gravity[0] + (1 - alpha) * acceleration[0]. Alpha is defined as alpha = timeConstant / (timeConstant + dt) where the time constant is the length of signals the filter should act on and dt is the sample period (1/frequency) of the sensor. Linear acceleration can then be calculated as linearAcceleration = (acceleration - gravity). This implementation can work very well assuming the acceleration sensor is mounted in a relativly fixed position and the periods of linear acceleration is relatively short. For more information on low-pass filters, see here and here.

IMU Sensor Fusion Linear Acceleration

Calculating the gravity components of a normalized orientation is trivial, so FSensor can use the IMU orientation fusions to provide an estimation of linear acceleration that is far more customizable than what Android provides alone.

Sensor Offset Calibration

FSensor contains an algorithm capable of compenstating for hard and soft iron distortions in the magnetic field. This algorithm can be found in the .util package. This same algorithm can also be used to correct for static offsets found in acceleration sensors. The algorithm fits points from an ellipsoid to the polynomial expression Ax^2 + By^2 + Cz^2 + 2Dxy + 2Exz + 2Fyz + 2Gx + 2Hy + 2Iz = 1. The polynomial expression is then solved and the center and radii of the ellipse are determined.

Under certain conditions this algorithm can calibrate a magnetic sensor in an environment that has hard and soft iron distortions (usually from metal or electronics). These environments are commonly found in vehicles, boats and aircraft.

Hard Iron Distortions

Hard iron distortions manifest as an offset of center from the point (0,0,0) Once the center of the ellipsoid is known, it can be transposed to the position (0,0,0) correcting for any hard iron distortions or static offsets found on the sensor.

Soft Iron Distortions

Soft iron distortions manifest as radii that do not have a normalized unit length of 1 (effectively creating a ellipsoid instead of a sphere). Since the radii of the ellipsoid are known, they can be rescaled to a unit length of 1 providing a sphere with a radii of (1,1,1) centered on the point (0,0,0).

The algorithm can be visualized. The red cloud of dots represent the initial measurements as an ellipsoid with an offset. The green cloud of dots represent the red cloud of dots after being corrected by the algorithm with a center of (0,0,0) and a radii of (1,1,1).

Alt text

Magnetic Tilt Compensation

The trigonometry used to calculate the heading from the magnetic sensors vector depends the vector being measured on a flat cartesian plane. Any tilt in the device will skew the measurements. FSensor provides helper functions to compenstate the tilt from either the acceleration sensor directly (for static cases) or from one of the Orientation Fusions provided by FSensor (in dynamic cases).

Published under Apache License, Version 2.0

fsensor's People

Contributors

kalebke avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

fsensor's Issues

Not an issue, more like a question

Hello,

Is it possible to build the FSensor? I've tried on the latest Android studio and looks like it's not:( I can't even add an app module with activity.
Are there like a guidelines or something?

front / back inverted compass

i get compass / gyroscope data from sensor on my android app for google glass i have the rotation degree
but front and backdirection are inverted when marker is rotated

private void updateValues(float[] values) {
 float value_0 = fusedOrientation[0];
float gyro_left_right_degree = (float) (Math.toDegrees(value_0) + 360) % 360;

}

https://i.stack.imgur.com/3u7TT.png

this is my code for creating a line from my lat/lng pos

 public static GeoPoint movePoint(double latitude, double longitude, double distanceInMetres, double bearing) {
        double brngRad = toRadians(bearing);
        double latRad = toRadians(latitude);
        double lonRad = toRadians(longitude);
        int earthRadiusInMetres = 6371000;
        double distFrac = distanceInMetres / earthRadiusInMetres;
        double latitudeResult = asin(sin(latRad) * cos(distFrac) + cos(latRad) * sin(distFrac) * cos(brngRad));
        double a = atan2(sin(brngRad) * sin(distFrac) * cos(latRad), cos(distFrac) - sin(latRad) * sin(latitudeResult));
        double longitudeResult = (lonRad + a + 3 * PI) % (2 * PI) - PI;
        //Log.i(TAG,"movePoint latitude: " + toDegrees(latitudeResult) + ", longitude: " + toDegrees(longitudeResult));
        GeoPoint GeoPoint = new GeoPoint(toDegrees(latitudeResult), toDegrees(longitudeResult));
        return GeoPoint;
    }

Thanks

NullPointerException in OrientationFusedComplimentary.calculateFusedOrientation():122

rotationVectorAccelerationMagnetic can occasionally be null, presumably due to SensorManager.getRotationMatrix() returning false in getOrientationVectorFromAccelerationMagnetic()

This then causes a NPE in the subsequent line, rotationVectorAccelerationMagnetic.multiply((double)oneMinusAlpha);

relevant stack trace:

java.lang.NullPointerException: Attempt to invoke virtual method 'org.apache.commons.math3.complex.Quaternion org.apache.commons.math3.complex.Quaternion.multiply(double)' on a null object reference at com.kircherelectronics.fsensor.filter.gyroscope.fusion.complimentary.OrientationFusedComplimentary.calculateFusedOrientation(OrientationFusedComplimentary.java:122) at com.redacted.location.FSensorCompassKt$getFSCompassObservable$1$listener$1.onSensorChanged(FSensorCompass.kt:62)

Syntax error on maven block

Hi,

I noticed that you created a code snippet to help on how to add the maven repository that hosts the library. It contains

maven {
  maven { url "https://jitpack.io" }
}

and it should be pasted inside the project level build.gradle file as you explained.

I guess that the maven block is duplicated and that the correct code snippet should be

maven {
  url "https://jitpack.io"
}

If this proceeds, let me know if I can help more by sending a PR.
Thanks for the nice effort of putting up this code library and all the explanation regarding the filtering. Really nice work!

Could not install this library

Hi, first of all, thanks for this amazing work.

I'm trying to install this library but without success so far. I'm getting a 403 Forbidden error

> Could not resolve all artifacts for configuration ':app:debugCompileClasspath'.                              
   > Could not resolve com.github.KalebKE:FSensor:v2.x.                                                                                  
     Required by:                                                   
         project :app                                                                                                                    
      > Could not resolve com.github.KalebKE:FSensor:v2.x.                                                                               
         > Could not get resource 'http://dl.bintray.com/lukaville/maven/com/github/KalebKE/FSensor/v2.x/FSensor-v2.x.pom'.              
            > Could not GET 'http://dl.bintray.com/lukaville/maven/com/github/KalebKE/FSensor/v2.x/FSensor-v2.x.pom'. Received status code 403 from server: Forbidden    

LinearAccelerationFusion: low-pass filter?

Hi,
I try to use LinearAccelerationFusion to find linearAcceleration and after that try to remap acceleration in world coordinate.

if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER)
{
               System.arraycopy(event.values, 0, acceleration, 0, event.values.length);
                hasAcceleration = true;
                linearAcceleration = linearAccelerationFusion.filter(acceleration);
}


float[] deviceRelativeAcceleration = new float[4];
                        deviceRelativeAcceleration[0] = linearAcceleration[0];
                        deviceRelativeAcceleration[1] = linearAcceleration[1];
                        deviceRelativeAcceleration[2] = linearAcceleration[2];
                        deviceRelativeAcceleration[3] = 0;


                        float[] R = new float[16], I = new float[16], earthAcc = new float[16];

                        SensorManager.getRotationMatrix(R, I, linearAccelerationFusion.getGravity(), magnetic);

                        float[] inv = new float[16];

                        android.opengl.Matrix.invertM(inv, 0, R, 0);
                        android.opengl.Matrix.multiplyMV(earthAcc, 0, inv, 0, deviceRelativeAcceleration, 0);

                        float [] earthAccNorm = new float[3];
                        earthAccNorm[0] = earthAcc[0]/G_CONSTANT;
                        earthAccNorm[1] = earthAcc[1]/G_CONSTANT;
                        earthAccNorm[2] = earthAcc[2]/G_CONSTANT;

But the earthNorm takes strange values.

From OrientationFusion to OrientationFused

Hi, i really love your software! But i don't understand, how OrientationFused should be used now, since it was changed 2 months ago and the documentation still contains OrientationFusion.

OrientationFused readings erratic/incorrect when oriented South (180/-180 degrees)

Using both the Kalman and Complimentary filters, when the device is rotated through +/-180 degrees, instead of smoothly wrapping, the reported sensor readings "take the long way around", increasing from -180 to 180 (or vice-versa) causing the compass needle (or other visualization) to rapidly flip through all 360 degrees. Alternately, leaving the device oriented south causes the readings to waver very erratically, with occasional brief stabilization at 0 degrees.
Overall this has the effect of rendering a good 20 degree wedge of the compass unusable
Notably, this problem does not manifest at all with the pure gyroscope mode.
(Normally I would assume I've implemented something wrong but the problem manifests in the demo app as well)
I'm not very familiar with quaternions, but I would have expected this would be a problem using them would solve. Unless something weird is happening when the different sensor readings are combined?

Linear acceleration showing big measurements when device is upside down - getGravityFromOrientation issue

Finding your sensor fusion algorithm was a great relieve. Since I don't want to have my product dependent on gravity and/or linear sensor algorithms that are manufacturer or android version dependent.

It works very well and intuitive, I'm only experiencing one bug. After a day of debugging and also trying to understand what quaternions are (pfft ;)) I finally debugged where things are going wrong.

When the device is in a static face up position the accelero values are for example:

  • Accelero: -0.04, 2.12, 9.51 (xyz)
  • GetGravity: -0.04, 2.13, 9.57
  • Lin Acc: +-0, +-0, +-0

My phone is not totally flat but almost flat on a surface.

Now when I turn my phone upside down I get this:

  • Accelero: -0.38, 2.03, -9.65.
  • GetGravity(): 0.38, -2.02, -9.58. (note the difference with accelero positive vs negative numbers)
  • Lin x, y, z are now calculated as: -0.7, 4.05, +-0
    linx = -0.38 - 0.38 = -0.7
    liny = 2.03 - -2.02 = 4.05
    linz = -9.65 - -9.58 = 0

So linx and liny are both reporting acceleration which clearly not exists but is caused by an error in the sum op.

The error is in the GetGravity() values since that should have been: -0.38, 2.02, -9.58, that is also what the standard TYPE_GRAVITY (built in) is reporting.

I guess it has something to do with this note of you:
"Assumes a positive, counter-clockwise, right-handed rotation" on top of the getGravityFromOrientation function.

I've been debugging and testing all day now, but I don't want to implement a hacky fix to support upside down positions of the sensors. I really would prefer to fix this the right way. Can you point me in the right direction on how to fix this? Than I can open a PR and fix it on the repo as well.

getGravity() arguments

Dear KalebKE,
I have a question regarding the argument of getGravity().
LinearAcceleration.filter() receives "acceleration".
But getGravity(0 calls filter.filter() with this "acceleration".

public float[] getGravity(float[] values) {
        return Util.getGravityFromOrientation(filter.filter(values));
}

I think the "filter" is orientationFusion and it gets "gyroscope" values, isn't it?
Please correct me if my understanding is wrong.
Thank you.

Z-axis orientation gets different values depends on height angle of device.

I've used your awesome sensor fusion to get device orientation around 3 axes. The problem is that the z-axis orientation has two values when the device is pointed towards the ground and the same situation when the device is pointed upward. The image can demonstrate the problem to be better understand.

image
As the figure shows, the absolute combination of two angles results in 180 degrees. I've used accelerometer and magnetometer to calculate orientation which gives me the correct z-axis independent of device hight angle.

missing classes

Hi.
I'm trying to use your OrientationKalmanFusion example,
but can't find the class "OrientationFusion" in the project.

private OrientationFusion orientationFusion;
then initialised as orientationFusion = new OrientationKalmanFusion();

i tried declaring it as "OrientationFused" and initialize it with KalmanFusion but the methods:
-setAcceleration
-setMagneticField
-filter
are missing still missing.
Plese help me with some clarification how are this implemented.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.