揭开图像的秘密:OpenCV直方图入门详解
揭开图像的秘密:OpenCV直方图入门详解
大家好!在你学习计算机视觉的旅途中,你一定会遇到一个既基础又极其强大的工具–直方图。初看这个词可能有点抽象,但别担心,它其实非常直观。简单来说,图像直方图就是一个“像素人口普查”图。它统计了一张图片中每个亮度级别的像素有多少个。通过这张图,我们可以一眼看出照片是偏暗、偏亮,还是对比度适中。(图片来源于网络,如有侵权联系我删除)
一、什么是直方图?
想象一下,我们有一张8-bit的灰度图。这意味着每个像素的亮度值都在0(纯黑)到255(纯白)之间。
直方图的X轴代表像素的亮度值(0-255),而Y轴代表具有该亮度值的像素数量(频率)。
图像类型 | 对应的直方图特点 |
---|---|
偏暗的图像 | 直方图的大部分数据会集中在X轴的左侧(低亮度值区域) |
偏亮的图像 | 直方图的大部分数据会集中在X轴的右侧(高亮度值区域) |
低对比度图像 | 直方图的数据会记载一个很窄的区域里,说明像素的亮度变化不大 |
高对比度图像 | 直方图的数据会分布在X轴的大部分区域,说明图像同时包含了很暗和很亮的像素 |
所以,直方图就是图像的“指纹”,它揭示了图像的亮度分布和对比度信息,是进行图像分析、分割和增强的重要依据。
二、OpenCV实战:计算并绘制直方图
理论看完了,让我们立刻动手实践!在OpenCV中,我们主要使用cv2.calcHist()
函数来计算直方图。
函数原型:cv2.calcHist()
hist = cv2.calcHist(images, channels, mask, histSize, ranges)
image
:源图像,必须用方括号括起来,例如[image]
channels
: 指定要计算哪个通道的直方图。同样要用方括号。
- 对于灰度图,它是 [0]。
- 对于 BGR 彩色图,[0] 是蓝色通道,[1] 是绿色通道,[2] 是红色通道。
mask
: 掩码图像。如果你想计算整张图的直方图,设为 None。如果你只想分析图中某个特定区域,可以传入一个掩码图像。
histSize
: 也叫 BINS 的数量。这决定了直方图 X 轴的“柱子”数量。对于 8-bit 图像,通常设为 [256]
,表示我们关心 0 到 255 每一个亮度级别的像素数。
ranges
: 像素值的范围。对于 8-bit 图像,范围是 0 到 255,所以我们传入 [0, 256]
(包含 0,不包含 256)。
1.计算灰度图的直方图
计算很简单,但OpenCV本身不能直接画图,所以我们通常借助matplotlib
库来可视化
import cv2
import numpy as np
from matplotlib import pyplot as plt# 1. 加载一张图片并转为灰度图
img = cv2.imread('your_image.jpg') # 替换成你的图片路径
gray_img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)# 2. 使用 cv2.calcHist() 计算直方图
# 参数: [源图像], [通道], 无掩码, [BINS数量], [像素值范围]
hist = cv2.calcHist([gray_img], [0], None, [256], [0, 256])# 3. 使用 Matplotlib 显示直方图
plt.figure()
plt.title("Grayscale Histogram")
plt.xlabel("Bins (Pixel Value)")
plt.ylabel("# of Pixels (Frequency)")
plt.plot(hist)
plt.xlim([0, 256]) # 设置X轴范围
plt.show()# 同时显示原始灰度图
cv2.imshow("Grayscale Image", gray_img)
cv2.waitKey(0)
cv2.destroyAllWindows()
运行代码,你会得到一张灰度图和它对应的直方图。试着用一张偏亮的图片和一张偏暗的图片,看看它们的直方图有什么不同!


2.计算彩色图的直方图
彩色图的原理完全一样,我们只需要为 B, G, R 三个通道分别计算一次,然后画在同一张图上即可。
import cv2
import numpy as np
from matplotlib import pyplot as plt# 1. 加载彩色图片
img = cv2.imread('your_image.jpg') # 替换成你的图片路径# 2. 遍历三个通道 (Blue, Green, Red)
colors = ('b', 'g', 'r')
plt.figure()
plt.title("Color Histogram")
plt.xlabel("Bins")
plt.ylabel("# of Pixels")for i, col in enumerate(colors):# 计算当前通道的直方图hist = cv2.calcHist([img], [i], None, [256], [0, 256])# 绘制直方图plt.plot(hist, color = col)plt.xlim([0, 256])plt.show()cv2.imshow("Color Image", img)
cv2.waitKey(0)
cv2.destroyAllWindows()
这段代码会生成一张包含蓝、绿、红三条线的直方图,让你能清晰地看到每个颜色通道的分布情况。
三、 直方图的应用:直方图均衡化
这是直方图最经典、最直观的应用之一。
问题:当一张图片的像素值集中在某个狭窄的范围内时(例如,整体偏暗),图像的细节就会丢失。
解决思路:直方图均衡化 。它的核心思想就是“拉伸”这个狭窄的直方图,让它均匀地分布在整个 0-255 的范围内。这样一来,图像的对比度就被大大增强了。
在 OpenCV 中,实现这个功能只需要一行代码:cv2.equalizeHist()
。
注意:cv2.equalizeHist()
只能处理单通道的灰度图像。
import cv2
import numpy as np
from matplotlib import pyplot as plt# 加载一张对比度较低的灰度图
img = cv2.imread('low_contrast_image.jpg', 0) # 替换成你的低对比度图片, 0表示以灰度模式加载# 1. 进行直方图均衡化
equ_img = cv2.equalizeHist(img)# 2. 并排显示原始图和均衡化后的图
res = np.hstack((img, equ_img)) # 水平拼接图像
cv2.imshow("Original vs Equalized", res)# 3. 显示原始直方图和均衡化后的直方图
plt.figure(figsize=(10, 5))# 原始图直方图
plt.subplot(1, 2, 1) # 1行2列的第1个
plt.title("Original Histogram")
plt.hist(img.ravel(), 256, [0, 256])# 均衡化后直方图
plt.subplot(1, 2, 2) # 1行2列的第2个
plt.title("Equalized Histogram")
plt.hist(equ_img.ravel(), 256, [0, 256])plt.show()cv2.waitKey(0)
cv2.destroyAllWindows()
运行结果会非常惊人:
你可以清楚地看到,原始图像(左)的直方图挤在左侧,而经过均衡化处理后,图像(右)的细节变得清晰可见,其直方图也变得更加“宽广”和“平坦”。
进阶提示:全局的直方图均衡化可能会过度增强图像中的噪声。为了解决这个问题,OpenCV 还提供了一种更高级的方法,叫做 对比度受限的自适应直方图均衡化 (CLAHE) 。你可以通过 cv2.createCLAHE()
来使用它,效果通常会更好。
总结
直方图是计算机视觉中许多高级技术的基础,例如基于颜色的对象跟踪、图像分割和图像检索。理解它,就是为你未来的学习打下了坚实的基础。
现在,去寻找一些不同的图片,亲手实践一下吧!看看你能从它们的“像素人口普查”中发现什么秘密!