月壤结构探测仪LRPR 地形校正和数据分析
继上一篇LRPR基础教程后,本文将深入探讨地形校正与高级信号分析技术。随着嫦娥五号、六号任务数据的不断积累,对这些数据进行高级处理和分析变得愈发重要。地形校正能有效消除地表起伏对雷达数据的影响,而希尔伯特变换、相位分析等高级技术则能从原始数据中提取更多有价值的地质信息。本教程将系统讲解这些技术的原理和实现方法,并提供实际应用案例。零时点(zero time)是指雷达波开始传播的时刻。在理想情况下,
文章目录
前言
继上一篇LRPR基础教程后,本文将深入探讨地形校正与高级信号分析技术。随着嫦娥五号、六号任务数据的不断积累,对这些数据进行高级处理和分析变得愈发重要。地形校正能有效消除地表起伏对雷达数据的影响,而希尔伯特变换、相位分析等高级技术则能从原始数据中提取更多有价值的地质信息。本教程将系统讲解这些技术的原理和实现方法,并提供实际应用案例。
1. 地形校正原理与实现
1.1 地形校正的必要性
月球地表并非完全平坦,存在各种地形起伏。这些起伏会导致雷达反射界面在时间剖面上呈现"起伏状",干扰对地下结构的判断。地形校正的目的是消除地表地形起伏的影响,使地下反射界面在处理后的剖面上能更加接近其真实形态。
地形校正的主要优势有:
- 消除地表起伏引起的"假"地层结构
- 提高深层反射事件的连续性
- 便于准确估计地下界面的深度
- 改善后续处理(如迁移成像)的效果
1.2 地形校正算法
地形校正的核心思想是将各道数据沿时间轴(或深度轴)进行移动,使地表反射事件对齐到同一水平线上。主要步骤包括:
- 检测每道数据中的地表反射位置(底部位置)
- 确定参考水平面(通常为平均地表高度)
- 计算每道需要移动的时间/样本量
- 执行数据移动和填充操作
1.3 底部位置检测
在实施地形校正前,首先需要准确检测每道数据中的地表反射(底部)位置。常用方法有:
- 基于阈值的检测:设定振幅阈值,寻找超过该阈值的最深位置
- 基于能量的检测:通过计算信号能量分布,确定能量突变点
- 基于相关性的检测:利用道间相关性,确保检测结果的连续性
以下是基于阈值的底部位置检测算法:
def calculate_bottom_positions_optimized(data, threshold=0.00001):
"""
优化后的计算每道底部位置的函数。
参数:
- data (ndarray): 地形校正前的数据,形状为 (num_traces, num_samples)
- threshold (float): 判断底部的阈值
返回:
- bottom_positions (ndarray): 每道的底部位置索引,形状为 (num_traces,)
"""
# 创建掩码,标记数据中大于阈值的值
data = np.abs(data)
mask = data > threshold # 形状: (num_traces, num_samples)
# 生成每个样本点的索引
sample_indices = np.arange(data.shape[1])
# 使用广播将样本索引与掩码结合
# 对于每个Trace,未满足条件的位置设为-1
masked_indices = np.where(mask, sample_indices, -1)
# 找到每个Trace中最后一个满足条件的位置
last_true_indices = masked_indices.max(axis=1) # 形状: (num_traces,)
# 计算底部位置
bottom_positions = last_true_indices # 因为索引从0开始
# 如果没有任何值大于阈值,则底部位置设为0
bottom_positions[last_true_indices == -1] = 0
return bottom_positions
1.4 校正实现代码
以下是地形校正的核心实现代码:
def terrain_correct(data, bottom_positions):
"""
进行地形校正,使每道数据的底部位置对齐。
参数:
- data (ndarray): 原始数据,形状为 (num_traces, num_samples)
- bottom_positions (ndarray): 每道的底部位置索引,形状为 (num_traces,)
返回:
- corrected_data (ndarray): 校正后的数据,形状与输入相同
"""
num_traces, num_samples = data.shape
corrected_data = np.full_like(data, fill_value=0)
# 确定参考位置(平均底部位置)
reference_position = np.mean(bottom_positions)
reference_position = int(reference_position)
# 计算每道需要移动的采样点数
shifts = reference_position - bottom_positions
# 应用移动
for i in range(num_traces):
shift = shifts[i]
if shift > 0: # 向下移动
# 使用roll函数移动数据
rolled = np.roll(data[i], shift)
# 将移入的部分填充为0
rolled[:shift] = 0
corrected_data[i] = rolled
elif shift < 0: # 向上移动
# 使用roll函数移动数据
rolled = np.roll(data[i], shift)
# 将移入的部分填充为0
rolled[shift:] = 0
corrected_data[i] = rolled
else: # 不需要移动
corrected_data[i] = data[i]
return corrected_data
在实际应用中,地形校正可能需要针对特定地形特点进行参数调整。此外,为了改善校正效果,有时会将预设的移动量应用于数据,而不是自动检测底部位置。这在以下代码中有所体现:
def terrain_correct_with_manual_shifts(data):
num_traces, num_samples = data.shape
corrected_data = np.full_like(data, fill_value=0)
# 预设的移动量(通常基于先验知识或前期分析结果)
shifts = [...]
for i in range(num_traces):
shift = shifts[i] if i < len(shifts) else 0
if shift > 0:
rolled = np.roll(data[i], -shift)
rolled[-shift:] = 0
corrected_data[i] = rolled
else:
corrected_data[i] = data[i]
return corrected_data
在处理完成后,可以使用可视化函数验证校正效果:
def visualize_data(data, bottom_positions, cmap='seismic', figsize=(15, 8)):
"""
可视化地形校正后的数据,并在图像上绘制底部位置。
参数:
- data (ndarray): 地形校正后的数据,形状为 (num_traces, num_samples)
- bottom_positions (ndarray): 每道的底部位置索引,形状为 (num_traces,)
- cmap (str): 颜色映射
- figsize (tuple): 图像尺寸
"""
data_transposed = data.T # 转置为 (num_samples, num_traces)
plt.figure(figsize=figsize)
# 使用imshow绘制数据
img = plt.imshow(data_transposed, aspect='auto', cmap=cmap)
plt.colorbar(img, label='振幅') # 根据数据调整标签
# 绘制底部位置
traces = np.arange(data.shape[0])
plt.plot(traces, bottom_positions, color='yellow', linewidth=1, label='底部')
# 添加标签和标题
plt.xlabel('道号 (Traces)')
plt.ylabel('样本索引 (Samples)')
plt.title('地形校正后的数据及底部位置')
plt.legend()
plt.show()
2. 零时校正技术
2.1 零时点定义与意义
零时点(zero time)是指雷达波开始传播的时刻。在理想情况下,雷达数据的零时点应该对应于电磁波从发射天线发出的那一刻。然而,由于设备延迟、系统抖动、触发时间等因素,实际数据中的零时点可能存在偏移,导致深度估计误差。
零时校正的目标是确定真实的零时点,并将数据进行适当移动,使波形起点对应于电磁波真实的发射时刻。这对于准确估计地下界面深度至关重要。
2.2 基于能量的零时点检测
最常用的零时点检测方法是基于信号能量或振幅的突变点。通常,在零时点附近,信号会出现明显的能量变化,表示电磁波开始传播。以下是一种基于能量的零时点检测方法:
def detect_zero_time_by_energy(data, window_size=20, threshold_factor=0.3):
"""
基于能量变化检测零时点
参数:
- data: 输入数据,形状为(traces, samples)
- window_size: 计算能量的窗口大小
- threshold_factor: 能量阈值因子
返回:
- zero_times: 每道的零时点位置
"""
num_traces, num_samples = data.shape
zero_times = np.zeros(num_traces, dtype=int)
for i in range(num_traces):
trace = data[i, :]
# 计算移动窗口能量
energy = np.zeros(num_samples - window_size)
for j in range(len(energy)):
energy[j] = np.sum(trace[j:j+window_size]**2)
# 找到能量超过阈值的第一个点
max_energy = np.max(energy)
threshold = max_energy * threshold_factor
for j in range(len(energy)):
if energy[j] > threshold:
zero_times[i] = j
break
return zero_times
2.3 希尔伯特变换辅助零时校正
希尔伯特变换为零时点检测提供了更强大的工具,因为它能够转换为信号的包络,更容易检测信号的起始点。以下是基于希尔伯特变换的零时校正方法:
def zero_time_correction_with_hilbert(data, startPoint=None, endPoint=None, window=100):
"""
使用希尔伯特变换进行零时校正
参数:
- data: 输入数据,形状为(traces, samples)
- startPoint: 分析的起始点
- endPoint: 分析的终止点
- window: 寻找谷点的窗口大小
返回:
- corrected_data: 校正后的数据
- valley_time: 检测到的谷点位置
"""
# 设置分析范围
if startPoint is None:
startPoint = 0
if endPoint is None:
endPoint = data.shape[1]
data_slice = data[:, startPoint:endPoint]
# 计算希尔伯特变换和包络
analytic_signal = hilbert(data_slice, axis=1)
envelope = np.abs(analytic_signal)
# 计算平均包络
mean_envelope = np.mean(envelope, axis=0)
# 找到最大能量点
max_energy_time = np.argmax(mean_envelope)
# 在最大能量点之前寻找谷点
valley_time = find_valley_before_peak(mean_envelope, max_energy_time, window)
# 应用校正
corrected_data = np.zeros_like(data)
for i in range(data.shape[0]):
# 执行数据移位
rolled_data = np.roll(data[i], -valley_time)
# 对移位后末尾的无效数据置零
rolled_data[-valley_time:] = 0
corrected_data[i] = rolled_data
return corrected_data, valley_time
def find_valley_before_peak(signal, peak_idx, window=100):
"""
在峰值前寻找谷点
参数:
- signal: 信号数组
- peak_idx: 峰值位置
- window: 搜索窗口大小
返回:
- valley_idx: 谷点位置
"""
start_idx = max(0, peak_idx - window)
search_region = signal[start_idx:peak_idx]
if len(search_region) == 0:
return start_idx
valley_idx = start_idx + np.argmin(search_region)
return valley_idx
2.4 逐道零时校正算法
在某些应用中,每道数据的零时点可能不同,需要对每道单独进行校正。以下是逐道零时校正的算法:
def zero_time_calibration_per_trace(data):
"""
对每条迹线单独进行零时校正。
参数:
data (numpy.ndarray): 输入数据,形状为 (traces, samples)
返回:
corrected_data (numpy.ndarray): 校正后的数据,形状与输入相同
zero_time_indices (list): 每条迹线的零时刻位置索引
"""
traces, samples = data.shape
corrected_data = np.zeros_like(data)
zero_time_indices = []
center_index = samples // 2
for trace_idx in range(traces):
# 使用希尔伯特变换计算包络
envelope = np.abs(hilbert(data[trace_idx, :]))
# 找到包络的最大值位置作为零时点
zero_time_index = np.argmax(envelope)
zero_time_indices.append(zero_time_index)
# 计算需要移动的点数,使零时点移动到中心位置
shift = center_index - zero_time_index
# 应用移动
if shift > 0:
corrected_data[trace_idx, shift:] = data[trace_idx, :samples - shift]
corrected_data[trace_idx, :shift] = 0
elif shift < 0:
corrected_data[trace_idx, :shift] = 0
corrected_data[trace_idx, shift:] = data[trace_idx, -shift:]
else:
corrected_data[trace_idx, :] = data[trace_idx, :]
return corrected_data, zero_time_indices
3. 希尔伯特变换与瞬时属性
3.1 希尔伯特变换基本原理
希尔伯特变换是信号处理中的重要工具,它在时域中对输入信号进行90度相移。希尔伯特变换的数学定义为:
H [ x ( t ) ] = 1 π ∫ − ∞ ∞ x ( τ ) t − τ d τ H[x(t)] = \frac{1}{\pi} \int_{-\infty}^{\infty} \frac{x(\tau)}{t - \tau} d\tau H[x(t)]=π1∫−∞∞t−τx(τ)dτ
在离散信号处理中,通常通过频域计算实现希尔伯特变换:
- 对信号进行傅里叶变换
- 将负频率分量乘以-j,正频率分量乘以j
- 进行逆傅里叶变换得到变换结果
3.2 解析信号与瞬时属性
希尔伯特变换最常用的应用是构建解析信号。解析信号是一个复数信号,由原始信号作为实部,希尔伯特变换结果作为虚部:
z ( t ) = x ( t ) + j ⋅ H [ x ( t ) ] z(t) = x(t) + j \cdot H[x(t)] z(t)=x(t)+j⋅H[x(t)]
解析信号有一个重要特性:它的频谱在负频率区间为零,这使得我们可以方便地计算信号的瞬时属性。
3.3 瞬时振幅计算
瞬时振幅(也称为信号包络)是解析信号模的计算结果:
A ( t ) = ∣ z ( t ) ∣ = x ( t ) 2 + H [ x ( t ) ] 2 A(t) = |z(t)| = \sqrt{x(t)^2 + H[x(t)]^2} A(t)=∣z(t)∣=x(t)2+H[x(t)]2
以下是使用SciPy库计算瞬时振幅的代码:
def calculate_instantaneous_amplitude(data):
"""
计算信号的瞬时振幅(包络)
参数:
- data: 输入数据,可以是一维数组或二维数组
返回:
- instantaneous_amplitude: 瞬时振幅,形状与输入相同
"""
if data.ndim == 1:
# 一维数据
analytic_signal = hilbert(data)
instantaneous_amplitude = np.abs(analytic_signal)
else:
# 二维数据,对每一道分别处理
instantaneous_amplitude = np.zeros_like(data)
for i in range(data.shape[0]):
analytic_signal = hilbert(data[i])
instantaneous_amplitude[i] = np.abs(analytic_signal)
return instantaneous_amplitude
3.4 瞬时相位计算
瞬时相位是解析信号相角的计算结果:
ϕ ( t ) = arctan ( H [ x ( t ) ] x ( t ) ) \phi(t) = \arctan\left(\frac{H[x(t)]}{x(t)}\right) ϕ(t)=arctan(x(t)H[x(t)])
以下是计算瞬时相位的代码:
def calculate_instantaneous_phase(data):
"""
计算信号的瞬时相位
参数:
- data: 输入数据,可以是一维数组或二维数组
返回:
- instantaneous_phase: 瞬时相位(弧度),形状与输入相同
"""
if data.ndim == 1:
# 一维数据
analytic_signal = hilbert(data)
instantaneous_phase = np.angle(analytic_signal)
else:
# 二维数据,对每一道分别处理
instantaneous_phase = np.zeros_like(data)
for i in range(data.shape[0]):
analytic_signal = hilbert(data[i])
instantaneous_phase[i] = np.angle(analytic_signal)
return instantaneous_phase
3.5 瞬时频率计算
瞬时频率是瞬时相位对时间的导数:
f ( t ) = 1 2 π d ϕ ( t ) d t f(t) = \frac{1}{2\pi} \frac{d\phi(t)}{dt} f(t)=2π1dtdϕ(t)
在离散信号中,通常使用相位差分来近似导数:
def calculate_instantaneous_frequency(data, dt):
"""
计算信号的瞬时频率
参数:
- data: 输入数据,可以是一维数组或二维数组
- dt: 采样时间间隔(秒)
返回:
- instantaneous_frequency: 瞬时频率(Hz),形状与输入相同
"""
if data.ndim == 1:
# 一维数据
analytic_signal = hilbert(data)
instantaneous_phase = np.angle(analytic_signal)
instantaneous_frequency = np.zeros_like(instantaneous_phase)
# 计算相位差分
phase_diff = np.diff(instantaneous_phase)
# 处理相位跳变(超过π的部分通过加减2π调整)
phase_diff = np.where(phase_diff > np.pi, phase_diff - 2*np.pi, phase_diff)
phase_diff = np.where(phase_diff < -np.pi, phase_diff + 2*np.pi, phase_diff)
# 转换为频率
instantaneous_frequency[1:] = phase_diff / (2*np.pi*dt)
instantaneous_frequency[0] = instantaneous_frequency[1] # 边界处理
else:
# 二维数据,对每一道分别处理
instantaneous_frequency = np.zeros_like(data)
for i in range(data.shape[0]):
analytic_signal = hilbert(data[i])
instantaneous_phase = np.angle(analytic_signal)
# 计算相位差分
phase_diff = np.diff(instantaneous_phase)
# 处理相位跳变
phase_diff = np.where(phase_diff > np.pi, phase_diff - 2*np.pi, phase_diff)
phase_diff = np.where(phase_diff < -np.pi, phase_diff + 2*np.pi, phase_diff)
# 转换为频率
instantaneous_frequency[i, 1:] = phase_diff / (2*np.pi*dt)
instantaneous_frequency[i, 0] = instantaneous_frequency[i, 1] # 边界处理
return instantaneous_frequency
4. 相位分析与瞬时频率计算
4.1 相位解缠绕技术
在计算瞬时相位时,得到的结果通常被限制在[-π, π]范围内,导致相位跳变。相位解缠绕是指移除这些2π跳变,使相位曲线连续的过程。以下是相位解缠绕的实现:
def unwrap_phase(phase):
"""
对相位进行解缠绕处理
参数:
- phase: 输入相位数据(弧度),可以是一维或二维数组
返回:
- unwrapped_phase: 解缠绕后的相位
"""
if phase.ndim == 1:
# 一维数据
unwrapped_phase = np.unwrap(phase)
else:
# 二维数据,对每一道分别处理
unwrapped_phase = np.zeros_like(phase)
for i in range(phase.shape[0]):
unwrapped_phase[i] = np.unwrap(phase[i])
return unwrapped_phase
4.2 相位属性的地质意义
相位信息在雷达数据解释中具有重要意义:
- 瞬时相位:与地层边界和连续性有关,有助于识别细微的地层变化
- 相位连续性:反映地层的连续性和断裂情况
- 相位反转:通常表示介电常数反转,有助于识别地下介质变化
在月球环境中,相位反转可能指示月壤和岩石界面,或不同类型月壤之间的过渡。
4.3 频率属性分析与可视化
频率属性分析是通过研究信号的频率变化来提取地质信息的方法。以下代码展示了如何分析并可视化瞬时频率属性:
def analyze_frequency_attributes(data, dt):
"""
分析数据的频率属性并可视化
参数:
- data: 输入数据,形状为(traces, samples)
- dt: 采样时间间隔(秒)
"""
# 计算瞬时频率
instantaneous_frequency = calculate_instantaneous_frequency(data, dt)
# 计算频率统计特征
mean_freq = np.mean(instantaneous_frequency, axis=1)
std_freq = np.std(instantaneous_frequency, axis=1)
# 可视化
plt.figure(figsize=(15, 10))
# 瞬时频率图
plt.subplot(2, 1, 1)
plt.imshow(instantaneous_frequency.T, aspect='auto', cmap='viridis')
plt.colorbar(label='Frequency (Hz)')
plt.title('Instantaneous Frequency')
plt.xlabel('Trace Number')
plt.ylabel('Sample Number')
# 频率统计图
plt.subplot(2, 1, 2)
plt.plot(mean_freq, label='Mean Frequency')
plt.fill_between(np.arange(len(mean_freq)),
mean_freq - std_freq,
mean_freq + std_freq,
alpha=0.3, label='±1 Std Dev')
plt.title('Frequency Statistics per Trace')
plt.xlabel('Trace Number')
plt.ylabel('Frequency (Hz)')
plt.grid(True)
plt.legend()
plt.tight_layout()
plt.show()
4.4 基于窗口的频谱分析
窗口频谱分析可以揭示信号随时间变化的频率特性。短时傅里叶变换(STFT)是一种常用的窗口频谱分析方法:
def calculate_stft(data, dt, nperseg=64, noverlap=48):
"""
计算短时傅里叶变换
参数:
- data: 输入数据,一维数组
- dt: 采样时间间隔(秒)
- nperseg: 每个窗口的长度
- noverlap: 窗口重叠的点数
返回:
- f: 频率数组
- t: 时间数组
- Zxx_db: STFT结果(dB)
"""
fs = 1 / dt # 采样频率
f, t, Zxx = signal.stft(data, fs=fs,
nperseg=nperseg,
noverlap=noverlap,
window='hann',
boundary=None)
# 转换为dB
Zxx_db = 20 * np.log10(np.abs(Zxx) + 1e-16)
return f, t, Zxx_db
5. 信号包络提取与振幅归一化
5.1 包络提取方法比较
信号包络提取是一种重要的处理方法,它可以突出显示信号的能量变化。常用的包络提取方法包括:
- 希尔伯特变换法:基于希尔伯特变换构建解析信号,然后计算其振幅
- 峰值插值法:识别信号的局部极大值,然后通过插值连接这些极大值
- 均方根包络:使用滑动窗口计算信号的均方根值
以下代码比较了不同的包络提取方法:
def compare_envelope_methods(signal, method='all'):
"""
比较不同的包络提取方法
参数:
- signal: 输入信号,一维数组
- method: 要计算的包络方法,'all'表示所有方法
返回:
- envelopes: 包含不同方法的包络结果的字典
"""
envelopes = {}
if method in ['hilbert', 'all']:
# 希尔伯特变换法
analytic_signal = hilbert(signal)
envelopes['hilbert'] = np.abs(analytic_signal)
if method in ['peak', 'all']:
# 峰值插值法
envelopes['peak'] = envelope_peak_interp(signal, kind='cubic')
if method in ['rms', 'all']:
# 均方根包络
window_size = min(51, len(signal)//10) # 自适应窗口大小
window_size = window_size + 1 if window_size % 2 == 0 else window_size # 确保窗口大小为奇数
envelopes['rms'] = envelope_rms(signal, window_size)
return envelopes
def plot_envelope_comparison(signal, envelopes):
"""
绘制不同包络提取方法的比较图
参数:
- signal: 原始信号
- envelopes: 包含不同方法的包络结果的字典
"""
plt.figure(figsize=(12, 8))
# 绘制原始信号
plt.plot(signal, 'b-', label='Original Signal', alpha=0.6)
# 绘制各种包络
colors = ['r-', 'g-', 'm-']
for i, (method, envelope) in enumerate(envelopes.items()):
plt.plot(envelope, colors[i], label=f'{method.capitalize()} Envelope', linewidth=2)
plt.title('Envelope Extraction Method Comparison')
plt.xlabel('Sample')
plt.ylabel('Amplitude')
plt.legend()
plt.grid(True)
plt.show()
5.2 峰值插值法
峰值插值法是一种简单而有效的包络提取方法,特别适用于振荡信号。以下是其实现:
def envelope_peak_interp(data, kind='linear'):
"""
使用峰值插值法计算包络线
参数:
data (np.ndarray): 输入数据
kind (str): 插值方法 ('linear', 'cubic', 'quadratic')
返回:
np.ndarray: 计算得到的包络线
"""
from scipy.interpolate import interp1d
# 找到所有局部最大值
peaks = []
peak_indices = []
for i in range(1, len(data) - 1):
if data[i] > data[i - 1] and data[i] > data[i + 1]:
peaks.append(abs(data[i]))
peak_indices.append(i)
# 确保包含起点和终点
if len(peak_indices) < 2:
return np.abs(data) # 如果找不到足够的峰值,返回绝对值
# 添加起点和终点
peaks.insert(0, abs(data[0]))
peak_indices.insert(0, 0)
peaks.append(abs(data[-1]))
peak_indices.append(len(data) - 1)
# 使用插值生成包络线
f = interp1d(peak_indices, peaks, kind=kind, fill_value="extrapolate")
envelope = f(np.arange(len(data)))
return envelope
5.3 振幅归一化技术
振幅归一化是将数据缩放到特定范围(如[-1, 1])的过程,有助于不同数据集的比较和处理。以下是振幅归一化的代码实现:
def amplitude_normalization(data):
"""
对输入的二维数据进行振幅归一化,并使用 imshow 进行可视化展示。
参数:
----------
data : np.ndarray
输入数据,形状应为 (num_traces, num_samples),且 num_traces < num_samples。
返回:
----------
normalized_data : np.ndarray
归一化后的数据,形状与输入相同。
"""
# 输入验证
if not isinstance(data, np.ndarray):
raise TypeError("输入数据必须是一个 numpy.ndarray 对象。")
if data.ndim != 2:
raise ValueError(f"输入数据必须是二维数组,但当前维度为 {data.ndim}。")
num_traces, num_samples = data.shape
if num_traces >= num_samples:
print(f"警告:输入数据的形状可能需调整,当前 num_traces={num_traces} >= num_samples={num_samples}。")
# 振幅归一化
max_vals = np.max(np.abs(data), axis=1).reshape(-1, 1)
max_vals[max_vals == 0] = 1 # 避免除以零
normalized_data = data / max_vals
return normalized_data
5.4 道间均衡处理
道间均衡是一种使不同道的振幅水平相似的处理方法,有助于减少采集条件变化导致的振幅差异:
def trace_equalization(data, method='rms', window=None):
"""
对数据进行道间均衡处理
参数:
- data: 输入数据,形状为(traces, samples)
- method: 均衡方法,'rms'或'max'
- window: 用于计算均衡因子的时间窗口 (start, end),默认使用全部数据
返回:
- equalized_data: 均衡后的数据
"""
ntraces, nsamples = data.shape
if window is None:
window = (0, nsamples)
start, end = window
window_data = data[:, start:end]
if method == 'rms':
# 使用均方根均衡
factors = np.sqrt(np.mean(window_data ** 2, axis=1))
elif method == 'max':
# 使用最大值均衡
factors = np.max(np.abs(window_data), axis=1)
else:
raise ValueError("方法必须是 'rms' 或 'max'")
# 处理零因子
factors = np.where(factors > 0, factors, 1)
# 应用均衡因子
equalized_data = data / factors[:, np.newaxis]
return equalized_data
6. GPR信号综合分析方法
6.1 时域-频域联合分析
时域-频域联合分析是同时研究信号的时域特性和频域特性的方法,能够提供更全面的信号理解。以下是一个实现例子:
def time_frequency_analysis(trace, dt):
"""
对单道数据进行时域-频域联合分析
参数:
- trace: 输入的单道数据
- dt: 采样时间间隔(秒)
"""
# 计算希尔伯特变换及相关属性
analytic_signal = hilbert(trace)
envelope = np.abs(analytic_signal)
instantaneous_phase = np.angle(analytic_signal)
unwrapped_phase = np.unwrap(instantaneous_phase)
# 计算瞬时频率
inst_freq = np.diff(unwrapped_phase) / (2*np.pi*dt)
inst_freq = np.append(inst_freq, inst_freq[-1]) # 补充边界点
# 计算FFT
n = len(trace)
freqs = np.fft.rfftfreq(n, dt)
fft_mag = np.abs(np.fft.rfft(trace))
# 计算STFT
f_stft, t_stft, Zxx_db = calculate_stft(trace, dt)
# 可视化
fig = plt.figure(figsize=(15, 12))
# 时域信号
ax1 = fig.add_subplot(3, 2, 1)
ax1.plot(trace)
ax1.plot(envelope, 'r-', linewidth=1.5)
ax1.set_title('Time Domain Signal & Envelope')
ax1.set_xlabel('Sample')
ax1.set_ylabel('Amplitude')
ax1.grid(True)
# 相位
ax2 = fig.add_subplot(3, 2, 2)
ax2.plot(unwrapped_phase)
ax2.set_title('Unwrapped Phase')
ax2.set_xlabel('Sample')
ax2.set_ylabel('Phase (rad)')
ax2.grid(True)
# 瞬时频率
ax3 = fig.add_subplot(3, 2, 3)
ax3.plot(inst_freq / 1e6) # 转换为MHz
ax3.set_title('Instantaneous Frequency')
ax3.set_xlabel('Sample')
ax3.set_ylabel('Frequency (MHz)')
ax3.grid(True)
# FFT频谱
ax4 = fig.add_subplot(3, 2, 4)
ax4.plot(freqs / 1e6, fft_mag) # 转换为MHz
ax4.set_title('Frequency Spectrum')
ax4.set_xlabel('Frequency (MHz)')
ax4.set_ylabel('Magnitude')
ax4.grid(True)
# STFT时频图
ax5 = fig.add_subplot(3, 1, 3)
im = ax5.pcolormesh(t_stft, f_stft / 1e6, Zxx_db, shading='gouraud', cmap='viridis')
ax5.set_title('Short-Time Fourier Transform')
ax5.set_xlabel('Time (s)')
ax5.set_ylabel('Frequency (MHz)')
fig.colorbar(im, ax=ax5, label='Magnitude (dB)')
plt.tight_layout()
plt.show()
6.2 短时傅里叶变换(STFT)
短时傅里叶变换通过对信号应用一系列窗口,然后对每个窗口内的数据进行傅里叶变换,从而生成时频表示。以下是更详细的STFT实现:
def stft_analysis(data, dt, window_type='hann', nperseg=64, noverlap=48, nfft=None):
"""
对数据进行短时傅里叶变换分析
参数:
- data: 输入数据,可以是一维或二维数组
- dt: 采样时间间隔(秒)
- window_type: 窗口类型,如'hann', 'hamming', 'blackman'等
- nperseg: 每个段的长度
- noverlap: 段之间的重叠点数
- nfft: FFT的长度,默认为None(自动选择)
返回:
- 如果输入是一维数据,返回 (f, t, Zxx)
- 如果输入是二维数据,返回包含每个道STFT结果的列表
"""
fs = 1 / dt # 采样频率
if data.ndim == 1:
# 一维数据
f, t, Zxx = signal.stft(data, fs=fs,
window=window_type,
nperseg=nperseg,
noverlap=noverlap,
nfft=nfft,
boundary=None)
return f, t, Zxx
else:
# 二维数据,对每一道分别处理
results = []
for i in range(data.shape[0]):
f, t, Zxx = signal.stft(data[i], fs=fs,
window=window_type,
nperseg=nperseg,
noverlap=noverlap,
nfft=nfft,
boundary=None)
results.append((f, t, Zxx))
return results
6.3 质心频率分析
质心频率是频谱能量分布的加权平均频率,可以用来研究信号的频率变化趋势。对于月球雷达数据,质心频率的下降通常与介质损耗有关:
def cal_center_freq_stft(data_input, dt, overlop_time=0.76, nfft=8192):
"""
计算STFT的质心频率
参数:
- data_input: 输入数据,一维数组
- dt: 采样时间间隔(秒)
- overlop_time: 重叠时间(纳秒)
- nfft: FFT的长度
返回:
- t: 时间数组
- center_freq: 质心频率数组
"""
fs = 1 / dt # 采样频率
nperseg = int(overlop_time / (dt * 1e9)) + 1 # 将纳秒转换为样本数
noverlap = nperseg - 3 # 重叠样本数
# 计算STFT
f, t, Zxx = signal.stft(data_input,
fs=fs,
window='hann',
nperseg=nperseg,
noverlap=noverlap,
nfft=nfft,
boundary=None,
padded=False)
# 计算幅度
magnitude = np.abs(Zxx)
# 对每个时间点计算质心频率
center_freq = np.zeros(len(t))
for i in range(len(t)):
if np.sum(magnitude[:, i]) != 0:
# 质心频率 = Σ(f * magnitude) / Σ(magnitude)
center_freq[i] = np.sum(f * magnitude[:, i]) / np.sum(magnitude[:, i])
return t, center_freq
8. 总结与展望
本教程详细介绍了LRPR数据处理中的地形校正与信号分析。通过应用这些技术,可以显著提高雷达数据的信息提取能力,为月球次表面结构的研究提供更准确的依据。
主要内容包括:
- 地形校正技术,用于消除地表起伏对雷达数据的影响
- 零时校正方法,确保电磁波传播时间的准确计算
- 希尔伯特变换与瞬时属性分析,包括瞬时振幅、相位和频率
- 质心频率分析
- 信号包络提取和振幅归一化技术
通过深入研究这些技术,将能更好地理解月球地下结构,为未来的月球资源勘探和人类月球活动提供科学支持。
更多推荐


所有评论(0)