fgg blog

sampling and aliasing

Table of Contents

模拟信号(analog signal)和数字信号(digital signal)是两种不同类型的信号,在电子通讯和 信号处理领域中有重要作用。

模拟信号是连续变化的信号,它的取值是在一定范围可以无限细分的(连续型变量)。例如声音波形。

数字信号是离散的信号,是对模拟信号进行离散化(采样和量化)得到的。进行这种离散化操作的叫 做模数转换器(ADC),转成数字信号后方便存储和传输,但利用这些数字信号时通常需要再将它们转 换为模拟信号(通过数模转换器,DAC),因为人类不像计算机,不能直接处理0/1信号,只能接收模 拟信号。(人的耳朵解析的是模拟信号,恐怕还没人练就听一串01组成的数字就能听出那是贝多芬的 第九交响曲的本领。)

# 混叠现象(Aliasing)

混叠(Alias)现象的产生是由于采样定理(也称为奈奎斯特定理)未被满足。奈奎斯特定理指出, 为了避免混叠,采样频率必须至少是信号中最高频信号频率的两倍。如果信号频率高于采样频率的一 半,就会出现混叠现象。也就是:它发生在信号的采样频率不足以表示原始信号中的所有频率成分时:

  • 当信号中某些组分的信号频率高于采样频率的一半(也称为:折叠频率)时,高频部分会被错误的 解析成低频信号,这就导致频谱中出现虚假的频率成分(本是高频的,被折叠为低频的)。
  • 由于折叠频率的存在,信号频谱中部分频率被重叠,导致信息丢失和失真。

## 采样采样,越采越像

framerate = 10000  # 采样频率

# 构建原始信号,频率:4500
signal = thinkdsp.CosSignal(4500)
duration = signal.period*5
# 对原始信号进行采样
segment = signal.make_wave(duration, framerate=framerate)
segment.plot(label="freq=4500")

# 构建原始信号,频率:5500
signal = thinkdsp.CosSignal(5500)
# 对原始信号进行采样
segment = signal.make_wave(duration, framerate=framerate)
segment.plot(linestyle="-.", label="freq=5500")

plt.legend()

IMG_sampling

## 混叠的频率

下面是一个三角形信号(频率为1100)的频谱图,注意其谐波(harmonics)应该是基频 (fundamental frequency)的奇数倍(如 3300,5500,7700,9900,12100等等),但是,并非完 全如此,第三个谐波出现在4500,而第四个则在2300,认真看右边的y轴放大图,第五个出现在100的 位置,而第六个呢(在2100的位置)。啥情况?!

这就是混叠现象。采样频率为10k,折叠频率=$\frac{1000}{2}=5000$,当信号中频率高于5000的, 会被“折叠”(折回来,回到小于折叠频率的范围内)。所以,频率为5500的谐波被折叠到4500的位置, 同理,频率为7700的谐波被折叠到2300的位置,以此类推(镜像中心就是5000的位置)。

就这样,原本的三角波形模拟信号经过这个采样得到的数字信号,再重新转换为模拟信号时,已失真。

fig, axs = plt.subplots(1, 2, figsize=(15, 8))

signal = TriangleSignal(1100)
segment = signal.make_wave(duration=0.5, framerate=10000)
spectrum = segment.make_spectrum()
axs[0].plot(spectrum.fs, spectrum.hs)
axs[0].set_ylim(0, 2200)
axs[0].set_xlabel('Frequency (Hz)')
axs[0].set_title('Original y-axis')

signal = TriangleSignal(1100)
segment = signal.make_wave(duration=0.5, framerate=10000)
spectrum = segment.make_spectrum()
axs[1].plot(spectrum.fs, spectrum.hs)
axs[1].set_ylim(0, 500)
decorate(xlabel='Frequency (Hz)')
decorate(title='Scaled the y-axis (to show the harmonics)')

plt.suptitle('Sampling and Aliasing')
plt.tight_layout()

IMG_aliasing

# 频谱是怎么计算得到的

频谱图实际上就是以频率(frequency)为横轴,幅度(amplitude)为纵轴的图。

那么,信号中的频率及其幅度又是怎么算出来的?快速傅里叶转换(FFT)。

def plot(self, high=None, **options):
    """Plots amplitude vs frequency.

    Note: if this is a full spectrum, it ignores low and high

    high: frequency to cut off at
    """
    if self.full:
        fs, amps = self.render_full(high)
        plt.plot(fs, amps, **options)
    else:
        i = None if high is None else find_index(high, self.fs)
        plt.plot(self.fs[:i], self.amps[:i], **options)

def render_full(self, high=None):
    """Extracts amps and fs from a full spectrum.

    high: cutoff frequency

    returns: fs, amps
    """
    hs = np.fft.fftshift(self.hs)
    amps = np.abs(hs)
    fs = np.fft.fftshift(self.fs)
    i = 0 if high is None else find_index(-high, fs)
    j = None if high is None else find_index(high, fs) + 1
    return fs[i:j], amps[i:j]

## DFT & FFT

快速回顾: 离散傅里叶转换(DFT)是一种数学思想(所有波可以由若干正余弦波叠加而成), 而快速傅里叶转换(FFT)是一种高效算法,用于实现离散傅里叶转换。

快速回顾2: 傅里叶级数是一种将周期函数(periodic function)分解为一系列正弦和余弦函数的无穷和的方法。 假设我们有一个周期为 $ T $ 的函数 $ f(t) $,它可以表示为如下的傅里叶级数:

$$ f(t) = \frac{a_0}{2} + \sum_{n=1}^{\infty} \left( a_n \cos\left(\frac{2\pi nt}{T}\right) + b_n \sin\left(\frac{2\pi nt}{T}\right) \right) $$

其中 $ a_0, a_n, b_n $ 是系数,它们可以通过函数 $ f(t) $ 的周期性质计算得到。

系数的计算公式为:

$$ \begin{align*}% align* 的 * 号用于阻止自动编号生成; hugo中要用 \\ 换行,第一个\为转义字符 a_0 &= \frac{1}{T} \int_{0}^{T} f(t) dt \\ a_n &= \frac{2}{T} \int_{0}^{T} f(t) \cos\left(\frac{2\pi nt}{T}\right) dt \\ b_n &= \frac{2}{T} \int_{0}^{T} f(t) \sin\left(\frac{2\pi nt}{T}\right) dt \end{align*} $$

这些系数描述了正弦和余弦函数的振幅和相位,它们决定了如何将原始函数 $ f(t) $ 分解成频率 为 $ \frac{n}{T} $ 的正弦和余弦函数的和。

from np.fft import rfft, rfftfreq

class Wave:

    # lots of other attrs, methods

    def make_spectrum(self):
        """
        self: Wave object
        """
        # n   : number of samples in the wave
        # d   : inverse of framerate, means the time between samples
        # hs  : a NumPy array of complex numbers that represents the amplitude and
        #       phase offset of each frequency component in the wave
        # fs  : an array that contains frequencies corresponding to the `hs`

        n = len(self.hs)
        d = 1 / self.framerate

        hs = rfft(self.ys)
        fs = rfftfreq(n, d)

        return Spectrum(hs, fs, self.framerate)