CVPR 2025 | 新颖的L型卷积,即插即用,效率狂飙还超 SOTA 1dB+!
本文提出DnLUT框架,通过Pairwise Channel Mixer(PCM)和L形卷积核设计,实现了彩色图像高效去噪。PCM模块通过重组RGB通道为三对并行处理,有效捕捉通道间相关性;L形卷积核则减少50%像素重复访问,降低17倍存储需求。实验表明,DnLUT在CBSD68等数据集上的CPSNR显著优于现有LUT方法超1dB,存储仅需500KB,且可作为插件提升其他LUT方法性能。该框架在保
1.【导读】
彩色图像在数字领域应用广泛,但易受噪声影响,且人类对色彩失真更敏感。深度学习模型虽能实现高精度去噪,却因高计算复杂度和内存需求难以在边缘设备部署;现有基于查找表(LUT)的方法虽高效,却要么忽略RGB通道相关性导致去噪效果差,要么因捕捉通道信息导致存储需求爆炸(如4D/12D LUT需数百TB存储)。
为此,本文提出DnLUT框架:通过Pairwise Channel Mixer(PCM)捕捉通道与空间关联,以L形卷积核减少存储开销,在保持LUT高效性的同时提升去噪质量。其意义在于填补了算法能力与边缘部署需求的缺口,为智能手机等资源受限设备提供了“质量-效率”平衡的解决方案,推动轻量化图像处理技术落地。
2. 【论文基本信息】
- 论文标题:DnLUT: Ultra-Efficient Color Image Denoising via Channel-Aware Lookup Tables
- 论文链接:[2503.15931] DnLUT: Ultra-Efficient Color Image Denoising via Channel-Aware Lookup Tables
- 项目链接:https://github.com/stephen0808/dnlut
- 核心模块:这篇论文的核心模块包括Pairwise Channel Mixer(PCM),用于有效捕捉通道间相关性和空间依赖性,以及一种新颖的L形卷积设计,旨在最大化感受野覆盖的同时最小化存储开销。
3.【模块创新点】
3.1 Pairwise Channel Mixer (PCM)
核心功能:
- PCM模块通过将RGB通道组合成三对(RG、GB、BR),并在并行分支中使用1x2卷积核进行处理,有效地捕捉了通道间的相关性。
- PCM的设计不仅能够平衡空间和通道信息的处理,还能通过级联的1x1卷积层进一步细化特征。
实现逻辑:
- PCM首先将RGB通道重新组织成三个通道对(RG、GB、BR)。
- 每个通道对通过一个1x2卷积核进行处理,生成一个中间特征图。
- 中间特征图再通过级联的1x1卷积层进行细化,最终生成去噪后的图像。
优势:
- PCM有效地捕捉了通道间的相关性,提升了去噪效果。
- PCM的设计保持了较低的存储需求,适合在边缘设备上部署。
- PCM可以作为现有LUT方法的插件模块,提升其性能约1dB。
3.2 L-shaped Convolution Kernel
核心功能:
- L形卷积核设计通过每次旋转处理两个额外的像素,确保每个周围像素仅贡献一次输出,最大化像素值的利用并减少存储需求。
- L形卷积核能够在不牺牲有效感受野覆盖的情况下,将4D LUT转换为3D LUT,显著减少存储需求。
实现逻辑:
- 在每次旋转时,L形卷积核处理两个额外的像素,避免像素重叠。
- 通过这种设计,L形卷积核能够有效地扩大感受野,同时保持与4D LUT相同的有效感受野大小。
优势:
- L形卷积核减少了存储需求,提升了计算效率。
- L形卷积核的设计使得DnLUT能够在保持高质量去噪效果的同时,显著减少计算和存储资源的消耗。
3.3 PCM Plug-in Module
核心功能:
- PCM插件模块旨在增强现有LUT方法的性能,通过捕捉通道间的相关性来提升去噪效果。
实现逻辑:
- PCM插件模块在现有LUT方法的基础上,增加了PCM模块的处理步骤。
- PCM模块在推理过程中与现有LUT方法并行运行,捕捉通道间的相关性。
优势:
- PCM插件模块可以无缝集成到现有的LUT方法中,无需进行结构修改。
- PCM插件模块在提升性能的同时,仅增加了少量的运行时和存储开销。
4.【算法框架与核心模块】
4.1算法框架
DnLUT 的整体流程分为训练(DnNet)、转换(生成 LUTs)和推理(DnLUT)三个阶段。
- 训练阶段(DnNet):构建包含 Pairwise Channel Mixer(PCM)和 L 形卷积的网络,输入图像经 4 种旋转(0°、90°、180°、270°)增强后,通过多尺度融合模块整合特征,以 MSE 为损失函数训练。
- 转换阶段:将训练好的 DnNet 模块映射为 3D 和 4D LUTs,通过穷尽所有可能输入组合生成索引与对应输出,实现从网络到 LUT 的转换。
- 推理阶段:输入图像经旋转生成索引,通过查询预存的 3D/4D LUTs 快速获取输出,经插值和旋转还原后得到最终去噪结果,避免复杂计算。
4.2核心模块
-
Pairwise Channel Mixer(PCM)
针对通道相关性建模不足的问题,PCM 将 RGB 通道重组为 RG、GB、BR 三对,通过并行分支处理:每对通道采用 1×2 卷积核(深度 2),每个操作处理 4 个像素值并输出 1 个通道特征,训练后转换为 4D LUTs(LUT RG、LUT GB、LUT BR)。该设计既捕捉了通道间噪声关联,又控制了存储开销(图 3 对比显示其优于单纯空间或通道 - wise 核),且可作为插件提升现有 LUT 方法性能超 1dB。
-
L 形卷积核
为解决传统旋转扩展感受野导致的像素冗余问题,L 形核在 4 种旋转中仅处理新增像素(无重叠),使每个像素仅贡献一次,等效于 4D LUT 的感受野但可转换为 3D LUT。图 4 对比显示,与 SR-LUT 的 2×2 核相比,L 形核消除了 50% 的像素重复访问,存储需求降低 17 倍(表 7 中 PCM+L 的 LUT 尺寸为 518KB,而 PCM+S 为 902KB)。
5.【框架适用任务】
- 高斯彩色图像去噪场景
适用场景:处理受高斯噪声污染的彩色图像(如合成噪声场景,验证于 CBSD68、Kodak24、Urban100 等基准数据集)。
核心作用:在低资源消耗(500KB 存储、仅 0.1% 于 DnCNN 的能耗)下,比现有 LUT-based 方法提升 1dB 以上的 CPSNR,平衡去噪质量与计算效率。 - 真实世界彩色图像去噪场景
适用场景:处理智能手机等设备拍摄的含真实噪声的彩色图像(验证于 SIDD、DnD 等真实噪声数据集)。
核心作用:解决传统 LUT 方法色彩失真和过平滑问题,在保留细节的同时维持色彩一致性,性能远超经典方法(如 CBM3D)近 5dB。 - 增强现有 LUT-based 方法性能场景
适用场景:适配各类基于 LUT 的图像去噪方法(如 SR-LUT、MuLUT、SPFLUT 等)。
核心作用:通过 PCM 插件模块无需修改原结构,即可提升 1dB 以上的去噪性能,弥补传统 LUT 对 RGB 通道相关性捕捉不足的缺陷。 - 边缘设备部署场景
适用场景:资源受限的边缘设备(如智能手机、物联网终端等,验证于高通骁龙 8 Gen2 平台)。
核心作用:通过 L 形卷积核(存储需求降低 17 倍)和 LUT 架构,实现 20× 于 CNN 的推理速度,满足实时、低功耗的处理需求。
6.【实验结果与可视化分析】
-
模型综合性能对比 :在 CBSD68 数据集(高斯噪声 σ=25)上,DnLUT 的 CPSNR 显著高于现有 LUT-based 方法(如 SPFLUT、MuLUT 等),超出超 1dB;同时存储需求仅约 500KB,运行时间远低于其他方法,且 PCM 插件能为现有 LUT 方法提升 1dB 以上性能,体现了 “高性能 - 高效率” 的平衡。、
-
高斯彩色图像去噪定量结果 :在 CBSD68、Kodak24 等 4 个基准数据集上,DnLUT 在各噪声水平(σ=15、25、50)下的 CPSNR 均为 LUT-based 方法中最高。例如,σ=25 时,CBSD68 上 DnLUT 的 CPSNR 为 29.88dB,超过 SPFLUT(28.56dB)1.32dB,且接近经典方法 CBM3D 和 DNN 方法 DnCNN 的性能。
-
**真实世界彩色图像去噪定量结果 :在 SIDD 和 DnD 数据集上,DnLUT 的 CPSNR/PSNR 显著优于传统方法(如 CBM3D)和其他 LUT-based 方法。如 SIDD 数据集上,DnLUT 的 CPSNR 为 35.44dB,远超 SPFLUT 的 34.91dB,且接近 DnCNN 的 36.45dB;DnD 数据集上,其 PSNR 达 36.67dB,优于 SPFLUT 的 36.22dB。
-
**定性结果 :图 5 显示在合成高斯噪声场景中,现有 LUT 方法存在明显色彩失真,而 DnLUT 的去噪效果接近 DnCNN 和 CBM3D;图 6 表明在真实世界噪声场景中,DnLUT 能更好保留色彩一致性和细节,避免了其他方法的过平滑问题。
-
**PCM 插件效果
表 4 显示 PCM 集成到现有 LUT 方法(如 SR-LUT、SPFLUT)后,CPSNR 提升 1dB 以上(如 SPFLUT 在 CBSD68 上从 28.56dB 提升至 29.72dB)
图 7 可视化了添加 PCM 后,图像色彩失真减少, perceptual 质量显著提升。
-
**效率评估 :DnLUT 存储仅 500KB,能耗为 DnCNN 的 0.1%、SPFLUT 的 30%,运行时间是 DnCNN 的 1/20,且在移动平台(骁龙 8 Gen2)上表现优异,适合边缘设备部署。
-
**L 形卷积核效果 :与 2×2 空间核(PCM+S)相比,L 形核(PCM+L)在 CPSNR 接近的情况下(如 CBSD68 上 26.63dB vs 26.71dB),存储需求从 494+408KB 降至 494+24KB,减少 17 倍,验证了其存储效率优势。
7.【核心代码】
以下是某于提供的代码文件整理的“即插即用”核心模块介绍及关键代码段,涵盖模型加载、推理、训练三大核心场景,突出“即插即用”的便挂性。
7.1模型加载模块
功能
模型加载模块主要负责初始化模型,并在需要时加载之前保存的模型参数,以便继续训练或进行推理。
优势
- 支持多 GPU 训练,提高训练效率。
- 可以从指定的迭代次数开始继续训练,方便中断后恢复。
# 以 1_train_model_dnlut.py 为例
if __name__ == "__main__":
opt_inst = TrainOptions()
opt = opt_inst.parse()
modes = [i for i in opt.modes]
stages = opt.stages
model = getattr(model, opt.model)
model_G = model(nf=opt.nf, scale=opt.scale, modes=modes, stages=stages).cuda()
if opt.gpuNum > 1:
model_G = torch.nn.DataParallel(model_G, device_ids=list(range(opt.gpuNum)))
# Load saved params
if opt.startIter > 0:
lm = torch.load(
opt.expDir, 'Model_{:06d}.pth'.format(opt.startIter))
model_G.load_state_dict(lm.state_dict(), strict=True)
lm = torch.load(opt.expDir, 'Opt_{:06d}.pth'.format(opt.startIter))
opt_G.load_state_dict(lm.state_dict())
7.2 模型训练部分
功能
模型训练部分负责对模型进行训练,包括数据加载、前向传播、计算损失、反向传播和参数更新等步骤,同时会定期进行验证和保存模型。
优势
- 使用 Adam 优化器和学习率调度器,提高训练效果。
- 支持多 GPU 训练,加速训练过程。
- 定期保存模型和优化器状态,方便后续恢复训练。
- 使用 TensorBoard 进行训练过程的可视化监控。
# 以 1_train_model_dnlut.py 为例
if __name__ == "__main__":
# ... 模型加载部分代码 ...
# Optimizers
params_G = list(filter(lambda p: p.requires_grad, model_G.parameters()))
opt_G = optim.Adam(params_G, lr=opt.lr0, betas=(0.9, 0.999), eps=1e-8, weight_decay=opt.weightDecay, amsgrad=False)
# LR
if opt.lr1 < 0:
lf = lambda x: (((1 + math.cos(x * math.pi / opt.totalIter)) / 2) ** 1.0) * 0.8 + 0.2
else:
lr_b = opt.lr1 / opt.lr0
lr_a = 1 - lr_b
lf = lambda x: (((1 + math.cos(x * math.pi / opt.totalIter)) / 2) ** 1.0) * lr_a + lr_b
scheduler = optim.lr_scheduler.LambdaLR(opt_G, lr_lambda=lf)
# Training dataset
train_iter = ProviderDN_C(opt.batchSize, opt.workerNum, opt.scale, opt.trainDir, opt.cropSize, opt.sigma)
# Valid dataset
valid = DNBenchmark(opt.valDir, sigma=opt.sigma)
# TRAINING
i = opt.startIter
for i in range(opt.startIter + 1, opt.totalIter + 1):
model_G.train()
# Data preparing
st = time.time()
im, lb = train_iter.next()
im = im.float().cuda()
lb = lb.float().cuda()
dT += time.time() - st
# TRAIN G
st = time.time()
opt_G.zero_grad()
pred = mulut_predict(model_G, im, 'train', opt)
loss_G = F.mse_loss(pred, lb)
loss_G.backward()
opt_G.step()
scheduler.step()
rT += time.time() - st
# For monitoring
accum_samples += opt.batchSize
l_accum[0] += loss_G.item()
# Show information
if i % opt.displayStep == 0:
writer.add_scalar('loss_Pixel', l_accum[0] / opt.displayStep, i)
logger.info("{} | Iter:{:6d}, Sample:{:6d}, GPixel:{:.2e}, dT:{:.4f}, rT:{:.4f}".format(
opt.expDir, i, accum_samples, l_accum[0] / opt.displayStep, dT / opt.displayStep,
rT / opt.displayStep))
l_accum = [0., 0., 0.]
dT = 0.
rT = 0.
# Save models
if i % opt.saveStep == 0:
if opt.gpuNum > 1:
SaveCheckpoint(model_G.module, opt_G, opt, i)
else:
SaveCheckpoint(model_G, opt_G, opt, i)
# Validation
if i % opt.valStep == 0:
# validation during multi GPU training
if opt.gpuNum > 1:
valid_steps(model_G.module, valid, opt, i)
else:
valid_steps(model_G, valid, opt, i)
7.3 推理模块
功能
推理模块负责使用训练好的模型对输入图像进行去噪处理,并计算输出图像的 PSNR 指标,同时将结果保存到指定目录。
优势
- 支持多种数据集的推理。
- 可以将输入图像、真实标签和模型输出保存为图像文件,方便可视化结果。
# 以 1_train_model_dnlut.py 为例
def valid_steps(model_G, valid, opt, iter):
datasets = ['CBSD68', 'Kodak', 'Urban100', 'McMaster']
with torch.no_grad():
model_G.eval()
for i in range(len(datasets)):
psnrs = []
files = valid.files[datasets[i]]
result_path = os.path.join(opt.valoutDir, datasets[i])
if not os.path.isdir(result_path):
os.makedirs(result_path)
for j in range(len(files)):
key = datasets[i] + '_' + files[j][:-4]
lb = valid.ims[key]
input_im = valid.ims[key + '_noise']
input_im = input_im.astype(np.float32) / 255.0
im = torch.Tensor(np.expand_dims(
np.transpose(input_im, [2, 0, 1]), axis=0)).cuda()
pred = mulut_predict(model_G, im, 'valid', opt)
pred = np.transpose(np.squeeze(
pred.data.cpu().numpy(), 0), [1, 2, 0])
pred = np.round(np.clip(pred, 0, 255)).astype(np.uint8)
left, right = pred, lb
psnrs.append(PSNR(left, right, opt.scale)) # CPSNR
if iter < 10000: # save input and gt at start
input_img = np.round(np.clip(input_im * 255.0, 0, 255)).astype(np.uint8)
Image.fromarray(input_img).save(
os.path.join(result_path, '{}_input.png'.format(key.split('_')[-1])))
Image.fromarray(lb.astype(np.uint8)).save(
os.path.join(result_path, '{}_gt.png'.format(key.split('_')[-1])))
Image.fromarray(pred).save(
os.path.join(result_path, '{}_net.png'.format(key.split('_')[-1])))
logger.info(
'Iter {} | Dataset {} | AVG Val PSNR: {:02f}'.format(iter, datasets[i], np.mean(np.asarray(psnrs))))
writer.add_scalar('PSNR_valid/{}'.format(datasets[i]), np.mean(np.asarray(psnrs)), iter)
writer.flush()
更多推荐
所有评论(0)