数字信号处理实验报告docx自动化:FFT、滤波器与python-docx交付
2026/9/17 13:06:20 网站建设 项目流程

简介:本资源为北京理工大学数字信号处理课程实验报告文档,面向电子信息、通信工程等专业本科生及需要完成相关实验的学习者。报告围绕离散傅里叶变换(DFT)展开,涵盖DFT与DTFT的取样关系、由N点DFT恢复DTFT的两种方法,以及利用DFT分析连续时间信号频谱的完整流程,并针对频谱混叠、栅栏效应、频谱分辨率与频谱泄露等问题给出参数选取思路。四个实验分别考察补零对频谱分辨率的影响、序列频谱的DFT估计、复合正弦波信号频率分辨率的确定,以及不同采样间隔与截取长度下连续信号幅度谱的分析,每个实验均附MATLAB代码、幅度谱与相位谱结果及简要分析。资源包内含1个docx文件,大小约795KB,结构完整便于直接参考与排版。目前已有1351人学习下载,适合用于实验预习、报告撰写参照及DFT知识点查漏补缺。

1. 数字信号处理实验报告 docx:从"能跑通"到"能交付"

实验室里卷积、FFT、滤波器的代码半小时能跑出图,真正拖住进度的是后面那件事:把波形、参数、结论按格式塞进一份 docx,还要对齐页边距、题注编号、公式编号。北京理工大学数字信号处理实验报告.docx 这类文件值得单独拎出来讲,是因为它把两个世界缝在一起——一边是采样率、窗函数、幅频响应这些硬指标,一边是样式、题注、目录域这些排版规则。只懂一边的人,交付时一定要返工。这篇面向的是要交 DSP 实验报告的学生,也面向任何需要用程序批量产出带图带表 Word 文档的工程师:把数据流水线和文档流水线拆开,再让它们对齐。

2. 数字信号处理实验报告 docx 的内容骨架与数据落点

一份报告写不下去,八成不是因为不会算 FFT,而是因为不知道哪一段该放什么、数据从哪个文件来、图用什么口径画。先把骨架和数据落点定死,后面写代码才有靶子。

2.1 数字信号处理实验报告 docx 的固定段落与每段该写什么

常见的六个实验(离散信号与系统、卷积、DFT/FFT、IIR 滤波器、FIR 滤波器、采样与重构)报告结构基本一致,段落顺序不要自创:

  • 实验目的:三到四条,写清验证哪个理论点,不要抄实验指导书整段。
  • 实验原理:公式为主体,差分方程、系统函数 $H(z)$、窗函数表达式各占一段,每条公式给编号。
  • 实验内容与步骤:参数表加关键代码片段,代码只贴调用和参数设置,不贴完整脚本。
  • 实验结果与分析:图、表、数值三者互相对照,图必须能读出坐标轴单位和量纲。
  • 误差与思考题:把实测值和理论值列在一起,差值算到相对误差。

原理段最容易写空。以 FIR 低通为例,至少写清 $h[n]$ 的由来(窗函数法:理想冲激响应乘窗)、截止频率 $\omega_c$ 与归一化频率的关系 $\omega_c = 2\pi f_c / f_s$、以及阻带衰减由窗型决定的定性结论。这三句话决定了后面分析图的解释力,也是老师判断你有没有真正做过实验的地方。

2.2 数据来源的口径统一:MATLAB、Python 与硬件采集

同一份报告里最忌讳两套参数。有人频谱图用 $f_s = 1000\ \text{Hz}$,滤波器设计又用 $f_s = 8000\ \text{Hz}$,图看着都对,结论全错。常见做法是把所有实验参数集中写成一个配置文件或者一段常量,导出数据统一落成 CSV,图片重新用脚本渲染,而不是从 MATLAB 界面上截图。

# params.py —— 全实验共用参数,任何脚本都从这里取 FS = 1024 # 采样率 Hz N = 1024 # 采样点数,取 2 的幂便于 FFT F0 = 50.0 # 主频分量 1 F1 = 120.0 # 主频分量 2 FC = 100.0 # 低通截止频率 Hz ORDER = 4 # 巴特沃斯阶数 WINDOW = "hanning" # 窗函数类型

参数集中在常量里,好处是报告正文的参数表、代码里的数值、图上标注的轴范围三者能靠同一份来源对齐;改一次 $f_s$,全篇跟着变。注意N取 2 的幂不是为了省事,而是为了让np.fft.rfft走基 2 分解,同时保证频率分辨率 $\Delta f = f_s / N = 1\ \text{Hz}$ 是个整数,读谱线位置时不会有小数误差。

硬件采集的数据多一步:先做直流去除(减均值),再确认量程与 AD 位数,否则频谱里的低频鼓包会被误判成信号成分。采集卡导出的二进制或 CSV 在入报告前统一转成浮点电压值,别混着原始码值和电压两套单位。

2.3 图表、有效数字与单位约定

报告里的图不是"能看就行",横轴纵轴标签、单位、刻度方向都要能被人复现。约定一张表,写脚本时照着填:

实验项目采样率 fs点数 N窗函数关键指标
DFT/FFT 频谱1024 Hz1024汉宁窗频谱分辨率 1 Hz,幅值误差 <5%
IIR 低通1024 Hz2048通带 0–80 Hz,阻带衰减 ≥30 dB
FIR 低通1024 Hz2048汉明窗截止 100 Hz,过渡带宽度约 30 Hz
采样与重构2000 Hz1024重构信噪比 ≥40 dB

有效数字按仪器和算法精度定:频率类保留到小数点后一位或整数,幅度类保留三位有效数字,相对误差保留两位。表格里同一列的小数位数必须一致,这一条比数字本身准不准更容易被扣分。

3. 用 python-docx 把 FFT 与滤波器结果写进报告

数据齐了,接下来是组装。python-docx 的能力边界很清楚:段落、样式、表格、图片、页眉页脚都能做,公式只能靠 OMML 片段或域代码,图表动画和复杂排版不要指望。把边界认清,就不会在样式上反复折腾。

3.1 先定样式,再写内容:Normal、题注与三线表

默认模板的 Normal 样式是西文字体,中文会掉成宋体以外的字体,字号也不一定是小四。样式必须在插内容之前设好,中途改样式会让已有段落行为不一致。

from docx import Document from docx.shared import Pt, Cm from docx.enum.text import WD_ALIGN_PARAGRAPH from docx.oxml.ns import qn doc = Document() # 基于默认模板,也可换成学校模板 normal = doc.styles["Normal"] normal.font.name = "Times New Roman" # 西文 normal.font.size = Pt(12) # 小四 normal._element.rPr.rFonts.set(qn("w:eastAsia"), "宋体") # 中文字体 for sec in doc.sections: # 页边距按学校要求 sec.top_margin, sec.bottom_margin = Cm(2.5), Cm(2.5) sec.left_margin, sec.right_margin = Cm(3.0), Cm(2.5)

normal._element.rPr.rFonts这一步是绕不开的:python-docx 没有中文专属字体接口,只能直接写w:eastAsia这个 XML 属性。西文字体设成 Times New Roman、中文设宋体,是多数高校模板的默认组合;如果学校要求正文用"宋体小四、行距 1.5 倍",还要补一句normal.paragraph_format.line_spacing = 1.5。标题样式不要手写字号,用doc.add_heading(text, level=2)走内置 Heading 样式,这样后期在 Word 里生成目录才能识别层级。

题注(图 1、表 1)没有内置样式,老老实实加一个居中段落,字体设为五号:

def add_caption(doc, text): p = doc.add_paragraph(text) p.alignment = WD_ALIGN_PARAGRAPH.CENTER for r in p.runs: r.font.size, r.font.name = Pt(10.5), "Times New Roman" r._element.rPr.rFonts.set(qn("w:eastAsia"), "宋体") return p

3.2 numpy 算频谱、scipy 设计滤波器并出图

出图和算数放在同一个脚本里,图才能和参数严格对应。下面的频谱计算里有两个容易被忽略的细节:加窗后要按窗函数求和归一化,否则幅值修正是错的;单边谱要乘 2,但直流和奈奎斯特点不能乘。

import numpy as np import matplotlib matplotlib.use("Agg") # 无 GUI 环境下必须指定后端 import matplotlib.pyplot as plt from params import FS, N, F0, F1 t = np.arange(N) / FS x = 0.8*np.sin(2*np.pi*F0*t) + 0.5*np.sin(2*np.pi*F1*t) + 0.05*np.random.randn(N) w = np.hanning(N) # 汉宁窗,降低旁瓣泄漏 X = np.fft.rfft(x * w) mag = 2.0 * np.abs(X) / np.sum(w) # 幅值修正 freq = np.fft.rfftfreq(N, 1.0/FS) # 频率轴,步长 = FS/N fig, ax = plt.subplots(figsize=(6, 3.5), dpi=200) ax.plot(freq, mag, lw=0.9) ax.set_xlabel("频率 / Hz"); ax.set_ylabel("幅值 / V") ax.set_xlim(0, 200); ax.grid(alpha=0.3) fig.tight_layout(); fig.savefig("fig_fft.png"); plt.close(fig)

dpi=200是折中值:低于 150 打印时锯齿明显,高于 300 文件体积涨得快而 Word 显示并不更清楚。figsize=(6, 3.5)对应正文里约 12 cm 的图宽,插入时按width=Cm(12)缩放,纵横比不会变形。滤波器部分用scipy.signal.butter拿系数,再用freqz取幅频响应:

from scipy import signal from params import FS, FC, ORDER b, a = signal.butter(ORDER, FC/(FS/2), btype="low") # 归一化频率必须除以 Nyquist w2, h = signal.freqz(b, a, worN=1024, fs=FS) # 指定 fs 后横轴直接是 Hz h_db = 20*np.log10(np.abs(h) + 1e-12) print("b =", np.round(b, 5)); print("a =", np.round(a, 5))

FC/(FS/2)里的FS/2是奈奎斯特频率,忘了归一化会得到完全错误的截止特性,这是新手最常踩的坑。打印ba两组系数贴进报告,比只贴一张幅频图更有说服力,因为读者能拿系数复算出同样的曲线。

3.3 组装 docx:图片、表格与结论段的写入顺序

写入顺序按报告阅读顺序来,先标题后正文,图片紧跟在"结果与分析"段落之后,题注紧随图片。

doc.add_heading("3 实验结果与分析", level=1) doc.add_paragraph("对加汉宁窗后的信号做 1024 点 FFT,频率分辨率为 1 Hz," "50 Hz 与 120 Hz 处出现明显谱峰,与理论设置一致。") doc.add_picture("fig_fft.png", width=Cm(12)) doc.paragraphs[-1].alignment = WD_ALIGN_PARAGRAPH.CENTER # 图片居中 add_caption(doc, "图 3-1 加汉宁窗后的单边幅值谱") tab = doc.add_table(rows=1, cols=3); tab.style = "Table Grid" for i, h in enumerate(["分量", "理论幅值/V", "实测幅值/V"]): tab.rows[0].cells[i].text = h for name, th, me in [("50 Hz", 0.8, 0.79), ("120 Hz", 0.5, 0.50)]: cells = tab.add_row().cells cells[0].text, cells[1].text, cells[2].text = name, f"{th:.2f}", f"{me:.2f}" doc.save("dsp_report.docx")

doc.add_picture后返回的是最后一个段落对象,直接给它设居中即可,不需要再定位。表格用Table Grid边框样式,如果学校要求三线表,就手动改首行上下框和末行下框的 XML 边框属性,比找第三方样式可靠。数值用 f-string 格式化而不是str(),避免0.7900000000000001这类浮点尾巴进入报告。

4. 多组实验参数下的批量生成与一致性控制

一个学期六到八个实验,结构一样、参数不同,逐份手改既慢又容易串味。批量的关键不是循环,而是决定"模板替换"还是"全代码生成"。

4.1 模板占位符替换与全代码生成怎么选

方案适用场景优点代价
模板占位符替换学校给了固定格式模板样式、页眉页脚、封面一次搞定只能替换文本,图片和表格要额外插
全代码生成格式要求宽松、参数变化多可控性最强,便于版本管理页眉、页脚、目录要自己补
混合:模板做壳、代码填肉多数实际情况兼容两者的长处需要处理占位符定位

混合方案最省事:把封面、页眉、页脚、目录页做进模板 docx,正文部分留{{PRINCIPLE}}{{RESULT}}这样的占位符,用脚本替换文本后再插图和表。文本替换要小心 run 被切碎的问题:

from docx import Document def replace_in_doc(doc, mapping): for p in doc.paragraphs: for run in p.runs: # 逐 run 替换,保留原格式 for k, v in mapping.items(): if k in run.text: run.text = run.text.replace(k, v) return doc tpl = Document("template.docx") replace_in_doc(tpl, {"{{TITLE}}": "实验四 IIR 数字滤波器设计", "{{FS}}": "1024 Hz", "{{FC}}": "100 Hz"}) tpl.add_picture("fig_iir.png", width=Cm(12)) tpl.save("exp04_report.docx")

逐 run 替换的问题是:占位符如果被 Word 拆到两个 run 里({{TITLE}}),一次性替换会失败。稳妥做法是先合并段落内所有 run 的文本再重新写入首个 run,或者干脆保证模板里的占位符不经过拼写检查修改。参数表驱动批量时,把每个实验的参数字典放进列表循环即可:

experiments = [ {"name": "exp03_fft", "fs": 1024, "n": 1024, "win": "hanning"}, {"name": "exp04_iir", "fs": 1024, "n": 2048, "win": None}, {"name": "exp05_fir", "fs": 1024, "n": 2048, "win": "hamming"}, ] for e in experiments: generate(e) # 内部完成算数、出图、填模板、另存

字典里显式写win: None而不是省略键,是为了让下游代码用e["win"]时不会 KeyError,这份一致性在参数变多时价值很大。

4.2 踩坑清单:图片路径、样式串味与 WPS 打开异常

第一类坑是路径。脚本在 A 目录运行、图片写到 B 目录,add_picture找不到文件直接抛异常;相对路径还会因为批处理脚本的当前工作目录不同而失效。统一用相对于脚本文件的路径:

from pathlib import Path BASE = Path(__file__).resolve().parent # 脚本所在目录 doc.add_picture(str(BASE / "figs" / "fig_fft.png"), width=Cm(12))

第二类坑是样式串味。往模板里插图、插表时,新段落的样式继承自模板的 Normal,如果模板 Normal 是"段前 0 行、单倍行距",插进去的正文就可能和其他段落不一致。做法是在模板里专门留一个"正文占位段",用它作为模板段复制 XML,而不是新建段落。python-docx 没有直接复制段落样式的接口,简单办法是把样式名写死在代码里:p.style = doc.styles["BodyText"]

第三类坑是 WPS 打开时提示"文件格式与扩展名不一致"或目录页码不刷新。python-docx 直接拼 XML 出来的包多数情况下没问题,但如果手动改过settings.xml、删过关系项,校验就会失败。至于页码和目录,代码生成的文档里目录域是静态的,打开后要在 Word 或 WPS 里按 F9 更新域才会显示正确页码;如果模板里放了 TOC 域却没有更新,交上去就是一片"错误!未定义书签"。另外 WPS 的默认新建格式设置是它自身的选项,不影响脚本产出的.docx,不要被这个现象误导去改代码。

5. 交付前校验:OMML 公式、包结构与自检脚本

写到最后一版,最该花时间的不是再润色文字,而是把文档当二进制产物验一遍:公式是不是公式、图片在不在包里、样式有没有被污染。

5.1 公式用 OMML,不要贴截图

原理段的公式如果是从别处截图贴进来的,放大就糊,字号也和正文不匹配,更致命的是老师无法用搜索定位。Word 2007 之后的公式本质是 OMML,存在word/document.xml里就是一段数学标记。python-docx 不直接支持公式,可以用两种办法:一是从已有文档里把公式段落的 XML 抽出来,用parse_xml插到目标位置;二是用 LaTeX 转 OMML 的工具先生成片段,再doc.element.body.append(...)。抽 XML 的做法最实用:

from docx import Document from docx.oxml import parse_xml src = Document("formula_lib.docx") # 预置公式的素材文档 omml = src.paragraphs[0]._p.xml # 取出第一个公式段落 new_p = parse_xml(omml) # 反序列化为元素 doc.element.body.append(new_p) # 追加到目标文档末尾

这样插进去的公式是可编辑的域对象,Word 里双击就能改参数,缩放不失真。注意parse_xml需要带命名空间声明的完整 XML,直接从_p.xml取出的片段恰好满足这一点。

5.2 用 zipfile 检查 docx 内部结构是否完整

docx 本身就是一个 zip 包,交付前拆开看一眼,比在 Word 里翻半天管用:

import zipfile with zipfile.ZipFile("dsp_report.docx") as z: names = z.namelist() media = [n for n in names if n.startswith("word/media/")] print("图片数量:", len(media), media) print("document.xml 存在:", "word/document.xml" in names) print("损坏项:", z.testzip()) # None 表示 CRC 全部通过

word/media/下每张插入的图对应一个imageN.png,数量对不上说明有图没进去;testzip()返回None才说明压缩包没有损坏。这一步还能顺手查出重复插入的同一张图——python-docx 不会去重,同一张图插三次包里就有三份,几十兆的文档多半是这么来的。

5.3 交付前自检表

检查项判据工具
公式可编辑双击弹出公式编辑器Word / WPS
图片齐全word/media数量等于插入次数zipfile
目录页码正确F9 更新域后页码与正文一致Word
字体统一正文中文宋体、西文 Times New Roman样式检查
参数字段一致正文参数表与代码常量逐项相等人工比对
数值格式同列小数位数一致,无浮点尾巴脚本正则
包完整testzip()返回 Nonezipfile

数值格式那一项可以写成脚本,扫全文段落用正则找\d+\.\d{6,}这类长小数,命中就报行号,比人眼翻三十页快得多。整份报告生成脚本放在版本库里,改一次参数重跑一遍,比在 Word 里手动调格式稳得多。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询