实时波形与频谱分析———傅立叶变换

embedded/2025/2/6 14:28:59/

实时波形与频谱分析:一个交互式动画演示

在信号处理领域,时域波形频域频谱是理解信号特性的重要工具。通过时域波形,我们可以直观地观察信号随时间的变化,而频域频谱则揭示了信号中所包含的频率成分及其幅值。为了帮助大家更好地理解这两者之间的关系,我开发了一个交互式的动画演示系统,展示波形与频谱的实时分析。

本文将详细介绍该系统的功能、实现原理以及如何使用它来探索信号的特性。


功能概述

这个演示系统实现了以下功能:

  1. 实时波形显示

    • 左侧显示信号的时域波形。
    • 支持两个正弦波的叠加,并实时动态更新波形。
  2. 频谱分析

    • 右侧显示信号的频谱图。
    • 通过频谱图可以直观地看到信号中包含的频率分量及其幅值。
  3. 交互控制

    • 为每个正弦波提供独立的频率和振幅控制。
    • 可以通过复选框启用或禁用某个波形分量。
  4. 视觉效果

    • 网格背景便于观察波形和频谱。
    • 使用不同颜色区分不同波形的频率分量,使频谱图更加直观。

系统界面与交互

1. 时域波形

时域波形展示信号随时间的变化。在演示中,时域波形是一个复合信号,它由两个正弦波叠加而成。通过滑块调整正弦波的频率和振幅,波形会实时更新,帮助用户观察不同参数对信号的影响。

2. 频谱图

频谱图展示信号的频率成分及其幅值。在演示中,频谱图会显示两个正弦波的频率分量,分别用不同颜色标注正频率的幅值。通过调整正弦波的参数,可以观察频谱图的变化,例如频率峰值的位置和高度。

3. 交互控制

用户可以通过以下方式与系统交互:

  • 频率滑块:调节正弦波的频率(1-20 Hz)。
  • 振幅滑块:调节正弦波的振幅(0-100)。
  • 复选框:启用或禁用某个正弦波。
  • 系统会实时响应用户的调整,并动态更新波形与频谱图。

实现原理

1. 时域波形计算

时域波形是两个正弦波的叠加,表达式如下:

y(t) = A₁ * sin(2πf₁t + φ₁) + A₂ * sin(2πf₂t + φ₂)

其中:

  • A₁ 和 A₂ 是两个波形的振幅。
  • f₁ 和 f₂ 是两个波形的频率。
  • φ₁ 和 φ₂ 是相位,这里通过时间戳动态变化,实现波形的动画效果。
2. 频谱分析

对于单一正弦波,其频谱在频域中表现为两个对称的冲击峰,分别位于正频率和负频率处。通过分析复合波形的频率成分,我们可以在频谱图中绘制每个频率分量的幅值。

3. 动画与动态更新

通过 JavaScript 的 requestAnimationFrame 函数实现动画效果。系统每秒更新波形和频谱图,确保用户的调整能够实时反映在画布上。


使用方法

  1. 加载页面:将代码保存为 HTML 文件并在浏览器中打开。
  2. 调节波形参数
    • 使用滑块调整频率和振幅,观察时域波形的变化。
    • 启用或禁用某个波形,查看复合波形的变化。
  3. 观察频谱图
    • 调节频率,观察频谱峰值的位置变化。
    • 调节振幅,观察频谱峰值的高度变化。

实际应用

这个演示系统不仅可以帮助初学者理解时域与频域的关系,还可以用于以下场景:

  1. 信号分解

    • 通过调整参数,观察复合信号的组成成分。
    • 理解如何通过频谱图分析信号的频率特性。
  2. 滤波器设计

    • 通过观察频谱图,思考如何设计滤波器以保留或去除特定频率成分。
  3. 教育与教学

    • 作为教学工具,帮助学生直观理解傅立叶变换和频谱分析的基本概念。

示例代码

以下是完整的 HTML 演示代码,可以直接复制并运行:

html"><!DOCTYPE html>
<html>
<head><meta charset="UTF-8"><title>波形与频谱分析</title><script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="bg-gray-100 p-8"><div class="max-w-7xl mx-auto"><h1 class="text-3xl font-bold text-center mb-6">波形与频谱实时分析</h1><div class="grid grid-cols-1 md:grid-cols-2 gap-6"><!-- 时域波形 --><div class="bg-white rounded-lg shadow-lg p-4"><h2 class="text-xl font-semibold mb-4">时域波形</h2><canvas id="waveCanvas" width="600" height="300" class="border border-gray-300 rounded"></canvas></div><!-- 频谱图 --><div class="bg-white rounded-lg shadow-lg p-4"><h2 class="text-xl font-semibold mb-4">频谱图</h2><canvas id="spectrumCanvas" width="600" height="300" class="border border-gray-300 rounded"></canvas></div></div><!-- 控制面板 --><div class="mt-6 bg-white rounded-lg shadow-lg p-6"><h3 class="text-lg font-semibold mb-4">波形控制</h3><div id="waveControls" class="space-y-4"><!-- 波形1 --><div class="p-4 border rounded-lg"><div class="flex items-center mb-2"><span class="w-20">波形 1</span><input type="checkbox" id="wave1Enable" checked class="ml-2"></div><div class="grid grid-cols-2 gap-4"><div><label class="block text-sm">频率 (Hz)</label><input type="range" id="freq1" min="1" max="20" value="2" class="w-full"><span id="freq1Value" class="text-sm">2 Hz</span></div><div><label class="block text-sm">振幅</label><input type="range" id="amp1" min="0" max="100" value="50" class="w-full"><span id="amp1Value" class="text-sm">50</span></div></div></div><!-- 波形2 --><div class="p-4 border rounded-lg"><div class="flex items-center mb-2"><span class="w-20">波形 2</span><input type="checkbox" id="wave2Enable" checked class="ml-2"></div><div class="grid grid-cols-2 gap-4"><div><label class="block text-sm">频率 (Hz)</label><input type="range" id="freq2" min="1" max="20" value="5" class="w-full"><span id="freq2Value" class="text-sm">5 Hz</span></div><div><label class="block text-sm">振幅</label><input type="range" id="amp2" min="0" max="100" value="30" class="w-full"><span id="amp2Value" class="text-sm">30</span></div></div></div></div></div></div><script>const waveCanvas = document.getElementById('waveCanvas');const spectrumCanvas = document.getElementById('spectrumCanvas');const waveCtx = waveCanvas.getContext('2d');const spectrumCtx = spectrumCanvas.getContext('2d');// 控制参数const controls = {wave1: {enabled: document.getElementById('wave1Enable'),freq: document.getElementById('freq1'),amp: document.getElementById('amp1'),freqValue: document.getElementById('freq1Value'),ampValue: document.getElementById('amp1Value')},wave2: {enabled: document.getElementById('wave2Enable'),freq: document.getElementById('freq2'),amp: document.getElementById('amp2'),freqValue: document.getElementById('freq2Value'),ampValue: document.getElementById('amp2Value')}};// 更新显示值function updateValues() {controls.wave1.freqValue.textContent = controls.wave1.freq.value + ' Hz';controls.wave1.ampValue.textContent = controls.wave1.amp.value;controls.wave2.freqValue.textContent = controls.wave2.freq.value + ' Hz';controls.wave2.ampValue.textContent = controls.wave2.amp.value;}// 为所有控制添加事件监听Object.values(controls).forEach(control => {control.freq.addEventListener('input', updateValues);control.amp.addEventListener('input', updateValues);});// 绘制网格function drawGrid(ctx, width, height) {ctx.beginPath();ctx.strokeStyle = '#e5e5e5';ctx.lineWidth = 0.5;// 垂直线for (let x = 0; x <= width; x += 50) {ctx.moveTo(x, 0);ctx.lineTo(x, height);}// 水平线for (let y = 0; y <= height; y += 50) {ctx.moveTo(0, y);ctx.lineTo(width, y);}ctx.stroke();}// 绘制时域波形function drawWaveform(timestamp) {waveCtx.clearRect(0, 0, waveCanvas.width, waveCanvas.height);drawGrid(waveCtx, waveCanvas.width, waveCanvas.height);// 绘制中心线waveCtx.beginPath();waveCtx.strokeStyle = '#666';waveCtx.lineWidth = 1;waveCtx.moveTo(0, waveCanvas.height / 2);waveCtx.lineTo(waveCanvas.width, waveCanvas.height / 2);waveCtx.stroke();// 采样点数组const samples = new Float32Array(waveCanvas.width);const phase = timestamp / 1000;// 计算复合波形for (let x = 0; x < waveCanvas.width; x++) {let sample = 0;const t = x / 100;// 波形1if (controls.wave1.enabled.checked) {const freq1 = parseFloat(controls.wave1.freq.value);const amp1 = parseFloat(controls.wave1.amp.value);sample += (amp1 / 100) * Math.sin(2 * Math.PI * freq1 * t + phase);}// 波形2if (controls.wave2.enabled.checked) {const freq2 = parseFloat(controls.wave2.freq.value);const amp2 = parseFloat(controls.wave2.amp.value);sample += (amp2 / 100) * Math.sin(2 * Math.PI * freq2 * t + phase);}samples[x] = sample;}// 绘制波形waveCtx.beginPath();waveCtx.strokeStyle = '#3b82f6';waveCtx.lineWidth = 2;for (let x = 0; x < samples.length; x++) {const y = waveCanvas.height / 2 * (1 - samples[x]);if (x === 0) {waveCtx.moveTo(x, y);} else {waveCtx.lineTo(x, y);}}waveCtx.stroke();}// 绘制频谱function drawSpectrum() {spectrumCtx.clearRect(0, 0, spectrumCanvas.width, spectrumCanvas.height);drawGrid(spectrumCtx, spectrumCanvas.width, spectrumCanvas.height);// 绘制中心线spectrumCtx.beginPath();spectrumCtx.strokeStyle = '#666';spectrumCtx.lineWidth = 1;spectrumCtx.moveTo(0, spectrumCanvas.height);spectrumCtx.lineTo(spectrumCanvas.width, spectrumCanvas.height);spectrumCtx.stroke();// 频谱分析const maxFreq = 20; // 最大显示频率const freqScale = spectrumCanvas.width / maxFreq;// 绘制频谱线if (controls.wave1.enabled.checked) {const freq1 = parseFloat(controls.wave1.freq.value);const amp1 = parseFloat(controls.wave1.amp.value);drawFrequencyPeak(freq1, amp1, '#ef4444');}if (controls.wave2.enabled.checked) {const freq2 = parseFloat(controls.wave2.freq.value);const amp2 = parseFloat(controls.wave2.amp.value);drawFrequencyPeak(freq2, amp2, '#3b82f6');}function drawFrequencyPeak(freq, amp, color) {const x = freq * freqScale;const height = (amp / 100) * spectrumCanvas.height;spectrumCtx.beginPath();spectrumCtx.strokeStyle = color;spectrumCtx.lineWidth = 2;spectrumCtx.moveTo(x, spectrumCanvas.height);spectrumCtx.lineTo(x, spectrumCanvas.height - height);spectrumCtx.stroke();// 添加频率标签spectrumCtx.fillStyle = color;spectrumCtx.font = '12px Arial';spectrumCtx.textAlign = 'center';spectrumCtx.fillText(`${freq}Hz`,x,spectrumCanvas.height - height - 10);}}// 动画循环function animate(timestamp) {drawWaveform(timestamp);drawSpectrum();requestAnimationFrame(animate);}// 启动动画updateValues();requestAnimationFrame(animate);</script>
</body>
</html>

总结

通过这个交互式的动画演示,我们可以直观地理解时域波形与频域频谱之间的关系。无论是学习信号处理的初学者,还是从事相关领域的专业人士,这个工具都可以作为一个有趣且实用的学习和研究工具。

如果你对这个演示有任何改进建议,或者希望了解更多相关内容,欢迎留言讨论!


http://www.ppmy.cn/embedded/160049.html

相关文章

C#中堆和栈的区别

C#中的堆&#xff08;Heap&#xff09;和栈&#xff08;Stack&#xff09;详解 基本概念 栈&#xff08;Stack&#xff09; 栈是一个后进先出&#xff08;LIFO&#xff09;的内存结构由系统自动分配和释放存储空间连续&#xff0c;大小固定主要用于存储值类型和对象引用 堆…

零基础Vue入门6——Vue router

本节重点&#xff1a; 路由定义路由跳转 前面几节学习的都是单页面的功能&#xff08;都在专栏里面https://blog.csdn.net/zhanggongzichu/category_12883540.html&#xff09;&#xff0c;涉及到项目研发都是有很多页面的&#xff0c;这里就需要用到路由&#xff08;vue route…

熵采样在分类任务中的应用

熵采样在分类任务中的应用 在机器学习的分类任务里,数据的标注成本常常制约着模型性能的提升。主动学习中的熵采样策略,为解决这一难题提供了新的思路。本文将带你深入了解熵采样在分类任务中的原理、应用及优势。 一、熵采样的原理(优化版) 熵,源于信息论,是对不确定…

DeepSeek大模型指定github项目版本安装环境

最近DeepSeek非常的火爆&#xff0c;有一些公司复现了DeepSeek&#xff0c;如open-r1, 但其依赖的环境往往是最新的&#xff0c;甚至是新增的功能&#xff0c;整个生态安装没有完善。需要需要指定特定的依赖安装&#xff1a; 查看open-r1的setup.py发现&#xff0c;lighteval&…

python算法和数据结构刷题[1]:数组、矩阵、字符串

一画图二伪代码三写代码 LeetCode必刷100题&#xff1a;一份来自面试官的算法地图&#xff08;题解持续更新中&#xff09;-CSDN博客 算法通关手册&#xff08;LeetCode&#xff09; | 算法通关手册&#xff08;LeetCode&#xff09; (itcharge.cn) 面试经典 150 题 - 学习计…

企业微信开发012_使用WxJava企业微信开发框架_封装第三方应用企业微信开发005_多企业授权实现---企业微信开发014

这里主要说一下如何授权的思路,如何来做,其实非常简单, 如果你有很多企业微信需要授权以后才能使用自己开发的,第三方企业微信功能,那么 首先,在企业列表中,你可以给某个企业去配置,这个企业,他对应的企业微信的,比如, 这个企业的企业id,cropID,当然还可以有,比如企业名称,用…

蓝桥杯python基础算法(2-1)——排序

目录 一、排序 二、例题 P3225——宝藏排序Ⅰ 三、各种排序比较 四、例题 P3226——宝藏排序Ⅱ 一、排序 &#xff08;一&#xff09;冒泡排序 基本思想&#xff1a;比较相邻的元素&#xff0c;如果顺序错误就把它们交换过来。 &#xff08;二&#xff09;选择排序 基本思想…

【Excel笔记_4】平均绝对偏差(MAD,Mean Absolute Deviation)的EXCEL公式表达

平均绝对偏差&#xff08;MAD&#xff0c;Mean Absolute Deviation&#xff09;&#xff0c;其数学表达式如下&#xff1a; M A D S 1 N ∑ t 1 N ∣ S t − S ‾ ∣ MAD_S \frac{1}{N} \sum_{t1}^{N} |S_t - \overline{S}| MADS​N1​t1∑N​∣St​−S∣ 在 Excel 中&…