使用苹果的Accelerate框架进行希尔伯特变换(解析信号)?

3
我在使用苹果的Accelerate Framework时,在C++中获得了一个与Matlab等效的Hilbert变换存在问题。我已经成功地让vDSP的FFT算法工作,并且在Paul R的帖子的帮助下,已经成功地得到了与Matlab相同的结果。
我阅读了这个Jordan的stackoverflow问题,并阅读了Matlab算法(在“算法”子标题下)。为了总结这个算法,分为三个阶段:
1. 对输入进行正向FFT。 2. 零反射频率和DC到Nyquist之间的双倍频率。 3. 对修改后的正向FFT输出进行反向FFT。
以下是Matlab和C++每个阶段的输出。示例使用以下数组:
- Matlab:`m = [1 2 3 4 5 6 7 8]`; - C++:`float m[] = {1,2,3,4,5,6,7,8}`。
  36.0000 + 0.0000i
  -4.0000 + 9.6569i
  -4.0000 + 4.0000i
  -4.0000 + 1.6569i
  -4.0000 + 0.0000i
  -4.0000 - 1.6569i
  -4.0000 - 4.0000i
  -4.0000 - 9.6569i

第二阶段:

  36.0000 + 0.0000i
  -8.0000 + 19.3137i
  -8.0000 + 8.0000i
  -8.0000 + 3.3137i
  -4.0000 + 0.0000i
   0.0000 + 0.0000i
   0.0000 + 0.0000i
   0.0000 + 0.0000i

第三阶段:

   1.0000 + 3.8284i
   2.0000 - 1.0000i
   3.0000 - 1.0000i
   4.0000 - 1.8284i
   5.0000 - 1.8284i
   6.0000 - 1.0000i
   7.0000 - 1.0000i
   8.0000 + 3.8284i

C++示例(使用苹果的加速框架)


阶段1:

Real: 36.0000, Imag: 0.0000
Real: -4.0000, Imag: 9.6569
Real: -4.0000, Imag: 4.0000
Real: -4.0000, Imag: 1.6569
Real: -4.0000, Imag: 0.0000

第二阶段:

Real: 36.0000, Imag: 0.0000
Real: -8.0000, Imag: 19.3137
Real: -8.0000, Imag: 8.0000
Real: -8.0000, Imag: 3.3137
Real: -4.0000, Imag: 0.0000

阶段三:

Real: -2.0000, Imag: -1.0000
Real:  2.0000, Imag: 3.0000
Real:  6.0000, Imag: 7.0000
Real: 10.0000, Imag: 11.0000

很明显,结果不同。我想要计算Matlab的“第三阶段”结果(或至少是虚部),但我不知道该怎么做,我已经尝试了我能想到的一切,但没有成功。我有一种感觉,这是因为在苹果加速版本中没有将反射频率清零,因为它们没有提供(由于是从DC和Nyquist之间的频率计算出来的)-所以我认为FFT只是将双频的共轭作为反射值,这是错误的。如果有人能帮我解决这个问题,我将非常感激。

代码


void hilbert(std::vector<float> &data, std::vector<float> &hilbertResult){

    // Set variable.
    dataSize_2 = data.size() * 0.5;

    // Clear and resize vectors.
    workspace.clear();
    hilbertResult.clear();

    workspace.resize(data.size()/2+1, 0.0f);
    hilbertResult.resize(data.size(), 0.0f);

    // Copy data into the hilbertResult vector.
    std::copy(data.begin(), data.end(), hilbertResult.begin());

    // Perform forward FFT.
    fft(hilbertResult, hilbertResult.size(), FFT_FORWARD);

    // Fill workspace with 1s and 2s (to double frequencies between DC and Nyquist).
    workspace.at(0) = workspace.at(dataSize_2) = 1.0f;

    for (i = 1; i < dataSize_2; i++)
        workspace.at(i) = 2.0f;

    // Multiply forward FFT output by workspace vector.
    for (i = 0; i < workspace.size(); i++) {
        hilbertResult.at(i*2)   *= workspace.at(i);
        hilbertResult.at(i*2+1) *= workspace.at(i);
    }

    // Perform inverse FFT.
    fft(hilbertResult, hilbertResult.size(), FFT_INVERSE);

    for (i = 0; i < hilbertResult.size()/2; i++)
        printf("Real: %.4f, Imag: %.4f\n", hilbertResult.at(i*2), hilbertResult.at(i*2+1));
}

以上代码是我用来获得“阶段3”C++(使用苹果的Accelerate Framework)结果的。前向fft的fft(..)方法执行vDSP fft例程,然后按比例缩放0.5并解压缩(根据Paul R的帖子)。当执行反向fft时,数据被打包,乘以2.0缩放,使用vDSP进行fft,最后乘以1 /(2 * N)缩放。


如果你能发布你的代码(只需要包含这里的转换部分),那么帮助你会相对容易一些。 - undefined
代码已附加到问题中。 :) - undefined
如果你正在对信号进行Hilbert变换,那么你可能想使用overlap-add。即每次以1024个样本的步长移动信号,并使用4096的FFT窗口。在进行FFT之前,使用包络(raised cosine或其他更好的方法)。调整你的FFT频率区间,然后进行逆FFT并将重叠的帧组合起来。一旦你搞定了,你可以轻松地改编Stefan的代码,并通过加速优化它。 - undefined
1个回答

7
据我所知,您的主要问题(由于您的代码示例未包含对vDSP的实际调用)是在第三个步骤中尝试使用实数到复数FFT例程,而这基本上是一个复杂到复杂的逆FFT(因为您的Matlab结果具有非零虚部)。以下是使用vDSP的简单C语言实现,与您的预期Matlab输出匹配(我使用更现代化的vDSP_DFT例程,通常应优先使用较旧的fft例程,但除此之外,这与您正在做的事情非常相似,并说明了需要进行实数到复数的正向变换,但进行复杂到复杂的逆变换):
#include <Accelerate/Accelerate.h>
#include <stdio.h>

int main(int argc, char *argv[]) {
  const vDSP_Length n = 8;
  vDSP_DFT_SetupD forward = vDSP_DFT_zrop_CreateSetupD(NULL, n, vDSP_DFT_FORWARD);
  vDSP_DFT_SetupD inverse = vDSP_DFT_zop_CreateSetupD(forward, n, vDSP_DFT_INVERSE);
  //  Look like a typo?  The real-to-complex DFT takes its input separated into
  //  the even- and odd-indexed elements.  Since the real signal is [ 1, 2, 3, ... ],
  //  signal[0] is 1, signal[2] is 3, and so on for the even indices.
  double even[n/2] = { 1, 3, 5, 7 };
  double odd[n/2] = { 2, 4, 6, 8 };
  double real[n] = { 0 };
  double imag[n] = { 0 };
  vDSP_DFT_ExecuteD(forward, even, odd, real, imag);
  //  At this point, we have the forward real-to-complex DFT, which agrees with
  //  MATLAB up to a factor of two.  Since we want to double all but DC and NY
  //  as part of the Hilbert transform anyway, I'm not going to bother to
  //  unscale the rest of the frequencies -- they're already the values that
  //  we really want.  So we just need to move NY into the "right place",
  //  and scale DC and NY by 0.5.  The reflection frequencies are already
  //  zeroed out because the real-to-complex DFT only writes to the first n/2
  //  elements of real and imag.
  real[0] *= 0.5; real[n/2] = 0.5*imag[0]; imag[0] = 0.0;
  printf("Stage 2:\n");
  for (int i=0; i<n; ++i) printf("%f%+fi\n", real[i], imag[i]);

  double hilbert[2*n];
  double *hilbertreal = &hilbert[0];
  double *hilbertimag = &hilbert[n];
  vDSP_DFT_ExecuteD(inverse, real, imag, hilbertreal, hilbertimag);
  //  Now we have the completed hilbert transform up to a scale factor of n.
  //  We can unscale using vDSP_vsmulD.
  double scale = 1.0/n; vDSP_vsmulD(hilbert, 1, &scale, hilbert, 1, 2*n);
  printf("Stage 3:\n");
  for (int i=0; i<n; ++i) printf("%f%+fi\n", hilbertreal[i], hilbertimag[i]);
  vDSP_DFT_DestroySetupD(inverse);
  vDSP_DFT_DestroySetupD(forward);
  return 0;
}

请注意,如果您已经构建了DFT设置并分配了存储空间,则计算非常简单;“真正的工作”只是:
  vDSP_DFT_ExecuteD(forward, even, odd, real, imag);
  real[0] *= 0.5; real[n/2] = 0.5*imag[0]; imag[0] = 0.0;
  vDSP_DFT_ExecuteD(inverse, real, imag, hilbertreal, hilbertimag);
  double scale = 1.0/n; vDSP_vsmulD(hilbert, 1, &scale, hilbert, 1, 2*n);

示例输出:

Stage 2:
36.000000+0.000000i
-8.000000+19.313708i
-8.000000+8.000000i
-8.000000+3.313708i
-4.000000+0.000000i
0.000000+0.000000i
0.000000+0.000000i
0.000000+0.000000i
Stage 3:
1.000000+3.828427i
2.000000-1.000000i
3.000000-1.000000i
4.000000-1.828427i
5.000000-1.828427i
6.000000-1.000000i
7.000000-1.000000i
8.000000+3.828427i

对于任何利用这个很棒的答案的人,只是一个小提示 - real和imag数组就足够了,没有必要使用额外的even、odd和hilbert数组。只需将real和imag的前半部分初始化为偶数和奇数内容,并将后半部分置零即可。 - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接