Android calculating Velocity wtih Accelerometer data

Viewed 6382

Due to the power consuming GPS data, I would like to calculate the device speed with only the accelerometer x,y and z data. I have read a lot of questions about this topic and I tried many set-ups to find a satisfactory solution to calculate the speed when my device is in my car. It seems so simple but nothing works, which drives me crazy. Been trying the Sensor.TYPE_LINEAR_ACCELERATION and the Sensor.TYPE_ACCELEROMETER with removed gravity. Tried a Low Pass Filter on the Linear acceleration data. Unfortunately all with no succes. Looks like the calculated speed is correct but testing in my car the calculated speed doesn't get higher then about 2 m/s.

below a code snip

mAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_LINEAR_ACCELERATION);


public void onSensorChanged(SensorEvent event) {
    if (event.sensor == mAccelerometer) {

        if (timestamp != 0) {
            final float dT = (event.timestamp - timestamp) * NS2S;

            lax = event.values[0];
            lay = event.values[1];
            laz = event.values[2];

            vx = vxo + lax * dT ;
            vy = vyo + lay * dT ;
            vz = vzo + laz * dT ;

            speed = (float) (Math.sqrt(vx*vx + vy*vy + vz*vz)) ;
            if (speed < 0.01) {speed = 0 ; } 
            tv_speed.setText(String.valueOf(speed));

        }          
        timestamp = event.timestamp;
    }
}

Hope someone can help, thanks a lot.

3 Answers

It's possible to compute distance and speed using only accelerometer, but with three conditions: 1. Linear movement - trajectory must be straight. 2. Slope of the road must be constant. 3. You must perform calibration procedure before start.

Where can you use this method with such restrictions - it's up to you... Now, how to do it:

We need something, implementing SensorEventListener interface. For the future use, let's add following abstract class:

public abstract class Accelerometer implements SensorEventListener {

        protected float lastX;
        protected float lastY;
        protected float lastZ;
        public abstract Point getPoint();
        public void onAccuracyChanged(Sensor arg0, int arg1) {

     }
}

and this will be our SensorEventListener:

public class XYZAccelerometer extends Accelerometer {


    private static final int BUFFER_SIZE = 500;
    // calibration
    private  float dX = 0;
    private  float dY = 0;
    private  float dZ = 0;
    // buffer variables
    private float X;
    private float Y;
    private float Z;
    private int cnt = 0;

    // returns last SenorEvent parameters
    public Point getLastPoint(){
        return new Point(lastX, lastY, lastZ, 1);
    }

    // returrns parameters, using buffer: average acceleration
    // since last call of getPoint(). 
    public Point getPoint(){

        if (cnt == 0){
            return new Point(lastX, lastY, lastZ, 1);
        }

        Point p =  new Point(X, Y, Z, cnt);

        reset();
        return p;
    }

    // resets buffer
    public void reset(){
        cnt = 0;
        X = 0;
        Y = 0;
        Z = 0;
    }


    public void onSensorChanged(SensorEvent se) {
        float x = se.values[SensorManager.DATA_X] + dX;
        float y = se.values[SensorManager.DATA_Y] + dY;
        float z = se.values[SensorManager.DATA_Z] + dZ;

        lastX = x;
        lastY = y;
        lastZ = z;

        X+= x;
        Y+= y;
        Z+= z;

        if (cnt < BUFFER_SIZE-1) {
            cnt++;
        } else
        {
            reset();
        }
    }

    public int getCnt(){
        return cnt;
    }

    public  void setdX(float dX) {
        this.dX = dX;
    }

    public  void setdY(float dY) {
        this.dY = dY;
    }

    public  void setdZ(float dZ) {
        this.dZ = dZ;
    }
}

Calibrating accelerometer must be called before each experiment. Phone orientation must not be changed while measuring.

To calibrate accelerometer, i use this class:

public class Calibrator {

    final static int UPDATE_INTERVAL = 400;
    final static int ITERATIONS = 5;
    Handler hRefresh;
    XYZAccelerometer acc;
    int eventNumber;
    private LinkedList calData;

    public Calibrator(Handler hRefresh, XYZAccelerometer acc, int eventNumber) {
        this.hRefresh = hRefresh;
        this.acc = acc;
        this.eventNumber = eventNumber;
    }

    public void calibrate() {
        final Timer calTimer = new Timer();
        calData = new LinkedList();
        acc.setdX(0);
        acc.setdY(0);
        acc.setdZ(0);

        calTimer.scheduleAtFixedRate(
                new TimerTask() {

                    public void run() {
                        addCalData(calData);
                        if (calData.size() > ITERATIONS) {
                            calTimer.cancel();
                            try {
                                calSensor(calData);
                            } catch (Exception ex) {
                                try {
                                    throw ex;
                                } catch (Exception ex1) {
                                     hRefresh.sendEmptyMessage(5);
                                }
                            }
                            hRefresh.sendEmptyMessage(eventNumber);
                        }
                    }
                },
                0,
                UPDATE_INTERVAL);
    }

    private void addCalData(LinkedList cD) {
        Point p = acc.getPoint();
        cD.add(p);
        acc.reset();
    }

    private void calSensor(LinkedList cD) throws Exception {
        if (cD.size() < ITERATIONS-1) {
            throw new Exception("not enough data to calibrate");
        }
        float x = 0;
        float y = 0;
        float z = 0;
        // Don't use first measure
        for (int i = 1; i < cD.size(); ++i) {
            x += cD.get(i).getX();
            y += cD.get(i).getY();
            z += cD.get(i).getZ();
        }

        x = x / (cD.size() - 1);
        y = y / (cD.size() - 1);
        z = z / (cD.size() - 1);

        acc.setdX(-x);
        acc.setdY(-y);
        acc.setdZ(-z);
    }
}

maintenance class to keep data of one measure

public class Point {
    private float x = 0;
    private float y = 0;
    private float z = 0;
    private int cnt = 1;

    public float getX() {
        return x/(float)cnt;
    }

    public float getY() {
        return y/(float)cnt;
    }

    public float getZ() {
        return z/(float)cnt;
    }

    public Point(float x, float y, float z, int cnt) {
        this.x = x;
        this.y = y;
        this.z = z;
        this.cnt = cnt;
    }


    public float getForce(){
        return getX()*getX()+getY()*getY()+getZ()*getZ();
    }
}

And class to process data of measure

public class MeasurePoint {
    private float x;
    private float y;
    private float z;
    private float speedBefore;
    private float speedAfter;
    private float distance;
    private float acceleration;
    private long interval;
    private Point averagePoint;

    public MeasurePoint(float x, float y, float z, float speedBefore, long interval, Point averagePoint) {
        this.x = x;
        this.y = y;
        this.z = z;
        this.speedBefore = speedBefore;
        this.interval = interval;
        this.averagePoint = averagePoint;
        speedAfter = 0;
        calc();
    }

    private void calc(){
        //Acceleration as projection of current vector on average
        acceleration = this.x*averagePoint.getX() +
                        this.y*averagePoint.getY() +
                        this.z*averagePoint.getZ();
        acceleration = acceleration / ((float)Math.sqrt(averagePoint.getForce()));
        float t = ((float)interval / 1000f);
        speedAfter = speedBefore + acceleration * t;
        distance = speedBefore*t + acceleration*t*t/2;

    }

    public String getStoreString(){
        String s = "write here whatever you want";
        return s;
    }

// add getters
}

This one - to store and save data array

public class MeasureData {
    // points from accelerometr
    private LinkedList accData;
    private LinkedList data;
    // timer interval of generating points
    private long interval;

    public MeasureData(long interval) {
        this.interval = interval;
        accData = new LinkedList ();
        data = new LinkedList ();
    }

    public void addPoint(Point p){
        accData.add(p);
    }

    public void process(){

        for(int i = 0; i < accData.size(); ++i){
            Point p = accData.get(i);
            float speed = 0;

            if(i > 0){
                speed = data.get(i-1).getSpeedAfter();
            }
            data.add(new MeasurePoint(p.getX(), p.getY(), p.getZ(), speed, interval, getAveragePoint()));
        }
    }

    public boolean saveExt(Context con, String fname) throws Throwable {

        try {

            File file = new File(con.getExternalFilesDir(null), fname);
            FileOutputStream os = new FileOutputStream(file);
            OutputStreamWriter out = new OutputStreamWriter(os);


            for (int i = 0; i < data.size(); ++i) {
                MeasurePoint m = data.get(i);
                out.write(m.getStoreString());
            }

            out.close();
        } catch (Throwable t) {
            throw (t);
        }
        return true;
    }

    private Point getAveragePoint() {
        float x = 0;
        float y = 0;
        float z = 0;

        for(int i = 0; i < accData.size(); ++i){
            Point p = accData.get(i);
            x += p.getX();
            y += p.getY();
            z += p.getZ();
        }

        return new Point(x, y, z, 1);
    }

    public float getLastSpeed(){
        return data.getLast().getSpeedAfter();
    }

    public float getLastSpeedKm(){
        float ms = getLastSpeed();
        return ms*3.6f;
    }
}

And, finally, how to use all this in your activity(I cleaned it up a lot, sorry if it will not complie - fill free to write it in comments:

public class TestActivity extends Activity {


    static final int TIMER_DONE = 2;
    static final int START = 3;
    static final int CAL_TIMER_DONE = 4;
    static final int ERROR = 5;

    private StartCatcher mStartListener;
    private XYZAccelerometer xyzAcc;
    private SensorManager mSensorManager;
    private static final long UPDATE_INTERVAL = 500;
    private static final long MEASURE_TIMES = 20;
    private Timer timer;
    private TextView tv;
    private Button testBtn;
    int counter;
    private MeasureData mdXYZ;


    /** handler for async events*/
    Handler hRefresh = new Handler() {

        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case TIMER_DONE:

                    onMeasureDone();
                    String es1 = Float.toString(Math.round(mdXYZ.getLastSpeedKm()*100)/100f);
                    tv.append(" END SPEED " + es1 + " " + es2 + " \n");
                    enableButtons();
                    break;
                case START:
                    tv.append(" START");
                    timer = new Timer();
                    timer.scheduleAtFixedRate(
                            new TimerTask() {

                                public void run() {
                                    dumpSensor();
                                }
                            },
                            0,
                            UPDATE_INTERVAL);

                    break;
                case ERROR:
                    Toast.makeText(getApplicationContext(), "ERROR", Toast.LENGTH_SHORT).show();
                    break;
            }
        }
    };

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        tv = (TextView) findViewById(R.id.txt);
        testBtn = (Button) findViewById(R.id.btn);
    }

    @Override
    protected void onResume() {
        super.onResume();
        tv.append("\n ..");
        mSensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
        setAccelerometer();
        setStartCatcher();
        mSensorManager.registerListener(xyzAcc,
                mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER),
                SensorManager.SENSOR_DELAY_GAME);

    }

    @Override
    protected void onPause() {
        mSensorManager.unregisterListener(xyzAcc);
        super.onPause();
    }


    public void onButtonTest(View v) {
        disableButtons();
        mdXYZ = new MeasureData(UPDATE_INTERVAL);
        counter = 0;
        tv.setText("");
        tv.append("Calibrating");
        Calibrator cal = new Calibrator(hRefresh, xyzAcc, START);
        cal.calibrate();

    }

    void dumpSensor() {
        ++counter;
        mdXYZ.addPoint(xyzAcc.getPoint());

        hRefresh.sendEmptyMessage(TICK);

        if (counter > MEASURE_TIMES) {
            timer.cancel();
            hRefresh.sendEmptyMessage(TIMER_DONE);
        }

    }

    private void enableButtons() {
        testBtn.setEnabled(true);

    }


    private void setAccelerometer() {
        xyzAcc = new XYZAccelerometer();
        mSensorManager.registerListener(xyzAcc,
                mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER),
                SensorManager.SENSOR_DELAY_UI);
    }


    private void disableButtons() {
        testBtn.setEnabled(false);
    }

    private void onMeasureDone() {
        try {
            mdXYZ.process();
            long now = System.currentTimeMillis();
            mdXYZ.saveExt(this, Long.toString(now) + ".csv");
        } catch (Throwable ex) {
            Toast.makeText(this, ex.getMessage().toString(), Toast.LENGTH_SHORT);
        }
    }
}

<serviceLinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
    <serviceButton 
        android:id="@+id/btn"
        android:text="TEST"
        android:layout_width="300px"
        android:layout_height="200px"
        android:onClick="onButtonTest"  />

        <serviceTextView  
    android:id = "@+id/txt"
    android:layout_width="fill_parent" 
    android:layout_height="wrap_content" 
    android:text=":"
    />
<service/LinearLayout>

Please see the below code to get the velocity using accelerometer

public class SensorTestActivity extends Activity implements SensorEventListener {
    double calibration = Double.NaN;
    private SensorManager sensorManager;
    private boolean color = false;
    private TextView view;
    private long lastUpdate;

    float appliedAcceleration = 0;
    float currentAcceleration = 0;
    float velocity = 0;
    Date lastUpdatedate;

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
                WindowManager.LayoutParams.FLAG_FULLSCREEN);

        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main2);
        view = findViewById(R.id.textView);
//        view.setBackgroundColor(Color.GREEN);
        lastUpdatedate = new Date(System.currentTimeMillis());
        sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
        lastUpdate = System.currentTimeMillis();
    }

    @Override
    public void onSensorChanged(SensorEvent event) {
//        if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
//            getAccelerometer(event);
//        }

        double x = event.values[0];
        double y = event.values[1];
        double z = event.values[2];
        double a = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2) + Math.pow(z, 2));
        if (calibration == Double.NaN)
            calibration = a;
        else {
            updateVelocity();
            currentAcceleration = (float)a;
        }

    }

    private void getAccelerometer(SensorEvent event) {
        float[] values = event.values;
        // Movement
        float x = values[0];
        float y = values[1];
        float z = values[2];

        float accelationSquareRoot = (x * x + y * y + z * z)
                / (SensorManager.GRAVITY_EARTH * SensorManager.GRAVITY_EARTH);
        long actualTime = event.timestamp;
        if (accelationSquareRoot >= 2) //
        {
            if (actualTime - lastUpdate < 200) {
                return;
            }
            lastUpdate = actualTime;
//            Toast.makeText(this, "Device was shuffed", Toast.LENGTH_SHORT)
//                    .show();
            if (color) {
                view.setBackgroundColor(Color.GREEN);
            } else {
                view.setBackgroundColor(Color.RED);
            }
            color = !color;
            view.setText("SPEEDDDDD=== "+accelationSquareRoot);
//            Log.i("SensorTestActivity","SPEEDDDDD=== "+accelationSquareRoot+"     ");
        }
    }

    private void updateVelocity() {
        // Calculate how long this acceleration has been applied.
        Date timeNow = new Date(System.currentTimeMillis());
        long timeDelta = timeNow.getTime()-lastUpdatedate.getTime();
        lastUpdatedate.setTime(timeNow.getTime());

        // Calculate the change in velocity at the
        // current acceleration since the last update.
        float deltaVelocity = appliedAcceleration * (timeDelta/1000);
        appliedAcceleration = currentAcceleration;

        // Add the velocity change to the current velocity.
        velocity += deltaVelocity;

        final double mph = (Math.round(100*velocity / 1.6 * 3.6))/100;
        Log.i("SensorTestActivity","SPEEDDDDD=== "+mph+"     "+velocity);
    }

    @Override
    public void onAccuracyChanged(Sensor sensor, int accuracy) {

    }

    @Override
    protected void onResume() {
        super.onResume();
        // register this class as a listener for the orientation and
        // accelerometer sensors
        sensorManager.registerListener(this,
                sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER),
                SensorManager.SENSOR_DELAY_NORMAL);
    }

    @Override
    protected void onPause() {
        // unregister listener
        super.onPause();
        sensorManager.unregisterListener(this);
    }
}
Related