3. 雷达信号处理链(下):CFAR恒虚警率检测、角度估计(MUSIC/DBF)、点云生成

好,咱们接着聊。上一节我们把距离-多普勒谱给算出来了,拿到了一个二维矩阵。但说实话,那个矩阵里全是能量值,你根本分不清哪个是目标、哪个是噪声。今天要讲的这三个模块,就是专门干这个事的——从一堆乱七八糟的数据里,把真正的目标揪出来,再告诉你在哪个角度上。

3.1 CFAR恒虚警率检测——别把噪声当目标

CFAR,全称Constant False Alarm Rate。名字挺唬人,说白了就是:我设定一个虚警率,比如千分之一,然后算法自动算出每个点的检测门限。高于门限的,我认它是目标;低于的,就当噪声处理掉。

为什么不能直接用固定门限?我刚开始做雷达时也这么想过。结果有一次在测试场地,背景噪声忽高忽低——安静时门限太高漏目标,嘈杂时门限太低全是虚警。嗯,从那以后我就老老实实用CFAR了。

3.1.1 CFAR的核心思想

CFAR的做法其实很直观:

  • 对每个待检测点(CUT,Cell Under Test),在它周围画一个“保护带”
  • 保护带外面再取一圈“参考窗”
  • 用参考窗里的噪声能量,估算当前点的噪声水平
  • 乘以一个系数,得到自适应门限

你想想看,如果目标本身很大,它的旁瓣可能会污染参考窗。保护带就是干这个用的——把目标能量隔离开,只让纯噪声参与计算。

3.1.2 常见的CFAR变体

类型 特点 适用场景
CA-CFAR 取参考窗均值,计算简单 均匀噪声背景
SO-CFAR 取左右窗中较小的均值 多目标靠近时
GO-CFAR 取左右窗中较大的均值 噪声边缘区域
OS-CFAR 排序后取第k个值 杂波环境复杂

我个人习惯在车载雷达场景下用OS-CFAR。为什么?因为路上经常有多个目标挨得很近,CA-CFAR会把旁边的目标也算进噪声里,导致门限抬太高,漏掉弱目标。OS-CFAR排序后取中间值,抗干扰能力强很多。

关键参数设置:

  • 参考窗长度:一般取16~32个单元
  • 保护带长度:取目标可能占据的单元数,通常4~8个
  • 门限因子α:由虚警率Pfa和参考窗长度N决定,α = N * (Pfa^(-1/N) - 1)

3.1.3 代码实现要点

def cfar_2d(rd_map, guard_len, ref_len, pfa):
    # rd_map: 距离-多普勒谱
    # guard_len: 保护带半长
    # ref_len: 参考窗半长
    # pfa: 虚警率
    
    n_rows, n_cols = rd_map.shape
    threshold = np.zeros_like(rd_map)
    detections = np.zeros_like(rd_map, dtype=bool)
    
    # 计算门限因子
    n_ref = (2*ref_len+1)**2 - (2*guard_len+1)**2
    alpha = n_ref * (pfa ** (-1/n_ref) - 1)
    
    for i in range(ref_len, n_rows - ref_len):
        for j in range(ref_len, n_cols - ref_len):
            # 提取参考窗
            ref_win = rd_map[i-ref_len:i+ref_len+1, j-ref_len:j+ref_len+1]
            # 挖掉保护带
            guard_win = rd_map[i-guard_len:i+guard_len+1, j-guard_len:j+guard_len+1]
            # 计算噪声均值
            noise = (np.sum(ref_win) - np.sum(guard_win)) / n_ref
            # 设置门限
            threshold[i, j] = alpha * noise
            # 判决
            detections[i, j] = rd_map[i, j] > threshold[i, j]
    
    return detections, threshold

我曾经踩过的坑:边界处理。如果目标刚好在距离维或多普勒维的边缘,参考窗会越界。我早期没做边界填充,结果边缘区域全是漏检。后来加了镜像填充,问题才解决。

3.2 角度估计——目标到底在哪个方向?

CFAR检测完,我们知道了目标在哪个距离、哪个速度上。但还缺一个关键信息——角度。没有角度,你只知道目标在某个距离圈上,但不知道具体方位,这跟没检测差不多。

角度估计依赖的是多根接收天线之间的相位差。信号到达不同天线时,走过的路程不一样,相位也就不同。通过这个相位差,就能反推出角度。

3.2.1 DBF——数字波束形成

DBF是最直观的方法。说白了就是:我假设目标在某个角度θ,然后给每根天线的信号补一个相位偏移,让它们对齐。如果对齐后信号叠加起来能量最大,那θ就是真实角度。

数学上就是做加权求和:

def dbf_angle(data, theta_range, d, lambda_):
    # data: 各天线数据,shape=(n_ant,)
    # theta_range: 扫描角度范围
    # d: 天线间距
    # lambda_: 波长
    
    n_ant = len(data)
    spectrum = []
    
    for theta in theta_range:
        # 计算导向矢量
        steering = np.exp(-1j * 2*np.pi * d * np.arange(n_ant) * np.sin(theta) / lambda_)
        # 波束形成
        output = np.abs(np.dot(steering.conj(), data))
        spectrum.append(output)
    
    # 找峰值
    peak_idx = np.argmax(spectrum)
    return theta_range[peak_idx], spectrum

DBF的好处是计算量小,适合实时处理。但分辨率受限于天线数量——天线越多,波束越窄,分辨能力越强。我做过一个4天线的雷达,DBF只能分辨出大概15°间隔的两个目标,再近就糊在一起了。

3.2.2 MUSIC——超分辨率算法

当两个目标挨得很近,DBF分不开时,就该MUSIC上场了。MUSIC的全称是Multiple Signal Classification,它利用了信号子空间和噪声子空间的正交性。

核心思路是这样的:

  1. 把多帧数据拼成协方差矩阵
  2. 做特征值分解,把特征向量分成信号子空间和噪声子空间
  3. 在噪声子空间里搜索,找到使“投影最小”的角度——那就是目标
def music_angle(data, n_sources, theta_range, d, lambda_):
    # data: 多快拍数据,shape=(n_ant, n_chirps)
    # n_sources: 目标数量
    
    n_ant = data.shape[0]
    
    # 协方差矩阵
    R = np.dot(data, data.conj().T) / data.shape[1]
    
    # 特征值分解
    eigvals, eigvecs = np.linalg.eigh(R)
    
    # 取噪声子空间(最小的n_ant - n_sources个特征向量)
    noise_subspace = eigvecs[:, :n_ant - n_sources]
    
    # 搜索角度
    spectrum = []
    for theta in theta_range:
        steering = np.exp(-1j * 2*np.pi * d * np.arange(n_ant) * np.sin(theta) / lambda_)
        # 投影到噪声子空间
        proj = np.dot(steering.conj(), noise_subspace)
        power = 1 / np.abs(np.dot(proj, proj.conj()))
        spectrum.append(power)
    
    # 找峰值
    peaks = find_peaks(spectrum, n_sources)
    return theta_range[peaks], spectrum

我的经验:MUSIC虽然分辨率高,但对信噪比敏感。SNR低于10dB时,MUSIC的性能会急剧下降。我在项目中一般这样搭配:SNR高时用MUSIC精细测角,SNR低时切回DBF保底。

3.3 点云生成——把检测结果变成可用数据

CFAR给了我们距离-多普勒单元,角度估计给了我们角度。现在要把这些信息拼起来,生成一个完整的点云——每个点包含距离、速度、角度、信噪比四个维度。

3.3.1 点云结构

一个标准的雷达点云,通常长这样:

字段 含义 单位
x 横向位置 米
y 纵向位置 米
z 高度(部分雷达支持) 米
v 径向速度 米/秒
snr 信噪比 dB

从距离-角度转换到xy坐标,其实就是极坐标转直角坐标:

x = range * sin(azimuth)
y = range * cos(azimuth)

3.3.2 点云聚合与过滤

CFAR检测出来的点,往往一个目标会对应好几个相邻单元。直接全部输出的话,点云会非常密集,而且很多是冗余的。我一般会做两步处理:

  1. 聚类:把空间上相邻的点归到一个目标里,取质心作为最终位置
  2. 过滤:去掉SNR低于阈值的点,去掉速度异常的点(比如突然出现的静止目标)

点云生成的关键流程:

  • CFAR检测 → 得到距离-多普勒单元索引
  • 提取该单元的复数值 → 用于角度估计
  • 角度估计 → 得到方位角
  • 坐标转换 → 距离+角度 → xy坐标
  • 速度提取 → 从多普勒维得到径向速度
  • SNR计算 → 信号能量 / 噪声基底

3.4 本章知识体系总览

下面这张图,把今天讲的三个模块串起来了。你可以看到数据是怎么一步步从原始回波变成点云的:

雷达信号处理链(下)—— 从回波到点云 距离-多普勒谱 (上一章输出) CFAR恒虚警检测 提取目标单元 角度估计 DBF / MUSIC 点云生成 坐标转换 + 聚合 点云输出 每个检测单元的处理流程: 1. 从距离-多普勒谱提取复数值 2. 多天线数据组成向量 → 角度估计 3. 距离 + 角度 → 极坐标转直角坐标 4. 多普勒维提取速度信息 5. 计算SNR,过滤低质量点

嗯,到这里,雷达信号处理链的核心部分就讲完了。从原始回波到点云,每一步都有它的道理,也都有它的坑。CFAR帮你过滤噪声,角度估计告诉你方位,点云生成把结果整理成可用的格式。这三步走完,你手里的数据才真正能拿去给上层算法用——比如跟踪、分类、或者直接送给自动驾驶的决策模块。

最后提醒一句:点云的质量,很大程度上取决于前面每一步的参数设置。CFAR的门限因子、MUSIC的目标数量估计、DBF的扫描步长——任何一个参数没调好,点云都会出问题。我建议你在实际项目中,先把每个模块单独调通,再串起来联调。别问我怎么知道的,都是泪。

可可精品资料库