简介:面向雷达工程、自动驾驶感知、无人机导航及高校课程设计的学习者,这套资料以Matlab仿真为主线,配合Python分类实验,覆盖FMCW回波读取、距离-多普勒与距离-角度处理、CFAR检测、聚类、角度估计以及STFT微多普勒特征生成等环节,可用于汽车防撞、无人机感知等场景的算法验证与教学实践。压缩包共54个文件,约50.42MB,包含22个m脚本、5个py程序、3个mat数据、2个ipynb笔记本,以及jpg结果图、xml工程配置和md说明等;结果图涵盖距离-多普勒、距离-角度、微多普勒与点云可视化,数据文件可直接用于复现。已有2049人学习下载。读者可借助README与配置快速理清目录,运行检测、聚类与微多普勒成像流程,并用分类器训练/测试笔记本完成特征提取、网络搭建与识别评估,同时对照结果图检查处理效果、积累排错与调参思路。
1. 微多普勒毫米波雷达这套链路,为什么我建议先跑通仿真再碰实测
一块 77GHz 的板子摆在桌上,IQ 数据一帧一帧往外吐,常见的做法是直接喂给分类网络,跑出五十几个点的准确率,然后开始换骨干、加注意力。这种场面我见得太多了,问题往往不在网络——MTI 把低速目标削掉了,时频图里只剩一条模糊的亮线,喂什么模型都没戏。毫米波雷达的信号处理是一条串行漏斗:去斜、距离维 FFT、多普勒维 FFT、CFAR 检测、卡尔曼跟踪、微多普勒时频分析、目标识别,前面一级塌了,后面全是垃圾进垃圾出。这套 Matlab 实现从 chirp 建模一路走到航迹输出和分类器验证,适合手里有板子、想把实测数据跑出结果的人,也适合先在仿真里把参数关系摸熟了再上硬件的同学。
2. 从 Chirp 到距离-多普勒图:把 FMCW 的六个参数算准
2.1 参数之间是互相锁死的,不能一个个拍脑袋
新手最容易犯的错是先定采样率再定带宽,结果量程和分辨率互相打架。FMCW 的四个核心关系必须一起算:距离分辨率 ΔR = c/(2B),最大不模糊距离 Rmax = c·Fs/(4S),最大不模糊速度 vmax = ±λ·PRF/4,速度分辨率 Δv = λ/(2·M·Tc)。其中 S 是调频斜率,PRF = 1/Tc 是 chirp 重复频率。
举个例子,B = 1 GHz 时 ΔR = 0.15 m,这个分辨率对人体目标够用,但要分开手指这种微动手势就不够了,得把带宽拉到 3~4 GHz。Rmax 这条线更隐蔽:很多人把 Fs 设成 10 MHz 就觉得"采样够快了",其实拍频上限是 Fs/2,对应的距离只有三十来米,远处目标会直接折叠到近处,而且在距离-多普勒图上长得跟真目标一样。
| 参数 | 符号 | 本文取值 | 决定了什么 |
|---|---|---|---|
| 载频 | fc | 77 GHz | 波长 3.9 mm,多普勒灵敏度 |
| 扫频带宽 | B | 1 GHz | 距离分辨率 0.15 m |
| chirp 时长 | Tc | 40 us | PRF 25 kHz,速度量程 ±24 m/s |
| 调频斜率 | S = B/Tc | 2.5e13 Hz/s | 拍频与距离的换算系数 |
| 快时间采样率 | Fs | 10 MHz | 最大不模糊距离约 30 m |
| 每帧 chirp 数 | M | 128 | 速度分辨率 0.38 m/s |
注意调频斜率的定义。有的资料写 S = 2B/Tc,那是把上下扫频的三角波折算成单程的做法;如果你的数据手册按 B/Tc 定义,你按 2B/Tc 去反算距离,出来的结果会整整差一倍,而且图上目标位置看着"挺合理",这个坑我踩过。
2.2 去斜之后,拍频就是距离
FMCW 不用匹配滤波,走的是去斜(dechirp)路线:把回波和本振混频,取差频分量,得到的是一个单频信号,频率与目标距离成正比。好处是采样率只要覆盖拍频带宽,不用按射频带宽去采,A/D 压力小一个量级。
下面的脚本生成一个目标加一段静止强杂波的复基带数据,可以直接跑:
c = 3e8; fc = 77e9; lambda = c/fc; B = 1e9; Tc = 40e-6; S = B/Tc; % 斜率按 B/Tc 定义,别和 2B/Tc 混用 Fs = 10e6; Ns = round(Tc*Fs); % 400 个快时间点 M = 128; % 一帧内的 chirp 数 Nfft_r = 512; % 距离维补零 t = (0:Ns-1)/Fs; % 快时间轴 R0 = 8.3; v = 1.6; % 目标初始距离、径向速度 X = complex(zeros(Ns, M)); for m = 1:M Rm = R0 + v*(m-1)*Tc; % 第 m 个 chirp 起始时刻的距离走动 fb = 2*S*Rm/c; % 拍频,与距离成正比 X(:,m) = exp(1j*2*pi*(fb*t + 2*fc*Rm/c)); end X = X + 1e-2*(randn(size(X)) + 1j*randn(size(X))); % 复高斯噪声 X = X + 3*exp(1j*2*pi*(2*S*4.0/c)*t); % 4 m 处静止强杂波 Xr = fft(X .* hamming(Ns), Nfft_r, 1); % 距离维 FFT range_axis = (0:Nfft_r-1).' * c*Fs/(2*S*Nfft_r); % 距离栅格,单位 m逐行看几个关键点。Rm里带了v*(m-1)*Tc,这是慢时间维上的距离走动,也是后面多普勒的来源,如果这里写死成常量 R0,多普勒维就永远是一条零频直线,很多人第一次跑仿真就是这么被自己骗了。相位项2*fc*Rm/c不能省,它对应 2Rm/λ 的相位变化,正好是慢时间维的调制量。窗函数用hamming压距离旁瓣,代价是主瓣展宽约 1.4 倍,强杂波旁边的小目标才不会被旁瓣淹掉。Nfft_r补零只改变栅格密度,不改变 0.15 m 的物理分辨率,这点在写论文图和做峰值插值时容易混淆。
2.3 多普勒维 FFT 与距离-多普勒图
距离维出来的是距离×chirp 的矩阵,沿第二维做 FFT 就得到多普勒。记住一个映射关系:多普勒频率 f 对应的径向速度是 v = f·λ/2,正负号取决于目标靠近还是远离,取决于你的混频器接法和 FFT 后的频率轴方向,实测时一定要用已知方向的目标校一次。
prf = 1/Tc; RD = fftshift(fft(Xr, [], 2), 2); % 慢时间 FFT,零多普勒居中 RD_dB = 20*log10(abs(RD) + eps); dop_axis = (-M/2:M/2-1) * (prf/M); % Hz v_axis = dop_axis * lambda/2; % m/s X_mti = X - mean(X, 2); % 一阶对消,压制零多普勒 RD_mti = fftshift(fft(fft(X_mti .* hamming(Ns), Nfft_r, 1), [], 2), 2);fftshift不能省,否则零多普勒在矩阵第一列,做 CFAR 时二维邻域会从矩阵尾部绕回来,产生一圈假检测。mean(X,2)是最简单的一阶 MTI,沿着慢时间减均值,把静止分量减掉,代价是把速度接近零的目标也一起减了——这条在微多普勒场景里是致命的,后面第 5 章会专门讲。
多普勒轴的点数是 M 而不是补零后的长度,因为慢时间只有 128 个样本,物理分辨率就是 λ/(2·M·Tc) ≈ 0.38 m/s。有人把多普勒维也补零到 1024 点,图上看着细腻了,分辨率一分没涨。
2.4 换成实测数据前,先做一次维度对齐
仿真跑通之后换实测数据,翻车点几乎都集中在维度约定上。仿真里我按[快时间, 慢时间]排,而不少采集板导出的顺序是[chirp, sample, rx],直接送进 FFT 就是沿着错误的轴做变换,出来的图"有东西"但完全对不上物理量。
先做三件事再谈算法:一是permute把维度换成[快时间, 慢时间, 通道];二是用一面墙或者静止角反做一次零多普勒校验,回波应该稳稳落在 v=0 那一列;三是量一帧的实际时长,M*Tc和采集软件显示的帧率对不上时,大概率有丢包或者 chirp 之间有 idle time,Tc要按 chirp 周期而不是有效扫频时长来算。这三步做完,再去看距离-多普勒图,心里才有底。
3. 微多普勒时频分析:STFT 窗长、重叠率与速度量程的取法
3.1 微多普勒不是"谱线变宽",是多个散射中心的瞬时速度不同
刚体目标的回波在距离-多普勒图上是一条谱线,因为整个目标只有一个径向速度。一旦目标上有相对运动部件——人的四肢摆动、轮子的转动、旋翼的旋转——每个散射中心的瞬时径向速度都不一样,回波相位里就叠了一层周期性调制。
写成公式很干净:某个散射中心的瞬时多普勒是 f_d(t) = (2/λ)·v_r(t),其中 v_r(t) 是它相对雷达的径向速度投影;反过来,慢时间信号的相位是 4π·R(t)/λ,对时间求导就是瞬时频率。所以时频图上的曲线形状,本质上就是目标各部件的速度-时间轨迹。人走路的时频图里,中间那条稳定的亮带是躯干,两侧周期性张开的"翅膀"是手臂和腿,张开的频率就是步频,这点是后面做目标识别最硬的物理依据。
理解这一层之后,参数选择就不再是玄学:你要分辨的是两条速度相差多大的轨迹,这直接决定了频率分辨率,而频率分辨率由窗长唯一确定。
3.2 STFT 的三组参数,其实只有一个自由度
时频分析绕不开测不准关系:时间分辨率 Δt 和频率分辨率 Δf 满足 Δt·Δf ≥ 常数。在 STFT 里,窗长 T_win 同时决定了这两者——Δf ≈ 1/T_win,Δt ≈ T_win。剩下的重叠率和补零数只影响图的平滑度和栅格密度,不改变这个基本权衡。
拿数字说话。Tc = 40 us 时,窗长 128 个慢时间点对应 5.12 ms,频率分辨率约 195 Hz,折算成速度是 0.38 m/s;窗长 64 点则退到 0.76 m/s。成年人手臂摆动峰值速度和躯干速度的差值通常在 1~3 m/s 量级,用 64 点窗勉强能看出"张开了",但要做特征提取就太糙。所以我一般从 128 起步,动作快的手势类目标可以降到 64。
还有一个容易忽略的量:慢时间总长度要够。M = 128 只覆盖 5.12 ms,一个窗就把整段吃完了,根本做不出时间轴。做步态分析至少 0.5~2 s 的连续数据,也就是上万个 chirp;做手势识别 50~200 ms 就够。这不是算法问题,是采集配置问题,早点跟做采集的人对齐。
% X_all 为 [快时间, 慢时间] 排好的长时采集数据,Mlong 为慢时间总点数 Mlong = 2048; [~, rb] = min(abs(range_axis - 8.3)); % 锁定目标所在距离单元 slow = X_all(rb, 1:Mlong).'; % 慢时间复序列 win = 128; nover = 96; % 窗长 128,重叠 75% nfft = 512; % 频率维补零,只为画图好看 [S, f, tt] = spectrogram(slow, hamming(win), nover, nfft, 1/Tc); S = fftshift(S, 1); % 零多普勒移到中间行 f = f - 1/(2*Tc); % 频率轴改成 -prf/2 ~ +prf/2 v_md = f * lambda/2; % 转成速度轴,单位 m/s SdB = 20*log10(abs(S) + eps);spectrogram对复信号返回的是 0~PRF 的单边频率轴,必须先fftshift再减 PRF/2 才能得到正负速度,顺序反了会出现半个 PRF 的偏移,图上目标速度永远差一截。重叠率取 75% 是常规做法,再高只是让图更连续,计算量线性上涨。nfft补零到 512 让曲线更平滑,方便肉眼看周期,但做特征计算时我更倾向用原始栅格,避免插值引入的虚假峰值。
提示:时频图一定要在 dB 域做显示归一化,并且把每帧的最大值对齐,否则同一段动作在强弱不同的距离上看起来完全不像。
3.3 从时频图压出可判别的特征向量
时频图是二维图像,直接送 CNN 在小样本下很容易过拟合。工程上更稳的做法是先压成十几维的手工特征,再配一个轻量分类器,特征本身有物理含义,出问题能查。
| 特征 | 计算方式 | 主要区分什么 |
|---|---|---|
| 躯干速度 | 能量最强的那条脊线的均值 | 人与车、快走与慢走 |
| 峰值展宽 | 速度轴上 95% 能量分位点间距 | 肢体摆动幅度 |
| 谱宽 | 每帧多普勒标准差的时间均值 | 刚体与铰接体 |
| 谱熵 | -Σp·log p,p 为归一化多普勒谱 | 回波复杂度 |
| 微动周期 | 峰值速度曲线的自相关首峰位置 | 步频、转动频率 |
| 上下不对称度 | 正负速度能量比 | 前进/后退、上肢主导 |
| 低频能量占比 | 0.5 m/s 以内能量占总能量比 | 躯干占比,抑制杂波残留 |
谱熵这条特别有用:刚体目标的能量集中在少数几个多普勒单元,熵很低;人这种多散射中心目标能量摊得开,熵明显高。但它对噪声敏感,一定要先做噪底扣除,把低于噪底 6 dB 的单元置零再算。
周期特征我一般不从时频图直接取,而是先把每帧的峰值速度序列抽出来,做一次带通(0.5~4 Hz)再自相关,这样能避开躯干强分量对周期的干扰。这个细节看着小,实测里能把步频估计的误差从 20% 压到 5% 以内。
3.4 先选距离单元,还是整帧做时频
这里有两条路,选错了后面全拧巴。
第一条是先在距离-多普勒图上找峰,锁定目标所在距离单元,只取这一路的慢时间做 STFT。优点是干净,杂波和其他目标基本被隔离;缺点是对距离走动敏感,一个 CPI 内目标走了半个距离单元,能量就散到相邻单元里,时频图会出现断续。
第二条是把目标附近几个距离单元的能量做非相干累加,再对累加后的慢时间序列做 STFT。优点是保留了多散射中心的完整性,微多普勒的"翅膀"更饱满;缺点是近处强杂波会漏进来,需要先做距离维加窗或自适应抑制。
我的经验是单目标、近距离、静止或慢速场景走第一条;人体步态、多人场景走第二条,并且把 ROI 限制在目标前后各 2 个距离单元内。两种方式算出来的特征分布不一样,训练集和测试集必须用同一套,混着用准确率会莫名其妙地掉。
4. CFAR 检测与卡尔曼跟踪:门限、过程噪声和航迹管理的配参顺序
4.1 二维 CA-CFAR:把虚警率翻译成门限系数
CFAR 的核心是一句话:用待检测单元周围的参考单元估计噪底,再乘一个由虚警率决定的系数。均匀杂波背景下,CA-CFAR 的门限系数有闭式解 α = N·(Pfa^(-1/N) - 1),N 是参考单元个数。
参考单元之外还要留保护单元。原因很实在:目标能量会沿着距离和多普勒两个方向泄漏一到两个单元,如果不留保护,目标自己的旁瓣会被算进噪底,把自己抬高的门限挡住,出现"目标越强越检不出"的怪现象。
Tg = 2; Tr = 8; % 保护单元、参考单元(单边) pfa = 1e-4; % 虚警率 Nref = (2*(Tr+Tg)+1)^2 - (2*Tg+1)^2; % 有效参考单元数 alpha = Nref * (pfa^(-1/Nref) - 1); P = abs(RD).^2; % 必须在线性功率域做 big = conv2(P, ones(2*(Tr+Tg)+1), 'same'); guard = conv2(P, ones(2*Tg+1), 'same'); noise = (big - guard) / Nref; det = P > alpha * noise;几个参数怎么定。Nref越大,噪底估计越稳,但杂波边缘处的虚警也越多,因为参考窗跨越了两种统计特性;Tr = 8对应约 1.2 m 的距离跨度,在城市道路这种场景里已经偏大,室内场景可以降到 4。pfa设 1e-4 时alpha大约 30 倍,这个量级意味着噪底之上的检测点会比较多,必须靠后面的凝聚环节筛。
最容易错的是域的问题。RD如果是 dB 域,conv2求的是对数和,门限彻底失效,图上的检测点会均匀糊满整个平面。所有 CFAR 计算一律先转成功率,再取门限。
4.2 点迹凝聚:CFAR 出来的不是目标,是像素
二维 CFAR 输出的是二值图,一个目标往往对应十几个甚至几十个相邻的检测单元,尤其是有微多普勒展宽的人体目标。直接把这些像素当成量测送进跟踪器,会出现同一个目标在一帧里生成多条航迹的灾难。
常规做法是连通域标记加质心提取,用bwlabel或自己写 8 邻域搜索,每个连通域出一个点迹,位置取 SNR 加权质心,速度取多普勒加权均值。加权比直接取几何中心稳,因为强散射中心的位置更接近目标真实相位中心。
量测噪声 R 不要凭感觉填。用同一批静止目标做统计:把连续几百帧的质心位置记下来,算它的标准差,那就是 R 的对角元。我见过 Q 和 R 差三个数量级的情况,滤波器要么完全不信量测、航迹拖在后面,要么跟着噪声满屏乱飞,两种现象在图上都很好认。
4.3 卡尔曼滤波:Q 是加速度谱密度,不是"调参旋钮"
匀速模型的状态量取 [x, vx, y, vy],量测是 [x, y]。过程噪声矩阵按连续白加速度模型展开,Q 里那个 q 的量纲是 m²/s³,物理含义是目标机动强度的谱密度。
dt = 0.05; % 帧间隔,按 20 Hz 帧率 F = [1 dt 0 0; 0 1 0 0; 0 0 1 dt; 0 0 0 1]; q = 0.5; % 加速度谱密度 Q = q * [dt^4/4 dt^3/2 0 0; dt^3/2 dt^2 0 0; 0 0 dt^4/4 dt^3/2; 0 0 dt^3/2 dt^2]; H = [1 0 0 0; 0 0 1 0]; R = diag([0.09 0.09]); % 质心标准差 0.3 m x = [z(1); 0; z(2); 0]; P = diag([1 4 1 4]); for k = 2:numel(track_t) x = F*x; P = F*P*F' + Q; % 预测 zk = z_meas(k,:).'; S = H*P*H' + R; d2 = (zk - H*x)' / S * (zk - H*x); % 马氏距离 if d2 < chi2inv(0.99, 2) % 门限内才更新 K = P*H'/S; x = x + K*(zk - H*x); P = (eye(4) - K*H)*P; end endP的初值不要给全零,否则第一步卡尔曼增益退化成零,航迹永远起不来。马氏距离门限用卡方分布的分位数,二维量测取 0.99 分位约 9.21,这个数比拍脑袋定"3 米门限"靠谱得多,因为它自动随 P 的大小自适应。
q 的调法有个可操作的经验:先按目标最大加速度估一个上界,比如人体走路约 2 m/s²,取 q 在 0.1~1 之间扫一遍,看航迹的速度曲线是否平滑且不滞后。q 太大速度曲线抖,q 太小转弯时航迹会"切角"。
4.4 航迹管理:起始、确认、删除三件事
卡尔曼只负责单条航迹的滤波,多目标场景还要解决"哪个点迹喂哪条航迹"和"航迹什么时候生、什么时候死"。
关联用代价矩阵加匈牙利算法,代价取马氏距离,超过门限的置成大数;matchpairs或assignDetectionsToTracks都能直接用。航迹起始用 M/N 逻辑,比如连续 3 帧里命中 2 帧才确认,确认前不对外输出。删除条件一般两个:连续 miss 超过 5 帧,或者航迹的协方差迹超过阈值——后者能自动清掉那些被杂波拖着跑偏的鬼影。
ID 跳变最常见的诱因是删除条件太激进。目标被遮挡两帧就删掉,重新出现时自然分到新 ID,下游做行为分析时看到的就是两个目标。经验值是把删除帧数设成遮挡时长的 1.5 倍,宁可多留两帧也不要频繁重建。
5. 避坑与排查:五个让结果"看起来对"的翻车现场
5.1 距离与速度量程类的坑
坑一:斜率的两种定义混用。现象是目标距离整体偏大一倍或偏小一半,但在图上位置稳定、航迹平滑,很容易被当成"标定误差"放过。原因是数据手册按 2B/Tc 定义斜率,代码里按 B/Tc 换算。解决方式是拿一个已知距离的静止目标做一次单点标定,反算出实际斜率,写进配置里做常量,别每次靠记忆。
坑二:速度折叠。现象是快速目标的径向速度符号反转,或者显示成一个不可能的大速度。原因是目标多普勒超过 PRF/2,速度发生了模糊。FMCW 的 PRF 由 chirp 周期决定,25 kHz 的 PRF 对应 ±24 m/s 的量程,看着够用,但如果你的 Tc 里包含了 idle time 而代码里只用了有效扫频时长,实际 PRF 会偏低,量程就缩水了。解决方式是用两个不同 PRF 做一次解模糊,或者干脆把 Tc 按完整周期算。
5.2 检测与跟踪类的坑
坑三:MTI 把要检测的目标一起消掉了。现象是仿真里目标清清楚楚,实测里人站着不动就消失,只有走起来才出现。原因是一阶对消对消的是零多普勒,而人体躯干在站立时径向速度就在 0 附近,正好被对消掉。解决方式分场景:做微多普勒分析时不做 MTI,改用杂波图或者静杂波抑制后的残差;做检测跟踪时降低对消阶数,或者在对消后保留零多普勒通道单独处理。
坑四:航迹频繁断批、ID 反复跳。现象是目标连续可见但航迹每隔十几帧就重开一次。原因通常是三个叠加:马氏距离门限过小、q 设得太小导致 P 收缩过快、删除帧数太少。解决顺序是先看 P 的迹随时间的变化曲线,如果几帧之内就掉到很小,说明 q 偏小;再检查门限内是否有可用点迹,如果门限外总有匹配点迹,说明 R 估小了或者目标机动超出匀速模型。按这个顺序排查,一般二十分钟能定位。
5.3 时频与评估类的坑
坑五:识别准确率虚高。
本文还有配套的精品资源,点击获取