4. 直方图均衡化实战
理论讲完了,咱们得动真格的了。这一章我带你亲手写代码,用 OpenCV 把灰度图的直方图均衡化跑一遍。你想想看,前面那些数学公式再漂亮,不落地到实际图像上,终究是纸上谈兵。
我个人习惯,学任何图像处理算法,第一步永远是「拿一张图试试看」。别怕出错,跑通了再回头理解细节,效率反而更高。
4.1 环境准备与核心函数
OpenCV 里实现直方图均衡化,其实就一个函数的事——cv2.equalizeHist()。但别小看它,背后就是我们上一章讲的累积分布函数映射。
核心函数签名:
dst = cv2.equalizeHist(src)
参数说明:
src:输入图像,必须是 8 位单通道灰度图(uint8)dst:输出图像,与 src 同尺寸、同类型
注意:这个函数只接受灰度图。如果你传了彩色图进去,OpenCV 会直接报错,不会帮你自动转换。我曾经在项目里就犯过这个低级错误,排查了半天才发现是通道数不对。
4.2 完整代码实战
下面是一段完整的 Python 脚本,我加了详细的注释。你直接复制就能跑,建议用一张偏暗或偏亮的图片做测试,效果会更明显。
import cv2
import numpy as np
import matplotlib.pyplot as plt
# 1. 读取图像(灰度模式)
img = cv2.imread('dark_image.jpg', cv2.IMREAD_GRAYSCALE)
if img is None:
print("图片读取失败,请检查路径")
exit()
# 2. 直方图均衡化
equ = cv2.equalizeHist(img)
# 3. 计算均衡前后的直方图
hist_original = cv2.calcHist([img], [0], None, [256], [0, 256])
hist_equalized = cv2.calcHist([equ], [0], None, [256], [0, 256])
# 4. 可视化对比
plt.figure(figsize=(12, 8))
# 原图
plt.subplot(2, 2, 1)
plt.imshow(img, cmap='gray')
plt.title('Original Image')
plt.axis('off')
# 均衡后
plt.subplot(2, 2, 2)
plt.imshow(equ, cmap='gray')
plt.title('Equalized Image')
plt.axis('off')
# 原图直方图
plt.subplot(2, 2, 3)
plt.plot(hist_original, color='black')
plt.title('Original Histogram')
plt.xlim([0, 256])
# 均衡后直方图
plt.subplot(2, 2, 4)
plt.plot(hist_equalized, color='black')
plt.title('Equalized Histogram')
plt.xlim([0, 256])
plt.tight_layout()
plt.show()
小技巧:如果你没有合适的测试图,可以用 np.random.randint() 生成一张模拟的低对比度图像,或者用 cv2.normalize() 把一张正常图压窄灰度范围,再去做均衡化,对比会更直观。
4.3 效果对比与分析
跑完上面的代码,你会看到四张图。我挑一张典型的低对比度医学 X 光片来说说效果:
| 对比项 | 原图 | 均衡化后 |
|---|---|---|
| 整体亮度 | 偏暗或偏亮,集中在窄区间 | 亮度分布均匀,视觉舒适 |
| 细节可见性 | 暗部细节丢失,亮部过曝 | 暗部细节明显提升,亮部不过曝 |
| 直方图形状 | 集中在某个灰度峰附近 | 近似均匀分布,铺满整个灰度范围 |
| 对比度 | 低,灰度级利用率低 | 高,灰度级利用率接近 100% |
说白了,均衡化就是把原来挤在一起的灰度值「拉开」,让它们均匀地占满 0-255 的整个区间。你想想看,原本只有 50 个灰度级在用,现在 256 个都用上了,对比度自然就上去了。
4.4 直方图均衡化核心流程
为了让你更直观地理解整个过程,我画了一张流程图。从输入图像到最终输出,每一步都对应着我们前面讲的理论。
4.5 避坑指南与个人经验
代码虽然简单,但实际用起来有几个坑。我一个个说:
- 输入类型必须对:
equalizeHist()只接受uint8单通道。如果你读进来是uint16或浮点型,得先转格式。我曾经在医学图像上吃过这个亏,DICOM 图像通常是 16 位的,直接传进去就报错。 - 彩色图怎么办?不能直接对 RGB 三通道做均衡化,那样会破坏色彩平衡。正确做法是转到 YUV 或 Lab 色彩空间,只对亮度通道做均衡化。这个我们后面章节会细讲。
- 噪声会被放大:均衡化本质是拉伸对比度,如果原图有噪声,噪声也会被一起放大。我建议在均衡化之前先做一次轻度去噪,比如高斯模糊或中值滤波。
- 不要过度均衡化:有些图像本身对比度已经很高了,再均衡化反而会失真。你可以先看直方图分布,如果已经铺满了 0-255,就没必要再做。
我的调试习惯:每次跑完均衡化,我一定把原图和结果图的直方图并排打印出来。光看图像有时候会骗人,但直方图不会。如果均衡后的直方图不是「近似平坦」的,说明要么输入有问题,要么参数没调对。
4.6 小结
这一章我们亲手用 OpenCV 实现了灰度图的直方图均衡化。核心就一句话:cv2.equalizeHist() 帮你完成了从直方图统计到 CDF 映射的全部数学过程。但作为工程师,你不能只会调 API,得理解它背后做了什么,以及什么时候该用、什么时候不该用。