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

为什么Swift中的FFT与Python中的FFT不同?,python,swift,signal-processing,fft,accelerate-framework,Python,Swift,Signal Processing,Fft,Accelerate Framework,我正在尝试使用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 Fou

我正在尝试使用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
    }
RealFloat和ImaginaryFloat的打印方式如下:

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

有没有关于我应该做什么不同的想法?

我不擅长numpy,但根据我的经验,
fft
需要复杂的输入。那么它的等价物将是
vDSP\u fft\u zip
,而不是
vDSP\u fft\u 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
}

我不擅长numpy,但据我所知,
fft
需要复杂的输入。那么它的等价物将是
vDSP\u fft\u zip
,而不是
vDSP\u fft\u 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
}

关于vDSP_fft_zip和vDSP_fft_zrip之间的区别有点混淆。在您的情况下,最直接的影响是zrip输出是压缩的,需要按1/2的比例缩放,以等于标准FFT的正常数学输出

这在某种程度上隐藏在苹果的文档中:它没有出现在页面上,而是出现在页面中。然而,从指南中还不清楚在每种情况下输入数据是如何准备的,而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结构的实数组中,虚数组被调零

输出非常复杂,需要两倍于输入的内存。然而,这种输出大部分是对称的——这就是zrip的压缩输出能够在一半内存中表示相同信息的方式


vDSP_fft_zrip 输入通过DSPSplitComplex.init(来自InputArray:)或使用vDSP_ctoz分布在DSPSplitComplex上

fromInputArray:方法的作用与ctoz相同,但它是一种更简单/更安全的方法-不必使用UnsafeRawPointer和bindMemory

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

缩放:结果是标准数学FFT的2倍,因此需要进行缩放


见:

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

关于vDSP_fft_zip和vDSP_fft_zrip之间的区别有点混淆。在您的情况下,最直接的影响是zrip输出是压缩的,需要按1/2的比例缩放,以等于标准FFT的正常数学输出

这在某种程度上隐藏在苹果的文档中:它没有出现在页面上,而是出现在页面中。然而,从指南中还不清楚在每种情况下输入数据是如何准备的,而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结构的实数组中,虚数组被调零

输出非常复杂,需要两倍于输入的内存。然而,这种输出大部分是对称的——这就是zrip的压缩输出能够在一半内存中表示相同信息的方式


vDSP_fft_zrip 输入通过DSPSplitComplex.init(来自InputArray:)或使用vDSP_ctoz分布在DSPSplitComplex上

fromInputArray:方法的作用与ctoz相同,但它是一种更简单/更安全的方法-不必使用UnsafeRawPointer和bindMemory

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

缩放:结果是标准数学FFT的2倍,因此需要进行缩放


见:

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

这很有效!vDSP_fft_zrip是fft.rfft的等价物吗?不完全是。我们预计
vDSP_fft_zrip
的工作原理与
vDSP_fft_zip
的工作原理相同,所有虚部均为0.0,但
vDSP_fft_zrip
的工作原理与预期不同。似乎
vDSP_fft_zrip
fft.rfft
使用了不同的方法来优化实数,因此很难得到完全相同的结果。有趣的是,看起来fft.rfft只保留日志2的前半部分加上一个值。所以对于4,它保持前3,对于8,它保持前5,对于16,它保持前9,等等,因为后面的数字有点相互抵消。这里是scipy的一个注释:“当DFT计算为纯实输入时,输出是厄米对称的,即负频率项只是相应正频率项的复共轭项,因此负频率项是冗余的。”是的<代码>vDSP_fft_zrip对其结果进行类似的截断。但是他们有点不同。也许需要一些缩放和重新排列,但我现在还不能精确地说。一些fft专家可能知道如何正确使用
vDSP\u fft\u zrip
。它应该是更有效的,当所有的现实。这是工作!vDSP_fft_zrip是fft.rfft的等价物吗?不完全是。我们预计
vDSP_fft_zrip
的工作原理与
vDSP_fft_zip
的工作原理相同,所有虚部均为0.0,但
vDSP_fft_zrip
的工作原理与预期不同。似乎
vDSP_fft_zrip
fft.rfft
使用了不同的方法来优化实数,因此很难得到完全相同的结果。有趣的是,看起来fft.rfft只保留日志2的前半部分加上一个值。所以对于4,它保持前3,对于8,它保持前5,对于16,它保持前9,等等,因为后面的数字有点相互抵消。这里是scipy的一个注释“当DFT是为纯实输入计算时,输出是厄米对称的,即neg