1. 一个反复出现的调试场景:为什么同一套mesh LSC在ISO变化后会失效
1.1 第一次在灯箱里发现“画面变脏”的瞬间
某个项目上,我刚拿到一颗sensor做画质调试。低ISO从ISO 100到ISO 400,跑一遍mesh LSC标定,灯箱下画面均匀,四角不发暗、不发色,心里还挺美。等切到ISO 1600再拍同一块灰卡,边缘和中心的亮度差又回来了,而且比不校正的时候更显眼,中间区域还泛着不大干净的颜色。
我把同一个位置的RAW拉出来对比,发现网格点上的比例关系变了:低ISO下中心与边缘亮度比大约是1.26,到了ISO 1600变成1.19,可画面反而更脏。这就不对劲了——如果只是光学衰减,比例应该基本稳定不动;比例变了,说明高增益下引入了另外的成分。
这不是个例。很多调试工程师都会撞见类似场景:低ISO调得好好的mesh LSC表,一到高ISO就“露馅”。如果你也遇到,第一反应不要怀疑网格表标定错了,也不要急着重新采集十几张图。先想清楚一个问题:LSC的校正强度系数,跟当前ISO到底是怎么联动的。
1.2 从“固定一张表”到“强度随ISO变”:认知转变的关键节点
早期一些方案里LSC表就一张,跑一次标定写死,全ISO共用。这个做法听起来合理——镜头阴影是光学物理量,它不随曝光增益变化。但实际上sensor系统在低感光和高感光下掺入的“污染信号”不一样,固定表只能解决其中一部分。
后来ISP平台开始提供多组表或带联动系数的配置,有些叫shading strength,有些叫gain table。不少第一次接触的人不知道这个联动参数是干嘛的,甚至直接填固定值1.0,等于没开联动。直到高ISO画面翻车,才回头研究。
直接说结论:强度系数随ISO联动,不是经验和玄学,而是对sensor物理特性的补偿。下面把原理讲透,再给实际操作流程。
2. 镜头阴影不只是光学问题:增益、暗电流和噪声预算如何改变校正强度
2.1 乘法世界里的镜头阴影:为什么光学衰减看起来“稳定”
先说基础。镜头阴影(lens shading)主要指径向亮度衰减和颜色衰减,来源是镜头边缘入射光通量变少,理论上服从余弦四次方定律,还叠加镜头内部反射、微镜头阵列(microlens)倾斜、传感器表面入射角依赖等因素。
这类衰减是乘性过程,对图像的作用相当于“每个像素的响应乘以一个小于1的系数”。它通常不随曝光时间、不随模拟增益变化。所以在理想sensor上,同一颗镜头拍出的shading比例应该稳定,这也是“一张表走天下”说法的来源。
但注意一个前提:理想sensor。真实sensor还有其他贡献,而且这些贡献恰恰跟ISO强相关。
2.2 暗电流和增益的闯入:加性偏差混入乘性校正
暗电流是sensor在没有光照射的情况下也会产生的电荷,与温度、曝光时间强相关。和光学衰减不同,暗电流是加性偏移,增益越高它被放大得越明显。高ISO不仅意味着模拟增益变高,通常也意味着在相同亮度场景下曝光时间可能需要延长,暗电流在暗部区域会占更大比例,而shading的边缘正好是暗部。
问题就出在这里:mesh LSC的校正本质是乘性增益。当图像中暗电流贡献不可忽略时,单纯乘性校正并不能把加性分量“压平”。边缘本来光信号就弱,暗电流占的比例更高,你按低ISO的强度去乘,会把噪声和暗电流一起放大,画面自然更“脏”。
所以高ISO的校正强度往往需要和低ISO不同,甚至校正形状也要微调。有些全自动方案会先把黑色电平(black level)做精确减除再进LSC,能在一定程度上缓解,但sensor暗电流还有空间非均匀性,完全减不干净,联动强度依然必要。
2.3 双转换增益和响应非线性导致的“形态松动”
不少sensor会在某个ISO点切换转换增益(高转换增益HCG / 低转换增益LCG),比如ISO 800以上从LCG切到HCG。切换之后电荷到电压的转换系数变化,光响应非均匀性(PRNU)的表观大小也跟着变化。PRNU本身也是一种乘性小扰动,它占比变大的时候,shading的细节形状也会轻微改变。
另外传感器响应的线性度并不完美,模拟增益越大,线性偏离越明显。这就解释了一个现象:有些sensor高ISO下中心区域响应偏高,边缘偏低,变化规律又不完全符合低ISO的形状。这时只改强度不改形状,往往会有残余shading。
这也是为什么联动表里有时要存多组网格形状,而不是只存一个标量强度。
2.4 噪声预算:为什么高ISO不该追求完美均匀
最后还要考虑一个工程约束:噪声。LSC校正增益大于1,它会把暗电流噪声、读出噪声一起放大。在ISO 1600,边缘增益如果是1.3倍,噪声等效增加20%以上;在暗部区域,这个被放大后的噪声会显得很刺眼,甚至出现明显的放大噪声块。
调试时如果非要把高ISO也校正到低ISO那样的“完全均匀”,边缘信噪比会被打得很低,尤其是颜色通道不平衡的地方还会产生彩色噪点。所以正确做法是留一部分残余shading不校正,让噪声增量与均匀度之间取一个平衡点。这也是强度系数随ISO下降的常见原因之一。
3. ISP里强度系数随ISO联动的实现方式与配置逻辑
3.1 mesh LSC模块的网格与插值工作方式
mesh LSC和传统径向LSC的区别在于,它把画面分成规则网格。每个网格点存一个校正增益,校正引擎用双线性插值算出每个像素的增益。网格越密,越能拟合复杂shading形态,代价是表格存储和带宽占用。
一个典型配置是横向32格、纵向32格,每个通道一张表,网格点数据16bit。四个通道加起来一张表大约32×32×4×2字节,8KB左右。看起来不多,但在寄存器表、OTP、驱动固件里塞多个版本,就会开始心疼资源。
每个通道单独一张表的原因很简单:sensor的R、Gr、Gb、B四个通道对入射角、波长的响应不一样,边缘偏色(color shading)普遍存在,必须四个通道分别校正才能还原白色。
3.2 两个层级:网格增益表与标量强度系数
ISP一般把校正参数拆成两个层级:
- 网格增益表:定义shading的空间形状,通常是相对中心点归一化的一组数值。
- 强度系数:一个标量,或者一条随ISO变化的曲线,表示当前ISO下对这一形状的“应用程度”。
计算时,当前帧的校正增益 = 网格增益 × 当前ISO对应的强度系数。这样做的好处是,如果不同ISO下shading形状近似,只要调强度,不用重新烧表。
比这更进一步的做法是准备多组网格表,每个关键ISO一组,ISO在两组之间插值强度或直接插值网格系数。适用场景是sensor在高增益下形状变化明显,比如有DCG切换。
提示:这里的“强度系数”有的平台叫strength,有的叫ratio,有的叫compensation factor,不管叫什么,指的都是网格增益之外的那个总缩放因子。
3.3 联动表怎么组织:ISO点与强度值的映射
具体到配置上,联动表通常是一组点对:
| ISO | 强度系数(示例) |
|---|---|
| 100 | 1.00 |
| 200 | 0.98 |
| 400 | 0.96 |
| 800 | 0.90 |
| 1600 | 0.85 |
| 3200 | 0.80 |
以上数值是随手写的示意,实际每颗sensor差异很大,而且不一定是单调下降。有的sensor暗电流大,高ISO反而需要提高一点强度来压住暗场的shading,具体以实测为准。
这里有个关键点:ISO在很多ISP里不是直接可读的物理量,而是由自动曝光(AE)根据模拟增益、数字增益和曝光时间折算出来的“曝光指数”。联动表索引时一般用总增益或精确到增益值,而不是显示给用户的ISO档位。否则同一个“ISO 1600”下,模拟增益和数字增益比例不同,校正强度也可能需要不同,联动就会失准。
3.4 一段伪代码看透联动计算
直接看一段配置逻辑的伪代码。
float get_lsc_strength(float iso) { if (iso <= points[0].iso) return points[0].strength; if (iso >= points[n-1].iso) return points[n-1].strength; for (i = 0; i < n-1; ++i) { if (iso >= points[i].iso && iso <= points[i+1].iso) { float t = (iso - points[i].iso) / (points[i+1].iso - points[i].iso); return lerp(points[i].strength, points[i+1].strength, t); } } }分段线性插值是最小可用方案,但它在相邻ISO间隔较大时,过渡会显得“硬”。很多ISP会在3A收敛过程中让strength跟着gain平滑变化,插值本身已经做了。如果发现ISO连续变化时边缘亮度有台阶感,可以加密ISO采样点,或者把插值改成样条。
台阶感的根源通常是相邻两张表差异过大,而不是插值算法本身的问题。所以遇到台阶,先检查相邻ISO的strength差值是否超过0.05,再考虑插值方式。我自己的经验是:强度曲线调整平滑之后,比单纯换插值算法效果明显得多。
4. 实测标定联动强度系数的操作步骤与数据计算
4.1 采集前的环境与设备准备
标定强度系数,本质上是回答一个问题:在不同ISO下,同样一个均匀目标,shading比是多少。所以采集条件要严格控制。
我习惯这样做:
- 用积分球或高均匀度的灯箱提供D65光源。没有积分球的话,用两层硫酸纸加均匀背光也能凑合,但均匀性要验证,目标区域中心到边缘光照差异最好小于1%。
- 镜头固定在项目常用光圈,不要用最大光圈去标定LSC。边缘衰减太剧烈会导致数值可信度下降。一般标定用F2.8到F4之间的中间光圈,如果产品必须覆盖多光圈,就每个光圈各标定一组。
- 拍摄对象是均匀灰卡,灰卡放平,表面不要有镜面反射和指纹。
- 白平衡选择sensor厂标定的标准模式,或者直接用RAW分析,不经过后端白平衡。
- 每一档ISO下,调整光源亮度或曝光时间,让画面中间区域亮度接近同一目标值,比如18%灰对应的DN值。这一步容易被忽略:如果不同ISO拍出来的中心亮度差好几档,信噪比不一致,算出来的shading比没有可比性。
4.2 从RAW里计算每个网格的shading比
拿到RAW之后,把图像切块统计。直接用脚本把画面按LSC网格划分,统计每个网格区域的平均值,避开坏点和缺陷。注意要用RAW的线性数据,不要用做完ISP处理后的成品图,否则白平衡、gamma、降噪都会污染数据。
对每个通道分别统计,核心计算如下:
center_mean = mean(raw[center_area]) # 中心区域均值 grid_mean[k] = mean(raw[grid_area_k]) # 第k个网格区域均值 ratio[k] = center_mean / grid_mean[k] # 该网格需要的补偿增益归一化后,中心区域ratio约1.0,边缘大于1.0。这组ratio就是当前ISO下所需的补偿增益形状。如果ratio超过1.5,说明边缘衰减太强,需要检查光圈和镜头装配,不要硬靠LSC去救。
以ISO 100作为基准形状,记录各个网格点的ratio_baseline。其他ISO下会得到一组ratio_iso,但因为曝光、噪声不同,网格上每个点的比值不可能完全和基准形状呈同一个比例。工程上可以先取一个整体强度:
strength_approx = median( (ratio_iso[k] - 1) / (ratio_baseline[k] - 1) )式中ratio减1,表示需要补偿的“额外增益”。如果某档ISO下整体偏离不大,用median或对网格加权平均来估计一个标量强度就够。如果偏离过大,就不能只换强度,要考虑换一组网格形状。
4.3 拟合联动曲线与写入配置
把所有ISO档位对应的strength算出来,会得到一组点。接下来判断它是单调的还是起伏的,有起伏说明sensor可能存在DCG切换或其他非线性因素。
把点连成折线后,设置相邻ISO之间的插值。需要注意首尾行为:小于最低ISO、大于最高ISO时,直接钳位到端点值,不要外推。外推强度到0.8以下或1.1以上,都会导致过度校正或校正不足。
写入配置时,优先以sensor的真实模拟增益值做索引键,而不是UI上的ISO档位。因为同一个ISO档位在不同曝光策略下模拟增益比例可能不同,按档位索引会在数字增益改变时失准。
4.4 验证结果:不只看均匀度,还要看噪声和过渡
写完配置不是结束,要验证。我的验证清单包含四件事:
- 均匀性测试:在积分球前从ISO 100连续拨到最高ISO,抓帧看中心到四角的亮度差,目标是每一档都落在目标残余shading范围内(比如中心与边缘比值小于1.08,视产品要求而定)。
- 噪声评价:高ISO下截取暗部区域,放大看LSC增益放大后是否出现明显噪点纹理。如果出现,适当下调该区间的strength。
- 颜色趋势:用色卡或灰卡看边缘是否偏色。通道间强度如果差异大,需要分别微调,而不是只调一个总强度。
- 连续性:视频模式下缓慢变ISO,观察画面是否出现亮度“呼吸”或台阶。如果发现,检查是不是两张表差异过大,或插值区间设置不当。
5. 调联动容易踩的坑:存储、插值、通道独立性与pipeline顺序
5.1 只调标量强度,不管理形状差异:高ISO的马鞍形残余
最常见的翻车现场是:高ISO下shading形状发生了变化,但工程上只把每档ISO的strength改了,网格形状还是基准表那些。结果就是中心校正过头,边缘仍暗,形成一种“马鞍形”或“碗形”的残余亮度。
判断方法很简单:在ISO 3200下计算ratio_iso与ratio_baseline逐网格的比值,如果比值在画面上呈有规律的径向分布,而不是围绕某个值的小幅波动,说明形状已经不一样了,需要在高ISO附近增加一组网格表,而不是继续压强度。
我见过一个项目,为了省存储只存一张表,硬调强度去压高ISO,结果所有高ISO的暗部色彩都不对,最后返工加了两组表才解决。这个教训说明,省存储不能靠牺牲形状精度,而要靠合理选点。
5.2 通道独立性和暗部偏色:一个strength不够用
白平衡通道的shading形态不完全一致,尤其是边缘的R/B衰减和G通道差别很大。用同一个标量强度去控制四个通道,可能会导致边缘出现明显的红边或蓝边。不少ISP支持按通道分别设置强度联动表,调试时务必利用起来。
另外注意暗电流的影响在B通道往往更明显。如果高ISO下B通道的暗部偏紫、偏蓝,先把黑色电平校准和暗电流减除做扎实,再回来看LSC联动,否则你会在LSC里反复挣扎,以为是强度系数不对,其实病根在减除环节。
5.3 表格存储与带宽约束下的取舍
一组32×32×4通道的16位表大约8KB,如果每个ISO都存一套,OTP和驱动里要装十几套,存储和加载带宽都会吃紧。大多数项目的做法是:
- 选基准形状表2~3套,比如低ISO一套、中ISO一套、高ISO一套。
- 其余ISO档用标量强度联动逼近。
- 对形状差异较大的临界ISO(如DCG切换点附近),单独加表。
这套做法的工程意义很大:既控制了资源,又不至于让画质有明显跳变。具体选哪几个基准点,可以先用4.2的数据画一张“形状差异随ISO变化”的曲线,差异突变的地方就是基准表应该加密的地方。
5.4 别忘了看pipeline顺序和温度漂移
LSC在ISP管线中的位置会影响联动逻辑。如果数字增益在LSC之前,shading已经被数字增益同等放大,联动强度应该基于总增益(模拟+数字)计算;如果数字增益在LSC之后,只考虑模拟增益即可。这个细节很多配置文档都写得模糊,拿到不确定时直接查芯片手册里的block diagram,别猜。
还有温度。暗电流对温度敏感,同一ISO下,机器热起来之后暗部shading形态会变。做联动标定要在常温到满载温度之间各跑一轮,看看强度曲线是否需要按温度再补偿。至少,我没有见过哪个量产项目能在完全忽略温度的情况下把高ISO调好。
另外,如果产品支持在线调试,强烈建议先在PC端搭一个模拟ISP环境,把ISO和strength联动曲线可视化出来,观察插值曲线有没有折角、有没有突变,再烧进固件。这一步能省下大量现场抓帧排查的时间。
我在实际调试中发现,强度系数随ISO联动的调校,本质上是在“光学校正需求”“噪声放大代价”“sensor非线性”和“系统资源”四者之间找平衡,没有一个公式能一套走天下。遇到问题别急着怀疑工具,先把sensor的增益特性摸明白,再动手调联动表,会顺利很多。