3.5 关键追问:修改 AE 目标亮度后,LV 还能表示环境实际亮度吗?
如果保持外部环境亮度不变,只是修改目标亮度值。根据
LV= EV-log(iso_speed/100)
EV =AV+TV
由于TV、SV改变了,导致LV都有改变。
因此修改 AE 目标亮度后,LV 不能表示实际的环境的亮度。LV 会随目标亮度整体平移,平移量 = log2(新目标/旧目标)。
LV 表示的是"场景亮度相对标定目标的比值",而不是绝对环境亮度。
3.5.1 原因:AE 收敛方程的常数 C0 里藏着目标亮度
回顾 3.1 节第二步的收敛条件:
H × G = Y_target / c = C0
↑
AE 目标灰度!
C0 不是系统固有常数,它正比于 AE 目标亮度 Y_target。第 3.1 节第五步的最终结果:
LV = log2(L) + [log2(k) - log2(C0) - log2(iso_base) + log2(100)]
↑
含 log2(Y_target)
展开写就是:
LV = log2(L) - log2(Y_target) + 常数
LV 中残留了一项 -log2(Y_target) 。之前说"LV 只含场景亮度",前提是
Y_target 固定不变------此时 -log2(Y_target) 并入常数,LV ∝ log2(L) 成立。
一旦修改目标亮度,这个"常数"就变了,LV 整体平移。
3.5.2 直观理解:LV 度量的是"曝光相对目标的偏差"
LV 的本质是 AE 闭环的不动点,而不动点的位置由参考点决定:
- AE 的任务是把画面打到目标灰度
- LV 记录的是"为了打到这个目标,相机用了多少曝光"
- 目标调亮 → AE 给更多曝光 → EV 变小 → LV 变小(同一场景"看起来变暗"了)
所以 LV 实际度量的是:
LV = log2( L / L_ref ) + 常数
其中 L_ref 是"AE 目标灰度对应的等效场景亮度"(由标定时的目标决定)。
改目标 = 改 L_ref = 平移整个 LV 刻度。
3.5.3 数值例子:目标从 50 调到 100(调亮一档)
场景不变:明亮商铺,真实 LV(原标定口径)= 8.91,iso_base = 50,增益固定 2x(iso_speed = 100)。
原目标 Y = 50 :AE 收敛于 F2.0, 1/120s,EV = 8.91,LV = 8.91 - 0 = 8.91
目标调到 Y = 100(×2,调亮一档) :画面要亮一档,AE 把曝光加大一档 :
F2.0, 1/60s
EV = log2(4) + log2(60) = 2 + 5.91 = 7.91 (比原来小 1)
LV = 7.91 - log2(100/100) = 7.91 (比原来小 1)
环境亮度纹丝未动,LV 却从 8.91 掉到 7.91 ------按 Lux 换算 2.5×2^LV,
上报照度从 1200 lux 变成 600 lux,凭空"变暗"一档。
目标调暗到 Y = 25(×1/2):AE 把曝光减小一档,F2.0, 1/240s:
EV = 2 + log2(240) = 9.91 (大 1)
LV = 9.91 (大 1) ← 凭空"变亮"一档
3.5.4 定量关系与修正方法
目标从 Y_cal(标定值)改为 Y_new,LV 的平移量为:
ΔLV = -log2(Y_new / Y_cal)
验证: Y_new = 2×Y_cal → ΔLV = -1 ✓ (上例 8.91 → 7.91)
Y_new = Y_cal/2 → ΔLV = +1 ✓
若确实需要运行时修改目标亮度,又想保持 LV 的物理意义,有两种做法:
方法1 (推荐): 修正上报值
LV_true = LV_report + log2(Y_new / Y_cal)
即把目标变化量从 LV 中扣回去, 恢复到标定时的口径
方法2: 重新标定 Lux 系数
保持 LV_report 原样, 重测 5.5 节的 A:
A_new = A_old × (Y_cal / Y_new)
Lux = A_new × 2^LV_report
(本质相同: 平移量被打包进换算系数)
3.5.5 与 3.3 节反例的对比:两种"LV 失真"的本质
| 3.3 节反例(AE 未收敛) | 本节(改 AE 目标) | |
|---|---|---|
| 操作 | 锁定曝光,拉增益 | 环境不变,改目标亮度 |
| EV 是否变 | 不变 | 变(AE 重新收敛到新曝光) |
| LV 偏移量 | +log2(增益倍数) | -log2(目标倍数) |
| 物理含义 | 当前曝光相对场景的偏差 | 场景相对新目标的偏差 |
| 是否"错误" | 不是------正确反映过/欠曝量 | 不是"错误",但不再是绝对环境亮度 |
两者的共同本质:LV 永远忠实度量"当前曝光策略相对参考点的位置" 。
参考点(增益显式进入公式、目标隐含在收敛点里)任何一侧变动,LV 都会平移。
只有"AE 收敛 + 目标固定 + iso_base 标定准确"三个条件同时满足,
LV 才是纯环境亮度。
工程要点 :AE 目标亮度(含分区域权重、人脸逆光提升等动态目标调整)
是 LV/Lux 上报链路中最容易被忽视的平移源------增益和曝光参数的变化都被公式
显式抵消了,唯独目标亮度的变化直接烙在收敛点上。若产品有"逆光人脸提亮"
等动态目标功能,Lux 上报必须按 3.5.4 方法 1 同步补偿,否则夜间/逆光场景
Lux 会出现与目标调整幅度完全相关的系统性跳变。
3.5.6 方法 1 深入详解:为什么 LV_true = LV_report + log2(Y_new/Y_cal) 成立
(a) 修正公式的推导------把"被偷走的常数"加回来
从 3.5.1 节的结论出发。设标定时目标为 Y_cal,当前运行目标为 Y_new,
AE 收敛后 LV_report 的完整表达式是:
LV_report = log2(L) - log2(Y_new) + C_fix
其中 C_fix 是与目标无关的固定常数(含 k、iso_base、log2(100) 等)。
我们想要的是"标定口径"的 LV,即假设目标仍是 Y_cal 时会算出的值:
LV_true = log2(L) - log2(Y_cal) + C_fix
两式相减:
LV_true - LV_report = [log2(L) - log2(Y_cal) + C_fix] - [log2(L) - log2(Y_new) + C_fix]
= log2(Y_new) - log2(Y_cal)
= log2(Y_new / Y_cal)
移项即得修正公式:
LV_true = LV_report + log2(Y_new / Y_cal)
直观解读 :目标从 Y_cal 改成 Y_new 时,LV 被平移了 -log2(Y_new/Y_cal)
(3.5.4 节的 ΔLV)。修正就是把这个平移量原样加回去 ------
目标调大多少倍,就补回多少个 log2;目标调小则补回负值。
场景项 log2(L) 和固定常数在相减时完全消掉,所以修正不依赖场景、
不依赖增益、不依赖曝光参数,是一个纯加性补偿。
(b) 为什么"加回去"在数学上是严格的------修正后的 LV 恢复不动点性质
验证修正后的量是否重新成为"AE 不动点"(环境不变则修正后 LV 不变):
环境亮度 L 固定,目标从 Y_cal 改为 Y_new。AE 重新收敛,新收敛方程:
H × G = Y_new / c → AE 给出的曝光量变为原来的 Y_new/Y_cal 倍
→ EV_new = EV_cal - log2(Y_new/Y_cal)
代入 LV_report 公式(iso_speed 项不变,因为增益路径没变):
LV_report = EV_new - log2(iso_speed/100)
= EV_cal - log2(Y_new/Y_cal) - log2(iso_speed/100)
= LV_cal_口径 - log2(Y_new/Y_cal)
修正:
LV_true = LV_report + log2(Y_new/Y_cal) = LV_cal_口径 ✓
逐项对消与 3.1 节 log2(G) 的抵消完全同构:
- 目标变化在收敛点里"隐含"地平移了 EV(AE 行为)
- 修正项"显式"地把同一个平移量加回去
- 一隐一显,符号相反,目标的影响被彻底抹掉
修正后,无论目标怎么改,LV_true 都只随真实环境亮度 L 变化------
LV_true 恢复了"环境不变则读数不变"的不动点性质 ,
Lux = 2.5 × 2^LV_true 重新成立。
© 完整数值例子 1:静态改目标(延续 3.5.3 的场景)
场景:明亮商铺,真实照度 1200 lux(真实 LV = 8.91),iso_base = 50,
增益固定 2x(iso_speed = 100),标定目标 Y_cal = 50。
目标改为 Y_new = 100 后 AE 重新收敛:
AE 把曝光加大一档: F2.0, 1/60s
EV = log2(4) + log2(60) = 7.91
LV_report = 7.91 - log2(100/100) = 7.91 ← 比真实值低 1
应用修正:
修正项 = log2(Y_new / Y_cal) = log2(100/50) = log2(2) = +1
LV_true = 7.91 + 1 = 8.91 ✓ ← 精确回到真实值
Lux = 2.5 × 2^8.91 = 1200 lux ✓ ← 与照度计一致
反向验证(目标调暗):Y_new = 25,AE 收敛于 F2.0, 1/240s:
EV = 2 + log2(240) = 9.91
LV_report = 9.91 ← 比真实值高 1
修正项 = log2(25/50) = -1
LV_true = 9.91 + (-1) = 8.91 ✓
换目标前后对照表:
| 目标 Y_new | AE 收敛设置 | EV | LV_report | 修正项 log2(Y/50) | LV_true |
|---|---|---|---|---|---|
| 25 | F2.0, 1/240s | 9.91 | 9.91 | -1 | 8.91 |
| 50(标定值) | F2.0, 1/120s | 8.91 | 8.91 | 0 | 8.91 |
| 100 | F2.0, 1/60s | 7.91 | 7.91 | +1 | 8.91 |
| 200 | F2.0, 1/30s | 6.91 | 6.91 | +2 | 8.91 |
目标每翻一倍,LV_report 掉 1、修正项涨 1,两者相加恒为 8.91------
修正后的读数对目标变化完全免疫。
(d) 完整数值例子 2:动态目标(逆光人脸提亮场景)
真实产品中目标亮度往往逐帧变化。设某安防相机:
- 标定目标 Y_cal = 56(Q8 定点习惯值,对应 8bit 的 22%)
- 普通场景目标 56;检测到逆光人脸时目标提升到 112(提亮一档)
时间线(环境为傍晚室内,真实 LV = 6.3,约 100 lux,iso_base = 50):
| 帧 | 场景 | Y_new | AE 收敛 | LV_report | 修正项 | LV_true | 未修正 Lux | 修正后 Lux |
|---|---|---|---|---|---|---|---|---|
| t0 | 无人脸 | 56 | EV=6.3 | 6.3 | 0 | 6.3 | 98 lux | 98 lux |
| t1 | 人脸入画 | 112 | EV=5.3 | 5.3 | +1 | 6.3 | 49 lux | 98 lux |
| t2 | 人脸出画 | 56 | EV=6.3 | 6.3 | 0 | 6.3 | 98 lux | 98 lux |
未修正的后果 :人脸入画瞬间 Lux 上报从 98 跳到 49(凭空"变暗"一档),
出画又跳回------环境明明没变,Lux 曲线出现与目标调整完全同步 的方波跳变。
若上层用 Lux 做日夜切换/补光灯控制,会在人脸出入画时反复误触发。
修正后 :LV_true 恒为 6.3,Lux 恒为 98 lux,曲线平滑。
注意画面本身确实变亮了(这是提亮的目的),但亮度状态量与画面观感解耦 ------
这正是"状态量"与"控制量"分离的意义:AE 用目标控制画面,
Lux 上报用修正后的 LV 描述环境。
(e) Q8 定点实现
修正项也是纯 log2,可直接复用工程的定点 log2(fixed_log2_apex),
或更简单------目标值通常是有限集合(P-line 档位/场景模式),可预计算查表:
c
/* Y_cal: 标定时的 AE 目标 (如 56)
* Y_new: 当前帧 AE 实际使用的目标
* 返回 Q8 修正项, LV_true_q8 = LV_report_q8 + corr_q8 */
int32_t lv_target_corr_q8(uint32_t y_new, uint32_t y_cal)
{
/* corr_q8 = 256 * log2(y_new / y_cal)
* 用定点 log2: log2_q8(x) 以 Q8.8 输入 256=x 输出 */
if (y_new == 0 || y_cal == 0) return 0;
return fixed_log2_q8((int64_t)y_new * 256 / y_cal); /* Q8 */
}
/* Lux 上报链路 */
LV_true_q8 = LV_report_q8 + lv_target_corr_q8(y_target_cur, Y_CAL);
三个实现要点:
-
修正项必须用"AE 本帧实际收敛所用的目标" 。若目标在 AE 收敛过程中
逐帧变化,收敛瞬间的 LV_report 对应的是"最后一步用的目标",
用错目标值会留下残余误差(目标差 1 档 → 残余 1 EV)。
工程上取"AE 稳定判据(连续 N 帧曝光不变)成立那一帧的目标"最稳妥。
-
修正加在 Lux 上报路径,不加在 AE 反馈路径 。AE 内部用 LV_report
与目标比较来调曝光(err = LV_target - LV_current),这条路径里
目标变化本来就是"信号"而不是"误差",加了修正反而破坏控制。
-
标定 A(5.5 节)时必须把修正项置零或固定目标 ,否则目标平移量
会被吸收进 A,之后动态目标再变化就会双重补偿。
(f) 方法 1 与方法 2 的等价性证明
方法 2 把平移量打包进 Lux 系数:A_new = A_old × (Y_cal/Y_new),则:
Lux_方法2 = A_new × 2^LV_report
= A_old × (Y_cal/Y_new) × 2^LV_report
取 log2: log2(Lux_方法2) = log2(A_old) + LV_report + log2(Y_cal/Y_new)
= log2(A_old) + LV_true (由 (a) 的修正公式)
→ Lux_方法2 = A_old × 2^LV_true = Lux_方法1 ✓
两者数学上完全等价,区别只在工程形态:
| 方法 1(修正 LV) | 方法 2(重标 A) | |
|---|---|---|
| 修正位置 | LV 计算处(源头) | Lux 换算处(末端) |
| 目标动态变化 | 每帧实时修正,天然支持 | A 是静态常数,无法跟随动态目标 |
| 下游 LV 使用方 | 拿到的直接是物理口径 | 拿到的是"偏移口径",需自行知晓 |
| 适用场景 | 目标会运行时变化(动态目标/用户 EV 补偿联动) | 目标固化的产品,改动最小 |
结论 :只要目标亮度会动态变化,方法 1 是唯一正确选择;
方法 2 只适用于目标永久固定的场合。
3.5.7 工程落地:ISP 调试改了目标亮度,如何保证 LV 永远等于环境实际亮度?
问题背景 :iso_base/Lux 系数标定完成后,ISP 图像效果调试是持续进行的------
调优人员经常改 AE 目标亮度(调画面明暗观感),且不会通知 做 Lux/ISO
标定的人。标定时的 Y_cal 与量产固件里的实际目标 Y_tuning 天然存在差异。
如何让 LV 在任何调参之后都自动保持"环境实际亮度"的物理意义?
核心答案:不要依赖"目标不变"这个脆弱假设,而是把目标修正
固化进 LV 计算链本身------LV 计算时永远显式除掉当前生效的目标值。
(a) 根本性方案:把 Y_target 写进 LV 公式,而不是藏在常数里
回顾 3.5.1 节,LV 的完整表达式是:
LV = log2(L) - log2(Y_target) + C_fix
标定时 Y_target = Y_cal 固定,-log2(Y_cal) 被并入常数,公式简化为
LV = log2(L) + 常数------这个简化正是脆弱性的来源 :它隐含假设
"目标永远等于标定值"。
根治方法是不做这个简化,让 LV 计算公式显式包含当前目标:
LV_true = AV + TV - SV + log2(Y_cur / Y_cal)
↑当前帧AE实际用的目标 ↑标定时的目标(固化常量)
这样无论调优人员把目标改成多少,修正项自动跟随,LV_true 恒为
标定口径的环境亮度。调参自由度与状态量物理意义彻底解耦:
- 调优改目标 → 只影响画面观感和 AE 行为(本来就该如此)
- LV/Lux 上报 → 自动补偿,永远描述真实环境
(b) 配套的工程纪律(三条,缺一不可)
纪律 1:Y_cal 成为受控常量,与标定数据绑定
- Y_cal (如 56) 与 iso_base、Lux 系数 A 一起存入标定数据区/OTP,
与算法参数分离, 调优人员不可修改
- 标定数据区只随 sensor/模组标定流程更新, 不随 ISP 调参版本更新
纪律 2:LV 计算处必须能拿到"当前帧 AE 实际使用的目标"
- AE 模块每帧输出 y_target_cur (本帧收敛用的目标, 含动态目标调整)
- LV 计算函数签名显式带上该参数, 不允许在 LV 内部另读一份
"配置表目标"------两处读数不一致是常见 bug 源
- 动态目标(人脸提亮等)天然被覆盖: 用哪个目标收敛, 就用哪个修正
纪律 3:验收用照度计闭环,不信任任何中间推导
- 每次大版本调参合并后, 用照度计 + 灰板复测 2~3 个照度点:
|Lux_report - Lux_照度计| < 0.1 EV 即通过
- 该测试不依赖任何代码审查------无论谁改了什么, 只要 LV 链路
被破坏, 此测试立刻暴露
© 数值例子:调优改目标后,修正机制自动生效
延续 3.5.6© 场景:1200 lux 商铺,Y_cal = 50(固化在标定区),
iso_base = 50,增益 2x。
调优人员把目标从 50 改成 80(+0.68 档),未告知标定方:
AE 重新收敛: 曝光加大 log2(80/50)=0.68 档 → F2.0, 1/75s
EV = 2 + log2(75) = 8.23
LV_report(无修正) = 8.23 - 0 = 8.23 ← 偏离真实值 0.68 EV
LV 计算链自动修正:
修正项 = log2(Y_cur / Y_cal) = log2(80/50) = 0.68
LV_true = 8.23 + 0.68 = 8.91 ✓ ← 自动回到真实值
Lux = 2.5 × 2^8.91 = 1200 lux ✓
调优改任何目标值(56、80、112......),LV_true 恒为 8.91------
修正项与目标变化逐档对消,机制对调参行为完全透明。
(d) 若无法改造 LV 计算链(遗留代码)的降级方案
若 LV 计算函数无法加参数,退而求其次:
降级方案: 在 Lux 上报出口处统一补偿
lux_q8 = A_q8 × 2^((LV_report_q8 + corr_q8) >> 8)
corr_q8 = log2_q8(y_target_cur × 256 / Y_CAL)
风险与对策:
- 遗留代码里 LV 的其他使用方(场景分类/日夜切换)拿到的仍是
偏移口径 → 必须梳理全部 LV 消费点, 逐一确认口径
- 更稳妥: 在 LV 计算函数出口处直接加 corr (单点修改),
保证所有下游拿到统一物理口径
(e) 总结:三层防线
| 层级 | 措施 | 防什么 |
|---|---|---|
| 第 1 层(根治) | LV 公式显式含 log2(Y_cur/Y_cal),修正项自动跟随 | 任何目标修改,含动态目标 |
| 第 2 层(纪律) | Y_cal 入标定受控区;AE 每帧输出实际目标给 LV 链 | 口径分裂、读数不一致 |
| 第 3 层(验收) | 照度计闭环回归测试,阈值 0.1 EV | 一切未被意识到的链路破坏 |
一句话 :LV 的物理意义不靠"没人改参数"来保证,而靠
"参数改了,公式自动知道"来保证------把目标亮度从隐含常数
升级为显式输入,是唯一健壮的做法。
3.6 工程实践讨论:手机 AE 是否都用固定目标亮度?
原命题 :"手机 AE 中不同 sensor 基本都用相同的固定目标亮度(标定和实际使用一致);
要不同亮度效果就调其他参数,不动目标亮度;动了目标亮度,算出的 LV 就不准了。"
结论:方向正确,是主流工程实践,但需要三处精确化。
3.6.1 命题中准确的部分
(1) "LV 相对固定目标而言"------准确,这是 LV 的数学性质
由 3.5.1 节,LV = log2(L) − log2(Y_target) + C_fix。LV 是 AE 闭环的不动点,
而不动点的位置由目标亮度唯一决定。不存在"绝对 LV",只存在"某目标口径下的 LV" 。
标定时用什么目标,运行时就必须还是那个目标,LV 的数值口径才不变。
(1a) 原理详解:为什么"不动点由目标唯一决定"、"不存在绝对 LV"
① 先把"AE 闭环"写成迭代映射,看清不动点在哪
AE 每帧做的事可以抽象成一个映射 f:输入当前的 LV 观测值,输出调整后的 LV。
设当前曝光状态对应的 LV 为 x,AE 统计画面平均亮度 Y_cur,与目标 Y_target 比较:
误差 err = log2(Y_cur) - log2(Y_target)
新曝光: x_next = x - err (画面偏亮就减曝光, 偏暗就加曝光)
而 Y_cur 由物理链路决定:Y_cur = c · L · (T/N²) · G = c · L · G · 2^(-x+常数)。
代入整理(细节即 3.1 节第三步),迭代映射化简为:
x_next = x - [log2(L) - log2(Y_target) + C_fix - x] = log2(L) - log2(Y_target) + C_fix
关键观察:x_next 与 x 无关! 无论从什么初始曝光出发,一步之后都到达
同一个值 x* = log2(L) − log2(Y_target) + C_fix,且 x* 代回去满足 x* = f(x*)------
这就是不动点。AE 收敛 = 到达不动点;收敛后读出的 LV 恰好等于 x*。
② 不动点的位置由谁决定------三个自变量中只有 L 是"环境"
x* = log2(L) − log2(Y_target) + C_fix
↑环境 ↑AE目标 ↑标定常数(iso_base等)
- L 变 → 不动点移动 → LV 跟随环境(这是 LV 有用的原因)
- Y_target 变 → 不动点同样移动 → LV 跟随目标(这是 LV 口径依赖目标的原因)
- 在不动点公式里,L 和 Y_target 的地位完全对称 ------都是"亮度",都进 log2,
一个来自场景,一个来自算法配置。LV 无法区分"环境变暗了"和"目标调亮了",
因为这两种情况对收敛点的影响逐比特相同。
这就是"不存在绝对 LV"的数学含义:x 是二元函数 x (L, Y_target),
不是一元函数 x*(L)。只有把 Y_target 冻结成常数,它才退化为 x*(L),
"LV ↔ 环境亮度"的一一映射才成立。冻结与否不是数学强制的,
是工程约定**------约定一旦破坏(改目标),映射立刻退回二元。
③ 类比:LV 是一把"零点由目标校准的尺子"
LV 度量的是比值 L/Y_target 的 log2,本质是相对量:
LV = log2( L / Y_target ) + C_fix
↑"场景是目标的多少倍"
就像摄氏温标的 0 度是"水的冰点"这个人为参考点 ------换一个参考点
(华氏、开尔文)温度数值全变,但"哪杯水更热"的判断不变。
LV 的参考点就是 Y_target:目标 56 时 LV=8.91 的场景,和目标 112 时
LV=7.91 的场景,物理亮度完全相同 (都对应 1200 lux),
只是尺子的零点挪了一格。尺子本身没有"绝对零度"式的天然锚点------
锚点是标定时人为选的。
④ 数值例子:三个不动点,同一个场景
同一 1200 lux 场景(log2(L)+C_fix = 8.91 + log2(56) 口径),三台设备目标不同:
| 设备 | Y_target | AE 收敛设置 | 不动点 x* = LV_report | 未补偿 Lux |
|---|---|---|---|---|
| 甲(标定口径) | 56 | F2.0, 1/120s | 8.91 | 1200 ✓ |
| 乙(目标调亮一档) | 112 | F2.0, 1/60s | 7.91 | 600 ✗ |
| 丙(目标调暗半档) | 40 | F2.0, 1/170s | 9.26 | 1540 ✗ |
三台设备拍的照片亮度各不相同(乙最亮、丙最暗),各自的 LV 都"正确"地
描述了自己口径下的场景 ------乙的 7.91 精确等于"场景亮度相对 112 目标的比值",
没有任何计算错误。但三台设备的 LV 互相不可比 :甲乙丙对同一环境的
读数差恰好是 log2(Y_target 比值)(乙比甲低 1 = log2(112/56),
丙比甲高 0.35 = log2(56/40))。
⑤ 不动点性质的验证实验(可实机复现)
实验 1(验证"是不动点"):对甲设备人为锁定错误曝光(过曝 2 档)后放开:
t0: 锁定曝光, LV_report = 10.91 (偏离不动点 +2, 画面过曝)
t1: 放开, AE 迭代: x1 = 10.91 - err, err = +2 → x1 = 8.91
t2: 已在不动点, err = 0, x2 = 8.91 → 稳定
→ 无论从哪个初始曝光放开, 都回到 8.91: 不动点唯一 ✓
实验 2(验证"不动点随目标移动"):对甲设备把目标 56 → 112:
t0: 旧不动点 8.91, 画面 1200 lux 口径正常
t1: 改目标, AE 观测到 Y_cur=56 < Y_target=112, err = -1
→ 加曝光一档 → x1 = 8.91 - (-1) = 7.91
t2: Y_cur = 112 = Y_target, err = 0 → 稳定在新不动点 7.91
→ 环境没变, 不动点从 8.91 挪到 7.91, 挪动量恰为 log2(112/56) ✓
两个实验合起来的结论:AE 保证收敛到不动点(对初始条件鲁棒),
但不保证不动点对应"标定口径"------不动点在哪,完全听目标的 。
所以"LV = 环境亮度"这条性质的所有权链是:
AE 收敛 (保证到达不动点) + 目标恒等于标定值 (保证不动点位置不变)
→ LV 才等于标定口径的环境亮度
第一环由算法自动保证,第二环没有任何机制自动保证 ------它靠的是
"没人去改目标"这个管理约定。3.5.7 节的三层防线,本质就是把第二环
从"管理约定"升级为"机制保证"。
(2) "不同 sensor 基本用相同目标"------基本属实,是行业惯例
主流手机/安防平台的 AE base target 高度集中在 8bit 50~56(约 20%~22% 灰):
| 平台/惯例 | 典型 base target (8bit) |
|---|---|
| Qualcomm Chromatix 默认 | 52~55 |
| MTK 常用配置 | 50~55 |
| 安防 sensor 常用 | 50~56(Q8 习惯取 56 ≈ 22%) |
统一到 20% 左右的物理原因:
- 接近 18% 中性灰:测光体系的物理锚点,AE 收敛点与灰卡标定体系自洽
- 留 headroom:平均 20% 时高光还有约 2.3 档余量(20%→100% 是 log2(5)≈2.3 EV),防高光溢出
- SNR 最优:再低浪费动态范围,再高则高光 clip 风险陡增
统一目标的工程收益:iso_base 标定流程、Lux 系数 A、P-line 表、场景分类阈值
(LV<3 夜景 / 3~8 室内 / >11 晴天)全部可跨项目、跨 sensor 复用 。
若每个项目目标各异,这些标定数据和阈值全部要重标重调。
(3) "调效果不动目标"------准确,且是推荐做法(举例见 3.6.3)
(4) "动了目标 LV 不再准确"------准确(机理见 3.5 节,此处不重复)
3.6.2 命题需要精确化的三个地方
(1) "所有 LV 都相对固定目标"------目标并非永远不动,而是分了"层"
实际产品中目标亮度分三层,性质完全不同:
| 层 | 例子 | 是否变化 | 与 LV 口径的关系 |
|---|---|---|---|
| 层1: base target | 56(8bit 22%) | 标定后固化,几乎不动 | LV/Lux 上报的口径锚点 |
| 层2: 分照度目标表 | 高照度压到 52 防过曝、低照度提到 64 降噪 | 随 BV/照度连续变化 | 控制量,Lux 上报需补偿或排除 |
| 层3: 场景动态目标 | 逆光人脸提亮到 112、夜景模式提亮 | 逐帧/逐场景变化 | 控制量,同上 |
所以准确的说法是:LV 的"上报口径"只绑定层1 的 base target;
层2/层3 的目标变化属于 AE 控制行为,必须按 3.5.4 方法 1 补偿
(或设计上就让 Lux 链路只使用 base target 口径的收敛结果) 。
"目标固定"不是"从来没人动过目标",而是"动目标的影响被链路设计吸收了"。
(1a) 原理详解:为什么上报口径只绑定 base target,层2/层3 怎么处理
① 为什么口径锚点只能是 base target------由 LV 公式的结构决定
LV 的收敛点表达式(3.5.1 节):
LV_report = log2(L) − log2(Y_eff) + C_fix
其中 Y_eff 是"AE 最后一次收敛实际使用的目标" ------注意不是配置表里
的某个静态值,而是层1/层2/层3 叠加后本帧生效的那个值:
Y_eff = base_target × f_照度表(BV) × f_场景(人脸/夜景/逆光...)
↑层1(固定) ↑层2(随照度) ↑层3(随场景)
要 LV_report = log2(L) + 常数(纯环境量),必须 log2(Y_eff) 是常数,
即 Y_eff 恒等于标定时的 Y_cal 。三层中只有层1 满足"恒定"------
所以口径锚点只能 是 base target,这不是选择,是排除法的结果:
层2 随 BV 连续变、层3 随场景跳变,谁都不可能充当常数。
② 关键有利条件:层2/层3 的平移量是"已知的",不是"未知的"
层2/层3 造成 LV 平移 −log2(Y_eff/Y_cal),这与噪声、与 iso_base 偏差
有本质区别:Y_eff 是算法自己设置的,每一帧都精确知道 。
未知误差只能标定或容忍,已知平移可以精确扣除------这就是"必须按
3.5.4 方法 1 补偿"的可行性依据:
LV_true = LV_report + log2(Y_eff / Y_cal) (3.5.4 方法1, Y_eff 逐帧已知)
补偿后 LV_true 恢复不动点性质(3.5.6(b) 已证明),Lux 链路对
层2/层3 完全免疫。
③ 两条落地路线的完整对比
路线 A:逐帧补偿(3.5.4 方法 1)
AE 每帧输出 y_eff_cur(本帧实际生效目标),LV 计算处加修正项。
路线 B:设计上隔离------让 Lux 链路只消费 base target 口径的收敛结果
利用一个重要事实:LV = AV + TV − SV 的计算公式本身不含目标 ,
目标只通过"收敛点"隐式进入。所以只要保证"计算 LV 用的 (N,T,gain)
是 AE 在 base target 口径下收敛出来的",LV 就自动是标定口径,
一行补偿代码都不用写。具体有三种实现形态:
B1 (门控): 层2/层3 生效期间冻结 Lux 上报, 退出后恢复更新
B2 (旁路): 层2/层3 生效期间, Lux 改由"曝光直算路径"提供
Lux ≈ C_exp / (T × G_total) (由 iso_base 标定 C_exp)
------ 该路径同样只在 base 口径收敛时正确, 故需配合门控
B3 (双收敛): AE 内部并行维护一个"虚拟 base 口径收敛器":
用当前 LV 估计值查 base target 的 P-line, 得到
"假如目标没被层2/层3 修改, AE 本应使用的 (N,T,gain)"
→ 用这套虚拟参数算 LV/Lux, 与实际曝光解耦
④ 数值例子:夜景模式提亮,两条路线的效果
场景:夜晚街道,真实 40 lux(真实 LV = 4.0),base target = 56,
iso_base = 50。夜景模式把目标提到 112(提亮一档,层3 生效)。
AE 重新收敛: 曝光加大一档 (如 1/30s → 1/15s)
LV_report = 4.0 − log2(112/56) = 3.0
未补偿 Lux = 2.5 × 2^3.0 = 20 lux ← 凭空"变暗"一半 ✗
路线 A(逐帧补偿):
修正项 = log2(112/56) = +1
LV_true = 3.0 + 1 = 4.0 → Lux = 40 ✓
夜景模式全程 Lux 连续正确上报; 用户从街道走进 5 lux 巷子,
Lux 平滑降到 5 --- 状态量始终鲜活
路线 B1(门控冻结):
夜景模式生效期间: Lux 保持进入前的 40 lux 不更新
退出夜景模式: AE 回到 base 口径收敛, LV_report = 4.0, Lux 恢复更新
若用户在夜景模式期间环境从 40 lux 变到 5 lux:
→ Lux 仍报 40, 直到退出模式才跳到 5 (滞后 + 跳变)
路线 B3(虚拟收敛):
实际曝光: 1/15s (夜景口径) → LV_report = 3.0 (只给 AE 反馈用)
虚拟收敛: LV 估计 4.0 查 base P-line → 虚拟 (N,T,gain) = 1/30s
→ LV_virtual = 4.0 → Lux = 40 ✓
AE 控制与 Lux 上报彻底解耦, 但要多维护一套查表逻辑
⑤ 两条路线的选择依据
| 路线 A(补偿) | 路线 B1(门控) | 路线 B3(虚拟收敛) | |
|---|---|---|---|
| 实现代价 | 一行加法 + AE 输出 y_eff | 状态标志 + 冻结逻辑 | 双份 P-line 查表 |
| 层2/层3 期间 Lux | 连续正确 | 冻结(滞后) | 连续正确 |
| 环境突变响应 | 实时 | 退出模式才响应 | 实时 |
| 风险点 | y_eff 取值必须与收敛帧一致(3.5.6(e) 要点1) | 冻结期 Lux 失真 | 虚拟收敛与实际收敛可能发散 |
| 适用 | 有动态目标功能的产品(主流选择) | 目标调整短暂/低频 | AE 与 Lux 架构彻底分离的平台 |
工程结论 :层2/层3 的目标变化对 AE 控制是"信号"(就是要画面变亮),
对 Lux 上报是"误差"(环境没变读数不能变)------同一个量在两条消费链里
身份相反,所以必须在链路上分开处理 :AE 反馈用未补偿的 LV_report,
Lux/场景分类用补偿后的 LV_true(路线 A)或 base 口径的收敛结果(路线 B)。
这正是"上报口径只绑定 base target"的完整含义:不是层2/层3 不许存在,
而是它们的痕迹不允许出现在上报链路里。
(1b) 原理详解:为什么同一个量在两条消费链里"身份相反"
① 先明确两条消费链各自要回答的问题
LV 这个量在系统里被两条完全独立的链路消费,它们要回答的问题不同:
链路1 (AE 反馈环): "画面离我想要的样子还差多少?"
→ 比较对象是 Y_target (当前生效目标)
→ LV_report 恰好编码了这个偏差, 是"信号"
链路2 (状态上报): "环境现在有多亮?"
→ 比较对象是物理世界 (lux)
→ LV_report 里混入的目标痕迹是"误差"
② 为什么对 AE 反馈环,目标变化是"信号"------它就是控制量本身
AE 反馈环的误差定义(3.6.1(1a) ①):
err = log2(Y_cur) − log2(Y_target)
夜景模式把目标 56→112 后,err 立刻变成 −1,AE 据此加一档曝光------
这个 err 正是设计者想要的 :目标改动的全部目的就是制造这个 err,
驱动曝光移动。此时如果把 LV 补偿成 LV_true 再喂给 AE 反馈环会怎样?
错误做法: AE 反馈用 LV_true
err' = LV_target − LV_true
夜景模式: 目标 112 (LV_target 口径随之平移 −1), LV_true 不含平移
→ err' = (LV_target_新口径) − LV_true
→ 平移量在等式两侧各出现一次, 相互抵消
→ err' = 0, AE 认为已经收敛, 不加曝光!
→ 夜景模式失效: 目标改了, 画面纹丝不动
补偿项在反馈环里被"自消" ------因为 LV_target(查表用的目标口径 LV)
和 LV_true 若用同一口径,目标平移在减法两侧同号出现,控制误差归零。
这就是"AE 反馈用未补偿的 LV_report"的数学原因:反馈环内部
口径自洽即可,目标平移必须保留在环内才能驱动曝光移动。
③ 为什么对 Lux 上报链,目标变化是"误差"------它破坏了状态量的定义
状态量的定义(第 3 节):环境不变则读数不变。Lux 链路的全部价值
建立在这个性质上------下游(日夜切换、补光灯、曝光指数显示、
多帧合成触发)都假设"Lux 变化 = 环境变化"。
目标变化破坏这个假设的方式(3.6.2(1a) ④ 已算过):夜景提亮一档,
Lux 从 40 凭空掉到 20。下游看到的是"环境突然变暗一半"------
这是一个假事件 。假事件的危害不是"数值不准"这么简单,而是
它与环境真事件在下游眼里无法区分:
下游视角:
事件A (真): 用户走进树荫, 40 lux → 20 lux → 应该触发夜切预备
事件B (假): 夜景模式提亮一档, 40 lux → 20 lux → 不应该触发任何东西
两条 Lux 曲线逐比特相同 → 下游必然误触发
④ "身份相反"的严格表述:同一个平移量,在两个环里的符号角色不同
设目标平移量 δ = log2(Y_eff/Y_cal)。考察它在两条链路里的出现位置:
AE 反馈环: err = [LV_target(含δ口径)] − [LV_report(含δ)]
δ 在两侧同号 → 相减后保留 → 驱动曝光 → δ 是"信号"
Lux 链路: Lux = A × 2^LV_report, LV_report 含 −δ 的平移
δ 单侧出现 → 直接污染读数 → δ 是"误差"
修正: LV_true = LV_report + δ → δ 被显式扣除
关键区别:反馈环是"差分结构"(两个同口径量相减),平移量在
两侧成对出现、相互加强控制;上报链是"直通结构"(单量直接换算),
平移量单侧出现、直接落进读数 。同一个 δ,差分结构里是驱动力,
直通结构里是污染源------这就是"身份相反"的数学本质。
⑤ 反例:两条链路混用一口径的两种灾难
灾难 1:上报链用了未补偿的 LV_report(③ 的假事件)
延续 3.6.2(1a) ④ 的夜景例子,加上下游日夜切换阈值(Lux < 15 触发夜切):
时间线 (环境恒定 40 lux, 夜切阈值 15 lux):
t0: 普通模式, Lux=40, 不触发 ✓
t1: 用户开夜景模式, 目标 56→112
t2: AE 收敛, Lux 掉到 20 ------ 仍高于 15, 未触发 (侥幸)
t3: 用户再开"极致夜景", 目标 56→224 (再提亮一档)
t4: AE 收敛, Lux 掉到 10 < 15 → 触发夜切!
→ 夜切把曝光进一步拉长, 与夜景模式的提亮目标打架
→ 画面闪烁/来回切换 (夜切退出条件也满足不了, 死循环边缘)
灾难 2:AE 反馈环用了补偿后的 LV_true(② 的自消)
时间线 (环境 40 lux, 夜景模式目标 112):
t0: 普通模式收敛, LV_true = LV_report = 4.0
t1: 开夜景模式, 目标 112
t2: 若 AE 用 LV_true 反馈: err = LV_target − LV_true
LV_target 也按 base 口径记录 (4.0), LV_true = 4.0 → err = 0
→ AE 不动, 画面亮度不变
→ 夜景模式"开启成功"但毫无效果, 用户投诉"夜景模式没用"
两个灾难方向相反(一个误动作、一个不动作),根源相同:
给链路喂了错误身份的 LV。
⑥ 正确架构:单点计算、双口径输出
工程上最清晰的实现是 LV 只算一次,出口处分叉成两个口径:
c
/* LV 只计算一次 (单点, 保证口径一致性) */
lv_report_q8 = av_q8 + tv_q8 - sv_q8; /* 未补偿, AE 反馈用 */
corr_q8 = log2_q8((int64_t)y_eff_cur * 256 / Y_CAL); /* 本帧生效目标修正 */
/* 出口分叉: 两条消费链各取所需 */
lv_for_ae_q8 = lv_report_q8; /* 反馈环: 保留 δ */
lv_for_state_q8 = lv_report_q8 + corr_q8; /* 上报链: 扣除 δ */
lux_q8 = lux_from_lv(lv_for_state_q8); /* Lux 上报 */
scene_class = classify_scene(lv_for_state_q8); /* 场景分类 */
night_switch = (lux_q8 < NIGHT_TH_Q8); /* 日夜切换 */
/* AE 反馈环内部: err = lv_target_q8 - lv_for_ae_q8; */
三条纪律:
1. 单点计算: lv_report 只在一处算, 两条链都从它分叉
→ 杜绝"两处各算一遍、口径悄悄不一致"的 bug 源
2. 命名显式: lv_for_ae / lv_for_state 强制使用者声明用途
→ code review 时口径混用一眼可见
3. 修正项与目标同源: corr_q8 的 y_eff_cur 必须取自 AE 本帧
收敛实际用的目标 (3.5.6(e) 要点1), 不能另读配置表
⑦ 数值例子:正确架构下的完整时间线
环境恒定 40 lux,用户操作:开夜景(目标 112)→ 走进 5 lux 巷子 → 关夜景:
| 帧 | 场景/操作 | Y_eff | AE 收敛 | LV_report | corr | LV_true | Lux | 下游行为 |
|---|---|---|---|---|---|---|---|---|
| t0 | 普通模式 | 56 | 1/30s | 4.0 | 0 | 4.0 | 40 | 正常 |
| t1 | 开夜景 | 112 | 1/15s | 3.0 | +1 | 4.0 | 40 | Lux 不跳变 ✓ |
| t2 | 走进巷子 | 112 | 1/120s | 1.0 | +1 | 2.0 | 5 | Lux 平滑下降 ✓ |
| t3 | 关夜景 | 56 | 1/240s | 2.0 | 0 | 2.0 | 5 | Lux 不跳变 ✓ |
对照两条错误架构:
若上报链用 LV_report (灾难1): t1 处 Lux 40→20 假跳变, t3 处 5→10 假跳变
若 AE 反馈用 LV_true (灾难2): t1 处 err=0, 曝光停在 1/30s, 夜景无效
正确架构: 画面按意图变亮/变暗 (AE 链), Lux 曲线只反映环境 (上报链)
t1→t2 期间画面从"夜景提亮"平滑过渡到"巷子更暗",两个变化在画面上
叠加,但 Lux 曲线只走出"40→5"一段------环境真相 。画面观感与
状态量读数各说各话、互不污染,这就是"链路分开处理"的最终形态。
⑧ 与 3.5.7 三层防线的关系
3.5.7 防的是"调优改目标没人知道"(配置漂移),本节防的是
"动态目标本身是功能"(设计使然)。两者共用同一套修正机制
(LV_true = LV_report + log2(Y_eff/Y_cal)),区别只在 Y_eff 的来源:
3.5.7 场景: Y_eff = 调优改过的静态目标 (长期恒定, 但值可能≠Y_cal)
本节场景: Y_eff = 层2/层3 动态目标 (逐帧/逐场景变化)
统一公式: LV_true = LV_report + log2(Y_eff / Y_cal) (Y_eff 永远取"本帧实际生效值")
所以 3.5.7 的"根本性方案"(LV 公式显式含 log2(Y_cur/Y_cal))
天然同时覆盖本节------一个机制,防两种漂移 。这也是为什么
修正项必须挂在"本帧实际生效目标"上而不是任何静态配置上:
静态配置读数只能覆盖 3.5.7,覆盖不了层2/层3。
(2) "调其他参数满足亮度效果"------有一个隐含的技术前提:AE 统计位置
"动 gamma/tone mapping 不动目标"能成立,前提是 AE 统计在 raw 域
(BLC 之后、gamma 之前)。此时:
- 调 gamma → 只改变 ISP 输出观感,raw 域统计不变 → AE 收敛点不动 → LV 稳定 ✓
- 若 AE 统计在 gamma 之后的 Y 域 → 调 gamma 等效于调目标 → 收敛点移动 → LV 平移 ✗
这是很多平台坚持 raw 域统计(或统计前先做逆 gamma 归一)的深层原因之一:
把"观感调整"与"曝光状态量"在信号链上解耦 。评审 AE 设计时,
统计位置与目标口径必须一起确认,只看目标值是否固定是不够的。
(3) "用户 EV 补偿"是隐藏的例外------它等效于改目标
用户手动 EV +1 档,AE 收敛点移动 1 档,效果与把目标翻倍完全等价
(都是要求画面亮一档)。此时 LV_report 会掉 1 EV。行业惯例的处理是:
c
/* Lux 上报使用不含 EV comp 的 LV */
LV_for_lux = AV + TV - SV + log2_q8(Y_base / Y_cal); /* 不加 EV comp 项 */
/* 或等价地: LV_for_lux = LV_report + EV_comp_q8 */
即 EV comp 平移被显式排除在 Lux 口径之外 ------用户故意过曝是"意图",
不是"环境变亮了"。这与 3.5.4 方法 1 的补偿公式同构,只是符号方向相反。
3.6.3 数值例子对比:调 gamma vs 调目标
同一场景(1200 lux 商铺,真实 LV=8.91),两种"画面提亮一档"的手段:
手段 A:gamma 提亮(AE 统计在 raw 域)
raw 域统计不变 → AE 收敛点不动: F2.0, 1/120s, gain 2x
EV = 8.91, LV_report = 8.91, Lux = 1200 ✓ 全部稳定
画面: 输出变亮一档 (观感目标达成)
手段 B:目标 56 → 112(AE 统计无论在哪都会响应)
AE 重新收敛: F2.0, 1/60s (曝光加大一档)
EV = 7.91, LV_report = 7.91 (掉 1), 未补偿 Lux = 600 ✗ (凭空"变暗"一半)
画面: 输出变亮一档 (观感目标同样达成)
| 手段 A (gamma) | 手段 B (改目标) | |
|---|---|---|
| 画面观感 | 亮一档 | 亮一档 |
| LV_report | 8.91(不变) | 7.91(掉 1) |
| Lux 上报 | 1200(正确) | 600(错误,需补偿) |
| 场景分类阈值 | 不受影响 | 全部阈值需重校 |
| 跨机型一致性 | 保持 | 破坏(若各机型目标不同) |
| AE 收敛行为 | 不变 | 收敛点移动,收敛过程重走 |
两种手段观感等价,但对 LV/Lux 链路的影响天差地别 ------这就是
"调效果优先动 gamma/tone mapping,不动目标"的完整理由。