How to find out if an array is a sine wave?

I work with an accelerometer and collect data from the last few seconds. The motion that I want to detect can be represented as a sine wave in terms of the values ​​that I receive from the motion sensor. Therefore, to make sure that I want to check whether the data returned from the sensor is really a sine wave.

What I want to avoid is to manually compare each value in the array and make a decision.

I am wondering if there is an efficient way that could tell if my array represents a sine wave or not.

+4
source share
1 answer

As @NeilForrester commentator notes, FFTs are a way to do this. Writing your own effective FFT is not easy, but the Accelerateframework of vDSP routines provides a direct way to do this if you use Objective-C - not so easy in Swift due to the use of parameters UnsafePointerand UnsafeMutablePointer. Here is a simple example of using FFT quickly.

import Foundation
import Accelerate

public struct GFFT {
    let size: Int
    let halfSize: Int
    let log2n: vDSP_Length
    let twoOverSize: [Float]
    var weights: FFTSetup

    init?(size: Int) {
        self.size = size
        self.log2n = vDSP_Length(log2(Float(size)))
        guard let weights = vDSP_create_fftsetup(log2n, FFTRadix(kFFTRadix2)) else {
            print("Aargh in GFFT.fft - weights failed")
            return nil
        }
        self.halfSize = size / 2
        self.twoOverSize = [2 / Float(size)]
        self.weights = weights
    }

    public func forward(realArray: [Float]) -> (magnitude: [Float], phase: [Float]) {
        assert(realArray.count == self.size, "Aargh in GFFT.forward - size mismatch")
        var real = realArray // copy into var
        var imag = GFFT.zeros(size)
        var magnitudesSquared = GFFT.zeros(self.halfSize)
        var magnitudes = GFFT.zeros(self.halfSize)
        var normalizedMagnitudes = GFFT.zeros(self.halfSize)
        var phases = GFFT.zeros(self.halfSize)

        var splitComplex = DSPSplitComplex(realp: &real, imagp: &imag)

        vDSP_fft_zip(self.weights, &splitComplex, 1, self.log2n, FFTDirection(FFT_FORWARD))

        vDSP_zvmags(&splitComplex, 1, &magnitudesSquared, 1, vDSP_Length(self.halfSize))
        vvsqrtf(&magnitudes, &magnitudesSquared, [Int32(self.halfSize)])

        vDSP_zvphas(&splitComplex, 1, &phases, 1, vDSP_Length(self.halfSize))

        vDSP_vsmul(&magnitudes, 1, self.twoOverSize, &normalizedMagnitudes, 1, vDSP_Length(self.halfSize))

        // you may choose to return magnitudesSquared, for the power
        // magnitudes for the scaled amplitudes or 
        // normalizedMagnitudes for, well, normalised magnitude.
        return (normalizedMagnitudes, phases)
    }
    private static func zeros(_ n: Int) -> [Float] { return [Float](repeating: 0, count: n) }
}

let testInput = (0 ..< 512).map {
    return sin(Float($0))
}
if let fft = GFFT(size: testInput.count) {
    let (freq, phase) = fft.forward(realArray: testInput)
    freq.map({$0})
}

Playground Output:

FFT graph

As for what you are testing, this will depend on the actual outputs you get, so I would experiment with what the actual data gives, but your test should be something like this:

  • find the average value of the amplitudes
  • find the maximum amplitude
  • check that the ratio of two (max / average) is large.
  • check that the maximum index is not close to zero (or most likely a DC signal)
  • , ( ) .
+8

Source: https://habr.com/ru/post/1668849/


All Articles