4. 镜头阴影校正(LSC):亮度衰减与色彩阴影成因、增益矩阵校正法、网格插值实现

镜头阴影校正,圈内人常叫它 LSC。说白了,就是解决照片四周发暗、偏色的问题。你拿手机拍一张白墙,如果四角比中心暗,那就是亮度衰减;如果四角还带点绿或带点红,那就是色彩阴影。

我刚开始做 ISP 时,觉得这步很简单——乘个系数不就完了?后来被一款广角镜头坑过一次,才老老实实把 LSC 当回事。嗯,咱们今天就把这事聊透。

4.1 亮度衰减与色彩阴影的物理成因

先说说为什么会暗。镜头是个物理器件,光线从中心通过和从边缘通过,路径长度不一样。中心光线直来直去,边缘光线斜着走,能量自然就分散了。

这里面有两个核心效应:

  • 余弦四次方定律:像面照度与入射角余弦的四次方成正比。角度一大,亮度掉得飞快。
  • 镜头渐晕:镜筒本身会遮挡一部分边缘光线,尤其是广角镜头和大光圈时更明显。

色彩阴影又是怎么回事?

其实很简单——不同波长的光在镜头里折射率不同。红光偏折小,蓝光偏折大。到了边缘,红蓝光的落点就不一样了。再加上传感器每个像素的微透镜也有角度响应差异,结果就是:四角偏绿或偏紫。

关键认知:亮度衰减是几何问题,色彩阴影是色散问题。两者常常同时出现,但校正策略不同。

我在项目中遇到过一款 120° 广角镜头,边缘亮度掉到中心的 30%,而且四角明显偏绿。如果不做 LSC,整张图就像戴了个劣质滤镜。

4.2 增益矩阵校正法

最直接的校正思路:每个像素乘一个增益系数,把衰减补回来。

但问题来了——传感器有几百万甚至几千万像素,每个像素都存一个增益?那存储量太大了,硬件吃不消。

所以实际做法是:用网格代替全图。

我们把图像分成 M×N 个网格,每个网格顶点存一个增益值。网格内部的像素,通过插值算出增益。这就是增益矩阵校正法的核心。

增益矩阵通常按颜色通道分开存:

通道 网格尺寸(常见) 存储格式 典型位宽
R 17×13 定点数 Q4.8 12 bit
Gr 17×13 定点数 Q4.8 12 bit
Gb 17×13 定点数 Q4.8 12 bit
B 17×13 定点数 Q4.8 12 bit

我的习惯:网格密度不是越密越好。17×13 是很多芯片的默认配置,再密一倍,存储翻四倍,但画质提升肉眼几乎看不出来。

增益值的计算通常在标定阶段完成。你拍一张均匀亮度的灰卡,然后反推每个位置需要的补偿系数。公式很简单:

gain(x, y) = target_value / raw_value(x, y)

其中 target_value 通常是图像中心区域的亮度值。

4.3 网格插值实现

有了网格顶点上的增益,接下来就是插值。硬件里最常用的是双线性插值,偶尔用双三次。

4.3.1 双线性插值

双线性插值,说白了就是做两次线性插值。先插水平方向,再插垂直方向。

假设当前像素落在四个网格顶点 A、B、C、D 围成的矩形里:

// 双线性插值伪代码
float dx = (x - x0) / (x1 - x0);
float dy = (y - y0) / (y1 - y0);

float gain_top    = A * (1 - dx) + B * dx;
float gain_bottom = C * (1 - dx) + D * dx;
float gain        = gain_top * (1 - dy) + gain_bottom * dy;

硬件实现时,dx 和 dy 通常用定点小数表示。我建议保留 4~6 bit 小数精度,再低的话,网格边界会出现肉眼可见的带状条纹。

我曾经踩过的坑:双线性插值在网格边界处导数不连续。如果你用的镜头阴影变化很剧烈,边界处会看到「接缝」。解决办法是改用双三次,或者把网格加密。

4.3.2 双三次插值

双三次插值用 4×4 邻域,共 16 个顶点。它考虑了斜率连续性,插出来的增益曲面更平滑。

核心公式:

// 双三次插值核心函数
float cubic_hermite(float t) {
    // t 在 [0,1] 范围内
    return t * t * (3 - 2 * t);
}

// 先对每行做三次插值,再对列做三次插值
for (int i = 0; i < 4; i++) {
    row_gain[i] = cubic_interp(grid[i], t_x);
}
float gain = cubic_interp(row_gain, t_y);

双三次的硬件代价比双线性大不少:

指标 双线性 双三次
乘法器数量 6 32
查找表需求 无 需要 1 个系数 LUT
流水线级数 2~3 级 5~7 级
适用场景 普通手机镜头 广角/超广角镜头

我的建议:如果芯片面积和功耗允许,优先用双三次。尤其是现在 48M、64M 像素的传感器,双线性插值的微小瑕疵会被放大得很明显。

4.4 LSC 硬件流水线架构

下面这张图是我个人比较推荐的 LSC 硬件模块结构。它把增益计算和像素校正分成两个阶段,便于流水线并行。

LSC 硬件流水线架构 Raw 像素输入 坐标生成模块 增益网格 SRAM 插值计算单元 乘法校正单元 校正后像素 控制状态机 流水线说明: • 坐标生成与插值计算可并行,减少等待周期 • 增益网格 SRAM 建议用双端口,同时支持标定写入和实时读取 • 乘法校正单元需考虑溢出保护,增益上限通常设为 4.0x

这个架构里,我特别想强调两点:

  • 坐标生成要提前:像素还没到,坐标先算好。这样插值单元可以提前从 SRAM 里取数据,不卡流水线。
  • 增益上限要钳位:边缘增益可能到 3x 甚至 4x,但乘完后像素值不能超过传感器最大输出。我一般设个 4095 的钳位(12 bit 传感器)。

4.5 标定与调试注意事项

最后聊几句标定的事。LSC 的效果好不好,一半看算法,一半看标定。

  1. 灰卡要均匀:我用过一块有轻微污渍的灰卡,标定出来的增益全是错的。后来换了专业级积分球光源,问题才解决。
  2. 多温度标定:镜头阴影会随温度漂移。我建议至少做 25°C 和 60°C 两组标定,然后线性插值。
  3. 边缘留余量:传感器边缘的像素响应往往不稳定。我习惯把增益网格的有效区域比传感器有效像素区缩 2~4 个像素,避免边缘异常。

曾经有个项目,标定时一切正常,量产后发现一批手机四角偏红。查了两个月,最后发现是镜头模组的组装公差导致光轴偏移。从那以后,我坚持在产线加一道 LSC 自校准流程。

好了,LSC 的核心内容就这些。亮度衰减和色彩阴影的成因要理解透,增益矩阵的网格密度要选对,插值方法要根据硬件资源权衡。做到这三点,你的 LSC 模块基本不会出大问题。