十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

水下目标方位估计的两种路径:CBF与CNN的时间窗设计对比

水下目标方位估计的两种路径:CBF与CNN的时间窗设计对比 简介面向水下目标方位估计研究的完整项目资料包围绕常规波束形成与卷积神经网络两种时间窗处理方案展开适合信号处理、水声工程、人工智能等专业的学生和研发人员用于毕业设计、课程设计或科研入门。压缩包大小约为四十四兆字节共包含十八个文件以十一个Python脚本为主干覆盖信号仿真、数据集生成、模型构建、训练与测试全流程两个预训练权重文件可直接加载验证另有若干文本与Markdown说明文档帮助理解使用方法和工程配置。项目源码经过运行验证并曾获导师指导认可、答辩评审得分九十五可直接复现实验结果或二次开发改进。目前已有五十六人下载学习对希望快速上手水下目标方位估计、对比传统信号处理与深度学习方案的读者而言是一份结构清晰、可操作性强的优质参考资料。1. 水下目标方位估计CBF 与 CNN 被时间窗绑在一起水下目标方位估计要回答的问题很具体一段声呐阵列接收数据里哪个方向上有目标、目标有几个、角度是多少。常规波束形成Conventional Beamforming, CBF用物理模型回答它把阵列信号按假设方向做延迟补偿再求和输出功率最大的方向就是目标方位。卷积神经网络CNN换了一种路径先把时间窗内的数据变换成二维特征图再从大量带标签样本里学出方位到特征的映射。这两条路线看似分叉实际上都被同一个变量钳制着——时间窗。窗长决定 CBF 协方差矩阵的估计质量和频域分辨率也决定 CNN 输入张量的时间跨度和样本数量。这篇文章把两条路线串起来讲清楚CBF 从公式到仿真CNN 从特征构造到训练再给一套可复现的对比实验方法和调参技巧。适合正在做声呐阵列信号处理、水下无人平台感知的工程师也适合刚接触深度学习 DOA 估计的研究者。2. 常规波束形成的时间窗原理与仿真实现2.1 从接收数据到空间谱CBF 的数学形式考虑一个 M 元均匀线阵ULA阵元间距 d远场目标以方位角 θ 入射。在窄带假设下第 m 个阵元相对参考阵元的传播时延是τ_m(θ) (m · d · sinθ) / c这个时延在频点 f 上体现为相位旋转 e^{-j2πfτ_m(θ)}。把每个可能方向上的相位补偿向量写成导向矢量 a(θ)CBF 做的事情就是匹配滤波用 a(θ) 对阵列接收向量做加权求和得到该方向的输出功率P(θ) a^H(θ) · R · a(θ)其中 R 是阵列协方差矩阵。实际工程中 R 是从有限长观测里估计出来的这就引出时间窗的第一层作用——所有统计量都来自一段有限时间内的快拍。窗内快拍数 L 越多协方差估计的方差越小但“窗内信号统计保持平稳”这个假设越容易被目标运动和信道时变打破。CBF 的方位分辨能力由波束宽度决定瑞利限大约是 0.886λ / (M·d·cosθ)两个目标夹角小于半个波束宽度时CBF 只有一个宽峰这一基线后面会和 CNN 的对比直接相关。2.2 时间窗的三个关键参数与一张参数表时间窗设计不是随便选个长度就行。下面三个参数在 CBF 实现里必须一起定窗长 T。T 直接决定频率分辨率 Δf ≈ 1/T。水下目标噪声是宽带的CBF 通常先在频域做窄带处理再宽带平均频点间隔由 STFT 窗长决定而协方差矩阵 R 的积攒窗长又决定了做统计的快拍数。这两个窗要分开看STFT 窗短了频率分辨率差R 平均窗短了空间谱方差大。重叠率。相邻时间窗之间的重叠率控制输出空间谱帧率。重叠越高方位估计的轨迹越平滑但计算量线性上升。窗函数。汉宁窗降低频谱泄漏代价是主瓣略宽矩形窗分辨率最好但旁瓣高。水声里目标与干扰动态范围常超过 20 dB旁瓣会掩盖弱目标所以汉宁窗是更稳妥的起点。参数常规取值影响方向STFT 窗长512~2048 点 4~48 kHz 采样频率分辨率、频点数量R 平均窗长0.5~2 s协方差估计方差、平稳性假设重叠率50%~75%输出帧率、计算量窗函数汉宁 / 海明主瓣宽度、旁瓣电平这个矛盾没办法完全消除只能按场景折中。常规做法是固定平台先取 1 s 起步高动态场景压缩到 100~200 ms频带很窄、需要分辨接近目标时把 STFT 窗拉长R 平均窗保持可接受的运动误差。2.3 用 Python 跑通最小 CBF 估计下面这段代码生成两个同频目标-20° 和 15°用 16 阵元均匀线阵接收然后扫角度画空间谱import numpy as np def gen_ula_signal(theta, f, M, d, fs4000.0, T1.0, c1500.0): 生成一个窄带目标的 ULA 接收数据返回 (M, N) 复数矩阵 N int(fs * T) t np.arange(N) / fs s np.sin(2 * np.pi * f * t) X np.zeros((M, N), dtypecomplex) for m in range(M): tau m * d * np.sin(np.deg2rad(theta)) / c X[m, :] s * np.exp(-1j * 2 * np.pi * f * tau) return X def cbf_scan(X, f, d, c1500.0): 对 (M, N) 数据做单频 CBF 扫描返回 (角度数组, 空间谱) M, N X.shape R (X X.conj().T) / N theta np.arange(-90, 91, 1) P np.zeros(len(theta)) for i, th in enumerate(theta): tau np.arange(M) * d * np.sin(np.deg2rad(th)) / c a np.exp(-1j * 2 * np.pi * f * tau) P[i] np.real(a.conj() R a) return theta, P M, d, fs 16, 1.0, 4000 f0, T 400.0, 1.0 X gen_ula_signal(-20, f0, M, d, fs, T) gen_ula_signal(15, f0, M, d, fs, T) rng np.random.default_rng(0) X 0.5 * (rng.standard_normal(X.shape) 1j * rng.standard_normal(X.shape)) theta, P cbf_scan(X, f0, d) for idx in np.argsort(P)[-3:]: print(theta[idx], P[idx])代码里的R (X X.conj().T) / N就是时间窗内的最大似然协方差估计N 是窗内快拍数。扫描时对每一个假设角度构造导向矢量直接算二次型a^H R a。argsort取后三名是为了同时看到主峰和次大峰真实目标对应前两个最大值。这段代码验证的是最基础的窄带 CBF实际工程目标频带很宽需要把数据做 STFT在每个频点做同样扫描再把频带内的空间谱取对数平均思路不变。3. 卷积神经网络时间窗方法特征构造与模型训练3.1 把时间窗内容做成 CNN 输入两种常见特征CNN 不能直接吃复数原始波形必须先把每个时间窗内的数据变换成适合卷积操作的张量。常见做法有两种。第一种是把时间窗内 M 个阵元的时域波形堆叠成一张二维图形状是 M × N。阵元维度当图像的高时间维度当宽。这种输入保留了完整相位信息网络可以自行学习波前结构理论上不损失信息但缺点也很明显采样率变化、阵元数变化、阵型变化都会让输入分布整体漂移换一个阵型基本要重新训练。卷积、池化在时间维上移动时网络学的其实是“某一阵元间距下”的相位差模式泛化到不同布阵配置时表现下降明显。第二种是先用 CBF 做预处理对每个时间窗内的数据做 STFT在每个频点、每个时间帧计算窄带空间谱再把时间维求平均得到一张“频率 × 角度”的功率谱图。这张图就是 CNN 的输入形状是 N_freq × 181。这种输入非常贴近声呐操作员看的东西网络任务从“从波形猜角度”退化成“从谱图峰值猜角度”训练数据需求量小得多也更容易做可视化解释。代价是输入的信息被 CBF 的波束宽度限制住了CNN 不可能从一张已经模糊的谱图里恢复出超越瑞利限的细节。如果目标是做超分辨输入中必须保留原始阵元数据。从工程落地角度看第二种更常见。CBF 谱图天然对角度做了两维压缩输入维度从 M × N 降到一个固定尺寸网络结构稳定训练也快。下面按第二种继续实现。3.2 一个可以跑的最小 CNN 结构输入谱图形状是 (N_freq, 181)N_freq 取频带内频点数比如 100~800 Hz 带宽、Δf≈1 Hz 时大约 700 个频点但相邻频点高度相关实际可以每几个频点取一个压到 64 个左右。下面给出一个可以直接训练的小网络import torch.nn as nn class AzimuthCNN(nn.Module): def __init__(self, in_ch1, n_angle181): super().__init__() self.features nn.Sequential( nn.Conv2d(in_ch, 16, 3, padding1), # 频域和方位域都是局部相关 nn.BatchNorm2d(16), nn.ReLU(), nn.MaxPool2d(2), # 空间尺寸减半 nn.Conv2d(16, 32, 3, padding1), nn.BatchNorm2d(32), nn.ReLU(), nn.MaxPool2d(2), nn.AdaptiveAvgPool2d(1), ) self.classifier nn.Sequential( nn.Linear(32, 64), nn.ReLU(), nn.Dropout(0.3), nn.Linear(64, n_angle), ) def forward(self, x): x self.features(x) return self.classifier(x.flatten(1))卷积核尺寸固定 3×3第一层在频率方向看局部频带的相关性在方位方向看邻近角度的连续性。AdaptiveAvgPool2d(1)把最后特征图压成一个向量避免谱图尺寸变化时全连接层无法匹配。输出维度 181 对应 -90° 到 90° 每个整度一个类别交给交叉熵损失训练。这个网络参数量很小几万量级在几百个样本上就能开始收敛不容易把仿真数据的噪声背下来。3.3 仿真数据生成与训练流程仿真数据生成是最容易出错的一步直接影响 CNN 能否学到可泛化的方位特征。每个样本按以下方式生成def cbf_spectrogram(X, fs, f_low, f_high, d, c1500.0): X: (M, N)返回 (N_freq, N_angle) 的 dB 谱图 from scipy.signal import stft _, f, Z stft(X, fs, nperseg1024, noverlap512) fk f[(f f_low) (f f_high)] theta np.arange(-90, 91, 1) spec np.zeros((len(fk), len(theta))) for i, fi in enumerate(fk): Zf Z[:, (f f_low) (f f_high), :][:, i, :] for j, th in enumerate(theta): a np.exp(-1j * 2 * np.pi * fi * d * np.sin(np.deg2rad(th)) * np.arange(M) / c) P np.abs(a.conj() Zf) ** 2 / M ** 2 spec[i, j] 10 * np.log10(P.mean() 1e-12) return fk, theta, spec外层训练数据循环需要随机化四个要素方位角在 -80°~80° 内均匀采样避开端射方向那里阵列本身模糊信噪比在 -5~15 dB 间随机目标个数 1~2 个两个目标时夹角随机 8°~40°覆盖可分辨与不可分辨区域每次生成独立的噪声种子。做 5000~8000 个样本按时间顺序切出前 80% 做训练、后 20% 做验证。注意不要随机打乱后划分相邻时间窗来自同一段声场相关性很强随机打乱会让验证集虚高。loss_fn nn.CrossEntropyLoss() opt torch.optim.Adam(model.parameters(), lr1e-3) for epoch in range(30): for spec, label in train_loader: opt.zero_grad() out model(spec.unsqueeze(1)) # (B, 1, N_freq, 181) loss loss_fn(out, label) loss.backward() opt.step()label是角度对应的类别索引计算方式是label int(round(theta_deg 90))。训练时把谱图减均值除标准差做归一化dB 值分布会更稳BatchNorm层也能部分缓解这个问题。这里展示的是单目标分类两个目标时输出改成多标签或直接回归多个角度逻辑一样只是损失函数换成二值交叉熵或 SmoothL1。4. 两种时间窗方法的对比实验设计4.1 公平对比的实验设置把 CBF 和 CNN 放在同一把尺子下面量需要严格控制变量。第一条是同一份测试数据。测试集由另一个随机种子独立生成仿真参数可以一样但样本不能和训练集重叠第二条是同一个时间窗长度。CBF 的 R 平均窗、CNN 输入谱图的 STFT 窗都取自同一段接收数据不能给 CBF 多帧平均、给 CNN 单帧输入第三条是同一个角度网格。CBF 的峰值搜索和 CNN 的类别输出都定义在 -90°~90°、间隔 1° 的网格上。评估代码按下面的方式组织def evaluate_cbf(test_data, f_low, f_high, d): errs [] for X, true_theta in test_data: fk, theta, spec cbf_spectrogram(X, fs, f_low, f_high, d) est theta[spec.mean(axis0).argmax()] errs.append(abs(est - true_theta)) return np.array(errs) def evaluate_cnn(model, test_loader): errs [] model.eval() with torch.no_grad(): for spec, label in test_loader: logits model(spec) est logits.argmax(dim1).numpy() - 90 truth label.numpy() - 90 errs.extend(np.abs(est - truth)) return np.array(errs)这里spec.mean(axis0)把频带内所有频点的空间谱平均成一条一维方位曲线再取峰值。CNN 侧直接取分类输出的最大激活角度。两份误差数组先保存下来再做汇总统计。做误差随信噪比变化的曲线时把测试样本按真实 SNR 分成若干桶在每个桶内分别计算 RMSE 和检测率。4.2 评估指标与结果解读的一张对照表对比维度CBF 的常规表现CNN 的常规表现低 SNR 鲁棒性峰值易被噪声淹没-5 dB 以下跳变明显空间谱统计特征更稳低 SNR 退化更平缓角度分辨率受瑞利限约束夹角小的目标只能看到一个峰输入含原始阵元信息时有机会超分辨输入只有 CBF 谱图时同样受限多目标场景峰值个数靠人为阈值判断训练时见过多少目标模式推理就按哪种模式输出计算开销单帧毫秒级无训练成本训练成本高推理在 GPU 上毫秒级泛化到真实海况物理模型不依赖数据换海区可用仿真到实测必然有分布偏移需要迁移/微调这张表不承诺具体数字趋势性结论来自水声阵列处理文献和工程经验。真正写报告时把上面两个评估函数跑出来的结果填入表格比任何定性描述都有说服力。需要注意的一点是如果 CNN 的输入是 CBF 谱图它的可解释性反而比纯黑盒好——把测试样本输入网络用 Grad-CAM 之类的工具看激活区域理想情况下会落在谱图能量峰附近如果激活分散在无能量的频带说明网络学到了仿真的伪相关而不是物理上的方位特征。还有一类对比容易被忽略计算复杂度。CBF 的扫描角度数乘以频点数是它的浮点预算整套算法可以用 FPGA 或 DSP 裸奔CNN 推理在嵌入式端通常要借助 NPU模型量化后精度会掉 1~2°对比实验里最好把量化误差一起算进去否则部署后复现不了仿真结果。5. 参数调优、验证技巧与部署注意点5.1 时间窗参数的现场检查清单在水池或海试数据上跑之前先在仿真数据上过一遍这个清单第一窗长是否让目标在窗内发生了明显角度变化。判断方法很简单同一段数据分别用 0.5 s 和 2 s 的窗长跑 CBF对比空间谱峰宽峰明显变宽就是窗太长。水下低速目标通常问题不大但平台本身机动时窗内的方位变化率不是目标速度决定的是平台转弯角速度决定的。第二STFT 窗长和频带宽度是否匹配。Δf≈1/T如果目标信号在某个频点是窄带线谱T 太短会把线谱的能量抹到多个频点里CBF 宽带平均后信噪比下降。第三CNN 样本的标签是否严格对应时间窗中心。连续目标在多个窗间移动如果标签取的是窗头方位而不是窗中心相邻窗之间的估计会出现持续的滞后偏差。5.2 训练与推理中的三个修正技巧时间窗样本不是独立的。同一个目标在相邻窗内高度相关直接随机切分训练验证集会泄漏信息。改成按时间段切分后验证集误差会变高但这才是真实水平。第二个技巧是给角度标签加高斯软边界。把 one-hot 标签换成以真实角度为中心的高斯分布网络就不再把邻近角度当成完全错误def build_soft_label(theta_deg, sigma2.0, n_class181): grid np.arange(-90, 91, 1) label np.exp(-0.5 * ((grid - theta_deg) / sigma) ** 2) return label / label.sum()配合 KL 散度损失使用。推理时不用argmax而是计算 softmax 输出在整个角度网格上的期望值量化误差会从 ±1° 减小到零点几度prob torch.softmax(logits, dim1) grid torch.arange(-90, 91, 1.0) est (prob * grid).sum(dim1)最后一个技巧出现在部署阶段。CNN 输入的特征图是 CBF 谱图而谱图的动态范围在不同频带上差异很大训练时减均值除标准差所用的均值方差要在部署时原样保存用同一个 scaler 处理推理数据。现实中常见的错误是训练脚本里用整批数据的全局均值归一化部署代码里却用单帧均值归一化两套数字不一致方位估计在目标远离视场中心时产生系统性偏移。把均值方差和模型权重一起导出这个坑就绕开了。本文还有配套的精品资源点击获取
返回列表