【问题标题】:Detecting periodic data from the phone's accelerometer从手机的加速度计检测周期性数据
【发布时间】:2011-12-21 19:56:24
【问题描述】:

我正在开发一个 Android 应用,我需要检测用户上下文(如果步行或开车最少)

我正在使用加速度计和所有轴的总和来检测加速度矢量。它工作得很好,我可以在走路时看到一些周期值。但我需要以编程方式检测这些时间点。

请问有什么数学函数可以检测一组值中的周期吗?我听说傅里叶变换可用于此,但我真的不知道如何实现它。看起来很复杂:)

请帮忙

【问题讨论】:

    标签: android algorithm signal-processing accelerometer


    【解决方案1】:

    检测数据周期性的最简单方法是autocorrelation。这也很容易实现。要获得i 的自相关,您只需将数据的每个数据点与移动了i 的每个数据点相乘。这是一些伪代码:

    for i = 0 to length( data ) do
      autocorrel[ i ] = 0;
      for j = 0 to length( data ) do
         autocorrel[ i ] += data( j ) * data( ( j + i ) mod length( data ) )
      done
    done
    

    这将为您提供一个值数组。最高的“周期性”在具有最高值的索引处。这样您就可以提取任何周期性部分(通常不止一个)。

    另外我建议您不要尝试在应用程序中实现自己的 FFT。虽然这个算法非常适合学习,但有很多错误是很难测试的,而且你的实现也可能比已经可用的慢得多。如果在您的系统上可行,我建议您使用 FFTW,在 FFT 实现方面,它在任何方面都无法击败。

    编辑:

    解释,为什么这对不完全重复的值也有效:

    计算自相关的通常且完全正确的方法是从数据中减去平均值。假设您有[1, 2, 1.2, 1.8 ]。然后你可以从每个样本中提取 1.5,留下[-.5, .5, -.3, .3 ]。现在,如果您将其与自身相乘,偏移量为零,负数将乘以负数,正数乘以正数,得到(-.5)^2 + (.5)^2 + (-.3)^2 + (.3)^2=.68。在偏移量为 1 处,负数将乘以正数,得到 (-.5)*(.5) + (.5)*(-.3) + (-.3)*(.3) + (.3)*(-.5)=-.64。在两个偏移量处,负数将乘以负数,正数乘以正数。在偏移量为 3 时,类似于偏移量 1 的情况再次发生。如您所见,在偏移量 0 和 2(周期)处获得正值,在 1 和 4 处获得负值。

    现在只检测周期,不需要减去平均值。如果您只是将样本保持原样,则每次添加时都会添加平方平均值。由于将为每个计算的系数添加相同的值,因此比较将产生与您首先减去平均值相同的结果。在最坏的情况下,您的数据类型可能会溢出(如果您使用某种整数类型),或者当值开始变大时可能会出现舍入错误(如果您使用浮点数,通常这不是问题)。如果发生这种情况,请先减去平均值,然后尝试结果是否更好。

    使用自相关与某种快速傅立叶变换相比,最大的缺点是速度。自相关采用 O(n^2),而 FFT 仅采用 O(n log(n))。如果您需要经常计算很长序列的周期,则自相关可能不适用于您的情况。

    如果你想知道傅里叶变换是如何工作的,以及关于实部、虚部、幅度和相位的所有这些东西(例如,看看 Manu 发布的代码)意味着什么,我建议你有一个看看this book

    EDIT2:

    在大多数情况下,数据既不是完全周期性的,也不是完全混沌和非周期性的。通常,您的数据将由几个具有不同强度的周期性分量组成。周期是一个时间差,您可以通过该时间差移动数据以使其与自身相似。如果将数据移动一定量,自相关会计算数据的相似程度。因此,它为您提供了所有可能时期的力量。这意味着,不存在“重复值索引”,因为当数据完全周期性时,所有索引都会重复。具有最强值的索引为您提供数据与其自身最相似的偏移。因此,该索引给出了时间偏移,而不是数据的索引。为了理解这一点,重要的是要理解时间序列如何被认为是由完美周期函数(正弦基函数)的总和组成。

    如果您需要在很长的时间序列中检测到这一点,通常最好在数据上滑动一个窗口,然后检查这个较小数据帧的周期。但是您必须注意,您的窗口会为您的数据添加额外的句点,您必须注意这一点。

    我在上次编辑中发布的链接中有更多内容。

    【讨论】:

    • 实际上不是我拒绝了你的答案。如果我是,那是偶然的。您的答案看起来非常棒,但是如果周期性值不完全相同,它是否会成为工作事件(例如,不会重复数字 2,但会出现从 1.8 到 2.2 的重复值) ?
    • 谢谢你的努力,这个答案真的很全面..如果我得到了算法,结果是结果值最高的索引是输入数据中重复值最多的索引?我需要断言数据是否是周期性的。我有这样的输入值 cl.ly/0m1D2d2h0g0M2d351r08 。这实际上比我将处理的数据量大得多。我在 Matlab 中对这些数据进行了自相关,得到了 cl.ly/1m2I2R3D083S0R1i1z1b 这个数据的自相关 cl.ly/122Q00021Z2P3k2R0J3x 看起来像这样 cl.ly/1d3t0N0p3E441k3R1A1t
    • 仅供参考,第一张图片是行走时的高峰,而失速时的低极端
    【解决方案2】:

    还有一种方法可以使用 FFT 计算数据的自相关,从而将复杂性从 O(n^2) 降低到 O(n log n)。基本思想是您获取周期性样本数据,使用 FFT 对其进行转换,然后通过将每个 FFT 系数乘以其复共轭来计算功率谱,然后对功率谱进行逆 FFT。您可以轻松找到预先存在的代码来计算功率谱。例如,查看Moonblink android library。该库包含 FFTPACK 的 JAVA 翻译(一个很好的 FFT 库),它还具有一些用于计算功率谱的 DSP 类。我成功使用的一种自相关方法是 McLeod Pitch Method (MPM),其 java 源代码可在here 获得。我在 McLeodPitchMethod 类中编辑了一个方法,它允许它使用 FFT 优化的自相关算法计算音高:

    private void normalizedSquareDifference(final double[] data) {
        int n = data.length;
        // zero-pad the data so we get a number of autocorrelation function (acf)
        // coefficients equal to the window size
        double[] fft = new double[2*n];
            for(int k=0; k < n; k++){
            fft[k] = data[k];
        }
        transformer.ft(fft);
        // the output of fft is 2n, symmetric complex
        // multiply first n outputs by their complex conjugates
        // to compute the power spectrum
        double[] acf = new double[n];
        acf[0] = fft[0]*fft[0]/(2*n);
        for(int k=1; k <= n-1; k++){
            acf[k] = (fft[2*k-1]*fft[2*k-1] + fft[2*k]*fft[2*k])/(2*n);
        }
        // inverse transform
        transformerEven.bt(acf);
        // the output of the ifft is symmetric real
        // first n coefficients are positive lag acf coefficients
        // now acf contains acf coefficients
        double[] divisorM = new double[n];
        for (int tau = 0; tau < n; tau++) {
            // subtract the first and last squared values from the previous divisor to get the new one;
            double m = tau == 0 ? 2*acf[0] : divisorM[tau-1] - data[n-tau]*data[n-tau] - data[tau-1]*data[tau-1];
            divisorM[tau] = m;
            nsdf[tau] = 2*acf[tau]/m;
        }
    }
    

    其中transformer 是来自java FFTPACK 转换的FFTTransformer 类的私有实例,transformerEvenFFTTransformer_Even 类的私有实例。 使用您的数据调用 McLeodPitchMethod.getPitch() 可以非常有效地估计频率。

    【讨论】:

    • 我身边的一个问题:如果您有一个简单的 FFT 可用,那么在检测最强的周期性分量时使用它来计算自相关的目的是什么。难道不能直接从 FFT 中得到最强的分量而跳过其余的吗?据我了解,此任务的自相关只是一种不必理解和实现 FFT 的方法,因此如果有可用的 FFT,我就不再认为它有意义了。
    • 这是一种策略,是的。然而,识别 FFT 频谱中的“最强分量”并不总是直截了当的。幅度最大的分量可能不代表数据的基频,或者更糟的是,基频甚至可能根本不存在于频谱中。虽然自相关也不能直接测量基频,但面对这类问题,它是估计它的最佳方法。如果您怀疑这些问题不适用于您的数据,那么您可能会发现自相关是不必要的。
    【解决方案3】:

    这是一个使用 libgdx 中的 FFT 类计算傅里叶变换 android 的示例:

        package com.spec.example;
    import android.app.Activity;
    import android.os.Bundle;
    import com.badlogic.gdx.audio.analysis.FFT;
    import java.lang.String;
    import android.util.FloatMath;
    import android.widget.TextView;
    
    public class spectrogram extends Activity {
        /** Called when the activity is first created. */
        float[] array = {1, 6, 1, 4, 5, 0, 8, 7, 8, 6, 1,0, 5 ,6, 1,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
        float[] array_hat,res=new float[array.length/2];
        float[] fft_cpx,tmpr,tmpi;
        float[] mod_spec =new float[array.length/2];
        float[] real_mod = new float[array.length];
        float[] imag_mod = new float[array.length];
        double[] real = new double[array.length];
        double[] imag= new double[array.length];
        double[] mag = new double[array.length];
        double[] phase = new double[array.length];
        int n;
        float tmp_val;
        String strings;
        FFT fft = new FFT(32, 8000);
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            TextView tv = new TextView(this);
    
           fft.forward(array);
           fft_cpx=fft.getSpectrum();
           tmpi = fft.getImaginaryPart();
           tmpr = fft.getRealPart();
          for(int i=0;i<array.length;i++)
           {
               real[i] = (double) tmpr[i];
               imag[i] = (double) tmpi[i];
               mag[i] = Math.sqrt((real[i]*real[i]) + (imag[i]*imag[i]));
               phase[i]=Math.atan2(imag[i],real[i]);
    
               /****Reconstruction****/        
               real_mod[i] = (float) (mag[i] * Math.cos(phase[i]));
               imag_mod[i] = (float) (mag[i] * Math.sin(phase[i]));
    
           }
    
           fft.inverse(real_mod,imag_mod,res);
       }
    }
    

    更多信息在这里:http://www.digiphd.com/android-java-reconstruction-fast-fourier-transform-real-signal-libgdx-fft/

    【讨论】:

    • 请注意,这只是一个使用 FFT 的简单示例,绝不描述 FFT 本身的实际实现/算法。这更像是对 libgdx 的单元测试,如果没有更深入的傅立叶变换知识,这绝对是毫无价值的。正如页面上所说:“请随意使用此代码,但我要求至少在使用之前了解它的工作原理。”
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-05-11
    • 1970-01-01
    相关资源
    最近更新 更多