44

I am developing some application like Runtastic Pedometer using the algorithm but I am not getting any similarity between the results.

my code is as follows:

public void onSensorChanged(SensorEvent event) 
{
        Sensor sensor = event.sensor; 
        synchronized (this)
 {
            if (sensor.getType() == Sensor.TYPE_ORIENTATION) {}
            else {
            int j = (sensor.getType() == Sensor.TYPE_ACCELEROMETER) ? 1 : 0;
                if (j == 1) {
                    float vSum = 0;
                    for (int i=0 ; i<3 ; i++) {
                        final float v = mYOffset + event.values[i] * mScale[j];
                        vSum += v;

                    }
                    int k = 0;
                    float v = vSum / 3;
                    //Log.e("data", "data"+v);

                    float direction = (v > mLastValues[k] ? 1 : (v < mLastValues[k] ? -1 : 0));
                    if (direction == - mLastDirections[k]) {
                        // Direction changed
                        int extType = (direction > 0 ? 0 : 1); // minumum or maximum?
                        mLastExtremes[extType][k] = mLastValues[k];
                        float diff = Math.abs(mLastExtremes[extType][k] - mLastExtremes[1 - extType][k]);

                        if (diff > mLimit) {

                            boolean isAlmostAsLargeAsPrevious = diff > (mLastDiff[k]*2/3);
                            boolean isPreviousLargeEnough = mLastDiff[k] > (diff/3);
                            boolean isNotContra = (mLastMatch != 1 - extType);

                            if (isAlmostAsLargeAsPrevious && isPreviousLargeEnough && isNotContra) {

                                for (StepListener stepListener : mStepListeners) {
                                    stepListener.onStep();
                                }
                                mLastMatch = extType;
                            }
                            else {
                                Log.i(TAG, "no step");
                                mLastMatch = -1;
                            }
                        }
                        mLastDiff[k] = diff;
                    }
                    mLastDirections[k] = direction;
                    mLastValues[k] = v;
                }
            }
        }
    }

for registering sensors:

mSensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
        mSensor = mSensorManager.getDefaultSensor(
                Sensor.TYPE_ACCELEROMETER);
mSensorManager.registerListener(mStepDetector,mSensor,SensorManager.SENSOR_DELAY_NORMAL);

in the algorithm i have different levels for sensitivity as public void

setSensitivity(float sensitivity) {
        mLimit = sensitivity; // 1.97  2.96  4.44  6.66  10.00  15.00  22.50  33.75  50.62
    }

on various sensitivity level my result is:

sensitivity   rantastic pedometer  my app
10.00           3870                 5500
11.00           3000                 4000
11.15           3765                 4576
13.00           2000                 890
11.30           754                  986

I am not getting any proper pattern to match with the requirement. As per my analysis this application is using Sensor.TYPE_MAGNETIC_FIELD for steps calculation please let me know some algorithm so that I can meet with the requirement.

Prashant Pokhriyal
  • 3,727
  • 4
  • 28
  • 40
user3056585
  • 441
  • 1
  • 5
  • 4
  • 1
    Most high quality pedometers use an algorithm to detect any movements repeated in a specific pattern. So when e.g. at least 3 steps have been detected in a row with approx the same frequency (usually limited to a typical span for walking) all three steps are added. The pedometer continues to add any steps after that within approximately the same fequency. This way other device movements are filtered out and the sensitivity can kept at a higher setting without too much noise. – Lmickos Dec 05 '13 at 15:37
  • 2
    A bit off-topic, in case you are just targeting API 19 (which you are probably not), there is a built-in step counter and step detector software sensor. I tested it before, it's very accurate. Maybe try to dig into the source code? https://developer.android.com/reference/android/hardware/Sensor.html#TYPE_STEP_COUNTER – kevin Dec 06 '13 at 04:12
  • Just to be sure, you tested this on the same device at the exact same time, right? The main question that needs to be answered is what are the differences between your code and the pedometer code. Any differences you know about, or should they be exactly the same? Edit: just verified that the `onSensorChanged()` is identical to the pedometer project. – Jeffrey Klardie Dec 06 '13 at 08:45
  • @kevin i am curious.. is the step counter software or hardware.. i have three devices all running kitKat an HTC one, a nexus 5, and a nexus 7.. when i do a call to see if the sensor type step_counter is available it only registers true on the nexus 5. – erik Jun 23 '14 at 22:13
  • @erik yeah, I suppose it's hardware, didn't look into it very carefully before. – kevin Jun 23 '14 at 22:17
  • @kevin its a bit unclear.. the documentation actually states that it uses the accelerometer (which i feel like it must, or linear acceleration) anyway, id also personally like to be able to determine if the user stepped forward or backwards.. anyone have luck with this? – erik Jun 23 '14 at 22:23
  • @kevin : Its for Android Wear, which has the hardware – Akshit Rewari Nov 04 '14 at 14:11
  • @user3056585: I m facing the same issue.. I want to calculate steps using accelerometer. Did you find any solution? – Mehta Apr 29 '16 at 09:45
  • https://github.com/bagilevi/android-pedometer i hope this might be helpfull – madhu kotagiri Dec 10 '13 at 12:45

5 Answers5

21

The first thing you need to do is decide on an algorithm. As far as I know there are roughly speaking three ways to detect steps using accelerometers that are described in the literature:

  1. Use the Pythagorean theorem to calculate the magnitude of the acceleration vector of each sample from the accelerometer. Low-pass filter the magnitude signal to remove high frequency noise and then look for peaks and valleys in the filtered signal. You may need to add additional requirements to remove false positives. This is by far the simplest way to detect steps, it is also the way that most if not all ordinary pedometers of the sort that you can buy from a sports store work.

  2. Use Pythagoras' like in (1), then run the signal through an FFT and compare the output from the FFT to known outputs of walking. This requires you to have access to a fairly large amount of training data.

  3. Feed the accelerometer data into an algorithm that uses some suitable machine learning technique, for example a neural network or a digital wavelet transform. You can of course include other sensors in this approach. This also requires you to have access to a fairly large amount of training data.

Once you have decided on an algorithm you will probably want to use something like Matlab or SciPy to test your algorithm on your computer using recordings that you have made on Android phones. Dump accelerometer data to a cvs file on your phone, make a record of how many steps the file represents, copy the file to your computer and run your algorithm on the data to see if it gets the step count right. That way you can detect problems with the algorithm and correct them.

If this sounds difficult, then the best way to get access to good step detection is probably to wait until more phones come with the built-in step counter that KitKat enables.

Rasmusob
  • 508
  • 3
  • 13
5

I am using step detection in my walking instrument. I get nice results of step detection. I use achartengine to plot accelerometer data. Take a look here. What I do:

  1. Analysis of magnitude vector for accelerometer sensor.
  2. Setting a changeable threshold level. When signal from accelerometer is above it I count it as a step.
  3. Setting the time of inactive state (for step detection) after first crossing of the threshold.

Point 3. is calculated:

  • arbitrary setting the maximum tempo of our walking (e.g. 120bpm)
  • if 60bpm - 1000msec per step, then 120bpm - 500msec per step
  • accelerometer passes data with certain desired frequency (SENSOR_DELAY_NORMAL, SENSOR_DELAY_GAME, etc.). When DELAY_GAME: T ~= 20ms (this is included in Android documentation)
  • n - samples to omit (after passing the threshold)
  • n = 500msec / T
  • n = 500 / 20 = 25 (plenty of them. You can adjust this value).
  • after that, the threshold becomes active.

Take a look at this picture: My application

Michał Dobi Dobrzański
  • 1,449
  • 1
  • 20
  • 19
3

This is my realization. It was written about 1.5-2 years ago. And I really don't remember all this stuff that I wrote. But it worked. And it worked good for my needs.

I know that this is really big class (some methods are deleted), but may be it will be helpful. If not, I'll just remove this answer...

public class StepDetector implements SensorEventListener
{
    public static final int MAX_BUFFER_SIZE = 5;

    private static final int Y_DATA_COUNT = 4;
    private static final double MIN_GRAVITY = 2;
    private static final double MAX_GRAVITY = 1200;

    public void onSensorChanged(final SensorEvent sensorEvent)
    {
        final float[] values = sensorEvent.values;
        final Sensor sensor = sensorEvent.sensor;

        if (sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD)
        {
            magneticDetector(values, sensorEvent.timestamp / (500 * 10 ^ 6l));
        }
        if (sensor.getType() == Sensor.TYPE_ACCELEROMETER)
        {
            accelDetector(values, sensorEvent.timestamp / (500 * 10 ^ 6l));
        }
    }

    private ArrayList<float[]> mAccelDataBuffer = new ArrayList<float[]>();
    private ArrayList<Long> mMagneticFireData = new ArrayList<Long>();
    private Long mLastStepTime = null;
    private ArrayList<Pair> mAccelFireData = new ArrayList<Pair>();

    private void accelDetector(float[] detectedValues, long timeStamp)
    {
        float[] currentValues = new float[3];
        for (int i = 0; i < currentValues.length; ++i)
        {
            currentValues[i] = detectedValues[i];
        }
        mAccelDataBuffer.add(currentValues);
        if (mAccelDataBuffer.size() > StepDetector.MAX_BUFFER_SIZE)
        {
            double avgGravity = 0;
            for (float[] values : mAccelDataBuffer)
            {
                avgGravity += Math.abs(Math.sqrt(
                        values[0] * values[0] + values[1] * values[1] + values[2] * values[2]) -    SensorManager.STANDARD_GRAVITY);
            }
            avgGravity /= mAccelDataBuffer.size();

            if (avgGravity >= MIN_GRAVITY && avgGravity < MAX_GRAVITY)
            {
                mAccelFireData.add(new Pair(timeStamp, true));
            }
            else
            {
                mAccelFireData.add(new Pair(timeStamp, false));
            }

            if (mAccelFireData.size() >= Y_DATA_COUNT)
            {
                checkData(mAccelFireData, timeStamp);

                mAccelFireData.remove(0);
            }

            mAccelDataBuffer.clear();
        }
    }

    private void checkData(ArrayList<Pair> accelFireData, long timeStamp)
    {
        boolean stepAlreadyDetected = false;

        Iterator<Pair> iterator = accelFireData.iterator();
        while (iterator.hasNext() && !stepAlreadyDetected)
        {
            stepAlreadyDetected = iterator.next().first.equals(mLastStepTime);
        }
        if (!stepAlreadyDetected)
        {
            int firstPosition = Collections.binarySearch(mMagneticFireData, accelFireData.get(0).first);
            int secondPosition = Collections
                .binarySearch(mMagneticFireData, accelFireData.get(accelFireData.size() - 1).first - 1);

            if (firstPosition > 0 || secondPosition > 0 || firstPosition != secondPosition)
            {
                if (firstPosition < 0)
                {
                    firstPosition = -firstPosition - 1;
                }
                if (firstPosition < mMagneticFireData.size() && firstPosition > 0)
                {
                    mMagneticFireData = new ArrayList<Long>(
                           mMagneticFireData.subList(firstPosition - 1, mMagneticFireData.size()));
                }

                iterator = accelFireData.iterator();
                while (iterator.hasNext())
                {
                    if (iterator.next().second)
                    {
                        mLastStepTime = timeStamp;
                        accelFireData.remove(accelFireData.size() - 1);
                        accelFireData.add(new Pair(timeStamp, false));
                        onStep();
                        break;
                    }
                }
            }
        }
    }

    private float mLastDirections;
    private float mLastValues;
    private float mLastExtremes[] = new float[2];
    private Integer mLastType;
    private ArrayList<Float> mMagneticDataBuffer = new ArrayList<Float>();

    private void magneticDetector(float[] values, long timeStamp)
    {
        mMagneticDataBuffer.add(values[2]);

        if (mMagneticDataBuffer.size() > StepDetector.MAX_BUFFER_SIZE)
        {
            float avg = 0;

            for (int i = 0; i < mMagneticDataBuffer.size(); ++i)
            {
                avg += mMagneticDataBuffer.get(i);
            }

            avg /= mMagneticDataBuffer.size();

            float direction = (avg > mLastValues ? 1 : (avg < mLastValues ? -1 : 0));
            if (direction == -mLastDirections)
            {
                // Direction changed
                int extType = (direction > 0 ? 0 : 1); // minumum or maximum?
                mLastExtremes[extType] = mLastValues;
                float diff = Math.abs(mLastExtremes[extType] - mLastExtremes[1 - extType]);

                if (diff > 8 && (null == mLastType || mLastType != extType))
                {
                    mLastType = extType;

                    mMagneticFireData.add(timeStamp);
                }
            }
            mLastDirections = direction;
            mLastValues = avg;

            mMagneticDataBuffer.clear();
        }
    }

    public static class Pair implements Serializable
    {
        Long first;
        boolean second;

        public Pair(long first, boolean second)
        {
            this.first = first;
            this.second = second;
        }

        @Override
        public boolean equals(Object o)
        {
            if (o instanceof Pair)
            {
                return first.equals(((Pair) o).first);
            }
            return false;
        }
    }
}
mig35
  • 1,432
  • 12
  • 16
  • thanks but result are not improved, can you explain the use Magnetic Sensors in counting. I'm searching for it but didn't find any article. – strike Dec 12 '13 at 09:22
  • Well. I wish I could write better and clearer two years ago.. So. As I remember, I used two peaks as for accelerometer and magnetometer. I used canvas to render graphics and found some ways to count steps. And I've implemented it here. And I miss single peaks of each sensor, only both. And something more. This code only works if you put your device in your pocket. – mig35 Dec 12 '13 at 10:29
  • i have first position and last position both -1 even if i move. They never change.Plz help – Arpan Sharma Aug 12 '16 at 12:14
2

One main difference I spotted between your implementation and the code in the grepcode project is the way you register the listener.

Your code:

mSensorManager.registerListener(mStepDetector,
                                mSensor,
                                SensorManager.SENSOR_DELAY_NORMAL);

Their code:

mSensorManager.registerListener(mStepDetector,
                                mSensor,
                                SensorManager.SENSOR_DELAY_FASTEST);

This is a big difference. SENSOR_DELAY_NORMAL is intended for orientation changes, and is therefor not that fast (ever noticed that it takes some time between you rotating the device, and the device actually rotating? That's because this is some functionality that does not need to be super fast (that would probably be pretty annoying even). The rate at which you get updates is not that high).

On the other hand, SENSOR_DELAY_FASTEST is intended for things like pedometers: you want the sensor data as fast and often as possible, so your calculations of steps will be as accurate as possible.

Try to switch to the SENSOR_DELAY_FASTEST rate, and test again! It should make a big difference.

Jeffrey Klardie
  • 3,020
  • 1
  • 18
  • 23
  • 1
    using the FAST_DELAY increases the battery efficiency , most of the good apps don't use this parameter. we have done testing on FAST_DEAY as well but results are not improved.I'm missing the concept of magnetic sensors here. – strike Dec 12 '13 at 09:27
0
 public void onSensorChanged(SensorEvent event) {
   if (event.sensor.getType()==Sensor.TYPE_ACCELEROMETER ){
            float x = event.values[0];
            float y = event.values[1];
            float z = event.values[2];

            currentvectorSum = (x*x + y*y + z*z);
            if(currentvectorSum < 100 && inStep==false){
                inStep = true;
            }
            if(currentvectorSum > 125 && inStep==true){
                inStep = false;
                numSteps++;
                Log.d("TAG_ACCELEROMETER", "\t" + numSteps);
            }
        }
}