8、滤波器 【入门软件无线电(SDR)】PySDR:使用 Python 的 SDR 和 DSP 指南

您所在的位置:网站首页 低通滤波器原理图符号 8、滤波器 【入门软件无线电(SDR)】PySDR:使用 Python 的 SDR 和 DSP 指南

8、滤波器 【入门软件无线电(SDR)】PySDR:使用 Python 的 SDR 和 DSP 指南

2023-04-11 13:19| 来源: 网络整理| 查看: 265

> 在本章中,我们将学习使用Python的数字滤波器。我们介绍了滤波器的类型(FIR/IIR和低通/高通/带通/带阻),滤波器如何以数字方式表示,以及如何设计它们。最后,我们将介绍脉冲整形,我们将在脉冲整形一章中进一步探讨。

滤波器基础知识

滤波器用于许多学科。例如,图像处理大量使用 2D 滤镜,其中输入和输出是图像。在DSP中,滤波器主要用于: 1、分离已组合的信号(例如,提取所需的信号) 2、接收信号后消除多余噪声 3、恢复以某种方式失真的信号(例如,音频均衡器是一个滤波器) 您可能认为我们只关心数字滤波器;毕竟,本教程探讨了DSP。但是,重要的是要知道许多滤波器都是模拟的,例如SDR中放置在接收侧模数转换器(ADC)之前的滤波器。下图将模拟滤波电路的原理图与数字滤波算法的流程图并置。

在这里插入图片描述

滤波器有四种基本类型:低通、高通、带通和带阻。每种类型都会修改信号以专注于其中的不同频率范围。下图演示了如何针对每种类型过滤信号中的频率,首先仅显示正频率(更易于理解),然后还包括负频率。

在这里插入图片描述在这里插入图片描述

每个滤波器都允许某些频率从信号中保留,同时阻止其他频率。滤波器允许通过的频率范围称为“通带”,“阻带”是指被阻挡的频率范围。在低通滤波器的情况下,它通过低频并阻挡高频,因此0 Hz将始终在通带中。对于高通和带通滤波器,0 Hz将始终处于阻带中。 不要将这些滤波类型与滤波算法实现(例如,IIR 与 FIR)混淆。到目前为止,最常见的类型是低通滤波器(LPF),因为我们经常在基带上表示信号。LPF 允许我们过滤掉信号“周围”的所有内容,消除多余的噪声和其他信号。

滤波器表示

对于我们将看到的大多数滤波器(称为FIR或有限脉冲响应类型滤波器),我们可以用单个浮点数组表示滤波器本身。对于频域对称的滤波器,这些浮点数将是真实的(相对于复数)。我们将这种浮点数组称为“过滤器抽头”(filter taps)。我们经常使用 h 作为过滤器抽头的符号。下面是一组过滤器抽头的示例,它们定义了一个过滤器:

h = [ 9.92977939e-04 1.08410297e-03 8.51595307e-04 1.64604862e-04 -1.01714338e-03 -2.46268845e-03 -3.58236429e-03 -3.55412543e-03 -1.68583512e-03 2.10562324e-03 6.93100252e-03 1.09302641e-02 1.17766532e-02 7.60955496e-03 -1.90555639e-03 -1.48306750e-02 -2.69313236e-02 -3.25659606e-02 -2.63400086e-02 -5.04184562e-03 3.08099470e-02 7.64264738e-02 1.23536693e-01 1.62377258e-01 1.84320776e-01 1.84320776e-01 1.62377258e-01 1.23536693e-01 7.64264738e-02 3.08099470e-02 -5.04184562e-03 -2.63400086e-02 -3.25659606e-02 -2.69313236e-02 -1.48306750e-02 -1.90555639e-03 7.60955496e-03 1.17766532e-02 1.09302641e-02 6.93100252e-03 2.10562324e-03 -1.68583512e-03 -3.55412543e-03 -3.58236429e-03 -2.46268845e-03 -1.01714338e-03 1.64604862e-04 8.51595307e-04 1.08410297e-03 9.92977939e-04]使用案例

为了了解如何使用滤波器,让我们看一个例子,我们将SDR调谐到现有信号的频率,并希望将其与其他信号隔离。请记住,我们告诉SDR调谐到哪个频率,但SDR捕获的样本位于基带,这意味着信号将显示为以0 Hz为中心。我们必须跟踪我们告诉SDR调谐到哪个频率。以下是我们可能会收到的内容:

在这里插入图片描述

由于我们的信号已经以直流(0 Hz)为中心,因此我们知道我们需要一个低通滤波器。我们必须选择一个“截止频率”(又称转折频率),这将决定通带何时过渡到阻带。截止频率将始终以 Hz 为单位。在此示例中,3 kHz 似乎是一个不错的值:

在这里插入图片描述

但是,与大多数低通滤波器的工作方式一样,负频率边界也将为-3 kHz。也就是说,它在DC周围是对称的(稍后你会看到为什么)。我们的截止频率如下所示(通带是介于两者之间的区域):

在这里插入图片描述

创建并应用截止频率为 3 kHz 的滤波器后,我们现在有:

在这里插入图片描述

这个滤波后的信号看起来会令人困惑,您应该记得我们的本底噪声位于-65 dB左右的绿线。尽管我们仍然可以看到以10 kHz为中心的干扰信号,但我们严重降低了该信号的功率。它现在低于本底噪音!我们还消除了阻带中存在的大部分噪音。 除了截止频率外,我们的低通滤波器的另一个主要参数称为“过渡宽度”。过渡宽度(也以Hz为单位)指示滤波器在通带和阻带之间必须多快,因为瞬时转换是不可能的。 让我们可视化过渡宽度。在下图中,绿线表示通带和阻带之间转换的理想响应,其转换宽度基本上为零。红线显示了实际滤波器的结果,该滤波器具有一定的纹波和一定的过渡宽度.

在这里插入图片描述

您可能想知道为什么我们不将过渡宽度设置为尽可能小。原因主要是较小的过渡宽度会导致更多的抽头,而更多的抽头意味着更多的计算 - 我们很快就会看到原因。一个 50 次抽头的过滤器可以使用树莓派 上 1% 的 CPU 全天运行。同时,一个 50,000 次抽头过滤器会导致您的 CPU 爆炸!通常我们使用过滤器设计器工具,然后查看它输出的抽头数,如果太多(例如,超过 100 个),我们增加过渡宽度。当然,这完全取决于运行过滤器的应用程序和硬件。 在上面的滤波示例中,我们使用了 3 kHz 的截止和 1 kHz 的过渡宽度(仅查看这些屏幕截图很难实际看到过渡宽度)。由此产生的过滤器有 77 个抽头。

返回到滤波器表示形式。尽管我们可能会显示滤波器的抽头列表,但我们通常在频域中直观地表示滤波器。我们称之为滤波器的“频率响应”,它向我们展示了滤波器在频率上的行为。这是我们刚刚使用的滤波器的频率响应:

在这里插入图片描述

请注意,我在这里展示的不是信号,它只是滤波器的频域表示。一开始可能有点难以理解,但是当我们查看示例和代码时,你会理解它。

给定滤波器还具有时域表示;它被称为滤波器的“脉冲响应”,因为如果你把一个脉冲放在滤波器上,它就是你在时域中看到的。对于FIR型滤波器,脉冲响应只是抽头本身。对于我们之前使用的 77 抽头过滤器,抽头是:

h = [-0.00025604525581002235, 0.00013669139298144728, 0.0005385575350373983,..., -0.00025604525581002235]

尽管我们还没有进入滤波器设计,但以下是生成该滤波器的 Python 代码:

import numpy as np from scipy import signal import matplotlib.pyplot as plt num_taps = 51 # it helps to use an odd number of taps cut_off = 3000 # Hz sample_rate = 32000 # Hz # create our low pass filter h = signal.firwin(num_taps, cut_off, nyq=sample_rate/2) # plot the impulse response plt.plot(h, '.-') plt.show()

简单地绘制这个浮点数组,就可以得到滤波器的脉冲响应:

在这里插入图片描述

下面是用于产生频率响应的代码,如前所述。这有点复杂,因为我们必须创建频率的x轴阵列。

# plot the frequency response H = np.abs(np.fft.fft(h, 1024)) # take the 1024-point FFT and magnitude H = np.fft.fftshift(H) # make 0 Hz in the center w = np.linspace(-sample_rate/2, sample_rate/2, len(H)) # x axis plt.plot(w, H, '.-') plt.show()实数与复数过滤器

我给你看的滤波器有实数的抽头,抽头也可能是复数。抽头是实数还是复数,不必与您通过它的信号相匹配,即,您可以将复杂的信号通过带有实数抽头的滤波器,反之亦然。当抽头为实数时,滤波器的频率响应将在直流(0 Hz)周围对称。通常,当我们需要不对称时,我们会使用复数的抽头,这种情况不会经常发生。

在这里插入图片描述

作为复数抽头的示例,让我们回到滤波用例,只是这次我们希望接收其他干扰信号(无需重新调谐无线电)。这意味着我们想要一个带通滤波器,而不是一个对称的滤波器。我们只想将(也称为“通过”)频率保持在 7 kHz 到 13 kHz 之间(我们不想同时传递 -13 kHz 到 -7 kHz):

在这里插入图片描述

设计这种滤波器的一种方法是制作截止频率为3 kHz的低通滤波器,然后将其进行频移。请记住,我们可以通过将其乘以

e^{j2\pi f_0t}

来频移 x(t)(时域)。在这种情况下,

在这里插入图片描述

应该是 10 kHz,这会将我们的滤波器上移 10 kHz。回想一下,在我们的 Python 代码中, h 是低通滤波器的滤波器抽头。为了创建我们的带通滤波器,我们只需要将这些抽头乘以

在这里插入图片描述

,尽管它涉及创建一个向量来表示基于我们的采样周期的时间(采样率的倒数):

# (h was found using the first code snippet) # Shift the filter in frequency by multiplying by exp(j*2*pi*f0*t) f0 = 10e3 # amount we will shift Ts = 1.0/sample_rate # sample period t = np.arange(0.0, Ts*len(h), Ts) # time vector. args are (start, stop, step) exponential = np.exp(2j*np.pi*f0*t) # this is essentially a complex sine wave h_band_pass = h * exponential # do the shift # plot impulse response plt.figure('impulse') plt.plot(np.real(h_band_pass), '.-') plt.plot(np.imag(h_band_pass), '.-') plt.legend(['real', 'imag'], loc=1) # plot the frequency response H = np.abs(np.fft.fft(h_band_pass, 1024)) # take the 1024-point FFT and magnitude H = np.fft.fftshift(H) # make 0 Hz in the center w = np.linspace(-sample_rate/2, sample_rate/2, len(H)) # x axis plt.figure('freq') plt.plot(w, H, '.-') plt.xlabel('Frequency [Hz]') plt.show()

脉冲响应和频率响应的曲线如下所示:

在这里插入图片描述

因为我们的滤波器在0 Hz左右不是对称的,所以它必须使用复数的抽头。因此,我们需要两条线来绘制这些复数抽头。我们在上面的左图中看到的仍然是脉冲响应。我们的频率响应图真正验证了我们创建了我们希望的滤波器,它将滤除以10 kHz为中心的信号之外的所有内容。再一次,请记住,上面的图不是实际信号:它只是滤波器的表示。掌握起来可能非常混乱,因为当您将滤波器应用于信号并在频域中绘制输出时,在许多情况下,它看起来与滤波器的频率响应本身大致相同。

如果这一小节增加了混乱,请不要担心,无论如何,99% 的时间您都会处理带有实数抽头的简单低通滤波器。

滤波器实现

我们不会太深入地探讨滤波器的实现。相反,更专注于滤波器设计(无论如何,您可以在任何编程语言中找到现成的实现)。现在,这里有一个要点:要使用FIR滤波器滤除信号,您只需将脉冲响应(抽头阵列)与输入信号进行卷积即可。(别担心,后面的部分将解释卷积。在离散世界中,我们使用离散卷积(下面的示例)。标记为 b 的三角形是水龙头。在流程图中,三角形上方标记为

z^{-1}

的方块表示延迟一个时间步长。

在这里插入图片描述

也许能够理解为什么我们现在根据过滤器本身的实现方式称它们为“抽头/水龙头(tap)”。

FIR vs IIR

数字滤波器主要分为两类:FIR 和 IIR 1、Finite impulse response (FIR) 有限脉冲响应 2、Infinite impulse response (IIR) 无限脉冲响应 我们不会深入探讨这个理论,但现在请记住:FIR滤波器更容易设计,如果你使用足够的抽头,可以做任何你想做的事情。IIR滤波器更复杂,可能不稳定,但它们更有效(为给定滤波器使用更少的CPU和内存)。如果有人只是给你一个抽头列表,则假定它们是FIR滤波器的抽头。如果他们开始提到“极点(poles)”,他们正在谈论 IIR 滤波器。我们将在本教程坚持使用FIR滤波器。 下面是一个频率响应示例,比较了执行几乎完全相同滤波的FIR和IIR滤波器;它们具有相似的过渡宽度,正如我们所知,这将决定需要多少个抽头。FIR 滤波器有 50 个抽头,IIR 滤波器有 12 个极,就所需的计算而言,这就像有 12 个抽头。

在这里插入图片描述

FIR滤波器需要比IIR更多的计算资源来执行大致相同的滤波操作。 以下是您以前可能使用过的 FIR 和 IIR 滤波器的一些实际示例。

卷积(Convolution)

我们将绕道介绍卷积运算符。如果您已经熟悉本节,请随时跳过它

将两个信号相加是将两个信号组合成一个信号的一种方法。在频域一章中,我们探讨了将两个信号相加时线性度属性如何应用。卷积是将两个信号组合成一个信号的另一种方法,但它与简单地添加它们非常不同。两个信号的卷积就像一个信号在另一个信号上滑动并积分。如果您熟悉该操作,则它与互相关非常相似。事实上,在许多情况下,它等同于互相关。

我相信卷积运算最好通过示例来学习。在第一个例子中,我们将两个方形脉冲卷积在一起

在这里插入图片描述

因为它只是一个滑动积分,所以结果是一个三角形,在两个方形脉冲完美排列的点处具有最大值。让我们看看如果我们将方形脉冲与三角脉冲卷积会发生什么:

在这里插入图片描述

在这两个示例中,我们有两个输入信号(一个红色,一个蓝色),然后显示卷积的输出。您可以看到,输出是两个信号的积分,因为一个信号在另一个信号上滑动。由于这种“滑动”性质,输出的长度实际上比输入长。如果一个信号是 M 样本,另一个信号是 N 样本,则两者的卷积可以产生 N+M-1 样本。但是, numpy.convolve() 等函数可以指定是需要整个输出( max(M, N) 样本)还是仅需要信号完全重叠的样本(如果您好奇,请指定 max(M, N) - min(M, N) + 1 )。无需纠结于这些细节。只要知道卷积输出的长度不仅仅是输入的长度。

那么为什么卷积在DSP中很重要呢?首先,要滤除信号,我们可以简单地获取该滤波器的脉冲响应并将其与信号卷积。FIR 滤波只是一种卷积操作。

在这里插入图片描述

这可能会令人困惑,因为前面我们提到卷积接收两个信号并输出一个信号。我们可以将脉冲响应视为信号,卷积毕竟是一个数学运算符,它在两个一维数组上运行。如果其中一个一维阵列是滤波器的脉冲响应,则另一个一维阵列可以是输入信号的一部分,输出将是输入的滤波版本。

让我们看另一个示例来帮助此理解。在下面的示例中,三角形将代表滤波器的脉冲响应,绿色信号表示我们被滤波的信号。

在这里插入图片描述

红色输出是滤波信号。 问题:三角形是什么类型的过滤器? 它平滑了绿色信号的高频分量(即正方形的急剧过渡),因此它充当低通滤波器。

现在我们开始理解卷积,我将介绍它的数学方程。星号 (*) 通常用作卷积的符号:

在这里插入图片描述

在上面的表达式中, g(t) 是翻转并在 f(t) 上滑动的信号或输入,但 g(t) 和 f(t) 可以交换,它仍然是相同的表达式。通常,较短的数组将用作 g(t) 。卷积等于互相关,定义为 ,当 g(t) 是对称的时,即当围绕原点翻转时它不会改变。

Python 中的过滤器设计

现在我们将考虑一种在 Python 中自己设计 FIR 滤波器的方法。虽然设计滤波器的方法有很多,但我们将使用从频域开始并反向工作以找到脉冲响应的方法。最终,这就是我们的滤波器(通过其抽头)的表示方式。 首先创建所需频率响应的矢量。让我们设计一个任意形状的低通滤波器,如下所示:

在这里插入图片描述

用于创建此滤波器的代码相当简单:

import numpy as np import matplotlib.pyplot as plt H = np.hstack((np.zeros(20), np.arange(10)/10, np.zeros(20))) w = np.linspace(-0.5, 0.5, 50) plt.plot(w, H, '.-') plt.show()

我们的最终目标是找到这个滤波器的抽头,以便我们可以实际使用它。给定频率响应,我们如何获得抽头?那么,我们如何从频域转换回时域呢?反向FFT(IFFT)!回想一下,IFFT函数与FFT函数几乎完全相同。我们还需要在IFFT之前IFFT移位我们想要的频率响应,然后在IFFT之后我们需要另一个IFF移位(不,它们不会自行抵消,你可以试试)。这个过程可能看起来令人困惑。请记住,您始终应该在FFT之后进行FFT移位,在IFFT之后进行IFF移位。

h = np.fft.ifftshift(np.fft.ifft(np.fft.ifftshift(H))) plt.plot(np.real(h)) plt.plot(np.imag(h)) plt.legend(['real','imag'], loc=1) plt.show()

在这里插入图片描述

我们将使用上面显示的这些抽头作为我们的滤波器。我们知道脉冲响应正在绘制抽头,所以我们在上面看到的是我们的脉冲响应。让我们取抽头的FFT,看看频域的实际外观。我们将做一个 1,024 点的 FFT 以获得高分辨率:

H_fft = np.fft.fftshift(np.abs(np.fft.fft(h, 1024))) plt.plot(H_fft) plt.show()

在这里插入图片描述

看看频率响应怎么不是很直...它与我们的原版不太匹配,如果你还记得我们最初想要制作滤波器的形状。一个很大的原因是因为我们的脉冲响应没有衰减,即左右两侧没有达到零(加窗,第2章节有)。我们有两个选项可以允许它衰减到零: 选项1:我们“窗口”当前的脉冲响应,使其在两侧衰减为0。它涉及将我们的脉冲响应乘以一个从零开始和结束的“窗口函数”。

# After creating h using the previous code, create and apply the window window = np.hamming(len(h)) h = h * window

在这里插入图片描述

选项2:我们使用更多的点重新生成脉冲响应,以便它有时间衰减。我们需要为原始频域阵列添加分辨率(称为插值)。

在这里插入图片描述在这里插入图片描述

两种选择都奏效了。你会选择哪一个?第二种方法导致更多的抽头,但第一种方法导致频率响应不是很尖锐,下降沿不是很陡峭。设计滤波器的方法有很多种,每种方法都有自己的权衡取舍。许多人认为滤波器设计是一门艺术。

脉冲整形简介

我们将简要介绍DSP中一个非常有趣的主题,脉冲整形。稍后我们将抽出一章节中深入讨论该主题 。值得一提的是滤波,因为脉冲整形最终是一种滤波器,用于特定目的,具有特殊性能。 正如我们所了解的,数字信号使用符号来表示一个或多个信息位。我们使用数字调制方案,如ASK,PSK,QAM,FSK等,来调制载波,以便可以无线发送信息。当我们在数字调制章节中模拟QPSK时,我们只模拟每个符号的一个样本,即我们创建的每个复数都是星座上的一个点 - 它是一个符号。在实践中,我们通常为每个符号生成多个样本,原因与过滤有关。

我们使用滤波器来制作符号的“形状”,因为时域中的形状会改变频域中的形状。频域告诉我们信号将使用多少频谱/带宽,我们通常希望将其最小化。重要的是要了解,当我们调制载波时,基带符号的频谱特性(频域)不会改变;它只是在形状保持不变的情况下将基带的频率向上移动,这意味着它使用的带宽量保持不变。当我们每个符号使用 1 个样本时,就像传输方波脉冲一样。事实上,BPSK 每个符号使用 1 个样本只是随机 1 和 -1 的方波:

在这里插入图片描述

正如我们所了解到的,方波脉冲效率不高,因为它们使用了过量的频谱:

在这里插入图片描述

因此,我们要做的是“脉冲形状”这些块状符号,以便它们在频域中占用更少的带宽。我们使用低通滤波器来“脉冲形状”,因为它丢弃了我们符号的高频分量。下面显示了应用脉冲整形滤波器之前和之后时域(顶部)和频率域(底部)中的符号示例:

在这里插入图片描述在这里插入图片描述

注意信号的频率下降速度有多快。脉冲整形后旁瓣降低 ~30 dB;少了 1,000 倍!更重要的是,主瓣更窄,因此每秒相同数量的比特使用更少的频谱。

现在,请注意,常见的脉冲整形滤波器包括: 1、Raised-cosine filter 升余弦滤波器 2、Root raised-cosine filter 根升余弦滤波器 3、Sinc filter 辛格滤波器 4、Gaussian filter 高斯滤波器 这些滤波器通常有一个参数,您可以调整该参数以减少使用的带宽。下面演示了具有不同值

\beta

的升余弦滤波器的时域和频域,该参数定义了滚降的陡峭程度。

在这里插入图片描述

您可以看到,较低的

\beta

值会减少使用的频谱(对于相同数量的数据)。但是,如果该值太低,则时域符号需要更长的时间才能衰减到零。实际上,当符号永远不会完全衰减到零时,这意味着我们无法在实践中传输此类符号。0.35 左右的

\beta

值很常见。

您将在脉冲整形一章中了解有关脉冲整形的更多信息,包括脉冲整形滤波器必须满足的一些特殊属性。



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3