android-sensorssensor-fusion

Measuring vertical movement of non-fixed Android device


My goal is to make an Android mobile app (SDK16+) that measures road quality while riding a bike.

I have found a Sensor fusion demo for Android that I assume will do all the measurements for me.

How can I get only the vertical movement when the phone is not fixed in a certain orientation?


Solution

  • The problem

    The problem here is that you have two systems of coordinates, the dx, dy, dz, of your device and the wX, wY, wZ of the world around you. The relationship between the two changes as you move your device around.

    Another way to formulate your question would be to say:

    Given a sensor reading [dx, dy, dz], how do I find the component of the reading which is parallel to wZ?

    A solution

    Luckily, the orientation sensors (such as Android's own ROTATION_VECTOR sensor) provide the tools for transformation between these two coordinate systems.

    For example, the output of the ROTATION_VECTORsensor comes in the form of an axis-angle representation of the rotation your device has from some certain "base" rotation fixed in the world frame (see also: Quaternions).

    Android the provides the method SensorManager#getRotationMatrixFromVector(float[] R, float[] rotationVector), which takes axis-angle representation from your sensor and translates it into a rotation matrix. A rotation matrix is used to transform a vector given in one frame of reference to another (in this case [ World -> Device ]).

    Now, you want to transform a measurement in the device frame into the world frame? No problem. One nifty characteristic of rotation matrices is that the inverse of the rotation matrix is the rotation matrix of the opposite transformation ([Device -> World] in our case). Another, even niftier thing is that the inverse of a rotation matrix simply is it's transpose.

    So, your code could follow the lines of:

    public void findVerticalComponentOfSensorValue() {
        float[] rotationVectorOutput = ... // Get latest value from ROTATION_VECTOR sensor
        float[] accelerometerValue = ... // Get latest value from ACCELEROMETER sensor
    
        float[] rotationMatrix = new float[9]; // Both 9 and 16 works, depending on what you're doing with it
        SensorManager.getRotationMatrixFromVector(rotationMatrix, rotationVectorOutput);
    
        float[] accelerationInWorldFrame = matrixMult(
                matrixTranspose(rotationMatrix), 
                accelerometerValue); 
        // Make your own methods for the matrix operations or find an existing library
    
        accelerationInWorldFrame[2] // This is your vertical acceleration
    }
    

    Now, I'm not saying this is the best possible solution, but it should do what you're after.

    Disclaimer

    Strapping a device that uses sensor fusion including a magnetometer to a metal frame may produce inconsistent results. Since your compass heading doesn't matter here, I'd suggest using a sensor fusion method that doesn't involve the magnetometer.