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 硬件模块结构。它把增益计算和像素校正分成两个阶段,便于流水线并行。
这个架构里,我特别想强调两点:
- 坐标生成要提前:像素还没到,坐标先算好。这样插值单元可以提前从 SRAM 里取数据,不卡流水线。
- 增益上限要钳位:边缘增益可能到 3x 甚至 4x,但乘完后像素值不能超过传感器最大输出。我一般设个 4095 的钳位(12 bit 传感器)。
4.5 标定与调试注意事项
最后聊几句标定的事。LSC 的效果好不好,一半看算法,一半看标定。
- 灰卡要均匀:我用过一块有轻微污渍的灰卡,标定出来的增益全是错的。后来换了专业级积分球光源,问题才解决。
- 多温度标定:镜头阴影会随温度漂移。我建议至少做 25°C 和 60°C 两组标定,然后线性插值。
- 边缘留余量:传感器边缘的像素响应往往不稳定。我习惯把增益网格的有效区域比传感器有效像素区缩 2~4 个像素,避免边缘异常。
曾经有个项目,标定时一切正常,量产后发现一批手机四角偏红。查了两个月,最后发现是镜头模组的组装公差导致光轴偏移。从那以后,我坚持在产线加一道 LSC 自校准流程。
好了,LSC 的核心内容就这些。亮度衰减和色彩阴影的成因要理解透,增益矩阵的网格密度要选对,插值方法要根据硬件资源权衡。做到这三点,你的 LSC 模块基本不会出大问题。