4、镜头阴影校正(LSC):从现象到实战

镜头阴影校正,圈里人常叫 LSC。这玩意儿说白了,就是解决照片四周发暗、发偏色的问题。你拿手机拍一张纯白墙壁,如果中间亮、四角暗,那就是典型的镜头阴影。

我刚开始做 ISP 时,觉得这步不就是乘个增益嘛,有啥难的?直到有一次,客户反馈说他们的无人机拍出来的画面,左上角总是偏绿...嗯,从那以后,我再也不敢小看 LSC 了。

4.1 镜头阴影现象:为什么会暗角?

镜头阴影分两种:亮度阴影和色度阴影。咱们先看物理成因。

阴影类型 物理原因 表现特征
亮度阴影(Luma Shading) 镜头光学余弦四次方定律:边缘光线入射角大,到达 sensor 的光强按 cos⁴θ 衰减 四角变暗,亮度下降明显
色度阴影(Color Shading) 不同波长光线通过镜头时折射率不同,导致 RGB 通道衰减不一致 边缘偏色,常见偏绿或偏紫
微透镜串扰 sensor 像素微透镜对边缘入射光的收集效率下降 加剧暗角和偏色

你想想看,一个 1200 万像素的 sensor,中心像素和边缘像素接收到的光量可能差 30%-50%。如果不校正,整张图就没法看了。

关键指标: 阴影比(Shading Ratio)= 边缘亮度 / 中心亮度。一般要求校正后 > 95%。

4.2 增益补偿原理:怎么把暗角拉回来?

原理其实很简单:给每个像素乘一个补偿系数。中心区域系数接近 1.0,边缘区域系数大于 1.0。但这里有个坑——你不能简单粗暴地乘一个固定值。

为什么?因为镜头阴影不是线性的。它跟镜头焦距、光圈、对焦距离都有关系。我记得有一次调试一个广角镜头,F1.8 光圈下阴影比只有 60%,但收到 F4.0 就变成 80% 了。同一个镜头,不同光圈,补偿曲线完全不同。

增益补偿的数学模型:

// 基本补偿公式
I_corrected(x, y) = I_raw(x, y) × G(x, y)

// 其中 G(x, y) 是位置相关的增益系数
// 对于 Bayer 格式,每个通道独立计算
G_r(x, y) = 1 / gain_r(x, y)
G_gr(x, y) = 1 / gain_gr(x, y)
G_gb(x, y) = 1 / gain_gb(x, y)
G_b(x, y) = 1 / gain_b(x, y)

这里要注意,Bayer 格式下 R、Gr、Gb、B 四个通道的阴影曲线不一样。如果你只用一个增益矩阵,色度阴影就校正不了。我见过不少方案,亮度拉回来了,但边缘偏色更严重了。

避坑指南: 我曾经在一个项目里偷懒,只做了亮度 LSC,没做色度 LSC。结果客户拿出去拍蓝天,四角全变紫了。后来老老实实把四个通道的增益表都做了。

4.3 网格校正算法:工程上怎么实现?

理论讲完了,咱们聊聊工程实现。你不可能给每个像素都存一个增益系数——那太费内存了。实际做法是:用网格(Grid)来近似。

网格校正的核心思路:

  1. 标定阶段: 拍一张均匀光照下的灰卡或白板,计算出每个像素的理想增益
  2. 网格采样: 按固定步长(如 16x16、32x32 像素)采样增益值,形成网格表
  3. 插值计算: 运行时对网格内的像素做双线性插值,得到每个像素的增益
  4. 增益应用: 将插值后的增益乘到原始像素上

网格大小怎么选?我个人的经验:

网格步长 内存占用(1080P) 校正精度 适用场景
8x8 ~1.2MB 高 高精度、大光圈镜头
16x16 ~300KB 中 大多数手机镜头
32x32 ~75KB 低 小光圈、低要求场景

双线性插值的实现:

// 双线性插值计算增益
float interpolate_gain(float x, float y, float grid[][], int grid_w, int grid_h) {
    // 计算网格索引
    int x0 = (int)(x / grid_step);
    int y0 = (int)(y / grid_step);
    int x1 = min(x0 + 1, grid_w - 1);
    int y1 = min(y0 + 1, grid_h - 1);
    
    // 计算小数部分
    float dx = (x - x0 * grid_step) / grid_step;
    float dy = (y - y0 * grid_step) / grid_step;
    
    // 双线性插值
    float top = grid[y0][x0] * (1 - dx) + grid[y0][x1] * dx;
    float bottom = grid[y1][x0] * (1 - dx) + grid[y1][x1] * dx;
    
    return top * (1 - dy) + bottom * dy;
}

工程技巧: 实际硬件实现时,网格步长通常选 2 的幂次(16、32、64),这样除法可以用移位代替,节省硬件资源。我建议用 16x16 起步,精度不够再加密。

4.4 LSC 与 AI 的融合:新思路

传统 LSC 依赖标定,但标定过程很麻烦。每个镜头模组都要单独标,产线效率低。而且镜头老化、温度变化都会导致阴影曲线漂移。

我最近在尝试用轻量级 CNN 来做自适应 LSC:

  • 输入: 原始 Bayer 图像 + 镜头参数(光圈、焦距、温度)
  • 输出: 每个通道的增益网格
  • 网络结构: 3-4 层卷积,参数量控制在 50K 以内
  • 优势: 无需产线标定,自动适应镜头差异和环境变化

当然,AI 方案也有坑。推理延迟要控制在 1ms 以内,否则影响帧率。我建议先用传统 LSC 做粗校正,再用 AI 做精细补偿,这样既保证实时性,又提升精度。

我的建议: 如果你刚开始做 LSC,先把传统网格校正吃透。AI 融合是加分项,不是必选项。基础打牢了,再考虑上 AI。

4.5 本章知识体系

下面这张图总结了 LSC 的核心流程和关键点:

镜头阴影校正知识体系 镜头阴影校正(LSC)知识体系 镜头阴影现象 增益补偿原理 网格校正算法 亮度阴影 色度阴影 余弦四次方 通道独立补偿 网格标定 双线性插值 核心目标:消除暗角 + 校正偏色 工程实现:网格表 + 插值 + 通道独立处理 AI 融合方向:自适应网格预测

4.6 实战要点总结

最后,我把 LSC 调试中的几个关键点列出来,供你参考:

  • 标定环境: 用积分球或均匀光源,保证光照均匀度 > 98%。我见过有人用白墙标定,结果墙本身就不均匀,校正完反而更糟。
  • 温度补偿: 镜头阴影随温度漂移,建议在 -20°C 到 60°C 范围内做多组标定,运行时根据温度查表。
  • 边界处理: 图像边缘的像素可能没有完整的网格邻居,需要做镜像或边界复制,避免插值越界。
  • 性能优化: 网格插值可以用定点数实现,避免浮点运算。我习惯把增益放大 256 倍存成整数,运行时移位回来。

再次提醒: 千万不要忽略色度阴影!亮度校正看起来好了,但偏色问题会在后期白平衡阶段放大。我曾经吃过这个亏,调试了三天才发现是 LSC 没做通道分离。

好了,LSC 的内容就到这里。记住一句话:镜头阴影校正,不是简单的乘增益,而是对光学系统、sensor 特性和工程实现的综合把控。把基础打牢,后面做 AI 融合才心里有底。