1. 直方图均衡化概述
各位同学好,我是你们这堂课的主讲。今天咱们来聊聊嵌入式视觉里一个非常基础、但又极其好用的工具——直方图均衡化。
说实话,我刚开始做嵌入式视觉那会儿,总觉得这玩意儿太简单,不就是把图像拉亮一点嘛。直到有一次在户外安防项目里,摄像头拍到的画面灰蒙蒙一片,人脸都看不清,我才真正意识到它的价值。嗯,咱们今天就把它彻底讲透。
什么是直方图?
先说说直方图。你想想看,一张数字图像,说白了就是一个像素矩阵。每个像素都有一个灰度值,从0(纯黑)到255(纯白)。
直方图,就是统计每个灰度值上出现了多少个像素。
举个例子,一张 640×480 的图像,总共有 307200 个像素。直方图会告诉你:灰度值0有多少个像素?灰度值1有多少个?……一直到灰度值255有多少个?
我习惯用下面这段代码来快速查看直方图:
// 计算灰度直方图(C语言,嵌入式常用)
void calc_histogram(uint8_t *img, int width, int height, uint32_t *hist) {
// 先清零
for(int i = 0; i < 256; i++) hist[i] = 0;
// 统计每个灰度值的像素个数
int total = width * height;
for(int i = 0; i < total; i++) {
hist[img[i]]++;
}
}
直方图能告诉我们什么?说白了就两件事:
- 图像的整体亮度分布——是偏暗还是偏亮?
- 对比度高低——像素是集中在某个小范围,还是均匀分布在整个0-255区间?
我在项目中遇到过一张夜间监控图像,它的直方图几乎全部挤在0-50这个低灰度区域。这种图像,你肉眼看上去就是一团黑,啥也看不清。
直方图均衡化的基本原理
好,那直方图均衡化是干嘛的呢?
它的目标很简单:把原始图像的直方图,拉伸成近似均匀分布的状态。
为什么会这样?你想想看,如果像素集中在某个小范围,图像的细节就丢失了。比如暗部区域,原本灰度值10和灰度值12的两个像素,人眼根本分不出来。但如果我们把它们映射到0和255的整个范围,差异就明显了。
均衡化的核心公式其实不复杂:
// 直方图均衡化核心步骤(伪代码)
1. 计算原始直方图 hist[0..255]
2. 计算累积分布函数 CDF:
cdf[0] = hist[0]
for i = 1 to 255:
cdf[i] = cdf[i-1] + hist[i]
3. 归一化映射:
new_pixel = (cdf[old_pixel] - cdf_min) / (total_pixels - cdf_min) * 255
// 其中 cdf_min 是第一个非零的 cdf 值
我建议你记住这个映射关系:均衡化本质上是一个非线性映射,它把像素多的灰度区间拉伸,把像素少的区间压缩。
举个例子,如果原始图像中灰度值100-150这个区间占了80%的像素,均衡化就会把这个区间拉伸到几乎整个0-255范围。而灰度值0-50这个区间如果只有1%的像素,它就会被压缩成很小的一段。
核心要点:直方图均衡化不增加信息量,它只是重新分配灰度级,让对比度看起来更高。说白了,就是把有限的信息用更高效的方式展示出来。
在嵌入式视觉中的应用价值
聊完了原理,咱们说说实际应用。嵌入式视觉和PC端视觉最大的区别是什么?资源受限。
我做过一个车载夜视项目,摄像头是OV7670,主控是STM32F4。这种组合,你跑个复杂的深度学习模型?想都别想。但直方图均衡化,它只需要:
- 一次遍历统计直方图
- 一次遍历计算CDF
- 一次遍历做映射
总共三次遍历,256字节的查找表,就能让图像质量提升一个档次。这在嵌入式平台上简直是性价比之王。
具体应用场景我列几个:
| 应用场景 | 问题描述 | 均衡化效果 |
|---|---|---|
| 车载夜视 | 夜间道路对比度极低 | 显著提升路面和障碍物的可见度 |
| 安防监控 | 逆光或背光场景 | 人脸和背景都能看清 |
| 工业检测 | 光照不均匀的工件 | 缺陷区域更容易被检测 |
| 医疗内窥 | 组织图像细节模糊 | 血管和病灶边界更清晰 |
我的经验:在嵌入式平台上,我建议用局部直方图均衡化代替全局均衡化。全局均衡化有时会过度增强噪声,而局部均衡化(比如分块处理)效果更自然。代价是计算量翻倍,但很多Cortex-M4以上的芯片都能扛得住。
避坑指南:我曾经在一个项目中直接对彩色图像的RGB三个通道分别做均衡化,结果颜色完全失真,图像变得花花绿绿。后来改用YUV色彩空间,只对Y通道(亮度)做均衡化,U和V通道保持不变,效果就对了。记住:彩色图像均衡化,只处理亮度通道!
最后说一句,直方图均衡化不是万能的。如果原始图像本身质量太差(比如过曝到一片白),均衡化也救不回来。它更像是一个预处理步骤,为后续的特征提取、目标检测打好基础。
嗯,这一节就到这里。下一节咱们会深入代码实现,手把手教你如何在STM32上跑通直方图均衡化。