4407 字
22 分钟
画面四角为什么更暗?从增益网格理解 Lens Shading Correction

对着一面照明均匀的白墙拍照,画面中心往往明亮,四角却逐渐变暗;把颜色饱和度拉高,还可能看到角落发红、发绿或发紫。这不一定是场景真的不均匀,而可能是镜头、滤光片、微透镜与传感器共同留下的空间响应。

Lens Shading Correction,简称 LSC,要做的就是为图像不同位置乘上不同增益:中心接近 1.0,越靠近响应较弱的角落,增益通常越大。它看起来像给照片套一层反向暗角,但 RAW ISP 里的真实实现更细:Bayer 的 R、Gr、Gb、B 四个平面可以各有一张二维增益网格。

本文从一个均匀场景出发,逐步回答四个问题:阴影为什么出现?怎样从标定图得到增益?稀疏网格怎样覆盖几百万个像素?校正以后,噪声和高光为什么也会改变?

一面均匀的墙,为什么拍出来不均匀#

如果场景亮度是 S(x,y)S(x,y),镜头—传感器系统在位置 (x,y)(x,y) 的相对响应是 V(x,y)V(x,y),暂时忽略噪声和黑电平,RAW 测量可以写成:

M(x,y)=S(x,y)V(x,y)M(x,y)=S(x,y)V(x,y)

中心响应通常归一化为 V1V\approx1,角落可能只有 0.5。LSC 保存的增益场近似为其倒数:

G(x,y)=1V(x,y),C(x,y)=M(x,y)G(x,y)G(x,y)=\frac{1}{V(x,y)},\qquad C(x,y)=M(x,y)G(x,y)

于是角落 0.52.0,理论上回到与中心一致。实际系统会留出增益上限、平滑网格并处理噪声,因此不会简单地对每个像素做一次完全自由的除法。

径向暗角合成场景

假设一面亮度完全均匀的墙。调节镜头阴影和校正强度,观察空间增益怎样把四角拉回中心。

均匀场传感器采样
角落 / 中心38%
理想角落增益2.63×
当前校正100%
观察阶段

这里的场景本来均匀,所有空间变化都来自镜头—传感器系统。真实镜头的阴影并不一定严格径向对称,因此量产 ISP 通常使用二维网格。

组件用径向函数制造了一张均匀灰场。真实镜头的阴影可能偏心、椭圆,甚至因为装配误差出现不对称;径向模型适合建立直觉,却不是量产标定必须遵守的形状。

阴影从哪里来#

“四角暗”常被统称为渐晕,但它背后有几类机制。

光学渐晕与自然渐晕#

离开光轴的光束会受到镜筒、光圈和镜片边缘限制,一部分光线无法到达像面。大光圈时这种机械或光学遮挡通常更明显。即使没有明显遮挡,离轴成像也会因投影面积、立体角等因素出现自然亮度下降,经典近似常提到 cos4θ\cos^4\theta 规律。

这个规律只能描述某类理想条件。具体镜头的结构、焦距、对焦距离、光圈和传感器尺寸都会改变实际分布,不能拿一条固定径向曲线替代标定。

CRA 与像素角度响应#

CRA(Chief Ray Angle,主光线角)描述主光线相对像面法线的角度。画面边缘的光往往以更斜的角度进入传感器。像素上方还有彩色滤光片、金属布线和微透镜,斜入射光可能不能被完整导向光敏区域,响应因此下降。

不同波长的折射、滤光片透过率和微透镜效率又不完全相同,所以红、绿、蓝的衰减比例可能随位置变化。这就是颜色阴影:问题不只是角落“更暗”,还会改变 RGB 比例。

像素响应与装配误差#

传感器自身存在像素响应不均匀,红外截止滤镜、保护玻璃和镜头相对传感器的偏心或倾斜也会引入低频空间差异。高频的单个坏点通常交给 DPC 或固定模式噪声校正,LSC 更擅长描述平滑、缓慢变化的二维场。

因此,LSC 不是一个只属于镜头的常数。它往往依赖具体的镜头—传感器组合,也可能随焦距、对焦距离、光圈、温度或光源光谱变化。

亮度阴影和颜色阴影是两件事#

假设中心中性块测得:

R = 0.50, G = 0.50, B = 0.50

角落若变成:

R = 0.30, G = 0.30, B = 0.30

三个通道同比例下降,主要表现为亮度阴影。若角落是:

R = 0.34, G = 0.30, B = 0.25

除了总体变暗,通道比例也变了,角落会带颜色。只用一张公共亮度增益图,可以把明暗大致拉平,却无法同时让中性区域恢复中性。

RAW Bayer 又不是每个位置都有完整 RGB。以 RGGB 为例:

R Gr
Gb B

工程实现通常分别为 R、Gr、Gb、B 保存四张低分辨率 gain mesh。两个绿色平面理论上响应相近,但空间位置、读出路径或微透镜布局不同,保留两张表更容易覆盖细微差异。

RGGB 四平面的颜色阴影

同一个位置的 R、Gr、Gb、B 衰减可以不同。分别标定四张增益图,才能同时修正亮度阴影和颜色阴影。

R 平面 · 角落 0.37
Gr 平面 · 角落 0.47
Gb 平面 · 角落 0.49
B 平面 · 角落 0.33
R 角落增益2.72×
Gr 角落增益2.13×
Gb 角落增益2.06×

Gr 与 Gb 来自 Bayer 单元中两个不同位置。多数情况下它们很接近,但分别保存能够覆盖微透镜、读出路径或标定误差造成的不对称。

这四张图不是四张输出彩色图,而是四类 CFA 样本各自的空间系数。应用时,程序根据 Bayer pattern 和像素坐标,为当前位置选择正确平面的插值增益。

为什么用 gain mesh,而不是逐像素表#

一张 4000×3000 的 RAW 有 1200 万个采样点。如果每个像素、每个通道都保存一个浮点增益,标定文件和硬件 SRAM 都会很大,而且逐像素测量噪声容易被直接拟合进去。

镜头阴影通常是低频变化,所以可以只保存例如 17×13、33×25 这样的稀疏网格节点,再在节点间插值。这样做有三点好处:

  • 存储和带宽显著下降;
  • 平滑插值自然抑制平场拍摄中的随机噪声;
  • ISP 可以用固定流水线逐像素生成增益。

代价是网格太稀时无法描述局部变化,太密时又可能拟合灰尘、照明不均或噪声。网格尺寸是模型能力与稳健性之间的选择,不是越大越好。

二维双线性插值怎样工作#

假设像素落在四个网格节点之间。左上、右上、左下、右下的增益分别为 g00,g10,g01,g11g_{00},g_{10},g_{01},g_{11},像素在当前单元内的归一化位置为 (u,v)(u,v),其中 u,v[0,1]u,v\in[0,1]

先沿水平方向插值:

gtop=(1u)g00+ug10g_{top}=(1-u)g_{00}+ug_{10}gbottom=(1u)g01+ug11g_{bottom}=(1-u)g_{01}+ug_{11}

再沿垂直方向插值:

g(u,v)=(1v)gtop+vgbottomg(u,v)=(1-v)g_{top}+vg_{bottom}

合并后就是:

g(u,v)=(1u)(1v)g00+u(1v)g10+(1u)vg01+uvg11g(u,v)=(1-u)(1-v)g_{00}+u(1-v)g_{10}+(1-u)vg_{01}+uvg_{11}

四个权重非负且相加为 1,所以结果位于四个节点增益的范围内。它对节点值是线性的,但二维曲面在每个小格内是双线性的。

增益网格与二维双线性插值

ISP 不为每个像素保存一个系数,而是在稀疏节点间插值。移动采样位置,观察四邻点共同决定结果。

5 × 5 gain mesh当前点 (0.72, 0.63)
2.101.691.551.692.101.691.271.141.271.691.551.141.001.141.551.691.271.141.271.692.101.691.551.692.10
插值增益1.192×
解析参考1.144×
局部误差0.0488
网格分辨率

每个像素只读取所在网格单元的四个节点。网格越密越能描述不规则阴影,但标定数据、存储量和对噪声的拟合风险也会增加。

真实实现还要明确网格覆盖的是有效成像区还是包含 optical black 的完整 RAW、节点位于像素中心还是边界、裁切和缩放后的坐标怎样映射。半个像素的坐标约定差异可能形成格状残差,不能只确认“公式写了双线性”就结束。

怎样从平场图标定一张 LSC 表#

最直接的标定输入是一张平场图:相机对准亮度和光谱尽量均匀的积分球或漫射光源,在固定曝光、模拟增益、光圈和焦距下采集多帧 RAW。

1. 先做基础 RAW 预处理#

标定图必须先减去正确的黑电平。若测量值为:

raw(x,y)=black+signal(x,y)raw(x,y)=black+signal(x,y)

直接用 center / raw 求增益,会把黑电平也当成有效光信号,尤其在暗角处造成系统性错误。坏点、饱和点和明显污染也应剔除。多帧平均可以降低 shot noise 与 read noise。

2. 分离 Bayer 四平面#

根据 CFA pattern 抽取 R、Gr、Gb、B。Pattern 写错、活动区起点错一行或错一列,都会让四套增益套到错误样本上。结果未必崩溃,却会产生周期性颜色网格。

3. 估计每个网格节点的响应#

在每个节点周围选取窗口,用均值、中位数或稳健拟合估计局部响应 mc(i,j)m_c(i,j)。窗口要足以压低噪声,又不能跨过明显的非均匀区域。若平场本身存在梯度,需要先校准光源,或用旋转相机、多位置拍摄等办法区分光源与相机响应。

4. 选择参考并归一化#

常见做法以中心区域或全图最大可靠响应为参考:

gc(i,j)=rcmc(i,j)g_c(i,j)=\frac{r_c}{m_c(i,j)}

这里的 cc 表示 R、Gr、Gb、B 平面。以中心归一化会让中心增益接近 1.0;以其他统计量归一化也可以,但会改变整体亮度,必须与曝光和白平衡的增益预算一起设计。

5. 平滑、约束并验证#

节点增益通常需要平滑,限制突变与最大值。应在独立平场上验证校正后的亮度均匀性、角落颜色和格状残差,而不是只看生成表所用的同一批数据。

一个简化的 NumPy 标定示意如下:

import numpy as np
def estimate_plane_gain(plane, rows=13, cols=17, max_gain=3.0):
"""从已经减黑电平的单个 Bayer 平面估计节点增益。"""
h, w = plane.shape
y_edges = np.linspace(0, h, rows + 1, dtype=int)
x_edges = np.linspace(0, w, cols + 1, dtype=int)
response = np.empty((rows, cols), dtype=np.float32)
for j in range(rows):
for i in range(cols):
patch = plane[y_edges[j]:y_edges[j + 1],
x_edges[i]:x_edges[i + 1]]
response[j, i] = np.median(patch)
center = response[rows // 2, cols // 2]
gain = center / np.maximum(response, 1e-6)
return np.clip(gain, 1.0, max_gain)
8 collapsed lines
raw_linear = raw.astype(np.float32) - black_level
r = raw_linear[0::2, 0::2]
gr = raw_linear[0::2, 1::2]
gb = raw_linear[1::2, 0::2]
b = raw_linear[1::2, 1::2]
meshes = [estimate_plane_gain(p) for p in (r, gr, gb, b)]

这段代码用于说明流程,不是完整标定程序。它默认 RGGB、平场完全均匀、中心可靠且没有饱和,也没有处理窗口几何、平滑、离群值和多个镜头状态。

在 Bayer 域应用四张增益网格#

应用阶段要把低分辨率 mesh 插值到每个 Bayer 样本位置。教学代码可以先把单平面网格放大,再写回相应位置:

import numpy as np
def bilinear_sample(mesh, x, y):
"""x、y 是 0 到 1 的归一化坐标数组。"""
rows, cols = mesh.shape
gx = x * (cols - 1)
gy = y * (rows - 1)
x0 = np.floor(gx).astype(int)
y0 = np.floor(gy).astype(int)
x1 = np.minimum(x0 + 1, cols - 1)
y1 = np.minimum(y0 + 1, rows - 1)
tx = gx - x0
ty = gy - y0
top = mesh[y0, x0] * (1 - tx) + mesh[y0, x1] * tx
bottom = mesh[y1, x0] * (1 - tx) + mesh[y1, x1] * tx
return top * (1 - ty) + bottom * ty
20 collapsed lines
def apply_lsc_rggb(raw, meshes):
"""raw 已减黑电平;meshes 顺序为 R、Gr、Gb、B。"""
output = raw.astype(np.float32).copy()
locations = [
(slice(0, None, 2), slice(0, None, 2)),
(slice(0, None, 2), slice(1, None, 2)),
(slice(1, None, 2), slice(0, None, 2)),
(slice(1, None, 2), slice(1, None, 2)),
]
for location, mesh in zip(locations, meshes):
plane = output[location]
yy, xx = np.meshgrid(
np.linspace(0, 1, plane.shape[0]),
np.linspace(0, 1, plane.shape[1]),
indexing="ij",
)
output[location] = plane * bilinear_sample(mesh, xx, yy)
return output

生产实现通常边扫描边递增插值权重,不会真的创建与图像同尺寸的 xxyy 数组。硬件还可能把增益量化成定点数,所以要测试系数量化、累加位宽和舍入误差是否产生条带或网格边界。

LSC 在 ISP 管线的什么位置#

一条简化的 RAW 路径可以写成:

RAW 读取
BLC 黑电平校正
DPC / RAW 域降噪
LSC 镜头阴影校正
Bayer 域 WB
Debayer
CCM / Tone Mapping / Gamma

具体产品会调整 DPC、降噪、LSC 和 WB 的次序,但几个原则很稳定。

BLC 通常在 LSC 前#

LSC 应放大有效信号,不应放大电子偏置。正确关系是:

output=(rawblack)G(x,y)output=(raw-black)G(x,y)

若先乘空间增益再减固定 black,角落会残留随增益变化的偏置,表现为黑位不均和颜色阴影。

LSC 通常在 Debayer 前#

颜色阴影发生在不同 CFA 样本的采集阶段。在 Bayer 域分别应用四平面 mesh,可以在插值混合相邻通道前修正原始样本,也避免为完整 RGB 保存三张全分辨率增益图。

Debayer 后做图像域 vignette correction 并非绝对错误,它适合通用照片编辑或只有 RGB 图像的场景;但它很难准确还原 Bayer 四平面差异,也可能把已经扩散到邻域的颜色阴影一起处理。

LSC 与 WB 都是乘法,但维度不同#

WB gain 随通道变化、通常不随位置变化;LSC gain 同时随通道和位置变化。在线性浮点且没有中间裁剪时,两次标量乘法可以交换:

xgWB,cgLSC,c(x,y)=xgLSC,c(x,y)gWB,cx\,g_{WB,c}\,g_{LSC,c}(x,y)=x\,g_{LSC,c}(x,y)\,g_{WB,c}

但定点量化、增益上限、裁剪和不同模块位宽会破坏严格等价。工程上分开保存更容易针对镜头状态更新 LSC、针对光源更新 WB,也方便调试增益预算。

校正会放大四角噪声#

设角落信号为 sVsV,噪声为 nn。乘以 G=1/VG=1/V 后:

(sV+n)G=s+nG(sV+n)G=s+nG

信号均值得到恢复,噪声幅度却也乘了 GG。LSC 无法重新收集进入镜头时已经损失的光子;它只是改变数字比例。因此一张“亮度完全均匀”的校正图,角落信噪比仍可能比中心差。

Shot noise 与信号相关,read noise 又有不同模型,真实噪声传播不能只用一个固定标准差描述。但结论不变:角落增益越大,暗部噪声越显眼。这也是 LSC 常与空间相关降噪强度、最大增益限制一起设计的原因。

高光与余量同样要算清楚#

如果角落测量值因为阴影只有 0.6,乘 2.0 会得到 1.2。若中间格式允许浮点或留有整数 headroom,后续曝光、tone mapping 仍能处理这个值;若 LSC 模块立即裁剪到 1.0,超过上限的差异就消失了。

四个 Bayer 平面的增益不同,还可能让某一通道最先越界,从而形成高光偏色。标定时把中心归一化为 1.0 并不代表任何真实场景都不会溢出,整个 ISP 必须共同规划 white level、WB、LSC 与数字增益的余量。

校正强度、噪声与高光余量

空间增益不会免费创造信号:它会一起抬高角落信号、噪声和高光。比较保留浮点与立即裁剪。

输入高光
0.72
阴影后
0.30
校正后
0.72
当前空间增益2.38×
输出噪声 σ0.083
越界样本0 / 32

欠校正会保留暗角,过校正会制造亮角。高增益区域通常还需要更强的降噪或增益上限;裁剪应尽量延后。

中间格式

“为了不裁剪,把整张图除以最大 LSC gain”可以换取数值空间,但会降低整体亮度或有效精度;它并不能恢复传感器已经饱和的通道。保留更宽中间位深、延后裁剪通常更稳妥。

什么时候一张 LSC 表不够#

变焦镜头在不同焦距下光学结构明显变化;光圈改变遮挡;对焦会移动镜组;不同光谱让颜色阴影发生变化。因此相机可能保存多组标定表,并根据运行状态插值:

  • 焦距节点之间插值;
  • 对焦距离或光圈节点之间插值;
  • 不同色温或光源类别之间插值;
  • 模组个体表与典型 golden table 混合。

这又引入一层参数空间插值。切表必须平滑,否则视频中会看到角落亮度或颜色突然跳变。若设备只使用一张典型表,则要验证模组公差下的最坏情况,并避免过度校正。

常见错误怎样从画面反推#

四角仍然偏暗:增益不足、坐标覆盖范围不对、使用了不匹配的焦距表,或平场标定把光源暗角误当成镜头特性。

四角反而变亮:增益过大、中心参考选择错误,或同一份校正被应用了两次。

出现彩色棋盘或 2×2 周期纹:Bayer pattern、有效区起点或 R/Gr/Gb/B 表映射错误。

出现网格边界:节点顺序、插值坐标或定点精度错误;也可能是增益表本身不连续。

亮度拉平但颜色仍偏:只使用了公共 luma mesh,没有分别校正四个 Bayer 平面,或标定光谱与实际光源相差太大。

角落彩噪明显:可能是预期的噪声放大,不一定是插值错误。检查原始角落 SNR、最大增益和空间降噪策略。

高光边缘变色:不同通道在 LSC 或后续 WB 后先后饱和,且中间阶段过早裁剪。

验证一套 LSC 的清单#

实现完成后,不要只凭一张普通照片判断。至少检查:

  1. 单位表测试:所有节点设为 1.0,输出应与输入一致。
  2. 常数输入测试:给定解析 gain mesh,逐点比对 CPU 参考结果。
  3. 节点与边界测试:像素恰好落在节点、四条边和四个角时,索引不能越界。
  4. CFA 映射测试:用 R、Gr、Gb、B 四张明显不同的常数表确认位置对应。
  5. 平场均匀性:比较校正前后中心、边缘和角落的均值与色度残差。
  6. 独立数据验证:生成表和验证表不能完全来自同一帧,避免把噪声当成能力。
  7. 噪声检查:测量不同半径处的均值、方差与 SNR,确认角落代价符合预期。
  8. 高光检查:在 LSC 与 WB 的组合最大增益下验证位宽、white level 与裁剪位置。
  9. 状态切换检查:变焦、对焦、光圈或色温切表时,视频不能闪烁或跳色。
  10. 元数据检查:确认有效区、裁切、旋转、镜像和 binning 后的坐标仍匹配标定。

最后记住:LSC 修正响应,不恢复光子#

LSC 的数学核心只是一个随位置变化的乘法:测得哪里响应弱,就在那里使用更大增益。gain mesh 与二维双线性插值让这张空间函数可以被紧凑保存,Bayer 四平面又让亮度阴影与颜色阴影能够同时处理。

但“校正后看起来均匀”不等于各处图像质量相同。角落失去的光子不会因为乘法回来,噪声会随增益上升,高光也需要更多数值余量。真正可靠的 LSC,既要把平场拉平,也要把 CFA 映射、标定条件、插值坐标、噪声和整条 ISP 的增益预算一起算清楚。

画面四角为什么更暗?从增益网格理解 Lens Shading Correction
https://cloudsir.top/posts/how-lens-shading-correction-works/
作者
CloudSir
发布于
2026-08-24
许可协议
CC BY-NC-SA 4.0
黑电平为什么不是零?从 Optical Black 到逐通道 BLC
降噪为什么会抹掉细节?从高斯滤波走到双边滤波