三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

Scilab信号处理实战:从正弦信号生成到噪声滤波与幅度调制

Scilab信号处理实战:从正弦信号生成到噪声滤波与幅度调制

1. 项目概述:为什么用Scilab处理正弦信号?

如果你正在学习信号处理,或者你的工作涉及到从传感器数据中提取信息,那么“正弦信号”这个概念你一定绕不开。它就像信号世界里的“基本粒子”,简单、纯粹,却构成了几乎所有复杂信号的基础。无论是音频处理、通信系统,还是你提到的生物医学信号处理和MIMO雷达信号处理,理解并熟练操作正弦信号都是第一步。但理论懂了,怎么上手实操呢?这时候,一个强大又免费的工具就显得至关重要——这就是Scilab。

Scilab可能不像MATLAB那样家喻户晓,但它是一个功能完整、完全开源的科学计算软件。对于学生、研究人员和工程师来说,它意味着零成本获得一个强大的数值计算和信号处理环境。这个项目,就是带你用Scilab,从零开始,亲手“玩转”正弦信号。我们将不满足于画出一条简单的正弦波,而是要深入其核心:生成、分析、变换,并模拟它在真实世界处理链路中可能遇到的情况,比如混入噪声、被滤波器处理。最终,你会掌握一套用Scilab进行基础信号处理的完整工作流,这将成为你探索更复杂领域(如生物医学信号分析或雷达信号处理)的坚实跳板。

2. 核心思路与工具选型解析

2.1 为什么选择正弦信号作为切入点?

正弦信号,数学上表示为 \( x(t) = A \sin(2\pi f t + \phi) \),它只用三个参数(振幅A、频率f、初相位φ)就能完全描述。这种简洁性使其成为理想的教学和实验对象。在工程实践中,许多物理现象(如交流电、机械振动、声波)都可以近似为正弦或余弦信号。更重要的是,根据傅里叶分析理论,任何满足条件的复杂信号都可以分解为一系列不同频率、振幅和相位的正弦信号的叠加。因此,掌握了正弦信号的处理,就等于拿到了理解复杂信号的钥匙。

在生物医学信号处理中,心电信号(ECG)的节律性波动、脑电信号(EEG)中的特定频段(如α波、β波),其核心成分都可以用正弦或类正弦模型来分析和模拟。在MIMO雷达信号处理中,发射和接收的信号本质也是高频正弦载波被调制后的结果。所以,这个看似基础的项目,其底层逻辑直接贯通了这些前沿应用。

2.2 为什么是Scilab,而不是Python或MATLAB?

这是一个很实际的问题。Python(配合NumPy, SciPy, Matplotlib)无疑是当前数据科学和信号处理领域的热门选择,生态庞大。MATLAB则是该领域的传统工业标准,工具箱丰富。选择Scilab,是基于以下几个核心考量:

  1. 纯粹性与教学友好性:Scilab内置了完整的信号处理工具箱,语法与MATLAB高度相似但更简洁。它让你能更专注于信号处理概念本身,而不是花费大量时间在Python库的安装、版本管理和语法细节上。对于初学者,一个开箱即用、界面统一的集成环境(类似MATLAB的工作区、编辑器、图形窗口)更能降低入门门槛。
  2. 零成本与合规性:Scilab是完全开源免费的,这对于个人学习者、高校教学以及预算有限的项目团队极具吸引力。你可以合法地在任何地方安装和使用它,无需担心授权问题。
  3. 足够的专业能力:对于基础的信号生成、时频分析、滤波器设计和仿真来说,Scilab的功能完全够用。它的fft(快速傅里叶变换)、滤波器设计函数(eqfir,iir等)、以及绘图功能,足以支撑起一个完整的信号处理实验流程。
  4. 平滑过渡:如果你未来需要转向MATLAB,由于语法相似,过渡会非常平滑。如果你需要转向Python,在Scilab中建立起的信号处理概念框架将直接适用,你只需要学习用不同的语法(库函数)来实现它。

因此,本项目选择Scilab,旨在提供一个低成本、低门槛、高聚焦的信号处理实操入门路径。

3. 环境搭建与基础信号生成

3.1 Scilab的安装与初体验

首先,前往Scilab官网下载对应你操作系统(Windows, macOS, Linux)的安装包。安装过程与普通软件无异。安装完成后打开,你会看到三个主要窗口:控制台(用于输入命令)、文件编辑器(用于编写脚本)、变量浏览器和图形窗口等。

注意:建议从一开始就养成编写脚本(.sce文件)的习惯,而不是只在控制台输入单行命令。脚本便于修改、保存和重复运行,是工程实践的基础。

让我们从生成第一个正弦信号开始。核心参数我们已经知道:振幅A、频率f、初相位phi。此外,在数字世界里,我们需要定义采样频率fs和信号持续时间T(或总采样点数N)。

// 示例1:生成一个1Hz的正弦波 clear; clc; // 清空工作区和命令窗口,良好的习惯 // 1. 定义信号参数 A = 1; // 振幅,单位根据实际情况定,这里设为1(无量纲或伏特) f = 1; // 频率,1 Hz phi = 0; // 初相位,0弧度 fs = 100; // 采样频率,100 Hz。根据奈奎斯特定理,需大于2*f T = 2; // 信号持续时间,2秒 // 2. 生成时间向量和信号向量 t = 0 : 1/fs : T; // 时间向量,从0到T,步长为1/fs x = A * sin(2*%pi*f*t + phi); // 正弦信号 // 3. 绘制时域波形 figure(0); clf(); // 创建或清空0号图形窗口 plot(t, x, ‘b-‘, ‘LineWidth‘, 2); // 蓝色实线,线宽2 xgrid(1, 1, 3); // 添加网格线(颜色索引3,通常是淡灰色) xlabel(‘时间 (秒)‘, ‘fontsize‘, 3); ylabel(‘幅度‘, ‘fontsize‘, 3); title(‘1 Hz 正弦信号时域波形‘, ‘fontsize‘, 4);

运行这段脚本,你将看到一个纯净的正弦波在2秒内完成了两个完整周期(因为频率是1Hz)。这里的关键是理解t = 0 : 1/fs : T;这行代码,它创建了一个离散的时间点序列,1/fs就是采样间隔。2*%pi*f*t计算了每个时间点对应的相位弧度值,%pi是Scilab预定义的圆周率常数。

3.2 多信号生成与基本操作

单一信号太枯燥,我们生成多个信号来演示基本操作。

// 示例2:生成并操作多个信号 clear; clc; fs = 1000; T = 1; t = 0:1/fs:T; f1 = 5; f2 = 50; x1 = sin(2*%pi*f1*t); x2 = 0.5 * sin(2*%pi*f2*t + %pi/4); // 振幅0.5,相位偏移π/4 // 信号相加(模拟简单混叠) x_sum = x1 + x2; // 绘制对比 figure(1); clf(); subplot(3,1,1); plot(t(1:200), x1(1:200), ‘r-‘); // 只画前200个点以便观察 title(‘5 Hz 低频信号‘); subplot(3,1,2); plot(t(1:200), x2(1:200), ‘g-‘); title(‘50 Hz 高频信号(有衰减和相移)‘); subplot(3,1,3); plot(t(1:200), x_sum(1:200), ‘b-‘, ‘LineWidth‘, 1.5); title(‘合成信号‘); xlabel(‘时间 (秒)‘);

这个例子展示了信号的线性叠加。在现实中,我们接收到的信号往往是多个分量(如不同频率的振动、噪声)的叠加。通过绘图,你能直观看到高频信号“叠加”在低频信号的“轮廓”之上。

实操心得:在绘制包含快速振荡的信号时,像上面一样只绘制一小段时间(t(1:200))的波形是个好习惯。如果绘制整个长时间序列,图形会因为点太密而糊成一团,看不出细节。通过调整绘图的索引范围,可以灵活地观察信号的局部或全局特征。

4. 信号分析:从时域到频域

4.1 离散傅里叶变换与频谱分析

时域波形告诉我们信号随时间的变化,但频率成分信息是隐藏的。傅里叶变换(在离散系统中是DFT,常用其快速算法FFT)能将信号从时域变换到频域,让我们看清信号由哪些频率的正弦波组成,以及它们的强度(振幅谱)和起始位置(相位谱)。

// 示例3:对合成信号进行FFT分析 clear; clc; fs = 1000; T = 1; N = T * fs; // 总采样点数 t = (0:N-1)/fs; f1 = 5; f2 = 50; x = sin(2*%pi*f1*t) + 0.5*sin(2*%pi*f2*t + %pi/4); // 执行FFT X = fft(x); // X是复数数组,包含频域信息 X_mag = abs(X); // 取模,得到振幅谱 X_phase = atan(imag(X), real(X)); // 计算相位谱,使用atan(y,x)得到四象限相位 // 构建频率向量 freqs = (0:N-1) * (fs / N); // 对应的频率点 // 绘制双边频谱(包含正负频率) figure(2); clf(); subplot(2,1,1); plot(freqs(1:N/2), X_mag(1:N/2)*2/N, ‘b-‘, ‘LineWidth‘, 1.5); // 通常只显示前一半(正频率),并归一化 xlabel(‘频率 (Hz)‘); ylabel(‘幅度‘); title(‘振幅谱(单边)‘); xgrid(1,1,3); subplot(2,1,2); plot(freqs(1:N/2), X_phase(1:N/2), ‘r.‘); // 绘制相位点 xlabel(‘频率 (Hz)‘); ylabel(‘相位 (弧度)‘); title(‘相位谱(单边)‘); xgrid(1,1,3);

运行后,你会在振幅谱上看到两个明显的“尖峰”,分别位于5Hz和50Hz附近,且50Hz处的尖峰高度大约是5Hz处的一半(因为其振幅是0.5)。这完美印证了我们的信号构成。相位谱在对应频率点上的值,也大致接近我们设定的0和π/4。

关键点解析

  • fft(x)返回一个复数数组,长度与输入信号x相同。
  • abs(X)得到每个频率分量的幅度。由于FFT结果是对称的(对于实信号),我们通常只取前一半(1:N/2)。
  • 绘图时乘以2/N是一种常见的幅度归一化方式,使得频谱图上尖峰的高度等于原始正弦分量的振幅(对于单频成分)。如果不做归一化,幅度值会与采样点数N相关,不直观。
  • 频率向量freqs的计算是关键:第k个点对应的频率是k * (fs / N),其中k从0到N-1。

4.2 频谱泄露与窗函数应用

在上一个理想例子中,我们恰好截取了整数个信号周期。如果截取的不是整数倍周期,会发生“频谱泄露”——能量会从主频点“泄露”到旁边的频率点上,导致频谱图上的尖峰变宽、旁边出现许多小旁瓣。

// 示例4:演示频谱泄露 clear; clc; fs = 1000; T_nonint = 1.23; // 非整数周期时长 t = 0:1/fs:T_nonint; x = sin(2*%pi*5*t); // 仍然是5Hz信号 N = length(x); X = fft(x); freqs = (0:N-1) * (fs / N); figure(3); clf(); subplot(2,1,1); plot(t, x); title(‘非整周期截取的5Hz正弦波‘); xlabel(‘时间 (秒)‘); subplot(2,1,2); plot(freqs(1:N/2), abs(X(1:N/2))*2/N); title(‘对应的频谱(出现泄露)‘); xlabel(‘频率 (Hz)‘); ylabel(‘幅度‘); xgrid(1,1,3);

你会发现,频谱图上5Hz处的尖峰不再“干净”,周围出现了很多杂散的频率分量。这不是噪声,而是截断效应带来的数学失真。

为了抑制泄露,我们需要在FFT前对信号加“窗”。窗函数在信号两端平滑地衰减到零,减少截断处的突变。最常用的是汉宁窗。

// 示例5:使用汉宁窗减少频谱泄露 w = window(‘hn‘, N); // 生成长度为N的汉宁窗 x_windowed = x .* w; // 点乘,对信号加窗 X_w = fft(x_windowed); figure(4); clf(); subplot(3,1,1); plot(t, x); title(‘原始信号‘); subplot(3,1,2); plot(t, w, ‘r-‘); title(‘汉宁窗‘); subplot(3,1,3); plot(t, x_windowed, ‘g-‘); title(‘加窗后的信号‘); xlabel(‘时间 (秒)‘); figure(5); clf(); plot(freqs(1:N/2), abs(X(1:N/2))*2/N, ‘b-‘); // 原始频谱 plot(freqs(1:N/2), abs(X_w(1:N/2))*2/N, ‘r-‘, ‘LineWidth‘, 1.5); // 加窗后频谱 legend([‘无窗‘, ‘汉宁窗‘]); title(‘加窗前后频谱对比‘); xlabel(‘频率 (Hz)‘); ylabel(‘幅度‘); xgrid(1,1,3);

加窗后,频谱的旁瓣(泄露的能量)会显著降低,但主峰会稍微变宽(这是抑制泄露的代价,称为“频率分辨率”下降)。在实际信号分析中,尤其是分析非平稳信号或进行精确频率估计时,窗函数的选择和运用是一门大学问。

5. 模拟真实场景:噪声与滤波

5.1 为信号添加噪声

纯净的正弦信号在实验室外很少见。真实信号总是伴随着噪声。我们可以用Scilab轻松模拟。

// 示例6:生成含噪声的信号 clear; clc; fs = 1000; T = 1; t = 0:1/fs:T; x_clean = sin(2*%pi*10*t); // 10Hz纯净信号 // 添加高斯白噪声 SNR_dB = 10; // 信噪比,单位dB // 计算噪声功率 signal_power = mean(x_clean.^2); noise_power = signal_power / (10^(SNR_dB/10)); // 生成与x_clean同长度的噪声序列 noise = grand(1, length(x_clean), ‘nor‘, 0, sqrt(noise_power)); x_noisy = x_clean + noise; figure(6); clf(); subplot(2,1,1); plot(t, x_clean, ‘b-‘); title(‘纯净的10Hz正弦信号‘); subplot(2,1,2); plot(t, x_noisy, ‘r-‘); title(‘加入高斯白噪声后的信号 (SNR=10dB)‘); xlabel(‘时间 (秒)‘);

grand函数是Scilab的随机数生成器,‘nor‘表示正态分布(高斯分布),后两个参数是均值和标准差。我们通过信噪比来控制噪声的强度。现在信号看起来已经“毛糙”了很多,时域上几乎看不出正弦波的形状。

5.2 设计滤波器滤除噪声

为了从x_noisy中恢复出x_clean,我们需要一个滤波器。假设我们知道有用信号是10Hz,而噪声遍布全频带,我们可以设计一个低通滤波器,只让10Hz及以下的低频成分通过,滤除高频噪声。

Scilab的eqfir函数可以用于设计线性相位的FIR滤波器。

// 示例7:设计并应用一个低通FIR滤波器 // 设计一个低通滤波器,截止频率15Hz fc = 15; // 截止频率 (Hz) fn = fs / 2; // 奈奎斯特频率 Wc = fc / fn; // 归一化截止频率 (0到1之间) // 使用eqfir设计一个65阶的FIR低通滤波器 taps = 65; // 滤波器阶数,阶数越高,过渡带越陡峭,但延迟和计算量越大 hm = eqfir(taps, [0 Wc; Wc+0.1 1], [1 0], [1 1]); // 设计参数需要仔细理解 // 参数解释: // taps: 滤波器系数个数(阶数+1) // [0 Wc; Wc+0.1 1]: 频带划分。第一段[0, Wc]为通带,第二段[Wc+0.1, 1]为阻带。中间[Wc, Wc+0.1]是过渡带。 // [1 0]: 期望的幅度响应,通带为1,阻带为0。 // [1 1]: 权重,通带和阻带的误差权重均为1。 // 应用滤波器 x_filtered = filter(hm, 1, x_noisy); // ‘1‘表示滤波器的分母多项式为1(FIR滤波器) // 由于FIR滤波器引入的群延迟是固定的 (taps/2 个采样点),我们需要补偿这个延迟 delay = taps / 2; x_filtered_aligned = x_filtered(delay+1:length(x_filtered)); t_aligned = t(1:length(x_filtered_aligned)); // 绘制结果 figure(7); clf(); subplot(3,1,1); plot(t, x_noisy, ‘c-‘); title(‘带噪声的输入信号‘); subplot(3,1,2); plot(t_aligned, x_filtered_aligned, ‘m-‘, ‘LineWidth‘, 1.5); title(‘经过低通滤波器后的信号(已对齐)‘); subplot(3,1,3); plot(t, x_clean, ‘b-‘); hold on; plot(t_aligned, x_filtered_aligned, ‘m–‘, ‘LineWidth‘, 1.5); legend([‘原始纯净信号‘, ‘滤波恢复信号‘]); title(‘对比‘); xlabel(‘时间 (秒)‘); // 绘制滤波器的频率响应 [hm_freq, fr] = frmag(hm, 2048); // 计算频率响应 figure(8); clf(); plot(fr*fn, 20*log10(hm_freq)); // 纵坐标转换为dB xlabel(‘频率 (Hz)‘); ylabel(‘增益 (dB)‘); title(‘FIR低通滤波器的幅频响应‘); xgrid(1,1,3);

运行后,你会看到滤波后的信号(洋红色)虽然无法完全恢复到原始蓝色信号那么光滑(因为滤波器不是理想的,会损失一些高频细节,并且噪声中与信号同频的成分无法去除),但已经非常接近,正弦波的形状清晰可见。图8的滤波器频率响应曲线显示,在15Hz以下增益接近0dB(通过),在15Hz以上增益迅速衰减到很低(抑制)。

注意事项:eqfir函数的设计参数需要花时间理解。[0 Wc; Wc+0.1 1]这种写法定义了两个频带区间。过渡带(Wc, Wc+0.1)的存在是必须的,实际滤波器无法实现从通带到阻带的瞬间跳变。过渡带越窄,需要的滤波器阶数taps就越高。这是一个在性能(滤波效果)和成本(计算量、延迟)之间的权衡。

6. 综合案例:模拟幅度调制

让我们做一个更有趣的综合实验:幅度调制。这是无线电通信的基础,也是将低频信号(如音频)搭载到高频载波上进行远距离传输的方法。

// 示例8:幅度调制与解调仿真 clear; clc; fs = 10000; // 需要较高的采样率来处理高频载波 T = 0.1; // 持续时间0.1秒 t = 0:1/fs:T; // 1. 生成调制信号(低频,包含信息)和载波信号(高频) f_mod = 10; // 调制信号频率 10Hz f_carrier = 200; // 载波频率 200Hz A_mod = 0.8; // 调制信号振幅 m_t = A_mod * cos(2*%pi*f_mod*t); // 调制信号 c_t = cos(2*%pi*f_carrier*t); // 载波信号 // 2. 进行幅度调制 (AM) // 标准AM: s(t) = [A + m(t)] * c(t), 其中A >= |m(t)|的最大值,这里A取1 A = 1; s_t = (A + m_t) .* c_t; // 3. 绘制时域波形 figure(9); clf(); subplot(3,1,1); plot(t, m_t, ‘b-‘); title(‘调制信号 (10Hz)‘); ylabel(‘幅度‘); subplot(3,1,2); plot(t, c_t, ‘r-‘); title(‘载波信号 (200Hz)‘); ylabel(‘幅度‘); subplot(3,1,3); plot(t, s_t, ‘g-‘); title(‘已调信号 (AM)‘); ylabel(‘幅度‘); xlabel(‘时间 (秒)‘); // 4. 绘制频谱,观察频率搬移现象 N = length(s_t); S_f = fft(s_t); freqs = (0:N-1) * (fs / N); figure(10); clf(); subplot(2,1,1); plot(freqs(1:N/2), abs(S_f(1:N/2))*2/N); xlabel(‘频率 (Hz)‘); ylabel(‘幅度‘); title(‘已调信号频谱‘); xgrid(1,1,3); // 理论上,频谱应在载频f_carrier处有一个尖峰,在其左右f_carrier +/- f_mod处有两个边带。 // 我们可以放大看看 subplot(2,1,2); plot(freqs(1:N/2), abs(S_f(1:N/2))*2/N); xlabel(‘频率 (Hz)‘); ylabel(‘幅度‘); title(‘已调信号频谱(局部放大)‘); xgrid(1,1,3); // 通过图形窗口的缩放工具,可以放大200Hz附近区域,观察边带。 // 5. 简易包络检波解调(一种非相干解调方法) // 对已调信号取绝对值,再经过低通滤波,即可近似得到包络 s_abs = abs(s_t); // 设计一个截止频率略高于f_mod的低通滤波器来提取包络 fc_lp = f_mod * 2; // 截止频率设为20Hz Wc_lp = fc_lp / (fs/2); // 使用一个简单的移动平均滤波器(也是一种FIR滤波器) taps_lp = 101; // 阶数高一些,平滑效果好 b = ones(1, taps_lp) / taps_lp; // 系数全为1/taps_lp a = 1; m_demod = filter(b, a, s_abs); // 补偿滤波器延迟 delay_lp = (taps_lp-1)/2; m_demod_aligned = m_demod(delay_lp+1:length(m_demod)); t_demod = t(1:length(m_demod_aligned)); // 绘制解调结果 figure(11); clf(); plot(t, m_t, ‘b-‘, ‘LineWidth‘, 2); hold on; plot(t_demod, m_demod_aligned - mean(m_demod_aligned), ‘r–‘, ‘LineWidth‘, 1.5); // 减去直流分量便于对比 legend([‘原始调制信号‘, ‘解调恢复信号‘]); title(‘幅度调制解调结果对比‘); xlabel(‘时间 (秒)‘); ylabel(‘幅度‘); xgrid(1,1,3);

这个案例综合运用了信号生成、运算、频谱分析和滤波。在频谱图(图10)中,你能清晰地看到能量集中在200Hz(载波)以及190Hz和210Hz(上下边带)处,这正是幅度调制的特征——将低频信息(10Hz)搬移到了高频载波两侧。解调部分,我们用了最简单的包络检波法,虽然恢复的信号有失真和延迟,但基本形状得以重现。更精确的解调需要相干解调,即需要与原始载波同频同相的本地振荡器,这涉及到锁相环等技术,更为复杂。

7. 常见问题与调试技巧实录

在实际操作中,你肯定会遇到各种问题。下面是我在多次使用Scilab进行信号处理实验中总结的一些典型问题和解决方法。

7.1 FFT结果看起来不对?

  • 现象:频谱图上的频率峰值位置不对,或者幅度值非常奇怪(比如巨大无比)。
  • 排查步骤
    1. 检查频率向量:这是最常见错误。确保频率向量freqs是用(0:N-1) * (fs / N)正确计算的。N是信号长度,fs是采样频率。
    2. 检查归一化:对于幅度谱,如果你想知道真实振幅,记得对abs(fft(x))的结果进行归一化,通常乘以2/N(对于单边谱)。如果不关心绝对幅度,只关心相对峰值,可以跳过。
    3. 检查信号长度:FFT对信号长度敏感,尤其是非2的幂次时。虽然Scilab的fft处理任意长度,但如果是2的幂次(如256,512,1024)计算效率最高。可以使用nextpow2函数来寻找合适的长度:NFFT = 2^nextpow2(N); X = fft(x, NFFT);
    4. 检查直流分量:频谱的第一个点(0Hz)是直流分量,即信号的均值。如果你的信号有非零均值,0Hz处会有一个很大的值,可能让你误以为有低频信号。在FFT前对信号去均值是个好习惯:x = x - mean(x);

7.2 滤波器设计函数参数令人困惑?

  • 现象:使用eqfiriir等函数时,设定的通带截止频率和实际滤波效果对不上。
  • 关键理解
    • 归一化频率:数字滤波器设计函数通常使用归一化频率,范围是0到1,其中1对应奈奎斯特频率(fs/2)。因此,你的目标频率f_cut需要转换为Wn = f_cut / (fs/2)
    • 频带矩阵eqfir的频带矩阵bands是一个m x 2的矩阵,m是频带数量。每一行定义了一个频带的起始和结束归一化频率。例如,对于低通滤波器:bands = [0, Wn; Wn+delta, 1]delta是一个小的正数,用于定义过渡带。没有过渡带的理想滤波器是无法物理实现的。
    • 期望响应向量des向量定义了每个频带期望的幅度响应,长度与频带数m相同。
    • 权重向量weight向量定义了每个频带在优化设计中的权重。权重越大,该频带的设计误差就越被重视。
  • 调试建议:始终使用frmag函数计算并绘制出你设计的滤波器的频率响应图。眼见为实,图形能最直观地告诉你滤波器的通带、阻带和过渡带是否符合预期。

7.3 滤波后的信号有延迟或畸变?

  • 现象:滤波后的信号和原始信号在时间上对不齐,或者波形起始/结束处有奇怪的震荡。
  • 原因与解决
    1. 群延迟:所有实际滤波器都会引入延迟。对于线性相位的FIR滤波器,这个延迟是固定的,等于(N-1)/(2*fs)秒,其中N是滤波器阶数(系数个数)。在比较滤波前后信号时,需要将滤波后信号向后平移这个延迟量,如示例7所示。可以使用grpdelay函数计算群延迟。
    2. 瞬态效应:在滤波开始和结束时,由于滤波器内部状态未稳定,输出端会产生瞬态响应,导致信号起始和结束部分畸变。对于有限长信号,一种缓解方法是先对信号进行镜像扩展,再滤波,最后截取中间部分。更简单的方法是接受开头和结尾一小段数据的失真,在分析时忽略它们。
    3. 滤波器阶数过高:阶数太高的滤波器虽然频率响应好,但瞬态响应可能更长,延迟也更大。需要在性能和延迟之间权衡。

7.4 绘图时图形混乱或重叠?

  • 现象:新画的图覆盖了旧的,或者多个曲线挤在一个坐标系里看不清。
  • Scilab绘图命令技巧
    • figure(n):创建或切换到第n个图形窗口。每个窗口是独立的。
    • clf():清除当前图形窗口中的所有内容。
    • subplot(m,n,p):将当前图形窗口划分为m x n个子图,并激活第p个子图进行绘制。
    • hold on/hold off:在当前坐标系中保持图形(允许叠加绘图)或取消保持。
    • 在绘制多条曲线时,善用legend函数添加图例,并用不同的颜色(‘r‘,‘g‘,‘b‘,‘m‘,‘c‘,‘k‘)和线型(‘-‘,‘–‘,‘:‘,‘-.‘)区分。

7.5 脚本运行慢?

  • 现象:处理长信号或进行大量循环计算时,脚本执行速度很慢。
  • 优化策略
    1. 向量化操作:这是Scilab/MATLAB编程的核心思想。尽量避免使用for循环对数组元素逐个操作。像x = A * sin(2*%pi*f*t)这样的操作是向量化的,一次计算整个数组,速度极快。
    2. 预分配数组:如果必须使用循环,且结果需要存入一个数组,请先用zerosones函数预分配一个足够大的数组。例如:result = zeros(1, N);。这避免了循环中数组大小动态增长带来的巨大开销。
    3. 使用内置函数:Scilab的内置函数(如fft,filter,conv等)都是用C/Fortran优化过的,比自己写的循环快几个数量级。
    4. 减少图形刷新:在循环中更新图形(如plot)会严重拖慢速度。可以将数据先计算并存储起来,循环结束后再统一绘图。

信号处理是一门实践性极强的学科。我个人的体会是,最初的理解往往来自于书本和公式,但真正的掌握和“感觉”则来自于像这样在Scilab中一遍遍地生成信号、观察波形、变换频谱、添加噪声、设计滤波器并观察效果。每一个参数的变化带来的影响都直观可见,这种反馈是无可替代的。当你能够熟练地用这些工具去验证理论、模拟现象时,你就已经从一个被动的知识接收者,转变为一个主动的探索者了。不妨以这个项目为起点,尝试修改参数,比如改变噪声类型(试试脉冲噪声?)、设计一个带通滤波器来提取特定频段、或者模拟更复杂的调制方式(如频率调制FM),你会发现一片更广阔的天地。

← 返回列表