现在看来毫米波雷达的相关资料太少了,初学者弄清相关的流程都是很费力,所以我在这里将自己的预处理方案开源,也许对向我一样的初学者有一点帮助。
先给出我的初始化代码,这一部分需要根据硬件进行设计
Matlab
function params = initializeParams()
% 初始化毫米波雷达参数
params.startFreq = 77e9; % 起始频率 (Hz)
params.endFreq = 81e9; % 结束频率 (Hz)
params.adcStartTime = 6e-6; % ADC起始时间 (s)
params.adcSamples = 250; % ADC采样点数
params.sampleRate = 6250e3; % 采样率 (Hz)
params.rampEndTime = 50e-6; % 调频斜坡结束时间 (s)
params.idleTime = 7e-6; % 空闲时间 (s)
params.frameCount = 1200; % 帧数
params.framePeriodicity = 50e-3; % 帧周期 (s)
params.chirpLoopCount = 128; % Chirp循环数
params.rxGain = 30; % RX增益 (dB)
params.txCount = 2; % 发送天线数
params.rxCount = 4; % 接收天线数
% 计算参数
params.BW = params.endFreq - params.startFreq; % 带宽 (4GHz)
params.fSlope = params.BW / (params.rampEndTime - params.adcStartTime); % 调频斜率 (~90.9e12 Hz/s)
params.rangeFFTSize = 256; % Range FFT点数
params.c = 3e8; % 光速 (m/s)
% 距离分辨率
params.rangeRes = params.c / (2 * params.BW); % [m]
%多普勒FFT点数
params.fft_points = 128;
%DOA参数
params.fc = (params.startFreq + params.endFreq) / 2; % 中心频率 (79GHz)
params.lambda = params.c / params.fc; % 波长
params.ant_spacing = params.lambda / 2; % 天线间距 (~1.898mm)
end
我将标题输入Deepseek,deepseek生成如下的流程图:

Range-FFT(距离维FFT)
将每个 chirp 的时域采样信号转换为频域信号。由于雷达发射的是线性调频波(FMCW),目标的距离信息正比于其回波频率(中频频率)。因此,通过FFT可以将不同距离的目标在频率轴上分离开,得到距离维信息。
输入:单个Chirp的时域采样信号
输出:每个 chirp 的距离谱(每个天线应该单独进行)
在这一步,理论上来讲就可以进行相位的求解了,所以可以在这之后直接进行去噪并求相位:
Matlab
function [dynamic_data, range_axis] = processRangeFFTAndRemoveStatic(current_data, params)
% Range FFT处理
range_fft = fft(current_data, params.rangeFFTSize, 1);
% 静态杂波去除
static_clutter = mean(range_fft, 2);
dynamic_data = range_fft - static_clutter;
% 关键修正:物理距离轴生成(取消fftshift)
range_resolution = params.c/(2*params.BW); % 3.75cm
max_range = (params.sampleRate * params.c)/(2*params.fSlope); % 理论最大无模糊距离
% 生成正向距离轴(从0开始)
range_axis = (0:params.rangeFFTSize-1) * range_resolution;
% 截断到理论最大距离
valid_bins = range_axis <= max_range;
range_axis = range_axis(valid_bins);
dynamic_data = dynamic_data(valid_bins, :, :);
end
% % 可视化部分
% figure;
% subplot(2, 1, 1);
% % 绘制Range FFT的幅度(去除静态杂波前)
% imagesc(abs(range_fft(:, :, 1))); % 假设你的数据是3维的,第一个维度是距离,第三个是时间
% colorbar;
% title('Range FFT (Before Static Clutter Removal)');
% xlabel('Time Index');
% ylabel('Range Bin');
%
% subplot(2, 1, 2);
% % 绘制动态数据的幅度(去除静态杂波后)
% imagesc(abs(dynamic_data(:, :, 1))); % 同样假设数据是3维的
% colorbar;
% title('Dynamic Data (After Static Clutter Removal)');
% xlabel('Time Index');
% ylabel('Range Bin');
%
% % 也可以画出一个简单的距离轴上的幅度变化(例如在某个时间索引上)
% figure;
% plot(range_axis, abs(dynamic_data(:, 1, 1)));
% title('Range Profile at Time Index 1');
% xlabel('Range (m)');
% ylabel('Amplitude');


多普勒-FFT(速度维FFT)
在得到距离谱之后,我们对多个连续 chirp 在相同距离单元上的信号 进行第二次FFT。由于目标运动引起的相位变化是相干的,而噪声是非相干的,这次FFT可以将不同速度(即不同多普勒频率)的目标分离开,得到速度维信息。
输入:多个 chirp 在同一距离门(range bin) 上的数据。
输出:距离-多普勒谱(RD谱)。
Matlab
function [doppler_fft_result, velocity_axis] = doppler_fft_processing(range_fft_data, params, plot_enable)
% 多普勒FFT处理函数
%
% 输入参数:
% range_fft_data: 距离FFT后的数据,维度为256×153600×4
% params: 参数结构体,来自initializeParams.m
% plot_enable: 是否启用可视化 (可选,默认为true)
%
% 输出参数:
% doppler_fft_result: 多普勒FFT结果,维度为256×128×1200×4
% velocity_axis: 速度轴,用于绘图
% 设置默认参数
if nargin < 3
plot_enable = false; % 默认不启用可视化
end
% 获取数据维度
[range_bins, ~, channels] = size(range_fft_data);
frames = params.frameCount;
chirps_per_frame = params.chirpLoopCount;
fft_points = params.fft_points;
% 重新组织数据: 256×128×1200×4
reshaped_data = reshape(range_fft_data, ...
[range_bins, chirps_per_frame, frames, channels]);
% 初始化多普勒FFT结果
doppler_fft_result = zeros(range_bins, fft_points, frames, channels);
% 计算每个chirp的时间
chirp_time = params.rampEndTime + params.idleTime;
% 对每个距离门、每帧和每个通道进行多普勒FFT
for channel = 1:channels
for frame = 1:frames
for range_bin = 1:range_bins
% 提取当前距离门、当前帧、当前通道的所有啁啾数据
chirp_data = squeeze(reshaped_data(range_bin, :, frame, channel));
% 确保数据是行向量
if iscolumn(chirp_data)
chirp_data = chirp_data';
end
% 加窗处理 (减少频谱泄漏)
if length(chirp_data) > 1
window = hamming(length(chirp_data))';
windowed_data = chirp_data .* window;
else
windowed_data = chirp_data;
end
% 执行多普勒FFT并进行fftshift
doppler_fft = fft(windowed_data, fft_points);
doppler_fft = fftshift(doppler_fft);
% 确保结果是行向量后再赋值
if iscolumn(doppler_fft)
doppler_fft = doppler_fft';
end
% 存储结果
doppler_fft_result(range_bin, :, frame, channel) = doppler_fft;
end
end
end
% 计算速度轴
prf = 1 / chirp_time; % 脉冲重复频率
max_doppler = prf / 2; % 最大多普勒频率
% 多普勒频率轴
doppler_axis = linspace(-max_doppler, max_doppler, fft_points);
% 转换为速度轴 (v = λ*fd/2)
velocity_axis = doppler_axis * params.lambda / 2;
% 可视化部分 - 只保留RDM图
if plot_enable
% 选择要可视化的帧和通道
frame_to_plot = min(1, frames); % 第一帧
channel_to_plot = min(1, channels); % 第一通道
% 创建距离-速度图 (RDM)
figure('Name', '距离-速度图 (RDM)', 'Position', [100, 100, 800, 600]);
rdm_data = 20*log10(abs(squeeze(doppler_fft_result(:, :, frame_to_plot, channel_to_plot))));
% 使用兼容性更好的方法设置颜色范围
imagesc(velocity_axis, (1:range_bins)*params.rangeRes, rdm_data);
% 设置颜色范围的兼容方法
caxis([max(rdm_data(:))-60, max(rdm_data(:))]); % 使用caxis而不是clim
xlabel('速度 (m/s)');
ylabel('距离 (m)');
title(sprintf('距离-速度图 (帧: %d, 通道: %d)', frame_to_plot, channel_to_plot));
colorbar;
axis xy;
colormap('jet');
end
end
在这步之后我们就可以解相位并进行后续工作了。

另一个分支:角度信息
**波束成形(Beamforming)**利用多个接收天线之间的空间相位差来实现。
对于RD谱中的每一个"点"(即一个特定的距离-速度单元),我们提取所有天线在该单元上的复数数据。对这些空间数据(跨天线维度)进行FFT(即Angle-FFT)或数字波束成形算法(如Capon, MUSIC),就可以得到该目标点的角度谱,峰值位置即对应角度信息。
这里面临一个问题:内存,4D数据[range,doppler,angle,time],这样的数据是非常庞大的,我个人在处理时,信号的长度为一分钟,如果保留完整的数据一分钟的4D数据内存占用约为100G,所以在波束成形前我们需要确定目标,缩小range,doppler的范围。一般使用CFAR,其实就是寻找峰值,所以我个人就是寻找峰值之后框选附近的几个bin。
Matlab
function [targetWindows, rangeAngleProfile, physicalInfo] = extractStrongestTarget(data, params, visualize)
% 输入:
% data - 4D矩阵 [256×128×1200×4] (Range x Doppler x Frame x Antenna)
% params - 参数结构体,包含距离分辨率等信息
% visualize - 布尔值,是否可视化结果
%
% 输出:
% targetWindows - 4D矩阵 [7x7x1200x4] 提取的目标窗口(复数)
% rangeAngleProfile - 4D矩阵 [7x7x1200x361] 距离-角度谱(复数,使用MUSIC算法)
% physicalInfo - 结构体,包含物理距离和多普勒信息
% 设置默认参数
if nargin < 3
visualize = false; % 默认不启用可视化
end
% 获取数据尺寸
[numRangeBins, numDopplerBins, numFrames, numAnt] = size(data);
targetWindows = zeros(7, 7, numFrames, numAnt);
targetRanges = zeros(numFrames, 1);
targetDopplers = zeros(numFrames, 1);
% 计算距离轴(基于筛选后的数据)
range_axis = (0:numRangeBins-1) * params.rangeRes;
% 计算多普勒轴
chirp_time = params.rampEndTime + params.idleTime;
prf = 1 / chirp_time;
max_doppler = prf / 2;
doppler_axis = linspace(-max_doppler, max_doppler, numDopplerBins);
velocity_axis = doppler_axis * params.lambda / 2;
% 定义角度搜索范围 (-90° 到 90°)
angle_range = -90:0.5:90; % 0.5° 分辨率
num_angles = length(angle_range); % 361个角度点
rangeAngleProfile = complex(zeros(7, 7, numFrames, num_angles)); % 确保为复数
% 构建天线阵列导向矢量
antenna_positions = (0:numAnt-1) * params.ant_spacing;
steering_vectors = zeros(num_angles, numAnt);
for ang_idx = 1:num_angles
angle_rad = deg2rad(angle_range(ang_idx));
steering_vectors(ang_idx, :) = exp(-1j * 2 * pi * antenna_positions * sin(angle_rad) / params.lambda);
end
% 存储物理信息
physicalInfo.range_axis = range_axis;
physicalInfo.velocity_axis = velocity_axis;
physicalInfo.angle_range = angle_range;
physicalInfo.targetRanges = zeros(numFrames, 1);
physicalInfo.targetDopplers = zeros(numFrames, 1);
physicalInfo.windowRangeBins = zeros(7, numFrames);
physicalInfo.windowDopplerBins = zeros(7, numFrames);
% 遍历所有帧
for frameIdx = 1:numFrames
% 对4个天线的数据取平均,找到最强目标位置(非相干累加)
combinedSlice = mean(abs(data(:,:,frameIdx,:)), 4);
% 找到全局最大值位置
[~, maxIdx] = max(combinedSlice(:));
[rangeBinIdx, dopplerBinIdx] = ind2sub(size(combinedSlice), maxIdx);
% 记录目标的物理距离和多普勒速度
targetRanges(frameIdx) = range_axis(rangeBinIdx);
targetDopplers(frameIdx) = velocity_axis(dopplerBinIdx);
physicalInfo.targetRanges(frameIdx) = range_axis(rangeBinIdx);
physicalInfo.targetDopplers(frameIdx) = velocity_axis(dopplerBinIdx);
% 计算窗口边界(处理边界情况)
rangeStart = max(1, rangeBinIdx - 3);
rangeEnd = min(numRangeBins, rangeBinIdx + 3);
dopplerStart = max(1, dopplerBinIdx - 3);
dopplerEnd = min(numDopplerBins, dopplerBinIdx + 3);
% 存储窗口对应的物理距离和多普勒值
physicalInfo.windowRangeBins(:, frameIdx) = range_axis(rangeStart:rangeEnd);
physicalInfo.windowDopplerBins(:, frameIdx) = velocity_axis(dopplerStart:dopplerEnd);
% 遍历所有天线
for antIdx = 1:numAnt
% 获取当前帧和天线的距离-多普勒平面
currentSlice = data(:,:,frameIdx,antIdx); % 使用复数数据
% 提取目标窗口
window = currentSlice(rangeStart:rangeEnd, dopplerStart:dopplerEnd);
% 创建7x7窗口(边界不足时补零)
fullWindow = zeros(7,7);
r1 = max(1, 4 - (rangeBinIdx - rangeStart));
r2 = min(7, 4 + (rangeEnd - rangeBinIdx));
d1 = max(1, 4 - (dopplerBinIdx - dopplerStart));
d2 = min(7, 4 + (dopplerEnd - dopplerBinIdx));
fullWindow(r1:r2, d1:d2) = window;
targetWindows(:,:,frameIdx,antIdx) = fullWindow;
end
% 使用MUSIC算法计算距离-角度谱
for rangeBin = 1:7
for dopplerBin = 1:7
% 提取当前距离-多普勒单元的所有天线复数数据
antData = squeeze(targetWindows(rangeBin, dopplerBin, frameIdx, :));
% 计算协方差矩阵
R = antData * antData';
% 特征值分解
[eigenvectors, eigenvalues] = eig(R);
eigenvalues = diag(eigenvalues);
[~, idx] = sort(eigenvalues, 'descend');
eigenvectors = eigenvectors(:, idx);
% 假设只有一个信号源,噪声子空间是除最大特征值对应的特征向量外的所有特征向量
noise_subspace = eigenvectors(:, 2:end);
% 计算MUSIC谱 - 保持复数形式
music_spectrum = zeros(1, num_angles);
for ang_idx = 1:num_angles
a = steering_vectors(ang_idx, :).';
music_spectrum(ang_idx) = 1 / (a' * (noise_subspace * noise_subspace') * a);
end
% 存储复数结果
rangeAngleProfile(rangeBin, dopplerBin, frameIdx, :) = music_spectrum;
end
end
end
% 可视化第一帧结果
if visualize
visualizeFirstFrame(data, targetWindows, physicalInfo, rangeAngleProfile);
end
end
function visualizeFirstFrame(data, windows, physicalInfo, rangeAngleProfile)
% 创建新图窗
figure('Position', [100, 100, 1400, 800], 'Name', 'First Frame Target Extraction and MUSIC Angle Spectrum');
% 获取物理信息
range_axis = physicalInfo.range_axis;
velocity_axis = physicalInfo.velocity_axis;
angle_range = physicalInfo.angle_range;
targetRange = physicalInfo.targetRanges(1);
targetDoppler = physicalInfo.targetDopplers(1);
windowRangeBins = physicalInfo.windowRangeBins(:, 1);
windowDopplerBins = physicalInfo.windowDopplerBins(:, 1);
% 找到对应的bin索引
[~, rangeBinIdx] = min(abs(range_axis - targetRange));
[~, dopplerBinIdx] = min(abs(velocity_axis - targetDoppler));
% 显示第一帧的4个天线结果
for antIdx = 1:4
% 原始距离-多普勒平面
subplot(3, 4, antIdx);
imagesc(velocity_axis, range_axis, 10*log10(abs(data(:,:,1,antIdx))));
hold on;
plot(velocity_axis(dopplerBinIdx), range_axis(rangeBinIdx), 'rx', 'MarkerSize', 15, 'LineWidth', 2);
hold off;
title(sprintf('Antenna %d - Original', antIdx));
subtitle(sprintf('Range: %.2fm, Doppler: %.2fm/s', targetRange, targetDoppler));
xlabel('速度 (m/s)'); ylabel('距离 (m)');
axis xy; colorbar;
caxis([-50 0]);
% 提取的目标窗口 - 使用物理坐标
subplot(3, 4, antIdx+4);
imagesc(windowDopplerBins, windowRangeBins, abs(windows(:,:,1,antIdx)));
title(sprintf('Antenna %d - 7x7 Window', antIdx));
xlabel('速度 (m/s)'); ylabel('距离 (m)');
axis xy; colorbar;
% 标记中心点
hold on;
plot(targetDoppler, targetRange, 'rx', 'MarkerSize', 15, 'LineWidth', 2);
hold off;
end
% 显示距离-角度谱(在中心多普勒单元)
subplot(3, 4, [9, 10, 11, 12]);
centerDopplerBin = 4; % 中心多普勒单元
rangeAngleSlice = squeeze(rangeAngleProfile(:, centerDopplerBin, 1, :));
% 获取当前窗口对应的物理距离
currentRangeBins = windowRangeBins;
% 显示幅度谱
imagesc(angle_range, currentRangeBins, 10*log10(abs(rangeAngleSlice)));
title('MUSIC距离-角度谱 (中心多普勒单元)');
xlabel('角度 (度)'); ylabel('距离 (m)');
colorbar;
colormap jet;
% 添加总体信息
sgtitle(sprintf('目标提取结果 - 距离: %.2fm, 速度: %.2fm/s', targetRange, targetDoppler), ...
'FontSize', 14, 'FontWeight', 'bold');
end
在这里有几个注意点:
需要现在天线维度累加来估计幅值
Matlab
% 对4个天线的数据取平均,找到最强目标位置(非相干累加)
combinedSlice = mean(abs(data(:,:,frameIdx,:)), 4);
我用的是music方法,普遍来讲music方法只保存谱,而我需要后续其它的处理,所以将其保存为复数:
Matlab
% 存储复数结果
rangeAngleProfile(rangeBin, dopplerBin, frameIdx, :) = music_spectrum;
% 存储实数结果
rangeAngleProfile(rangeBin, dopplerBin, frameIdx, :) = abs(music_spectrum);

相位解包裹(Phase Unwrapping)及降噪
Matlab
%% 步骤1:找到最大幅值距离门
% 沿慢时间维度累加幅值
amplitude_sum = sum(abs(beamformed_data), 2); % [range_bins, 1]
% 找到最大幅值对应的距离门索引
[~, max_range_idx] = max(amplitude_sum);
target_signal = beamformed_data(max_range_idx, :); % [1, time_frames]
%% 步骤2:相位计算与解包裹
% 计算原始相位
raw_phase = angle(target_signal); % [1, time_frames]
% 相位解包裹(沿时间维度)
unwrapped_phase = unwrap(raw_phase, [], 2); % [1, time_frames]
%% 步骤3:去趋势/除直流分量(一阶差分)
v = diff(unwrapped_phase); % [1, time_frames-1]
v = detrend(v);
%% 步骤4:滑动平均滤波
N_w = 5; % 可以是奇数或偶数
% 1. 计算 movmean (会短 N_w-1 个点)
V_smooth_short = movmean(v, N_w, 2); % 等价于 'Endpoints', 'shrink'
%在最后两个点之间插入它们的平均值
last_two_points = V_smooth_short(:, end-1:end);
inserted_value = mean(last_two_points, 2);
matlab的unwrap可以自动解包裹,不需要额外处理。
至此我们应该以及解决了预处理所需要的一般操作,可在此基础上继续开发分析算法或调整预处理中的参数、操作来进行优化。