简介:压缩包内共114个文件,含23个Python脚本、73张仿真结果图、17个编译缓存文件及1份README说明文档,整体大小约9.11MB。项目聚焦波束形成典型算法仿真,覆盖延迟求和、最小方差无失真响应(MVDR)、线性约束最小方差(LCMV)及自适应波束形成等。基于NumPy/SciPy实现,通过修改阵元数、阵元间距、信噪比、期望角度等参数生成极坐标波束图、热力图与方向图,直观呈现不同算法的波束指向、旁瓣抑制和零点控制效果。仿真中还考虑了多径效应、非理想阵元等因素,使结果更具参考价值。已有155人学习下载,适合雷达、声纳、无线通信及生物医学成像方向的初学者和研究人员作为算法验证与教学演示工具。压缩包内附README说明,结合生成图像与Python源码,可快速复现不同条件下的仿真结果,并为后续硬件实现或算法改进提供参考。
1. 波束形成算法仿真这个zip,装着哪些值得复现的东西
一个写着“基于Python实现的不同波束形成算法仿真”的zip包,解压以后通常不会有什么惊喜:几个py文件、几张方向图、一份说明,没了。但真正值钱的从来不是那几张画出来的花瓣状方向图,而是从接收信号生成到权矢量求解、再到性能对比的整条链路。波束形成要解决的核心问题很集中——在来波方向未知或只知道粗估方向时,怎么用一组阵元把目标方向信号放大、把其他方向的干扰压下去。
用Python做这件事的优点很明显:numpy处理复数矩阵运算几乎是零成本,MATLAB能写的公式在Python里一样能写,还不用纠结许可证。这篇文章面向的是正在做阵列信号处理课程设计、需要对比几种波束形成算法、或者刚接触雷达/声呐/5G上行接收想搞清CBF和MVDR差别的工程学习者。我会按“从数据生成到算法实现,再到参数扫描和排错”的顺序讲,保证你看完能自己复现出方向图和信干噪比曲线。
2. 先搭信号模型:用NumPy生成一组能用于波束形成的接收数据
2.1 均匀线阵与半波长间距:为什么d=0.5λ是默认值
波束形成仿真的第一步不是写波束形成器,而是先造一组接收数据。我见过不少人直接拿随机矩阵开跑,结果方向图画出来主瓣很漂亮,换一个入射角就完全乱掉——十有八九是数据生成环节的阵列模型没建对。最常见的阵型是均匀线阵(ULA):N个全向阵元沿直线等间距排列,间距为d。信号源假设在远场,波前到达阵列时近似为平面波,所以每个阵元收到的信号只有相位差,幅度近似相等。
这个相位差就是阵列流型向量(steering vector)的来源。以第一个阵元为参考点,第n个阵元相对参考点的时延是 n·d·sinθ/c,换算成相位就是 -j·2π·n·d·sinθ/λ,其中θ是来波方向与阵列法线的夹角。这里最容易被忽略的是角度定义:如果你把θ定义成和阵列轴向(x轴)的夹角,那么相位差里的sinθ会变成cosθ,画出来的方向图整体偏移90度。我习惯统一用“与法线的夹角”,并且在代码里用注释写死。
阵元间距d取半波长,这个不是拍脑袋定的。均匀线阵等效于对空间连续孔径做离散采样,d > λ/2时会出现栅瓣——除了真实来波方向外,其他角度也会出现同样的响应峰。仿真里这个坑很隐蔽,因为输入只有一个方向时,栅瓣位置的谱峰看起来也合理,只有扫描全角度才会暴露。d小于半波长不会引入栅瓣,但会增大阵元间相位差的分辨难度,主瓣变宽,所以默认就是d = λ/2。
2.2 接收信号生成代码:窄带信号、加性噪声与快拍数
数据生成这一块,我一般会把阵元位置、流型向量、快拍生成拆成三个函数,后面所有算法都用这套接口。这样换阵型(比如改成均匀圆阵)时不用动算法代码。
import numpy as np # 仿真参数:阵元数、载频、间距、快拍数、信噪比 N = 8 # 阵元数 fc = 2.4e9 # 载频,单位Hz,取2.4GHz只是示例 c = 3e8 # 光速,单位m/s d = 0.5 * c / fc # 阵元间距,取半波长,约0.0625m snapshots = 1024 # 快拍数 snr_db = 10 # 信噪比,单位dB def array_positions(N, d): """沿x轴排列的均匀线阵,第一个阵元在原点""" return np.arange(N) * d def steering_vector(theta_deg, positions, wavelength): """计算theta_deg方向对应的阵列流型向量 theta_deg: 来波方向与法线(y轴)的夹角,单位度 positions: 阵元坐标数组,单位m wavelength: 波长,单位m """ theta = np.deg2rad(theta_deg) # 每个阵元相对参考点(第一个阵元)的相位差 return np.exp(-1j * 2 * np.pi * positions * np.sin(theta) / wavelength) def generate_snapshots(theta_deg, N=8, snapshots=1024, snr_db=10): """生成一个远场窄带信号的接收数据 返回X: 阵元数×快拍数的复数矩阵 返回a: 该方向对应的流型向量 """ wavelength = c / fc pos = array_positions(N, d) a = steering_vector(theta_deg, pos, wavelength) # 窄带复信号:实部虚部都是标准正态分布,功率归一化 s = (np.random.randn(snapshots) + 1j * np.random.randn(snapshots)) / np.sqrt(2) signal_power = np.mean(np.abs(s) ** 2) # 约等于1.0 noise_power = signal_power / (10 ** (snr_db / 10)) # 阵元间噪声独立,复噪声功率要均分给实部和虚部 noise = np.sqrt(noise_power / 2) * ( np.random.randn(N, snapshots) + 1j * np.random.randn(N, snapshots) ) # X = a * s^T + N,信号从theta_deg方向入射 X = a.reshape(-1, 1) * s.reshape(1, -1) + noise return X, a # 生成一个来自10度方向的信号 X, a_true = generate_snapshots(theta_deg=10, N=N, snapshots=1024, snr_db=10) print("X shape:", X.shape) # (8, 1024)这段代码的要点在最后一行:X的shape是(阵元数, 快拍数),后面CBF、MVDR、LCMV全部以它作为输入。如果你习惯把快拍放第一维,那所有矩阵运算的转置关系都要跟着改,非常容易出错。
几个参数值得展开说。N决定阵列的自由度和能同时抑制的干扰数量,N个阵元理论上最多形成N-1个零陷,也决定主瓣能做多窄。快拍数snapshots是每次采样的点数,后面估计协方差矩阵需要它,取值太小会导致矩阵奇异,这在第5章会单独讲。snr_db用的是信号功率反推噪声功率的写法,这样信噪比是严格按照功率定义的,不会出现“设了10dB实际算出来只有7dB”的情况。
2.3 信号、噪声和信噪比怎么定标:仿真可信度的第一步
复数信号功率的定标是新手最容易翻车的地方。上面代码里信号s是复高斯序列,单个样本功率|s|²的均值约等于1,所以signal_power约等于1。噪声是N×snapshots的复高斯矩阵,每个元素功率的期望是noise_power。生成复噪声时,实部和虚部各分配noise_power/2,所以要乘以sqrt(noise_power/2)。如果直接写np.sqrt(noise_power)再乘randn,实部虚部总功率就是noise_power的两倍,实际信噪比会比设定值低3dB。
快拍数的取值有一个经验法则:协方差矩阵是N×N的,满秩至少需要N个线性无关的快拍,工程上一般取4N以上,1024个快拍对8阵元来说已经很充足。快拍数不够时,MVDR这类依赖矩阵求逆的算法会表现出两个极端:要么协方差奇异直接报错,要么矩阵勉强可逆但谱峰位置乱跳,出现伪峰。后面第5章会详细讲这种现象的排查方法。
到这里,接收数据这块就可以收住了。把这套generate_snapshots函数保存好,后面所有算法对比和参数扫描都复用它,能保证不同算法之间的差异只来自算法本身,而不是因为换了随机种子导致数据对不上。
3. 三种常用波束形成算法:CBF、MVDR、LCMV的Python实现与代价函数
3.1 常规波束形成CBF:扫描功率谱的Python实现
常规波束形成是最直观的做法:把每个阵元的输出加权求和,权矢量就是某个方向的流型向量本身。它的逻辑是“我把阵列的接收方向对准θ,如果信号真的从θ来,那各阵元经过相位补偿后应该同相相加,输出功率最大”。输出功率计算公式为P(θ) = a^H(θ)·R·a(θ),其中R是接收数据的协方差矩阵。
CBF的分辨率受瑞利限约束,主瓣宽度大约为λ/(N·d)弧度,对8阵元、半波长间距的阵列来说,主瓣宽度在12度左右。两个来波方向夹角小于这个值,CBF在谱上就分辨不出来。它的优势是稳健,不涉及矩阵求逆,协方差矩阵估计差点也不至于崩,适合做所有对比实验的基线。
def cbf_spectrum(X, positions, wavelength, scan_range=(-90, 91), step=0.5): """常规波束形成:扫描角度,计算各方向输出功率 X: 阵元数×快拍数 positions: 阵元坐标数组 wavelength: 波长 """ L = X.shape[1] R = X @ X.conj().T / L angles = np.arange(scan_range[0], scan_range[1], step) spectrum = [] for theta in angles: a = steering_vector(theta, positions, wavelength) power = a.conj() @ R @ a spectrum.append(power.real) return angles, np.array(spectrum) # 使用第2章生成的X angles, spec_cbf = cbf_spectrum(X, array_positions(N, d), c / fc) print("CBF谱峰位置(度):", angles[np.argmax(spec_cbf)])这里的R = X @ X.conj().T / L 是样本协方差矩阵,除以快拍数L是为了让功率估计无偏。后面MVDR也用同一个R,区别只在权矢量的求解方式。CBF不需要对R求逆,所以即使快拍数很少,画出来的谱也不会报错,只是方差变大、旁瓣起伏变多。
3.2 MVDR/Capon:最小方差无失真响应和它的代价函数
MVDR的出发点比CBF讲究:我希望在期望方向增益固定为1(无失真)的前提下,让输出总功率最小。输出功率等于w^H·R·w,所以最优化问题写成 min w^H R w,约束 w^H a(θ) = 1。这个约束保证期望方向信号增益为1,最小化输出功率则迫使算法自动在干扰方向压低响应——因为干扰功率是输出功率的一部分,想让总功率小,最优解就会在干扰方向形成零陷。
这个问题的闭式解是 w = R⁻¹a / (a^H R⁻¹a),把解代回功率表达式,得到输出功率 P(θ) = 1 / (a^H R⁻¹a)。之所以叫Capon波束形成,是因为Capon在1969年把它用到方位估计上。和CBF相比,MVDR对协方差矩阵的准确性极其敏感,快拍数不足或者信噪比过高时,R的估计误差会被R⁻¹放大。
def mvdr_spectrum(X, positions, wavelength, scan_range=(-90, 91), step=0.5, diagonal_loading=1e-6): """MVDR/Capon波束形成:扫描角度,输出功率谱 diagonal_loading: 对角加载系数,缓解低快拍下的矩阵奇异 """ L = X.shape[1] R = X @ X.conj().T / L # 对角加载:给R加上一个小单位阵,避免求逆时奇异 R_loaded = R + diagonal_loading * np.eye(R.shape[0]) R_inv = np.linalg.inv(R_loaded) angles = np.arange(scan_range[0], scan_range[1], step) spectrum = [] for theta in angles: a = steering_vector(theta, positions, wavelength) power = 1.0 / (a.conj() @ R_inv @ a) spectrum.append(power.real) return angles, np.array(spectrum) # 注意:这里用MVDR会得到一个比CBF更窄的主瓣 angles, spec_mvdr = mvdr_spectrum(X, array_positions(N, d), c / fc) print("MVDR谱峰位置(度):", angles[np.argmax(spec_mvdr)])代码里默认加了diagonal_loading=1e-6,这是我在项目里的习惯,不是MVDR公式的一部分。理论上R满秩时不需要加载,但仿真中信号加噪声的R在低快拍下特征值分布很病态,求逆结果会被小特征值主导,加一个很小的对角加载相当于给特征值加了个下限,谱估计稳定很多。加载系数不能太大,太大会让MVDR退化成CBF,分辨率优势消失。
3.3 LCMV与对角加载:约束方向变多时权矢量怎么解
LCMV是MVDR的推广。MVDR只约束一个方向增益为1,LCMV允许你同时约束多个方向:比如θ₁方向增益为1(主瓣),θ₂方向增益为0(零陷)。把约束条件写成矩阵形式 C^H w = f,其中C的每一列是一个约束方向的流型向量,f是对应的期望响应。最优化问题变成 min w^H R w,约束 C^H w = f,解是 w = R⁻¹C(C^H R⁻¹C)⁻¹f。
LCMV适合的场景是已知干扰方向或者需要展宽零陷的情况。比如通信抗干扰里,来波方向估计有误差时,你希望零陷不是一个点而是一个角度范围,那就在干扰方向附近多约束几个相邻角度的响应为0。约束越多,消耗的阵列自由度越多,可用的干扰抑制自由度就减少,所以不能无脑加约束。
def lcmv_weights(R, C, f): """LCMV最优权矢量求解 R: 样本协方差矩阵 C: 约束矩阵,每列是一个约束方向的流型向量 f: 期望响应向量,长度等于约束数 """ R_inv = np.linalg.inv(R) # 拉格朗日乘子法闭式解 w = R_inv @ C @ np.linalg.inv(C.conj().T @ R_inv @ C) @ f return w # 约束10度方向增益为1,-20度方向增益为0 positions = array_positions(N, d) C = np.column_stack([ steering_vector(10, positions, c / fc), steering_vector(-20, positions, c / fc) ]) f = np.array([1.0, 0.0]) R = X @ X.conj().T / X.shape[1] w_lcmv = lcmv_weights(R, C, f) print("LCMV权矢量维度:", w_lcmv.shape)注意这个代码里直接对R求逆,没有加对角加载。实际用的时候我建议和MVDR一样加上,特别是约束数接近阵元数时,C^H R⁻¹C 本身就可能是病态的。另外,LCMV做DOA估计不如MVDR方便,因为你需要逐个假设来波方向去验证约束是否成立;它更多是被用在“方向已知,要同时抑制多个干扰”的场合。
4. 对比仿真与参数调优:阵元数、快拍数、对角加载对输出的影响
4.1 定一个带干扰的对比场景:主瓣、零陷和计算量差在哪
单信号场景只能看出主瓣宽度差异,看不出波束形成最核心的“抗干扰”能力。我通常在对比实验里放两个信号:一个目标信号从10度入射,信噪比10dB;一个干扰从-20度入射,干噪比20dB。这样MVDR必须先在-20度压出零陷,才能让目标方向输出功率不被干扰抬高。
def generate_two_source(theta_signal, theta_interf, N=8, snapshots=1024, snr_db=10, inr_db=20): """生成目标+干扰的接收数据,干扰功率一般设得比信号大""" wavelength = c / fc pos = array_positions(N, d) a_s = steering_vector(theta_signal, pos, wavelength) a_i = steering_vector(theta_interf, pos, wavelength) # 目标信号和干扰独立,功率分别为1和10^(INR/10) s = (np.random.randn(snapshots) + 1j * np.random.randn(snapshots)) / np.sqrt(2) i = (np.random.randn(snapshots) + 1j * np.random.randn(snapshots)) / np.sqrt(2) i = i * (10 ** ((inr_db - snr_db) / 20)) # 按INR和SNR的差值缩放干扰幅度 noise_power = 1.0 / (10 ** (snr_db / 10)) noise = np.sqrt(noise_power / 2) * ( np.random.randn(N, snapshots) + 1j * np.random.randn(N, snapshots)) X = (a_s.reshape(-1, 1) * s.reshape(1, -1) + a_i.reshape(-1, 1) * i.reshape(1, -1) + noise) return X, a_s, a_i X2, a_s, a_i = generate_two_source(10, -20, N=N, snapshots=1024, snr_db=10, inr_db=20)这段代码里缩放干扰用的是幅度关系:功率比INR-SNR dB = 10log10(幅度比²),所以幅度要乘10^((INR-SNR)/20)。很多人直接乘10^((INR-SNR)/10),功率就多了20倍,干扰把目标信号完全淹没,MVDR都救不回来。
跑完三种算法后,对比表一眼就能看出差异:
| 指标 | CBF | MVDR | LCMV(约束10°/零陷-20°) |
|---|---|---|---|
| 主瓣宽度(8阵元,0.5λ) | 约12° | 约6°(窄且尖锐) | 约12°(受约束限制) |
| -20°干扰处响应 | 无零陷,旁瓣级约-13dB | 自动形成深零陷,可达-60dB | 约束强制零陷,深度有限 |
| 计算量(扫描180°) | 只需做矩阵乘法,快 | 每个角度都要算1/(a^H R⁻¹a),慢 | 只需解一次权矢量,最快 |
| 稳健性(低快拍) | 不受影响 | 需要对角加载 | 需要对角加载 |
MVDR之所以主瓣比CBF窄一倍,是因为它通过R⁻¹做了白化,等效于把阵列孔径“变长”。但窄主瓣不是白来的——代价是旁瓣起伏大,尤其在干扰附近会出现凹槽状起伏,谱峰位置对R的估计误差非常敏感。
4.2 快拍数不足时MVDR的“伪峰”:对角加载系数怎么扫
把快拍数从1024降到16,MVDR的谱会变得很难看:除了真实方向外,其他角度出现好几个尖峰,甚至真实方向的峰被淹没。原因很直接,16个快拍估计出来的协方差矩阵特征值分布严重偏离真实值,R⁻¹等价于放大了噪声子空间里的小特征值,这些被放大的分量在全角度扫描时随机产生峰值。
解决手段就是对角加载。问题是加载系数取多少?太小没用,太大把特征值抹平,MVDR退化成CBF。我的做法是扫对数间隔:
loading_values = [0, 1e-8, 1e-6, 1e-4, 0.01, 0.1, 1.0] peak_error = [] for loading in loading_values: angles, spec = mvdr_spectrum(X2, array_positions(N, d), c / fc, diagonal_loading=loading) peak_idx = np.argmax(spec) peak_error.append(abs(angles[peak_idx] - 10)) print("加载系数 谱峰误差(度)") for load, err in zip(loading_values, peak_error): print(f"{load:.1e} {err:.2f}")扫完你会看到:loading=0时谱峰可能偏到十几度甚至乱跳,loading=1e-6附近误差最小且稳定,loading=0.1以上主瓣开始变宽,误差反而增大。这个规律在8阵元、1024快拍的配置下很稳定,但换了阵元数和快拍数,最优加载系数会变,所以要扫不要猜。
4.3 阵元数、信噪比与分辨率的参数表与批量实验
阵元数对三个算法的影响有两条线:N越大,主瓣越窄,CBF和MVDR的分辨率都提升;但N增大后,MVDR对快拍数的要求也提高,因为协方差矩阵的维度变大了。信噪比则直接影响MVDR的谱峰尖锐程度——SNR低时信号特征值接近噪声特征值,R⁻¹白化能力变弱,主瓣展宽。
| 阵元数N | CBF主瓣宽度 | MVDR主瓣宽度 | 最小所需快拍数(MVDR经验值) |
|---|---|---|---|
| 4 | 约26° | 约13° | 16以上 |
| 8 | 约12° | 约6° | 32以上 |
| 16 | 约6° | 约3° | 64以上 |
批量实验代码就按不同N循环调用generate_two_source和mvdr_spectrum,把谱峰的位置和主瓣宽度记录下来。这套流程跑完,你基本能掌握一个项目的核心数据:在什么阵元数和快拍数下,这几种算法谁更值得用。
5. 仿真避坑:方向图指向偏了90度、伪峰与噪声定标的5个真实问题
5.1 现象:角度指向整体偏了90度,方向图“翻车”了
把steering_vector里的sin(theta)改成cos(theta),或者角度定义从“与法线夹角”改成“与阵列轴向夹角”,你会看到谱峰不在10度而在80度附近,而且看起来还挺像一个正常峰。原因就是相位差公式里三角函数的几何关系搞反了。
解决方法是统一约定:均匀线阵法线方向是y轴,来波方向用与法线的夹角。在steering_vector函数里只用sin(theta),不要混用cos。如果一定要用“与阵列轴向的夹角”,那公式改成cos(theta)后,全程序所有调用处都要保持一致。我见过最折腾的翻车案例是画方向图用了一套角度定义,DOA估计又用另一套,两个结果差了90度却各自看着都合理。
5.2 现象:MVDR在快拍数小于阵元数时直接报错或出现伪峰
快拍数L小于阵元数N时,样本协方差矩阵的秩最多只有L,必然奇异,np.linalg.inv直接抛LinAlgError。即使L略大于N,矩阵条件数也可能超过1e12,求逆结果被数值误差主导,谱峰随机乱跳。这不是算法写错了,是信息量不足。
解决分两步。第一步,让L至少大于N,经验值取4N以上。第二步,给R加上对角加载,最小化数值误差的影响。如果项目里快拍数确实受限(比如雷达的相参积累时间有限),那优先考虑对角加载而不是硬上高分辨率算法。
5.3 现象:设置snr_db=10,但统计出的信干噪比明显对不上
这类问题通常不在波束形成器,而在噪声生成。复高斯噪声的功率是实部功率加虚部功率,如果生成噪声时只乘了sqrt(noise_power)而没有再除以sqrt(2),噪声实际功率就是设定值的两倍,等效信噪比低3dB。还有干扰缩放时误用功率倍数代替幅度倍数,干扰功率会高出几十倍。
解决方法是写一个自检函数:生成数据后,用np.mean(np.abs(X)**2, axis=1)统计每个阵元接收总功率,再用信号流型向量做相关投影估计信号功率,验证和理论值的偏差在0.5dB以内。这个检查10行就能写完,值得加进仿真流程。
5.4 现象:扫描范围出现对称的“鬼峰”,像是栅瓣
如果你的阵元间距不是0.5λ而是1.5λ,方向图上除了真实峰,还会在对称角度出现一样的峰。因为空间采样不满足奈奎斯特条件,d > λ/2时,相位差2π·d·sinθ/λ在sinθ超过某个阈值后会混叠。
解决方法是把阵元间距改回λ/2,或者在代码里加一个断言:if d > wavelength/2: raise ValueError。栅瓣在真实阵列里可以通过阵元方向图抑制,仿真里阵列流型用的是全向阵元,栅瓣会完整暴露,所以必须从源头上规避。
5.5 现象:谱峰变宽,以为是算法问题,其实是扫描步长太粗
扫描步长取5度时,方向图谱峰附近只有一两个采样点,看起来峰很胖,MVDR的窄主瓣优势被完全掩盖。反过来,步长取0.01度时,扫描角度数量上万,MVDR每个角度都要做一次矩阵运算,仿真时间从秒级变分钟级。
解决方法是先粗扫定位峰的大致范围,再在峰附近±10度做细扫,步长0.1度。CBF用0.5度步长就够了,MVDR建议至少0.2度,否则-3dB主瓣宽度都测不准。
6. 从方向图到信干噪比曲线:一个能写进报告的验证流程
单次仿真只能说“算法能跑”,要说“算法有效”,需要统计意义上的性能曲线。我一般在最后做一步蒙特卡洛:固定目标方向10度和干扰方向-20度,在SNR从-10dB扫到20dB的每个点上,重复200次随机实验,统计MVDR输出的信干噪比SINR均值,和理论最优SINR对比。
def mc_sinr(theta_signal=10, theta_interf=-20, snr_list=range(-10, 21, 5), repeats=200, N=8, snapshots=1024): results = {} for snr in snr_list: sinr_mvdr = [] for _ in range(repeats): X2, a_s, a_i = generate_two_source( theta_signal, theta_interf, N=N, snapshots=snapshots, snr_db=snr, inr_db=snr + 20) # MVDR权矢量(含对角加载) R = X2 @ X2.conj().T / snapshots R_loaded = R + 1e-6 * np.eye(N) a = steering_vector(theta_signal, array_positions(N, d), c / fc) w = np.linalg.inv(R_loaded) @ a / (a.conj() @ np.linalg.inv(R_loaded) @ a) sinr = np.abs(w.conj() @ a_s) ** 2 / np.abs(w.conj() @ a_i) ** 2 sinr_mvdr.append(sinr) results[snr] = np.mean(sinr_mvdr) return results注意这里SINR的计算只用了信号和干扰的流型向量乘以权值,没有把噪声功率放进去,因为对比的是MVDR对干扰的抑制能力。如果你要看输出端的总信噪比,要把噪声项补上:w^H R_noise w。
这个流程跑完,你会得到一条SINR随SNR变化的曲线,MVDR在SNR大于0dB后基本逼近理论最优值,CBF则一直低一截。这就是波束形成算法仿真从“画图好看”到“数据可信”的关键一步。
我自己做这类仿真有个习惯:固定随机种子,把所有中间结果(协方差矩阵、谱数据、权矢量)存成npy文件,方便回头换参数重新分析。方向图这种东西,看得见但摸不着,存了数据才有后悔药。希望这份笔记能帮你在波束形成仿真上少走几段弯路。
本文还有配套的精品资源,点击获取