3、直方图与图像统计:亮度直方图的生成原理,如何通过直方图判断过曝/欠曝

说到自动曝光,有个东西绕不开——亮度直方图。

我刚开始做ISP的时候,总觉得直方图就是个统计图,看看像素分布而已。后来调过几个项目才发现,直方图其实是整个AE算法的“眼睛”。你连场景的亮度分布都看不清,怎么决定曝光参数?

3.1 亮度直方图是怎么生成的?

说白了,直方图就是一张图里所有像素亮度的“人口普查”。

我们把0到255的亮度值分成256个“桶”,然后遍历每个像素,看它的亮度值落在哪个桶里,就给那个桶加1。最后,每个桶里的像素数量就是直方图的高度。

核心公式:

对于一张宽W、高H的图像,亮度直方图H(k)定义为:

H(k) = Σ Σ δ(I(x,y) - k)   for k = 0, 1, ..., 255
其中:
  I(x,y) 是像素(x,y)的亮度值
  δ(n) 是狄拉克函数,当n=0时值为1,否则为0

简单理解:就是数一数亮度值为k的像素有多少个。

嗯,这里要注意——我们通常说的亮度直方图,输入的是亮度分量Y,不是RGB。如果你直接从RGB三通道分别统计,那叫颜色直方图,用途不一样。

生成步骤(我习惯这么写代码)

// 伪代码:亮度直方图生成
uint32_t histogram[256] = {0};

for (int y = 0; y < height; y++) {
    for (int x = 0; x < width; x++) {
        // 假设已经拿到Y分量
        uint8_t Y = image[y][x];
        histogram[Y]++;
    }
}

// 归一化(可选,方便后续处理)
float normalized[256];
uint32_t totalPixels = width * height;
for (int i = 0; i < 256; i++) {
    normalized[i] = (float)histogram[i] / totalPixels;
}

我在项目中遇到过一个问题:如果直接对原始RAW图做直方图,你会发现它特别“瘦”,因为RAW图的动态范围还没展开。所以实际工程里,我们通常对Gamma校正后的Y分量做统计,这样直方图才“胖”得好看,能反映人眼感知的亮度分布。

3.2 直方图长什么样?

你想想看,一张正常的照片,直方图应该是什么形状?

  • 正常曝光:直方图中间高、两边低,像个“钟形”或“小山包”。像素主要分布在128附近,暗部和亮部都有少量像素。
  • 过曝:直方图右侧“撞墙”了,大量像素堆积在240-255区域。说白了就是画面太亮,细节全没了。
  • 欠曝:直方图左侧“贴地”,大量像素挤在0-15区域。画面黑乎乎一片,暗部细节丢失。
  • 低对比度:直方图集中在中间一小段,两边几乎没有像素。画面灰蒙蒙的,像蒙了一层雾。
  • 高对比度:直方图两端高、中间低,像个“U”形。亮部很亮,暗部很暗,中间过渡少。

我的经验:判断过曝/欠曝,别只看直方图的“尖峰”位置。有时候直方图中间有个大峰,但右侧尾巴拖到255,说明高光区域已经饱和了。这种“局部过曝”在逆光场景特别常见。

3.3 如何通过直方图判断过曝/欠曝?

判断方法其实不复杂,我总结了一套量化指标:

指标 计算公式 正常范围 过曝/欠曝判断
平均亮度 μ = Σ(k × H(k)) / ΣH(k) 100 ~ 150 > 180 可能过曝;< 70 可能欠曝
过曝像素占比 P_over = ΣH(k) / total, k ∈ [240, 255] < 1% > 5% 严重过曝
欠曝像素占比 P_under = ΣH(k) / total, k ∈ [0, 15] < 1% > 5% 严重欠曝
动态范围利用率 DR = (max_k - min_k) / 255 > 80% < 50% 对比度不足

我曾经在一个安防项目中踩过坑:只靠平均亮度判断曝光,结果在雪景场景下,平均亮度只有90,但直方图显示暗部像素很少,反而是亮部像素被压缩了。后来我加上了过曝/欠曝像素占比这个指标,才把问题解决。

3.4 直方图在AE算法中的实际应用

直方图不只是用来“看”的,它可以直接参与曝光决策。我常用的几种策略:

  1. 基于平均亮度的反馈控制:计算当前帧的平均亮度,和目标亮度(比如120)做差,然后用PID控制器调整曝光时间或增益。这是最基础的方法。
  2. 基于直方图分布的权重调整:不是所有像素都“平等”。人脸区域的像素权重应该更高,天空区域的权重可以降低。我习惯用语义分割+直方图加权的方式,让AE优先保证主体区域的曝光。
  3. 基于直方图“拉伸”的对比度优化:如果直方图集中在中间,说明对比度低。这时候可以适当增加曝光时间,让直方图“撑开”,提高画面层次感。
  4. 基于过曝/欠曝区域的保护策略:当检测到过曝像素占比超过阈值时,强制降低曝光量;当欠曝像素过多时,提升增益。这种策略在逆光场景特别有效。

注意:直方图统计的是全局信息,它无法告诉你“哪里”过曝了。比如一张图左边很暗、右边很亮,直方图可能看起来“正常”,但实际局部已经过曝了。所以工程上,我建议配合局部直方图或网格统计一起使用。

3.5 直方图与AE算法的知识体系

下面这张图是我自己整理的,把直方图在AE中的角色和流程串起来了:

直方图在自动曝光算法中的角色与流程 原始图像(RAW/YUV) 亮度直方图生成(Y分量) 特征提取 平均亮度 | 过曝占比 | 欠曝占比 | 动态范围 曝光决策(PID / 查表 / 规则) 曝光参数(时间/增益/光圈) 反馈闭环 说明:直方图是AE算法的“眼睛”,提供场景亮度分布的量化信息,驱动曝光参数的闭环调整。 实际工程中常配合局部直方图、人脸检测、场景分类等模块共同决策。

3.6 工程落地中的几个坑

直方图看着简单,但真用到产品里,坑不少。我挑几个常见的说说:

  • 直方图抖动问题:相邻两帧的直方图可能差异很大,导致曝光参数来回跳。解决办法是加时间域滤波,比如用指数移动平均(EMA)平滑直方图。
  • ROI区域的选择:整张图的直方图不一定代表主体。我习惯把画面分成3×3或5×5的网格,给中心区域更高的权重,或者结合人脸检测框做局部统计。
  • HDR场景的直方图“假象”:在HDR模式下,直方图可能看起来“正常”,但实际暗部和亮部细节是通过多帧融合出来的。这时候直方图不能直接反映单帧的曝光质量。
  • 低光照下的噪声干扰:暗光环境下,直方图在低亮度区域会有大量噪声像素,导致统计结果失真。我建议先做降噪预处理,或者只统计亮度值大于某个阈值的像素。

避坑指南:我曾经在一个手机项目中,发现夜景模式的直方图总是“偏左”,但画面看起来并不暗。后来排查发现,是因为多帧合成后的直方图统计的是合成前的单帧数据。记住:直方图统计的时机要和最终输出对齐。

3.7 小结

直方图是AE算法的基础,但别把它当成万能药。它告诉你“是什么”,但不告诉你“为什么”。

实际工程中,我通常把直方图作为第一层判断,再结合场景分类、人脸检测、运动检测等信息做综合决策。这样既保证了算法的鲁棒性,又不会让逻辑变得过于复杂。