4. 统计信息采集:从RAW图到亮度直方图与分区统计

好,我们进入AE收敛技术中最关键的一环——统计信息采集。

你想想看,自动曝光要收敛,它得先知道当前画面亮不亮、哪里亮、哪里暗。这些信息从哪来?就是从RAW图里算出来的。说白了,没有准确的统计信息,后面的AE算法就是盲人摸象。

我个人习惯把统计信息采集分成三个层次:全局直方图、分区统计、权重矩阵。咱们一层一层剥开。

4.1 从RAW图获取亮度直方图

直方图这东西,大家应该不陌生。横轴是亮度等级(0~1023,如果是10-bit RAW),纵轴是像素个数。但这里有个坑——RAW图的直方图和RGB直方图不是一回事。

RAW图每个像素只有一个颜色通道(R/G/B之一),所以它的直方图反映的是单通道的原始光强分布,还没经过白平衡、去马赛克、Gamma校正这些处理。这个原始分布对AE来说特别宝贵,因为它最真实地反映了场景的物理亮度。

核心原则:AE统计一定要在RAW域做,越早越好。一旦进了RGB域,很多非线性变换会把亮度关系搞乱。

具体怎么算?我贴一段伪代码,大家感受一下:

// 10-bit RAW, 宽度W, 高度H
uint32_t histogram[1024] = {0};

for (int y = 0; y < H; y++) {
    for (int x = 0; x < W; x++) {
        uint16_t pixel = raw_buffer[y * W + x];
        // 直接累加,不做任何变换
        histogram[pixel]++;
    }
}

// 可选:归一化到百分比
float hist_norm[1024];
uint32_t total_pixels = W * H;
for (int i = 0; i < 1024; i++) {
    hist_norm[i] = (float)histogram[i] / total_pixels;
}

嗯,这里要注意:不要对RAW像素做任何预处理。我在项目中遇到过有人先做了黑电平校正再算直方图,结果暗部细节全丢了,AE死活收敛不到目标亮度。后来查了半天才发现是统计源的问题。

我的经验:如果传感器有坏点,可以在统计前做简单的坏点校正(中值滤波),但千万别做降噪或锐化,那些会改变亮度分布。

4.2 分区统计(Zone Statistic)的原理

全局直方图有个致命问题——它不知道亮和暗分别在哪。比如一个画面左边亮右边暗,全局直方图可能看起来是中间调,但实际AE需要知道左边过曝了、右边欠曝了。

所以我们需要分区统计。

分区统计,就是把画面切成M×N个格子,每个格子独立算亮度统计值。这样AE就能知道每个区域的亮度情况。

我常用的分区方案:

应用场景 分区数 说明
普通拍照 5×5 或 7×7 平衡精度和计算量
人脸优先 9×9 或 11×11 需要更精细的人脸区域定位
视频/预览 3×3 或 5×5 帧率高,计算量要小
HDR场景检测 15×15 以上 需要捕捉局部高动态细节

每个分区算什么呢?我一般算三个值:

  • 平均亮度:分区内所有像素的均值,代表整体亮度水平
  • 亮度方差:反映分区内的纹理/对比度,用于判断是平滑区域还是边缘区域
  • 过曝/欠曝像素比例:亮度大于阈值(比如950/1023)或小于阈值(比如50/1023)的像素占比

我曾经在一个强背光场景的项目里,只用平均亮度做分区统计,结果AE把主体人脸压到全黑,背景倒是正常了。后来加上过曝/欠曝比例检测,才把背光场景的AE收敛做好。

避坑:分区数不是越多越好。分区太多,每个分区像素太少,统计结果噪声大,AE反而会抖动。我建议分区内至少要有1000个像素。

4.3 权重矩阵的设计

有了分区统计,接下来就是权重矩阵。权重矩阵决定了AE更关注画面的哪个区域。

为什么需要权重?因为用户拍照时,通常主体在画面中央。如果中央和边缘的亮度权重一样,AE可能会为了照顾边缘而牺牲主体。

我常用的权重矩阵设计思路:

  1. 中心权重:中央区域权重高,边缘权重低。适合普通拍照场景。
  2. 人脸权重:检测到人脸后,人脸所在分区权重极高,其他区域权重降低。
  3. 亮度自适应权重:根据场景亮度动态调整。比如夜景下,暗部区域权重提高,防止AE把暗部压死。
  4. 用户触控权重:用户点击屏幕对焦/测光的位置,权重设为最高。

下面是一个典型的5×5中心权重矩阵示例:

// 5×5 中心权重矩阵
// 中心区域权重最高,向外递减
float weight_5x5[5][5] = {
    {0.2, 0.4, 0.6, 0.4, 0.2},
    {0.4, 0.8, 1.0, 0.8, 0.4},
    {0.6, 1.0, 1.5, 1.0, 0.6},
    {0.4, 0.8, 1.0, 0.8, 0.4},
    {0.2, 0.4, 0.6, 0.4, 0.2}
};

// 归一化,使所有权重之和为1
float sum = 0;
for (int i = 0; i < 5; i++)
    for (int j = 0; j < 5; j++)
        sum += weight_5x5[i][j];

for (int i = 0; i < 5; i++)
    for (int j = 0; j < 5; j++)
        weight_5x5[i][j] /= sum;

最终AE的亮度统计值就是:

float weighted_brightness = 0;
for (int i = 0; i < NUM_ZONES; i++) {
    weighted_brightness += zone_stats[i].avg_luma * zone_weights[i];
}

这个weighted_brightness就是喂给AE控制器的核心输入。

我的习惯:权重矩阵不要写死。我会在ISP的tuning阶段留出接口,让调试工程师可以动态调整权重分布。同一个传感器在不同项目里,权重矩阵可能完全不同。

4.4 整体流程:一张图说清楚

下面我用一张SVG图把整个统计信息采集的流程串起来:

统计信息采集流程 RAW图输入 全局直方图计算 分区统计计算 亮度分布直方图 分区亮度/方差/过曝比 权重矩阵加权 加权亮度统计值 → AE控制器 直方图用于场景分类 分区统计用于AE收敛

4.5 实战中的几个关键点

最后,我总结几个实战中容易踩的坑:

  • 统计源选择:一定要用RAW域数据,不要用RGB或YUV域。我见过有人用YUV的Y分量做统计,结果Gamma校正后的亮度分布和物理亮度完全对不上。
  • Bayer格式处理:如果直接统计RAW,不同颜色通道的响应曲线不同。我建议先做简单的白平衡增益补偿,或者只统计G通道(因为G通道对亮度贡献最大)。
  • 权重矩阵归一化:权重矩阵一定要归一化到总和为1,否则AE控制器的增益计算会出错。
  • 统计帧率:统计信息不需要每帧都算。我一般设置每2~3帧算一次,节省带宽和功耗。
曾经踩过的坑:有一次我在一个高帧率项目里,每帧都做全图直方图统计,结果DMA带宽被占满,导致预览画面卡顿。后来改成隔帧统计,问题解决。统计频率不是越高越好,够用就行。

好了,这一章的内容就到这里。统计信息采集是AE收敛的基石,直方图给你全局视野,分区统计给你局部细节,权重矩阵告诉你该关注哪。三者配合好了,AE收敛才能又快又稳。