4. 镜头阴影校正(LSC):成因分析、增益映射表、校正算法

镜头阴影校正,简称 LSC。这名字听着挺学术,说白了就是解决照片四周发暗的问题。你拿手机对着白墙拍一张,如果四角比中心暗,那就是镜头阴影。

我刚开始做 ISP 时,觉得这步很简单——不就是把四周像素乘个系数吗?后来踩了不少坑才发现,LSC 的细节远比想象中多。今天咱们就把这事聊透。

4.1 阴影是怎么来的?

镜头阴影分两种:亮度阴影和色度阴影。咱们一个一个说。

类型 成因 表现
亮度阴影(Luma Shading) 镜头光学设计导致边缘进光量少于中心 四角变暗,亮度下降
色度阴影(Color Shading) 不同波长光线通过镜头折射角度不同 四角偏色,常见偏绿或偏紫

为什么会这样?你想想看,镜头中心的光线几乎是直射到 sensor 上的,而边缘的光线需要斜着穿过去。光路更长,衰减更大,这是物理规律,躲不开。

关键指标:阴影程度通常用中心亮度与四角亮度的比值来衡量。比如中心亮度 100,四角只有 70,那阴影就是 30%。好的镜头能做到 10% 以内,差的可能超过 40%。

我记得有一次调试一个广角模组,四角亮度只有中心的 55%。当时 sensor 已经饱和了,但四角还是暗的。这就是典型的亮度阴影太严重,必须靠 LSC 硬拉回来。

4.2 增益映射表——LSC 的核心

校正阴影的思路很简单:给每个像素算一个增益值,暗的地方多补,亮的地方少补。这些增益值存在一张表里,就是增益映射表(Gain Map)。

这张表长什么样?

// 假设 sensor 分辨率 1920x1080
// 增益映射表通常按网格采样,比如 16x16 网格
// 每个网格点存一个增益值,中间区域用插值计算

// 伪代码示例:生成增益映射表
for (int row = 0; row < GRID_ROWS; row++) {
    for (int col = 0; col < GRID_COLS; col++) {
        // 计算该网格点距离中心的归一化距离
        float dist = sqrt(pow(row - center_row, 2) + pow(col - center_col, 2));
        dist /= max_dist;  // 归一化到 [0, 1]
        
        // 增益 = 1 / cos^4(θ) 的近似
        // 实际项目中用多项式拟合
        gain_map[row][col] = 1.0 + a2 * pow(dist, 2) + a4 * pow(dist, 4);
    }
}

实际项目中,增益映射表不是随便算的。我习惯的做法是:

  1. 拍一张均匀亮度的灰卡,确保整个画面亮度一致
  2. 提取每个像素的原始值,得到实际的亮度分布
  3. 计算每个位置需要的增益,使校正后亮度均匀
  4. 下采样到网格,比如 16x16 或 32x32
  5. 平滑处理,避免网格边界出现跳变

个人经验:网格密度不是越高越好。太密了会放大 sensor 本身的噪声,校正后反而出现条纹。我一般用 16x16 起步,效果不够再加密到 32x32。64x64 以上基本没必要。

4.3 校正算法怎么跑?

有了增益映射表,校正过程就是查表 + 插值 + 乘系数。流程如下:

// LSC 校正核心流程
for (每个像素 (x, y)) {
    // 1. 找到该像素所在的网格区域
    grid_x = x / grid_step_x;
    grid_y = y / grid_step_y;
    
    // 2. 双线性插值计算该像素的增益
    gain = bilinear_interp(gain_map, grid_x, grid_y, 
                           x % grid_step_x, y % grid_step_y);
    
    // 3. 对 R、G、B 分别应用增益
    // 注意:色度阴影需要不同通道不同增益
    pixel.r = clamp(pixel.r * gain_r, 0, max_val);
    pixel.g = clamp(pixel.g * gain_g, 0, max_val);
    pixel.b = clamp(pixel.b * gain_b, 0, max_val);
}

这里有个坑:色度阴影的校正。亮度阴影对所有通道影响一致,但色度阴影不是。比如四角偏绿,那 R 和 B 通道的增益就要比 G 通道大。我曾经遇到过只做亮度校正,结果四角亮度正常了,但偏色更明显了——因为 G 通道补多了。

避坑指南:我曾经在量产前发现一批模组的 LSC 效果不一致。排查了两天才找到原因——增益映射表是在实验室 25°C 下标定的,但产线环境 35°C,sensor 暗电流变了,阴影分布也跟着变了。从那以后,我坚持在产线环境温度下重新标定 LSC 表。

4.4 增益映射表的存储与压缩

一张 1920x1080 的 sensor,如果用 16x16 网格,每个增益值存 16-bit,那整张表才 512 字节。但实际项目中,我们通常存多组表:

  • 不同色温下的表:D65(日光)、A 光源(白炽灯)、TL84(荧光灯)
  • 不同焦距的表:变焦镜头每个焦段都要单独标定
  • 不同增益下的表:高 ISO 时阴影表现会变化

这么多表,存起来很占空间。我常用的压缩方法:

  1. 对称性利用:镜头阴影通常是对称的,只存 1/4 象限,其余镜像
  2. 差值存储:存相对于中心增益的差值,中心为 1.0,四周存增量
  3. 定点化:用 Q8.8 格式存,精度够用,省一半空间

4.5 LSC 的硬件实现要点

嵌入式系统里,LSC 通常由 ISP 硬件模块完成。软件只需要把增益映射表配置进去。但有几个参数必须注意:

参数 说明 常见值
网格行数 垂直方向的网格数量 16, 32
网格列数 水平方向的网格数量 16, 32
增益精度 每个增益值的位宽 12-bit, 16-bit
插值方式 网格间像素的增益计算 双线性, 双三次
通道独立 R/G/B 是否使用不同表 是/否

硬件调试技巧:如果发现校正后画面出现网格状条纹,大概率是增益映射表精度不够或插值算法太粗糙。先检查网格密度,再检查增益值的量化误差。我遇到过 8-bit 精度不够导致条纹,换成 12-bit 就好了。

4.6 LSC 校正效果评估

怎么判断 LSC 做得好不好?我一般看三个指标:

  • 均匀性:校正后画面最亮和最暗区域的比值,要求 < 5%
  • 色差:四角和中心的色温差异,要求 Δuv < 0.01
  • 信噪比:校正后四角的噪声不能明显高于中心

嗯,这里要注意第三点。增益拉得越大,噪声也越大。如果镜头阴影太严重,四角增益到了 2.0 以上,那噪声会被放大 6dB。这时候光靠 LSC 是不够的,得从镜头设计上改善。

我个人习惯在评估时拍一张灰卡,然后用 imatest 或自己写的脚本分析亮度分布。如果看到校正后的亮度曲线有锯齿,说明插值参数需要调。如果看到四角有偏色,说明色度阴影没处理好。

镜头阴影校正(LSC)流程 输入 RAW 图像 查增益映射表(Gain Map) 双线性插值计算增益 R/G/B 分通道校正 输出校正后图像 注意事项 • 增益映射表需预先标定 • 色度阴影需独立通道增益 • 增益过大噪声会被放大 常见问题 • 网格条纹 → 精度不足 • 四角偏色 → 色度校正未做 • 校正后噪声大 → 增益过高

这张图把 LSC 的完整流程串起来了。从 RAW 输入开始,查表、插值、分通道校正,最后输出。每一步都有坑,每一步都需要调试。

好了,LSC 的核心内容就这些。记住一句话:阴影校正不是简单的乘系数,而是对镜头光学特性的逆向补偿。理解了这个,你就能做出漂亮的校正效果。