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上跑通直方图均衡化。