SADA动态采样统一修复雨痕、条带与摩尔纹 处理一批手机翻拍屏幕的照片时我第一次真切体会到“三类退化混在一起”有多难搞画面里既有窗外的雨丝斜掠又有室内灯管拍摄产生的横向条带还有屏幕像素与相机采样错位打出来的彩色网格纹。当时的做法是先上条带抑制再跑摩尔纹分离最后接一个去雨网络三步串下来不但速度感人更麻烦的是每一步的输出都成了下一步的输入噪声——去条带把细密的雨丝一起削平了去摩尔纹的频域陷波又在雨痕边缘制造了振铃。这套链路换了三四个开源权重都没救回来问题不在某个模型弱而在于它们是各修各的没人管另外两种退化长什么样。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 到 KK 是每个窗口的采样点数实际配置里常见 9 或 13。这些偏移是在归一化坐标下表达的数值范围大致限制在 $[-2, 2]$ 之间超过这个范围再往外采就纯属噪声还会引入边界填充的假信息。第二组是调制权重 $w_k$经过 sigmoid 压到 0 到 1 之间作用是给每个采样点一个内容相关的软门控避免所有点不分青红皂白全都进来。这里有个我踩过的细节偏移量的初始化。如果偏移预测层的最后一层权重用默认的高斯初始化训练初期输出基本在零附近徘徊整个模块等价于普通 3×3 卷积网络会沿着这条好走的路一路滑下去再想拉回来就难了。我后来的做法是把最后一层权重初始化为零同时把采样的基准位置设成一个固定的空洞网格这样初始状态是个标准的空洞卷积梯度有了明确的改进方向采样点分布也更容易散开。2.2 可变形聚合与普通卷积的算量账很多人第一反应是“动态采样这么花哨肯定慢”。这个账得算细一点。K9 的情况下可变形聚合每个输出点要做 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 两组适合中高端芯片中配档只保留单组 K9去掉多尺度分支摩尔纹质量会下降但雨和条带基本不受影响低配档把偏移量强制量化为整数双线性插值退化成最近邻查表访存变成规则的 gather速度能提将近一倍代价是细密纹理区域会出现轻微块状感。还有一个容易被忽略的省算力的点动态采样模块不需要在每个尺度上都放。我做过消融只在编码器中段放一个模块、解码器对应位置放一个就能拿到完整配置八成以上的收益。浅层放了收益极低因为浅层特征还太局部偏移预测分支根本看不出来全局的周期结构深层放了开销太大特征图小但通道厚访存效率很差。最后分享一个我在调这套东西时养成的小习惯每次改完采样相关的超参别急着看整体指标先把偏移量分布画出来看一眼。如果分布形态没变那这次改动大概率没起作用指标的小幅波动多半是随机种子在作怪。这个检查花不了一分钟但能省下大量无效的调参时间。