4. 镜头阴影校正(LSC):从渐晕到FPGA加速
镜头阴影校正,英文叫Lens Shading Correction,简称LSC。说白了,就是解决照片四周发暗、中心亮的问题。你拿手机拍一张纯白墙壁的照片,放大看四角,十有八九是偏暗的。这就是渐晕现象。
我在项目中遇到过好几次,客户说“你们的sensor是不是坏了,怎么四周颜色不对?”其实不是sensor的问题,是镜头的光学特性决定的。今天我们就把它讲透。
4.1 渐晕现象与LSC原理
渐晕(Vignetting)产生的原因主要有三个:
- 光学渐晕:镜头边缘的光线被镜筒遮挡,入射角度越大,衰减越严重
- 像素角度响应:边缘像素对斜入射光的灵敏度下降
- 镜头cos⁴定律:理想情况下,照度与cos⁴θ成正比,θ是入射角
你想想看,一个广角镜头,边缘入射角可能到30°以上,cos⁴(30°) ≈ 0.56,也就是说边缘只有中心一半的亮度。这可不是小问题。
核心思路:LSC就是给每个像素乘一个增益系数,边缘乘大一点,中心乘小一点,让整幅画面亮度均匀。
增益矩阵怎么来?一般有两种方式:
- 标定法:拍一张均匀光源下的灰卡,计算每个像素需要的补偿增益
- 模型拟合法:用多项式或网格模型拟合渐晕曲线,生成增益矩阵
我个人习惯用标定法,简单直接。但要注意,标定时的光源均匀性必须保证,否则你校正的是光源不均匀,不是镜头阴影。
4.2 增益矩阵生成方法
增益矩阵的生成,我把它分成三步:
| 步骤 | 操作 | 注意事项 |
|---|---|---|
| 1. 采集raw图 | 均匀光源下拍灰卡,取raw数据 | 光源色温要匹配实际场景 |
| 2. 计算增益 | 中心亮度 / 各像素亮度 | 避免除以0,加小epsilon |
| 3. 平滑滤波 | 用低通滤波器去除噪声 | 保留低频趋势,滤除高频噪点 |
增益矩阵的尺寸通常比图像小很多,比如16×16或者32×32。为什么?因为渐晕是低频变化,不需要每个像素都存一个增益值,那样太浪费存储了。
经验之谈:我曾经用64×64的增益矩阵,结果FPGA的BRAM不够用了。后来改成32×32,配合双线性插值,效果几乎一样,资源省了一半。
4.3 FPGA双线性插值实现
增益矩阵是稀疏的,我们需要对每个像素插值得到精确的增益值。双线性插值是性价比最高的选择。
先看插值公式:
// 已知四个网格点的增益 G00, G10, G01, G11
// 像素在网格内的归一化坐标 (u, v),范围 [0, 1)
G_top = G00 * (1 - u) + G10 * u
G_bottom = G01 * (1 - u) + G11 * u
G_pixel = G_top * (1 - v) + G_bottom * v
FPGA实现时,我建议用流水线结构:
- 坐标计算模块:根据像素坐标算出网格索引和归一化坐标
- BRAM读取模块:同时读出四个相邻网格点的增益值
- 插值运算模块:用乘法器和加法器完成三次线性插值
- 乘法校正模块:将插值得到的增益与像素值相乘
注意:增益值通常是定点小数,比如Q1.8格式。乘法后要小心溢出,建议输出位宽比输入大2-3bit。
下面是我画的一个FPGA LSC流水线结构图:
嗯,这里要注意BRAM的读取时序。增益矩阵存在BRAM里,每个时钟周期要读出四个值。我一般用双端口BRAM,一个端口读偶数行,一个端口读奇数行,这样一拍就能拿到全部四个值。
4.4 调试技巧与避坑指南
调试LSC,我踩过的坑不少,分享几个实用的:
我曾经犯过的错:增益矩阵的边界处理没做好,图像边缘出现奇怪的条纹。原因是网格坐标计算时,边界像素的索引越界了。解决方案是在坐标计算模块加饱和截断,把索引限制在[0, grid_width-2]范围内。
调试步骤我建议这样走:
- 第一步:用MATLAB/Python离线验证增益矩阵和插值算法,确保数学正确
- 第二步:FPGA仿真,注入测试向量,比对RTL输出和软件参考结果
- 第三步:上板实测,拍均匀灰卡,检查校正后的均匀性
- 第四步:拍真实场景,检查是否有artifact
小技巧:调试时可以把增益矩阵的值直接映射成灰度图显示出来。如果增益图是平滑的渐变,说明标定没问题;如果出现跳变或噪点,那就是标定或滤波出了问题。
还有一个容易被忽略的点:增益矩阵的精度。我建议用Q1.8格式,也就是1bit整数位+8bit小数位,增益范围0~1.996。如果增益超过2.0,说明镜头阴影太严重,可能需要考虑光学方案改进,而不是一味靠算法补偿。
最后说一句,LSC的效果评估,除了看均匀性,还要看信噪比。增益放大的同时,噪声也被放大了。边缘区域的SNR会下降,这是物理限制,算法只能缓解,不能消除。