news 2026/10/6 13:30:07

MATLAB标准测试信号详解:blocks、bumps与doppler的生成与去噪应用

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
MATLAB标准测试信号详解:blocks、bumps与doppler的生成与去噪应用

做信号处理的这些年,我越来越觉得“标准测试信号”是个好东西。平时写个去噪算法、做个压缩感知实验,总不能老拿一段白噪声或者自己现场录的语音来验证效果,既没法复现,也没法和别人的结果对比。后来我接触到Donoho和Johnstone在经典论文里常用的那组标准测试信号:blocks、bumps和doppler,一下子觉得实验规范多了。这三个名字在MATLAB领域几乎成了小波去噪、稀疏表示、压缩感知这些方向的“通用语言”,你只要说“用blocks信号加噪,再用XX算法去噪”,同行立刻就知道你在测什么。

这篇文章我就把这几个信号的来龙去脉、MATLAB实现代码、参数怎么调、有哪些坑,一次性讲清楚。不绕弯子,直接上干货,保证你照着敲完代码就能跑出结果。

1. 三个标准测试信号的数学本质与设计初衷

1.1 为什么偏偏是blocks、bumps和doppler

很多刚接触的人会问:测试信号那么多,为什么几十年来大家反复用这三个?先说结论:因为它们各自代表了信号处理里最典型、最难处理的局部特征,用来检验算法非常“刁钻”。

blocks是分段常数信号,中间有一堆突变的“跳跃点”。这种信号考验的是算法对边缘、跳变的保持能力——你一平滑,台阶就糊了;你不敢平滑,噪声又去不掉,就这么烦人。bumps则是几个形状尖锐、幅度差异很大的脉冲峰,峰和峰之间还有宽窄变化,专门用来检验算法对瞬态成分和局部奇异的捕捉能力。doppler更狠,它是一个频率随时间剧烈变化的调频信号,而且幅度还跟着时间轴起伏,专门考验算法在时频分析上的分辨率,尤其是对高频段局部细节的还原。

Donoho那批人设计这些信号的时候,目的非常明确:一个信号里既要有多尺度结构,又要有局部突变,还要有全局趋势,这样算法好不好,一测就露馅。这也是为什么这些信号能一直火到现在,而不是被随机噪声或者正弦波取代。

1.2 数学定义逐项拆解

三个信号在MATLAB里的标准实现都有明确公式,先看数学定义再写代码,不容易出边界错误。

blocks信号是若干个不同高度、不同位置的单位阶跃函数的加权叠加。它的定义可以写成如下形式:

$$ f(t) = \sum_{j=1}^{m} h_j \cdot \mathbb{I}(t \ge p_j) $$

其中$$\mathbb{I}(\cdot)$$是指示函数,$$p_j$$是跳变位置,$$h_j$$是对应的跳变幅度。原始论文里给了一组固定参数:位置$$p$$为 [0.10, 0.13, 0.15, 0.23, 0.25, 0.40, 0.44, 0.65, 0.76, 0.78, 0.81],对应的高度$$h$$为 [4, -5, 3, -4, 5, -4.2, 2.1, 4.3, -3.1, 2.1, -4.2]。这些参数不是随便拍的,负高度意味着向下跳,高度绝对值大小决定了信号动态范围,这样合出来的信号既有大幅跳变又有小幅波动。

bumps信号的定义相对复杂一点,它是一组以位置$$p_j$$为中心、宽度$$w_j$$为尺度的有理函数峰叠加:

$$ f(t) = \sum_{j=1}^{m} \frac{h_j}{\left( 1 + \left|\frac{t - p_j}{w_j}\right| \right)^4} $$

这里$$h_j$$是峰高,$$w_j$$是峰宽。注意分母是四次方而不是平方,这样衰减更快,峰的形状更尖。位置参数和blocks基本一样,但峰的个数、高度、宽度都有微调,具体参数后面代码里直接给。

doppler信号则是一个典型的调幅调频信号:

$$ f(t) = \sqrt{t(1-t)} \cdot \sin\left( \frac{2\pi \cdot 1.05}{t + 0.05} \right) $$

其中$$t$$从 $$1/n$$ 取到 1。根号项是一个平滑的幅度包络,在两端趋于零;正弦项的瞬时频率随$$t$$增大而升高,越靠近右端频率越高,而且因为分母里加了0.05,避免在$$t=0$$处产生奇异。这个信号做小波分析的时候,高频段的系数会非常密,能有效考察时频局部化能力。

1.3 三个信号的典型应用场景

这三个信号在学术论文里出场频率极高。我列几个最常见的场景供你参考,实际用的时候可以直接对号入座。

第一类是信号去噪与恢复对比。这是最经典的应用,往三个干净信号上叠加不同强度的高斯白噪声,然后跑各种去噪算法(小波阈值、全变分、字典学习等),用信噪比提升、均方误差这些指标来横向对比。因为信号本身就是公开标准,谁都能复现,结论才有说服力。

第二类是压缩感知与稀疏重建。blocks和bumps信号在某个小波基或字典下通常是稀疏的,用来验证压缩感知重构算法非常合适。doppler则因为频率变化剧烈,更考验基函数的适配程度。

第三类是时频分析与特征提取教学。我在高校做技术分享的时候经常用这三个信号当课堂案例,因为它们特征清晰、可视化效果好,学生一眼就能看出不同算法对局部特征的保留差异。

2. MATLAB代码实现与逐行拆解

2.1 手把手生成blocks信号代码

我直接贴出一份可靠的实现,这段代码与文献中常用的Wavelab工具包逻辑一致,但去掉了多余依赖,适合复制到自己的脚本里。

function x = sig_blocks(n) % n: 信号长度,建议取 2 的幂,如 1024、2048 if nargin < 1 n = 1024; end pos = [0.10 0.13 0.15 0.23 0.25 0.40 0.44 0.65 0.76 0.78 0.81]; hgt = [4.0 -5.0 3.0 -4.0 5.0 -4.2 2.1 4.3 -3.1 2.1 -4.2]; t = (1:n) / n; x = zeros(1, n); for j = 1:length(pos) x = x + hgt(j) * ( t >= pos(j) ); end end

这里有个关键设计:(t >= pos(j))返回逻辑数组,在位置参数之后为1,之前为0。因为MATLAB里逻辑数组能直接参与数值运算,所以累加过程非常干净。要注意的是我把时间轴设成了从 $$1/n$$ 到 1,而不是从0开始,这样能避免在t=0处产生不必要的边界台阶。

有的版本会把t定义为(0:n-1)/n,即从0取到(n-1)/n。两种写法画出来的图形状略有不同:从0开始时,信号在起点附近正好落在某个跳变沿上;从1/n开始时,起点附近通常是0值平台。我个人建议使用从1/n开始的版本,因为和doppler信号的实现保持一致,三套信号的时间轴完全对齐,后续叠加加噪会更方便。

2.2 bumps信号的MATLAB实现

bumps信号的参数比blocks多一组峰宽,代码结构也更丰富。

function x = sig_bumps(n) % n: 信号长度,建议取 2 的幂 if nargin < 1 n = 1024; end pos = [0.10 0.13 0.15 0.23 0.25 0.40 0.44 0.65 0.76 0.78 0.81]; hgt = [4.0 5.0 3.0 4.0 5.0 4.2 2.1 4.3 3.1 5.1 4.2]; wth = [0.005 0.005 0.006 0.01 0.01 0.03 0.01 0.01 0.005 0.008 0.005]; t = (1:n) / n; x = zeros(1, n); for j = 1:length(pos) x = x + hgt(j) ./ (1 + abs((t - pos(j)) / wth(j))).^4; end end

bumps信号的不同之处在于每个峰的高度全部为正,但峰宽参数差异很大。最窄的峰宽是0.005,在1024个采样点上大约只占5个点左右,非常尖锐;最宽的峰宽是0.03,能在图上占30个点左右,看起来就平缓很多。这种多尺度特性,正好可以用来检验算法对“不同宽度瞬态”的敏感程度。

实现的时候注意点运算符:./和.^都不能漏。因为t是向量,wth(j)是标量,分子分母都是数组,必须用点除。我第一次写这段代码时把./写成了/,MATLAB直接报矩阵维度不匹配,排查了半天才发现是符号问题。

2.3 doppler信号代码与频率特性分析

doppler信号的代码是三段里最简洁的,但它的信号特性却最复杂。

function x = sig_doppler(n) % n: 信号长度,建议取 2 的幂 if nargin < 1 n = 1024; end t = (1:n) / n; x = sqrt(t .* (1 - t)) .* sin(2 * pi * 1.05 ./ (t + 0.05)); end

这段代码看起来短,但有两处细节值得说。第一,t .* (1 - t)是点乘,得到的是逐元素的乘积,不是矩阵乘法,少了点运算符结果就完全不对。第二,sin函数内部是2 * pi * 1.05 ./ (t + 0.05),其中./把标量除以整个向量,得到瞬时角频率的积分效果。

从频域角度看,doppler信号的瞬时频率可以近似写作:

$$ f_{\text{inst}}(t) \approx \frac{1.05}{(t + 0.05)^2} $$

也就是说在t接近0的时候频率极高,在t接近1的时候频率较低,整个信号呈现出“从高频啁啾到低频”的扫频特性。再加上根号项在两端压低了幅度,信号起点和终点附近能量都很弱,形成了典型的“两端尖中间鼓”的时频分布形态。

2.4 封装一个万能生成函数

实际做实验的时候,我通常不会一个个调用上面的单信号函数,而是封装成一个总入口,想生成哪个就生成哪个,顺便把默认参数也管起来。

function [x, t] = gen_test_signal(sigName, n) % 按名称生成标准测试信号 % sigName: 'blocks' / 'bumps' / 'doppler' % n: 采样点数,默认1024 % x: 信号值 % t: 对应的时间轴 if nargin < 2 n = 1024; end t = (1:n) / n; switch lower(sigName) case 'blocks' x = sig_blocks(n); case 'bumps' x = sig_bumps(n); case 'doppler' x = sig_doppler(n); otherwise error('未知信号名:%s', sigName); end end

封装之后,以后跑对比实验就非常省事。比如我要比较三种信号在同样噪声水平下的去噪效果,直接循环调用这个函数就行,不用每个地方都写一遍t的定义。这也是我个人的一个习惯:把试验中常用的小工具尽量都包成函数,既能减少重复代码,也能避免因为某次复制粘贴漏改参数而导致结果对不上。

3. 信号可视化与噪声注入的实操细节

3.1 画图脚本:一张图看出信号结构

生成信号之后,第一步永远是画图观察。如果图形形态不对劲,后面的实验基本没有意义。我提供一个标准画图脚本,你可以直接拿来用。

n = 1024; t = (1:n) / n; x_blocks = sig_blocks(n); x_bumps = sig_bumps(n); x_doppler = sig_doppler(n); figure('Color', 'w', 'Position', [100 100 1200 700]); subplot(3, 1, 1); plot(t, x_blocks, 'LineWidth', 1.2); title('Blocks 信号'); xlim([0 1]); ylim([-6 6]); grid on; subplot(3, 1, 2); plot(t, x_bumps, 'LineWidth', 1.2); title('Bumps 信号'); xlim([0 1]); ylim([0 7]); grid on; subplot(3, 1, 3); plot(t, x_doppler, 'LineWidth', 1.2); title('Doppler 信号'); xlim([0 1]); grid on;

画图时有两个直观检查点。第一个是blocks信号,如果画出来台阶边缘是斜的而不是垂直突变,说明采样点太少,比如n只有64时台阶看起来就有点“拖泥带水”;第二个是bumps信号,如果峰的最高点不在0.25附近,而是跑到别处去了,说明pos参数或wth参数敲错了。

另外建议把ylim固定下来,特别是blocks信号。因为它的动态范围从-5到5,如果不固定纵轴,每次画图的范围可能不同,视觉效果上会造成信号形态“好像变了”的错觉。

3.2 加噪声:信噪比到底怎么算才规范

标准测试信号的一大用途就是加噪实验。加噪的办法本身很简单,就是给干净信号叠加上高斯白噪声,但信噪比的定义需要严谨。

信噪比通常用dB表示,定义式是:

$$ \text{SNR}{\text{dB}} = 10 \log{10} \left( \frac{P_{\text{signal}}}{P_{\text{noise}}} \right) $$

其中$$P_{\text{signal}}$$是信号的平均功率,一般用均方值$$\frac{1}{n}\sum x_i^2$$来估计;$$P_{\text{noise}}}$$是噪声的平均功率,即噪声方差$$\sigma^2$$。所以给定目标SNR后,噪声标准差的计算公式是:

$$ \sigma = \sqrt{\frac{P_{\text{signal}}}{10^{\text{SNR}/10}}} $$

MATLAB里面可以这样写:

function noisy = add_awgn_noise(x, snr) % 按指定SNR(dB)添加高斯白噪声 P_signal = mean(x.^2); sigma = sqrt(P_signal / (10^(snr / 10))); noise = sigma * randn(size(x)); noisy = x + noise; end

这里有几个细节必须注意。第一,randn每次生成不同的噪声,如果要做可复现实验,运行前要设随机种子,比如rng(2024),否则每次结果都不一样,论文里的数据也会对不上。第二,我的实现是按照噪声方差等于要加的标准差平方来生成的,没有额外缩放,所以SNR计算公式严格成立,不要在这上面再乘任何系数。第三,实际算信噪比的时候,有人的习惯是用snr(x, noise)函数,但要注意这个函数对噪声功率的计算方式是“估计噪声方差”,和我这里直接根据定义生成的逻辑在结果上非常接近,但不会完全一致,写实验报告时说明清楚就好。

3.3 把生成结果保存成文件

有些场景下,比如要在Python里接着处理,需要把MATLAB生成的信号导出成.mat或CSV文件。我最常使用的是.mat格式,命令很简单:

n = 2048; x_blocks = sig_blocks(n); x_bumps = sig_bumps(n); x_doppler = sig_doppler(n); save('test_signals_2048.mat', 'x_blocks', 'x_bumps', 'x_doppler');

如果要在其他语言里读取,我更推荐CSV格式,一行代码搞定:

s = [gen_test_signal('blocks', 2048); ... gen_test_signal('bumps', 2048); ... gen_test_signal('doppler', 2048)]; writematrix(s, 'test_signals_2048.csv');

导出的时候要注意:决定保存之前,先想清楚后续处理需要哪个版本的信号。如果是要对比不同算法的去噪效果,最好从同一份干净信号出发“一次性”生成所有加噪版本,再分别处理,这样才公平。如果你每跑一个算法就重新生成一次干净信号,虽然理论上一样,但因为随机种子的关系,结果反而容易产生无谓的细微差异。

4. 常见问题与排查技巧实录

4.1 生成的blocks信号为什么和其他论文的图对不上

这是被问得最多的一个问题。明明照着网上代码敲的,为什么画出来的blocks信号和论文里的台阶位置不太一样?

原因多半在于时间轴的定义。不同版本的代码有的把t定义成(0:n-1)/n,有的定义成(1:n)/n,这会导致台阶在零点附近的位置不同。文献里Wavelab工具箱用的是(1:n)/n,所以你在复现论文时最好用这个定义。如果你发现自己的信号整体向右或向左偏移了一个采样点,那多半是时间轴差了一个1/n。

还有一个常见原因是对参数pos和hgt抄错。网上流传的版本很多,有些是早期人翻录时把-4.2抄成了-4.1,把0.40抄成了0.4(数值上等价但倍数没对齐)。我建议以本文或Wavelab官方源码中的参数为准。

4.2 信号的边界效应怎么处理

做小波去噪时,经常有人发现信号两端会出现“飞边”或“振荡”,比如doppler信号在t接近1的位置波形发生明显畸变。这不是信号生成的问题,而是后续算法处理时的边界效应。

信号在有限长度上截断,就相当于乘了一个矩形窗,频率域里会产生旁瓣泄漏。要减轻这种效应,有三种常见做法:

  • 对小波分解时扩展模式改为“对称延拓”,MATLAB里用dwtmode('sym')。
  • 对信号两端做余弦渐变衰减或反射延拓,然后再处理。
  • 用周期化延拓方式重构后裁剪掉延拓区域。

我个人经验是,如果你只是比较算法相对优劣,保持所有算法用同一种边界处理方式即可;但如果要追求绝对高的重建精度,那最好把信号长度取得足够长,并把边界区域排除在评价指标之外。

4.3 加噪后信号信噪比不对,怎么排查

很多新手会发现自己加完噪声之后,用snr函数算出来的实际信噪比和预设值差了好几个dB,于是开始怀疑是不是代码写错了。

我排查这类问题一般按顺序检查三件事。第一,randn生成的是标准正态分布,均值为0、方差为1,先确认自己生成的噪声数组满足这个统计特性,样本量太小时均值和方差会有随机波动,这个正常。第二,检查信号功率的估计方式,用mean(x.^2)还是sum(x.^2)/n结果基本一样,但如果信号包含直流分量,比如blocks信号的平均值不为0,有人会先减均值再算功率,这样得到的噪声方差会偏大或偏小。第三,检查计算信噪比的公式里是否用了10还是20。功率比要用10,如果误用20,算出来的值会翻倍,这是最容易犯的错。

4.4 多算法对比时的公平性问题

最后再说一个做实验时容易忽略的公平性问题。如果你拿这三个信号对比三种算法,那么务必保证:所有算法读入的是同一份加噪数据,而不是每轮重新生成。否则随机噪声的不同会让比较结果产生额外波动,搞得算法的好坏根本说不清。

好的做法是,先一次性生成所有测试信号并加好噪声,存成MAT文件,然后所有算法都从这个文件里读数据。我在自己的实验里还会额外保存一个seed_info.txt,记录当时用到的随机种子和信噪比参数,这样一个月之后回头查数据,还能准确复现当时的实验条件。

5. 一个实用小技巧:把三信号做成实时函数

可能有人觉得上面封装的gen_test_signal已经够用了,我这边再分享一个更灵活的做法。实际做算法调试时,我有时会希望直接改参数看效果,比如把bumps信号的峰宽整体调大,看算法对平滑峰是否更加敏感。所以我平时会把参数表定义成可选项。

function x = sig_bumps(n, scaleWidth) if nargin < 2 scaleWidth = 1; end wth = [0.005 0.005 0.006 0.01 0.01 0.03 0.01 0.01 0.005 0.008 0.005] * scaleWidth; % ...(其余不变) end

有了这个参数,调峰宽就是个乘法的事。同理,doppler信号的频率参数也可以做成输入项。这种小改动对写论文或做演示很有用,因为审稿人或听众如果质疑“你的算法只在固定频率范围上有效”,你可以直接现场调参数,再跑一遍结果,说服力比讲一堆理论要强得多。

我个人在实际使用中还有一个体会:不要把这三个信号神化。它们虽然经典,但都是有特定偏好的测试集,比如blocks信号对边缘检测算法的挑战大,对纯频谱类算法就相对宽松。做实验时最好把它们和随机信号、真实信号配合使用,综合作出判断。毕竟标准测试信号的核心价值在于“可复现、可比对”,而不是指望一套信号覆盖天下所有问题。

最后,如果你刚开始接触这一块,我的建议很简单:先把本文的代码原样跑一遍,画出图,加个噪声,再试着用小波阈值去噪看看效果,这一套流程走下来,你对这三个信号的脾气基本就摸透了。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/6 13:28:51

Surfer与ArcGIS无缝衔接:等值线面生成与GIS出图全流程实操

干这行这么多年&#xff0c;我越来越发现一个真相&#xff1a;光会ArcGIS不够&#xff0c;光会Surfer更不够。尤其是涉及做等值线、等值面这种活儿&#xff0c;很多项目要求成果必须进GIS系统&#xff0c;要和行政区划、地块边界、监测点位叠在一起出图&#xff0c;但Surfer的图…

作者头像 李华
网站建设 2026/10/6 13:27:59

跨越1948-2025:金融许可信息数据库建设与历史沿革数据模型实践

1. 项目缘起与总体设计思路1.1 为什么是1948-2025这个时间跨度做金融数据的人都知道&#xff0c;许可信息是金融行业最底层的"身份档案"。没有许可&#xff0c;机构就不存在&#xff0c;业务就是违规&#xff0c;合同可能无效&#xff0c;风险处置无从谈起。我最初拿…

作者头像 李华
网站建设 2026/10/6 13:27:37

数据库基础操作全解析:创建、查看、选择与删除

1. 动手前的准备&#xff1a;理解数据库操作的最小闭环 这些年带过不少新人&#xff0c;也帮同事排查过线上事故&#xff0c;发现大家对数据库的理解经常卡在一个错位的地方。很多人一上来就问“怎么建库”&#xff0c;但真正干活的时候&#xff0c;第一关不是SQL语法&#xff…

作者头像 李华
网站建设 2026/10/6 13:27:19

MySQL安装全指南:Windows/Linux/Docker平台避坑与配置详解

1. 安装之前&#xff0c;必须先想明白的三件事 先把话放前面&#xff1a;MySQL服务安装这个事&#xff0c;本身不难&#xff0c;难的是装完起不来、起来连不上、连上跑不动。我这些年帮人装过太多MySQL&#xff0c;Windows的、Linux的、Docker的&#xff0c;发现问题大多出在装…

作者头像 李华
网站建设 2026/10/6 13:27:15

Agent Skills 实战指南:从概念到云端部署与工程化治理

1. 从“skills”这个标题说起&#xff1a;它到底在指什么第一次看到“skills”这个标题&#xff0c;很多人会以为是某个招聘网站上的技能标签&#xff0c;或者是一份简历里的能力清单。但结合热搜词里反复出现的 Agent Skills、Google Cloud、GKE、Genkit、codex skills、claud…

作者头像 李华
网站建设 2026/10/6 13:26:30

SQL Server与MySQL语法差异对照:从连接配置到迁移踩坑全指南

干了这么多年数据库开发&#xff0c;SQL Server 和 MySQL 我几乎每天都在切着用。最烦的不是 SQL 写不出来&#xff0c;而是同一个思路在两个库里的写法看起来差不多&#xff0c;跑起来却一个对、一个错&#xff0c;查半天才发现是方言差异。这次我把平时自己备忘的那些点重新整…

作者头像 李华