首页 / 资讯中心 / 文章详情

PLFM_RADAR:基于脉冲线性调频的雷达信号处理验证平台

PLFM_RADAR:基于脉冲线性调频的雷达信号处理验证平台 ★ FEATURED ARTICLE
做雷达信号处理的朋友看到 PLFM_RADAR 这个名字大概率能猜个七八分PLFM 是 Pulsed Linear Frequency Modulation也就是脉冲线性调频RADAR 自然就是雷达。这套项目本质上是一套以 PLFM 波形为核心的雷达信号处理验证平台覆盖从波形设计、回波仿真、脉冲压缩、MTD 到 CFAR 检测的完整链路。做它的初衷很简单我在验证不同波形参数下的检测效果时发现 Matlab 脚本散得到处都是改一个带宽参数要翻好几个文件跑出来的中间结果也没法统一对比。所以就用 Python 从头搭了一个配置驱动的验证平台。这套东西适合正在学雷达信号处理的学生、刚接触雷达算法的工程师以及想快速验证波形设计的硬件同事参考。1. 项目背景与设计思路1.1 为什么叫 PLFM_RADAR从名字看核心需求线性调频是这个平台最核心的波形基础。雷达领域里脉冲线性调频信号几乎是中近程探测的标准选项车载毫米波雷达、交通流量检测、工业液位计、气象雷达里都能看到它的影子。它最吸引人的地方是能同时兼顾“看得远”和“看得清”。直观理解一下信号要想在噪声里被检测到能量必须足够大这就需要脉冲时宽长信号要想把两个距离很近的目标分开带宽必须足够大分辨率才高。常规单频脉冲很难同时满足这两个要求而 PLFM 通过在一个脉冲内把频率从低扫到高或者从高扫到低让时宽和带宽可以独立选择再用匹配滤波把展宽的能量重新压成一个非常窄的尖峰。这就是“脉冲压缩”技术。这套项目要解决的就是把这条链路在软件里完整跑通。不是只做一个仿真 demo而是让每个模块可以独立替换、参数可以集中配置、中间结果可以自动存盘。我以前吃过不少亏在一堆脚本里改了一个 PRF结果后面所有绘图函数都拿旧参数去跑出来一张错得离谱的距离-多普勒图排查了半天才发现是传参漏了。所以这次从架构上就把参数和逻辑分开。1.2 系统模块怎么划分才算清晰我一开始想得很简单写一个大函数把生成波形、仿真回波、检测全做完。但很快就发现只要你想换一种 CFAR 策略、换一个窗函数或者对比两种波形参数这个“大函数”就会变成一团乱麻。最后我把整个流程拆成了五个标准模块波形生成模块负责产生 PLFM 发射信号以及加窗后的参考信号。回波仿真模块模拟点目标回波支持距离延迟、多普勒频移、噪声叠加。接收机处理模块完成脉冲压缩、MTI/MTD。检测模块做二维 CFAR输出距离和速度的点迹。可视化与上报模块绘制中间量图和最终检测结果保存 npz 数据。每个模块之间只通过 numpy 数组和配置字典传递数据。这个设计的好处是我后来把脉压从时域卷积改成频域实现只动了 processing 模块内部外面完全无感。配置驱动还让我能一键跑批实验把带宽、脉宽、PRF 的枚举组合全部自动化结果统一存在一个目录里。这种可复现性是脚本时代最缺的。1.3 技术栈选型为什么又用回 Python有人可能会问雷达信号处理不是用 C 或 FPGA 更合适吗确实是但那是对实时处理而言。PLFM_RADAR 的定位是算法验证和教学研究更看重开发速度、代码可读性和生态成熟度所以选了 Python。具体依赖只有几个numpy 做数组运算scipy 提供信号处理工具numba 加速 CFAR 这类带循环的模块matplotlib 画图pyyaml 读配置。后期如果要把某个模块落地成 C接口边界也已经划好了直接替换就行。我实测下来5 万点规模的脉冲压缩在 numpy 频域实现下耗时在毫秒级256 个脉冲的多普勒处理也不到一秒对离线验证完全够用。真正慢的是 CFAR 二维遍历后面我会专门讲怎么优化。2. 核心信号处理链路拆解2.1 PLFM 信号模型与关键参数设计PLFM 的复基带表达式可以写成s_tx(t) exp(j * 2 * pi * (fc * t 0.5 * K * t^2))其中K B / Tp就是调频斜率。B 是扫频带宽Tp 是脉冲时宽fc 是载频。距离分辨率由带宽决定ΔR c / (2 * B)这个公式一定要记住很多参数选择都要从它出发。比如要求距离分辨率 0.6 米带宽就要 250 MHz分辨率 1 米带宽大约 150 MHz。带宽越大采样率和存储量就越大所以实际项目里不会无限堆带宽。脉宽 Tp 主要影响探测能量和最小作用距离。脉宽越大脉冲能量越大检测信噪比越高但近距离盲区也越大因为发射期间接收机不能工作。我通常在系统里先定带宽满足分辨率再看 Tp 是否满足信噪比要求最后用 PRF 来平衡距离模糊和速度模糊。2.2 回波建模延迟、衰减和多普勒回波仿真最基础的是距离延迟。目标距离 R回波相对发射信号的延迟就是τ 2R / c。在数字域延迟要换算成采样点数我用int(round(τ * fs))同时回波幅度按R^2衰减这里简化成固定的幅度缩放不模拟具体的雷达方程。多普勒频移是另一个核心。当目标有径向速度 v 时回波的载频会发生偏移多普勒频率fd 2v / λλ 是波长。这里最容易搞混的是符号约定我定义目标靠近雷达时 v 为正回波频率比发射高对应的移频项是exp(-j * 2 * pi * fd * t)还是exp(j * 2 * pi * fd * t)取决于你的基带定义。只要保证发射、回波、脉压参考三者的相位关系一致就行我建议在代码注释里把坐标系写死不然测速方向反了会排查很久。PLFM 还有一个特性叫距离-多普勒耦合。简单说如果目标速度太快脉冲压缩后的峰值位置会发生偏移偏移量约等于ΔR c * fd / (2K)。在带宽大、速度高时不能忽略。我在参数验证里专门加了几个高速场景目的就是观察这个偏移并设计补偿方案。2.3 脉冲压缩把“大网”收成“尖峰”脉冲压缩是 PLFM 雷达的核心处理。发射信号是一个扫频长脉冲回波经过匹配滤波后能量被压缩成一个宽度约为1/B的主峰同时旁瓣出现在两侧。用生活里的事类比发射信号就像撒一张大网网的面积大捞到的能量多匹配滤波就是把网迅速收紧把猎物集中到一个点上。实现上我优先用频域方式N next_pow2(len(ref) len(echo) - 1) REF np.fft.fft(ref, N) ECHO np.fft.fft(echo, N) pc np.fft.ifft(ECHO * np.conj(REF)) pc pc[:len(echo)]这里参考信号ref取发射信号本身np.conj(REF)就是匹配滤波器的频域表示。为什么不是时域卷积因为频域相乘在长度大时计算量远小于时域卷积numpy 的 FFT 也优化得很好。但有一点必须注意FFT 点数不够会造成循环卷积混叠说白了就是远的回波会“绕”回到近处形成假目标。所以我强制把 FFT 长度补到不小于两段信号长度之和减一再把输出裁剪到原始回波的长度。这个看似小的问题我见过很多人踩坑。旁瓣抑制也很关键。不加窗时LFM 脉冲压缩的第一旁瓣只比主瓣低约 13 dB两个稍大目标旁边的小目标很容易被淹没。我通常给参考信号乘一个海明窗或泰勒窗代价是主瓣会稍微变宽SNR 损失 12 dB。做检测前我会先确认旁瓣水平是否满足系统需求。2.4 MTI、MTD 和测速原理静止杂波和目标的分辨靠的是多普勒维处理。平台里我先做脉冲间对消MTI来滤掉零速杂波常见的是二脉冲对消y[n] x[n] - x[n-1]或者三脉冲对消。MTI 简单有效但会损失部分低速目标所以真正测速要靠 MTD。MTD 就是把同一个距离门的多个脉冲沿慢时间做 FFT。具体做法先把每一帧回波做距离维脉压得到矩阵[脉冲数, 距离门数]然后沿脉冲维做 FFT输出就是距离-多普勒图。每个多普勒单元对应一个速度v λ * fd / 2速度分辨率由积累时间和脉冲数决定Δv λ * PRF / (2 * N_pulse)这里 N_pulse 是相参积累的脉冲数。脉冲越多速度分辨越细但数据处理时间也越长。我在默认配置里用 N_pulse 25624 GHz 载频、PRF 10 kHz 下速度分辨率约 0.24 m/s对慢速行人检测也够用。多普勒模糊是另一个常见问题。当目标速度对应的 fd 超过PRF/2时速度会出现折叠测出来可能是错的。最大不模糊速度v_max λ * PRF / 4以 24 GHz、PRF 10 kHz 为例λ 约 0.0125 米v_max 约 31.25 m/s对于一般交通场景够用。要是遇到高速目标就得用多重 PRF 做解模糊这也是平台后续要扩展的方向。3. 工程实现与实操过程3.1 代码目录与配置驱动流程项目目录我保持得很干净plfm_radar/ ├── config/ │ └── radar.yaml ├── src/ │ ├── waveform.py │ ├── echo_sim.py │ ├── processing.py │ ├── detection.py │ └── plot.py ├── main.py └── requirements.txtmain.py 是唯一入口流程很直观读 yaml 配置、生成波形、仿真回波、脉压、MTD、CFAR、绘图。我把所有参数都放到了 yaml 里包括目标信息这样改场景不用动代码。radar.yaml 的核心字段是这样的fc: 24e9 bandwidth: 250e6 pulse_width: 20e-6 prf: 10000 fs: 300e6 pulse_count: 256 targets: - range: 500.0 velocity: 20.0 rcs: 1.0这么做有几个立竿见影的好处一是实验记录可以直接保留 yaml 文件回头能复现二是批量跑参数时只需循环改 yaml不用在每个脚本里翻找赋值三是文件命名可以自动带上参数摘要比如result_B250M_PR10k_256pulse.npz一看就知道是什么工况。3.2 波形生成与回波仿真的关键代码波形模块的核心函数长这样import numpy as np def make_lfm(fc, B, Tp, fs): t np.arange(0, Tp, 1.0 / fs) K B / Tp return np.exp(2j * np.pi * (fc * t 0.5 * K * t ** 2))注意这里我用的是复指数形式等于把信号搬到了复数基带进行分析。实际系统中解调之后得到的就是类似的复信号。这样处理的优势在于不用处理射频载波那一堆高频项采样率也可以按带宽来定而不是按载频来定。回波仿真我抽取一帧脉冲来写核心逻辑def simulate_pri(s, delay_points, fd, amp): n len(s) delay_points echo np.zeros(n, dtypecomplex) t np.arange(len(s)) / fs # 目标朝雷达靠近: fd 为正 echo[delay_points:delay_points len(s)] amp * s * np.exp(2j * np.pi * fd * t) return echo当 fd 0 时这个代码就是单纯延迟。当 fd 不为 0回波每个采样点会额外叠加一个相位旋转模拟多普勒频移。为了模拟多个目标我就在每个 PRI 里把不同延迟和频移的回波叠加起来最后加上高斯白噪声。延迟点数的计算要非常小心距离必须除以光速后乘采样率。我见过有人把2R/c算成了R/c结果所有目标位置都偏了一倍。代码里我会写delay int(round(2 * target_range / 3e8 * fs))这个注释我会刻意写清楚“往返距离”。3.3 脉冲压缩与加窗的工程细节脉冲压缩我封装成一个独立函数避免每次写重复的 FFT 代码def pulse_compress(echo, ref, fs): ref ref * np.hamming(len(ref)) n np.fft.fftfreq?? # no, use next pow2 N 1 (len(ref) len(echo) - 1).bit_length() REF np.fft.fft(ref, N) ECHO np.fft.fft(echo, N) pc np.fft.ifft(ECHO * np.conj(REF)) return pc[:len(echo)]这里有一个细节我先把窗函数乘到参考信号上再进行匹配滤波。这个操作等同于对参考信号的频谱做加权从而压低脉压输出旁瓣。如果要获得更高的旁瓣抑制比可以换成 Taylor 窗但计算复杂度没有区别。窗函数对 SNR 有轻微损失损失量取决于窗的等效噪声带宽。工程上我一般接受这个损失换取更好的弱目标分辨能力。如果你更看重最低可检测信噪比不加窗也是一种选择但后续检测阈值就要相应调高。3.4 MTD 处理与距离-多普勒图生成一旦拿到脉压结果我把它组织成二维矩阵行数是帧内脉冲数列数是距离门数。MTD 的核心就是沿行方向做 FFTrd_matrix np.fft.fft(pc_matrix, axis0) rd_matrix np.fft.fftshift(rd_matrix, axes0)为什么需要 fftshift因为 FFT 输出的多普勒通道是从负频率到正频率排列的不 shift 的话负速度和正速度会分成两半绘图时看起来很难受。shift 之后中心是零频两边是正负多普勒。多普勒维也建议加窗。很多做距离维加窗很熟练但忘记在多普勒维加窗。多普勒维如果不加窗频谱泄漏会让强目标“污染”相邻速度通道。我在 MTD 前对每一行的慢时间序列乘上汉宁窗slow_window np.hanning(pc_matrix.shape[0]) pc_matrix pc_matrix * slow_window[:, None]这个操作的代价是速度主瓣稍微变宽但好处是多普勒旁瓣大幅降低CFAR 检测的虚警会少很多。3.5 CFAR 检测与传统循环优化检测模块我用的是单元平均恒虚警CA-CFAR。先做一维版本的实现逻辑不难def ca_cfar_1d(x, guard, ref, pfa1e-6): n len(x) result np.zeros(n, dtypebool) alpha ref * (pfa ** (-1.0 / ref) - 1.0) for i in range(ref guard, n - ref - guard): win np.concatenate([x[i - ref - guard:i - guard], x[i guard 1:i ref guard 1]]) noise np.mean(np.abs(win)) result[i] np.abs(x[i]) alpha * noise return result这里alpha是门限因子由参考单元数 N 和虚警概率 Pfa 决定。参考单元多门限估计更稳但计算量大参考单元少虚警率会升高保护单元太大会降低对小目标的分辨能力。我用pfa1e-6、guard4、ref16作为默认值。二维 CFAR 我做得更简单先在距离维沿每个多普勒通道做一次一维 CFAR再沿多普勒维做一次一维 CFAR然后把两次检测结果做“与”合并最终得到目标点集。这种“串联 CFAR”比真正二维矩形窗 CFAR 实现简单实际效果在目标稀疏场景下足够好。但纯 Python 循环对二维距离-多普勒图来说太慢了。256 个多普勒通道、6000 个距离门两层循环可能要跑好几秒。我用了 numba 加速from numba import njit njit def cfar_2d_fast(rd, guard, ref, alpha): nr, nd rd.shape out np.zeros((nr, nd), dtypenp.bool_) for i in range(ref guard, nr - ref - guard): for j in range(ref guard, nd - ref - guard): left rd[i - ref - guard:i - guard, j] right rd[i guard 1:i ref guard 1, j] up rd[i, j - ref - guard:j - guard] down rd[i, j guard 1:j ref guard 1] noise (np.mean(left) np.mean(right) np.mean(up) np.mean(down)) / 4 out[i, j] abs(rd[i, j]) alpha * noise return outNumba 第一次调用会有编译开销但之后跑二维图只需几十毫秒到几百毫秒完全可接受。注意这里我同时取了距离维和多普勒维的参考单元做平均类似“十字形”CFAR比单纯一维串联更稳。3.6 可视化和结果落盘matplotlib 我用来画四类图回波实部波形、脉压后的距离剖面、距离-多普勒热力图、CFAR 检测点标记。关键图的代码大致是import matplotlib.pyplot as plt plt.figure(figsize(10, 6)) plt.imshow(20 * np.log10(np.abs(rd_matrix) 1e-12), aspectauto, cmapjet) plt.xlabel(Range bin) plt.ylabel(Doppler bin) plt.colorbar(labeldB) plt.savefig(rd_map.png, dpi150)实际上我会把中间数据全部用 np.savez 存下来方便事后重新画图不必重跑整个流程。文件名带上配置摘要我用一个小函数自动生成。这个习惯在排障时帮了大忙因为很多问题都不是检测算法本身而是前面某一步数据形态不对中间量一存下来问题立刻就能定位。4. 常见问题与参数调优指南4.1 脉压峰值位置不对优先检查延迟和 FFT 长度我遇到过最典型的问题是脉压后目标峰值比理论位置偏后一个小量或者偏到很远。排下来无非三类原因一是延迟计算用了单程距离二是 FFT 长度不够导致循环卷积混叠三是参考信号的频率轴方向和回波不一致。检查方法很简单先设一个速度为零的单目标理论距离已知跑完脉压后直接看峰值所在距离门。如果偏位第一步去掉所有窗函数看是不是窗函数延迟带来的影响。第二步把 FFT 长度改成len(a) len(b) - 1的 next_pow2再对比结果。第三步检查np.conj(REF)是否正确如果用的是时域相关函数可以直接和scipy.signal.correlate的结果对比。4.2 速度方向反了多普勒符号约定要统一测速方向反了十有八九是符号约定没统一。我在代码里定义目标靠近v 0fd 0回波相位项为exp(2jπfd t)。但如果你在脉冲压缩里用了np.conj之后再做慢时间 FFT符号可能翻转一次。更隐蔽的是绘图时我用 fftshift速度轴顺序会受到np.fft.fftfreq影响容易把正负速度轴标反。建议从一开始就用一个函数统一生成速度轴不要在所有绘图脚本里各写一遍。速度轴生成fd_axis np.fft.fftshift(np.fft.fftfreq(n_pulse, 1 / prf)) v_axis fd_axis * 3e8 / (2 * fc)这样标图永远和内部数据一致不会出现“目标明明在靠近图上却显示负速度”的怪象。4.3 CFAR 虚警很高或漏检严重先看热力图分布如果 CFAR 检测出来一堆杂散点不要急着调门限因子先打开距离-多普勒热力图看一眼。常见规律是热力图某一片区域特别亮比如距离零附近、零多普勒附近。这时 CFAR 参考窗如果跨越了强杂波区门限会被抬高造成边缘目标漏检而杂波区的边缘却可能因为参考单元不对称而虚警。我的处理办法是在做 CFAR 之前先做 MTI 或高通滤波把零速杂波压下去如果压不干净就增加保护单元数量并确保参考窗不跨过强杂波边界。另一个土办法是对数域做 CFAR。距离-多普勒图动态范围大线性域均值容易被强目标带偏转成 dB 后噪声分布更平稳门限效果往往更好。4.4 参数调优速查表我整理了一张默认参数表适合做早期验证不一定最优但能保证链路跑通参数默认值作用调大后的影响调小后的影响带宽 B250 MHz距离分辨率分辨率变好数据量变大分辨率变差脉宽 Tp20 μs脉冲能量SNR 更高盲区变大SNR 下降PRF10 kHz模糊距离/速度不模糊速度变大距离变短不模糊距离变大速度变短采样率 fs300 MHz距离采样精度数据量变大可能欠采样脉冲数256多普勒分辨率速度分辨更好处理变慢速度分辨变差CFAR Pfa1e-6虚警概率门限更高虚警更多调参的本质是各种矛盾指标的折中。距离模糊和速度模糊是雷达里最经典的矛盾想要不模糊距离远PRF 就得低想要不模糊速度高PRF 就得高。实际项目里要么选折中 PRF要么用多重 PRF 解模糊。我建这个平台很大程度上就是为了能快速看到不同折中方案的具体效果。4.5 内存占用问题大矩阵要预处理早期我在跑高带宽、长积累的实验时直接把 6000 距离门 × 256 脉冲的复数矩阵全部读进内存同时保留脉压前后的副本内存还能接受。但如果把脉冲数提到 2048或者加上多通道多帧矩阵就开始膨胀了。优化思路有三个一是所有中间量改用complex64而不是默认的complex128信号处理精度基本不受影响二是能原地操作的用np.multiply的out参数避免反复复制三是中间结果只保存关键的 npz不要把每一帧的原始回波都堆在内存里。如果矩阵实在太大就分块处理一次只做 256 个脉冲而不是全生命周期都加载。5. 复盘与后续思考5.1 做完这套平台我改变了哪些工作习惯这项目做完之后我自己最大的改变是不再直接打开编辑器写一长串脚本而是先把“输入是什么、输出是什么、参数放哪里”想清楚。以前我改带宽、改脉宽要层层往下翻代码现在只要改 yaml程序会自动完成全套处理和画图跑完还能统一对比。这种配置驱动的模式看起来没什么技术含量但对实验类项目帮助巨大。另一个习惯是保留中间量。以前我只关心最终检测点现在每一级处理的结果都存盘。排查算法问题时哪个环节出问题一目了然。比如有一次脉压后出现奇怪的旁瓣结构我同时看了脉压输入波形和参考信号的频谱立刻发现是参考信号忘了加窗。如果没有中间量光看最终图可能又要猜半天。5.2 后面我想继续在这套基础上做的事后续我已经在规划几个扩展点一是加入更真实的杂波模型比如地杂波、气象杂波让 CFAR 验证更有说服力二是支持多目标跟踪接口把检测点迹输给最近邻关联或卡尔曼滤波器三是把硬件采集的数据导入平台替代纯仿真输入。还有一个很想做的方向是距离走动校正当目标在积累时间内跨多个距离门时用 Keystone 变换或逐脉冲重对齐补偿把高速目标的能量重新积累起来。这一路做下来最大的心得是雷达信号处理的概念公式都不难难的是把链路串起来让每个模块的假设条件都对齐。PLFM_RADAR 这套平台并不炫但它让我可以把注意力重新放到算法本身而不是反复折腾脚本参数传递和绘图像限。如果你也在做类似的验证系统建议先从最小闭环开始跑通一个目标、一张距离-多普勒图、一次 CFAR 检测再慢慢加复杂度这条路走得最稳。
阅读完成 · 觉得有帮助?
咨询建站