
简介基于MUSIC算法的AOA/TOA联合仿真MATLAB工程面向无线通信、雷达定位与物联网设备定位方向的学习者和研究者用于理解到达角与到达时间估计原理并验证算法性能。资源包仅含1个m文件压缩包大小约1KB核心代码涵盖天线阵列定义、信号与信道建模、MUSIC谱估计、AOA/TOA求解及结果可视化结构紧凑适合快速复现。已有362人学习下载可作为入门高分辨率谱估计与多源参数估计的动手范例。通过调整阵元数量、阵元间距、信噪比、快拍数等参数可直观观察测角与测距精度变化掌握投影噪声子空间、谱峰搜索、伪谱处理等关键步骤同时可拆解AOA与TOA两个估计模块理解多径信道下的估计误差来源并作为拓展到分布式阵列、室内定位或声源定位算法的实验基础。1. AOA 与 TOA 的定位问题MUSIC 仿真前先把模型立住解压 aoa_toa.zip 之后常见的动作是直接找脚本跑一遍看到谱图上出现尖峰就算结束。但 AOA 和 TOA 放在一起不是巧合到达角测的是方向到达时间测的是距离两者在定位系统里互为补充而 MUSIC 算法恰好是这一对观测都能用的高分辨率谱估计工具。把接收信号换成频域观测MUSIC 就从“角度谱”平移成了“时延谱”数学结构几乎不动。这套方法主要用在通信仿真、雷达测向、无源定位这类需要从阵列信号里挤出分辨率的场景适合正在做算法验证和仿真链路搭建的工程师与学生。下面直接从信号模型开始把基于 MUSIC 的 AOA/TOA 仿真这条链路拆开写。2. 基于 MUSIC 的 AOA/TOA 估计原理特征分解与空间谱扫描2.1 均匀线阵下 AOA 信号模型与导向矢量AOA 仿真里最常用的阵列是均匀线阵ULA。假设 N 个阵元等间距摆放间距为 d一个远场窄带信号以角度 θ 入射时相邻阵元之间的波程差是 d·sinθ对应的相位差是 -2π·d·sinθ/λ。以第一个阵元为参考整个阵列的响应写成导向矢量a(θ) [1, e^(-j2πd·sinθ/λ), …, e^(-j2π(N-1)d·sinθ/λ)]^T有 M 个信源同时入射时单次快照的接收向量为 x(t) A(θ)·s(t) n(t)其中 A 是 N×M 的导向矢量矩阵s(t) 是 M 个信源的复包络n(t) 是噪声。这个模型成立的前提是信源数小于阵元数、信号为窄带、各源互不相关。TOA 到后面会复用同一个框架差别只在导向矢量里的“相位延迟”由角度变为时延。符号含义仿真中的典型取值N阵元数8 或 16d阵元间距λ/2λ载波波长与工作频率对应L快拍数1001000M信源数13θ来波方向-60°60°2.2 协方差矩阵特征分解与噪声子空间构造MUSIC 的核心思想是把接收数据的协方差矩阵分解成信号子空间和噪声子空间利用导向矢量与噪声子空间的正交性来搜索信源方向。实际仿真里协方差矩阵用有限快照估计R_x (1/L) · Σ x(t)·x^H(t)对 R_x 做特征分解得到 N 个特征值。在理想模型下大特征值对应 M 个信号其余 N-M 个特征值对应噪声。把最小 N-M 个特征值对应的特征向量拼成矩阵 U_n就得到噪声子空间。MUSIC 空间谱为P(θ) 1 / ‖U_n^H · a(θ)‖²谱峰位置就是来波方向。下面这段代码完成从快照矩阵到噪声子空间的构造是后面所有仿真的公共部分import numpy as np def estimate_noise_subspace(x, num_sources): # x: (N, L) 复矩阵N 个阵元L 个快照 N, L x.shape R (x x.conj().T) / L # 样本协方差矩阵 eigenvalues, eigenvectors np.linalg.eigh(R) # eigh 返回特征值升序排列前 N - M 列对应噪声子空间 U_n eigenvectors[:, :N - num_sources] return U_nnp.linalg.eigh 专门处理厄米矩阵比 eig 数值更稳。协方差矩阵除以 L 是归一化不影响特征向量方向但影响特征值尺度。num_sources 是必须显式给出的参数后面会看到它直接决定噪声子空间的维度给大了会把弱信号当成噪声给小了谱峰直接消失。拿到 U_n 之后角度谱就是在一组候选 θ 上计算倒数def music_spectrum(U_n, theta_grid, d, wavelength): N U_n.shape[0] spectrum [] for theta in theta_grid: # 均匀线阵导向矢量 steering np.exp(-1j * 2 * np.pi * d * np.sin(theta) / wavelength * np.arange(N)) # 分母趋近 0 时谱峰出现加小量避免除零 denom np.abs(steering.conj() U_n U_n.conj().T steering) 1e-12 spectrum.append(1.0 / denom) return np.array(spectrum)表达式里的 steering.conj() 是 1×N 行向量U_n 是 N×(N-M) 矩阵三者连乘得到标量。这个标量本质是导向矢量在噪声子空间上的投影能量理想情况下为 0。加 1e-12 只是防除零不影响峰位置。谱峰搜索的峰值点才是 AOA 估计值谱本身不需要归一化。2.3 TOA 对 MUSIC 的复用频域导向矢量AOA 里导向矢量随角度变化TOA 里导向矢量随频率变化。假设发射信号 S(f) 经过 P 条路径到达接收端每条路径有时延 τ_p 和复增益 α_p接收信号频域表示为Y(f) Σ α_p · S(f) · e^(-j2πf·τ_p) N(f)两边除以已知的 S(f)得到等效信道频响 H(f) Σ α_p·e^(-j2πf·τ_p) N(f)。把 K 个采样频点 f_1, ..., f_K 当成“虚拟阵元”时延 τ 对应的频域导向矢量为b(τ) [e^(-j2πf_1·τ), e^(-j2πf_2·τ), …, e^(-j2πf_K·τ)]^T于是 TOA 估计变成在 τ 网格上扫描 b(τ) 与噪声子空间的正交性步骤与 AOA 完全一致构造多组频域快照、估计协方差、特征分解、算谱、找峰。这就是为什么压缩包名字里把 AOA 和 TOA 放在一起——它们本质是同一个“谱估计引擎”驱动的两个应用。唯一需要注意频域 MUSIC 要求信号在这些频点上都有能量所以扫频信号或 OFDM 符号比单音信号更适合直接做 TOA。3. 用 Python 搭建 AOA 仿真空间谱计算与峰值提取3.1 最小可运行的 AOA-MUSIC 仿真脚本把上一章的模块拼起来加一个信号生成环节就得到一个完整的最小仿真。下面这段代码用两个不相关信源、8 阵元 ULA在 0° 和 20° 方向入射信噪比 15 dBimport numpy as np def generate_snapshots(num_elements, num_snapshots, angles, snr_db, wavelength1.0, spacing0.5): # 生成 L 个快照angles 为弧度制列表 num_sources len(angles) steering np.exp(-1j * 2 * np.pi * spacing / wavelength * np.sin(angles)[:, None] * np.arange(num_elements)[None, :]).T # N x M # 信源复包络各源独立随机 source (np.random.randn(num_sources, num_snapshots) 1j * np.random.randn(num_sources, num_snapshots)) / np.sqrt(2) noise (np.random.randn(num_elements, num_snapshots) 1j * np.random.randn(num_elements, num_snapshots)) / np.sqrt(2) signal_power 10 ** (snr_db / 10) x steering source noise / np.sqrt(signal_power) * \ np.linalg.norm(steering source) / np.linalg.norm(noise) return x这段信号生成的细节值得说明source 除以 sqrt(2) 是为了让实部虚部功率和为 1噪声按信噪比缩放而不是直接乘以固定系数是为了让任意角度组合下实际 SNR 都贴近设定值。角度要转成弧度sin 函数在 numpy 里接受弧度。spacing 用波长归一化0.5 就是半波长。接着跑谱估计angles_true np.deg2rad([0, 20]) x generate_snapshots(8, 500, angles_true, snr_db15) U_n estimate_noise_subspace(x, num_sources2) theta_grid np.deg2rad(np.linspace(-60, 60, 1801)) spectrum music_spectrum(U_n, theta_grid, spacing0.5, wavelength1.0) # 峰值提取 from scipy.signal import find_peaks peaks, props find_peaks(spectrum, height1e-6, distance30) peak_thetas np.rad2deg(theta_grid[peaks]) print(peaks at:, peak_thetas, dB:, 10 * np.log10(props[peak_heights]))find_peaks 的 height 参数过滤掉谱值过低的伪峰distance 限制两个峰之间的最小采样点间距防止一个宽峰被拆成多个。打印结果里应看到两个峰分别落在 0° 和 20° 附近dB 值高出底噪数十倍。如果 M 给成 1噪声子空间维度多了一维第二个峰不会出现如果 M 给成 3第一个峰的旁瓣会被抬起来可能出现假峰。3.2 阵元数、快拍数与信噪比参数对结果的影响这三个参数是 AOA 仿真里最先要调的。阵元数 N 决定自由度N 越大噪声子空间越“宽”谱峰越尖锐快照数 L 决定协方差矩阵估计质量L 太小时特征值发散谱峰偏移信噪比直接决定谱峰和底噪的对比度。参数变化谱峰表现建议设置N4峰宽两源角度差小于 15° 时难以分辨至少 8N16峰窄旁瓣也变多多源场景可用L50峰位置抖动明显底噪不平不低于 100L1000峰平稳接近理论性能精度验证用SNR0 dB峰还在但弱旁瓣开始冒头验证算法下限SNR30 dB谱线干净峰顶尖锐调通链路用3.3 谱峰提取的工程细节find_peaks 拿到的索引换算成角度后最好再加一步抛物线插值取峰顶前后各一个点用二次插值把视角分辨率从 1801 点提高到亚采样点精度。仿真中角度网格设成 0.1° 步进时插值后的估计偏差能降到 0.01° 量级。一个常见的误用是直接把最大谱值对应的网格点当估计值当真实角度落在两个网格点中间时这个操作会引入固定的量化误差。另外当两个信号间隔小于半功率波束宽度时谱峰可能合并成一个宽包络find_peaks 只会返回一个点。碰到这种情况先减少信源数假设用单一信源重跑一遍确认是扫描网格太粗还是阵元数不够。4. TOA 仿真与 AOA/TOA 联合定位从时延谱到坐标解算4.1 频域 MUSIC 做 TOA 的最小实现TOA 仿真的输入不是阵列快照而是频域采样序列。先用一组已知的复指数频点模拟发射信号 S(f)构造两个多径分量时延分别为 2.0 和 2.7 微秒幅值比 1:0.7叠加高斯噪声。对频域数据按 2.2 节的方式算谱def generate_frequency_observations(freqs, delays, gains, snr_db): # freqs: K 个采样频点delays: 时延向量秒gains: 复增益 K len(freqs) channel np.zeros(K, dtypecomplex) for delay, gain in zip(delays, gains): channel gain * np.exp(-1j * 2 * np.pi * freqs * delay) sig_power np.mean(np.abs(channel) ** 2) noise_power sig_power / (10 ** (snr_db / 10)) noise (np.random.randn(K) 1j * np.random.randn(K)) * np.sqrt(noise_power / 2) return channel noise这里 H(f) 本身就是 S(f) 被消掉后的信道频响真实系统里做法是发导频或用解调后的频域符号除以参考符号。freqs 数组的起始频率、截止频率和频点数直接决定时延分辨率和可观测范围。频点间隔 Δf 决定最大不模糊时延 1/Δf总带宽 B 决定时延分辨率 1/B。下面的扫描关键在构造时延网格freqs np.linspace(2.4e9, 2.416e9, 256) # 16 MHz 带宽256 个频点 delays [2.0e-6, 2.7e-6] gains [0.8 0.1j, 0.5 - 0.2j] y generate_frequency_observations(freqs, delays, gains, snr_db20) # 用多个独立符号构成快照矩阵把 y 复制加扰动模拟 L 次符号观测 L_sym 200 Y np.tile(y[:, None], (1, L_sym)) Y (np.random.randn(*Y.shape) 1j * np.random.randn(*Y.shape)) * 0.01 U_n estimate_noise_subspace(Y, num_sources2) tau_grid np.linspace(0, 0.5e-6, 2001) # 搜索范围不超过 1/Δf spectrum_tau music_spectrum(U_n, tau_grid, spacing1.0, wavelength1e6)这里的 music_spectrum 复用了角度谱函数但传入的 spacing 和 wavelength 已经失去物理意义频域导向矢量的相位项是 e^(-j2πfτ)与阵列模型里的 e^(-j2πd·sinθ/λ) 在数学上同构却没有真实的阵元间距。更严谨的写法是单独写一个时延谱函数把扫描变量从 theta 替换成 tau。谱峰对应的 tau_grid 位置就是 TOA 估计值。0.5 微秒搜索范围对应 150 米距离室内定位场景足够。4.2 AOA/TOA 联合定位最小二乘坐标解算有了单个基站的 AOA 和 TOA就能解出目标相对基站的极坐标距离 r c·τ方向为 θ。更常见的场景是多个基站混合提供两种观测用最小二乘统一融合。设目标位置为 p第 i 个基站位置为 p_i观测方程为角度观测θ_i atan2(p_y - p_i_y, p_x - p_i_x) 距离观测r_i ‖p - p_i‖把两类残差堆叠起来用 scipy.optimize.least_squares 迭代求解from scipy.optimize import least_squares def residual(pos, base_pos, angles, ranges, use_angle): pos np.array(pos) dx pos[0] - base_pos[:, 0] dy pos[1] - base_pos[:, 1] res [] if use_angle: res.extend(np.arctan2(dy, dx) - angles) if ranges is not None: res.extend(np.sqrt(dx**2 dy**2) - ranges) return res base_pos np.array([[0, 0], [100, 0]]) # 两个基站坐标 angles np.deg2rad([28.0, -31.5]) # 两个基站测得的 AOA ranges np.array([106.0, 88.0]) # TOA 转换得到 res_opt least_squares(residual, [50, 50], args(base_pos, angles, ranges, True)) print(res_opt.x)注意角度残差要做归一化因为 atan2 返回 (-π, π]AOA 测角误差小时问题不大但目标方向接近 π/-π 边界时需要把残差卷绕回 (-π, π]。least_squares 对初值敏感一般先用角度求交点的几何解当作初始值避免迭代掉进局部极小。4.3 采样率、带宽与时延分辨率的对齐通信仿真里最容易出问题的不是 MUSIC 本身而是前端参数不匹配。频点数是带宽除以频点间隔时延分辨率由总带宽决定而不是采样率。一个典型错误是把采样率当成带宽结果时延谱分辨率虚高两个相隔很近的径在谱上完全重合。另一个错误是频点间隔过大导致 1/Δf 小于实际时延范围真实时延超出不模糊区间后折叠回搜索窗内表现为一个假峰。建议先把带宽和频点数定下来再按 1/B 估算可分辨时延按 1/Δf 估算可观测范围两倍留出余量后再设置搜索网格。带宽 B频点数 K时延分辨率 1/B不模糊时延 1/Δf16 MHz25662.5 ns16 μs80 MHz51212.5 ns6.4 μs1 MHz641 μs64 μs5. AOA 仿真参数边界阵元间距、快拍数与相干源5.1 阵元间距超过半波长角度模糊MUSIC 谱扫描的是 sinθ阵元间距 d 决定 sinθ 的周期性。dλ/2 时 sinθ 在 [-1,1] 内唯一映射不会模糊dλ/2 时导向矢量在扫描范围内出现重复相位空间谱出现栅瓣角度区间两端会出现与真实峰几乎等高的假峰。仿真时如果想要扩大阵列孔径又不引入模糊常见做法是把天线排成稀疏阵再用子阵级 MUSIC 解模糊而不是简单加大 d。验证方法很简单把 theta_grid 改成 sin 域均匀扫描看谱峰是否仍然在真实角度附近唯一。5.2 快照数下限与信源数估计快照数太少时样本协方差矩阵的最大特征值被噪声拉高信源数与噪声子空间的边界变得模糊。判断方法不是直接看估计误差而是看特征值分布把 R_x 的特征值从大到小画出来信源对应的特征值会出现“断崖”噪声特征值则平缓下降。若断崖不明显优先增加 L。M 的估计可以借助 AIC 或 MDL 准则但工程上更常用的是“扫描 0N-1 个信源假设选谱峰最稳定的那个 M”虽然理论味淡一些但在仿真链路里鲁棒性更好。注意 MDL 在低快拍时过估计信源数MUSIC 谱会多出几个小峰不如直接观察特征值为主、信息论准则为辅。5.3 相干信源导致秩亏前向空间平滑多径场景里直达波和反射波可能完全相关或高度相关信号子空间维度降低MUSIC 直接失效表现为谱峰消失或偏移。针对均匀线阵常见做法是前向空间平滑把 N 阵元分成 P 个重叠子阵每个子阵长度 N-P1把各子阵的协方差矩阵求平均后替换原矩阵def forward_smooth(R, subarray_size): # R: 原始 N x N 协方差矩阵 N R.shape[0] P N - subarray_size 1 # 子阵个数 R_smoothed np.zeros((subarray_size, subarray_size), dtypecomplex) for i in range(P): R_smoothed R[i:i subarray_size, i:i subarray_size] return R_smoothed / P平滑后的协方差矩阵秩恢复为 min(M, P)但有效阵元数从 N 降到 subarray_size分辨率随之下降。P 个重叠子阵意味着最多分辨 P 个相干源所以 subarray_size 不能小于真实信源数加 1否则噪声子空间维度不足。前后向平滑可以同时做把前向和后向协方差平均等效增加快照但对阵列几何有对称性要求。另一个工程选择是直接在时域加少量频率分集让多径在频域不再完全相干效果等同给信源增加独立样本。6. 用克拉美罗界校准 MUSIC 仿真结果验证与调试技巧6.1 单目标 AOA 的 CRB 缩放关系仿真结果不是跑出峰就能用的。调完参数后最有效的验证方式是做蒙特卡洛把估计误差的 RMSE 与克拉美罗界CRB对比。单目标 ULA、复高斯噪声下AOA 估计方差的 CRB 正比于var(θ) ∝ 1 / (N(N²-1)·L·SNR·cos²θ)准确系数随信号模型差一个常数但缩放关系是确定的。验证步骤固定 N8、SNR10 dB分别跑 L100、200、500、1000 各 200 次蒙特卡洛统计角度估计 RMSE再把 RMSE 乘以 sqrt(L)若结果大致平直说明误差收敛速度与理论一致。随后改变 SNR 重复一遍看 RMSE 是否按 1/sqrt(SNR) 下降。若 RMSE 在低 SNR 处明显翘起高于 CRB这是阈值效应说明工作点接近 MUSIC 失效边界不是算法写错若高 SNR 处 RMSE 仍然平直不降多半是角度网格量化误差主导需要加密网格或加插值。6.2 TOA 仿真调试的两个技巧第一个技巧先做单径测试。TOA 脚本调通前先用单径、30 dB 高信噪比验证谱峰位置准确落在构造时延上再逐条添加多径。多径添加后如果峰合并减小时延差到小于 1/B 之前先确认主线仍可分辨。第二个技巧谱峰形状是诊断工具。理想时延谱峰宽度近似为 1/B若峰宽远大于理论值检查频点数是不是太少峰旁边出现等间距小峰检查频点间隔是否引入了周期性峰位置随噪声变化大则低信噪比下频域噪声子空间不干净需要用多个符号做快照平均而不是直接对单次观测做分解。把这两个技巧写进仿真脚本的测试函数里每次改动阵列参数或频点配置后先跑一遍再进入正式统计。本文还有配套的精品资源点击获取