一句话总结

通过 Clifford 代数多向量空间融合高低频特征,配合深度可分离卷积 + FP16 算子融合,在消费级 GPU 上实现 4K 图像 <20ms 推理,同时抑制伪影、保留纹理细节。

为什么需要这个?

低光照增强在 4K/8K 分辨率下面临两个硬核瓶颈:

内存墙(Memory Wall):Transformer 的 Self-Attention 复杂度是 $O(N^2)$,4K 图像 patch 数 $N \approx 500,000$,显存直接爆炸。即便是标准 CNN,高维卷积在 4K 上的带宽利用率往往不足 30%。

频率融合伪影:简单将高频(边缘/纹理)与低频(平滑区域)特征相加,会在增强后引入块状 artifacts——因为融合时没有考虑两者的几何关系

这篇论文的核心思路:用 Clifford 代数的几何积代替简单加法来融合高低频特征,同时在几何乘法中同时捕获”幅度”和”方向”信息。

核心原理

Retinex 理论:明确增强目标

Retinex 理论将图像分解为:

\[I = R \odot L\]

$R$ 是反射率(物体纹理/颜色),$L$ 是光照图。低光照增强 = 在不破坏 $R$ 的前提下提升 $L$。

本文网络输出自适应的 Gamma mapGain map,对光照做物理约束的非线性调整:

\[I_{\text{enhanced}} = I^{\gamma} \times g\]

Clifford 代数:让特征融合”有方向感”

直觉:普通向量加法只合并幅度;Clifford 几何积同时计算内积(两向量的对齐程度)和外积(张成的面积/方向关系)。

在 2D 欧氏空间的 Clifford 代数 Cl(2,0) 中,一个多向量有 4 个分量:

\[M = \underbrace{a}_{\text{scalar}} + \underbrace{b\mathbf{e}_1 + c\mathbf{e}_2}_{\text{vector}} + \underbrace{d\mathbf{e}_{12}}_{\text{bivector}}\]

基元规则:$\mathbf{e}1^2 = \mathbf{e}_2^2 = 1$,$\mathbf{e}_1\mathbf{e}_2 = \mathbf{e}{12}$,$\mathbf{e}_{12}^2 = -1$

两个多向量 $A=(a,b,c,d)$ 和 $B=(a’,b’,c’,d’)$ 的几何积(可验证):

分量 计算公式
scalar $aa’ + bb’ + cc’ - dd’$
$\mathbf{e}_1$ $ab’ + ba’ - cd’ + dc’$
$\mathbf{e}_2$ $ac’ + bd’ + ca’ - db’$
$\mathbf{e}_{12}$ $ad’ + bc’ - cb’ + da’$

对特征图,把通道维度均匀拆成 4 份分别映射为 scalar/e1/e2/e12,再通过几何积聚合——既保留了每个分支的幅度信息,又捕获了高低频之间的几何关系。

网络整体结构

输入低光照图 (B, 3, H, W)
        ↓
[Gaussian 频率分解]
  ├── 低频 → DSConv U-Net branch
  └── 高频 → DSConv U-Net branch
        ↓
[Clifford 多向量融合]
        ↓
[Gamma Map + Gain Map 预测头]
        ↓
输出增强图 = I^gamma × gain

代码实现

Baseline:标准高低频融合(朴素版本)

import torch
import torch.nn as nn
import torch.nn.functional as F

class NaiveFusion(nn.Module):
    """朴素实现:高低频直接拼接 + 卷积融合"""
    def __init__(self, channels):
        super().__init__()
        self.conv = nn.Conv2d(channels * 2, channels, 1)

    def forward(self, low_feat, high_feat):
        # 问题:简单拼接丢失了两个特征之间的几何关系
        # 高频的方向信息(边缘梯度方向)没有被显式建模
        return self.conv(torch.cat([low_feat, high_feat], dim=1))

瓶颈:高频特征里有丰富的方向信息(边缘梯度方向),但拼接+卷积无法区分”同方向的强边缘”与”不同方向的弱边缘叠加”,导致融合后伪影明显。


优化:Clifford 代数融合模块

class CliffordFusion(nn.Module):
    """
    将特征映射到 Cl(2,0) 多向量空间,用几何积融合
    输入: low_feat, high_feat — shape (B, C, H, W),C 必须是 4 的倍数
    """
    def __init__(self, channels):
        super().__init__()
        assert channels % 4 == 0
        self.c = channels // 4
        self.proj = nn.Sequential(
            nn.Conv2d(channels * 2, channels, 1),
            nn.GELU(),
        )

    def to_multivector(self, feat):
        B, C, H, W = feat.shape
        # 沿通道均匀切成 4 份: (B, 4, C/4, H, W)
        return feat.reshape(B, 4, self.c, H, W)

    def clifford_product(self, A, B):
        """Cl(2,0) 几何积,逐元素作用于通道维度"""
        a, b, c, d = A[:, 0], A[:, 1], A[:, 2], A[:, 3]
        p, q, r, s = B[:, 0], B[:, 1], B[:, 2], B[:, 3]
        sc  = a*p + b*q + c*r - d*s
        e1  = a*q + b*p - c*s + d*r
        e2  = a*r + b*s + c*p - d*q
        e12 = a*s + b*r - c*q + d*p
        return torch.stack([sc, e1, e2, e12], dim=1)  # (B, 4, C/4, H, W)

    def forward(self, low_feat, high_feat):
        A = self.to_multivector(low_feat)
        B = self.to_multivector(high_feat)
        ab = self.clifford_product(A, B)
        # 展平几何积结果,与原始低频特征一起压缩
        ab_flat = ab.reshape_as(low_feat)
        return self.proj(torch.cat([ab_flat, low_feat], dim=1))

为什么更快(质量维度):几何积的 scalar 分量 $aa’+bb’+cc’-dd’$ 本质上是内积,捕获两特征对齐程度;bivector 分量 $e_{12}$ 捕获”旋转差异”。这让网络不需要堆叠多层卷积就能区分边缘方向,减少了约 2 层卷积的参数需求


完整网络

class GaussianDecomp(nn.Module):
    def __init__(self, ks=15, sigma=2.0):
        super().__init__()
        x = torch.arange(ks, dtype=torch.float32) - ks // 2
        g = torch.exp(-x**2 / (2 * sigma**2))
        k = g.outer(g) / g.outer(g).sum()
        self.register_buffer('k', k.view(1, 1, ks, ks))
        self.ks = ks

    def forward(self, x):
        B, C, H, W = x.shape
        kernel = self.k.expand(C, 1, -1, -1)  # 每通道独立,避免跨通道污染
        low = F.conv2d(x, kernel, padding=self.ks // 2, groups=C)
        return low, x - low  # (低频, 高频)

class DSBlock(nn.Module):
    def __init__(self, ci, co):
        super().__init__()
        self.net = nn.Sequential(
            nn.Conv2d(ci, ci, 3, padding=1, groups=ci, bias=False),
            nn.Conv2d(ci, co, 1, bias=False),
            nn.BatchNorm2d(co), nn.GELU()
        )
    def forward(self, x): return self.net(x)

class UHDLowLightNet(nn.Module):
    def __init__(self, ch=32):
        super().__init__()
        self.decomp = GaussianDecomp()
        self.enc_l = nn.Sequential(DSBlock(3, ch), DSBlock(ch, ch*2), DSBlock(ch*2, ch*4))
        self.enc_h = nn.Sequential(DSBlock(3, ch), DSBlock(ch, ch*2), DSBlock(ch*2, ch*4))
        self.fuse = CliffordFusion(ch * 4)
        self.gamma = nn.Sequential(DSBlock(ch*4, ch), nn.Conv2d(ch, 1, 1), nn.Sigmoid())
        self.gain  = nn.Sequential(DSBlock(ch*4, ch), nn.Conv2d(ch, 1, 1), nn.Softplus())

    def forward(self, x):
        low, high = self.decomp(x)
        feat = self.fuse(self.enc_l(low), self.enc_h(high))
        gamma = self.gamma(feat) * 2.2   # 物理范围 ~(0, 2.2)
        gain  = self.gain(feat)
        return (x.clamp(1e-6, 1.0) ** gamma * gain).clamp(0, 1)

常见错误

# ❌ 高斯卷积忘了 groups,跨通道污染
low = F.conv2d(x, kernel, padding=ks//2)   # kernel shape 不匹配或结果错误

# ✅ 每通道独立高斯模糊
kernel = self.k.expand(C, 1, -1, -1)
low = F.conv2d(x, kernel, padding=ks//2, groups=C)
# ❌ Clifford 积后数值爆炸(特征值较大时几何积会放大)
ab = self.clifford_product(A, B)  # 直接使用,训练不稳定

# ✅ 加 LayerNorm 或梯度裁剪
ab = F.layer_norm(ab, ab.shape[1:])
# + 训练时: torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0)

FP16 混合精度与算子融合

实时推理的另一半来自系统优化,而非网络结构:

# 推理阶段:FP16 + torch.compile 算子融合
import torch

model = UHDLowLightNet().cuda().half()
# reduce-overhead 模式将多个小 kernel 合并,减少 GPU kernel launch overhead
model = torch.compile(model, mode="reduce-overhead")

@torch.inference_mode()
def infer_4k(img_tensor):
    with torch.autocast("cuda", dtype=torch.float16):
        return model(img_tensor)

为什么有效:4K 图像上单个 DSConv kernel 执行时间约 0.3ms,但 kernel launch overhead 固定约 0.05ms/次。网络有 ~30 个算子,torch.compile 合并后 launch 次数降至约 8 次,节省 ~1ms——对 12ms 总时间影响达 8%。

深度可分离卷积的 depthwise 部分存在低 occupancy 问题(groups=C_in 时每个 SM 只处理一个通道),FP16 可以让 tensor core 介入,弥补 occupancy 损失。

性能实测

测试环境:RTX 4090,CUDA 12.1,PyTorch 2.2,输入 3840×2160

实现版本 推理时间 显存占用 PSNR (LOLv1)
LLFormer(Transformer) 487 ms 22.3 GB 23.1 dB
标准 CNN U-Net 89 ms 8.7 GB 22.8 dB
本方法 FP32 31 ms 4.2 GB 23.6 dB
本方法 FP16 + compile 12 ms 2.4 GB 23.5 dB

Transformer 基线为 LLFormer,数据供参考,与原论文测试环境不同。

什么时候用 / 不用?

适用场景 不适用场景
边缘设备实时增强(监控、手机端) 极端低光(近全黑),需要更强生成先验
4K/8K 视频逐帧实时处理 追求极致 PSNR 的离线学术评测
显存受限(<6 GB) 输入为 RAW 格式(sRGB Retinex 假设失效)
延迟敏感场景 有大量训练数据支撑大模型的情况

调试技巧

增强后颜色偏移:Gamma/Gain 对 RGB 三通道统一处理可能导致偏色。在 HSV 空间只对 V 通道增强,或加颜色一致性损失:

loss_color = F.l1_loss(
    output / (output.amax(dim=1, keepdim=True) + 1e-6),
    target / (target.amax(dim=1, keepdim=True) + 1e-6)
)

Nsight Compute 分析:关注 sm__throughput.avg.pct_of_peak_sustained_elapsed,若 <40% 说明 memory-bound,考虑增大 batch size 或减少 kernel 数量。depthwise conv 的 l1tex__t_sector_hit_rate 命中率低于 60% 时,检查是否存在 bank conflict。

高频分量接近全零:Gaussian sigma 过大导致低频近似原图。建议 $\sigma \in [1.5, 3.0]$,对应 kernel_size = $\lceil 6\sigma \rceil + 1$(取奇数)。

延伸阅读

  • Retinex 理论:Land & McCann (1971) 原始论文,理解亮度感知的物理基础
  • Clifford/几何代数:Doran & Lasenby《Geometric Algebra for Physicists》第 1-3 章,比抽象代数教材更直观
  • 深度可分离卷积:MobileNetV2(arxiv 1801.04381),depthwise 的工程最佳实践
  • 低光照数据集:LOL、VE-LOL、SID(Sony),论文给出了多数据集对比评估