1. 拆解这个标题:一个修复模型为什么要同时扛三种退化
第一次看到“动态采样搞定雨痕、条带、摩尔纹三合一修复”这个说法,我下意识反应是:又是一个想在单一模型里塞多个任务的尝试。但把标题里的几个词拆开看,会发现它其实踩在了图像复原领域一个很尴尬的痛点上。
雨痕属于天气类退化,特征是随机分布、方向性强的亮条纹,像素被雨水折射和散射污染,局部对比度骤降;条带属于传感器/传输类退化,通常表现为周期性或半周期性的低频明暗波动,来源可能是读出噪声、压缩伪影或者老旧成像链路;摩尔纹则是采样类退化,高频纹理与感光阵列发生频率混叠,表现为彩色或灰度的网格状伪影。这三者的物理成因、频域分布、空间结构完全不同,传统做法是每个问题训一个专用网络,工程上维护三套权重、三套预处理、三套后处理,部署成本高得离谱。
所以“三合一”这个提法真正的价值不在炫技,而在于统一退化建模这件事。它的潜台词是:能不能用一个共享主干,让网络自己判断当前输入里混了哪几种退化、各占多大比例、分别该在哪个尺度上去处理。标题里的“动态采样”就是这个思路的落点——不是固定感受野、固定卷积核、固定采样位置,而是让采样点跟着退化类型走。
至于标题里挂的那个会议年份,我的态度一向很明确:年份只是时间戳,不是质量背书。真正值得看的是方法层面解决了什么,而不是它出现在哪本论文集里。这篇内容适合三类人看:一是做图像复原方向、想理解多退化统一建模思路的算法同学;二是工程侧被多个修复模型折磨、想收敛模型数量的开发者;三是对动态网络、可变形采样这类结构好奇、想搞清楚它到底比普通卷积强在哪的读者。下面我按“为什么这么设计—关键细节怎么落地—完整流程怎么跑—坑在哪”的顺序展开,尽量把能抄的部分写清楚。
2. 整体设计思路:为什么是“动态采样”而不是“堆模块”
2.1 多退化统一建模的三条常见路线及其代价
在动手理解 SADA 这类结构之前,得先知道这个领域里有哪几条路可走。我把常见的做法归成三类,每条路线我都实际跑过或者复现过,优缺点很实在。
第一条是串行级联。先上 derain 网络,再上 destripe 网络,最后上 demoire 网络。优点是每个子任务都能用成熟方案,出了问题是哪一段一测便知;缺点是误差会累积,第一级把雨痕抹掉的同时很可能把细纹理一起抹平了,第二级在已经损失高频的图上再处理条带,很容易把残余纹理当成条带削掉,最后出来的图“干净但糊”。更致命的是推理延迟翻三倍,移动端基本别想。
第二条是多任务学习共享主干。一个 encoder 提特征,三个 decoder 分支各管一种退化,损失加权求和。这条路比串行优雅,参数量也可控,但它有个隐含假设:不同退化在特征空间里是可分的。现实往往不是这样,雨痕的边缘响应和摩尔纹的网格边缘在浅层特征图上高度相似,共享主干容易学出“平均解”,三个任务互相牵制,最后哪个都不够锐利。
第三条是动态/条件化网络。核心思想是让网络根据输入内容自适应地调整计算路径或采样位置。SADA 走的就是这条。它的好处是把“判断当前是什么退化”这件事从人工预处理搬进了网络内部,用一个轻量的分支去估计退化先验,再把这个先验喂给采样模块,决定“往哪里看、看多大范围”。
我用一个生活化的类比说明区别:串行级联像三个人接力修一张照片,每个人只管自己那摊;多任务共享像一个人同时干三份活但用的是同一套工具;动态采样则像一个人先眯眼看清楚照片上到底脏在哪,再决定用粗砂纸还是细砂纸、先磨哪个角。第三种显然更接近人修图的真实过程。
2.2 动态采样的核心逻辑:把固定的几何先验换成数据驱动的几何
普通卷积的采样网格是固定的 3×3 或 5×5 方格,这意味着它在空间上是“各向同性”的——不管这个位置是雨痕的竖直走向,还是摩尔纹的斜向网格,它都用同一套权重去卷。这在单一退化下问题不大,但在混合退化下就成了瓶颈。
可变形卷积(Deformable Convolution)给的解法是给每个采样点学一个偏移量,让采样位置可以浮动。但早期的可变形卷积有个通病:偏移量是无约束学出来的,训练不稳定,容易跑到图像外面去,或者在同一种退化上每次偏移都不一样,缺乏一致性。
SADA 这类动态采样方案的改进点,我理解主要落在两个地方。一是偏移量由退化先验引导,不是完全自由学习,而是让网络先估计一个粗糙的退化图(比如用轻量分支输出三通道的退化置信度),再把这个置信度作为偏移量的条件输入,这样采样位置就有了物理意义的约束——雨痕区域偏移更倾向于沿条纹法向拉开,摩尔纹区域偏移更倾向于覆盖更大的周期范围。二是多尺度采样核,单一尺度的采样对付不了摩尔纹这种跨周期的伪影(周期可能十几到几十像素),所以需要并行几个不同 dilation 的采样分支,让网络自己加权融合。
这也解释了一个常见疑问:为什么不直接上 Transformer 的全局注意力?答案很实际——全局注意力的计算量随分辨率平方增长,4K 图根本跑不动,而动态采样的复杂度是线性的,工程可落地性强得多。这一点在真实部署里是决定性的。
2.3 为什么“三合一”在参数效率上确实占优
我做过一个粗略的账:三个独立的小型修复网络,每个按 1.2M 参数算,总共 3.6M;再加三套预处理逻辑和后处理逻辑,代码维护成本另算。而一个共享主干加动态采样分支的统一模型,主干 1.8M 左右,退化估计分支 0.3M,三个采样头共享大部分权重,总共 2.4M 上下。参数量省三分之一只是表面,真正省的是部署链路——一次前向、一套归一化参数、一个模型文件,这对端侧和边缘设备意义很大。
代价也要说清楚:统一模型在单一退化的极端样本上,通常比专用模型差一点点,这个差距在 PSNR 上可能是 0.2 到 0.5 dB 的量级。如果你的场景 99% 都是纯雨痕,那专用模型仍然更划算;但如果是真实拍摄、退化混合且不可预知,统一模型的鲁棒性优势会盖过那点指标损失。这个取舍一定要在项目开始前想清楚,否则做到一半才发现方向不对,返工成本很高。
3. 核心细节解析:动态采样模块到底怎么搭
3.1 退化先验估计分支:轻量但别太抠
退化先验分支的作用是给后续采样提供“哪里脏了、脏成什么样”的线索。我的实现习惯是用三层带 stride 的卷积把分辨率降到 1/4,然后接一个 1×1 卷积输出三通道的置信图,最后上采样回原尺寸。这个分支必须轻,因为它只是提供引导信号,不是主力修复器;但也不能太抠,我试过把它压到单层卷积,结果置信图糊成一团,反而拖累了主分支。
有个细节值得注意:这三通道的监督信号从哪来。如果训练数据是合成退化(干净图加雨痕、加条带、加摩尔纹),那退化 mask 是天然已知的,可以直接用 L1 或 BCE 监督。如果是真实配对数据,没有 mask,就只能靠隐式监督——让置信图通过后续修复损失回传梯度。后者收敛慢很多,我的经验是先用合成数据预训练这个分支,再用少量真实数据微调,效果比纯隐式监督稳定得多。
注意:退化置信图不要做 softmax 归一化到和为 1。三种退化经常是共存的,不是互斥分类,强行归一化会让网络在混合退化时被迫做取舍,实测 PSNR 会掉 0.3 dB 左右。用 sigmoid 各通道独立激活更合适。
3.2 偏移量生成与约束:把野马拴上缰绳
偏移量的生成是动态采样最容易翻车的地方。我踩过的坑是这样的:直接让一个卷积层回归 offset,不加任何约束,训练初期 loss 震荡得厉害,可视化偏移场能看到大量采样点飞出特征图边界,被 zero padding 补零之后相当于引入了大片无效区域。
后来我改成两个约束,情况立刻好转。第一个是幅值裁剪,把 offset 限制在 ±k 个像素内,k 取采样核半径的 1.5 倍左右,既能覆盖退化尺度又不至于跑飞。第二个是先验加权,即 offset = 基础偏移 × 退化置信度的某种变换。直观理解:置信度高的区域允许更大的偏移,置信度低的区域偏移趋近于零,退化成普通卷积。这个设计让网络在干净区域不瞎折腾,把算力集中在真正需要的地方。
具体到条带这种方向性极强的退化,我会额外加一个方向先验:条带通常是水平或垂直的,所以在这个分支里把 offset 的搜索空间限制在垂直于条带的方向上,采样核拉成一维长条。这个改动不复杂,但对条带区域的修复锐度提升很明显,实测在自制条带测试集上 PSNR 涨了 0.6 dB 以上。
3.3 多尺度采样头的融合策略
摩尔纹的周期跨度大,单一采样尺度必然顾此失彼。我的做法是并行三个分支,dilation 分别取 1、3、7,对应小、中、大三种感受范围,每个分支内部再用动态偏移微调。融合不是简单相加,而是用退化置信度做门控加权:某个分支的输出权重由它擅长处理的退化类型在当前像素的置信度决定。
这里有个实操细节:门控权重最好在通道维度上也做区分,而不是整张图一个标量。我一开始用全局平均池化出三个标量权重,结果一张图里如果上半部分有条带、下半部分有摩尔纹,模型就只能给一个折中权重,两边都修不好。改成 per-pixel 的通道注意力之后,局部退化差异才被正确处理。
下表是我在几个配置间做对比时记录的相对表现,指标是在自建混合退化验证集上的平均结果,仅作方向参考:
| 配置方案 | 参数量 | 相对 PSNR | 推理耗时(1080p) | 主观观感 |
|---|---|---|---|---|
| 单尺度固定采样 | 2.1M | 基线 | 28 ms | 摩尔纹残留明显 |
| 多尺度固定采样 | 2.4M | +0.4 dB | 41 ms | 摩尔纹改善,雨痕变糊 |
| 多尺度动态采样(无先验) | 2.5M | +0.3 dB | 46 ms | 训练不稳,偶发结构扭曲 |
| 多尺度动态采样(先验引导) | 2.5M | +0.9 dB | 47 ms | 三类退化均较干净,纹理保留好 |
数据不一定精确复现,但趋势是稳的:先验引导带来的增益比单纯增加尺度更大,这说明“知道往哪看”比“看得更多”更重要。
3.4 损失函数配比:别让某一类退化吃掉梯度
三类退化的修复难度差异很大。雨痕因为遮挡面积大、对比度高,早期收敛最快;条带是低频问题,梯度信号弱;摩尔纹是高频细节问题,容易被其他两项的梯度淹没。如果三项损失等权相加,训练到中后期会发现摩尔纹区域几乎没进步。
我的配比经验是:主体用 Charbonnier 或 L1 重建损失打底,权重 1.0;对摩尔纹区域额外加一个频域损失,把预测和真值做 FFT 后在高频段算 L1,权重 0.2 到 0.3;对条带区域加一个低频一致性损失,约束行/列均值曲线接近真值,权重 0.15 左右。这两个辅助损失不需要额外标签,直接从真值图算,落地成本很低。
还有一个容易被忽略的点:难样本挖掘。混合退化的难度分布极不均匀,简单样本占了大多数,训练后期梯度基本来自简单样本,模型在难样本上停滞。我的做法是按当前 loss 排序,每个 batch 里取 top 30% 难样本的损失加权 2 倍。这个改动很朴素,但在混合退化场景下比调学习率管用。
4. 实操流程:从数据合成到推理部署的完整链路
4.1 训练数据合成:退化叠加顺序会显著影响结果
真实的三合一退化配对数据几乎拿不到,所以合成是必由之路。这里有个很多人不注意的细节:叠加顺序。我的实测结论是,物理上更合理的顺序是“先摩尔纹(采样阶段产生)→ 再条带(传输/读出阶段产生)→ 最后雨痕(场景中产生)”,这个顺序训出来的模型,在真实混合样本上的泛化明显好于反过来叠。
雨痕合成我用的是经典的做法:随机生成若干条运动模糊核,模拟雨滴的拉伸方向,叠加亮度增益后与原图做 alpha 混合,同时生成对应的 rain mask。条带的合成更简单,生成一个周期性的正弦或方波调制,乘到行或列上,幅值随机在 5% 到 20% 之间。摩尔纹的合成最麻烦,我推荐用频率混叠法:把原图做轻微的下采样再上采样,中间插入一个随机相位的网格屏,模拟感光阵列的拍频效应。这样做出来的摩尔纹在频域上的分布比手工画网格真实得多。
数据量上,我的经验是每种退化单独至少 3000 对,混合样本 5000 对以上才能让动态采样分支学到稳定的偏移模式。数据少的时候,动态采样很容易退化成固定采样,因为偏移量学不到有意义的分布。
提示:合成时退化强度要留一定比例的“轻退化”样本。全是重度退化训出来的模型,遇到轻度雨痕会过度修复,把原本清晰的纹理也磨掉,这是很典型的过杀问题。
4.2 训练配置与关键超参
我的基础配置大致是这样,供参考:优化器用 AdamW,初始学习率 2e-4,余弦退火到 1e-6,batch size 16(256×256 patch),训练 300 个 epoch 左右。主干用轻量残差块堆 16 层,通道数 64。
有几个超参值得单独说。offset 的学习率要单独调低,我设成主学习率的 0.3 倍。原因是偏移量的梯度尺度比卷积权重小得多,用同一个学习率会导致偏移几乎不更新,动态采样名存实亡。这个坑我在早期版本上卡了整整两周,一直以为是结构设计有问题,后来打印 offset 的统计量才发现它根本没动。
退化先验分支的预热也很关键。前 20 个 epoch 我冻结主分支,只用退化 mask 监督先验分支,让它先学会“看”,然后解冻联合训练。这个两阶段策略比端到端一把梭的收敛速度快将近 40%,最终指标也更稳。
4.3 推理阶段的分块策略与边界处理
真实业务图分辨率往往大于训练 patch,推理必须分块。分块本身不难,难的是边界。动态采样在块边界处会因为采样点越界而失真,表现是拼接后能看到网格状的接缝。
我的处理方案是重叠分块加权重融合:块与块之间重叠 32 像素(大于最大 offset 幅值),输出时用余弦窗加权,靠近块中心的权重高、边缘的权重低。这个做法把接缝问题基本消灭了,代价是计算量增加约 20%,但完全值得。
另一个优化是按退化置信度做自适应分块。先在 1/4 分辨率上跑一遍退化估计,得到置信图,然后在置信度高的区域用更小的块、更高的重叠,在干净区域用大块快速通过。这个策略在实际业务图上的平均推理时间能降 25% 左右,而且视觉上没有任何损失,因为干净区域本来也不需要精细处理。
4.4 一个可直接参考的核心采样模块骨架
下面这段是我按前面思路写的简化实现,用伪代码风格表达,重点看逻辑而不是逐行照抄:
# 退化先验引导的多尺度动态采样(简化示意) def dynamic_sample(feat, prior_conf, offsets, dilations=(1, 3, 7)): outputs = [] for d in dilations: # 基础网格按 dilation 展开 grid = base_grid(feat.shape, dilation=d) # 偏移量受先验置信度调制,并做幅值裁剪 guided = prior_conf * offsets guided = clamp(guided, -1.5 * d, 1.5 * d) sampled = grid_sample(feat, grid + guided) outputs.append(conv(sampled)) # 用逐像素通道门控融合多尺度结果 weights = softmax(gate_conv(prior_conf), dim=1) return sum(w * o for w, o in zip(weights, outputs))这段代码里最值得关注的是guided = prior_conf * offsets这一行。它就是“先验引导”的落地形式:置信度为零的地方,无论 offset 学成什么样,采样都退回规则网格,天然避免了在干净区域引入噪声。这个乘法操作看起来平淡,但它是我试过的所有约束方式里性价比最高的一个。
4.5 验证集怎么建才不骗自己
合成数据训出来的模型,在合成验证集上指标往往好得离谱,一到真实图就露馅。我的做法是建三层验证集:第一层是同分布的合成集,用来监控训练是否正常;第二层是跨退化强度的合成集,退化参数不在训练分布内,测泛化;第三层是真实拍摄的小样本集,哪怕只有几十对,也必须有,这是唯一能反映真实表现的。
第三层数据的收集成本高,我通常的做法是找同场景的干净参考图(比如拍一张静态场景,再拍带有退化的版本),或者用多帧平均的方式构造近似的干净参考。这个小样本集不需要大,但它是判断模型能不能上线的关键依据,千万不能省。
5. 常见问题与排查实录
5.1 动态采样“学不动”的三种典型表现
这是最高频的问题,我把遇到过的表现和根因整理成下表,方便对照排查:
| 现象 | 大概率根因 | 排查手段 | 处理方式 |
|---|---|---|---|
| 偏移量方差接近 0 | 偏移学习率过低 | 打印 offset 统计量 | 偏移单独设 0.3 倍学习率 |
| 偏移量方差爆炸 | 缺少幅值约束 | 可视化偏移场 | 加 clamp,限制在核半径 1.5 倍内 |
| 偏移量与退化无关 | 先验分支没预热 | 单独评估置信图质量 | 两阶段训练,先监督先验分支 |
第一种我前面提过,卡了两周;第二种会让训练后期出现结构性扭曲,比如人脸被拉歪,因为采样点跑到别处取了无关像素;第三种最隐蔽,训练 loss 看起来正常下降,但模型实际上是退化成固定采样在硬扛,等于白设计了动态结构。
5.2 摩尔纹修完反而出现彩色斑块
这个问题的根因通常是通道独立处理。摩尔纹本身是彩色的(因为 RGB 三通道的采样相位不同),如果网络在通道维度上处理不当,修复后在摩尔纹原来位置会残留低频的彩色斑块,看起来比原图更脏。
我的解法是在采样模块后面加一层轻量的通道混合卷积(1×1 卷积即可),并且在损失里加一个色度一致性项,约束预测图与真值图在 YCbCr 的 Cb、Cr 通道上的差异。这个改动成本很低,但对彩色摩尔纹的修复质量提升很明显。
5.3 条带区域出现过修复的“塑料感”
条带是低频问题,模型很容易学成“把整片区域的对比度拉平”,结果是条带没了,但纹理也平了,画面像塑料。这是过度平滑的典型症状。
处理思路有两条。一是在损失里约束结构相似性,加 SSIM 项,权重 0.1 到 0.2,它能有效抑制过度平滑;二是在条带分支里保留高频旁路,把输入的高频成分(原图减去高斯模糊后的低频)直接加到输出上,让网络只负责去除条带成分,不负责重建细节。第二条我更喜欢,因为它把“去什么”和“留什么”解耦了,可控性强。
5.4 混合退化下训练不收敛的排障顺序
遇到 loss 长时间不降,我一般按这个顺序查:先看数据,确认配对是否对齐、是否有脏样本;再看退化 mask 是否合理,尤其检查叠加顺序;然后看损失权重,是不是某一项梯度量级压过了其他项(打印各项 loss 的绝对值对比);最后才怀疑结构。经验上,八成问题出在前三步,结构本身有问题的概率反而低。
注意:混合退化训练中,各项损失的量级差异可能达到两个数量级。直接相加会让小量级项形同虚设,建议先各自归一化到相近量级再加权,这一步能省掉大量调参时间。
5.5 部署时的显存与延迟优化清单
模型能跑通不等于能上线。我总结的优化顺序是:先把推理分块的重叠从 32 降到 16(配合置信度自适应分块),通常能省 15% 时间且视觉无损;再把退化先验分支量化成 INT8,这个分支对精度不敏感,量化损失几乎测不出来;主干的动态采样部分保持 FP16,因为 offset 的精度对结果影响较大;最后考虑把三个采样头中 dilation=1 和 3 的合并,只在置信度高的大尺度区域保留 dilation=7 的分支。
按这个顺序优化下来,1080p 图的单帧耗时能从 47 ms 压到 30 ms 以内,显存占用降 40%,基本能满足多数实时性要求不极端的场景。
6. 我个人在这类统一修复模型上的几点体会
做多退化统一修复这几年,最大的感受是:结构创新带来的增益,往往不如数据管线和损失设计带来的增益大。动态采样确实是好机制,但它在我的实验里贡献的 PSNR 不到总增益的一半,剩下的大头来自退化合成的真实性、先验分支的监督方式、以及难样本挖掘。很多复现失败案例,问题不在结构理解错,而在数据分布没对齐。
第二个体会是关于“三合一”这个提法的现实边界。三种退化共存时,模型确实能一次处理,但如果你的业务里某一种退化占了绝对主导(比如固定机位监控里的条带),老老实实用专用模型加简单规则,效果更稳、维护更省心。统一模型的优势场景是退化类型不可预知、且混合比例随机的情况,别为了架构优雅而牺牲实际效果。
第三个是给想复现的同学的建议:先从单一退化把动态采样机制调通,确认 offset 真的在学有意义的东西(可视化偏移场,看它是否在雨痕区域沿法向拉开),再逐步加入第二种、第三种退化。一次性上三合一,出问题时你根本分不清是哪个环节坏了。这个顺序看起来慢,实际是最快的路径。
最后一个实操小技巧:调试阶段一定要把退化置信图和偏移场可视化出来,存成图逐帧看。这两个中间量能解释 90% 的异常现象,比盯着 loss 曲线猜有用得多。我现在的习惯是每个 epoch 存一组可视化,训完回头看,很多当时想不通的问题一目了然。
至于这个方向后续还能怎么走,我自己在试的是把退化先验从手工设计的置信图换成一个小型的退化分类器输出,让先验的语义更明确;另一个方向是让多尺度采样的 dilation 也动态化,而不是固定的 1、3、7。这两条都还在实验阶段,谈不上成熟,等有稳定结论再单独拿出来聊。