news 2026/9/14 2:22:04

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
水下目标方位估计的两种路径: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, fs=4000.0, T=1.0, c=1500.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), dtype=complex) 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, c=1500.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 aargsort取后三名是为了同时看到主峰和次大峰,真实目标对应前两个最大值。这段代码验证的是最基础的窄带 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_ch=1, n_angle=181): super().__init__() self.features = nn.Sequential( nn.Conv2d(in_ch, 16, 3, padding=1), # 频域和方位域都是局部相关 nn.BatchNorm2d(16), nn.ReLU(), nn.MaxPool2d(2), # 空间尺寸减半 nn.Conv2d(16, 32, 3, padding=1), 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, c=1500.0): """X: (M, N),返回 (N_freq, N_angle) 的 dB 谱图""" from scipy.signal import stft _, f, Z = stft(X, fs, nperseg=1024, noverlap=512) 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(), lr=1e-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(axis=0).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(dim=1).numpy() - 90 truth = label.numpy() - 90 errs.extend(np.abs(est - truth)) return np.array(errs)

这里spec.mean(axis=0)把频带内所有频点的空间谱平均成一条一维方位曲线,再取峰值。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, sigma=2.0, n_class=181): 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, dim=1) grid = torch.arange(-90, 91, 1.0) est = (prob * grid).sum(dim=1)

最后一个技巧出现在部署阶段。CNN 输入的特征图是 CBF 谱图,而谱图的动态范围在不同频带上差异很大,训练时减均值除标准差所用的均值方差,要在部署时原样保存,用同一个 scaler 处理推理数据。现实中常见的错误是:训练脚本里用整批数据的全局均值归一化,部署代码里却用单帧均值归一化,两套数字不一致,方位估计在目标远离视场中心时产生系统性偏移。把均值方差和模型权重一起导出,这个坑就绕开了。

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

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

单级圆柱齿轮减速器设计与维护全解析

1. 单级圆柱齿轮减速器概述 单级圆柱齿轮减速器是机械传动领域最常见的减速装置之一&#xff0c;它通过一对相互啮合的圆柱齿轮实现转速降低和扭矩增大的功能。这种减速器结构简单、制造方便、传动效率高&#xff08;通常可达98%以上&#xff09;&#xff0c;在工业生产中应用极…

作者头像 李华
网站建设 2026/9/14 2:20:19

高并发排行榜方案:Redis ZSET + 快照缓存 + 双TTL

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/14 2:20:00

yuzu模拟器:5分钟在电脑上跑起Switch游戏

yuzu模拟器&#xff1a;5分钟在电脑上跑起Switch游戏 【免费下载链接】yuzu 任天堂 Switch 模拟器 项目地址: https://gitcode.com/GitHub_Trending/yu/yuzu 你在家里想玩Switch游戏&#xff0c;却不想每次都得把掌机翻出来&#xff1f;yuzu就是一台开源的任天堂Switch模…

作者头像 李华
网站建设 2026/9/14 2:18:46

YOLOv8火灾烟雾人员检测:数据集标签质量与训练调优实战

简介&#xff1a;YOLO系列算法的火灾与人员探测数据集&#xff0c;面向目标检测开发者和算法学习者&#xff0c;主要解决智能监控、消防预警、应急巡查等场景下的人、烟、火三类目标识别问题。压缩包内共2000个标注文件&#xff0c;包含VOC格式的XML与YOLO格式的TXT两种标签&am…

作者头像 李华
网站建设 2026/9/14 2:18:16

Keploy record 的 mock 存储格式 yaml 和 gob 怎么选?

Keploy record 的 mock 存储格式 yaml 和 gob 怎么选&#xff1f; 【免费下载链接】keploy Open-source platform for creating safe, isolated production sandboxes for API, integration, and E2E testing. 项目地址: https://gitcode.com/GitHub_Trending/ke/keploy …

作者头像 李华