《Python数字信号处理应用》学习笔记——第二章 谐波

专栏总目录

        本章了解多种波形,观察频谱理解他们的谐波结构,也就是构成他们的三角函数稽核的结构。

        介绍数字信号处理中最重要的现象之一:混叠,同时解释Spectrum类的工作原理

2.1 三角波

        一个正弦波,仅包含一个频率元素,所以其频谱只有一个尖峰。

        复杂波形,例如,下图小提琴录音片段,其离散傅里叶变换的结果中有很多尖峰。

        本章继续研究波形和频谱之间的关系。 

        从三角波开始,三角波就像是正弦波的直线版本。图2-1显示的三角波的频率是200Hz。

图 2-1 200Hz的三角波片段

2.1.1 创建TriangleSignal类

        可以使用thinkdsp.TriangleSignal生成三角波:

class TriangleSignal(Sinusoid):
    def evaluate(self, ts):
        # 获得周期数
        cycles = self.freq * ts + self.offset / PI2
        # 获取周期数的小数部分,忽略整数部分
        frac, _ = np.modf(cycles)
        # 减小振幅
        ys = np.abs(frac - 0.5)
        # unbias——将波形中心放置0的位置
        # normalize最大化波形(音量)
        ys = normalize(unbias(ys), self.amp)
        return ys

        TriangleSignal从Sinusoid继承了__init__,所以它们使用的参数是一样的,都有freq、amp和offset。

        唯一的差别在于evalute。如我们在前面看到过的,ts是我们相对信号求值的采样时间的序列。

 产生三角波的方法很多。其细节并不重要,但是我们需要知道evaluate的工作原理:

1. cycles是自起始时间的周期数。np.modf是将周期的数量分解成小数部分和整数部分,前者被存于frac,后者被忽略了。

2. frac序列介于0~1,其频率是给定的。减去0.5之后其值介于-0.5~0.5。对其取绝对值就得到了在0~0.5穿梭的波形。

3.unbias将波形下移,使得其中心位于0,然后normalize将波形归一化到给定的振幅amp。

2.3.2 创建三角波信号

        以下是产生图2-1的代码(可以在jupyter notebook中直接运行获得下图):

from thinkdsp import TriangleSignal
from thinkdsp import decorate

# 创建三角波信号
signal = TriangleSignal(200)
# 截取三个周期
duration = signal.period*3
# 创建wave数据
segment = signal.make_wave(duration, framerate=10000)

#图形化数据
segment.plot()
decorate(xlabel='Time (s)')

         如果在idle或.py文件中执行,需要在上述脚本下面追加代码:

#将上面数据保存成图像trianglesignal.png
plt.savefig('trianglesignal.png')

2.3.3 获取三角波声音

        如果想要听到该声音:

        将上述三角波转换为声音数据

# 获得采样率10000,持续时间0.5秒的波形数据 
wave = signal.make_wave(duration=0.5, framerate=10000)
# 音量最大化
wave.normalize()
# 淡入淡出
wave.apodize()

        jupyter notebook上直接执行即可

# 播放
wave.make_audio()

         如果在idle或.py文件中执行,需要在转换为声音数据之后追加如下代码:

# 保存音频为WAV文件
from scipy.io.wavfile import write
scaled = np.int16(wave.ys/np.max(np.abs(wave.ys)) * 32767)
write('output.wav', wave.framerate, scaled)
 
# 现在,您可以使用音频播放器打开并播放output.wav文件。

2.3.4 获取三角波频谱

        使用上面的signal创建wave,从而使用创建的wave来生成spectrum:

(jupyter notebook中可以直接运行下方代码)

spectrum = wave.make_spectrum()
spectrum.plot()
decorate(xlabel='Frequency (Hz)')

         在idle或.py中运行,则需要增加下面代码,运行后通过察看输出文件的方式看到该图像。

#将上面数据保存成图像trianglesignal_spectrum.png
plt.savefig('trianglesignal_spectrum.png')

        显示的是其结果的两个视图。下面图中的右图经过了缩放,使得谐波显示得更清楚。最高峰位于基频——200Hz,频率是基频200的倍数的谐波频率峰也被显示在图上。

图2-2 频率200Hz的三角波信号的频谱,以下两种不同的纵坐标尺度显示。右图中切去了基频部分,从而更清楚地显示谐波。

奇怪的是:在偶数倍频率,比如400Hz、800Hz等的位置没有峰值。三角波的谐波全都是基频的奇数倍,比如600Hz、1000Hz、1400Hz等。

上面频谱的另一个特征:谐波的振幅与频率的关系——各谐波的振幅随着频率的平方等比例地衰减。

如:最前面的两个谐波之间的频率(200Hz和600Hz)比率为3,幅度比为9。接下来的两个谐波(600Hz和1000Hz)频率比为1.7,对应的幅度比大约是1.7^2 = 2.9。这种关系被称为谐波结构


2.2 方波

2.2.1 创建SquareSignal类

        thinkdsp还提供了SquareSignal,用来处理方波。其类的定义如下:

class SquareSignal(Sinusoid):
    def evaluate(self, ts):
        cycles = self.freq * ts + self.offset / PI2
        frac, _ = np.modf(cycles)
        ys = self.amp * np.sign(unbias(frac))
        return ys

        和SquareSignal一样,它从Sinusoid继承了__init__,所以他们的参数是一样的。同时evaluate地 方式也类似。同样,cycles是从起始时间开始的周期数目,而frac是小数部分,其值在每个周期介于0~1。

        Unbias将frac移至-0.5~0.5,然后np.sign将负值投射到-1,而正值投射到1。乘以amp之后就得到取值于-amp到+amp之间的方波。

图 2-3 100Hz频率的方波的一部分

图 2-4 100Hz频率方波的频谱

        图2-3显示的是频率100Hz的方波的三个周期,图2-4显示的是其频谱。

        和三角波一样,方波也只含有奇数谐波,这也就是其频谱中在300Hz、500Hz和700Hz这样的值处有尖峰的原因。但相比来说,它的谐波衰减得要慢一些。也就是,其幅度随频率(而不是频率的平方)等比例地降低。

2.3.2 创建方波信号

        生成上述方波的代码如下(jupyter notebook直接运行 ):

from thinkdsp import SquareSignal

signal = SquareSignal(200)
duration = signal.period*3
segment = signal.make_wave(duration, framerate=10000)
segment.plot()
decorate(xlabel='Time (s)')

#在idle或.py文件中使用如下方式输出图像
#将上面数据保存成图像squareesignal.png
plt.savefig('squaresignal.png')

2.3.3获取方波声音

         将信号转换为声音,聆听

wave = signal.make_wave(duration=0.5, framerate=10000)
wave.apodize()
#jupyter notebook中运行到此步骤即可
wave.make_audio()


# 保存音频为WAV文件
from scipy.io.wavfile import write
scaled = np.int16(wave.ys/np.max(np.abs(wave.ys)) * 32767)
write('output.wav', wave.framerate, scaled)
 
# 现在,您可以使用音频播放器打开并播放output.wav文件。

2.3.4 获取方波频谱

        显示频谱图

spectrum = wave.make_spectrum()
spectrum.plot()
decorate(xlabel='Frequency (Hz)')

#在idle或.py文件中使用如下方式输出图像
#将上面数据保存成图像squareesignal.png
plt.savefig('squaresignal.png')


2.3锯齿波信号

2.3.1 创建锯齿波信号

        绘制3个周期的片段

from thinkdsp import SawtoothSignal

signal = SawtoothSignal(200)
duration = signal.period*3
segment = signal.make_wave(duration, framerate=10000)
segment.plot()
decorate(xlabel='Time (s)')

#在idle或.py文件中使用如下方式输出图像
#将上面数据保存成图像sawtoothsignal.png
plt.savefig('sawtoothsignal.png')

2.3.2 获取锯齿波声音

wave = signal.make_wave(duration=0.5, framerate=10000)
wave.apodize()
wave.make_audio()


# 保存音频为WAV文件
from scipy.io.wavfile import write
scaled = np.int16(wave.ys/np.max(np.abs(wave.ys)) * 32767)
write('output.wav', wave.framerate, scaled)
 
# 现在,您可以使用音频播放器打开并播放output.wav文件。

2.3.3 获取锯齿波频谱

spectrum = wave.make_spectrum()
spectrum.plot()
decorate(xlabel='Frequency (Hz)')

#在idle或.py文件中使用如下方式输出图像
#将上面数据保存成图像sawtoothsignal_spectrum.png
plt.savefig('sawtoothsignal_spectrum.png')


2.4 混叠

        Aliasing在信号处理领域通常指的是“混叠”现象。当一个信号被采样时,如果采样频率不足以捕捉信号中的高频成分,那么这些高频成分在重建过程中可能会错误地显示为低频成分,这种现象就称为混叠。为了防止混叠,采样频率必须至少是信号最高频率成分的两倍,这个原则被称为奈奎斯特采样定理。

图 2-5 一个频率为1100Hz的三角波的频谱,采样率为10000帧每秒。右图显示的是放大之后的谐波。

        上图这个波的谐波应该位于3300Hz、5500Hz、7700Hz和9900Hz。上图中和预期的一样,1100Hz和3300Hz处有尖峰,但是第三个尖峰在4500Hz,而不是5500Hz。第4个尖峰在2300Hz而不是7700Hz。如果仔细看的话,应对位于9900Hz的尖峰现在实际上在100Hz处。这是什么问题?

        如果对信号在离散的时间点上进行取值,就会失去在采样点之间的信息。对于低频元素,这不是问题,因为会在每个周期中采样多次。

        但是如果对频率为5000Hz的信号每秒采样10000次,那在每个周期之中就仅有两个采样。这仅仅是恰好够用,但如果频率更高的画,就不够了。

2.4.1 创建余弦信号

        为了演示,我们来产生频率4500Hz和5500Hz的余弦信号,然后对它们每秒采样10000次:

from thinkdsp import CosSignal

# 采样率
framerate = 10000
# 产生4500Hz信号
signal = CosSignal(4500)
# 选取样长度为5个信号周期的时长
duration = signal.period*5
# 生成wave
segment = signal.make_wave(duration, framerate=framerate)
segment.plot()

#在idle或.py文件中使用如下方式输出图像
#将上面数据保存成图像cossignal_4500.png
plt.savefig('cossignal_4500.png')

# 产生5500Hz信号
signal = CosSignal(5500)
# 生成wave
segment = signal.make_wave(duration, framerate=framerate)
segment.plot()

#在idle或.py文件中使用如下方式输出图像
#将上面数据保存成图像cossignal_5500.png
plt.savefig('cossignal_5500.png')

        4500Hz余弦信号,波形图如下:

        5500Hz余弦信号,波形图如下:

        上述脚本,输出结果如图 2-6所示。在绘制信号时,使用的灰色线,采样用的是垂直线表示,以便于比较这两个波形。这样问题清楚了:虽然两个信号是不同的,但是波形是一样的! 

 

图 2-6 频率为4500Hz和5500Hz的余弦信号,每秒采样10000次。信号是不同的,但采样结果是一样的

        当我们对一个5500Hz的信号进行10000帧每秒的采样时,其结果与4500Hz的无法区分。同样 7700Hz的信号也无法与2300Hz的信号区分,9900Hz的信号无法与100Hz的区分。

        这种效应被称为混叠,也就是采样高频信号后,其结果和采样低频信号的一样。

        在这个例子中,我们能测定的最高频率是5000Hz,也就是采样率的一半。高于5000Hz的信号被折叠到5000Hz以下,这也就是这个频率有时被称为 “折叠频率” 的原因另外一些时候也被称为奈奎斯特频率,参见:http://en.wikipedia.org/wiki/Nyquist_frequency。

        如果混叠频率低于0,折叠的模式一样。比如,1100Hz的三角波信号的第5个谐波位于12100Hz。以5000Hz折叠,就会出现在-2100Hz,但是它在0Hz处被再次折叠,从而回到2100Hz。实际上,能在图2-4的2100Hz处观察到一个小峰值,而下一个在4300Hz。

2.4.2 制作一个三角波信号并绘制其频谱。观察谐波如何折叠。

from thinkdsp import TriangleSignal


signal = TriangleSignal(1100)
segment = signal.make_wave(duration=0.5, framerate=10000)
spectrum = segment.make_spectrum()
spectrum.plot()
decorate(xlabel='Frequency (Hz)')


#在idle或.py文件中使用如下方式输出图像
#将上面数据保存成图像sawtoothsignal.png
plt.savefig('sawtoothsignal.png')

2.5 计算频谱

        make_spectrum的Wave方式,具体实现方式:

from np.fft import rfft, rfftfreq

#classWave:
    def mack_spectrum(self):
        n = len(self,ys)
        d = 1 / self.framerate

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

        参数  self  是一个Wave对象。  n  是波形的采样数,而  d  是采样频率的倒数,也就是样本间的时间差。

          np.fft  是NumPy模块,提供快速傅里叶变换(FFT)的相关函数,这是一种计算离散傅里叶变化的高效算法。

          make_spectrum  使用了  rfft  ,其意义是“傅里叶变换市属部分(realFFT)”,因为Wave只有实数值,而没有复数值。之后会了解完整的FFT,它能处理复数信号(见7.9节),将rfft的结果赋值给hs,这是一个NumPy复数数值,表示的是波形中各个频率元素的振幅和相位差。

        将  rfftfreq  的结果赋值给  fs  ,这是一个包含对应  hs  各元素的频率的数组。

为了便于理解hs的值,可以参考以下两种理解复数的方式。

1. 复数是实数部分和虚数部分的和,通常写做 x + iy 的形式,其中 i 是虚数单元

2. 复数还可以理解成一个量值与复指数的乘积,其中A 是量值,是弧度角,也被称为 “幅角”。可以在极角坐标系下理解A 和

          hs   中的每一个值对应一个频率元素。其量值与对应的元素的振幅成比例,其角度为相位差。

          Spectrum  类提供了两个制度属性:  amps   angles  。它们返回的是NumPy数组,代表的分别是  hs  的振幅和角度。在绘制  Spectrum  对象时,通常绘制的是  fs  对应的  amps  。有时绘制  fs  对应的  angles  也是有用的。

        实际上,几乎完全不需要察看  hs  的实数和虚数部分。

        可以将DFT想象为振幅和角度的向量,只不过恰好以复数的形式来表示了。

可以直接通过使用hs来修改Spetrum,例如:

spectrum.hs *= 2
spectrum.hs[spectrum.fs > cutoff] = 0

第一行将hs的元素乘以2,这将所有均匀的振幅都变成原来的两倍。第二行将hs元素中频率超过截至频率的所有元素都置零。

不过Spectrum还提供了如下的操作方式:

spectrum.scale(2)
spectrum.low_pass(cutoff)

可以在http://greenteapress.com/thinkdsp.html阅读以上所述方法相关文档。

 2.5.1 振幅和相位

        创建一个锯齿波信号

from thinkdsp import SawtoothSignal


signal = SawtoothSignal(500)
wave = signal.make_wave(duration=1, framerate=10000)
segment = wave.segment(duration=0.005)
segment.plot()
decorate(xlabel='Time (s)')


#在idle或.py文件中使用如下方式输出图像
#将上面数据保存成图像sawtoothsignal.png
plt.savefig('sawtoothsignal.png')

 2.5.2 获取该波声音

wave.make_audio()


# 保存音频为WAV文件
from scipy.io.wavfile import write
scaled = np.int16(wave.ys/np.max(np.abs(wave.ys)) * 32767)
write('output.wav', wave.framerate, scaled)
 
# 现在,您可以使用音频播放器打开并播放output.wav文件。

 2.5.3 计算hs——提取波形数组并计算实数FFT(这是一种针对实数输入优化的FFT)。

(jupyter notebook可以直接执行)

import numpy as np

hs = np.fft.rfft(wave.ys)
hs

 返回结果:

2.5.4 计算fs ——计算与FFT元素相对应的频率。

n = len(wave.ys)                 # number of samples
d = 1 / wave.framerate           # time between samples
fs = np.fft.rfftfreq(n, d)
fs

返回结果:

2.5.5绘制振幅与频率的关系图

import matplotlib.pyplot as plt

magnitude = np.absolute(hs)
plt.plot(fs, magnitude)
decorate(xlabel='Frequency (Hz)')


#在idle或.py文件中使用如下方式输出图像
#将上面数据保存成图像testsignal_spectrum.png
plt.savefig('testsignal_spectrum.png')

 2.5.6 绘制相位与频率的关系图

import numpy as np

angle = np.angle(hs)
plt.plot(fs, angle)
decorate(xlabel='Phase (radian)')

 2.5.7 随机打乱相位

import random
random.shuffle(angle)
plt.plot(fs, angle)
decorate(xlabel='Phase (radian)')

#在idle或.py文件中使用如下方式输出图像
#将上面数据保存成图像random_phase.png
plt.savefig('random_phase.png')

        将打乱的相位重新放回频谱中。hs中的每个元素都是一个复数,其幅度为A,相位为φ,我们可以用这个来计算Ae^(iφ)。

i = complex(0, 1)
spectrum = wave.make_spectrum()
spectrum.hs = magnitude * np.exp(i * angle)

2.5.8 使用irfft将频谱转换回波形。

wave2 = spectrum.make_wave()
wave2.normalize()
segment = wave2.segment(duration=0.005)
segment.plot()
decorate(xlabel='Time (s)')


#在idle或.py文件中使用如下方式输出图像
#将上面数据保存成图像wave.png
plt.savefig('wave.png')

2.5.9 获取该波形声音

输出output1.wav

wave2.make_audio()


# 保存音频为WAV文件
from scipy.io.wavfile import write
scaled = np.int16(wave2.ys/np.max(np.abs(wave2.ys)) * 32767)
write('output1.wav', wave2.framerate, scaled)
 
# 现在,您可以使用音频播放器打开并播放output1.wav文件。

 获取原始波形用于比较,输出output.wav

wave.make_audio()


# 保存音频为WAV文件
from scipy.io.wavfile import write
scaled = np.int16(wave.ys/np.max(np.abs(wave.ys)) * 32767)
write('output.wav', wave.framerate, scaled)
 
# 现在,您可以使用音频播放器打开并播放output.wav文件。

        尽管这两个信号的波形不同,但它们具有相同的频率成分和相同的振幅。它们只在相位上有所不同。

2.6 混叠相互作用

        下面的交互程序探索了混叠对锯齿信号谐波的影响。

        该代码只能在jupyter notebook中运行,运行后,随着对freq及framerate的调整,产生声音及频谱图,用于对比。

def view_harmonics(freq, framerate):
    """Plot the spectrum of a sawtooth signal.
    
    freq: frequency in Hz
    framerate: in frames/second
    """
    signal = SawtoothSignal(freq)
    wave = signal.make_wave(duration=0.5, framerate=framerate)
    spectrum = wave.make_spectrum()
    spectrum.plot(color='C0')
    decorate(xlabel='Frequency (Hz)', ylabel='Amplitude')
    display(wave.make_audio())
from ipywidgets import interact, interactive, fixed
import ipywidgets as widgets

slider1 = widgets.FloatSlider(min=100, max=10000, value=100, step=100)
slider2 = widgets.FloatSlider(min=5000, max=40000, value=10000, step=1000)
interact(view_harmonics, freq=slider1, framerate=slider2);

上一篇: 《Python数字信号处理应用》学习笔记——第一章 声音和信号-CSDN博客

下一篇:《Python数字信号处理应用》学习笔记——第三章 非周期信号

  • 8
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

静候光阴

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值