做雷达信号处理或者做成像算法仿真的人,大概率都碰到过同一个问题:匹配滤波已经把一个点目标压成了尖峰,可尖峰旁边那串旁瓣,反而成了压制弱目标的最大麻烦。强杂波回波幅度一大,它的距离旁瓣就能把附近的弱小目标整个埋掉,这时候你拿普通滤波器滤来滤去,效果往往很尴尬。我这次要讲的,就是用CLEAN算法做杂波抑制的一套Matlab仿真实现。它不依赖统计建模,不靠堆滤波器阶数,而是把强散射体一个一个“挖”出来,再把它在数据里留下的旁瓣烙印抹掉。如果你正在做一维距离像、SAR/ISAR图像处理,或者需要一个适合课程设计、论文复现的直观算法demo,这篇东西应该能给你省不少时间。
这套仿真做完之后,我最大的感受是:CLEAN是个老算法,但老算法不等于过时算法。它用在“强点旁瓣干扰”这类问题上,比很多花哨的现代算法都干净利落。下面我把整个项目的设计思路、仿真细节、代码结构和踩坑记录都拆开讲。
1. 为什么我偏偏选CLEAN来做杂波抑制
1.1 杂波抑制不是简单的高通滤波
很多刚接触雷达信号处理的人,听到“杂波抑制”四个字,第一反应是上高通滤波器、动目标显示MTI或者动目标检测MTD。这些方法确实能对付多普勒频率为零或者接近零的地物杂波,因为它们利用的是“动目标回波有多普勒偏移,静止杂波没有”这个物理差异。但问题在于,有一类杂波并不是时域均匀铺开的大范围回波,而是离散强散射体,比如建筑物边缘、金属塔架、大型船舶的角反射器效应。这些强散射体在距离维上就是一个很高的尖峰,它的旁瓣会直接覆盖周围几米到几十米范围内的弱目标信号。对这种离散强杂波,传统的多普勒滤波基本无效,因为它的多普勒特性和静止地物一模一样,你滤不掉它。
更麻烦的是,脉冲压缩本身是有副作用的。线性调频信号通过匹配滤波之后,点目标响应是一个sinc形状的窄脉冲,主瓣宽度由带宽决定,但第一旁瓣只比主瓣低约13.2 dB。这是什么概念?如果一个强杂波回波幅度是100,那么它第一旁瓣的幅度大约是20多,而一个弱目标回波可能只有1甚至0.5。弱目标完全淹没在强杂波的旁瓣里,你在距离像上看它,就像在满月旁边找一颗星星。只靠加窗函数能把旁瓣压低一些,但代价是主瓣展宽、距离分辨率变差。我实测下来,Hamming窗能把旁瓣压到约-43 dB,但主瓣也展宽了大约1.3到1.5倍。就算这样,100幅度强杂波的旁瓣仍然有0.7左右,和0.5幅度的弱目标依然是一个量级,检测还是困难。
所以,杂波抑制在这个场景下,本质上是“将强散射体在匹配滤波输出中留下的旁瓣污染剥离出去”,而不是简单滤波。
1.2 CLEAN到底在“洗”什么
CLEAN算法最早是射电天文学里的方法,1974年由Högbom提出,用来处理综合孔径成像里的“脏图”。它的核心思路可以用一句话概括:观测数据是真实场景和一个点扩散函数的卷积,那么我就用迭代方式,把真实场景中的强点一一猜出来,再从数据中减去这些强点对应的点扩散函数。
放到雷达脉冲压缩里,这句话可以改写为:匹配滤波输出y(t)是真实目标散射系数分布σ(t)与系统点扩散函数p(t)的卷积,再加上噪声。如果真实场景里只有少数几个强散射点,那卷积的结果就是几个尖峰周围带着一串旁瓣。CLEAN做的事情,就是不断寻找当前输出中幅度最大的位置,把它判定为一个真实散射点,记录它的位置和复振幅,然后以这个位置为中心,把“这个点贡献出来的那套旁瓣花纹”从数据中减掉。每减一次,剩余数据里的最大峰值就会下降一点,直到剩下的是噪声和其他弱目标。
我习惯用一个生活类比解释这个事情:想象一个房间里有人说话,你录到了这段声音,但某个大嗓门的人离麦克风特别近,他的声音把其他人的说话内容全盖住了。CLEAN的思路不是硬调音量,而是把这个大嗓门的声音波形整个抠出来,从录音里减掉。减掉一次还不够干净,就再找下一个最大的,继续减。减完之后,房间里其他人的声音就露出来了。这就是CLEAN做杂波抑制的本质。
1.3 CLEAN的适用边界,新手最容易搞错
我必须先泼一盆冷水:CLEAN不是万能的杂波抑制器,它只适用于“场景可以由少数离散强散射点主导”的情况。如果杂波是海面回波、气象杂波这类连续分布的强起伏信号,CLEAN的效果会很差,甚至会把连续区域碎成一堆虚假点目标。这时候你应该去看MTI、AMTI、STAP或者空时自适应处理,那些算法才是为均匀杂波背景设计的。
CLEAN的优势场景非常清晰:目标稀疏、强散射点集中、点扩散函数已知且稳定。比如雷达一维距离像中的人造金属目标、ISAR成像后飞机或舰船上的强散射中心、SAR图像中建筑物和角反射器造成的旁瓣串扰。这种地方用CLEAN,一是原理直观,二是实现简单,三是参数可解释性很强。我在做这个仿真时,刻意把场景设计成强杂波旁瓣淹没弱目标的状况,就是为了让CLEAN的“每一步在干什么”看得清清楚楚。
2. 仿真场景怎么搭,才能让CLEAN有用武之地
2.1 场景设计思路:强杂波旁瓣必须真的盖住弱目标
很多算法demo失败的共同原因,是场景设计得太温和,算法跑完看不出效果。做CLEAN仿真也是一样。如果你随手设两个目标,幅度差异不大,距离又拉得开,那CLEAN和普通加窗滤波没什么区别,完全展示不出算法的价值。我设计的场景是这样的:存在一个幅度为100的强杂波散射点,位于50米处;两个弱目标分别位于52.5米和57米,幅度分别只有0.8和0.5。强杂波和第一个弱目标之间的距离差只有2.5米,而我们的距离分辨率大概是1.5米,加窗后主瓣宽度约2.1米,所以弱目标刚好落在强杂波第一旁瓣和第二旁瓣之间的区域。这个位置设计非常关键,它让弱目标在脉冲压缩输出里完全被盖住,同时又保留了恢复的可能性。
第二个弱目标放在57米,作为对照。它距离强杂波7米,旁瓣泄漏相对小一些,但仍然受强杂波较高阶旁瓣的影响。这样处理完之后,你可以同时看到两个弱目标不同程度的恢复效果,比单目标演示有说服力得多。
2.2 回波生成与匹配滤波参数
这个仿真我采用线性调频信号,带宽B=100 MHz,脉宽T=10微秒,采样率Fs=200 MHz。距离分辨率由带宽决定,ΔR=c/(2B)≈1.5米。采样率取200 MHz意味着距离采样间隔约为0.75米,刚好在两个可分辨单元之间插入一个采样点,这样旁瓣的凹陷位置能被更精细地刻画,CLEAN减掉的模板也会更接近真实情况。载频我没有过多纠结,只在回波中加入了一个固定的相位项,因为我们的目标是处理脉冲压缩后的基带复信号,不是射频链路仿真。如果你想做完整的雷达链路仿真,可以把中频、混频、IQ解调都加进去,但CLEAN这一层关心的是匹配滤波输出,所以基带建模足够用。
发射信号可以定义为:
Fs = 200e6; B = 100e6; T = 10e-6; K = B / T; % 调频斜率 t = (0 : round(T*Fs)-1) / Fs; % 快时间轴 s_tx = exp(1j * pi * K * t.^2); % 基带LFM信号接收回波则通过将每个散射点的信号延时并叠加来生成:
c = 3e8; r_range = [50, 52.5, 57]; % 强杂波、弱目标1、弱目标2 amp = [100, 0.8, 0.5]; % 幅度 rx = zeros(1, length(t) + 200); for m = 1:length(r_range) tau = 2 * r_range(m) / c; n0 = round(tau * Fs) + 1; rx(n0 : n0 + length(s_tx)-1) = rx(n0 : n0 + length(s_tx)-1) + amp(m) * s_tx; end这里把强杂波定义成幅度100,就对应了前文说的旁瓣压制效果。加Hamming窗的匹配滤波可以用频域快速卷积实现。有一点要提醒:如果用FFT做匹配滤波,一定要把FFT长度设置成大于等于输入信号加参考信号长度减一,否则循环卷积会把尾部泄漏到头部,产生虚假目标。我在代码里加了padding,就是为了避开这个坑。
2.3 为什么必须单独准备一个PSF模板
CLEAN算法的核心输入有两个:一个是待处理的匹配滤波输出,另一个是点扩散函数PSF。PSF就是单位幅度点目标经过同一套脉冲压缩链路之后得到的输出形状。我建议在仿真里显式生成它,而不是直接用理论sinc函数代替。原因很简单:加窗、过采样、FFT长度、边界效应都会影响PSF的实际形状,理论sinc往往不够准确。用系统自身的脉冲响应做模板,CLEAN减的时候才减得干净。
生成PSF的代码其实很简单,就是让一个位于零距离延时的单位冲激通过你的匹配滤波器:
imp = zeros(1, length(t) + 200); imp(1) = 1; [psf_unorm, ~] = matched_filter(imp, s_tx, Nfft); psf = psf_unorm / max(abs(psf_unorm)); % 归一化中心峰值为1这段代码体现了CLEAN实施中一个非常容易被忽略的原则:模板必须归一化,而且归一化系数要固定。因为后续在CLEAN主循环里,你找到某个峰值val后,要在残差中减去的是val乘以psf。如果psf中心峰值不是1,那么减去的幅度就不等于你找到的那个点的贡献幅度,迭代就会发散或者收敛极慢。
匹配滤波函数也要单独封装一下,因为在后面的参数测试里会反复调用。封装之后,你只需要改变窗函数类型或采样率,就能看PSF如何变化,进而影响CLEAN表现。这比把匹配滤波代码重复粘贴几十次好维护得多。
3. CLEAN算法核心实现,代码和细节一起讲
3.1 主循环到底在做什么
CLEAN算法的流程看起来非常短,短到很多人觉得“就这么点东西?”。但它每一步背后都有对应的物理含义。完整流程如下:
第一步,输入初始残差R,它就是匹配滤波后的复距离像。第二步,找到R中幅度最大的位置,记录位置索引idx和复数值val。第三步,把val乘以一个衰减因子alpha,加到模型向量model的idx位置。第四步,以idx为中心,将归一化PSF乘以alpha和val,然后从残差R中减去。第五步,检查当前最大峰值是否低于阈值或者迭代次数是否用尽,如果满足条件就停止;否则回到第二步。第六步,如果要做传统的CLEAN图重构,就把model和一个更窄的clean beam卷积,再加上最终残差,得到恢复后的图像。但在杂波抑制场景里,我通常直接观察model和最终残差,因为model就对应了被清洗出来的离散散射中心。
这个流程里,为什么每次都要乘以alpha?因为实际数据中,峰值位置处的幅度并不完全等于那个点目标的真实幅度,它可能还混着附近目标的部分旁瓣贡献。如果你一次减掉100%,很容易过补偿,在残差里出现一个反向的鬼峰。乘一个0.1到0.5之间的衰减因子,相当于每次只“清掉一部分污染”,让剩余部分在下一轮继续迭代。这个思想看起来保守,但非常稳。
3.2 Matlab实现里的四个关键细节
第一个关键细节是复数域处理。很多初学者会把CLEAN写成只对幅度谱操作,这是错的。匹配滤波输出是复信号,PSF也是复信号,减的时候必须进行复数减法。如果只取模,相位信息就丢了,减出来的残差会出现严重失真。所以代码里我保存的val_c是res(idx)的完整复数值,而不是abs(res(idx))。
第二个关键细节是PSF中心归一化。前面已经说过了,这里再强调一句:归一化基准要统一,一旦PSF标定为峰值1,后续所有减法操作都以这个标定为依据。
第三个关键细节是边界处理。PSF长度可能比残差长度短,但减法时仍可能越界。我在循环里做了索引约束,当传播模板落在残差范围内时才执行减法。这看起来是小事,但如果不处理,跑上一百次迭代后,边界上的误差会积累成很大的假峰。
第四个关键细节是搜索区域的限制。在真实工程里,我们往往不会在整段距离像上无差别搜索最大幅度,因为噪声尖峰也可能被当成目标。合理做法是先做CFAR检测,确定潜在目标范围,然后只在这个范围内执行CLEAN。仿真里为了演示纯净的算法,我用了全范围搜索,但代码中预留了mask变量。如果你想贴近工程,把mask设成检测单元附近的局部区域即可。
主循环代码我给你贴出来:
alpha = 0.3; maxIter = 500; peakThresh = 1e-3 * max(abs(Y)); res = Y; model = zeros(size(Y), 'like', Y); Npsf = length(psf); cp = ceil(Npsf / 2); for k = 1:maxIter [amp, idx] = max(abs(res)); if amp < peakThresh break; end val_c = res(idx); model(idx) = model(idx) + alpha * val_c; shift = idx - cp; startIdx = shift + 1; endIdx = shift + Npsf; lo = max(1, startIdx); hi = min(length(res), endIdx); psfLo = lo - startIdx + 1; psfHi = Npsf - (endIdx - hi); res(lo:hi) = res(lo:hi) - alpha * val_c * psf(psfLo:psfHi).'; end这段代码不是我随手写的,是经过很多轮实验后确定下来的写法。最关键的一点是,减法时psf的截取段和res的截取段必须一一对应,不能错位。我曾经在这里犯过错误,结果每轮迭代都会引入一个固定的位置偏置,最终导致目标位置偏了半个采样点。
3.3 参数选取:alpha、迭代次数和停止阈值
参数选择是CLEAN项目里最影响结果的一环。alpha太小,比如0.05,收敛会很慢,500迭代都不一定把强散射点旁瓣洗干净;alpha太大,比如1.0,一两次迭代确实能把强点抹掉,但也会在目标周围制造出明显的过补鬼峰。我的经验是,如果信噪比比较高、PSF标定很准,alpha取0.2到0.5比较合适;如果数据里噪声较大或者PSF不够准,alpha往0.1以下调,宁可慢一点也要稳。
停止阈值的设置也有讲究。最简单的是用初始峰值乘以一个小比例,比如千分之一,作为阈值。这样做的好处是天然和信号幅度挂钩,换场景不用重新调整。更稳妥的做法是用噪声底估计来设阈值,比如先取残差尾部一段纯噪声区域的幅度,计算均值和标准差,然后把阈值设在均值加3到5倍标准差。这样能避免CLEAN把噪声也当成一个个点目标“清洗”出来。相信我,阈值设得太大,弱目标还没恢复就停了;阈值设得太小,你会看到最终模型里出现几十个幅度很小的假目标,那都是噪声被强行拟合成点散射体的结果。
这里我给一个参数参考表,是我在那个仿真条件下的实测趋势,具体数值会随采样率和窗函数变化:
| 参数 | 取值偏小 | 取值偏大 | 我的建议 |
|---|---|---|---|
| alpha | 收敛极慢,迭代次数不足时效果差 | 过补偿明显,目标周围出现鬼峰 | 0.2~0.5 |
| 迭代上限 | 强点旁瓣未清洗完 | 计算时间长,且可能拟合噪声 | 200~1000 |
| 停止阈值 | 把噪声当成目标 | 弱目标恢复不足 | 初始峰值0.1%~1% |
4. 仿真结果怎么看、怎么评估
4.1 处理前后的波形对比
跑完仿真,第一件事肯定是画图。我会把三个图放在同一张图里:上子是原始匹配滤波输出的幅度谱,中子是CLEAN处理后的残差幅度谱,下子是模型向量model的stem图。原始输出里,50米强杂波处有个大尖峰,52.5米和57米的弱目标根本看不清。处理后的残差里,强杂波尖峰大幅降低,弱目标处出现了明显可辨的峰。model图则更清楚:50米处一个高柱子,52.5米和57米处两个矮柱子。这三个柱子分别对应强杂波和两个弱目标,幅度比例也基本符合输入的0.8和0.5。
图是给人看的,量化的东西还要靠数据。我通常会同时打印出处理前后弱目标位置的幅度值和局部信杂比。在你自己的仿真里,可以先用光标工具直接读图上的峰值,但更推荐写脚本自动定位峰值并输出,不然测几十个参数时会疯掉。
4.2 定量指标怎么算
评估CLEAN效果,我一般看四个量。第一个是弱目标位置的信杂比改善量。定义很简单,处理前,在弱目标所在位置附近一定范围内取最大值,作为信号估计;再取该范围外的最大旁瓣作为杂波估计;两者之比就是信杂比。处理后重新计算,两个值相减,就是改善量。第二个是弱目标幅度估计误差,公式是20log10(|估计幅度-真实幅度|/真实幅度)。这个值能反映CLEAN重构出的弱目标幅度准不准。第三个是残差能量收敛曲线,记录每一轮迭代的残差峰值,画出一条下降曲线。正常的曲线应该是平滑单调下降,如果出现振荡,说明alpha太大或者PSF有问题。第四个是峰值旁瓣比PSLR,输出图中最大旁瓣与主瓣峰值的比值,处理前后对比。CLEAN的典型效果就是把强杂波贡献的旁瓣压到接近噪声底。
有一个重要的实操细节:计算信杂比改善量时,一定要把范围选对。如果范围选得太大,把别的真实目标或者残余旁瓣也算进去了,结果就会失真。我通常以目标位置为中心,取正负一个距离分辨率单元为信号区,正负3到5个距离分辨率单元为杂波区。
4.3 参数敏感性测试
我做完基准结果之后,又花了不少时间跑参数敏感性。这个测试非常值得,因为只有知道参数怎么影响结果,你才敢把算法交给别人用。我测了三组:alpha从0.1变到0.9,阈值从初始峰值的1%变到0.01%,以及PSF中加入幅度和相位误差。结果和我预期的一样:alpha在0.2到0.5之间表现稳定,alpha为0.9时在弱目标附近出现了明显的负值鬼峰,信杂比改善量反而下降;阈值太小导致最终模型多出十来个假峰,位置随机分布;PSF幅度误差对结果影响相对小,但相位误差非常致命,哪怕只有5度,收敛速度也会变慢,旁瓣残余明显变多。
这个测试给我一个很重要的经验:在实际系统里,PSF的相位准确性必须认真对待。如果你要处理的是实测雷达数据,最好用已知强定标体的实测回波去估PSF,而不是用理论公式算完就完事。
5. 实操中的坑与排查笔记
5.1 收敛慢或者永远收敛不到阈值
我遇到过的最常见问题,是CLEAN跑了几百轮还达不到停止阈值,或者残差峰值下降幅度很小。排查顺序基本是:先看PSF中心是否归一化为1,再看减法是否在复数域进行,再看alpha是否取得太小。我最初犯过的错误是在构造psf时忘了归一化,结果每一次减法都把目标幅度缩水一截,迭代虽然能跑,但旁瓣始终减不干净,弱目标恢复得也不对。如果你确认这些都正常,那可能是目标距离太近,多个PSF主瓣互相重叠,这种情况下CLEAN的分辨率极限天然受限,可以在更细的采样网格上运行算法再试试。
另一个容易忽视的点是FFT匹配滤波的padding长度。如果Nfft选得太小,循环卷积会把尾部信号卷到头部,产生的“鬼目标”会干扰CLEAN的峰值搜索。我调试时习惯先把单目标场景跑一遍,如果CLEAN结果里出现对称位置的一对假目标,立刻去检查padding。
5.2 弱目标恢复后位置偏了或幅度不对
就算能收敛,也可能出现弱目标位置和真实位置差了一两个采样点、幅度差几个dB的情况。这多半是过补偿造成的。alpha太大时,减掉的强点模板会连累邻近的弱目标,因为CLEAN在做减法时用的是整体PSF,它不会区分这个PSF的主瓣和旁瓣分别覆盖了哪些相邻目标。如果弱目标距离强点太近,强点的主瓣本身就是覆盖弱目标的,你怎么减都会带走一部分弱目标的能量。这种情况不是CLEAN失效,而是目标本身就落在别人家里了,处理完之后的幅度估计就会偏低。
解决办法有两个方向。一是降低alpha,让每次减掉的比例小一点,减小连带损失。二是对最终模型做幅度校正,比如根据目标位置和PSF相对关系,用最小二乘估计真实散射系数。在仿真里,我更推荐直接调小目标间距的测试,先把CLEAN的能力边界摸清楚,再谈精细校正。
5.3 从一维距离像扩展到二维图像
CLEAN绝不只能做一维,ISAR和SAR图像里的旁瓣抑制同样常用。只是扩展成二维之后,有几个额外的坑。一个是PSF变成二维矩阵,峰值搜索变成二维峰值检测;另一个是每次减法不再是更新一段向量,而是更新一个局部二维窗。如果还是照着二维全图卷积去做减法,计算量会大得离谱。
我的建议是先在一维环境里把这个算法吃透,再扩展到二维。二维CLEAN的代码结构和一维几乎一样,区别在于找峰函数从max(abs(res))变成max(max(abs(res))),减法模板从一维psf变成二维psfmatrix,循环内部改成二维矩阵切片更新。只要一维版本的成功经验和参数调整方法掌握了,二维只是时间问题。
6. 一些绕不开的真实体会
仿真做完,我最想叮嘱的就一句话:CLEAN算法看起来简单,但它极其依赖“模型和真实数据匹配”这个前提。PSF标得准、场景稀疏度高、目标以强点为主,CLEAN就是一把又快又稳的剔骨刀。反过来,如果场景不满足稀疏性,PSF又带有未知误差,再漂亮的迭代公式也救不回来。
我自己在这个项目里最大的收获,倒不是把弱目标从强杂波旁瓣里挖了出来,而是养成了“先构造可解释的场景,再跑算法”的习惯。仿真不是为了把结果跑得好看,而是为了让你理解每个参数在物理上到底代表什么。你把这个逻辑捋顺了,后面不管换到二维成像还是实测数据处理,心里都会很有底。
另外,Matlab调试时,别只盯着最终图看。花点时间把每一轮迭代的res和model都存下来,连续播放几帧,你会直观看到强杂波旁瓣是怎么一层层被剥掉的。这个画面比任何指标都能帮你理解CLEAN。