☰
常规波束形成MATLAB实现:从原理、代码到MVDR升级指南
2026/10/15 8:25:48 网站建设 项目流程

简介:这是一份用于常规聚焦波束形成的MATLAB仿真程序,面向水声信号处理、阵列信号处理等方向的学习者与研究人员,可帮助理解波束形成的基本原理与实现流程。程序以M=11元均匀线阵为例,通过设置声速、采样频率、阵元间距及声源位置等参数,依次完成阵列坐标生成、声程差计算、信号与噪声模拟、接收数据自协方差矩阵求解,并在扫描范围内完成聚焦波束扫描成像,最终输出归一化后的三维mesh声图和二维伪彩图,同时提供峰值定位与主瓣宽度估计。压缩包共1个doc文件,约29KB,文档中包含完整MATLAB脚本及关键步骤注释,结构清晰,便于直接运行和二次修改,适合作为课程设计、毕业设计或论文仿真的参考。资源已有1678人学习,适合正在学习常规波束形成、需要结合仿真理解空间谱成像的入门及进阶用户。

1. 常规波束形成没那么玄:十几行MATLAB就能跑通

常规波束形成(CBF,Conventional Beamforming)是阵列信号处理里最不值得恐惧的一块:它不做任何统计自适应,只靠“把阵元信号对齐到某个方向”这一件事,就能换来整排天线阵的阵列增益。配合MATLAB,十几行代码就能画出波束图、扫出信号来向,整个过程几乎没有黑匣子。可现实中很多人一上来就跳去自适应波束形成,被协方差矩阵求逆和特征分解搞得一头雾水,回头才发现CBF的空间谱才是所有高级算法的白盒基准。这篇笔记面向两类人:正在做DOA估计或阵列接收的工程师,以及课程作业里被“波束形成”困住的学生。目标很简单——用一段可复现的MATLAB程序,把常规波束形成的原理、参数、坑和下一步升级一次讲透。

2. 常规波束形成原理与最小可运行代码:从对齐时延到相位旋转

2.1 从时延对齐到相位旋转:CBF的数学骨架

阵列信号处理里有个朴素到容易被忽略的事实:把多根天线收到的信号对齐后再合并,得到的信噪比提升是单天线无法获得的。常规波束形成要做的正是这件事。考虑一个等间距排列的N元均匀线阵,阵元间距为d,一个远场窄带信号从θ方向入射。以第一个阵元为参考,第n个阵元相对它的传播时延是τ_n = nd sinθ / c。如果信号是窄带的,这个时延在频域里等价于一个相位旋转φ_n = -2π nd sinθ / λ。CBF做的就是:在假设来向为θ_s时,把每个阵元的相位反向补偿回去,让N路信号同相叠加。

把对某个方向θ_s的补偿写成向量a(θ_s) = [1, exp(-j2πd sinθ_s / λ), …, exp(-j2π(N-1)d sinθ_s / λ)]^T,这就是阵列导向矢量。波束输出为y = a^H(θ_s) x,当θ_s和真实来向θ一致时,各路信号对齐,输出模值最大;理想情况下输出幅度是单阵元的N倍,功率是N^2倍。当θ_s偏离真实来向时,相位补偿不匹配,输出功率下降。把θ_s扫过整个空域,得到的空间谱就是CBF的波束图。这套思路在教科书里叫延迟-求和波束形成,在工程里直接叫常规波束形成或时延相加波束形成。

CBF之所以值得先跑通,是因为它不依赖信号统计特性,只依赖阵列几何和来向假设,结果可以手算验证。我接触过的自适应算法项目,几乎都是从一版CBF空间谱开始的——先确认数据链路和阵列流型没问题,再上复杂度高的算法,否则翻车时根本分不清是数据问题还是算法问题。

两个默认前提要记牢。第一是窄带假设:CBF的相位补偿只在单一频率上严格成立,宽带信号必须分频带处理或用FIR时延滤波器代替移相器,否则波束会发散。第二是阵元间距:均匀线阵默认d = λ/2,这是空间采样定理的结论。超过半波长会出现栅瓣,小于半波长则孔径利用率下降、分辨率变差。这两个前提在后面的参数章节里会反复遇到。

2.2 12行MATLAB跑通CBF:最小可运行版本

下面是最小可运行版本,不追求效率,只求把CBF的骨架讲清楚。信号用复基带随机序列模拟,主程序加一个扫描循环,总共不到20行。

% 最小可运行的常规波束形成(CBF)示例 % 均匀线阵 + 远场窄带信号,8阵元,半波长为间距 c = 3e8; % 光速(m/s) fc = 2.4e9; % 工作频率(Hz) lambda = c / fc; % 波长(m) d = lambda / 2; % 阵元间距:半波长 N = 8; % 阵元数 theta_true = 30; % 信号真实来向(度) n_snap = 1024; % 快拍数 rng(0); % 复基带随机信号:N路阵元共享同一个包络 s = (randn(1, n_snap) + 1j*randn(1, n_snap)) / sqrt(2); % 真实来向的导向矢量 a_true = exp(-1j * 2 * pi * d * (0:N-1).' * sind(theta_true) / lambda); % 模拟接收数据:N x n_snap x = a_true * s; % 扫描角度 theta_scan = -90:0.5:90; p = zeros(size(theta_scan)); for k = 1:numel(theta_scan) a_scan = exp(-1j * 2 * pi * d * (0:N-1).' * sind(theta_scan(k)) / lambda); y = a_scan' * x; % 波束加权求和 p(k) = mean(abs(y).^2); % 快拍平均功率 end p_dB = 10 * log10(p / max(p)); plot(theta_scan, p_dB, 'LineWidth', 1.5); xlabel('扫描角度(度)'); ylabel('归一化功率(dB)'); grid on; ylim([-40 5]);

这段代码里,(0:N-1).'生成N×1的阵元序号列向量,是MATLAB写导向矢量最常用的技巧,后续写MVDR、ESPRIT都会复用。sind()保证角度单位是度,如果换成sin()且角度按度传入,峰值位置会偏到完全对不上。a_scan' * x是共轭转置加权求和,本质是空间匹配滤波;mean(abs(y).^2)对快拍维取平均功率,最后归一化成dB方便看旁瓣相对电平。

实际调试时值得动三个参数:N从8改成4或16,直接观察主瓣变宽还是变窄;theta_scan的步进从0.5°改成1°或0.1°,观察峰值是否抖动;d从λ/2改成0.7λ,观察第二个峰何时冒出来。每改一次重新看图,比背公式有用得多。

提示:a_true和a_scan的相位符号只要收发一致,峰值位置就正确;如果两处符号同时取反,空间谱会在-30°出现峰值,这是正常现象,不是bug。

想观察噪声影响,把x替换成x_noisy = x + 10^(-snr_db/20) * (randn(N, n_snap) + 1j*randn(N, n_snap)) / sqrt(2),SNR越低波束图底噪越高,但峰值位置基本不动,这就是CBF稳健性最直观的体现。

3. 把CBF封装成MATLAB函数:签名设计、三个调用场景与输出约定

3.1 封装成cbf_scan函数:签名、输入约定与参数选择

脚本能跑通只是第一步。实际工程里,DOA扫描、空域滤波、多波束接收三个场景的代码主体完全一样,差别只在加权向量和输出维度。我一般会把CBF封装成独立函数,避免每个脚本复制粘贴一长串扫描循环。另一个更现实的理由是:后续切换到自适应波束形成时,函数里只改一行加权向量,CBF和MVDR的实测对比成本会低很多。

function [p_dB, theta_out] = cbf_scan(x, d, lambda, theta_scan) % 常规波束形成空间谱扫描 % 输入: % x N x snap 复数阵列数据,N为阵元数 % d 阵元间距(米) % lambda 波长(米) % theta_scan 1 x M 扫描角度(度) % 输出: % p_dB 1 x M 归一化空间谱(dB) % theta_out 1 x M 扫描角度(度) N = size(x, 1); theta_out = theta_scan; p = zeros(size(theta_out)); for k = 1:numel(theta_out) a = exp(-1j * 2 * pi * d * (0:N-1).' * sind(theta_out(k)) / lambda); a = a / N; % 均匀加权归一化 p(k) = mean(abs(a' * x).^2); end p_dB = 10 * log10(p / max(p)); end

这里的关键设计是a = a / N,把加权向量归一化。归一化后输出量纲是阵元平均功率,峰值位置不变,方便后续和MVDR等算法横向对比;如果不归一化,峰值功率是N^2倍,dB图只差一个常数,但在对比输出信干噪比时会非常别扭。

参数选择上,theta_scan的步进一般先用0.5°或1°做粗扫,锁定峰值区间后再用0.05°细扫。x必须是复数IQ结构,如果数据文件里只有实部,不要直接喂给这个函数,先做希尔伯特变换恢复解析信号。

3.2 调用场景A:全空域扫描与DOA估计

最常见的调用方式是用CBF做全空域扫描,直接读空间谱峰值位置作为DOA估计结果。假设有两个等功率信号分别从-20°和10°入射,封装之后的调用代码很干净:

n_snap = 1024; theta1 = -20; theta2 = 10; d = 0.0625; % 2.4GHz下半波长(米),也可用lambda/2计算 lambda = 0.125; a1 = exp(-1j * 2 * pi * d * (0:N-1).' * sind(theta1) / lambda); a2 = exp(-1j * 2 * pi * d * (0:N-1).' * sind(theta2) / lambda); s2 = (randn(2, n_snap) + 1j*randn(2, n_snap)) / sqrt(2); x2 = a1 * s2(1,:) + a2 * s2(2,:); [p_dB, theta] = cbf_scan(x2, d, lambda, -90:0.5:90); plot(theta, p_dB);

这个场景的边界条件很直观:两个信号角度间隔40°,CBF能清晰分辨出两个峰。如果间隔缩小到5°,8元阵的波束宽度大约12.7°,两个峰会合并成一个宽包络,峰值位置落在中间,这就是CBF的分辨率极限。遇到这种情况,要么增加阵元数,要么换高分辨率算法,靠CBF硬扛是扛不过去的。

3.3 调用场景B:固定方向滤波与多波束接收

DOA扫描输出的是功率值,但很多场景需要直接输出某个方向的信号时间序列。固定方向空域滤波的封装更简单:

function y_beam = cbf_filter(x, d, lambda, theta) % 固定方向波束输出:返回对齐后的时间序列 N = size(x, 1); a = exp(-1j * 2 * pi * d * (0:N-1).' * sind(theta) / lambda); w = a / N; y_beam = w' * x; % 1 x snap end

使用时,如果只想提取10°方向的信号,调用y_beam = cbf_filter(x2, d, lambda, 10)即可。得到的y_beam是加权求和后的复时间序列,可以直接做解调、频谱分析或后续的编码解码,而不是只有一个功率值。这里要注意:CBF对非期望方向没有主动抑制能力,干扰信号只是被波束方向图的固定旁瓣衰减,典型旁瓣电平约-13dB;如果干扰很强,泄漏仍然可观,这个场景正是第4章加窗和第6章MVDR的意义所在。

多波束接收是雷达和声呐里最常见的形态,多个固定波束并行输出:

thetas = [-20, 10, 40]; Y = zeros(length(thetas), size(x2, 2)); for i = 1:length(thetas) a_i = exp(-1j * 2 * pi * d * (0:N-1).' * sind(thetas(i)) / lambda); Y(i, :) = (a_i / N)' * x2; end

Y的每一行就是一个方向的波束输出,所有波束并行工作。需要提醒的是,这里每个波束都没有做旁瓣抑制,如果想压低旁瓣,就得把a_i / N替换成导向矢量乘以窗向量再归一化,这正是下一章的内容。

4. 波束图三个必调参数:阵元数、窗函数、阵元间距

4.1 阵元数N决定角度分辨力:一张表摸清孔径上限

均匀线阵、均匀加权的半功率波束宽度近似为BW_3dB ≈ 0.886 * λ / (N * d),单位弧度。当d = λ/2时,λ/(N*d) = 2/N,于是得到一张非常实用的对照表:

| 阵元数 N | 半功率波束宽度 | 大致可分辨的最小角度间隔 | | 4 | 约 25.4° | 约 ±12.7° | | 8 | 约 12.7° | 约 ±6.3° | | 16 | 约 6.4° | 约 ±3.2° |

这张表的含义很朴素:物理孔径N*d决定了角度分辨力的天花板,加窗还会让主瓣变宽约1.2到1.6倍,实际分辨力要再打折。比如想分辨间隔5°的两个目标,8元阵勉强够,16元阵才比较舒服。工程上常见的错误是只加阵元数却不考虑波长:同样的N和d,工作频率升高后λ变小,波束宽度会显著变窄,这既是好事也是坑——高频段阵列的角度校准误差会更敏感。

4.2 窗函数控制旁瓣:加窗代码与主瓣代价

均匀加权的第一旁瓣电平是-13.2dB,在很多场合高得不能接受。加窗的本质是对各阵元幅度再加一组权,压低阵元域上的副瓣泄漏。工程上最常用的是汉宁窗和切比雪夫窗,对比关系如下:

| 窗类型 | 第一旁瓣电平 | 主瓣宽度倍数(相对均匀加权) | | 均匀 | -13.2 dB | 1.00 | | 汉宁(周期) | -31.5 dB | 约 1.62 | | 切比雪夫 30 dB | -30 dB | 约 1.18 | | 切比雪夫 40 dB | -40 dB | 约 1.28 |

切比雪夫窗的最大优势是旁瓣电平可精确设置,N固定时想让旁瓣是-30dB还是-50dB都能做到。代价是主瓣宽度随旁瓣要求提高而变宽,而且N较小时边缘阵元权重会很小,等效孔径缩水。加窗的MATLAB代码非常短:

% 构造一个含30°信号的接收数据x s30 = (randn(1, n_snap) + 1j*randn(1, n_snap)) / sqrt(2); a30 = exp(-1j * 2 * pi * d * (0:N-1).' * sind(30) / lambda); x = a30 * s30; % 均匀窗加权 w_uniform = a30 / N; % 切比雪夫窗,40dB旁瓣 win_cheb = chebwin(N, 40).'; w_cheb = (a30 .* win_cheb(:)) / sum(win_cheb); % 对比两种加权的输出功率 y_uniform = abs(w_uniform' * x); y_cheb = abs(w_cheb' * x);

窗向量是实数,导向矢量是复数,用点乘把窗应用到每个阵元上,再做求和归一化。从频域角度看,加窗相当于对空间谱做周期卷积,效果是旁瓣压低、主瓣展宽。N小于8时,切比雪夫窗的边缘阵元权重会掉得非常厉害,波束宽度比理论值宽不少,这是模板里最容易被忽略的边界条件。

4.3 阵元间距与栅瓣:半波长为什么是红线

阵元间距d大于λ/2时,除了真实来向外,某些扫描角会得到同样高的输出,这些位置叫栅瓣。栅瓣位置由sin(θ_g) = sin(θ_true) + mλ/d决定,m为整数。用d = 1.5λ、θ = 30°代入:λ/d = 0.667,m = -1时sin(θ_g) = 0.5 - 0.667 = -0.167,对应约-9.6°。也就是说,波束图上除了30°的峰,还会在-9.6°附近冒出几乎等高的假峰。

栅瓣比旁瓣危险得多:旁瓣可以用窗压低,栅瓣却和主瓣差不多高,加窗根本压不掉,只能改小阵元间距或限制扫描范围。代码里最常见的错误是图省事直接把d设成一个整数波长,而不是λ/2,结果扫描图上目标变成两个。把d改回lambda/2,问题立刻消失。实际操作中,我一般先按半波长起算,再根据安装空间做权衡:如果间距确实要拉大,就先把可能出现的栅瓣位置算一遍,确认它落在扫描范围之外再定方案。

注意:阵列间距是空间采样频率的物化表现,半波长是空间奈奎斯特条件,不是经验值。任何超过半波长的间距改动,都要先做栅瓣位置计算。

5. 常规波束形成MATLAB实现避坑指南:5个高频翻车点

CBF本身不复杂,但实测翻车率极高。下面5条是MATLAB实现里最常踩的坑,按现象、原因、解决三段式整理,基本覆盖从纯仿真到半实物过渡的全部高频问题。

5.1 翻车点1:角度单位混用,峰值偏移1°起步

现象:扫描角写成-90:0.5:90,信号真实来向30°,但波束图峰值出现在29°或31°,偏差忽大忽小,角度越大偏得越离谱。

原因:MATLAB的sin()和cos()默认输入弧度。代码里写成sin(theta)但theta是度数,或者从其他语言迁移代码时把sind()误改成了sin(),都会导致角度映射非线性失真。

解决:统一用sind()和cosd()。验证方法很简单——把峰值对应的扫描角打印出来和真实来向比对。半实物联调时,我第一个检查项永远是角度单位。

5.2 翻车点2:复数IQ信号被取成实数,方向性消失

现象:IQ数据进数组时写了x = real(data),波束图变得几乎全向,输出功率对所有扫描角都差不多,完全没有方向性。

原因:阵列增益靠复数相位对齐实现,取实部等于丢掉了一半的相位信息,加权求和从幅度叠加退化成部分抵消。这不是算法问题,是数据已经不再满足窄带复数模型。

解决:接收链路保持复数结构,x = complex(I, Q)。如果数据文件里只有实部,先用希尔伯特变换或正交混频恢复解析信号,再进CBF。这个坑在自采数据里出现率最高,因为很多人习惯先把ADC数据存成real数组。

5.3 翻车点3:快拍数太少,波束图旁瓣乱跳

现象:空间谱整体形状对,但旁瓣起伏严重,换一批数据运行结果明显不一样,时好时坏。

原因:CBF输出功率是对快拍维做样本平均,样本数太少时统计方差大。快拍数低于几十时,旁瓣起伏会完全掩盖真实旁瓣电平。

解决:快拍数加到512或1024,这是最直接的办法。如果数据本身很短,就只读峰值位置,不要读旁瓣电平。注意CBF不涉及协方差矩阵求逆,不需要做对角加载或矩阵平滑,直接对输出功率做时间平均即可。

5.4 翻车点4:近场信号当远场处理,相位补偿全错

现象:把发射源放在离阵列很近的位置(比如几米),仿真或实测的波束指向总是偏,峰值变钝,位置还随快拍移动。

原因:CBF导向矢量建立在平面波假设上。近场信号是球面波,各阵元的时延差不再等于nd sinθ/c,远场公式失效。

解决:先用远场条件判断:源到阵列距离R必须满足R > 2D²/λ,其中D = (N-1)d是阵列孔径。不满足时改用近场聚焦,逐元计算真实距离差做相位补偿。工程速算的经验是:先把D算出来,如果2D²/λ和实际距离在一个数量级甚至更大,直接按近场处理,别想着用远场模型硬凑。

5.5 翻车点5:扫描网格太粗,峰值存在量化误差

现象:真实来向21°,扫描步进1°,空间谱峰值在21°或22°,定位误差0.5°;真实来向正好落在两个网格点中间时,峰值能量分散在相邻格点,看起来像两个小峰。

原因:扫描网格本身就是一组离散假设。CBF不做插值,网格多粗输出就有多粗。

解决:先粗扫找峰,再在峰值附近±2°范围内用0.05°或0.1°细扫。如果对精度还有要求,在细扫结果上做抛物线插值,运算量几乎可以忽略。这个习惯比直接换高分辨率算法成本低得多,实测里能省掉大量无意义的精度排查。

6. 从CBF到自适应波束形成:MVDR的最小改动路径

6.1 只换一行加权向量:MVDR的MATLAB实现

CBF的白盒特性是它最大的价值:先跑出一版空间谱,确认阵列没接错、角度没偏、数据格式没问题,再往自适应算法上走。MVDR(最小方差无失真响应)是升级成本最低的一步,它和CBF的差别只体现在加权向量上。

% 沿用前文的8元均匀线阵,信号从30°入射 a_s = exp(-1j * 2 * pi * d * (0:N-1).' * sind(30) / lambda); % 接收数据x:包含30°期望信号和-10°强干扰 Rxx = x * x' / size(x, 2); % 对角加载,避免协方差矩阵奇异 Rxx_dl = Rxx + 1e-3 * trace(Rxx) / N * eye(N); % 常规波束形成加权 w_cbf = a_s / N; % MVDR加权:期望方向增益为1,同时最小化输出功率 w_mvdr = (Rxx_dl \ a_s) / (a_s' * (Rxx_dl \ a_s)); % 全空域扫描,对比两种算法的空间谱 p_mvdr = zeros(size(theta_scan)); for k = 1:numel(theta_scan) a_k = exp(-1j * 2 * pi * d * (0:N-1).' * sind(theta_scan(k)) / lambda); w_k = (Rxx_dl \ a_k) / (a_k' * (Rxx_dl \ a_k)); p_mvdr(k) = mean(abs(w_k' * x).^2); end

MVDR的思路是在期望方向增益等于1的约束下,最小化输出功率w^H Rxx w,解就是这个式子。它和CBF的本质差距体现在干扰方向上:CBF的波束方向图对干扰无能为力,固定旁瓣能衰减多少算多少;MVDR会自动在干扰来向形成深零陷,把干扰功率压到噪声底。代价是它对阵列流型误差和协方差估计误差敏感,所以对角加载系数是必加的,实测中1e-3不够稳时可以直接加到1e-1。

6.2 验证方法:从空间谱对比到信干噪比对比

验证MVDR正确性的方法非常直接。构造一个30°期望信号加一个-10°强干扰的接收数据,分别用CBF和MVDR扫描全空域。CBF的空间谱里,-10°位置通常会出现一个峰,如果干扰功率比期望信号高,这个峰甚至超过期望峰;MVDR的空间谱里,-10°位置会出现一个深达-40dB以下的凹坑,而不是峰。这一步能跑通,就说明自适应置零机制真正生效了。

我现在的习惯是:任何阵列实验,第一步永远先跑一版CBF,画波束图、做全空域扫描,确认数据链路是通的;第二步才上MVDR或其他高分辨率算法,而且一定把CBF的空间谱留在同一个坐标系里做对照。这个过程的代码改动通常不超过20行,却能把波束形成从黑匣子变成可视化工具,排查问题时的收益远超成本。CBF是起点,不是终点,但跳过他直接去追自适应算法,往往只会收获一屏幕无从下手的复数矩阵。希望帮到你。

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

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

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

立即咨询