4、镜头阴影校正(LSC):从现象到实战
镜头阴影校正,圈里人常叫 LSC。这玩意儿说白了,就是解决照片四周发暗、发偏色的问题。你拿手机拍一张纯白墙壁,如果中间亮、四角暗,那就是典型的镜头阴影。
我刚开始做 ISP 时,觉得这步不就是乘个增益嘛,有啥难的?直到有一次,客户反馈说他们的无人机拍出来的画面,左上角总是偏绿...嗯,从那以后,我再也不敢小看 LSC 了。
4.1 镜头阴影现象:为什么会暗角?
镜头阴影分两种:亮度阴影和色度阴影。咱们先看物理成因。
| 阴影类型 | 物理原因 | 表现特征 |
|---|---|---|
| 亮度阴影(Luma Shading) | 镜头光学余弦四次方定律:边缘光线入射角大,到达 sensor 的光强按 cos⁴θ 衰减 | 四角变暗,亮度下降明显 |
| 色度阴影(Color Shading) | 不同波长光线通过镜头时折射率不同,导致 RGB 通道衰减不一致 | 边缘偏色,常见偏绿或偏紫 |
| 微透镜串扰 | sensor 像素微透镜对边缘入射光的收集效率下降 | 加剧暗角和偏色 |
你想想看,一个 1200 万像素的 sensor,中心像素和边缘像素接收到的光量可能差 30%-50%。如果不校正,整张图就没法看了。
关键指标: 阴影比(Shading Ratio)= 边缘亮度 / 中心亮度。一般要求校正后 > 95%。
4.2 增益补偿原理:怎么把暗角拉回来?
原理其实很简单:给每个像素乘一个补偿系数。中心区域系数接近 1.0,边缘区域系数大于 1.0。但这里有个坑——你不能简单粗暴地乘一个固定值。
为什么?因为镜头阴影不是线性的。它跟镜头焦距、光圈、对焦距离都有关系。我记得有一次调试一个广角镜头,F1.8 光圈下阴影比只有 60%,但收到 F4.0 就变成 80% 了。同一个镜头,不同光圈,补偿曲线完全不同。
增益补偿的数学模型:
// 基本补偿公式
I_corrected(x, y) = I_raw(x, y) × G(x, y)
// 其中 G(x, y) 是位置相关的增益系数
// 对于 Bayer 格式,每个通道独立计算
G_r(x, y) = 1 / gain_r(x, y)
G_gr(x, y) = 1 / gain_gr(x, y)
G_gb(x, y) = 1 / gain_gb(x, y)
G_b(x, y) = 1 / gain_b(x, y)
这里要注意,Bayer 格式下 R、Gr、Gb、B 四个通道的阴影曲线不一样。如果你只用一个增益矩阵,色度阴影就校正不了。我见过不少方案,亮度拉回来了,但边缘偏色更严重了。
避坑指南: 我曾经在一个项目里偷懒,只做了亮度 LSC,没做色度 LSC。结果客户拿出去拍蓝天,四角全变紫了。后来老老实实把四个通道的增益表都做了。
4.3 网格校正算法:工程上怎么实现?
理论讲完了,咱们聊聊工程实现。你不可能给每个像素都存一个增益系数——那太费内存了。实际做法是:用网格(Grid)来近似。
网格校正的核心思路:
- 标定阶段: 拍一张均匀光照下的灰卡或白板,计算出每个像素的理想增益
- 网格采样: 按固定步长(如 16x16、32x32 像素)采样增益值,形成网格表
- 插值计算: 运行时对网格内的像素做双线性插值,得到每个像素的增益
- 增益应用: 将插值后的增益乘到原始像素上
网格大小怎么选?我个人的经验:
| 网格步长 | 内存占用(1080P) | 校正精度 | 适用场景 |
|---|---|---|---|
| 8x8 | ~1.2MB | 高 | 高精度、大光圈镜头 |
| 16x16 | ~300KB | 中 | 大多数手机镜头 |
| 32x32 | ~75KB | 低 | 小光圈、低要求场景 |
双线性插值的实现:
// 双线性插值计算增益
float interpolate_gain(float x, float y, float grid[][], int grid_w, int grid_h) {
// 计算网格索引
int x0 = (int)(x / grid_step);
int y0 = (int)(y / grid_step);
int x1 = min(x0 + 1, grid_w - 1);
int y1 = min(y0 + 1, grid_h - 1);
// 计算小数部分
float dx = (x - x0 * grid_step) / grid_step;
float dy = (y - y0 * grid_step) / grid_step;
// 双线性插值
float top = grid[y0][x0] * (1 - dx) + grid[y0][x1] * dx;
float bottom = grid[y1][x0] * (1 - dx) + grid[y1][x1] * dx;
return top * (1 - dy) + bottom * dy;
}
工程技巧: 实际硬件实现时,网格步长通常选 2 的幂次(16、32、64),这样除法可以用移位代替,节省硬件资源。我建议用 16x16 起步,精度不够再加密。
4.4 LSC 与 AI 的融合:新思路
传统 LSC 依赖标定,但标定过程很麻烦。每个镜头模组都要单独标,产线效率低。而且镜头老化、温度变化都会导致阴影曲线漂移。
我最近在尝试用轻量级 CNN 来做自适应 LSC:
- 输入: 原始 Bayer 图像 + 镜头参数(光圈、焦距、温度)
- 输出: 每个通道的增益网格
- 网络结构: 3-4 层卷积,参数量控制在 50K 以内
- 优势: 无需产线标定,自动适应镜头差异和环境变化
当然,AI 方案也有坑。推理延迟要控制在 1ms 以内,否则影响帧率。我建议先用传统 LSC 做粗校正,再用 AI 做精细补偿,这样既保证实时性,又提升精度。
我的建议: 如果你刚开始做 LSC,先把传统网格校正吃透。AI 融合是加分项,不是必选项。基础打牢了,再考虑上 AI。
4.5 本章知识体系
下面这张图总结了 LSC 的核心流程和关键点:
4.6 实战要点总结
最后,我把 LSC 调试中的几个关键点列出来,供你参考:
- 标定环境: 用积分球或均匀光源,保证光照均匀度 > 98%。我见过有人用白墙标定,结果墙本身就不均匀,校正完反而更糟。
- 温度补偿: 镜头阴影随温度漂移,建议在 -20°C 到 60°C 范围内做多组标定,运行时根据温度查表。
- 边界处理: 图像边缘的像素可能没有完整的网格邻居,需要做镜像或边界复制,避免插值越界。
- 性能优化: 网格插值可以用定点数实现,避免浮点运算。我习惯把增益放大 256 倍存成整数,运行时移位回来。
再次提醒: 千万不要忽略色度阴影!亮度校正看起来好了,但偏色问题会在后期白平衡阶段放大。我曾经吃过这个亏,调试了三天才发现是 LSC 没做通道分离。
好了,LSC 的内容就到这里。记住一句话:镜头阴影校正,不是简单的乘增益,而是对光学系统、sensor 特性和工程实现的综合把控。把基础打牢,后面做 AI 融合才心里有底。