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);
}
}
实际项目中,增益映射表不是随便算的。我习惯的做法是:
- 拍一张均匀亮度的灰卡,确保整个画面亮度一致
- 提取每个像素的原始值,得到实际的亮度分布
- 计算每个位置需要的增益,使校正后亮度均匀
- 下采样到网格,比如 16x16 或 32x32
- 平滑处理,避免网格边界出现跳变
个人经验:网格密度不是越高越好。太密了会放大 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/4 象限,其余镜像
- 差值存储:存相对于中心增益的差值,中心为 1.0,四周存增量
- 定点化:用 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 输入开始,查表、插值、分通道校正,最后输出。每一步都有坑,每一步都需要调试。
好了,LSC 的核心内容就这些。记住一句话:阴影校正不是简单的乘系数,而是对镜头光学特性的逆向补偿。理解了这个,你就能做出漂亮的校正效果。