为什么 Swift 中的 FFT 与 Python 中的不同?

Why is FFT different in Swift than in Python?

我正在尝试使用 Accelerate 框架将一些 python numpy 代码移植到 Swift。

在python我写

import numpy as np
frames = np.array([1.0, 2.0, 3.0, 4.0])
fftArray = np.fft.fft(frames, len(frames))
print(fftArray)

输出为:

[10.+0.j -2.+2.j -2.+0.j -2.-2.j]

所以在 Swift 中,我正在尝试像这样计算 FFT:

import Foundation
import Accelerate

    func fftAnalyzer(frameOfSamples: [Float]) {
        // As above, frameOfSamples = [1.0, 2.0, 3.0, 4.0]            

        let analysisBuffer = frameOfSamples
        let frameCount = frameOfSamples.count

        var reals = [Float]()
        var imags = [Float]()
        for (idx, element) in analysisBuffer.enumerated() {
            if idx % 2 == 0 {
                reals.append(element)
            } else {
                imags.append(element)
            }
        }
        var complexBuffer = DSPSplitComplex(realp: UnsafeMutablePointer(mutating: reals), imagp: UnsafeMutablePointer(mutating: imags))

        let log2Size = Int(log2f(Float(frameCount)))

        guard let fftSetup = vDSP_create_fftsetup(vDSP_Length(log2Size), Int32(kFFTRadix2)) else {
            return []
        }

        // Perform a forward FFT
        vDSP_fft_zrip(fftSetup, &(complexBuffer), 1, UInt(log2Size), Int32(FFT_FORWARD))

        let realFloats = Array(UnsafeBufferPointer(start: complexBuffer.realp, count: Int(frameCount)))
        let imaginaryFloats = Array(UnsafeBufferPointer(start: complexBuffer.imagp, count: Int(frameCount)))

        print(realFloats)
        print(imaginaryFloats)

        // Release the setup
        vDSP_destroy_fftsetup(fftSetup)

        return realFloats
    }

realFloats 和 imaginaryFloats 打印如下:

[20.0, -4.0, 0.0, 0.0]
[-4.0, 4.0, 0.0, 0.0]

关于我应该做些什么的任何想法?

我不擅长 numpy,但根据 the docfft 需要复杂的输入。那么它的等价物将是 vDSP_fft_zip,而不是 vDSP_fft_zrip

并且您的代码导致缓冲区溢出或可能导致悬空指针,所有这些问题都已修复我明白了:

func fftAnalyzer(frameOfSamples: [Float]) -> [Float] {
    // As above, frameOfSamples = [1.0, 2.0, 3.0, 4.0]

    let frameCount = frameOfSamples.count

    let reals = UnsafeMutableBufferPointer<Float>.allocate(capacity: frameCount)
    defer {reals.deallocate()}
    let imags =  UnsafeMutableBufferPointer<Float>.allocate(capacity: frameCount)
    defer {imags.deallocate()}
    _ = reals.initialize(from: frameOfSamples)
    imags.initialize(repeating: 0.0)
    var complexBuffer = DSPSplitComplex(realp: reals.baseAddress!, imagp: imags.baseAddress!)

    let log2Size = Int(log2(Float(frameCount)))
    print(log2Size)

    guard let fftSetup = vDSP_create_fftsetup(vDSP_Length(log2Size), FFTRadix(kFFTRadix2)) else {
        return []
    }
    defer {vDSP_destroy_fftsetup(fftSetup)}

    // Perform a forward FFT
    vDSP_fft_zip(fftSetup, &complexBuffer, 1, vDSP_Length(log2Size), FFTDirection(FFT_FORWARD))

    let realFloats = Array(reals)
    let imaginaryFloats = Array(imags)

    print(realFloats)
    print(imaginaryFloats)

    return realFloats
}

印刷

[10.0, -2.0, -2.0, -2.0]
[0.0, 2.0, 0.0, -2.0]

vDSP_fft_zip 和 vDSP_fft_zrip 之间的区别有些混乱。对您的情况最直接的影响是 zrip 输出被打包,需要缩放 1/2 以等于标准 FFT 的正常数学输出。

这在某种程度上隐藏在 Apple 文档中:而不是出现在 vDSP_fft_zrip, it's in the vDSP Programming Guide 的页面上。但是,指南中仍然不清楚在每种情况下如何准备输入数据 - 而 OOPer 的回答对此有很大帮助。

import Foundation
import Accelerate

var input: [Float] = [1.0, 2.0, 3.0, 4.0]

let length = input.count
let log2n = log2(Float(length))
let fftSetup = vDSP_create_fftsetup(vDSP_Length(log2n), FFTRadix(kFFTRadix2))!

print("--vDSP_fft_zip---")
var real = input
var imag = [Float](repeating: 0.0, count: length)
var splitComplexBuffer = DSPSplitComplex(realp: &real, imagp: &imag)

vDSP_fft_zip(fftSetup, &splitComplexBuffer, 1, vDSP_Length(log2n), FFTDirection(FFT_FORWARD))

print("real: \(real)")
print("imag: \(imag)")
print("-----------------")
print("DC      : real[0]")
print("Nyquist : real[2]")
print()
print()

print("--vDSP_fft_zrip--")
// only need half as many elements because output will be packed
let halfLength = length/2
real = [Float](repeating: 0.0, count: halfLength)
imag = [Float](repeating: 0.0, count: halfLength)

// input is alternated across the real and imaginary arrays of the DSPSplitComplex structure
splitComplexBuffer = DSPSplitComplex(fromInputArray: input, realParts: &real, imaginaryParts: &imag)

// even though there are 2 real and 2 imaginary output elements, we still need to ask the fft to process 4 input samples
vDSP_fft_zrip(fftSetup, &splitComplexBuffer, 1, vDSP_Length(log2n), FFTDirection(FFT_FORWARD))

// zrip results are 2x the standard FFT and need to be scaled
var scaleFactor = Float(1.0/2.0)
vDSP_vsmul(splitComplexBuffer.realp, 1, &scaleFactor, splitComplexBuffer.realp, 1, vDSP_Length(halfLength))
vDSP_vsmul(splitComplexBuffer.imagp, 1, &scaleFactor, splitComplexBuffer.imagp, 1, vDSP_Length(halfLength))

print("real: \(real)")
print("imag: \(imag)")
print("-----------------")
print("DC      : real[0]")
print("Nyquist : imag[0]")

vDSP_destroy_fftsetup(fftSetup)

印刷:

--vDSP_fft_zip---
real: [10.0, -2.0, -2.0, -2.0]
imag: [0.0, 2.0, 0.0, -2.0]
-----------------
DC      : real[0]
Nyquist : real[2]


--vDSP_fft_zrip--
real: [10.0, -2.0]
imag: [-2.0, 2.0]
-----------------
DC      : real[0]
Nyquist : imag[0]


vDSP_fft_zip

输入放入DSPSplitComplex结构的实数组,虚数组清零

Output 很复杂,需要双倍的输入内存。然而,大部分输出是对称的——这就是 zrip 的打包输出能够在一半内存中表示相同信息的方式。


vDSP_fft_zrip

Input 使用 DSPSplitComplex.init(fromInputArray: ) 或使用 vDSP_ctoz.

分布在 DSPSplitComplex 中

fromInputArray: 方法与 ctoz 做同样​​的事情,但它是一种 easier/safer 方法 - 不必使用 UnsafeRawPointer 和 bindMemory。

输出 是复杂的。打包时,输出需要与输入相同的内存量。

缩放: 结果是标准数学 FFT 的 2 倍,需要这样缩放。


见: vDSP Programming Guide

  • 真实 FFT 的数据打包
  • 缩放傅里叶变换