为什么Swift中的FFT与Python中的FFT不同?
我正在尝试使用Accelerate框架将一些python numpy代码移植到Swift 我用python编写为什么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
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的数据打包
- 缩放傅里叶变换
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
的工作原理与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