2、图像直方图基础:灰度直方图的定义、直方图的计算方法、直方图的数学表示
好,我们正式开始讲直方图。
说实话,直方图这个概念,在嵌入式视觉里太基础了。基础到什么程度?我面试新人时,第一个问题往往就是:「你给我说说,什么是灰度直方图?」
别小看这个问题。能讲清楚的人,通常对图像理解不会差。
2.1 灰度直方图的定义
灰度直方图,说白了就是一张统计图。
它统计的是:图像中每个灰度级出现了多少次。
举个例子。一张8位深度的灰度图,灰度范围是0到255。直方图就是告诉你:
- 灰度值为0的像素有多少个
- 灰度值为1的像素有多少个
- ……一直到灰度值为255的像素有多少个
嗯,就这么简单。
但简单归简单,它的意义非常大。我在项目中调试摄像头时,第一件事就是看直方图。为什么?因为直方图能直观反映图像的亮度分布、对比度、曝光是否正常。
核心理解:直方图是图像全局信息的「快照」。它不关心像素的位置,只关心像素的「值」出现了多少次。
2.2 直方图的计算方法
计算直方图,其实就是一个计数过程。
你想想看,如果让你手动算一张小图的直方图,你会怎么做?
遍历每个像素,看它的灰度值是多少,然后在对应的计数器上加1。对吧?
计算机也是这么干的。只不过它比你快得多。
我习惯用C语言来实现这个逻辑,因为嵌入式环境里,C是最常见的。下面是一个标准的直方图计算代码:
// 假设图像是8位灰度图,宽width,高height
// hist数组大小为256,初始化为0
void calc_histogram(unsigned char *img, int width, int height, unsigned int *hist) {
int i;
int total_pixels = width * height;
// 清零
for (i = 0; i < 256; i++) {
hist[i] = 0;
}
// 遍历每个像素,统计灰度值出现次数
for (i = 0; i < total_pixels; i++) {
hist[ img[i] ]++;
}
}
你看,核心就两件事:清零,然后遍历计数。
这里有个细节我要提醒你。我在做嵌入式优化时,发现内存访问模式会影响速度。上面的代码是按顺序访问内存的,缓存友好,效率高。如果你改成随机访问,性能会差很多。
小技巧:在嵌入式平台上,如果图像很大,可以考虑用uint32_t来存hist数组。因为一张1080p的图像有约200万像素,用unsigned int(通常是32位)不会溢出。但如果你用unsigned short(16位),最大只能计65535,会溢出。
2.3 直方图的数学表示
数学上,直方图可以写成这样:
h(r_k) = n_k
其中:
r_k表示第k个灰度级(比如0, 1, 2, ..., 255)n_k表示灰度级为r_k的像素个数
有时候我们也会用归一化直方图,也就是把频数转换成概率:
p(r_k) = n_k / N
这里 N 是图像的总像素数。归一化后,所有 p(r_k) 加起来等于1。
为什么要归一化?
因为不同尺寸的图像,直方图的数值范围不一样。归一化后,你就可以比较不同图像的直方图了。我在做图像质量评估时,经常用归一化直方图来对比两张图的亮度分布是否一致。
另外,直方图还可以用累积分布函数(CDF)来表示:
cdf(r_k) = sum_{i=0}^{k} p(r_i)
这个CDF在后面的直方图均衡化中非常关键。嗯,这里先埋个伏笔。
2.4 直方图的直观理解
光讲数学可能有点枯燥。我来说说怎么看直方图。
假设你拿到一张图的直方图:
- 直方图集中在左侧(低灰度值):图像偏暗,可能曝光不足
- 直方图集中在右侧(高灰度值):图像偏亮,可能过曝
- 直方图集中在中间,两边很少:图像对比度低,看起来灰蒙蒙的
- 直方图分布均匀,覆盖整个范围:图像对比度高,细节丰富
我曾经在调试一款安防摄像头时,发现夜间图像总是偏暗。一开始以为是传感器问题,后来一看直方图,发现像素全挤在0-30之间。嗯,问题找到了——是增益没调好。调整之后,直方图分布到了0-150,图像明显亮起来了。
注意:直方图只能反映灰度值的分布,不能反映像素的空间位置。两张完全不同的图像,可能拥有完全相同的直方图。所以直方图是「全局特征」,不是「局部特征」。
2.5 直方图在嵌入式视觉中的意义
在嵌入式平台上,直方图计算非常快。为什么?
因为它的计算量很小。一张VGA图像(640x480),总共30万个像素。遍历一遍,做30万次加法,对现在的MCU来说,几毫秒就搞定了。
而且直方图计算可以硬件加速。很多嵌入式视觉芯片(比如TI的DM系列、NXP的i.MX系列)都内置了直方图统计的硬件模块。你只需要配置一下寄存器,硬件自动算好,你直接读结果就行。
我个人建议:在嵌入式项目中,能用硬件直方图就用硬件。省CPU,省功耗,还快。
好,这一节就到这里。直方图是基础中的基础,但也是后续所有直方图相关算法的基石。下一节我们会讲直方图均衡化,到时候你会看到,今天学的这些数学表示,全都会用上。
本节要点回顾:
- 灰度直方图统计每个灰度级出现的像素个数
- 计算方法是遍历像素,累加计数
- 数学表示:h(r_k) = n_k,归一化后为 p(r_k) = n_k / N
- 累积分布函数CDF是后续均衡化的关键
- 直方图反映全局亮度分布,不包含空间信息