
简介基于CLEAN算法的雷达频域杂波抑制仿真源码面向雷达信号处理初学者与工程验证场景。程序针对FMCW波形回波信号设计通过频域相减实现杂波滤除描述中提供cos函数相减实现并支持改为指数形式以适配复数运算便于移植到不同雷达信号模型。资源本身十分精简压缩包内仅1个m脚本总大小约1KB参数模块化程度较高修改关键变量即可复用至其他频域杂波抑制任务。仿真基于真实雷达回波数据的处理思路搭建相比纯理论演示更具工程参考价值也有助于深入理解CLEAN算法在频域处理中的完整流程。目前已有897人学习下载体现该小型实现具备一定参考和复用价值。通过该代码可直接运行验证效果快速搭建自己的杂波抑制仿真环境为后续算法研究与参数调优提供起点。1. CLEAN算法拆解一份能直接跑的雷达杂波抑制仿真做雷达信号处理的人十有八九都被“强杂波里捞弱目标”这件事磨过脾气。频域里目标回波和杂波叠在一起滤波挖太深怕伤目标挖太浅杂波又压不下去。这次拆的这份CLEAN算法源码包解决的就是这个痛点——它在频域做迭代消减把杂波分量一步步“抠”干净非常适合FMCW雷达回波数据的杂波抑制仿真。项目入口明确核心就是main.m默认用cos()函数做相减运算工程注释里也给了改指数形式的路子想接复数信号改一行就能用。适合正在做雷达信号仿真、或者被静态杂波困扰想快速验证CLEAN效果的从业者拿来改参数跑通流程比从零造轮子快得多。CLEAN算法的思路不复杂把信号变换到频域找到幅度最大的杂波分量估计它的位置和强度然后用一个“清理模板”把它减掉反复迭代直到剩余能量低于阈值。这个源码包把这套流程完整落地了而且针对的是真实雷达回波的场景不是理想正弦波拼出来的假数据。下面就从原理、代码到踩坑一步步说清楚。2. 频域杂波为什么难搞CLEAN的数学直觉与适用边界2.1 从时域相减到频域迭代看CLEAN怎么“刮”杂波雷达回波里的杂波尤其是地杂波、海杂波这类静止或慢速目标能量往往比感兴趣的目标回波大几个数量级。时域里它们混在一起直接相减需要精确的杂波参考信号这在工程里很难拿到。频域处理则换了个思路杂波通常在特定频点附近集中比如静止杂波在零频附近有很强的尖峰FMCW雷达的差频信号里静止目标的差频固定对应频谱上的离散谱线。CLEAN算法在频域做的事情可以理解成“刮土豆皮”——每次找到最大的一颗杂波“土豆”把它刮掉看剩下的再找下一颗。数学上假设接收信号的频谱为(S(f))杂波是若干强谱线叠加目标信号是散布的较弱谱线。算法迭代执行三个步骤第一步在频谱中找到幅度最大的频点(f_{peak})及其幅值(A_{peak})第二步用窗函数源码里是cos()形式构造一个以该频点为中心的模板乘上幅值后从原频谱中减去第三步记录这个被“刮掉”的分量更新剩余频谱继续迭代。当剩余频谱的最大幅值低于设定的阈值门限时停止。这套流程的好处是自适应性强不需要预先知道杂波有多少根谱线只要能量够大就会被逐个找出来。缺点也明显如果目标信号本身很强在频域里也形成了明显的谱峰CLEAN可能会把目标当作杂波一起刮掉。所以这个源码包默认针对的是“杂波远强于目标”的场景这正是雷达远端小目标检测最常遇到的情况。用代码来模拟这个思路核心迭代过程大致是这样的% 清理主循环 - 从频谱中迭代扣除杂波分量 residual spec; % spec为接收信号的频域表示 cleaned zeros(size(spec)); % 存放被刮掉的杂波分量 for iter 1:max_iter [~, idx] max(abs(residual)); % 找剩余频谱中幅度最大的频点 peak_amp residual(idx); % 记录该点复幅度 if abs(peak_amp) threshold % 低于门限则停止 break; end window gen_clean_window(N, idx, win_width); % 生成清理模板 residual residual - peak_amp * window; % 相减消减 cleaned cleaned peak_amp * window; % 累积已清理部分 endmax(abs(residual))这一步是整个算法的引擎——它决定了每一次迭代“打哪颗土豆”。gen_clean_window生成的是cos()形状的清理模板模板宽度win_width控制了每次消减的影响范围。这里有个关键细节源码用的相减函数是cos()意味着清理模板是实数的余弦形状如果你的雷达回波是复数形式I/Q双通道模板也应该改成复指数源码注释里提到改成指数形式就行后面会具体讲怎么改。2.2 为什么不用高通滤波或MTI三个方案放在一起看很多初学者会问杂波集中在低频高通滤波不就行了吗确实静止杂波在零频附近高通或者带阻滤波能直接挖掉这一段。但雷达信号处理里有个要命的权衡滤波器的过渡带越陡群延迟越大通带内的相位失真越严重。FMCW雷达的差频频率直接对应目标距离相位失真意味着距离估计偏差。CLEAN算法没有设计固定滤波器它是自适应的只刮掉实际存在的强谱线对目标所在的频点不设“禁飞区”在密集谱线场景下比固定陷波更安全。MTI动目标显示是另一种经典方案通过对消器两脉冲相减、三脉冲相减把静止杂波对消掉。但MTI的盲速问题是硬伤——当目标径向速度恰好使回波相位差为2π的整数倍时目标会和对消后的杂波一起消失。CLEAN算法本质上不做速度滤波它只处理频域幅度没有盲速概念对慢速目标的保留能力比MTI强。放一张对比表看得更清楚方法原理优势硬伤高通/带阻滤波固定频段挖除简单粗暴计算量小过渡带失真目标近杂波时误伤MTI对消相邻脉冲相减对静止杂波抑制好盲速效应慢目标丢失CLEAN迭代消减逐根谱线估计并扣除自适应无盲速可保留弱目标强目标可能被误刮需设门限从这张表能看出来CLEAN的位置是“自适应陷波器”和“谱估计器”之间的折中。它不关心目标的物理含义只关心频谱里“哪个东西能量最大”然后把它拿掉。这种“无情”在雷达杂波场景下反而是优点——杂波就是能量大目标就是能量小按能量排序逐个清理逻辑朴素但有效。2.3 适用场景与选型判断什么情况下别用CLEAN源码包虽然好用但边界要清楚。CLEAN适合的场景有三类一是强地杂波背景下的慢目标检测比如岸基雷达看海面慢速小船二是射频干扰残留的频点抑制干扰在频谱上呈现窄带尖峰CLEAN可以逐个刮掉三是多目标谱线分离前的预处理先用CLEAN把杂波清掉再用CFAR去提目标。不适合的场景也有三类第一目标信号本身就是扫频宽带信号能量散布在整个频带内没有明显谱峰CLEAN找不到“最大杂波”也就无从下手第二杂波和目标频谱完全重叠且幅度差不多算法会交替误刮收敛不到正确结果第三实时性要求极高的场景CLEAN的迭代次数不确定最坏情况下延迟不可控不适合直接嵌入到每毫秒一帧的实时处理链里。这个源码包定位是仿真验证改参数跑数据不是拿来直接上DSP的。3. main.m逐段拆解从数据加载到清理输出的完整链路3.1 代码骨架总览文件里到底有什么源码包打开后核心就一个main.m其余是辅助函数。先看总体结构main.m分四个阶段生成或加载雷达回波数据、做FFT变换到频域、调用CLEAN迭代函数清杂波、把清理前后的频谱和时域波形画出来对比。整体没有复杂的外部依赖只要MATLAB基础安装就能跑。先看数据准备这一段源码用的是模拟真实雷达回波的方式构造数据%% 1. 生成模拟雷达回波信号带杂波 fs 10e6; % 采样率 10MHz t 0:1/fs:1e-3; % 观测时间 1ms % 目标回波单一频点正弦波模拟FMCW差频 f_target 50e3; % 目标差频 50kHz对应某距离 target 0.5 * exp(1j*2*pi*f_target*t); % 复数形式目标回波 % 杂波多个强频点叠加模拟地物杂波 f_clutter [2e3, -3e3, 8e3]; % 杂波频点分布 clutter zeros(size(t)); for k 1:length(f_clutter) clutter clutter 3.0 * exp(1j*2*pi*f_clutter(k)*t); end % 接收信号 目标 杂波 噪声 rx target clutter 0.05*randn(size(t));这里的参数设置很有讲究。fs10MHz采样率对FMCW雷达仿真来说比较常规观测时间1ms对应频率分辨率1kHz1/T所以f_clutter设置2kHz、-3kHz、8kHz这些值在频域正好是分开的谱线。目标差频50kHz远离杂波聚集区方便肉眼验证CLEAN是不是把目标留住了。杂波幅度3.0对比目标的0.5信杂比大约-15dB模拟了“杂波压目标”的恶劣场景。最后一个0.05的噪声项是为了让频谱有底噪避免算法在完全干净的数据上“作弊”。这里要提醒一个重要习惯复数形式的目标和杂波用了exp(1j*...)这为后面的频域处理做好了准备。源码里相减函数是cos()对实数信号没有区别但复数信号必须对应复指数后面会细说。3.2 频域变换与CLEAN核心调用数据准备好了下一步是变换到频域并调用清理函数。这一段是整个流程的技术核心每个参数都直接影响清理效果%% 2. 变换到频域 N length(t); spec fftshift(fft(rx)); % 移到零频居中 f_axis (-N/2:N/2-1) * (fs/N); % 频率轴单位Hz %% 3. 调用CLEAN迭代清理 max_iter 100; % 最大迭代次数 threshold 0.2; % 剩余谱峰幅度门限 win_width 5; % 清理窗半宽点数 [spec_clean, removed] clean_spectrum(spec, max_iter, threshold, win_width); %% 4. 恢复清理后的时域信号 rx_clean ifft(ifftshift(spec_clean));fftshift这一步很多人会忽略但很关键。MATLAB的fft输出是0到fs的自然顺序直流分量在第一个点如果不做fftshift正负频率会颠倒你看到的目标频率位置和f_axis对不上。clean_spectrum是核心函数输入原始频谱输出清理后的频谱和removed分量——如果想看杂波长什么样画removed就行。threshold0.2的物理意义当剩余频谱中的最大幅度低于0.2时停止迭代。这个值决定了“清得多干净”。杂波幅度是3.0目标幅度是0.5噪声底大约0.050.2的门限意味着杂波全部刮净但目标0.5 0.2已经不会被刮掉。这就是典型的工程折中——门限设太低会把噪声也当杂波刮掉设置太高则杂波残留多。win_width5表示清理模板的半宽是5个频点也就是±5个频率分辨单元的范围。FMCW里5个频点对应5kHz分辨率1kHz这个宽度要跟杂波谱线在频域的实际展宽匹配展宽通常由窗函数的泄漏决定。clean_spectrum函数的内部实现就是前面讲的迭代三步function [spec_clean, removed] clean_spectrum(spec, max_iter, threshold, win_width) spec_clean spec; removed zeros(size(spec)); for iter 1:max_iter [~, idx] max(abs(spec_clean)); if abs(spec_clean(idx)) threshold fprintf(迭代 %d 次后收敛剩余峰值 %.4f 低于门限 %.4f\n, ... iter-1, abs(spec_clean(idx)), threshold); break; end % 构造cos窗形式的清理模板 n length(spec_clean); win zeros(1, n); range max(1, idx-win_width) : min(n, idxwin_width); win(range) 0.5 * (1 cos(2*pi*(0:length(range)-1)/(2*win_width))); % 减去当前峰值的贡献 peak_amp spec_clean(idx); spec_clean spec_clean - peak_amp * win; removed removed peak_amp * win; end end这个cos窗的构造方式是“汉宁窗”的形状中间为1两端为0平滑地过渡而不是生硬地把一根谱线直接清零。好处是减少频谱泄漏带来的“振铃”坏处是每次清理会稍微影响到该峰值附近的弱小谱线——如果目标恰好在这5个频点之内就会被“误伤”一点。win_width越小定位越准但对泄漏的抑制越差越大则相反。这是一个需要根据实际频谱展宽来调的核心参数。3.3 可视化对比怎么确认清理效果源码里最后一步是画图对比这步看着简单却是验证算法有没有“刮错”的关键手段%% 5. 画图对比 figure; subplot(3,1,1); plot(f_axis/1e3, abs(spec)); grid on; xlabel(频率 (kHz)); ylabel(幅度); title(原始回波频谱杂波谱线清晰可见目标被掩盖); subplot(3,1,2); plot(f_axis/1e3, abs(spec_clean)); grid on; xlabel(频率 (kHz)); ylabel(幅度); title(CLEAN清理后频谱杂波消除目标显露); subplot(3,1,3); plot(f_axis/1e3, abs(removed)); grid on; xlabel(频率 (kHz)); ylabel(幅度); title(被清理的杂波分量确认刮掉的是什么);从上三图中应该能看到原始频谱在2kHz、-3kHz、8kHz处有三根很高的杂波谱线50kHz处的目标谱线矮小清理后的频谱里三根杂波线几乎消失50kHz处的目标变得明显被清理的杂波谱线上只有那三根线有能量。如果发现removed里混入了50kHz的成分就说明win_width太大或者threshold太低刮过界了。这个可视化是调试的第一道关三张图一看就知道算法干了什么比盯着数字判断效率高得多。4. 把仿真改到你的雷达数据上参数怎么调、模板怎么改4.1 真实雷达回波接入的五个改动点源码自带的是模拟数据拿真实雷达回波来跑需要改五个地方。第一数据加载方式要换——把rx target clutter noise这行替换成rx load(radar_data.mat).raw_data或者rx read_complex_binary(iq_data.bin)真实采集的FMCW回波通常是I/Q两路复数正好匹配源码里的复数信号格式。第二fs要改成你雷达的采样率这个参数直接决定f_axis对不对。第三观测时间T决定了距离分辨率如果采集的chirp时长和源码的1ms不同脉冲压缩后的距离分辨率会变。第四最重要的一步是确认你的频率轴单位。FMCW雷达中差频频率和目标距离的关系是(f 2 \cdot R \cdot B / (T_c \cdot c))其中B是扫频带宽Tc是chirp时长c是光速。如果你的雷达B200MHz、Tc1ms那么1kHz差频对应(R f \cdot c \cdot T_c / (2B) 1000 \cdot 3e8 \cdot 1e-3 / (4e8) 0.75m)。源码里的f_target50kHz就对应37.5m距离。搞清楚这层换算关系才能把仿真里的“频点”对应到真实世界的“目标距离”。第五CLEAN的threshold要按实际频谱的噪声底来标定。采集一段没有目标的“纯杂波噪声”数据统计频谱幅度的均值和方差threshold一般设成均值加3到5倍标准差。这个“标定”做法不是玄学而是CFAR检测里常用的恒虚警思想直接挪过来用即可。4.2 cos()模板改指数形式复数雷达信号的必改项源码注释里特意提到了相减函数是cos()如果需要复数形式改成指数就行。这句话在真实雷达数据里几乎必然遇到因为FMCW雷达的I/Q解调输出天然是复数信号。为什么必须改看清理模板的本质——它要对某一个频点上的复幅度做“相减”操作。如果模板是cos()实数窗那减掉的峰值相位永远是0度方向的分量而复数频谱里每个频点都有实部和虚部比如一个纯虚数的杂波峰值相位90度cos窗根本减不干净。正确的做法是把清理模板改成复指数窗% 构造复指数形式的清理模板 range max(1, idx-win_width) : min(n, idxwin_width); phase exp(1j*angle(spec_clean(idx))); % 取当前峰值的相位 win_real 0.5 * (1 cos(2*pi*(0:length(range)-1)/(2*win_width))); win phase * win_real; % 实数形状 * 复数相位这个改动本质上给模板加上了“方向性”——不仅刮掉幅度还按照杂波的实际相位方向去刮保证幅度和相位都准确对消。如果坚持用cos()模板处理复数数据最常见的现象是杂波峰值只减了一半残留了一个同样位置的半高谱峰迭代会在这个位置反复循环直到触发max_iter上限然后CLEAN就“翻车”了。这个坑我踩过当时盯着频谱看半天没想明白为什么杂波消不掉后来意识到是相位没对上改成复指数模板后一次通过。4.3 max_iter和win_width的调整策略从经验值开始的收敛路线max_iter100看起来够用但真实数据里杂波谱线的数量可能远超模拟值尤其是频谱泄漏严重的场景。经验做法是先跑一遍看MATLAB命令行打印的迭代次数如果迭代到了100次还没收敛说明杂波谱线太多或门限太低优先提高门限而不是提高迭代上限。迭代次数和计算量的关系是线性的每次迭代一次FFT规模的复数乘加运算100次迭代对现代CPU来说微秒级完成但实时系统里不能这么玩。win_width的调整更讲究。频谱泄漏的展宽通常和FFT窗函数有关如果你在FFT前加了汉明窗主瓣宽度就比矩形窗宽一倍win_width要相应加大。一个实用的标定方法是找一根已知的强杂波谱线看它的-3dB宽度是多少个频点把win_width设成这个宽度。还有如果多个杂波频点靠得很近比如间隔只有3个频点win_width5会导致两个清理模板相互重叠算法会交替在两个位置反复刮导致收敛慢。这时候应该把win_width调小到2或3宁可每次刮不干净让迭代多做几次也不能让模板打架。5. 避坑指南CLEAN算法仿真的五个经典翻车现场5.1 频点索引错位导致刮错位置现象清理后的removed频谱里出现了目标信号而杂波反而没被刮掉目标被“误杀”了。原因fftshift和ifftshift使用不对称。spec是fftshift后的频谱频率从负到正排列但清理模板的win是按照数组索引生成的范围是idx-win_width到idxwin_width。如果你的f_axis没有和spec对齐或者win_width超过了正负频率交界处模板会覆盖到错误的位置。最典型的错误是忘了做fftshift直接用fft的原始输出这时候直流分量在第一个点正频率在左半负频率在右半win_width覆盖的“附近”频点根本不是真实频率意义上的附近。解决每次都把f_axis和spec放在同一个段落里赋值确保二者经过同一套fftshift处理。改参数时先画一下f_axis和谱线位置确认目标在50kHz对应数组的哪个索引再决定win_width能不能覆盖到它而不会误伤。5.2 threshold设太低把噪声底也刮了现象清理后的频谱几乎和原始频谱一样干净连噪声底都被削平了但目标的幅度也明显下降。原因threshold低于噪声底。源码里的噪声幅度约0.05threshold设成0.2在后验上没问题但如果噪声更强比如你的数据信噪比低噪声底的幅度涨到0.5threshold0.2会导致算法把噪声谱峰也当作杂波来刮直到迭代次数耗尽。每次刮的峰值幅度可能小于噪音底但多次迭代累积起来把目标周围频谱搞乱。解决先用一段纯噪数据测出mean(abs(spec_noise))3*std(abs(spec_noise))把threshold设成这个值以上。如果你设置的门限值低于噪声底算法永远不会收敛每次都会找到一个“噪声峰”来刮直到max_iter上限。5.3 cos()模板用于复数数据杂波减不干净现象迭代进行了几十次max_iter快耗尽杂波峰值仍然明显存在重建的removed有锯齿状毛刺。原因没改指数模板。前面说过的相位问题实数cos窗只能减去杂波峰值在实轴上的投影虚部残留导致同频点反复出现“半减”现象。每次迭代cut掉一部分下一次又从残余中找到一个峰刮掉一部分模值不断减少但永远不完全归零。解决按4.2节的方案改成phase * win_real的复指数模板。改完之后观察迭代次数通常三根杂波谱线在三到五次迭代内就能全部清完命令行直接打印收敛信息。5.4 win_width取值过大把邻近目标刮掉现象清理后的频谱在目标频点处出现凹陷目标的幅度从0.5降到0.2甚至更低距离像上目标“变淡”了。原因win_width太大模板影响范围覆盖到了目标频点。假设目标在50kHz杂波在48kHz间隔2kHz即两个频点win_width5的模板从43到53kHz把目标完全包含进去了刮杂波时同时把目标削了一刀。解决查看相邻频点间距win_width必须小于杂波到目标的最小间距。如果间距紧张把win_width调小到1或2或者改用更陡峭的模板形状比如把cos()改成更高阶的窗函数减少对邻域的“污染”。底线原则是目标被刮掉的幅度不能超过CLEAN带来的杂波改善否则算法得不偿失。5.5 多普勒模糊与差频混淆导致清理结果“看着对但实际错”现象清理后的频谱和模拟时完全一致目标峰清晰但拿到真实雷达上一验证目标距离完全不对。原因FMCW的差频混淆。如果雷达是中短程FMCW差频信号里存在距离-速度耦合静止杂波的差频不代表杂波的真实距离而运动目标的差频里既有距离项又有速度项。CLEAN算法在频域刮掉的“杂波谱线”可能含有运动目标的镜像分量。解决先用非相参积累或者匹配滤波把距离-速度解耦再做CLEAN。仿真里所有信号都是单频点、无调制、无多普勒所以不存在这个问题但真雷达数据里不能直接套先把数据预处理成“距离维”再频域清理。这条是经验里最贵的一次教训——当时数据全流程跑通外场一验证差了一百多米回去查才知道是漏了多普勒项。6. 进阶验证技巧从“图上看不错”到“指标能过关”6.1 用改善因子定量评估CLEAN效果画图看频谱是主观判断落实到工程验收就要有数字指标。雷达信号处理里有个现成的评价参数叫改善因子Improvement Factor定义是输出信杂比除以输入信杂比。具体到CLEAN仿真的验证流程是先用清理前频谱的abs(spec)算杂波功率和目标功率的比值得到输入信杂比SCR_in再用清理后频谱算SCR_out改善因子就是两者之比用dB表示。在MATLAB里可以这样写% 计算CLEAN改善因子 target_bin find(abs(f_axis - f_target) 500); % 目标附近频点 clutter_bins find(abs(f_axis) 15e3); % 杂波聚集频段 SCR_in 10*log10(mean(abs(spec(target_bin)).^2) / ... mean(abs(spec(clutter_bins)).^2)); SCR_out 10*log10(mean(abs(spec_clean(target_bin)).^2) / ... mean(abs(spec_clean(clutter_bins)).^2)); improvement SCR_out - SCR_in; fprintf(输入信杂比 %.2f dB → 输出信杂比 %.2f dB → 改善 %.2f dB\n, ... SCR_in, SCR_out, improvement);注意目标bin的范围要窄就用目标频点周围±500Hz杂波bin取低频±15kHz。改善因子大于20dB可以认为CLEAN效果良好10-20dB说明杂波压制不够要查是不是win_width小了或者迭代次数不够。这样就把“图上看不错”变成了“数字能过关”写报告和做验收都有底气。6.2 针对你自己的数据做一键验证脚本实战中我习惯把上面这些验证逻辑打包成一个独立脚本跑在main.m后面。脚本做三件事输出改善因子、画清理前后对比频谱、打出一张“杂波被清理频点分布表”。这样每改一次参数自动生成一份验证记录方便回溯哪些参数组合效果好。具体做法是写一个verify_clean_result(spec, spec_clean, f_axis, f_target)函数把常用到的指标计算和画图逻辑封装进去。从那以后我每次调CLEAN参数都强制走一遍这个过程先跑基线数据确认改善因子过20dB再改参数对比最后看分布表确认没有误刮目标。“图上看不错”只能说明定性没问题改善因子才是能写进报告的数字——希望帮到你。本文还有配套的精品资源点击获取