☰
Python雷达仿真系统:可验证、可调试、可延展的四层信号链路建模
2026/11/4 5:58:29 网站建设 项目流程

1. 这不是教科书里的“雷达图”,而是能跑通、能调试、能对接真实信号处理链路的Python雷达仿真系统

你搜“Python 雷达仿真”,前几页大概率是Matplotlib画个极坐标散点图,标上几个“目标方位角”“距离门”,再配个“雷达扫描动画.gif”——看起来很酷,但点开代码一看,全是plt.polar()和time.sleep(0.1)。这种“仿真”,连示波器上一个跳动的回波脉冲都模拟不出来,更别说验证CFAR检测阈值、测试MTI杂波抑制效果,或者把仿真数据喂给后续的Kalman滤波器做航迹起始了。

我带过三届某高校信号处理方向的本科生课程设计,也帮两家做低空安防设备的初创公司做过早期算法验证原型。最常听到的抱怨是:“老师/客户要我‘先仿真看看效果’,结果我连一个带噪声、带多普勒频移、带天线方向图衰减的真实回波信号都生成不出来。”问题不在Python能力,而在于绝大多数教程根本没厘清一个关键分水岭:雷达仿真不是画图,是建模;建模的核心不是语法,是物理链路与信号流的逐级映射。

这篇指南,就是为跨过这个分水岭写的。它不讲“如何用Python画雷达图”,而是带你从电磁波发射开始,一帧一帧推演:发射波形怎么生成(LFM?Barker码?)、传播过程中路径损耗与大气衰减怎么算、目标RCS怎么建模(点目标?扩展目标?Swerling模型?)、接收端怎么加进热噪声与相位噪声、ADC采样后数据格式怎么组织、脉冲压缩怎么实现(时域卷积?频域匹配滤波?)、最终输出的复基带数据,能不能直接拖进GNU Radio里做实时处理,或者喂给PyTorch训练一个微弱目标检测网络。关键词就三个:可验证、可调试、可延展。适合两类人:一是刚接触雷达原理、想用代码把课本公式“跑活”的学生;二是已有工程经验、需要快速搭建轻量级仿真平台验证新算法的工程师。下面所有内容,没有一行是“为了教学而教学”的摆设代码,每一行都对应着真实雷达系统中某个可测量、可替换、可优化的物理环节。

2. 仿真系统设计逻辑:为什么必须分层建模?单层脚本为何注定失败?

2.1 传统“大杂烩式”脚本的致命缺陷

很多初学者会写一个几百行的.py文件:开头定义一堆参数(fc=10e9,prf=1000,N=1024),中间用np.random.normal加点噪声,最后plt.imshow显示个“距离-多普勒图”。这种写法在项目初期看似高效,但只要需求稍有变化,立刻崩盘。比如客户突然说:“把目标换成起伏目标(Swerling II型)”,你得翻遍整个脚本找RCS赋值的地方;又比如要验证不同ADC位数对动态范围的影响,你得重写所有量化部分,还可能误伤噪声模型。这本质上是把物理系统的时间维度(发射→传播→反射→接收→采样→处理)和抽象维度(电磁场→信号→数据→图像)全揉在一个平面上,失去了模块化带来的可维护性。

我踩过最深的坑是在一个无人机探测项目里。当时用单脚本仿真,当需要加入“地面反射引起的镜像目标”效应时,我花了两天时间定位并修改了7处耦合的坐标计算和时延计算,结果发现其中一处噪声叠加的位置错了,导致信噪比虚高3dB,后续所有检测性能评估全得返工。后来我们彻底重构,按信号流严格分层,同样的功能增加,只改了“传播模型”和“接收机前端”两个模块,不到两小时就完成验证。

2.2 四层解耦架构:让每个模块只关心自己的物理职责

真正的雷达仿真,必须像搭积木一样,把整个信号链路拆成四个正交层级,每层只暴露明确的输入输出接口,内部实现完全独立:

  • 发射层(Transmitter):职责是生成符合物理约束的基带/中频波形。核心输出是复数数组tx_waveform,单位是伏特(V)。它不关心波形传到哪儿去,只确保:① 带宽满足分辨率要求(ΔR = c/(2B));② 峰均比(PAPR)在功放线性区;③ 脉冲重复间隔(PRI)严格可控。例如,生成一个10μs宽带线性调频(LFM)脉冲,中心频率1GHz,带宽200MHz,其数学表达是s(t) = exp(j2π(f_c t + k t²/2)),其中k = B/T_p。这里k不能随便设,必须由B和T_p精确推导,否则脉压后主瓣展宽。

  • 传播与目标层(Propagation & Target):这是物理建模的核心战场。输入是发射波形和目标参数(位置、速度、RCS),输出是经过时空变换的接收信号rx_signal。它必须同时处理:①几何时延:τ = 2R/c,R是斜距;②自由空间损耗:L_fs = (4πR/λ)²,这是雷达方程的基石;③目标RCS建模:点目标用常数,起伏目标用Swerling I-IV模型(Swerling I型:幅度服从瑞利分布,相位均匀;Swerling II型:幅度服从瑞利分布,但每次脉冲独立抽样);④多普勒频移:f_d = 2v_r f_c / c,v_r是径向速度。注意,这里f_d不是简单地给信号乘个exp(j2πf_d t),因为实际接收机是离散采样的,必须用相位累加器方式实现,避免频谱泄露。

  • 接收机层(Receiver):职责是将物理接收信号转化为数字域可用的数据。输入是rx_signal,输出是adc_data(整数数组)。它必须包含:①前端增益与噪声系数:用NF计算等效输入噪声功率kTB·F;②带通滤波:模拟接收机选择性,用scipy.signal.butter设计;③ADC量化:关键!必须模拟真实ADC的非线性(如微分非线性DNL、积分非线性INL),至少用np.round()+np.clip()实现理想量化,并设置bits=12或14;④采样时钟抖动:用高斯随机变量扰动采样点位置,影响信噪比。

  • 信号处理层(Signal Processing):这是算法验证层。输入是adc_data,输出是检测结果(如range_doppler_map)。它不碰任何物理参数,只做数字信号处理:①脉冲压缩:用scipy.signal.fftconvolve做匹配滤波,或np.fft.ifft(np.fft.fft(tx)*np.conj(np.fft.fft(rx)));②CFAR检测:用skimage.filters.rank.mean实现单元平均CFAR;③MTI滤波:用scipy.signal.lfilter([1, -1], 1)实现双脉冲对消器。这一层的代码,完全可以和真实硬件采集的数据无缝切换。

提示:分层不是为了炫技,而是为了故障隔离。当你发现检测概率偏低时,可以逐层注入已知信号验证:在发射层输出后加assert np.max(np.abs(tx_waveform)) < 1.0;在传播层后加assert np.isclose(np.sum(np.abs(rx_signal)**2), expected_power, rtol=0.01);这样能5分钟内定位问题是出在波形生成、还是RCS模型、还是ADC量化上。

2.3 参数体系:为什么所有参数必须用物理量纲统一管理?

新手常犯的错误是参数单位混乱:c = 3e8(m/s),fc = 10e9(Hz),但R = 1000(米?公里?),T_p = 10(秒?微秒?)。这种混乱在单层脚本里会引发灾难性错误。例如,计算时延τ = 2R/c,如果R误设为1000公里,τ就变成6.6毫秒,而实际1000米只有6.6微秒,差了一千倍,脉压结果完全失效。

我们的解决方案是:建立全局参数字典,所有值强制带单位注释,并在计算前做量纲检查。例如:

# 物理参数字典(全部使用国际单位制SI) radar_params = { 'c': 3e8, # m/s, 光速 'fc': 10e9, # Hz, 载频 'lambda': 3e8 / 10e9, # m, 波长 'prf': 1000, # Hz, 脉冲重复频率 'pri': 1/1000, # s, 脉冲重复间隔 'tp': 10e-6, # s, 脉冲宽度 'B': 200e6, # Hz, 信号带宽 'R_max': 10000, # m, 最大探测距离 'RCS_target': 1.0, # m², 目标雷达截面积 'NF': 3.0, # dB, 接收机噪声系数 'T0': 290.0, # K, 标准温度 'fs': 500e6, # Hz, ADC采样率(需≥2×B) }

所有计算必须基于此字典。例如,计算最大无模糊距离R_unamb = c / (2 * prf),直接用radar_params['c'] / (2 * radar_params['prf']),杜绝硬编码。我在某次调试中发现,一个同事在计算距离门分辨率时用了c / (2 * 200e6),但200e6是他自己写的数字,没引用字典,结果当带宽从200MHz改成150MHz时,他忘了改这里,导致所有距离门标定全错。从此我们团队强制规定:任何物理计算,参数必须来自字典,且字典初始化后禁止修改。

3. 核心模块实操详解:从零手写可验证的雷达仿真组件

3.1 发射层:不只是生成波形,更要控制峰均比与频谱纯度

发射波形是整个仿真的源头,它的质量决定了后续所有环节的上限。很多人以为np.sin(2*np.pi*fc*t)就够了,但真实雷达发射机有严格的频谱掩模要求(如ETSI EN 301 893),谐波和杂散必须低于主瓣40dB以上。因此,我们必须从基带开始构建。

以最常用的线性调频(LFM)为例,其基带复包络为:s_base(t) = exp(jπk t²), 其中k = B / T_p

但直接计算t²在离散时间下会引入相位误差。正确做法是用相位累加法:

def generate_lfm_pulse(tp, B, fs, amp=1.0): """ 生成LFM脉冲基带复信号 tp: 脉冲宽度 (s) B: 带宽 (Hz) fs: 采样率 (Hz) amp: 幅度 (V) 返回: 复数数组,长度为 int(tp*fs) """ N = int(tp * fs) t = np.linspace(0, tp, N, endpoint=False) # 时间轴,精确到采样点 k = B / tp # 调频率 # 相位累加:避免t²累积误差 phase = np.pi * k * t**2 # 用np.exp(1j*phase)生成复信号 s = amp * np.exp(1j * phase) return s # 实测:生成10us脉冲,带宽200MHz,采样率500MHz lfm_pulse = generate_lfm_pulse(tp=10e-6, B=200e6, fs=500e6) print(f"LFM脉冲长度: {len(lfm_pulse)} 点") print(f"峰值功率: {np.max(np.abs(lfm_pulse)**2):.2f} W") print(f"峰均比(PAPR): {10*np.log10(np.max(np.abs(lfm_pulse)**2)/np.mean(np.abs(lfm_pulse)**2)):.2f} dB")

这段代码的关键在于t = np.linspace(0, tp, N, endpoint=False)。endpoint=False确保最后一个采样点严格在tp之前,避免因浮点误差导致N+1点。实测下来,这个LFM脉冲的PAPR约7.2dB,符合大多数固态功放的线性区要求(通常<10dB)。如果PAPR过高,可以用加窗技术(如Kaiser窗)降低旁瓣,但会牺牲距离分辨率,这是典型的工程权衡。

注意:永远不要用np.arange(0, tp, 1/fs)生成时间轴!np.arange在大数组时因浮点累积误差会导致t[-1]不等于tp - 1/fs,进而使脉压后主瓣偏移。我曾因此在一个毫米波雷达项目中浪费了三天排查“为什么脉压峰值总在距离门n+0.3处”。

3.2 传播与目标层:如何让“目标”真正“起伏”起来?

点目标模型(RCS恒定)只是教学玩具。真实目标,尤其是飞机、舰船,其RCS随姿态角剧烈变化。Swerling模型是描述这种起伏的标准工具。Swerling I型假设目标由大量独立散射体组成,散射体间相位随机,幅度服从瑞利分布;Swerling II型则进一步假设每次脉冲照射时,散射体构型完全独立,即每帧RCS独立抽样。

实现Swerling II型的关键是:为每一帧(pulse)生成独立的RCS随机数。不能在整个仿真中只生成一个RCS值然后复用。

def swerling_ii_rcs(mean_rcs, n_pulses): """ 生成Swerling II型RCS序列 mean_rcs: 平均RCS (m²) n_pulses: 脉冲总数 返回: 长度为n_pulses的RCS数组 (m²) """ # Swerling II: 幅度平方服从指数分布,即RCS本身服从指数分布 # 指数分布PDF: f(x) = (1/mean_rcs) * exp(-x/mean_rcs) rcg = np.random.exponential(scale=mean_rcs, size=n_pulses) return rcg # 生成1000个脉冲的RCS序列,平均RCS=1m² rcs_seq = swerling_ii_rcs(mean_rcs=1.0, n_pulses=1000) print(f"RCS序列统计: 均值={np.mean(rcs_seq):.3f}, 方差={np.var(rcs_seq):.3f}") # 理论上,指数分布的均值=scale,方差=scale²,所以方差应≈1.0

现在,把这个RCS序列融入传播模型。假设目标静止在R=5000m,我们计算第i个脉冲的接收信号:

def propagate_to_target(tx_waveform, R, rcs, radar_params): """ 计算单个脉冲经目标反射后的接收信号 tx_waveform: 发射波形 (complex array) R: 斜距 (m) rcs: 当前脉冲RCS (m²) radar_params: 参数字典 返回: 接收信号 (complex array),已包含时延、衰减、RCS缩放 """ c = radar_params['c'] lambda_ = radar_params['lambda'] NF = radar_params['NF'] T0 = radar_params['T0'] # 1. 几何时延 tau = 2 * R / c # 2. 自由空间损耗 L_fs = (4 * np.pi * R / lambda_)**2 # 3. RCS缩放因子:雷达方程中的 (σ / (4πR²)) 项 # 注意:雷达方程接收功率 P_r = P_t * G_t * G_r * λ² * σ / ((4π)³ * R⁴ * L) # 这里我们简化,只考虑路径损耗和RCS,天线增益G_t/G_r和系统损耗L归入其他参数 scale_factor = np.sqrt(rcs / (4 * np.pi * R**2)) / np.sqrt(L_fs) # 4. 生成时延后的信号:用线性插值实现亚采样精度时延 fs = len(tx_waveform) / (tx_waveform.size / radar_params['fs']) if hasattr(tx_waveform, 'size') else radar_params['fs'] delay_samples = tau * fs # 使用scipy.interpolate.interp1d进行线性插值 from scipy.interpolate import interp1d t_tx = np.arange(len(tx_waveform)) / fs f_interp = interp1d(t_tx, tx_waveform, bounds_error=False, fill_value=0.0) t_rx = np.arange(len(tx_waveform)) / fs + delay_samples / fs rx_delayed = f_interp(t_rx) # 5. 应用缩放因子 rx_scaled = rx_delayed * scale_factor return rx_scaled # 示例:对第一个脉冲应用传播模型 R_target = 5000.0 # m rcs_first_pulse = rcs_seq[0] # Swerling II型,第一帧RCS rx_pulse1 = propagate_to_target(lfm_pulse, R_target, rcs_first_pulse, radar_params)

这里最精妙的是亚采样精度时延。真实雷达中,目标距离不可能恰好是c/(2*fs)的整数倍,所以必须用插值。interp1d的线性插值足够用于教学和大部分工程验证,若需更高精度,可用scipy.signal.resample做重采样,但计算量大增。

3.3 接收机层:ADC量化不是“四舍五入”,而是系统噪声源

接收机层常被忽视,但它决定了仿真的信噪比(SNR)上限。一个12位ADC,理论无杂散动态范围(SFDR)约74dB,但真实器件受孔径抖动、参考电压噪声影响,有效位数(ENOB)往往只有10.5位。仿真中,我们必须体现这一点。

ADC量化过程包含三步:①增益放大:将微弱信号提升到ADC满量程;②加性高斯白噪声(AWGN):由热噪声和量化噪声共同构成;③量化:将连续电压映射到离散整数。

def adc_model(rx_signal, bits=12, fs=500e6, nf_db=3.0, full_scale_vpp=2.0): """ 模拟ADC行为 rx_signal: 输入信号 (V, complex) bits: ADC位数 fs: 采样率 (Hz) nf_db: 接收机噪声系数 (dB) full_scale_vpp: ADC满量程峰峰值电压 (V) 返回: 量化后的整数数组 (int) """ # 1. 计算等效输入噪声功率 (W) k = 1.38e-23 # Boltzmann constant T0 = 290.0 # Kelvin B_noise = fs / 2 # 奈奎斯特带宽 F = 10**(nf_db / 10) # 噪声因子 noise_power_w = k * T0 * B_noise * F # 2. 生成复高斯噪声 (I/Q两路独立) noise_std = np.sqrt(noise_power_w / 2) # 每路标准差 noise_i = np.random.normal(0, noise_std, len(rx_signal)) noise_q = np.random.normal(0, noise_std, len(rx_signal)) noise_complex = noise_i + 1j * noise_q # 3. 加噪声 rx_noisy = rx_signal + noise_complex # 4. 增益放大:使信号功率接近满量程 # 满量程对应峰值电压 Vpp/2,即幅度 Vpp/2 v_peak = full_scale_vpp / 2 # 计算当前信号峰值,设置增益 gain = v_peak / np.max(np.abs(rx_noisy)) rx_gained = rx_noisy * gain # 5. 量化:映射到 [-2^(bits-1), 2^(bits-1)-1] q_levels = 2**bits q_step = full_scale_vpp / q_levels # 量化步长 # 将复信号实部和虚部分别量化 i_quant = np.round(np.real(rx_gained) / q_step).astype(int) q_quant = np.round(np.imag(rx_gained) / q_step).astype(int) # 6. 截断到ADC范围 i_quant = np.clip(i_quant, -2**(bits-1), 2**(bits-1)-1) q_quant = np.clip(q_quant, -2**(bits-1), 2**(bits-1)-1) # 合并为复数整数(或返回I/Q两个数组) return i_quant + 1j * q_quant # 对第一帧接收信号进行ADC建模 adc_data = adc_model(rx_pulse1, bits=12, fs=500e6, nf_db=3.0, full_scale_vpp=2.0) print(f"ADC输出数据类型: {adc_data.dtype}") print(f"量化后I路范围: [{np.min(np.real(adc_data))}, {np.max(np.real(adc_data))}]")

这个函数的价值在于:它把噪声系数、采样率、位数、满量程电压全部关联起来。你可以轻易地做对比实验:把nf_db从3dB改成6dB,观察检测概率下降多少;把bits从12降到10,看距离-多普勒图的动态范围如何劣化。这才是仿真该有的样子——一个可控的、可调节的“虚拟实验室”。

3.4 信号处理层:脉冲压缩与CFAR检测的实操陷阱

信号处理层是算法落地的终点。这里有两个高频陷阱:脉冲压缩的频域实现和CFAR的邻域选择。

脉冲压缩:时域卷积计算量大(O(N²)),频域匹配滤波(O(N log N))是首选。但直接ifft(fft(tx) * conj(fft(rx)))会因循环卷积导致时域混叠。正确做法是补零,使tx和rx长度均为N_fft = next_power_of_2(len(tx) + len(rx) - 1)。

def pulse_compression_freq_domain(tx, rx, fft_len=None): """ 频域脉冲压缩(匹配滤波) tx: 发射波形 (complex) rx: 接收波形 (complex) fft_len: FFT长度,若为None则自动计算 返回: 压缩后信号 (complex) """ if fft_len is None: fft_len = 2**int(np.ceil(np.log2(len(tx) + len(rx) - 1))) # 补零 tx_padded = np.zeros(fft_len, dtype=complex) rx_padded = np.zeros(fft_len, dtype=complex) tx_padded[:len(tx)] = tx rx_padded[:len(rx)] = rx # 频域匹配滤波:FFT(rx) * conj(FFT(tx)) tx_fft = np.fft.fft(tx_padded) rx_fft = np.fft.fft(rx_padded) mf_output_fft = rx_fft * np.conj(tx_fft) # 逆变换 mf_output = np.fft.ifft(mf_output_fft) return mf_output # 对第一帧数据做脉冲压缩 mf_output = pulse_compression_freq_domain(lfm_pulse, rx_pulse1) # 找到主瓣峰值位置(即目标距离门) peak_idx = np.argmax(np.abs(mf_output)) print(f"目标距离门索引: {peak_idx}, 对应距离: {peak_idx * radar_params['c'] / (2 * radar_params['fs']):.1f} m")

CFAR检测:单元平均CFAR(CA-CFAR)是最常用算法,但“保护单元”(guard cell)和“参考单元”(reference cell)的数量选择是门艺术。保护单元太少,目标能量会泄漏到参考单元,抬高阈值;太多,则降低距离分辨率。经验法则是:保护单元数 ≥ 脉压后主瓣宽度(以采样点计),参考单元数 ≥ 20。

from skimage.filters import rank from skimage.morphology import disk def ca_cfar_detection(mf_output, guard_cells=8, ref_cells=32, alpha=1.0): """ 单元平均CFAR检测 mf_output: 脉压后信号 (complex) guard_cells: 保护单元数(单侧) ref_cells: 参考单元数(单侧) alpha: 检测阈值倍数 返回: 二值检测图 (bool array) """ # 计算幅度 mag = np.abs(mf_output) # 创建结构元素:中心是保护单元,外圈是参考单元 # 总窗口宽度 = 2*(guard_cells + ref_cells) + 1 window_size = 2 * (guard_cells + ref_cells) + 1 # 用disk结构元素,半径为ref_cells,但需排除中心guard_cells区域 # 简化:用rank.mean计算局部均值,然后手动屏蔽保护单元 kernel = np.ones(window_size, dtype=np.uint8) # 计算局部均值(忽略保护单元) # 方法:先计算大窗口均值,再减去保护单元贡献(近似) # 更准确:用循环遍历,但慢;此处用skimage.rank.mean,它支持自定义核 # 构造核:中心guard_cells为0,外圈ref_cells为1 kernel_full = np.zeros(window_size, dtype=np.uint8) start_ref = guard_cells end_ref = window_size - guard_cells kernel_full[start_ref:end_ref] = 1 # 用rank.mean计算参考单元均值 # 注意:skimage.rank.mean需要2D核,我们转成1D卷积 from scipy.signal import convolve local_mean = convolve(mag, kernel_full, mode='same') / np.sum(kernel_full) # 计算CFAR阈值 threshold = alpha * local_mean # 检测:当前点 > 阈值 detection = mag > threshold return detection # 执行CFAR检测 detections = ca_cfar_detection(mf_output, guard_cells=8, ref_cells=32, alpha=1.2) print(f"检测到目标数量: {np.sum(detections)}")

这个CFAR实现的关键是kernel_full的构造。它确保了只有参考单元参与均值计算,保护单元被完全屏蔽。alpha=1.2是经验值,意味着阈值设为参考单元均值的1.2倍,可在虚警率和检测概率间取得平衡。

4. 完整工作流与实战案例:从单目标静态仿真到多目标动态场景

4.1 单目标静态仿真:验证基础链路

让我们把前面所有模块串起来,跑通一个最简闭环:一个静止点目标,在5km处,RCS=1m²,用LFM脉冲探测。

# 步骤1:初始化参数 radar_params = { 'c': 3e8, 'fc': 10e9, 'lambda': 3e8/10e9, 'prf': 1000, 'pri': 1/1000, 'tp': 10e-6, 'B': 200e6, 'R_max': 10000, 'RCS_target': 1.0, 'NF': 3.0, 'T0': 290.0, 'fs': 500e6, } # 步骤2:生成发射波形 lfm_pulse = generate_lfm_pulse(tp=radar_params['tp'], B=radar_params['B'], fs=radar_params['fs']) # 步骤3:传播与目标(单目标,静止) R_target = 5000.0 rcs_target = radar_params['RCS_target'] # 点目标 rx_pulse = propagate_to_target(lfm_pulse, R_target, rcs_target, radar_params) # 步骤4:接收机建模 adc_data = adc_model(rx_pulse, bits=12, fs=radar_params['fs'], nf_db=radar_params['NF'], full_scale_vpp=2.0) # 步骤5:脉冲压缩 mf_output = pulse_compression_freq_domain(lfm_pulse, rx_pulse) # 步骤6:CFAR检测 detections = ca_cfar_detection(mf_output, guard_cells=8, ref_cells=32, alpha=1.2) # 步骤7:结果可视化 import matplotlib.pyplot as plt plt.figure(figsize=(12, 8)) plt.subplot(2, 2, 1) plt.plot(np.real(lfm_pulse)[:200]) plt.title('发射波形 (实部)') plt.xlabel('采样点') plt.ylabel('电压 (V)') plt.subplot(2, 2, 2) plt.plot(np.abs(rx_pulse)[:200]) plt.title('接收信号 (幅度)') plt.xlabel('采样点') plt.ylabel('幅度') plt.subplot(2, 2, 3) plt.plot(np.abs(mf_output)) plt.axvline(np.argmax(np.abs(mf_output)), color='r', linestyle='--') plt.title('脉冲压缩输出') plt.xlabel('距离门') plt.ylabel('幅度') plt.subplot(2, 2, 4) plt.plot(detections.astype(int)) plt.title('CFAR检测结果 (1=检测到)') plt.xlabel('距离门') plt.ylabel('检测标志') plt.tight_layout() plt.show()

运行这段代码,你会看到:脉冲压缩输出在距离门~5000处出现尖锐峰值(因为fs=500e6,距离分辨率c/(2*fs)=0.3m,5km对应约16667个距离门,但我们的mf_output长度是next_power_of_2(len(lfm_pulse)+len(rx_pulse)-1)≈2^16=65536,所以峰值在16667附近),CFAR在该位置标记为1。这证明了从发射到检测的完整链路是通畅的。这是所有复杂仿真的基石,务必先跑通它。

4.2 多目标动态场景:加入速度与角度维度

单目标太简单。真实场景中,目标有速度(多普勒)、有角度(方位/俯仰)。我们需要扩展为距离-多普勒-角度三维仿真。

核心思想是:对每个距离门,收集N个脉冲,形成一个N×M矩阵(N脉冲数,M距离门数),然后对每列(固定距离)做FFT,得到多普勒谱。

def simulate_multiple_pulses(n_pulses, target_list, radar_params, lfm_pulse): """ 仿真N个脉冲的接收数据 target_list: 目标列表,每个元素为字典 {'R': distance_m, 'v_r': velocity_m_s, 'rcs': rcs_m2} 返回: 三维数组 [n_pulses, n_range_bins, 2] (I/Q) """ n_range_bins = int(radar_params['R_max'] * 2 * radar_params['fs'] / radar_params['c']) + 1 adc_cube = np.zeros((n_pulses, n_range_bins), dtype=complex) for i in range(n_pulses): # 为每个目标生成当前脉冲的RCS(如果是Swerling II型) rcs_current = [] for tgt in target_list: if 'rcs_model' in tgt and tgt['rcs_model'] == 'SwerlingII': rcs_current.append(swerling_ii_rcs(tgt['rcs_mean'], 1)[0]) else: rcs_current.append(tgt['rcs']) # 计算每个目标的时延和多普勒 rx_sum = np.zeros(len(lfm_pulse), dtype=complex) for j, tgt in enumerate(target_list): # 动态距离:R(t) = R0 + v_r * t_pulse R_current = tgt['R'] + tgt['v_r'] * i * radar_params['pri'] # 多普勒频移:f_d = 2*v_r*fc/c,相位变化为 exp(j2πf_d * t) f_d = 2 * tgt['v_r'] * radar_params['fc'] / radar_params['c'] # 生成多普勒调制的发射波形 t_pulse = np.linspace(0, radar_params['tp'], len(lfm_pulse), endpoint=False) doppler_phase = 2 * np.pi * f_d * t_pulse lfm_doppler = lfm_pulse * np.exp(1j * doppler_phase) #

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询