OpenCV 高阶实战:图像直方图与掩码图像深度解析
目录
一、图像直方图:读懂图像的 “像素分布报告”
1. 什么是图像直方图?
2. 图像直方图的核心作用
(1)分析亮度分布
(2)判断对比度高低
(3)辅助图像增强与阈值分割
(4)检测色彩偏移
3、举例
4. OpenCV 直方图计算:cv2.calcHist() 详解
(1)函数语法与参数解析
(2)实战案例:计算灰度图与彩色图直方图
(3)运行结果分析
二、掩码图像(Mask):精准定位感兴趣区域
1. 什么是掩码图像?
2. 掩码的核心应用场景
3. OpenCV 掩码操作:cv2.bitwise_and() 详解
(1)函数语法
(2)实战案例:用掩码提取局部区域并计算直方图
3)运行结果分析
在计算机视觉领域,图像直方图和掩码图像是两种基础且核心的技术。直方图是分析图像像素分布的 “数据眼镜”,掩码则是精准定位感兴趣区域的 “手术刀”。本文将从概念原理出发,结合 OpenCV 实战代码,详细讲解图像直方图的计算与应用、掩码图像的制作与使用,以及直方图均衡化(含自适应均衡化)的实现,帮助大家掌握这两项 OpenCV 高阶技能。
一、图像直方图:读懂图像的 “像素分布报告”
1. 什么是图像直方图?
图像直方图是描述图像像素值分布的统计图形,它将图像的像素值(通常 0-255)作为横轴,像素值出现的频次(或概率)作为纵轴,用柱状图或折线图展示。
- 对灰度图:直方图反映不同灰度级(0-255)的像素数量;
- 对彩色图:可分别展示蓝(B)、绿(G)、红(R)三通道的像素分布。
简单来说,直方图就像图像的 “体检报告”,通过它能快速判断图像的亮度、对比度等关键信息。
2. 图像直方图的核心作用
(1)分析亮度分布
- 若直方图峰值集中在左侧(低像素值):图像整体偏暗;
- 若峰值集中在右侧(高像素值):图像整体偏亮;
- 若峰值分布均匀:图像亮度适中。
(2)判断对比度高低
- 直方图宽度宽(像素值跨度大,从 0 到 255 覆盖完整):对比度高,细节清晰;
- 直方图宽度窄(像素值集中在某一区间):对比度低,图像灰蒙蒙。
(3)辅助图像增强与阈值分割
- 直方图均衡化:通过重新分布像素值,扩大对比度;
- 阈值分割:利用直方图的 “双峰谷底” 确定分割阈值,分离前景与背景(如分割文字与背景)。
(4)检测色彩偏移
对彩色图三通道直方图对比,若某一通道(如红色)峰值偏移明显,说明图像存在色彩偏色。
3、举例
灰度值在0 - 255范围之间总共 256 个值,可以将我们的范围划分为子部分(称为bins),例
4. OpenCV 直方图计算:cv2.calcHist()
详解
(1)函数语法与参数解析
cv2.calcHist(images, channels, mask, histSize, ranges, accumulate=False)
(2)实战案例:计算灰度图与彩色图直方图
import cv2
import matplotlib.pyplot as plt
import numpy as np# 1. 读取灰度图并计算直方图
# 读取灰度图像(以手机图像为例)
gray_img = cv2.imread("phone.png", cv2.IMREAD_GRAYSCALE)
if gray_img is None:print("图像读取失败,请检查文件路径!")exit()# 方法1:用matplotlib直接绘制(需将图像展平为一维数组)
plt.figure(figsize=(12, 5))
plt.subplot(1, 3, 1)
plt.hist(gray_img.ravel(), bins=256, color='gray') # ravel()将二维图像转为一维
plt.title("灰度图直方图(matplotlib)")
plt.xlabel("像素值(0-255)")
plt.ylabel("像素数量")# 方法2:用OpenCV的cv2.calcHist()计算(bins=16,分组更粗)
hist_cv2 = cv2.calcHist([gray_img], [0], None, [16], [0, 256])
plt.subplot(1, 3, 2)
plt.plot(hist_cv2, color='black', marker='o') # 折线图展示
plt.title("灰度图直方图(cv2.calcHist,bins=16)")
plt.xlabel("分组索引(0-15)")
plt.ylabel("像素数量")# 2. 读取彩色图并计算三通道直方图
color_img = cv2.imread("phone.png") # OpenCV默认BGR格式
color = ('b', 'g', 'r') # 对应B/G/R通道plt.subplot(1, 3, 3)
for i, col in enumerate(color):# 分别计算B、G、R通道的直方图hist_channel = cv2.calcHist([color_img], [i], None, [256], [0, 256])plt.plot(hist_channel, color=col, label=f"{col.upper()}通道")plt.title("彩色图三通道直方图")
plt.xlabel("像素值(0-255)")
plt.ylabel("像素数量")
plt.legend()
plt.tight_layout()
plt.show()# 展示原始图像
cv2.imshow("灰度图", gray_img)
cv2.imshow("彩色图(BGR)", color_img)
cv2.waitKey(0)
cv2.destroyAllWindows()
(3)运行结果分析
- 灰度图直方图:可直观看到图像像素集中在哪个区间(如集中在 100-200,说明图像偏亮);
- 彩色图三通道:若 B 通道峰值高于 G/R,说明图像偏蓝;反之则偏红或偏绿;
bins=16
时,直方图更简洁,适合快速观察整体分布趋势。
二、掩码图像(Mask):精准定位感兴趣区域
1. 什么是掩码图像?
掩码图像是与原图像尺寸完全相同的二进制图像,像素值仅为 0
或 255
(无符号 8 位整数,np.uint8
):
- 像素值为
0
:屏蔽区域(后续操作不处理该区域); - 像素值为
255
:保留区域(后续操作仅作用于该区域)。
形象地说,掩码就像 “照片遮罩”,只让感兴趣的区域 “透出来” 参与处理。
2. 掩码的核心应用场景
- 计算局部区域的直方图(如仅分析人脸区域的亮度);
- 图像修复(如仅修复口罩遮挡的面部区域);
- 目标分割(如仅提取图像中的文字区域);
- 图像融合(如将 logo 贴到指定区域)。
3. OpenCV 掩码操作:cv2.bitwise_and()
详解
掩码通常与按位与运算结合使用,原理是:原图像像素 & 掩码像素
,只有当掩码像素为 255(二进制 11111111)时,原像素值才保留;若掩码为 0(二进制 00000000),结果为 0(黑色)。
(1)函数语法
cv2.bitwise_and(src1, src2, dst=None, mask=None)
(2)实战案例:用掩码提取局部区域并计算直方图
import cv2
import numpy as np
import matplotlib.pyplot as plt# 1. 读取灰度图像
gray_img = cv2.imread("phone.png", cv2.IMREAD_GRAYSCALE)
cv2.imshow("原始灰度图", gray_img)
cv2.waitKey(0)# 2. 创建掩码图像(步骤:全0矩阵 → 局部设为255)
# 2.1 生成与原图像尺寸相同的全0矩阵(纯黑图像)
mask = np.zeros(gray_img.shape[:2], dtype=np.uint8) # shape[:2]取高和宽
# 2.2 定义感兴趣区域(ROI):[y1:y2, x1:x2](注意:OpenCV中坐标是(y, x))
# 此处以“手机屏幕区域”为例,需根据实际图像调整坐标
mask[50:350, 100:470] = 255 # 该区域设为255(白色)# 展示掩码图像
cv2.imshow("掩码图像(白色为保留区域)", mask)
cv2.waitKey(0)# 3. 应用掩码:提取感兴趣区域
masked_img = cv2.bitwise_and(gray_img, gray_img, mask=mask)
cv2.imshow("掩码提取后的图像", masked_img)
cv2.waitKey(0)# 4. 对比:全图直方图 vs 掩码区域直方图
plt.figure(figsize=(10, 4))# 全图直方图
plt.subplot(1, 2, 1)
hist_full = cv2.calcHist([gray_img], [0], None, [256], [0, 256])
plt.plot(hist_full, color='black')
plt.title("全图直方图")
plt.xlabel("像素值")
plt.ylabel("数量")# 掩码区域直方图
plt.subplot(1, 2, 2)
hist_masked = cv2.calcHist([gray_img], [0], mask, [256], [0, 256])
plt.plot(hist_masked, color='red')
plt.title("掩码区域(手机屏幕)直方图")
plt.xlabel("像素值")
plt.ylabel("数量")plt.tight_layout()
plt.show()cv2.destroyAllWindows()
运行结果如下:
3)运行结果分析
- 掩码图像:黑色区域为屏蔽区,白色矩形为保留的 “手机屏幕区域”;
- 掩码提取后的图像:仅屏幕区域保留原像素,其他区域为黑色;
- 直方图对比:若屏幕区域偏亮,掩码直方图峰值会比全图直方图更靠右,精准反映局部亮度分布。