4、镜头阴影校正(Lens Shading Correction)
镜头阴影校正,圈里人常叫 LSC(Lens Shading Correction)。说白了,就是解决照片四周发暗、发绿的问题。你想想看,用手机拍一张纯色的白墙,是不是经常发现中间亮、四周暗?这就是镜头阴影,也叫 Vignetting。
我刚开始做 ISP 的时候,觉得这玩意儿不就是个简单的增益嘛,四周乘个系数就完事了。后来踩了坑才发现,这里面的门道比我想象的多得多。今天咱们就把成因、校正表生成、还有径向渐变算法,一次性讲透。
4.1 镜头阴影的成因
镜头阴影不是单一原因造成的。我习惯把它拆成两类:亮度衰减和色彩偏移。
4.1.1 亮度衰减(Luminance Shading)
这个好理解。光线从镜头中心进入传感器,和从边缘进入传感器,路径长度不一样。边缘的光线要走更远的路,而且入射角度也偏了。根据 cos⁴θ 定律,边缘的照度会急剧下降。
cos⁴θ 定律:传感器上某点的照度,正比于该点与光轴夹角的余弦值的四次方。
θ = 0° 时,照度 100%;θ = 30° 时,照度只剩约 56%。
除了物理光学原因,还有镜头遮挡。有些镜头模组的设计,镜筒本身就会挡住一部分边缘光线。尤其是小光圈的时候,这个效应更明显。
4.1.2 色彩偏移(Color Shading)
这个坑我印象特别深。有一次调一个广角模组,亮度校正做完后,四周还是发绿。查了半天才发现,是微透镜阵列(Microlens Array)的问题。
Bayer 传感器的每个像素上面都有一个微透镜,用来把光线汇聚到感光区域。但边缘像素的微透镜,接收到的光线角度偏大,导致不同颜色的像素响应不一致。通常红色通道衰减最快,蓝色次之,绿色相对好一些。结果就是:四周偏绿。
| 阴影类型 | 根本原因 | 表现特征 | 校正难度 |
|---|---|---|---|
| 亮度衰减 | cos⁴θ 定律、镜头遮挡 | 四周均匀变暗 | 低(单通道增益即可) |
| 色彩偏移 | 微透镜入射角差异 | 四周偏绿/偏紫 | 中(需分通道处理) |
4.2 增益校正表生成
校正的核心思路很简单:给暗的地方补光,给亮的地方不动。但怎么补、补多少,这里就有讲究了。
4.2.1 标定流程
我个人习惯用 均匀光源 + 灰卡 来做标定。具体步骤是这样的:
- 拍摄均匀灰卡:用积分球或者均匀光源,拍一张 RAW 图。确保画面中间区域亮度在 50% 左右,别过曝也别欠曝。
- 提取 Bayer 通道:把 RAW 图拆成 R、Gr、Gb、B 四个通道。注意,Gr 和 Gb 在镜头阴影下表现不一样,别偷懒合并。
- 计算参考值:取图像中心区域(比如 10% 半径内)的像素均值,作为每个通道的参考亮度。
- 生成增益矩阵:对于每个像素位置 (x, y),增益 = 参考值 / 当前像素值。
- 平滑与压缩:原始增益矩阵有噪声,需要做平滑滤波。然后压缩成网格形式,方便硬件查表。
我的经验:标定时光源色温要跟实际场景匹配。用 D65 光源标定的校正表,拿到白炽灯下用,色彩偏移反而会更严重。
4.2.2 网格增益表
硬件不可能存一整张跟图像一样大的增益矩阵。通常的做法是:存一个稀疏网格,比如 16×16 或者 32×32 的网格点。每个网格点存一个增益值,中间的点靠插值算出来。
// 网格增益表结构示例
struct LSC_GainTable {
uint16_t grid_width; // 网格列数,如 16
uint16_t grid_height; // 网格行数,如 16
uint16_t block_size_x; // 每个网格覆盖的像素列数
uint16_t block_size_y; // 每个网格覆盖的像素行数
uint16_t gains[16][16]; // 增益值,Q4.12 定点格式
};
这里有个细节:增益值的精度。我见过有人用 8bit 存增益,结果校正后出现带状条纹。建议至少用 12bit 定点数,Q4.12 格式比较常见,能表示 0 到 15.999 的增益,精度 1/4096。
4.3 径向渐变校正算法
网格增益表虽然通用,但计算量大。对于很多嵌入式平台,径向渐变模型更实用。它假设镜头阴影是中心对称的,只跟像素到图像中心的距离有关。
4.3.1 数学模型
径向模型用多项式拟合增益曲线:
G(r) = 1 + a₁·r² + a₂·r⁴ + a₃·r⁶
其中:
r = sqrt((x - cx)² + (y - cy)²) / R_max
cx, cy 是光轴中心坐标
R_max 是图像对角线的一半
a₁, a₂, a₃ 是拟合系数
为什么只用偶次项?因为镜头阴影是径向对称的,奇次项会引入不对称。我刚开始做的时候用了奇次项,结果校正后的图像一边亮一边暗,找了半天 bug。
4.3.2 分通道拟合
亮度衰减和色彩偏移要分开处理。我通常的做法是:
- 亮度校正:四个通道共用一组径向系数,只校正整体亮度。
- 色彩校正:R、B 通道各自独立拟合,G 通道作为参考基准。
这样做的好处是:亮度校正可以用低阶多项式(2 阶就够了),色彩校正需要高阶一些(4 阶或 6 阶)。
注意:径向模型假设光轴中心在图像正中心。但实际模组组装有公差,光轴中心可能偏移几个像素。标定时一定要先标定光轴中心,否则校正效果会打折扣。
4.3.3 硬件实现要点
在硬件里实现径向校正,我总结了几条经验:
- 查表代替计算:r² 到 r⁶ 的计算开销大,预计算一个增益查找表,用 r² 做索引。
- 定点化:系数用 Q2.14 格式,r² 用 Q0.16 格式,乘法结果截断时注意舍入。
- 边界处理:图像四角的增益可能超过 4 倍,要限制最大增益,防止噪声被过度放大。
// 径向校正查表实现(伪代码)
uint16_t lsc_lut[1024]; // 预计算的增益表,索引 0~1023 对应 r²=0~1.0
for (each pixel (x, y)) {
float dx = x - cx;
float dy = y - cy;
float r2 = (dx*dx + dy*dy) / (R_max * R_max);
int idx = (int)(r2 * 1023.0f + 0.5f);
idx = clamp(idx, 0, 1023);
uint16_t gain = lsc_lut[idx];
pixel_out = pixel_in * gain >> 12; // 定点乘法
}
4.4 知识结构总览
下面这张图,我把镜头阴影校正的完整链路画出来了。从成因分析到标定流程,再到两种校正算法的选择,一目了然。
4.5 避坑指南
最后分享几个我踩过的坑,希望能帮你少走弯路。
坑一:光轴中心不准
我曾经用图像几何中心代替光轴中心做径向校正,结果四周校正过度,出现亮环。后来用标定板重新标定光轴中心,问题才解决。标定方法:拍一张点阵图,找畸变最小的点作为光轴中心。
坑二:增益过大导致噪声爆炸
图像四角的增益有时候会到 4 倍以上。如果不限幅,暗区的噪声会被放大到肉眼可见。我一般把最大增益限制在 3.0 倍,超过的部分用相邻像素插值。
坑三:温度漂移
镜头阴影会随温度变化。传感器温度升高,微透镜的折射率会变,色彩偏移也跟着变。如果产品工作温度范围大,建议做多组温度标定表,根据温度传感器动态切换。
嗯,镜头阴影校正这块,说难不难,说简单也不简单。核心就是理解成因、选对模型、做好标定。你只要把径向模型吃透了,再去看网格法,会发现很多思路是相通的。