引言
图像直方图是图像处理中分析像素分布的核心工具。它通过统计每个灰度级别(或颜色通道)的像素数量,直观地反映了图像的亮度分布、对比度、动态范围等关键信息。无论是图像增强、分割、特征提取还是质量评估,直方图都扮演着不可或缺的角色。
OpenCV 提供了强大的直方图计算函数 cv2.calcHist,支持灰度图、彩色图以及带掩膜的局部统计。同时,OpenCV 还集成了直方图均衡化(cv2.equalizeHist)和自适应直方图均衡化(CLAHE)等增强技术,帮助我们改善图像质量。
本文将通过三个完整的代码片段(整合自实际项目笔记),详细介绍:
-
灰度图和彩色图的直方图计算与可视化
-
掩膜(mask)的原理与使用技巧
-
直方图均衡化及其局限性
-
自适应直方图均衡化(CLAHE)的实现与优势
环境与依赖
-
Python 3.x
-
OpenCV(
cv2) -
Matplotlib(
plt) -
NumPy(
np)
任务描述
我们将使用两张示例图片(读者可自行准备):
-
phone.png:一张包含手机主体的彩色图片(可用于演示掩膜和彩色直方图
-
black.jpg:一张整体偏暗的图片(用于展示均衡化效果)
通过以下步骤,逐步展示直方图的各种操作:
-
绘制灰度图的两种直方图(
plt.hist柱状图 和cv2.calcHist曲线图) -
绘制彩色图的三通道直方图
-
创建掩膜并应用于图像,统计感兴趣区域的直方图
-
对暗图像进行普通直方图均衡化,观察对比度提升效果
-
使用 CLAHE 进行自适应均衡化,并与普通均衡化对比
最终,我们将通过图像窗口直观地看到每一步的结果。
实现步骤详解
1. 灰度图直方图(Matplotlib hist)
首先,读取一张灰度图像,并用 matplotlib.pyplot.hist 直接绘制直方图。这种方法简单快捷,适合快速预览。
import cv2
import matplotlib.pyplot as plt
import numpy as np
# 读取灰度图
phone = cv2.imread('phone.png', cv2.IMREAD_GRAYSCALE)
# 将图像转换为一维数组(ravel() 展平)
a = phone.ravel()
# 绘制直方图,bins=256 表示每个灰度级一个柱子
plt.hist(a, bins=256)
plt.title('Grayscale Histogram (plt.hist)')
plt.show()
说明:
-
cv2.IMREAD_GRAYSCALE确保以灰度模式读取。 -
ravel()将二维图像数组展平为一维,便于hist统计。 -
bins=256对应灰度级 0~255 每个值单独一个区间。
结果展示:

2. 灰度图直方图(cv2.calcHist 曲线)
OpenCV 的 cv2.calcHist 函数返回一个数组,我们可以用 plt.plot 将其绘制成曲线,更适合后续的数据处理。
# 使用 calcHist 计算直方图,参数:[图像], [通道], 掩膜, [区间数], 范围
phone_hist = cv2.calcHist([phone], [0], None, [16], [0, 256])
# 绘制曲线
plt.plot(phone_hist)
plt.title('Grayscale Histogram (cv2.calcHist, bins=16)')
plt.show()
参数详解:
-
[phone]:输入图像,需要用方括号括起来。 -
[0]:通道索引,灰度图只有一个通道,所以是[0]。 -
None:不使用掩膜,统计全图。 -
[16]:BINS 的数目,这里设为 16,表示将 0~255 的灰度范围分成 16 个区间,每个区间统计像素总和。 -
[0, 256]:像素值范围,灰度图通常为 0~255(注意上限是开区间)。
结果展示:

3. 彩色图三通道直方图
对于彩色图像(BGR 格式),我们可以分别计算每个通道的直方图,并用对应颜色绘制在同一张图上。
img = cv2.imread('phone.png') # 默认BGR格式
color = ('b', 'g', 'r') # 通道颜色顺序
for i, col in enumerate(color):
histr = cv2.calcHist([img], [i], None, [256], [0, 256])
plt.plot(histr, color=col)
plt.title('Color Image Histogram (BGR)')
plt.show()
注意:OpenCV 读取彩色图像是 BGR 顺序,因此通道索引 0 对应蓝色,1 对应绿色,2 对应红色。绘制时指定对应颜色,使曲线与实际通道颜色一致。
结果展示:

4. 掩膜(mask)的使用
掩膜是一张二值图像,用于指定直方图统计的区域:只有掩膜中像素值为 255 的区域才会被计入直方图。这在需要分析图像特定部分时非常有用。
4.1 生成掩膜并观察效果
# 读取灰度图
phone = cv2.imread('phone.png', cv2.IMREAD_GRAYSCALE)
cv2.imshow('phone', phone)
# 创建掩膜,大小与图像相同,初始全0
mask = np.zeros(phone.shape[:2], np.uint8)
# 设置感兴趣区域(矩形区域)为白色(255)
mask[50:350, 100:470] = 255
cv2.imshow('mask', mask)
# 应用掩膜:使用 bitwise_and 保留掩膜区域
phone_mask = cv2.bitwise_and(phone, phone, mask=mask)
cv2.imshow('phone_mask', phone_mask)
cv2.waitKey(0)
cv2.destroyAllWindows()
cv2.bitwise_and 的作用 :对两幅图像(或图像与自身)逐像素进行"与"操作,当提供 mask 参数时,只有在掩膜对应位置非零的像素才会被计算(结果图像中保留原像素值),掩膜为零的位置输出 0(黑色)。这里我们将原图与自身进行"与"操作,并传入掩膜,结果只显示掩膜区域的图像内容。
结果展示:

4.2 计算带掩膜的直方图
将掩膜传递给 calcHist 的 mask 参数,即可统计指定区域的直方图。
phone_hist_mask = cv2.calcHist([phone], [0], mask, [256], [0, 256])
plt.plot(phone_hist_mask)
plt.title('Histogram of Masked Region')
plt.show()
运行后,曲线只反映矩形区域内的像素分布。

5. 直方图均衡化(Equalization)
直方图均衡化是一种自动调整图像对比度的方法,通过将原始直方图拉伸到更宽的灰度范围,使图像细节更加清晰。
5.1 普通均衡化
使用一张整体偏暗的图像 black.jpg 进行演示。
black = cv2.imread('black.jpg', cv2.IMREAD_GRAYSCALE)
# 原始直方图
plt.hist(black.ravel(), bins=256)
plt.title('Original Histogram (Dark Image)')
plt.show()
# 直方图均衡化
black_equalize = cv2.equalizeHist(black)
# 均衡化后的直方图
plt.hist(black_equalize.ravel(), bins=256)
plt.title('Equalized Histogram')
plt.show()
# 并排显示原图和均衡化后的图像
res = np.hstack((black, black_equalize))
cv2.imshow('Original vs Equalized', res)
cv2.waitKey(0)
cv2.destroyAllWindows()
cv2.equalizeHist 只能处理单通道灰度图。均衡化后,原本集中在低灰度区域的像素被拉伸到整个灰度范围,图像对比度显著提升。但这种方法可能放大噪声,且对整幅图像统一处理,有时效果不自然。
结果展示:



5.2 自适应直方图均衡化(CLAHE)
CLAHE(Contrast Limited Adaptive Histogram Equalization)将图像分成多个小块,在每个小块内独立进行均衡化,并通过对比度限制避免噪声放大,最后用插值消除块间边界。
# 创建CLAHE对象
clahe = cv2.createCLAHE(clipLimit=10, tileGridSize=(8, 8))
black_clahe = clahe.apply(black)
# 三图并排:原图、普通均衡化、CLAHE
res = np.hstack((black, black_equalize, black_clahe))
cv2.imshow('Comparison: Original vs Equalized vs CLAHE', res)
cv2.waitKey(0)
cv2.destroyAllWindows()
参数说明:
-
clipLimit:对比度限制阈值,超过该值的部分会被裁剪并重新分布,防止噪声放大。默认值为 8,这里设为 10 以增强效果。 -
tileGridSize:图像分块的大小,每个小块内独立进行均衡化。默认 (8,8),可根据图像尺寸调整。
CLAHE 在增强对比度的同时,能更好地保留局部细节,避免整幅图像过亮或过暗。
结果展示:

完整代码
将上述所有步骤整合,得到完整的演示脚本。注意:读者需要根据实际图片路径修改文件名,或准备好 phone.png 和 black.jpg。
import cv2
import matplotlib.pyplot as plt
import numpy as np
# ---------------------- 1. 灰度图直方图(plt.hist) ----------------------
phone = cv2.imread('phone.png', cv2.IMREAD_GRAYSCALE)
plt.hist(phone.ravel(), bins=256)
plt.title('Grayscale Histogram (plt.hist)')
plt.show()
# ---------------------- 2. 灰度图直方图(cv2.calcHist 曲线) ----------------------
phone_hist = cv2.calcHist([phone], [0], None, [16], [0, 256])
plt.plot(phone_hist)
plt.title('Grayscale Histogram (cv2.calcHist, bins=16)')
plt.show()
# ---------------------- 3. 彩色图三通道直方图 ----------------------
img = cv2.imread('phone.png')
color = ('b', 'g', 'r')
for i, col in enumerate(color):
histr = cv2.calcHist([img], [i], None, [256], [0, 256])
plt.plot(histr, color=col)
plt.title('Color Image Histogram (BGR)')
plt.show()
# ---------------------- 4. 掩膜(mask)的使用 ----------------------
phone = cv2.imread('phone.png', cv2.IMREAD_GRAYSCALE)
cv2.imshow('phone', phone)
mask = np.zeros(phone.shape[:2], np.uint8)
mask[50:350, 100:470] = 255
cv2.imshow('mask', mask)
phone_mask = cv2.bitwise_and(phone, phone, mask=mask)
cv2.imshow('phone_mask', phone_mask)
phone_hist_mask = cv2.calcHist([phone], [0], mask, [256], [0, 256])
plt.plot(phone_hist_mask)
plt.title('Histogram of Masked Region')
plt.show()
cv2.waitKey(0)
cv2.destroyAllWindows()
# ---------------------- 5. 直方图均衡化 ----------------------
black = cv2.imread('black.jpg', cv2.IMREAD_GRAYSCALE)
plt.hist(black.ravel(), bins=256)
plt.title('Original Histogram (Dark)')
plt.show()
black_equalize = cv2.equalizeHist(black)
plt.hist(black_equalize.ravel(), bins=256)
plt.title('Equalized Histogram')
plt.show()
# 并排显示原图与均衡化结果
res = np.hstack((black, black_equalize))
cv2.imshow('Original vs Equalized', res)
cv2.waitKey(0)
# ---------------------- 6. CLAHE自适应均衡化 ----------------------
clahe = cv2.createCLAHE(clipLimit=10, tileGridSize=(8,8))
black_clahe = clahe.apply(black)
res = np.hstack((black, black_equalize, black_clahe))
cv2.imshow('Comparison: Original vs Equalized vs CLAHE', res)
cv2.waitKey(0)
cv2.destroyAllWindows()
关键点解析
1. cv2.calcHist 参数详解
-
images:输入图像列表,即使一张图也要用方括号括起来,如[img]。 -
channels:要统计的通道索引列表,灰度图为[0],彩色图可指定[0]、[1]、[2]分别对应 B、G、R。 -
mask:掩膜图像,与输入图像同尺寸的 8 位单通道数组,非零像素位置参与统计。若为None则统计全图。 -
histSize:BINS 的数量列表,例如[256]表示 256 个灰度级区间。如果设为[16],则每 16 个灰度级合并为一个区间,每个区间的值是该区间内所有像素的总和。这在需要降低直方图维度时非常有用。 -
ranges:像素值范围,通常为[0, 256],注意上限是开区间(包含 0,不包含 256)。
2. 掩膜(mask)的工作机制
掩膜本质是一个二值图像,白色(255)表示感兴趣区域,黑色(0)表示忽略区域。在 cv2.bitwise_and 中,mask 参数起到"与"操作的控制作用:只有当掩膜对应位置为 255 时,原图该位置的像素才会被保留到输出结果。在 calcHist 中,mask 参数直接指定哪些像素参与统计。
常见应用:提取图像的特定区域(如 ROI)进行单独分析,避免背景干扰。
3. 直方图均衡化原理
直方图均衡化通过累积分布函数(CDF)将原始灰度分布映射到近似均匀分布。公式为:
sk=L−1MN∑j=0knjsk=MNL−1j=0∑knj
其中 LL 是灰度级数(通常 256),MNMN 是像素总数,njnj 是灰度级 jj 的像素数。映射后的像素值更均匀分布,从而增强对比度。
4. CLAHE 的优势
-
局部自适应:将图像划分为小块(tile),每个块独立均衡化,适应局部亮度变化。
-
对比度限制 :通过
clipLimit参数限制直方图的高度,避免噪声过度放大。 -
边界平滑:使用双线性插值消除块间边界,保证图像整体自然。
参数调优建议:
-
clipLimit值越大,对比度增强越强,但可能引入噪声。通常取 2~5 之间效果较好。 -
tileGridSize决定局部区域的尺寸,太小可能丢失整体信息,太大则趋近于全局均衡化。
5. 关于 BINS 的理解
BINS 是直方图的分组数量。例如,当 histSize=[16] 时,意味着将 0~255 的灰度范围划分为 16 个连续的区间(每个区间宽度为 16),直方图的每个值代表该区间内所有像素的总数。这有助于减少数据量,提取更宏观的分布特征。
运行结果与讨论
运行完整代码后,你将依次看到:
-
灰度直方图(柱状图):直观显示每个灰度级的像素数量。
-
灰度直方图(曲线,bins=16):显示 16 个区间的像素总和,分布趋势更平滑。
-
彩色三通道直方图:三条不同颜色的曲线叠加,反映各通道的亮度分布。
-
掩膜操作:
-
原始灰度图窗口
-
掩膜窗口(白色矩形区域)
-
掩膜后的图像(只显示矩形区域内容)
-
掩膜区域的直方图曲线
-
-
均衡化效果:
-
原始暗图像的直方图集中在低灰度区
-
均衡化后的直方图分布更宽
-
原图与均衡化结果并排显示,图像明显变亮,细节更清晰
-
-
CLAHE 对比:
- 三图并排(原图、普通均衡化、CLAHE),可以看到 CLAHE 在增强对比度的同时保留了更多自然感,且噪声没有明显放大。
总结
本文通过详细的代码示例,系统地介绍了 OpenCV 中图像直方图的相关操作:
-
使用
cv2.calcHist和matplotlib绘制灰度图、彩色图的直方图 -
掩膜的定义、生成及应用(
bitwise_and和calcHist) -
直方图均衡化(
equalizeHist)与自适应均衡化(createCLAHE)
掌握这些内容后,你可以灵活地分析图像的亮度分布,并通过直方图调整图像对比度、提取感兴趣区域的统计信息,为后续的图像分割、特征提取等任务打下基础。
希望这篇文章对你的学习有所帮助!如有疑问或建议,欢迎在评论区留言交流。
注意:运行代码前请确保相关图片文件存在于当前目录,或修改代码中的图片路径。
参考资料
-
OpenCV官方文档:https://docs.opencv.org/
-
NumPy 和 Matplotlib 官方文档