三角波fft的c语言程序设计教程课后答案,《数字信号处理》课程实验1 – FFT的实现...

一、按时间抽选的基-2 FFT实现原理

141b56c2393eb12aa5cbd03dd99925e4.png

观察DIT(基2)FFT的流图(N点,N为2的幂次),可以总结出如下规律:

(1)共有\(L=\log_2⁡N\)级蝶形运算;

(2)输入倒位序,输出自然顺序;

(3)第\(m\)级(\(m\)从1开始,下同)蝶形结对偶结点距离为\(2^{m-1}\);

(4)第\(m\)级蝶形结计算公式:

\(X_m (k)=X_{m-1} (k)+X_{m-1 } (k+2^{m-1} ) W_N^r\)

\(X_m (k+2^{m-1} )=X_{m-1} (k)-X_{m-1} (k+2^{m-1} ) W_N^r\)

\(m=1,2,…,L\)

(5)第\(m\)级不同旋转因子数为\(2^{m-1}\);

(6)第\(m\)级每一种旋转因子关联\(2^{L-m}\)个蝶形结;

(7)第\(m\)级一种旋转因子的两次相邻出现间隔\(2^m\)个位置;

(8)第\(m\)级第\(i\)种(\(i\)从0开始)旋转因子\(W_N^r\)的指数\(r\)为\(2^{L-m}i\);

(7)每个蝶形结运算完成后,输出的两节点值可以直接放到原输入的两节点的存储器中(原位运算)。

二、按时间抽选的基-2 FFT实现细节

依据如上观察,使用Python语言编写下列相关程序:

(1)倒位变址运算

自然序排列的二进制数,其下一个数总比前一个大1。而倒序二进制数的下一个数,是前一个数最高位加1,然后由高位向低位进位得到的。使用Rader算法,可以方便地计算倒位序。

Rader算法利用一个递推关系——如果已知第\(k\)步倒位序为\(J\),则第\(k+1\)步倒位序计算方法为:从\(J\)最高位看向最低位,为1则变0;为0则变1并立刻退出,即得到下一步倒位序。由于已知递推起点第1步的序号0的倒位序也为0,故可以使用该算法求出所有倒位序。

进行倒位变址运算时,为避免重复调换,设输入为\(x(n)\),倒位序后为\(x(m)\),当且仅当\(m>n\)时进行对调。

下面是相关代码:

new_index = [0]

J = 0 # J为倒位序

for i in range(N - 1): # i为当前数

mask = N // 2

while mask <= J: # J的最高位为1

J -= mask # J的最高位置0

mask = mask >> 1 # 准备检测下一位

J += mask # 首个非1位置1

new_index.append(int(J))

for i in range(N):

if new_index[i] <= i:

continue # 无需对调

seq[i], seq[new_index[i]] = seq[new_index[i]], seq[i] # 交换

(2)旋转因子的计算

利用欧拉公式可知:

\(W_N^k=\cos⁡(2kπ/N)-j \sin⁡(2kπ/N)\)

在\(k=0,1,…,N-1\)范围内循环一次,即可计算所有旋转因子。

一种优化策略是利用如下递推关系来加速计算,递推起点\(W_N^0=1\):

\(W_N^{(k+1)}=W_N^k \exp⁡(-j 2π/N)\)

相关代码如下:

WNk = []

two_pi_div_N = 2 * PI / N # 避免多次计算

for k in range(N // 2):

# WN^k = cos(2kPI/N) - j sin(2kPI/N)

WNk.append(Complex(math.cos(two_pi_div_N * k), math.sin(two_pi_div_N * -k)))

(3)蝶形结按层运算

以旋转因子的种类为循环标准,每一轮就算掉该种旋转因子对应的\(2^{L-m}\)个蝶形结。结合观察(3)~(8),相关代码如下:

L = int(math.log2(N)) # 蝶形结层数

for m in range(1, L + 1): # m为当前层数,从1开始

distance = 2 ** (m - 1) # 对偶结点距离,也是该层不同旋转因子的数量

for k in range(distance): # 以结合的旋转因子为循环标准,每一轮就算掉该旋转因子对应的2^(L-m)个结

r = k * 2 ** (L - m) # 该旋转因子对应的r

for j in range(k, N, 2 ** m): # 2^m为每组旋转因子对应的分组的下标差

right = seq[j + distance] * WNk[r]

t1 = seq[j] + right; t2 = seq[j] - right

seq[j] = t1; seq[j + distance] = t2

三、反变换IFFT的实现

由于IDFT公式为:

\(x(n)={\rm IDFT}[X(k)]=\frac {1}{N} ∑_{k=0}^{N-1} X(k) W_N^{-nk}\)

将该式取共轭:

\(x^* (n)=\frac {1}{N} [∑_{k=0}^{N-1}X(k) W_N^{-nk} ]^*=\frac {1}{N} ∑_{k=0}^{N-1}[X^* (k) W_N^{nk} ]=\frac {1}{N} {\rm DFT}[X^* (k)]\)

那么:

\(x(n)=\frac {1}{N} {{\rm DFT}[X^* (k)]}^*\)

这意味着IFFT可以共用FFT程序。只要将\(X(k)\)取共轭后做FFT,结果再取共轭并除以\(N\)即完成了IFFT。相关代码如下:

def ifft(seq: list):

# 检查是否为2^L点序列

N = len(seq)

if int(math.log2(N)) - math.log2(N) != 0:

raise ValueError('[ifft] Not 2^L long sequence.')

# 先对X(k)取共轭

seq = list(map(lambda x : x.conjugate(), seq))

# 再次利用FFT

seq = fft_dit2(seq)

# 再取共轭

seq = map(lambda x : x.conjugate(), seq)

# 最后除以N

seq = map(lambda x : x * Complex(1 / N, 0), seq)

return list(seq)

四、程序测试

按照要求,分别使用如下三种信号测试算法。使用matplotlib库作图,使用numpy库的FFT结果与自行撰写的FFT结果进行对照。

(1)正弦序列

产生\([-π,π]\)上的16点均匀采样,计算相应的\(\sin\)函数值,进行FFT,结果如下,正确无误:

5f09cc9ce47311d9250e93682fbd8cc7.png

绘制序列及其幅度频谱图,同时绘制IFFT结果以测试IFFT程序:

11f8b543769b0bb4ae9971027490436d.png

(2)三角波序列

从0开始向正方向,以\(π/8\)为间隔,产生三角波的16点\(x\)值,并按\([0, 0.5, 1, 0.5, 0, 0.5, 1…]\)规律赋\(y\)值。作FFT,结果如下,正确无误:

5c9ef8a10b52927fe9422db375fdc51e.png

绘制序列及其幅度频谱图,同时绘制IFFT结果以测试IFFT程序:

98a9ab85be5bd836b9c1535093c893ed.png

(3)方波序列

产生\([-π,π]\)上的32点均匀采样作为方波的\(x\)值,并按\([0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1…]\)规律赋\(y\)值(占空比50%,周期8点)。作FFT,结果如下,正确无误:

b860b449a093f483304241781997992b.png

绘制序列及其幅度频谱图,同时绘制IFFT结果以测试IFFT程序:

1c43abb1a0c9df9bee20a7f06393e6fa.png

五、补充说明

本程序的fft_dit2函数默认对输入的\(N\)长度序列作\(N\)点FFT,即采样点数与FFT点数相同。但有时候,需要对\(N\)长度序列作\(L(L>N)\)点FFT。为此,程序提供了一个实用函数add_zero_to_length来把\(N\)点序列末尾补0到指定长度以支持上述FFT运算。

使用一个矩形窗序列\(R_8 (n)\),对其作16点FFT,测试程序正确性,程序正确无误,如下所示:

cdff39c9e495889fcfa5b8a0a0f325a7.png

该段测试用的代码由于不是实验要求,故在fft_demo.py中作注释处理。

除了补0函数外,程序还提供了下列这些实用函数来帮助更方便地使用FFT:

42e61473228e779f7263ddc12bf46259.png

附录:全部代码

fft.py

# coding: utf-8

# 《数字信号处理》课程实验

# FFT与IFFT实现(一维)

# 09017227 卓旭

import math

'''

自定义复数类

'''

class Complex:

def __init__(self, re, im):

self.set(re, im)

def set(self, re, im):

self._re = re

self._im = im

def get(self, what):

return self._re if what == 're' else self._im

def __add__(self, other):

return Complex(

self._re + other._re,

self._im + other._im

)

def __sub__(self, other):

return Complex(

self._re - other._re,

self._im - other._im

)

def __mul__(self, other):

return Complex(

(self._re * other._re) - (self._im * other._im),

(self._re * other._im) + (self._im * other._re)

)

def conjugate(self):

return Complex(

self._re, -self._im

)

def __abs__(self):

return math.sqrt(self._re ** 2 + self._im ** 2)

def __str__(self):

return (str(round(self._re, 3)) + ' + j' + str(round(self._im, 3))) if (self._im >= 0) \

else (str(round(self._re, 3)) + ' - j' + str(round(-self._im, 3)))

PI=math.pi

'''

按时间抽选的基-2快速傅里叶变换(n点)

需要传入list

'''

def fft_dit2(seq: list):

# 检查是否为2^L点FFT

N = len(seq)

if int(math.log2(N)) - math.log2(N) != 0:

raise ValueError('[fft_dit2] Not 2^L long sequence.')

# 输入数据倒位序处理

new_index = [0]

J = 0 # J为倒位序

for i in range(N - 1): # i为当前数

mask = N // 2

while mask <= J: # J的最高位为1

J -= mask # J的最高位置0

mask = mask >> 1 # 准备检测下一位

J += mask # 首个非1位置1

new_index.append(int(J))

for i in range(N):

if new_index[i] <= i:

continue # 无需对调

seq[i], seq[new_index[i]] = seq[new_index[i]], seq[i] # 交换

# 计算所有需要的旋转因子WN^k(k在0~N/2-1)

# 一种优化策略是使用递推式WN(k+1) = WN(k) * e^(-j 2PI/N)计算

WNk = []

two_pi_div_N = 2 * PI / N # 避免多次计算

for k in range(N // 2):

# WN^k = cos(2kPI/N) - j sin(2kPI/N)

WNk.append(Complex(math.cos(two_pi_div_N * k), math.sin(two_pi_div_N * -k)))

# 蝶形运算

L = int(math.log2(N)) # 蝶形结层数

for m in range(1, L + 1): # m为当前层数,从1开始

# 见课本P219表4.1

distance = 2 ** (m - 1) # 对偶结点距离,也是该层不同旋转因子的数量

for k in range(distance): # 以结合的旋转因子为循环标准,每一轮就算掉该旋转因子对应的2^(L-m)个结

r = k * 2 ** (L - m) # 该旋转因子对应的r

for j in range(k, N, 2 ** m): # 2^m为每组旋转因子对应的分组的下标差

right = seq[j + distance] * WNk[r]

t1 = seq[j] + right; t2 = seq[j] - right

seq[j] = t1; seq[j + distance] = t2

return seq

'''

快速傅里叶变换的反变换

需要传入list

'''

def ifft(seq: list):

# 检查是否为2^L点序列

N = len(seq)

if int(math.log2(N)) - math.log2(N) != 0:

raise ValueError('[ifft] Not 2^L long sequence.')

# 先对X(k)取共轭

seq = list(map(lambda x : x.conjugate(), seq))

# 再次利用FFT

seq = fft_dit2(seq)

# 再取共轭

seq = map(lambda x : x.conjugate(), seq)

# 最后除以N

seq = map(lambda x : x * Complex(1 / N, 0), seq)

return list(seq)

'''

实用函数,将实序列转化为list

'''

def convert_to_complex(seq: list):

return list(map(lambda x : Complex(x, 0), seq))

'''

实用函数,将list转化为实序列(丢弃虚部)

'''

def convert_to_real(seq: list):

return list(map(lambda x : x.get('re'), seq))

'''

实用函数,获取Complex的幅度值

'''

def convert_to_amplitude(seq: list):

return list(map(lambda x: math.sqrt(x.get('re') ** 2 + x.get('im') ** 2), seq))

'''

实用函数,获取Complex的相位值

'''

def convert_to_phase(seq: list):

return list(map(lambda x: math.atan2(x.get('im'), x.get('re')), seq))

'''

实用函数,打印FFT结果

'''

def print_fft_result(seq: list):

toprint = '[\n'

modder = 0

for cplx in seq:

toprint += str(cplx)

toprint += '\t\t' if modder != 3 else '\n'

modder += 1

modder %= 4

toprint += ']'

return toprint

'''

实用函数,给实序列补0到指定长度,可用于采样点数小于FFT点数

'''

def add_zero_to_length(seq: list, n: int):

if len(seq) == n:

return seq

# 如果点数不足,把seq补到n点

if len(seq) > n:

raise ValueError('[add_zero_to_length] n < len(seq).')

if len(seq) < n:

res = [*seq]

while (len(res) < n):

res.append(0)

return res

fft_demo.py

# coding: utf-8

# 《数字信号处理》课程实验

# FFT与IFFT实测应用(作图)

# 09017227 卓旭

import matplotlib.pyplot as plt

import numpy as np

from fft import *

PI=math.pi

def ft_test(name, xs, ys):

# 产生测试点

y_points = ys

# 绘制原图

plt.subplots_adjust(hspace=0.7)

plt.subplot(311)

plt.title('Original Singal: ' + name)

plt.plot(xs, y_points, '.')

# 绘制FFT结果(幅度谱)

fft_res = fft_dit2(convert_to_complex(y_points))

plt.subplot(312)

plt.title('FFT Result (Amplitude Spectrum)')

fft_res_amp = convert_to_amplitude(fft_res)

plt.plot(xs, fft_res_amp, '.')

max_height = np.max(fft_res_amp)

for (idx, val) in enumerate(xs):

plt.axvline(val, 0, fft_res_amp[idx] / max_height) # 绘制竖线

# 绘制IFFT

ifft_res = convert_to_real(ifft(fft_res))

plt.subplot(313)

plt.title('IFFT Result')

plt.plot(xs, ifft_res, '.')

plt.show()

if __name__ == '__main__':

# 正弦函数测试

xs = np.linspace(-PI, PI, 16)

ys = [math.sin(x) for x in xs]

print("========正弦函数========")

print("np.fft标准结果:")

print(np.fft.fft(ys))

print("我的FFT结果:")

print(print_fft_result(fft_dit2(convert_to_complex(ys))))

ft_test('sin(x)', xs, ys)

print("========三角波========")

xs = [i * PI / 8 for i in range(16)]

ys = [0, 0.5, 1, 0.5, 0, 0.5, 1, 0.5, 0, 0.5, 1, 0.5, 0, 0.5, 1, 0.5]

print("np.fft标准结果:")

print(np.fft.fft(ys))

print("我的FFT结果:")

print(print_fft_result(fft_dit2(convert_to_complex(ys))))

ft_test('Tri(x)', xs, ys)

print("========方波========")

xs = np.linspace(-PI, PI, 32)

ys = [-1,-1,-1,-1, 1, 1, 1, 1] * 4

print("np.fft标准结果:")

print(np.fft.fft(ys))

print("我的FFT结果:")

print(print_fft_result(fft_dit2(convert_to_complex(ys))))

ft_test('Square(x)', xs, ys)

# print("========R_8========")

# xs = range(16)

# ys = [1, 1] * 4

# ys = add_zero_to_length(ys, 16)

# print("np.fft标准结果:")

# print(np.fft.fft(ys, 16))

# print("我的FFT结果:")

# print(print_fft_result(fft_dit2(convert_to_complex(ys))))

# ft_test('R_8(x), 16 dot', xs, ys)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值