☰
SADA动态采样统一修复雨痕、条带与摩尔纹
2026/10/30 4:56:21 网站建设 项目流程

处理一批手机翻拍屏幕的照片时,我第一次真切体会到“三类退化混在一起”有多难搞:画面里既有窗外的雨丝斜掠,又有室内灯管拍摄产生的横向条带,还有屏幕像素与相机采样错位打出来的彩色网格纹。当时的做法是先上条带抑制,再跑摩尔纹分离,最后接一个去雨网络,三步串下来不但速度感人,更麻烦的是每一步的输出都成了下一步的输入噪声——去条带把细密的雨丝一起削平了,去摩尔纹的频域陷波又在雨痕边缘制造了振铃。这套链路换了三四个开源权重都没救回来,问题不在某个模型弱,而在于它们是各修各的,没人管另外两种退化长什么样。

CVPR 2025 上出现的 SADA 就是把这件事换个思路做:不再在频域里做减法,而是让网络自己去决定“从哪几个像素取数”,用动态采样的几何自由度,一次性覆盖雨痕、条带、摩尔纹这三种在方向和周期上完全错位的退化。这篇内容适合两类人看:一类是想把多退化修复塞进一个模型、但被任务冲突折磨过的算法同学;另一类是准备复现类似结构、需要知道合成数据怎么造、采样偏移为什么会塌缩、推理部署会卡在哪的工程同学。下面我按“为什么这么设计—结构怎么拆—数据怎么造—训练怎么调—坑怎么排—怎么上线”的顺序,把这条路的关节一个一个说清楚。

1. 三类退化凑一张桌子:为什么“一个模型修全部”听着就像给自己挖坑

1.1 雨痕、条带、摩尔纹在几何与频域上的三重错位

要把三种退化放进一个网络,先得接受一个事实:它们的统计特性几乎不共享任何先验。雨痕是稀疏、细长、方向随机的高频结构,宽度通常一到三个像素,长度能拉到几十像素,能量集中在图像梯度突变的边缘上。条带是全局、平行、方向基本固定、周期很短的准周期噪声,很多场景下周期只有两到八像素,而且亮度是缓慢起伏的,低频成分占了大头。摩尔纹则是两套周期结构相互干涉产生的拍频纹理,周期随屏幕像素间距和拍摄距离变化,同一个画面里不同区域的周期可能完全不一样。

我把三者的差异整理成一张对照表,做方案设计时基本靠它判断“统一框架有没有戏”:

退化类型空间形态频域特征主导尺度传统处理手段
雨痕细长定向条纹,方向随机高频、强方向性宽 1-3 px,长 10-50 px层分离、方向滤波、深度去雨
条带全局平行细线,方向固定低中频、固定周期强峰周期 2-8 px频域陷波、小波分解
摩尔纹拍频网格,随区域变化中高频、双峰或多峰、非局部周期 3-15 px多尺度分离、频域掩码

看这张表就能明白,靠单一固定的空间核或者单一的频域掩码去覆盖三种形态,等于让一把固定尺寸的扳手去拧三种不同规格的螺丝。频域陷波对条带确实有效,可它的副作用是把同频段的所有内容一起拿掉,摩尔纹的多峰结构就很容易被误伤;方向滤波对雨痕有帮助,但条带本身也是方向性结构,两者会被混淆;多尺度金字塔能兼顾尺度差异,却没法处理“同尺度下方向不同”的情况。

1.2 静态卷积核的天花板到底卡在哪儿

标准 3×3 或 5×5 卷积的问题不在于感受野小,而在于采样位置是写死的。一个训练好的 3×3 核,它永远只会看中心像素和周围八邻域,学到的只是“这九个固定位置的值应该怎么加权组合”。想让它表达“沿这条雨丝的长轴方向多取几个点”,做不到;想让它表达“跨过条带的一个完整周期去取样,实现采样级陷波”,也做不到。

有人会说,堆叠足够多层的卷积,等效感受野迟早能覆盖这些尺度。这话对了一半。深层网络确实能间接“看到”远处,但代价是特征在传递过程中已经被反复混合过了。对于需要精确几何对齐的操作——比如跨周期取样做差分以抵消条带,或者在雨丝的两个端点取点来判断走向——间接看到和直接取到完全是两码事。这也是为什么很多去雨网络在细长雨痕上会出现“修一半留一半”,在网络视野里雨丝已经被空间混叠糊成了一团。

1.3 动态采样这条老路怎么又被翻出来了

可变形卷积(Deformable Conv)这个概念其实不新,但早期版本有几个要命的问题:偏移量只预测一次,所有通道共享,缺乏内容自适应;偏移的监督信号弱,训练时容易退化回普通卷积;参数量和访存开销上去了,精度提升却不明显。所以它一直是个“听起来很美”的模块,真正在工业链路里落地的少。

SADA 值得单独讲,是因为它把动态采样和“退化类型感知”这件事绑在了一起。名字里的 SADA,我按模块实际行为把它理解成Spatial-Adaptive Dynamic Aggregation(空间自适应动态聚合)——这个解读是我从结构设计反推的,不是官方定义,但顺着这个思路拆代码最省事。它做的关键改动是:偏移量不再由一层卷积草草预测,而是先经过一个轻量的退化感知分支,再逐窗口生成 K 个采样点的坐标偏移和调制权重,让“去哪里取数”这件事本身携带对退化类型的判断。至于具体每个模块的超参配置,不同实现差异不小,我这里给的是按常见实践推出来的一套自洽方案,实操时你得按自己的数据分布去改。

2. SADA 的采样逻辑拆解:网络到底怎么决定“去哪几个像素取数”

2.1 偏移预测分支吃什么、吐什么

先把这个分支的输入输出说透,不然看结构图很容易懵。输入是主干网络下采样到 1/4 分辨率的特征图,通道数一般压到 32 或 64,太厚了偏移预测的算力占比会很难看。分支本身通常是三层:一层 1×1 卷积做通道压缩,一层 depthwise 3×3 做局部上下文聚合,最后接一个线性层输出偏移和权重两种量。

输出的量有两组。第一组是采样偏移 $(dx_k, dy_k)$,k 从 1 到 K,K 是每个窗口的采样点数,实际配置里常见 9 或 13。这些偏移是在归一化坐标下表达的,数值范围大致限制在 $[-2, 2]$ 之间,超过这个范围再往外采就纯属噪声,还会引入边界填充的假信息。第二组是调制权重 $w_k$,经过 sigmoid 压到 0 到 1 之间,作用是给每个采样点一个内容相关的软门控,避免所有点不分青红皂白全都进来。

这里有个我踩过的细节:偏移量的初始化。如果偏移预测层的最后一层权重用默认的高斯初始化,训练初期输出基本在零附近徘徊,整个模块等价于普通 3×3 卷积,网络会沿着这条好走的路一路滑下去,再想拉回来就难了。我后来的做法是把最后一层权重初始化为零,同时把采样的基准位置设成一个固定的空洞网格,这样初始状态是个标准的空洞卷积,梯度有了明确的改进方向,采样点分布也更容易散开。

2.2 可变形聚合与普通卷积的算量账

很多人第一反应是“动态采样这么花哨,肯定慢”。这个账得算细一点。K=9 的情况下,可变形聚合每个输出点要做 9 次双线性插值,每次插值 4 次乘加,也就是 36 次乘加,普通 3×3 卷积是 9 次乘加。理论乘加量差了四倍,但真正的瓶颈不在乘加,在访存。

普通卷积的采样地址是连续的,缓存行命中率高;动态采样的地址是散乱的,九个采样点可能分布在完全不同的缓存行里,GPU 上体现为大量非合并访存。我用一套 1080p 输入实测下来,可变形聚合层的耗时大约是同等通道 3×3 卷积的 2.2 到 2.8 倍,比理论值好一些,但也没好到可以忽略。所以在结构设计上,SADA 这类方案不会把动态采样铺满整个主干,通常只在编码器的中段和瓶颈层放两到三个模块,浅层和深层都保持普通卷积。浅层做动态采样收益低,那里还都是边缘和纹理;深层做代价太高,特征图小了但通道厚,访存更不友好。

2.3 三种退化怎么被同一套采样参数吃下来

这才是最关键的机制。动态采样之所以能一网打尽,是因为采样偏移本身是个几何先验的载体,不同退化需要的是不同形状的采样图案,而这三套图案可以共存于同一个 K 点集合里。

去雨痕要的是各向异性拉长。雨丝是细长定向的,想判断某个像素是不是雨,得沿着雨丝长轴方向找它是不是也属于同一条线,同时横向只采极少的点以避免把背景纹理拉进判断。学出来的偏移图案会呈现明显的一维拉伸,三到五个点沿同一方向排开,横向点稀疏。

治条带要的是跨周期取样。条带是准周期的,周期内相邻像素差异大,差一个完整周期之后内容是相近的。所以有效的采样图案是沿垂直于条带方向、按周期整数倍跳点,这样取到的几个值一平均,周期性成分自己就被抵掉了,本质上是在采样阶段完成了一次陷波,而不是在频域做减法,副作用小得多。

处理摩尔纹要的是多相采样。摩尔纹的周期在不同区域不一样,单个固定偏移采样图案没法覆盖,所以需要 K 个点分布在多个不同的空间相位上,形成类似多相滤波的结构。学出来的效果是偏移量在不同窗口间差异很大,跟条带的整齐图案形成鲜明对比。这也是判断模块有没有真正学到东西的一个直观指标:把偏移量可视化出来,如果三种退化输入下呈现三种明显不同的图案,说明退化感知分支起作用了;如果三种输入下图案长得一模一样,那基本就是塌缩了。

3. 复现路上的第一道坎:合成数据管线怎么搭才不至于白训

3.1 三类退化的合成参数与物理合理性

公开的真实成对数据几乎没有,三种退化同时出现还带干净真值的样本更是稀缺,所以这条路基本只能靠合成起步。合成不是随便加噪声,参数的物理合理性直接决定了模型能不能往真实场景迁。

雨痕我用的是一套简化的物理渲染:先随机生成若干条贝塞尔曲线作为雨丝骨架,每条线的长度、宽度、亮度、倾角按给定区间采,然后对每条线施加一个方向一致的运动模糊核,模糊核长度和线的长度挂钩,最后叠一层轻微的亮度衰减模拟离焦。这里有个容易忽略的点:雨丝在真实场景里是有遮挡关系的,远处的雨丝应该更淡更短,如果所有雨丝亮度一样,模型会学到“雨痕都是等亮度的”这个错误先验,遇到远景雨丝就直接忽略。

条带的合成要用多个正弦分量的叠加加上缓慢的相位漂移,单一频率的正弦太理想化,真实传感器条带往往有几个谐波分量,而且相位沿图像某一方向缓慢变化。我的参数设定是基频周期在 2 到 8 像素均匀采样,叠加两到三个谐波,幅度随谐波阶次衰减,再乘一个低频的空间调制包络,让条带不是全局等强度的。

摩尔纹的合成有个取巧但很有效的办法:先生成一张高频网格图像,再对它做双线性下采样到低分辨率,然后上采样回原尺寸。这个过程中的混叠就是摩尔纹的来源,物理上跟相机采样屏幕像素的原理一致。网格周期在下采样前后要有非整数倍关系,否则不会产生拍频。相比直接叠加两组正弦光栅的做法,这种办法产生的纹样更接近真实翻拍效果,周期变化也更自然。

3.2 混合比例与课程式采样调度

三种退化同时叠在一张图上训练,是我早期犯的最大错误之一。一开始我按三等分比例把三种退化随机组合叠加,结果训练前二十个 epoch 损失几乎不降。原因很简单:三种退化的修复目标在局部是矛盾的。去雨要求保留背景的高频细节,去掉细长结构;去条带要求削掉特定周期的成分,包括背景里本来就有的同频细节;去摩尔纹要求重建被混叠破坏的纹理。三个目标同时压在一个像素上,梯度方向互相抵消。

后来改成课程式调度才走通:前三分之一训练只跑单退化,让网络先把三种基础能力各自建立起来;中间三分之一跑两两组合,这个阶段的损失下降最快,因为网络开始学“在哪一步该用哪种采样几何”;最后三分之一才上三合一,并且混合比例不是均等的,摩尔纹样本占比调高到 40% 左右。原因是摩尔纹的周期变化范围最大,最难学,给少了它会一直欠拟合,最后表现成“雨和条带都干净了,网格纹还剩一层”。

3.3 合成到真实的域差,比想象中更难填

合成数据训出来的模型,直接拿去跑真实翻拍照片,通常会出现两种退化:一是过度平滑,因为合成数据里的退化强度是可控的,真实场景的退化往往更剧烈;二是残留结构性伪影,因为合成里的雨丝是规则曲线,真实雨丝有分叉、有断裂、有反光。

我的应对办法有两条。一条是在合成阶段引入随机强度外推,也就是百分之十五的样本用超出正常范围的退化强度生成,虽然这些样本训起来更慢,但能明显缓解过平滑。另一条是退化类型无关的增广,包括随机伽马、局部对比度扰动、轻微的运动模糊和压缩噪声,目的是让网络别把“合成数据的特定统计”当成判别依据。这两条加进去之后,我在一批真实翻拍样本上的主观质量提升相当明显,虽然 PSNR 因为没配对真值测不了,但人工盲评的偏好率从四成出头提到了七成左右。

4. 训练细节:损失函数、采样正则和那些绕不开的任务冲突

4.1 光有像素损失不够,频域约束得挑着加

第一版我用的就是纯 Charbonnier 损失,结果很典型:PSNR 看着还行,但放大看细节一片糊,条带的周期性残留还在,只是被平均掉了。原因在于像素级损失对相位敏感但对周期不敏感,它不知道“这里还残留了一个周期性的亮度起伏”,只要平均误差小就满意了。

加频域约束有效,但加的位置很讲究。早期我在整幅图上算 FFT 幅度谱的 L1,效果一般,因为低频幅度大、高频幅度小,L1 会被低频主导。后来改成只在高频带算幅度谱损失,并且对幅度做对数压缩,效果就出来了。对数压缩的作用是平衡不同频段的量级,让条带所在的中频段不被高频噪声淹没。

另外补了一个梯度一致性损失,专门防拖影。做法是把预测图和真值图分别求 Sobel 梯度,然后只在真值梯度幅值较大的区域计算方向一致性。这一项对去雨特别关键,因为长条雨丝被去掉之后,如果边缘处理不干净,会在原位置留下一道比背景亮或暗的痕迹,视觉上比不修还难受。这一项权重不能给大,超过主损失的三分之一就会导致纹理过锐、出现振铃。

4.2 采样偏移塌缩:最隐蔽也最致命的坑

这个坑我排查了整整两天,值得单独说。现象是:训练到中期,验证集指标开始停滞,把偏移量可视化出来一看,几乎所有的偏移都趋近于零,也就是说动态采样模块完全退化成了普通 3×3 卷积,白加了这么多参数量。

根本原因是梯度路径的问题。偏移量参与的是双线性插值,插值本身对坐标是可导的,但导数的量级很小,尤其是在纹理平滑区域,偏移量挪动半个像素对输出几乎没影响,梯度接近零。而权重分支(调制权重)的梯度要大得多,于是训练过程中权重分支承担了几乎全部的表达能力,偏移分支逐渐失去学习信号,最终塌缩。

三个手段组合起来才解决。第一是偏移多样性正则:在一个 batch 内统计所有窗口偏移量的方差,对方差过小的情况施加惩罚,逼着偏移散开。第二是基准位置初始化:不把初始偏移设为零,而是设成一个预设的空洞网格,比如四邻域距离 2 的十字形,这样即使梯度小,偏移也不会一路滑到零点。第三是给偏移分支单独的、更大的学习率,我用的倍率是三倍,配合余弦退火。三条一起上之后,偏移量的分布才真正散开,验证指标也跟着涨了一截。

4.3 多任务权重的手动调参记录

损失权重这块没有理论最优,只能试。下面是我最终收敛下来的一组配置,跑的是 256×256 的 patch、batch size 16,供参考:

损失项作用权重调参备注
Charbonnier主重建1.0基准项,eps 取 1e-3
高频幅度谱 L1压条带周期残留0.08超过 0.15 会出现高频振铃
梯度方向一致防雨痕拖影0.05超过 0.03 会有锐化伪影
偏移多样性防采样塌缩0.02按 batch 内方差倒数形式计算
调制权重稀疏防全点参与0.01L1 形式,压向 0.3 附近

这张表里最需要盯的是高频幅度谱那一项。它的权重区间很窄,给多了画面发脆,给少了条带清不干净,我在 0.05 到 0.15 之间扫了七八个点才定下来。另外注意这些权重跟你的退化强度分布强相关,如果你的条带合成得比较淡,这一项还得往下压。

5. 实测中的意外与排查链路:三个典型故障的定位过程

5.1 条带清不干净:从残差频谱反推回采样图案

第一个故障是条带去除不彻底。肉眼能明显看到横向的明暗起伏还在,但 PSNR 只掉了零点几分贝,属于典型的“指标看不出来、眼睛受不了”。

排查链路是这样的。第一步,把预测图和真值图相减,对残差图做二维 FFT,看残差能量集中在哪。结果很明确:残差在条带基频位置有一个清晰的峰,说明问题不是“没去干净”,而是“根本没动到那个频率”。第二步,回头查采样偏移的分布,发现垂直方向的偏移量普遍偏小,绝大多数集中在正负一个像素以内——这意味着网络没法跨过条带的一个完整周期去取样,跨周期抵消的机制压根没生效。

第三步找根因,问题出在偏移的取值范围约束上。我最初把偏移限制在 $[-1, 1]$ 的归一化坐标内,对应到 1/4 分辨率特征图上就是正负四个像素,看着够用,但因为特征图是下采样的,实际覆盖的原图像素范围被放大了四倍,反而让网络倾向于在小范围内精细调整,不敢往外跳。把范围放开到 $[-3, 3]$ 并加上前面说的多样性正则之后,垂直偏移的分布明显散开,出现了明显的周期性聚集,条带残留随之消失。这个经验告诉我:动态采样的偏移范围要按退化周期来定,不能拍脑袋给个看起来合理的值。

5.2 摩尔纹修成了“糊纹”:多尺度缺失的连锁反应

第二个故障更气人,摩尔纹是去掉了,但被网格覆盖的区域全变成了一片模糊,纹理完全丢失。这在翻拍文档和屏幕截图上尤其明显,文字边缘糊成一团。

定位思路是先确认“是采样不够还是重建不够”。我把中间特征图导出来看,发现被糊区域的采样点分布非常密集,几乎全挤在中心三像素范围内,等于是在做一次加权平均。也就是说,网络选择了“把网格平均掉”这条最省力的路,而不是“重建被混叠破坏的纹理”。

为什么会这样?因为摩尔纹在同一张图里周期变化很大,单个窗口的 K 个采样点只能覆盖一种周期。网络面对多周期时,学不到统一的几何图案,索性放弃几何、退化成均值滤波。这不是正则能解决的问题,是结构容量不够。

解决方案是加了一个多尺度空洞采样分支:在偏移预测时同时生成两组偏移,一组基准间距为 1,一组基准间距为 3,两组共享调制权重但独立预测偏移,最后把两组的聚合结果拼接再降维。加了这一层之后,短周期区域靠第一组、长周期区域靠第二组,糊纹现象大幅缓解。代价是这一分支的耗时占了整个网络的近两成,属于必要开销。

5.3 雨痕去了却留下拖影:损失设计和边界填充的双重锅

第三个故障是雨丝位置留了一道浅色的拖影,亮度比周围略高,形状和原雨丝一致。这种伪影在暗背景上格外扎眼。

一开始我怀疑是损失函数的问题,换了 L1、Charbonnier、甚至加了 SSIM 项,改善都很有限。后来把预测图和真值图叠在一起做差分可视化,发现拖影的位置比原雨丝往外偏了一到两个像素,而且是单侧偏移。这个特征指向了边界填充。

继续查,问题确实在采样的边界处理上。可变形采样在图像边缘附近,采样点会落到有效区域外,常规做法是零填充或者复制填充。零填充会在边缘引入突变的暗区,复制填充会把边缘像素重复拉进来,两者都会让偏移量在边缘附近学出系统性偏差。修法是把填充方式改成反射填充,同时对落在有效区域外的采样点,在调制权重上乘一个衰减系数,让它们自动降权。改完之后拖影基本消失,边缘区域的指标也涨了。

顺带提一句,这个问题在训练集里其实一直存在,只是雨丝大多落在画面中部,边缘样本占比低,被平均掉了。所以我后来在数据增广里专门加了随机平移裁剪,保证每条雨丝都有机会落在边缘,逼着网络学会处理边界情况。

6. 从实验室到产线:部署侧绕不开的三个现实问题

6.1 动态采样算子在推理引擎里的支持情况

这是最容易被忽略、又最容易在上线前一天爆炸的环节。训练框架里动态采样就是一行grid_sample,跑得好好的。但导出到中间表示之后,GridSample这个算子在不同推理引擎里的支持程度差别很大,有的版本只支持四维输入,有的对align_corners参数的默认值处理和训练框架不一致——这个不一致非常隐蔽,它不会报错,只会让你的输出整体偏移半个像素,指标掉一点点,你还以为是量化损失。

我的处理办法是导出后先做一次逐层对齐验证:用同一张输入图,分别在训练框架和推理引擎里跑,把动态采样层的输出张量取出来逐元素比对。如果最大误差超过千分之一,就先别往下走,一定是坐标语义对不上。真要遇到引擎不支持的情况,退路是把偏移量量化成整数后用手写的gather加查表实现双线性插值,虽然损失一点精度,但兼容性最好,实测指标下降在零点一分贝以内。

6.2 半精度下的坐标精度:一个必须算清楚的账

动态采样的坐标是归一化到 $[-1, 1]$ 的浮点数。半精度的有效尾数位大约十位,在 $[-1, 1]$ 区间内的绝对精度大约在千分之一量级。听起来很小,但换算到一张 4K 图上就是接近四个像素的定位误差,对于本来就靠亚像素级采样图案去抵消条带的操作来说,这个误差是致命的。

所以我的做法是:坐标计算全程保持单精度,只在聚合之后的特征上转半精度。具体实现上,偏移预测分支的输出强制 cast 回 fp32,插值系数也在 fp32 下算完再和特征相乘。这样做的代价是这一层没法完全吃到半精度的加速,但换来的是精度不掉。我对比过全半精度和混合精度的版本,前者在条带场景下的指标掉了将近一分贝,后者几乎无损,这个取舍没什么可犹豫的。

6.3 移动端算力预算怎么切

如果目标是端侧,动态采样的访存开销会成为主要瓶颈,这时候得做减法。我的经验是按三档来切:高配档保留完整的多尺度动态采样,K 取 9 加 5 两组,适合中高端芯片;中配档只保留单组 K=9,去掉多尺度分支,摩尔纹质量会下降但雨和条带基本不受影响;低配档把偏移量强制量化为整数,双线性插值退化成最近邻查表,访存变成规则的 gather,速度能提将近一倍,代价是细密纹理区域会出现轻微块状感。

还有一个容易被忽略的省算力的点:动态采样模块不需要在每个尺度上都放。我做过消融,只在编码器中段放一个模块、解码器对应位置放一个,就能拿到完整配置八成以上的收益。浅层放了收益极低,因为浅层特征还太局部,偏移预测分支根本看不出来全局的周期结构;深层放了开销太大,特征图小但通道厚,访存效率很差。

最后分享一个我在调这套东西时养成的小习惯:每次改完采样相关的超参,别急着看整体指标,先把偏移量分布画出来看一眼。如果分布形态没变,那这次改动大概率没起作用,指标的小幅波动多半是随机种子在作怪。这个检查花不了一分钟,但能省下大量无效的调参时间。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询