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算法中的实际应用
直方图不只是用来“看”的,它可以直接参与曝光决策。我常用的几种策略:
- 基于平均亮度的反馈控制:计算当前帧的平均亮度,和目标亮度(比如120)做差,然后用PID控制器调整曝光时间或增益。这是最基础的方法。
- 基于直方图分布的权重调整:不是所有像素都“平等”。人脸区域的像素权重应该更高,天空区域的权重可以降低。我习惯用语义分割+直方图加权的方式,让AE优先保证主体区域的曝光。
- 基于直方图“拉伸”的对比度优化:如果直方图集中在中间,说明对比度低。这时候可以适当增加曝光时间,让直方图“撑开”,提高画面层次感。
- 基于过曝/欠曝区域的保护策略:当检测到过曝像素占比超过阈值时,强制降低曝光量;当欠曝像素过多时,提升增益。这种策略在逆光场景特别有效。
注意:直方图统计的是全局信息,它无法告诉你“哪里”过曝了。比如一张图左边很暗、右边很亮,直方图可能看起来“正常”,但实际局部已经过曝了。所以工程上,我建议配合局部直方图或网格统计一起使用。
3.5 直方图与AE算法的知识体系
下面这张图是我自己整理的,把直方图在AE中的角色和流程串起来了:
3.6 工程落地中的几个坑
直方图看着简单,但真用到产品里,坑不少。我挑几个常见的说说:
- 直方图抖动问题:相邻两帧的直方图可能差异很大,导致曝光参数来回跳。解决办法是加时间域滤波,比如用指数移动平均(EMA)平滑直方图。
- ROI区域的选择:整张图的直方图不一定代表主体。我习惯把画面分成3×3或5×5的网格,给中心区域更高的权重,或者结合人脸检测框做局部统计。
- HDR场景的直方图“假象”:在HDR模式下,直方图可能看起来“正常”,但实际暗部和亮部细节是通过多帧融合出来的。这时候直方图不能直接反映单帧的曝光质量。
- 低光照下的噪声干扰:暗光环境下,直方图在低亮度区域会有大量噪声像素,导致统计结果失真。我建议先做降噪预处理,或者只统计亮度值大于某个阈值的像素。
避坑指南:我曾经在一个手机项目中,发现夜景模式的直方图总是“偏左”,但画面看起来并不暗。后来排查发现,是因为多帧合成后的直方图统计的是合成前的单帧数据。记住:直方图统计的时机要和最终输出对齐。
3.7 小结
直方图是AE算法的基础,但别把它当成万能药。它告诉你“是什么”,但不告诉你“为什么”。
实际工程中,我通常把直方图作为第一层判断,再结合场景分类、人脸检测、运动检测等信息做综合决策。这样既保证了算法的鲁棒性,又不会让逻辑变得过于复杂。