在 Python 中使用 Goertzel 过滤器提取相位

信息处理 过滤器
2022-02-08 06:58:52

我正在研究使用 Goertzel 滤波器来破译编码为单个频率的二进制消息相移是否可行。

这是我的结果:

在此处输入图像描述

我希望底线反映顶线中可见的不连续性。

这是生成第二行和第三行的代码:

samps_per_sec = SAMP_RATE
cycles_per_sec = CARRIER_FREQ_Hz
rad_per_sec = two_pi * cycles_per_sec
rad_per_samp = rad_per_sec / samps_per_sec

import cmath

def Goertzel( signal ):

  w = rad_per_samp
  exp_w = np.exp( 1j*w )
  exp_nw = np.exp( -1j*w ) # or exp_w.conj()

  _s = 0.
  __s = 0.

  out_s= []
  out_phase= []
  for i in range( 0, len( signal ) ):
    s = signal[i] + 2.*exp_w.real * _s - __s
    out_s += [s]

    y = s - exp_nw * _s

    phase = cmath.phase( y ) - i * w # subtract expected phase increment

    phasewrap = ( phase + np.pi) % two_pi - np.pi
    out_phase += [ phasewrap / np.pi ] # range -1 to +1

    __s = _s
    _s = s

  export_WAV_mono( "out_s.WAV", 0.001 * np.array( out_s ) )
  export_WAV_mono( "out_phase.WAV", 0.1 * np.array( out_phase ) )

似乎第三行只是跟踪第二行的阶段。在信号开始之前,相位实际上是随机的(我在信号中加入了非常低的微观噪声水平)。

当第二条线振幅下降到 0 时,相位会发生震颤,这是有道理的:当振幅接近零时,它会到处传播。

真正令人担忧的是,当波反转相位时,过滤器似乎无法记录这一点,就好像前向推进器关闭而后向推进器打开一样。

我看不出如何从这个过程中获得有用的结果。

这种方法是否存在根本性缺陷?我从 Goertzel 算法的 Wikipedia 页面复制了数学,并与我从各种文章中找到的几个实现进行了交叉检查。

我怀疑也许实现实际上是正确的,我只是有一个错误的期望。

这是完整的 Python 脚本:

#!/usr/local/bin/python

import numpy as np
from itertools import *
from array import array


# generator expression
# similar to list comprehensions, but create one value at a time
# def white_noise( amp=1. ):
#   return ( np.random.uniform(-amp, +amp) for _ in count(0) )

two_pi = 2. * np.pi

SAMP_RATE = 44100
SAMPS_PER_WAVE = 20
WAVES_PER_HALF_BIT = 10

CARRIER_FREQ_Hz = SAMP_RATE / float( SAMPS_PER_WAVE )

SAMPS_PER_HALF_BIT = SAMPS_PER_WAVE * WAVES_PER_HALF_BIT

import wave

def export_WAV_mono( filepath, samps ):
  # the largest possible signed 16-bit integer
  S16MAX = float( 2 ** 15 - 1 )
  samps_sint16 = S16MAX * samps.clip(-1.,+1.)
  data = array( 'h', samps_sint16.astype(int) )

  wv = wave.open( filepath, "w" )

  wv.setparams( (
    1,                  # nchannels
    2,                  # sampwidth in bytes
    44100,              # framerate
    len( data ),        # nframes
    'NONE',             # comptype
    'not compressed'    # compname
    ) )

  # this is the crucial step: the .tostring() method forces this into the string format that AIFC requires
  wv.writeframes( data.tostring() )

  wv.close()

# Fs -> 2 pi
# 1 -> 2 pi / Fs
# CARRIER_FREQ_Hz -> ?
#theta = ( two_pi / SAMP_RATE ) * CARRIER_FREQ_Hz

samps_per_sec = SAMP_RATE
cycles_per_sec = CARRIER_FREQ_Hz
rad_per_sec = two_pi * cycles_per_sec
rad_per_samp = rad_per_sec / samps_per_sec

import cmath

def Goertzel( signal ):
  # https://sites.google.com/site/hobbydebraj/home/goertzel-algorithm-dtmf-detection
  # https://dsp.stackexchange.com/questions/145/estimating-onset-time-of-a-tone-burst-in-noise/151#151

  w = rad_per_samp
  exp_w = np.exp( 1j*w )
  exp_nw = np.exp( -1j*w ) # or exp_w.conj()

  _s = 0.
  __s = 0.

  out_s= []
  out_phase= []
  for i in range( 0, len( signal ) ):
    s = signal[i] + 2.*exp_w.real * _s - __s
    out_s += [s]

    y = s - exp_nw * _s

    phase = cmath.phase( y ) - i * w # subtract expected phase increment

    phasewrap = ( phase + np.pi) % two_pi - np.pi
    out_phase += [ phasewrap / np.pi ] # range -1 to +1

    __s = _s
    _s = s

  export_WAV_mono( "out_s.WAV", 0.001 * np.array( out_s ) )
  export_WAV_mono( "out_phase.WAV", 0.1 * np.array( out_phase ) )


def main( ):
  binary_signal = [1,0,1] # ,1,0,1,0,1]

  BEFORE = 100
  AFTER = 100

  signal = [ ]

  PHASE_SHIFT = np.pi

  phase = 0.
  for b in binary_signal:
    phase += PHASE_SHIFT

    counter = 0
    while True:
      for i in range( 0, SAMPS_PER_HALF_BIT ):
        s = np.sin( phase )
        signal += [s]
        phase += rad_per_samp

      counter += 1
      if counter == 2:
        break

      if b:
        phase += PHASE_SHIFT



  print len( signal)
  noise_len = BEFORE+len(signal)+AFTER
  amp = 0.001
  sig = amp * ( 2. * np.random.random( noise_len ) - 1. )

  # SIGNAL_SAMP_OFFSET = 50
  for i in range( 0, len(signal) ):
    sig[BEFORE + i] += signal[ i ];

  export_WAV_mono( "signal.WAV", 0.25 * sig )


  Goertzel( sig )

if __name__ == '__main__':
  main( )

编辑:链接 估计噪声中音调的开始时间? http://asp.eurasipjournals.com/content/2012/1/56 http://www.mstarlabs.com/dsp/goertzel/goertzel.html

2个回答

我认为您可能正在使用十字头螺丝刀来驱动梅花头螺钉(您使用了错误的工具来解决问题)。

您要检测的是原始数据信号中的相位不连续性。这些是局部不连续性:它们发生一次,然后我们不再关心过去的不连续性。

Goertzel 算法的不同之处在于:它将计算整个数据序列的傅立叶系数(幅度和相位)——包括所有过去的相变。

您可以知道它应该是什么样子的唯一方法是,如果您首先知道调制正弦曲线的数据序列。

为了尝试自己理解这一点,我编写了scilab下面的代码(对不起,python 在星期五晚上太过分了!)。我要计算的是信号在每个瞬间的“真实”相位......正如你所看到的,相位有点......不可预测——因为数据是不可知的(嗯,随机的) .

图中:红圈是每个比特周期结束时的“真”相位,蓝线是每个时刻的“真”相位,红线和绿线是相位(以及相位的负) 承运人。

为此特定实现生成的位序列是:

1.  1. -1.  1. -1. -1. -1.  1. -1. -1.  

在此处输入图像描述

scialab脚本仅在下面。

bits = bool2s(rand(1,10,'normal')>0)*2-1;    
N = 32;
f0 = 2/N;
ONE = cos(2*%pi*f0*[0:N-1] + %pi/3);
data = kron(bits,ONE);

clear phi

for k=1:length(data)
    ft = sum(data(1:k).*exp(-%i*2*%pi*[0:k-1]*f0));    
    phi(k) = atan(imag(ft),real(ft));
    disp(idx)
end

clf
plot(phi)
boundries = [N:N:length(phi)];
plot(boundries,phi(boundries),'ro');
plot([0 N*10], [%pi/3 %pi/3],'r');
plot([0 N*10], -[%pi/3 %pi/3],'g');

不要使用可变长度的 Goertzel 滤波器,而是使用两个固定长度的 Goertzel 滤波器。您当前的滤波器在每个输出点都变长了一个点,这会不断改变频率响应。如果您可以解决稳定性问题,滑动 Goertzel 滤波器可能比在每个输入样本处重新计算固定长度滤波器所需的计算量更少。

使用两个固定长度的滤波器,每个长度为一位时间,对偏移一位时间,您可以检查两个复数 Goertzel(或 1-bin DFT)滤波器之间相位差的最大值,以标记相移两个过滤器之间点的输入。