4. 镜头阴影校正(LSC):从现象到工程落地

镜头阴影校正,圈内人通常叫它 LSC(Lens Shading Correction)。

这玩意儿说难不难,说简单也不简单。我最早接触它是在做一款手机摄像头模组的时候,当时拍出来的照片四角发暗,中间亮得像打了聚光灯。客户一看样片就皱眉头——嗯,那场景我到现在还记得。

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

先聊聊成因。镜头阴影,也叫 Vignetting,说白了就是传感器接收到的光,从中心到边缘不均匀。

原因主要有三个:

  • 光学原因:镜头本身是个圆,传感器是个矩形。边缘光线入射角度大,光通量自然就少了。你想想看,斜着照进来的光,和直射的光,哪个能量强?
  • 机械原因:镜头筒、滤镜支架这些物理结构,有时候会挡住一部分边缘光线。尤其是小光圈下,这个效应更明显。
  • 像素角度响应:现代传感器每个像素其实是个小“井”,光线斜着进来,有一部分会被相邻像素的微透镜挡住。这个叫像素串扰,也是阴影的帮凶。

我在项目中遇到过一种极端情况:某款广角镜头,边缘亮度只有中心的 40%。那画面,简直像戴了墨镜拍照。

核心结论:镜头阴影是乘性噪声,不是加性的。这意味着我们不能简单地减一个固定值,而是要对每个像素乘一个系数。

4.2 LSC 原理:增益矩阵

LSC 的核心思路很简单:给暗的地方补光,亮的地方不动

具体做法是建立一个增益矩阵(Gain Map)。这个矩阵和图像尺寸一样大,每个位置存一个增益值。处理时,把原始像素值乘上对应的增益,就完成了校正。

公式长这样:

I_corrected(x, y) = I_raw(x, y) × Gain(x, y)

但这里有个坑——增益矩阵不能直接存全尺寸,太费内存了。实际工程中,我们通常存一个稀疏网格,比如 16×16 或者 32×32 的网格点。处理时再用双线性插值算出每个像素的增益。

我的经验:网格密度选 16×16 通常够用。太密了浪费存储,太疏了校正不平滑。我曾经试过 8×8 的网格,结果校正后出现明显的块状痕迹,像马赛克一样——嗯,那画面太美我不敢看。

4.3 LSC 校准流程:灰卡拍摄

校准流程,说白了就是拍一张均匀光照下的灰卡,然后反推增益矩阵。

标准步骤是这样的:

  1. 准备灰卡:用标准的 18% 灰卡,或者白色漫反射板。注意表面要均匀,不能有污渍。
  2. 搭建均匀光源:用积分球或者双灯对称照明。我建议用积分球,效果最稳定。有一次我用两个台灯凑合,结果校准出来的增益矩阵全是条纹——光源不均匀的锅。
  3. 拍摄 RAW 图:关闭所有数字增益,用最低 ISO,手动对焦到无穷远。光圈开到最大,因为大光圈下阴影最明显。
  4. 计算增益:对每个像素,增益 = 中心亮度均值 / 当前像素亮度。注意要取多个帧平均,消除噪声。
  5. 生成网格:把全尺寸增益矩阵降采样到网格,保存到校准文件。

避坑指南:我曾经犯过一个低级错误——拍灰卡时忘了关自动白平衡。结果增益矩阵里混入了色温信息,校正后的图像偏色严重。记住:校准 LSC 时,所有自动功能都要关掉。

4.4 LSC 实现细节与性能优化

到了工程实现这一步,细节就多了。我挑几个关键点说说。

4.4.1 插值算法选择

双线性插值是主流选择。但要注意边界处理:网格边缘的像素,需要 clamp 到最近的网格点。

// 伪代码:双线性插值计算增益
float getGain(float x, float y, float* grid, int gridW, int gridH) {
    int ix = (int)x;
    int iy = (int)y;
    float fx = x - ix;
    float fy = y - iy;
    
    float g00 = grid[iy * gridW + ix];
    float g10 = grid[iy * gridW + ix + 1];
    float g01 = grid[(iy+1) * gridW + ix];
    float g11 = grid[(iy+1) * gridW + ix + 1];
    
    float g0 = g00 * (1-fx) + g10 * fx;
    float g1 = g01 * (1-fx) + g11 * fx;
    
    return g0 * (1-fy) + g1 * fy;
}

4.4.2 性能优化三板斧

手机平台上,LSC 必须在几毫秒内跑完。我常用的优化手段:

  • 查表法:预计算所有可能的增益值,存成 LUT。运行时直接查表,省去浮点运算。
  • 定点化:把浮点增益转成 Q8.8 定点数,用整数乘法代替浮点乘法。速度能快 3-5 倍。
  • NEON 指令集:ARM 平台用 NEON 做向量化,一次处理 8 个像素。我优化过的一个项目,从纯 C 改成 NEON 后,耗时从 12ms 降到了 2ms。

小技巧:如果芯片支持硬件 LSC,优先用硬件。我见过不少团队自己写软件 LSC,结果功耗和延迟都打不过硬件方案。硬件不支持的场景,再用软件兜底。

4.4.3 多通道处理

RAW 图通常有 R、G、B 三个通道,每个通道的阴影特性不同。所以需要为每个通道单独生成增益矩阵。

但注意:G 通道有两个(G1 和 G2),它们的阴影特性几乎一样,可以共用同一个增益矩阵。省一点是一点。

通道 增益矩阵 说明
R Gain_R 单独校准
G1 / G2 Gain_G 共用
B Gain_B 单独校准

4.5 知识体系总览

下面这张图,是我自己梳理的 LSC 知识框架。你可以把它当作学习路线图:

镜头阴影校正(LSC)知识体系 LSC 核心 阴影成因 光学原因 机械遮挡 像素角度响应 增益矩阵原理 乘性校正 稀疏网格存储 双线性插值 校准流程 灰卡拍摄 均匀光源 增益计算 实现细节 插值算法 多通道处理 边界处理 性能优化 查表法 定点化 NEON 向量化 目标:均匀亮度 + 低功耗 + 实时处理

这张图把 LSC 拆成了五个模块:成因、原理、校准、实现、优化。每个模块之间是递进关系——先理解为什么,再知道怎么做,最后考虑怎么做得快。

我个人习惯把这张图打印出来贴在工位上。遇到问题,扫一眼就知道卡在哪个环节了。


好了,LSC 的核心内容就这些。说白了,它就是个“补光”的活,但补得均匀、补得高效,才是真本事。下次你看到一张四角均匀的照片,可以想想背后那个默默工作的增益矩阵——嗯,它值得被记住。

一句话总结:镜头阴影校正,就是用预先校准好的增益矩阵,对 RAW 图每个像素做乘性补偿,让整张图亮度均匀。工程上要兼顾精度、速度和存储开销。

专注资料整理