AE 控制器收到的并不是“场景亮度”这个抽象答案,而是一组有限的统计数据:每个区域有多亮、直方图怎样分布、多少像素已经饱和,以及哪些区域可能属于人脸或主体。测光阶段要把这些数据压缩成可靠的控制依据,目标阶段则要回答更难的问题:这幅画面究竟应该有多亮?
统计从哪里来
直接读取全分辨率 RAW 会浪费带宽,因此相机常由硬件统计模块生成 个亮度窗口、RGB 通道和、直方图与饱和计数。输入可能位于 Bayer 域,也可能来自经过部分 ISP 的预览支路。
无论统计位置在哪里,都需要明确:
- 黑电平是否已经扣除,坏点与光学黑区是否被排除;
- 统计值是线性光、近似线性值,还是 Gamma 编码后的亮度;
- Bayer 各颜色样本是否按密度与响应正确归一化;
- 裁切、数字变焦和防抖后,统计窗口对应画面的哪个位置;
- 饱和像素、极暗像素和不可信区域是否参与平均。
如果直接平均非线性码值,结果会受到传递函数影响;如果把饱和窗户与暗部主体等权平均,单个均值又会掩盖场景结构。因此工程实现通常同时保留分区统计和直方图,而不是只输出一个数字。
测光模式就是权重设计
设第 个统计块亮度为 ,权重为 ,最简单的测光结果是:
平均测光令各区权重接近;中央重点让画面中心贡献更大;点测光只使用很小区域。人脸优先或主体跟踪仍可写成加权统计,但权重会随检测结果在时间上变化。
同一画面为什么会得到不同曝光
平均、中央重点与点测光看到的是同一组像素,但赋予区域的权重不同。
CC0 实拍样片照片包含明亮窗户、灯具与暗部家具。指标仍由教学统计矩阵计算,用来隔离比较三种权重;真实相机则会直接读取样片像素与传感器统计。
权重切换不能突然发生。人脸刚进入画面时若立即把其他区域权重清零,曝光会产生明显跳变;更稳妥的做法是验证检测置信度,并让 ROI 权重逐帧渐变。多个主体同时出现时,还要决定优先级、面积与位置如何共同作用。
把饱和剔除、ROI 与目标写在一起
普通加权平均仍可能被已经剪切的窗户或灯泡支配。令 表示第 块的饱和比例, 表示统计可靠度,可以先构造有效权重:
全局测量和主体 ROI 测量分别为:
其中 是区域与人脸或主体框的重叠比例。检测置信度决定融合系数 ,而不是让 ROI 一出现就完全接管:
最后再由基础目标、用户曝光补偿和高光保护共同形成目标:
可随高光比例升高而下降。这里把饱和块整块剔除是为了便于说明;真实系统更常使用像素级截尾、分位数、软权重与最小有效样本保护,避免有效区域太少时统计失真。
下面的独立示例把一张统计网格、中心人脸 ROI、饱和剔除和目标计算串在一起:
import numpy as np
Y = np.array([ [0.96, 0.72, 0.30, 0.18], [0.82, 0.24, 0.20, 0.16], [0.35, 0.22, 0.19, 0.14], [0.18, 0.16, 0.14, 0.12],], dtype=float)sat_ratio = np.array([ [0.80, 0.30, 0.02, 0.00], [0.55, 0.03, 0.01, 0.00], [0.04, 0.02, 0.01, 0.00], [0.00, 0.00, 0.00, 0.00],])
base_weight = np.ones_like(Y)roi = np.zeros_like(Y)roi[1:3, 1:3] = 1.0 # 教学用人脸区域valid = (sat_ratio < 0.40) & (Y > 0.01)
22 collapsed lines
def weighted_mean(values, weights): total = weights.sum() return float((values * weights).sum() / max(total, 1e-6))
global_y = weighted_mean(Y, base_weight * valid)roi_y = weighted_mean(Y, base_weight * valid * roi)confidence = 0.85beta = np.clip(0.65 * confidence, 0.0, 0.65)measured = (1.0 - beta) * global_y + beta * roi_y
highlight_fraction = float((sat_ratio >= 0.40).mean())highlight_protection = np.clip(1.0 - 1.5 * highlight_fraction, 0.65, 1.0)base_target, exposure_comp_ev = 0.28, 0.3target = np.clip( base_target * 2.0 ** exposure_comp_ev * highlight_protection, 0.12, 0.45,)error_ev = np.log2(target / max(measured, 1e-6))
print(f"global={global_y:.3f}, roi={roi_y:.3f}, fused={measured:.3f}")print(f"target={target:.3f}, requested change={error_ev:+.2f} EV")示例假设 已是可比较的近似线性亮度,ROI 也已正确映射到统计网格。它没有处理多人优先级、肤色目标、检测迟滞或 Bayer 通道差异;这些都应在产品策略和测试中单独定义。
均值之外为什么还需要直方图
两个场景可能有相同均值,却一个是均匀灰墙,另一个由大片黑暗与小块高光组成。直方图可以提供:
- 高光与阴影所占比例;
- 中位数、分位点和截尾平均;
- 接近传感器饱和的像素比例;
- 场景动态范围与逆光程度;
- 多峰分布和潜在主体/背景分离线索。
截尾均值会忽略最暗与最亮的少量异常值,鲁棒性通常高于普通平均;高光比例则可成为曝光上限,避免为了抬亮主体而让大面积天空彻底饱和。它们不是互相替代的“最佳测光算法”,而是共同描述场景。
目标亮度不是固定的 18% 灰
测量完成后,控制器要比较当前亮度 与目标 。在 EV 域中:
对数表达让曝光倍数变得直观:误差 约等于曝光量乘 2, 约等于除以 2。
AE 追逐的不是固定灰度
目标亮度、曝光补偿和高光约束共同定义控制误差;18% 灰并不是所有画面的唯一答案。
实际目标会结合场景分类、人脸肤色、高光比例和动态范围动态调整,并设置死区避免来回抖动。
18% 反射率灰卡适合标定,却不是所有真实场景的审美目标。雪地本来就应明亮,夜景应保留夜感,舞台灯光允许大片暗部,逆光人脸又可能需要牺牲部分背景高光。现代 AE 常综合场景分类、人脸肤色、HDR 能力和产品风格动态选择目标。
曝光补偿可以理解为对目标施加 EV 偏置:
但最终目标还会受到高光保护、最低信噪比和硬件曝光范围限制。用户设置 +2 EV 并不保证画面能无条件增加两档;当高光或快门已到边界时,系统需要明确采用饱和、限幅还是改变其他参数。
死区防止统计噪声变成曝光呼吸
即使场景静止,相邻帧的统计也会受读出噪声、主体微动和检测抖动影响。若每个微小误差都触发曝光调整,预览会不断呼吸。常见做法是在目标附近设置死区:
死区可随 ISO、场景稳定度和视频模式变化。还可以对统计结果做时域滤波,但滤波会增加延迟,因此需要和后续控制器一起设计。
工程验证
一组有效的测光测试至少应包括:
- 灰卡在画面不同位置和不同占比下的结果;
- 明亮窗户、逆光人脸、雪地、夜景与舞台灯光;
- 人脸 ROI 出现、消失和多人切换时的权重连续性;
- 高光比例逐渐增加时,目标与曝光上限是否平滑;
- 线性统计与 Gamma 域统计是否被误混;
- 数字变焦、裁切和防抖后 ROI 坐标是否正确;
- 极暗统计值下是否通过 避免除零与异常 EV。
测光与目标只负责产生“希望改变多少曝光”的误差信号。如何跨帧稳定地执行这个变化,并在快门、增益和防频闪约束之间分配预算,是下一篇 AE 怎样稳定收敛?从曝光闭环到防频闪 的主题。