OpenCV 图像处理实战:从图像金字塔到直方图分析
OpenCV 图像处理实战:从图像金字塔到直方图分析
在计算机视觉领域,OpenCV 是一款功能强大的开源库,提供了丰富的图像处理函数。本文将围绕一段 OpenCV 实战代码,详细讲解图像金字塔(上采样与下采样)、图像直方图计算以及掩码操作的实现过程与原理,帮助大家快速掌握这些核心图像处理技术。
一、准备工作:环境与图像
在开始之前,需确保已安装所需的 Python 库:OpenCV(cv2)、Matplotlib(绘图)和 NumPy(数值计算)。可通过以下命令安装:
pip install opencv-python matplotlib numpy
代码中用到了 3 张测试图像:mywife.jpg(用于图像金字塔操作)、love.jpg(用于直方图分析)、phone.png(用于掩码与直方图结合操作)。建议将图像与代码放在同一目录下,或在cv2.imread()中填写完整图像路径。
二、核心技术 1:图像金字塔(上采样与下采样)
图像金字塔是一种多尺度表示方法,通过 “下采样”(缩小图像)和 “上采样”(放大图像)构建不同分辨率的图像序列。但需注意:下采样会丢失图像信息,再对下采样图像上采样无法完全复原原图。
1.1 下采样(pyrDown):缩小图像
下采样通过cv2.pyrDown()实现,核心步骤是:先对图像进行高斯滤波(去除高频噪声),再删除图像的偶数行和偶数列,最终图像尺寸变为原图像的 1/2(宽和高均减半)。
代码解析:
# 读取灰度图像(cv2.IMREAD_GRAYSCALE表示灰度模式)face = cv2.imread('mywife.jpg', cv2.IMREAD_GRAYSCALE)cv2.imshow('face', face) # 显示原图cv2.waitKey(0) # 等待按键(0表示任意键关闭窗口)# 第一次下采样:尺寸变为原图1/2face_down_1 = cv2.pyrDown(face)cv2.imshow('down_1', face_down_1)cv2.waitKey(0)# 第二次下采样:尺寸变为down_1的1/2(原图的1/4)face_down_2 = cv2.pyrDown(face_down_1)cv2.imshow('down_2', face_down_2)cv2.waitKey(0)
运行效果:连续按任意键,会依次显示 “原图→1/2 尺寸图→1/4 尺寸图”,图像逐渐变小,但视觉上仍保持基本轮廓(因高斯滤波平滑了边缘)。
1.2 上采样(pyrUp):放大图像
上采样通过cv2.pyrUp()实现,核心步骤是:先将图像的宽和高各扩大 2 倍(新增行 / 列填充 0),再对图像进行高斯滤波(平滑填充的空白区域)。但由于下采样已丢失信息,上采样无法恢复原图细节。
代码解析:
# 对原图直接上采样:尺寸变为原图2倍face_up_1 = cv2.pyrUp(face)cv2.imshow('up_1', face_up_1)cv2.waitKey(0)# 第二次上采样:尺寸变为up_1的2倍(原图的4倍)face_up_2 = cv2.pyrUp(face_up_1)cv2.imshow('up_2', face_up_2)cv2.waitKey(0)# 关键对比:对下采样图像上采样(无法复原)face_down_1_up = cv2.pyrUp(face_down_1) # down_1(1/2)→ up后(原图尺寸)face_down_2_up = cv2.pyrUp(face_down_2) # down_2(1/4)→ up后(1/2尺寸)cv2.imshow('down_1_up', face_down_1_up) # 与原图对比,明显模糊cv2.imshow('down_2_up', face_down_2_up)cv2.waitKey(0)
核心结论:face_down_1_up的尺寸与原图相同,但因下采样时丢失了高频细节(如边缘、纹理),上采样后图像会模糊,无法完全复原原图。
1.3 拉普拉斯金字塔:提取图像残差
为了保留下采样丢失的信息,可通过 “拉普拉斯金字塔” 计算 “残差图像”(原图与 “下采样 + 上采样” 图像的差值)。残差图像包含了下采样丢失的高频细节,可用于图像复原或融合。
代码解析:
# 计算残差(拉普拉斯金字塔层)L0 = face - face_down_1_up # 原图与down_1_up的差值(保留原图高频细节)L1 = face_down_1 - face_down_2_up # down_1与down_2_up的差值# 显示残差图像(暗部为0,亮部为丢失的细节)cv2.imshow('L0', L0)cv2.imshow('L1', L1)cv2.waitKey(0)# 尝试复原:残差 + 下采样上采样图像fuyuan = face_down_1_up + L0 # down_1_up(模糊图)+ L0(残差)→ 接近原图cv2.imshow('fuyuan', fuyuan)cv2.waitKey(0)
运行效果:残差图像(L0、L1)中,亮区域对应原图中边缘、纹理等高频细节;复原后的图像(fuyuan)与原图几乎一致,证明残差图像成功保留了丢失的信息。
三、核心技术 2:图像直方图分析
图像直方图是反映图像像素灰度分布的工具,通过统计每个灰度值(0-255)的像素数量,可直观了解图像的亮度、对比度等特征。OpenCV 的cv2.calcHist()和 Matplotlib 的plt.hist()均可计算直方图。
3.1 灰度图像直方图(两种方法)
以love.jpg为例,分别用 Matplotlib 和 OpenCV 计算灰度图像的直方图。
方法 1:Matplotlib plt.hist ()
# 读取灰度图像love = cv2.imread('love.jpg', cv2.IMREAD_GRAYSCALE)# 将图像展平为1维数组(ravel():多维数组→1维)a = love.ravel()# 绘制直方图:bins=256(灰度级0-255)plt.hist(a, bins=256)plt.title('Gray Image Histogram (Matplotlib)')plt.xlabel('Gray Level')plt.ylabel('Pixel Count')plt.show()
方法 2:OpenCV cv2.calcHist ()
# 计算直方图:[love](输入图像)、[0](通道索引,灰度图仅1通道)# None(无掩码)、[16](直方图 bins 数量,合并灰度级)、[0,256](灰度范围)love_hist = cv2.calcHist([love], [0], None, [16], [0,256])# 绘制直方图(OpenCV计算结果需用plt.plot())plt.plot(love_hist)plt.title('Gray Image Histogram (OpenCV, bins=16)')plt.xlabel('Binned Gray Level')plt.ylabel('Pixel Count')plt.show()
对比说明:plt.hist()直接对展平后的像素数组统计,bins=256 时可显示每个灰度级的分布;cv2.calcHist()支持指定 bins 数量(如 16),将 256 个灰度级合并为 16 组,直方图更简洁。
3.2 彩色图像直方图
彩色图像包含 B(蓝)、G(绿)、R(红)3 个通道,需分别计算每个通道的直方图并绘制。
代码解析:
# 读取彩色图像(cv2.imread()默认返回BGR格式,非RGB)img = cv2.imread('love.jpg')color = ('b', 'g', 'r') # 对应B、G、R通道# 遍历3个通道,计算并绘制直方图for i, col in enumerate(color):# i:通道索引(0=B,1=G,2=R)hister = cv2.calcHist([img], [i], None, [256], [0,256])plt.plot(hister, color=col) # 按通道颜色绘制plt.title('Color Image Histogram (BGR)')plt.xlabel('Gray Level')plt.ylabel('Pixel Count')plt.show()
注意事项:OpenCV 读取的彩色图像默认是 BGR 格式,而 Matplotlib 显示时默认 RGB 格式,但此处仅绘制直方图,通道顺序不影响统计结果,只需确保color元组与通道索引对应即可。
四、核心技术 3:掩码(mask)与直方图结合
掩码(mask)是一种 “区域选择” 工具,通过创建与图像尺寸相同的二进制数组(0 表示 “排除”,255 表示 “保留”),可仅对图像的指定区域进行处理(如计算直方图、滤波等)。
4.1 创建掩码与区域选择
以phone.png为例,创建掩码并选择手机图像的中间区域:
代码解析:
# 读取手机灰度图像phone = cv2.imread('phone.png', cv2.IMREAD_GRAYSCALE)cv2.imshow('phone', phone)cv2.waitKey(0)# 创建全黑掩码(尺寸与phone相同,uint8类型)mask = np.zeros(phone.shape[:2], np.uint8)# 将掩码的[50:350, 100:470]区域设为255(白色,表示保留该区域)mask[50:350, 100:470] = 255cv2.imshow('mask', mask) # 显示掩码:中间白色区域为选中区域cv2.waitKey(0)
掩码原理:mask的尺寸与原图一致,像素值为 255 的区域会被 “保留”,0 的区域会被 “排除”。通过调整[50:350, 100:470]的坐标,可选择不同的目标区域。
4.2 掩码与图像结合(bitwise_and)
通过cv2.bitwise_and()函数,可将掩码应用到原图,仅保留掩码选中的区域:
# 按掩码提取区域:仅保留mask为255的像素phone_mask = cv2.bitwise_and(phone, phone, mask=mask)cv2.imshow('phone_mask', phone_mask) # 显示:仅中间区域有图像,其余为黑cv2.waitKey(0)
函数作用:cv2.bitwise_and(a, b, mask)对 a 和 b 进行按位与操作,但仅在 mask 为 255 的区域执行,最终实现 “区域裁剪” 效果。
4.3 掩码区域的直方图
仅计算掩码选中区域的直方图,可更精准地分析目标区域的像素分布:
# 计算掩码区域的直方图:mask参数指定仅统计选中区域phone_hist_mask = cv2.calcHist([phone], [0], mask, [256], [0,256])plt.plot(phone_hist_mask)plt.title('Histogram of Masked Area (Phone)')plt.xlabel('Gray Level')plt.ylabel('Pixel Count')plt.show()# 关闭所有OpenCV窗口cv2.destroyAllWindows()
实际意义:若需分析图像中特定物体(如手机屏幕)的亮度分布,通过掩码仅统计目标区域的直方图,可避免背景像素的干扰,结果更准确。
五、总结与拓展
本文通过一段实战代码,覆盖了 OpenCV 中 3 个核心图像处理技术:
- 图像金字塔:下采样(缩小 + 平滑)、上采样(放大 + 填充),以及拉普拉斯金字塔(提取残差,用于复原);
- 直方图分析:灰度图 / 彩色图的直方图计算,两种工具(Matplotlib/OpenCV)的对比;
- 掩码操作:通过二进制掩码选择区域,结合直方图实现精准的区域分析。
希望本文能帮助大家快速上手这些实用技术,后续可结合具体场景(如人脸识别、目标检测)进一步深化应用!